Commit 655abd52 authored by earthdok@chromium.org's avatar earthdok@chromium.org

Add sandbox support for AsanCoverage.

Support dumping coverage data generated by AsanCoverage from sandboxed processes. For the GPU process, we simply pre-open a file before engaging the sandbox. For the renderers, we use a helper process which we fork from the zygote. The helper process collects coverage data from renderers over a socket and writes it to a file. This allows an arbitrary number of renderers to share one output file.

With this change, GPU and renderers will write coverage data to .sancov.packed files which may contain data from multiple modules/processes. Previously, we created one .sancov file per process per module (and still do so for other processes).

Note that the new behavior takes effect regardless of whether the sandbox is actually enabled.

BUG=336212
R=jln@chromium.org
TBR=kbr@chromium.org

Review URL: https://codereview.chromium.org/280303002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@274248 0039d316-1c4b-4281-b951-d872f2087c98
parent 6f5cd62d
......@@ -34,8 +34,8 @@
#include "sandbox/linux/services/yama.h"
#include "sandbox/linux/suid/client/setuid_sandbox_client.h"
#if (defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
defined(LEAK_SANITIZER))
#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
defined(LEAK_SANITIZER)
#include <sanitizer/common_interface_defs.h>
#endif
......@@ -114,10 +114,16 @@ LinuxSandbox::LinuxSandbox()
pre_initialized_(false),
seccomp_bpf_supported_(false),
yama_is_enforcing_(false),
setuid_sandbox_client_(sandbox::SetuidSandboxClient::Create()) {
setuid_sandbox_client_(sandbox::SetuidSandboxClient::Create())
{
if (setuid_sandbox_client_ == NULL) {
LOG(FATAL) << "Failed to instantiate the setuid sandbox client.";
}
#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
defined(LEAK_SANITIZER)
sanitizer_args_ = make_scoped_ptr(new __sanitizer_sandbox_arguments);
*sanitizer_args_ = {0};
#endif
}
LinuxSandbox::~LinuxSandbox() {
......@@ -132,11 +138,12 @@ LinuxSandbox* LinuxSandbox::GetInstance() {
void LinuxSandbox::PreinitializeSandbox() {
CHECK(!pre_initialized_);
seccomp_bpf_supported_ = false;
#if (defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
defined(LEAK_SANITIZER)) && defined(OS_LINUX)
#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
defined(LEAK_SANITIZER)
// Sanitizers need to open some resources before the sandbox is enabled.
// This should not fork, not launch threads, not open a directory.
__sanitizer_sandbox_on_notify(/*args*/ NULL);
__sanitizer_sandbox_on_notify(sanitizer_args());
sanitizer_args_.reset();
#endif
#if !defined(NDEBUG)
......
......@@ -11,6 +11,11 @@
#include "base/memory/scoped_ptr.h"
#include "content/public/common/sandbox_linux.h"
#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
defined(LEAK_SANITIZER)
#include <sanitizer/common_interface_defs.h>
#endif
template <typename T> struct DefaultSingletonTraits;
namespace base {
class Thread;
......@@ -82,6 +87,13 @@ class LinuxSandbox {
// to make some vulnerabilities harder to exploit.
bool LimitAddressSpace(const std::string& process_type);
#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
defined(LEAK_SANITIZER)
__sanitizer_sandbox_arguments* sanitizer_args() const {
return sanitizer_args_.get();
};
#endif
private:
friend struct DefaultSingletonTraits<LinuxSandbox>;
......@@ -120,6 +132,10 @@ class LinuxSandbox {
bool seccomp_bpf_supported_; // Accurate if pre_initialized_.
bool yama_is_enforcing_; // Accurate if pre_initialized_.
scoped_ptr<sandbox::SetuidSandboxClient> setuid_sandbox_client_;
#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
defined(LEAK_SANITIZER)
scoped_ptr<__sanitizer_sandbox_arguments> sanitizer_args_;
#endif
DISALLOW_COPY_AND_ASSIGN(LinuxSandbox);
};
......
......@@ -56,6 +56,10 @@
#include "base/message_loop/message_pump_mac.h"
#endif
#if defined(ADDRESS_SANITIZER)
#include <sanitizer/asan_interface.h>
#endif
const int kGpuTimeout = 10000;
namespace content {
......@@ -444,6 +448,14 @@ bool StartSandboxLinux(const gpu::GPUInfo& gpu_info,
// has really been stopped.
LinuxSandbox::StopThread(watchdog_thread);
}
#if defined(ADDRESS_SANITIZER)
LinuxSandbox* linux_sandbox = LinuxSandbox::GetInstance();
linux_sandbox->sanitizer_args()->coverage_sandboxed = 1;
linux_sandbox->sanitizer_args()->coverage_fd = -1;
linux_sandbox->sanitizer_args()->coverage_max_block_size = 0;
#endif
// LinuxSandbox::InitializeSandbox() must always be called
// with only one thread.
res = LinuxSandbox::InitializeSandbox();
......
......@@ -33,6 +33,10 @@
#include "ipc/ipc_channel.h"
#include "ipc/ipc_switches.h"
#if defined(ADDRESS_SANITIZER)
#include <sanitizer/asan_interface.h>
#endif
// See http://code.google.com/p/chromium/wiki/LinuxZygote
namespace content {
......@@ -77,11 +81,14 @@ void KillAndReap(pid_t pid, ZygoteForkDelegate* helper) {
} // namespace
Zygote::Zygote(int sandbox_flags, ScopedVector<ZygoteForkDelegate> helpers)
Zygote::Zygote(int sandbox_flags, ScopedVector<ZygoteForkDelegate> helpers,
const std::vector<base::ProcessHandle>& extra_children,
const std::vector<int>& extra_fds)
: sandbox_flags_(sandbox_flags),
helpers_(helpers.Pass()),
initial_uma_index_(0) {
}
initial_uma_index_(0),
extra_children_(extra_children),
extra_fds_(extra_fds) {}
Zygote::~Zygote() {
}
......@@ -147,6 +154,22 @@ bool Zygote::HandleRequestFromBrowser(int fd) {
if (len == 0 || (len == -1 && errno == ECONNRESET)) {
// EOF from the browser. We should die.
// TODO(earthdok): call __sanititizer_cov_dump() here to obtain code
// coverage for the Zygote. Currently it's not possible because of
// confusion over who is responsible for closing the file descriptor.
for (std::vector<int>::iterator it = extra_fds_.begin();
it < extra_fds_.end(); ++it) {
PCHECK(0 == IGNORE_EINTR(close(*it)));
}
#if !defined(ADDRESS_SANITIZER)
// TODO(earthdok): add watchdog thread before using this in non-ASAN builds.
CHECK(extra_children_.empty());
#endif
for (std::vector<base::ProcessHandle>::iterator it =
extra_children_.begin();
it < extra_children_.end(); ++it) {
PCHECK(*it == HANDLE_EINTR(waitpid(*it, NULL, 0)));
}
_exit(0);
return false;
}
......
......@@ -28,7 +28,9 @@ class ZygoteForkDelegate;
// runs it.
class Zygote {
public:
Zygote(int sandbox_flags, ScopedVector<ZygoteForkDelegate> helpers);
Zygote(int sandbox_flags, ScopedVector<ZygoteForkDelegate> helpers,
const std::vector<base::ProcessHandle>& extra_children,
const std::vector<int>& extra_fds);
~Zygote();
bool ProcessRequests();
......@@ -123,6 +125,17 @@ class Zygote {
// Count of how many fork delegates for which we've invoked InitialUMA().
size_t initial_uma_index_;
// This vector contains the PIDs of any child processes which have been
// created prior to the construction of the Zygote object, and must be reaped
// before the Zygote exits. The Zygote will perform a blocking wait on these
// children, so they must be guaranteed to be exiting by the time the Zygote
// exits.
std::vector<base::ProcessHandle> extra_children_;
// This vector contains the FDs that must be closed before reaping the extra
// children.
std::vector<int> extra_fds_;
};
} // namespace content
......
......@@ -5,8 +5,10 @@
#include "content/zygote/zygote_main.h"
#include <dlfcn.h>
#include <fcntl.h>
#include <pthread.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
......@@ -17,6 +19,7 @@
#include "base/memory/scoped_vector.h"
#include "base/native_library.h"
#include "base/pickle.h"
#include "base/posix/eintr_wrapper.h"
#include "base/posix/unix_domain_socket_linux.h"
#include "base/rand_util.h"
#include "base/sys_info.h"
......@@ -47,6 +50,10 @@
#include "third_party/libjingle/overrides/init_webrtc.h"
#endif
#if defined(ADDRESS_SANITIZER)
#include <sanitizer/asan_interface.h>
#endif
namespace content {
// See http://code.google.com/p/chromium/wiki/LinuxZygote
......@@ -323,10 +330,11 @@ static void ZygotePreSandboxInit() {
new FontConfigIPC(GetSandboxFD()))->unref();
}
static bool CreateInitProcessReaper() {
static bool CreateInitProcessReaper(base::Closure* post_fork_parent_callback) {
// The current process becomes init(1), this function returns from a
// newly created process.
const bool init_created = sandbox::CreateInitProcessReaper(NULL);
const bool init_created =
sandbox::CreateInitProcessReaper(post_fork_parent_callback);
if (!init_created) {
LOG(ERROR) << "Error creating an init process to reap zombies";
return false;
......@@ -336,7 +344,8 @@ static bool CreateInitProcessReaper() {
// Enter the setuid sandbox. This requires the current process to have been
// created through the setuid sandbox.
static bool EnterSuidSandbox(sandbox::SetuidSandboxClient* setuid_sandbox) {
static bool EnterSuidSandbox(sandbox::SetuidSandboxClient* setuid_sandbox,
base::Closure* post_fork_parent_callback) {
DCHECK(setuid_sandbox);
DCHECK(setuid_sandbox->IsSuidSandboxChild());
......@@ -364,7 +373,7 @@ static bool EnterSuidSandbox(sandbox::SetuidSandboxClient* setuid_sandbox) {
if (getpid() == 1) {
// The setuid sandbox has created a new PID namespace and we need
// to assume the role of init.
CHECK(CreateInitProcessReaper());
CHECK(CreateInitProcessReaper(post_fork_parent_callback));
}
#if !defined(OS_OPENBSD)
......@@ -398,10 +407,67 @@ static bool EnterSuidSandbox(sandbox::SetuidSandboxClient* setuid_sandbox) {
return true;
}
#if defined(ADDRESS_SANITIZER)
const size_t kSanitizerMaxMessageLength = 1 * 1024 * 1024;
// A helper process which collects code coverage data from the renderers over a
// socket and dumps it to a file. See http://crbug.com/336212 for discussion.
static void SanitizerCoverageHelper(int socket_fd, int file_fd) {
scoped_ptr<char[]> buffer(new char[kSanitizerMaxMessageLength]);
while (true) {
ssize_t received_size = HANDLE_EINTR(
recv(socket_fd, buffer.get(), kSanitizerMaxMessageLength, 0));
PCHECK(received_size >= 0);
if (received_size == 0)
// All clients have closed the socket. We should die.
_exit(0);
PCHECK(file_fd >= 0);
ssize_t written_size = 0;
while (written_size < received_size) {
ssize_t write_res =
HANDLE_EINTR(write(file_fd, buffer.get() + written_size,
received_size - written_size));
PCHECK(write_res >= 0);
written_size += write_res;
}
PCHECK(0 == HANDLE_EINTR(fsync(file_fd)));
}
}
// fds[0] is the read end, fds[1] is the write end.
static void CreateSanitizerCoverageSocketPair(int fds[2]) {
PCHECK(0 == socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds));
PCHECK(0 == shutdown(fds[0], SHUT_WR));
PCHECK(0 == shutdown(fds[1], SHUT_RD));
}
static pid_t ForkSanitizerCoverageHelper(int child_fd, int parent_fd,
base::ScopedFD file_fd) {
pid_t pid = fork();
PCHECK(pid >= 0);
if (pid == 0) {
// In the child.
PCHECK(0 == IGNORE_EINTR(close(parent_fd)));
SanitizerCoverageHelper(child_fd, file_fd.get());
_exit(0);
} else {
// In the parent.
PCHECK(0 == IGNORE_EINTR(close(child_fd)));
return pid;
}
}
void CloseFdPair(const int fds[2]) {
PCHECK(0 == IGNORE_EINTR(close(fds[0])));
PCHECK(0 == IGNORE_EINTR(close(fds[1])));
}
#endif // defined(ADDRESS_SANITIZER)
// If |is_suid_sandbox_child|, then make sure that the setuid sandbox is
// engaged.
static void EnterLayerOneSandbox(LinuxSandbox* linux_sandbox,
bool is_suid_sandbox_child) {
bool is_suid_sandbox_child,
base::Closure* post_fork_parent_callback) {
DCHECK(linux_sandbox);
ZygotePreSandboxInit();
......@@ -415,7 +481,8 @@ static void EnterLayerOneSandbox(LinuxSandbox* linux_sandbox,
linux_sandbox->setuid_sandbox_client();
if (is_suid_sandbox_child) {
CHECK(EnterSuidSandbox(setuid_sandbox)) << "Failed to enter setuid sandbox";
CHECK(EnterSuidSandbox(setuid_sandbox, post_fork_parent_callback))
<< "Failed to enter setuid sandbox";
}
}
......@@ -424,7 +491,26 @@ bool ZygoteMain(const MainFunctionParams& params,
g_am_zygote_or_renderer = true;
sandbox::InitLibcUrandomOverrides();
base::Closure *post_fork_parent_callback = NULL;
LinuxSandbox* linux_sandbox = LinuxSandbox::GetInstance();
#if defined(ADDRESS_SANITIZER)
base::ScopedFD sancov_file_fd(__sanitizer_maybe_open_cov_file("zygote"));
int sancov_socket_fds[2] = {-1, -1};
CreateSanitizerCoverageSocketPair(sancov_socket_fds);
linux_sandbox->sanitizer_args()->coverage_sandboxed = 1;
linux_sandbox->sanitizer_args()->coverage_fd = sancov_socket_fds[1];
linux_sandbox->sanitizer_args()->coverage_max_block_size =
kSanitizerMaxMessageLength;
// Zygote termination will block until the helper process exits, which will
// not happen until the write end of the socket is closed everywhere. Make
// sure the init process does not hold on to it.
base::Closure close_sancov_socket_fds =
base::Bind(&CloseFdPair, sancov_socket_fds);
post_fork_parent_callback = &close_sancov_socket_fds;
#endif
// This will pre-initialize the various sandboxes that need it.
linux_sandbox->PreinitializeSandbox();
......@@ -449,13 +535,32 @@ bool ZygoteMain(const MainFunctionParams& params,
}
// Turn on the first layer of the sandbox if the configuration warrants it.
EnterLayerOneSandbox(linux_sandbox, must_enable_setuid_sandbox);
EnterLayerOneSandbox(linux_sandbox, must_enable_setuid_sandbox,
post_fork_parent_callback);
std::vector<pid_t> extra_children;
std::vector<int> extra_fds;
#if defined(ADDRESS_SANITIZER)
pid_t sancov_helper_pid = ForkSanitizerCoverageHelper(
sancov_socket_fds[0], sancov_socket_fds[1], sancov_file_fd.Pass());
// It's important that the zygote reaps the helper before dying. Otherwise,
// the destruction of the PID namespace could kill the helper before it
// completes its I/O tasks. |sancov_helper_pid| will exit once the last
// renderer holding the write end of |sancov_socket_fds| closes it.
extra_children.push_back(sancov_helper_pid);
// Sanitizer code in the renderers will inherit the write end of the socket
// from the zygote. We must keep it open until the very end of the zygote's
// lifetime, even though we don't explicitly use it.
extra_fds.push_back(sancov_socket_fds[1]);
#endif
int sandbox_flags = linux_sandbox->GetStatus();
bool setuid_sandbox_engaged = sandbox_flags & kSandboxLinuxSUID;
CHECK_EQ(must_enable_setuid_sandbox, setuid_sandbox_engaged);
Zygote zygote(sandbox_flags, fork_delegates.Pass());
Zygote zygote(sandbox_flags, fork_delegates.Pass(), extra_children,
extra_fds);
// This function call can return multiple times, once per fork().
return zygote.ProcessRequests();
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment