crashpad/util/linux/ptrace_broker_test.cc
Joshua Peraza 0924e56751 linux: Add PtraceBroker and PtraceClient
A PtraceBroker/Client pair implement a PtraceConnection over a socket.
The broker runs in a process with `ptrace` capabilities for the target
process and serves requests for the client over a socket.

Bug: crashpad:30
Change-Id: Ied19bcedf84b46c8f68440fd1c284b2126470e5e
Reviewed-on: https://chromium-review.googlesource.com/780397
Commit-Queue: Joshua Peraza <jperaza@chromium.org>
Reviewed-by: Mark Mentovai <mark@chromium.org>
2017-12-11 18:35:24 +00:00

223 lines
6.1 KiB
C++

// Copyright 2017 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "util/linux/ptrace_broker.h"
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#include <utility>
#include "build/build_config.h"
#include "gtest/gtest.h"
#include "test/linux/get_tls.h"
#include "test/multiprocess.h"
#include "util/file/file_io.h"
#include "util/linux/ptrace_client.h"
#include "util/posix/scoped_mmap.h"
#include "util/synchronization/semaphore.h"
#include "util/thread/thread.h"
namespace crashpad {
namespace test {
namespace {
class ScopedTimeoutThread : public Thread {
public:
ScopedTimeoutThread() : join_sem_(0) {}
~ScopedTimeoutThread() { EXPECT_TRUE(JoinWithTimeout(5.0)); }
protected:
void ThreadMain() override { join_sem_.Signal(); }
private:
bool JoinWithTimeout(double timeout) {
if (!join_sem_.TimedWait(timeout)) {
return false;
}
Join();
return true;
}
Semaphore join_sem_;
DISALLOW_COPY_AND_ASSIGN(ScopedTimeoutThread);
};
class RunBrokerThread : public ScopedTimeoutThread {
public:
RunBrokerThread(PtraceBroker* broker)
: ScopedTimeoutThread(), broker_(broker) {}
~RunBrokerThread() {}
private:
void ThreadMain() override {
EXPECT_EQ(broker_->Run(), 0);
ScopedTimeoutThread::ThreadMain();
}
PtraceBroker* broker_;
DISALLOW_COPY_AND_ASSIGN(RunBrokerThread);
};
class BlockOnReadThread : public ScopedTimeoutThread {
public:
BlockOnReadThread(int readfd, int writefd)
: ScopedTimeoutThread(), readfd_(readfd), writefd_(writefd) {}
~BlockOnReadThread() {}
private:
void ThreadMain() override {
pid_t pid = syscall(SYS_gettid);
LoggingWriteFile(writefd_, &pid, sizeof(pid));
LinuxVMAddress tls = GetTLS();
LoggingWriteFile(writefd_, &tls, sizeof(tls));
CheckedReadFileAtEOF(readfd_);
ScopedTimeoutThread::ThreadMain();
}
int readfd_;
int writefd_;
DISALLOW_COPY_AND_ASSIGN(BlockOnReadThread);
};
class SameBitnessTest : public Multiprocess {
public:
SameBitnessTest() : Multiprocess(), mapping_() {}
~SameBitnessTest() {}
protected:
void PreFork() override {
ASSERT_NO_FATAL_FAILURE(Multiprocess::PreFork());
size_t page_size = getpagesize();
ASSERT_TRUE(mapping_.ResetMmap(nullptr,
page_size * 3,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON,
-1,
0));
ASSERT_TRUE(mapping_.ResetAddrLen(mapping_.addr(), page_size * 2));
auto buffer = mapping_.addr_as<char*>();
for (size_t index = 0; index < mapping_.len(); ++index) {
buffer[index] = index % 256;
}
}
private:
void MultiprocessParent() override {
LinuxVMAddress child1_tls;
ASSERT_TRUE(LoggingReadFileExactly(
ReadPipeHandle(), &child1_tls, sizeof(child1_tls)));
pid_t child2_tid;
ASSERT_TRUE(LoggingReadFileExactly(
ReadPipeHandle(), &child2_tid, sizeof(child2_tid)));
LinuxVMAddress child2_tls;
ASSERT_TRUE(LoggingReadFileExactly(
ReadPipeHandle(), &child2_tls, sizeof(child2_tls)));
int socks[2];
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, socks), 0);
ScopedFileHandle broker_sock(socks[0]);
ScopedFileHandle client_sock(socks[1]);
#if defined(ARCH_CPU_64_BITS)
constexpr bool am_64_bit = true;
#else
constexpr bool am_64_bit = false;
#endif // ARCH_CPU_64_BITS
PtraceBroker broker(broker_sock.get(), am_64_bit);
RunBrokerThread broker_thread(&broker);
broker_thread.Start();
{
PtraceClient client;
ASSERT_TRUE(client.Initialize(client_sock.get(), ChildPID()));
EXPECT_EQ(client.GetProcessID(), ChildPID());
EXPECT_TRUE(client.Attach(child2_tid));
EXPECT_EQ(client.Is64Bit(), am_64_bit);
ThreadInfo info1;
ASSERT_TRUE(client.GetThreadInfo(ChildPID(), &info1));
EXPECT_EQ(info1.thread_specific_data_address, child1_tls);
ThreadInfo info2;
ASSERT_TRUE(client.GetThreadInfo(child2_tid, &info2));
EXPECT_EQ(info2.thread_specific_data_address, child2_tls);
auto buffer = std::make_unique<char[]>(mapping_.len());
ASSERT_TRUE(client.Read(
mapping_.addr_as<VMAddress>(), mapping_.len(), buffer.get()));
auto expected_buffer = mapping_.addr_as<char*>();
for (size_t index = 0; index < mapping_.len(); ++index) {
EXPECT_EQ(buffer[index], expected_buffer[index]);
}
char first;
ASSERT_TRUE(
client.Read(mapping_.addr_as<VMAddress>(), sizeof(first), &first));
EXPECT_EQ(first, expected_buffer[0]);
char last;
ASSERT_TRUE(
client.Read(mapping_.addr_as<VMAddress>() + mapping_.len() - 1,
sizeof(last),
&last));
EXPECT_EQ(last, expected_buffer[mapping_.len() - 1]);
char unmapped;
EXPECT_FALSE(client.Read(mapping_.addr_as<VMAddress>() + mapping_.len(),
sizeof(unmapped),
&unmapped));
}
}
void MultiprocessChild() override {
LinuxVMAddress tls = GetTLS();
ASSERT_TRUE(LoggingWriteFile(WritePipeHandle(), &tls, sizeof(tls)));
BlockOnReadThread thread(ReadPipeHandle(), WritePipeHandle());
thread.Start();
CheckedReadFileAtEOF(ReadPipeHandle());
}
ScopedMmap mapping_;
DISALLOW_COPY_AND_ASSIGN(SameBitnessTest);
};
TEST(PtraceBroker, SameBitness) {
SameBitnessTest test;
test.Run();
}
// TODO(jperaza): Test against a process with different bitness.
} // namespace
} // namespace test
} // namespace crashpad