Bug 1012951 - Add Linux sandboxing for GeckoMediaPlugin processes. r=kang r=ted

--HG--
extra : rebase_source : 1b890000d5b8d2a8954cdd1118a1023eba829c29
This commit is contained in:
Jed Davis 2014-08-04 15:11:18 -07:00
parent 2d6844764c
commit 4d6ff2569b
8 changed files with 330 additions and 75 deletions

View File

@ -3843,6 +3843,7 @@ MOZ_PAY=
MOZ_AUDIO_CHANNEL_MANAGER=
NSS_NO_LIBPKIX=
MOZ_CONTENT_SANDBOX=
MOZ_GMP_SANDBOX=
JSGC_USE_EXACT_ROOTING=1
JSGC_GENERATIONAL=
@ -6394,6 +6395,28 @@ fi
AC_SUBST(MOZ_CONTENT_SANDBOX)
dnl ========================================================
dnl = Gecko Media Plugin sandboxing
dnl ========================================================
case $OS_TARGET in
WINNT)
MOZ_GMP_SANDBOX=1
;;
Linux)
case $CPU_ARCH in
x86_64|x86)
MOZ_GMP_SANDBOX=1
;;
esac
;;
esac
if test -n "$MOZ_GMP_SANDBOX"; then
AC_DEFINE(MOZ_GMP_SANDBOX)
fi
AC_SUBST(MOZ_GMP_SANDBOX)
dnl ========================================================
dnl =
dnl = Module specific options

View File

@ -27,6 +27,8 @@ using mozilla::dom::CrashReporterChild;
#if defined(XP_WIN)
#define TARGET_SANDBOX_EXPORTS
#include "mozilla/sandboxTarget.h"
#elif defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
#include "mozilla/Sandbox.h"
#endif
namespace mozilla {
@ -99,6 +101,13 @@ GMPChild::LoadPluginLibrary(const std::string& aPluginPath)
nsAutoCString nativePath;
libFile->GetNativePath(nativePath);
#if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
// Enable sandboxing here -- we know the plugin file's path, but
// this process's execution hasn't been affected by its content yet.
mozilla::SetMediaPluginSandbox(nativePath.get());
#endif
mLib = PR_LoadLibrary(nativePath.get());
if (!mLib) {
return false;

View File

@ -918,7 +918,7 @@ ContentChild::RecvSetProcessSandbox()
// at some point; see bug 880808.
#if defined(MOZ_CONTENT_SANDBOX)
#if defined(XP_LINUX)
SetCurrentProcessSandbox();
SetContentProcessSandbox();
#elif defined(XP_WIN)
mozilla::SandboxTarget::Instance()->StartSandbox();
#endif

View File

@ -19,12 +19,12 @@
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>
#include <fcntl.h>
#include "mozilla/Atomics.h"
#include "mozilla/NullPtr.h"
#include "mozilla/unused.h"
#include "mozilla/dom/Exceptions.h"
#include "nsString.h"
#include "nsThreadUtils.h"
#include "prenv.h"
@ -37,10 +37,8 @@
#include <android/log.h>
#endif
#if defined(MOZ_CONTENT_SANDBOX)
#include "linux_seccomp.h"
#include "SandboxFilter.h"
#endif
// See definition of SandboxDie, below.
#include "sandbox/linux/seccomp-bpf/die.h"
@ -52,6 +50,14 @@ namespace mozilla {
#define LOG_ERROR(fmt, args...) fprintf(stderr, "Sandbox: " fmt, ## args)
#endif
#ifdef MOZ_GMP_SANDBOX
// For media plugins, we can start the sandbox before we dlopen the
// module, so we have to pre-open the file and simulate the sandboxed
// open().
static int gMediaPluginFileDesc = -1;
static const char *gMediaPluginFilePath;
#endif
/**
* Log JS stack info in the same place as the sandbox violation
* message. Useful in case the responsible code is JS and all we have
@ -129,6 +135,27 @@ Reporter(int nr, siginfo_t *info, void *void_context)
args[4] = SECCOMP_PARM5(ctx);
args[5] = SECCOMP_PARM6(ctx);
#ifdef MOZ_GMP_SANDBOX
if (syscall_nr == __NR_open && gMediaPluginFilePath) {
const char *path = reinterpret_cast<const char*>(args[0]);
int flags = int(args[1]);
if ((flags & O_ACCMODE) != O_RDONLY) {
LOG_ERROR("non-read-only open of file %s attempted (flags=0%o)",
path, flags);
} else if (strcmp(path, gMediaPluginFilePath) != 0) {
LOG_ERROR("attempt to open file %s which is not the media plugin %s",
path, gMediaPluginFilePath);
} else if (gMediaPluginFileDesc == -1) {
LOG_ERROR("multiple opens of media plugin file unimplemented");
} else {
SECCOMP_RESULT(ctx) = gMediaPluginFileDesc;
gMediaPluginFileDesc = -1;
return;
}
}
#endif
LOG_ERROR("seccomp sandbox violation: pid %d, syscall %lu, args %lu %lu %lu"
" %lu %lu %lu. Killing process.", pid, syscall_nr,
args[0], args[1], args[2], args[3], args[4], args[5]);
@ -246,8 +273,7 @@ SetThreadSandbox()
{
bool didAnything = false;
if (PR_GetEnv("MOZ_DISABLE_CONTENT_SANDBOX") == nullptr &&
prctl(PR_GET_SECCOMP, 0, 0, 0, 0) == 0) {
if (prctl(PR_GET_SECCOMP, 0, 0, 0, 0) == 0) {
if (InstallSyscallFilter(sSetSandboxFilter) == 0) {
didAnything = true;
}
@ -277,19 +303,19 @@ SetThreadSandboxHandler(int signum)
}
static void
BroadcastSetThreadSandbox()
BroadcastSetThreadSandbox(SandboxType aType)
{
int signum;
pid_t pid, tid;
pid_t pid, tid, myTid;
DIR *taskdp;
struct dirent *de;
SandboxFilter filter(&sSetSandboxFilter,
PR_GetEnv("MOZ_CONTENT_SANDBOX_VERBOSE"));
SandboxFilter filter(&sSetSandboxFilter, aType,
PR_GetEnv("MOZ_SANDBOX_VERBOSE"));
static_assert(sizeof(mozilla::Atomic<int>) == sizeof(int),
"mozilla::Atomic<int> isn't represented by an int");
MOZ_ASSERT(NS_IsMainThread());
pid = getpid();
myTid = syscall(__NR_gettid);
taskdp = opendir("/proc/self/task");
if (taskdp == nullptr) {
LOG_ERROR("opendir /proc/self/task: %s\n", strerror(errno));
@ -322,9 +348,9 @@ BroadcastSetThreadSandbox()
// Not a task ID.
continue;
}
if (tid == pid) {
// Drop the main thread's privileges last, below, so
// we can continue to signal other threads.
if (tid == myTid) {
// Drop this thread's privileges last, below, so we can
// continue to signal other threads.
continue;
}
// Reset the futex cell and signal.
@ -417,24 +443,69 @@ IsSandboxingSupported(void)
return prctl(PR_GET_SECCOMP) != -1;
}
/**
* Starts the seccomp sandbox for this process and sets user/group-based privileges.
* Should be called only once, and before any potentially harmful content is loaded.
*
* Should normally make the process exit on failure.
*/
void
SetCurrentProcessSandbox()
// Common code for sandbox startup.
static void
SetCurrentProcessSandbox(SandboxType aType)
{
if (InstallSyscallReporter()) {
LOG_ERROR("install_syscall_reporter() failed\n");
}
if (IsSandboxingSupported()) {
BroadcastSetThreadSandbox();
BroadcastSetThreadSandbox(aType);
}
}
#ifdef MOZ_CONTENT_SANDBOX
/**
* Starts the seccomp sandbox for a content process. Should be called
* only once, and before any potentially harmful content is loaded.
*
* Will normally make the process exit on failure.
*/
void
SetContentProcessSandbox()
{
if (PR_GetEnv("MOZ_DISABLE_CONTENT_SANDBOX")) {
return;
}
SetCurrentProcessSandbox(kSandboxContentProcess);
}
#endif // MOZ_CONTENT_SANDBOX
#ifdef MOZ_GMP_SANDBOX
/**
* Starts the seccomp sandbox for a media plugin process. Should be
* called only once, and before any potentially harmful content is
* loaded -- including the plugin itself, if it's considered untrusted.
*
* The file indicated by aFilePath, if non-null, can be open()ed once
* read-only after the sandbox starts; it should be the .so file
* implementing the not-yet-loaded plugin.
*
* Will normally make the process exit on failure.
*/
void
SetMediaPluginSandbox(const char *aFilePath)
{
if (PR_GetEnv("MOZ_DISABLE_GMP_SANDBOX")) {
return;
}
if (aFilePath) {
gMediaPluginFilePath = strdup(aFilePath);
gMediaPluginFileDesc = open(aFilePath, O_RDONLY | O_CLOEXEC);
if (gMediaPluginFileDesc == -1) {
LOG_ERROR("failed to open plugin file %s: %s", aFilePath, strerror(errno));
MOZ_CRASH();
}
}
// Finally, start the sandbox.
SetCurrentProcessSandbox(kSandboxMediaPlugin);
}
#endif // MOZ_GMP_SANDBOX
} // namespace mozilla

View File

@ -9,7 +9,12 @@
namespace mozilla {
void SetCurrentProcessSandbox();
#ifdef MOZ_CONTENT_SANDBOX
void SetContentProcessSandbox();
#endif
#ifdef MOZ_GMP_SANDBOX
void SetMediaPluginSandbox(const char *aFilePath);
#endif
} // namespace mozilla

View File

@ -14,49 +14,26 @@
#include "mozilla/NullPtr.h"
#include <errno.h>
#include <unistd.h>
#include <linux/net.h>
#include <linux/ipc.h>
#include <linux/net.h>
#include <linux/prctl.h>
#include <linux/sched.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
namespace mozilla {
class SandboxFilterImpl : public SandboxAssembler
{
void Build();
public:
SandboxFilterImpl() {
Build();
Finish();
}
virtual void Build() = 0;
virtual ~SandboxFilterImpl() { }
};
SandboxFilter::SandboxFilter(const sock_fprog** aStored, bool aVerbose)
: mStored(aStored)
{
MOZ_ASSERT(*mStored == nullptr);
std::vector<struct sock_filter> filterVec;
{
SandboxFilterImpl impl;
impl.Compile(&filterVec, aVerbose);
}
mProg = new sock_fprog;
mProg->len = filterVec.size();
mProg->filter = mFilter = new sock_filter[mProg->len];
for (size_t i = 0; i < mProg->len; ++i) {
mFilter[i] = filterVec[i];
}
*mStored = mProg;
}
// Some helper macros to make the code that builds the filter more
// readable, and to help deal with differences among architectures.
SandboxFilter::~SandboxFilter()
{
*mStored = nullptr;
delete[] mFilter;
delete mProg;
}
void
SandboxFilterImpl::Build() {
#define SYSCALL_EXISTS(name) (defined(__NR_##name))
#define SYSCALL(name) (Condition(__NR_##name))
@ -69,32 +46,40 @@ SandboxFilterImpl::Build() {
Condition(__NR_##name, arg, argValues); \
})
// Some architectures went through a transition from 32-bit to
// 64-bit off_t and had to version all the syscalls that referenced
// it; others (newer and/or 64-bit ones) didn't. Adjust the
// conditional as needed.
// Some architectures went through a transition from 32-bit to
// 64-bit off_t and had to version all the syscalls that referenced
// it; others (newer and/or 64-bit ones) didn't. Adjust the
// conditional as needed.
#if SYSCALL_EXISTS(stat64)
#define SYSCALL_LARGEFILE(plain, versioned) SYSCALL(versioned)
#else
#define SYSCALL_LARGEFILE(plain, versioned) SYSCALL(plain)
#endif
// i386 multiplexes all the socket-related interfaces into a single
// syscall.
// i386 multiplexes all the socket-related interfaces into a single
// syscall.
#if SYSCALL_EXISTS(socketcall)
#define SOCKETCALL(name, NAME) SYSCALL_WITH_ARG(socketcall, 0, SYS_##NAME)
#else
#define SOCKETCALL(name, NAME) SYSCALL(name)
#endif
// i386 multiplexes all the SysV-IPC-related interfaces into a single
// syscall.
// i386 multiplexes all the SysV-IPC-related interfaces into a single
// syscall.
#if SYSCALL_EXISTS(ipc)
#define SYSVIPCCALL(name, NAME) SYSCALL_WITH_ARG(ipc, 0, NAME)
#else
#define SYSVIPCCALL(name, NAME) SYSCALL(name)
#endif
#ifdef MOZ_CONTENT_SANDBOX
class SandboxFilterImplContent : public SandboxFilterImpl {
protected:
virtual void Build() MOZ_OVERRIDE;
};
void
SandboxFilterImplContent::Build() {
/* Most used system calls should be at the top of the whitelist
* for performance reasons. The whitelist BPF filter exits after
* processing any ALLOW_SYSCALL macro.
@ -329,5 +314,159 @@ SandboxFilterImpl::Build() {
Allow(SYSCALL(exit_group));
Allow(SYSCALL(exit));
}
#endif // MOZ_CONTENT_SANDBOX
#ifdef MOZ_GMP_SANDBOX
class SandboxFilterImplGMP : public SandboxFilterImpl {
protected:
virtual void Build() MOZ_OVERRIDE;
};
void SandboxFilterImplGMP::Build() {
// As for content processes, check the most common syscalls first.
Allow(SYSCALL_WITH_ARG(clock_gettime, 0, CLOCK_MONOTONIC, CLOCK_REALTIME));
Allow(SYSCALL(futex));
Allow(SYSCALL(gettimeofday));
Allow(SYSCALL(poll));
Allow(SYSCALL(write));
Allow(SYSCALL(read));
Allow(SYSCALL(epoll_wait));
Allow(SOCKETCALL(recvmsg, RECVMSG));
Allow(SOCKETCALL(sendmsg, SENDMSG));
Allow(SYSCALL(time));
// Nothing after this line is performance-critical.
#if SYSCALL_EXISTS(mmap2)
Allow(SYSCALL(mmap2));
#else
Allow(SYSCALL(mmap));
#endif
Allow(SYSCALL_LARGEFILE(fstat, fstat64));
Allow(SYSCALL(munmap));
Allow(SYSCALL(getpid));
Allow(SYSCALL(gettid));
// The glibc source hasn't changed the thread creation clone flags
// since 2004, so this *should* be safe to hard-code. Bionic is
// different, but MOZ_GMP_SANDBOX isn't supported there yet.
//
// At minimum we should require CLONE_THREAD, so that a single
// SIGKILL from the parent will destroy all descendant tasks. In
// general, pinning down as much of the flags word as possible is a
// good idea, because it exposes a lot of subtle (and probably not
// well tested in all cases) kernel functionality.
//
// WARNING: s390 and cris pass the flags in a different arg -- see
// CLONE_BACKWARDS2 in arch/Kconfig in the kernel source -- but we
// don't support seccomp-bpf on those archs yet.
static const int new_thread_flags = CLONE_VM | CLONE_FS | CLONE_FILES |
CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM | CLONE_SETTLS |
CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID;
Allow(SYSCALL_WITH_ARG(clone, 0, new_thread_flags));
Allow(SYSCALL_WITH_ARG(prctl, 0, PR_GET_SECCOMP, PR_SET_NAME));
#if SYSCALL_EXISTS(set_robust_list)
Allow(SYSCALL(set_robust_list));
#endif
// NSPR can call this when creating a thread, but it will accept a
// polite "no".
Deny(EACCES, SYSCALL(getpriority));
// Stack bounds are obtained via pthread_getattr_np, which calls
// this but doesn't actually need it:
Deny(ENOSYS, SYSCALL(sched_getaffinity));
#ifdef MOZ_ASAN
Allow(SYSCALL(sigaltstack));
#endif
Allow(SYSCALL(mprotect));
Allow(SYSCALL_WITH_ARG(madvise, 2, MADV_DONTNEED));
#if SYSCALL_EXISTS(sigreturn)
Allow(SYSCALL(sigreturn));
#endif
Allow(SYSCALL(rt_sigreturn));
Allow(SYSCALL(restart_syscall));
Allow(SYSCALL(close));
// "Sleeping for 300 seconds" in debug crashes; possibly other uses.
Allow(SYSCALL(nanosleep));
// For the crash reporter:
#if SYSCALL_EXISTS(sigprocmask)
Allow(SYSCALL(sigprocmask));
#endif
Allow(SYSCALL(rt_sigprocmask));
#if SYSCALL_EXISTS(sigaction)
Allow(SYSCALL(sigaction));
#endif
Allow(SYSCALL(rt_sigaction));
Allow(SOCKETCALL(socketpair, SOCKETPAIR));
Allow(SYSCALL_WITH_ARG(tgkill, 0, uint32_t(getpid())));
Allow(SYSCALL_WITH_ARG(prctl, 0, PR_SET_DUMPABLE));
// Note for when GMP is supported on an ARM platform: Add whichever
// of the ARM-specific syscalls are needed for this type of process.
Allow(SYSCALL(epoll_ctl));
Allow(SYSCALL(exit));
Allow(SYSCALL(exit_group));
}
#endif // MOZ_GMP_SANDBOX
SandboxFilter::SandboxFilter(const sock_fprog** aStored, SandboxType aType,
bool aVerbose)
: mStored(aStored)
{
MOZ_ASSERT(*mStored == nullptr);
std::vector<struct sock_filter> filterVec;
SandboxFilterImpl *impl;
switch (aType) {
case kSandboxContentProcess:
#ifdef MOZ_CONTENT_SANDBOX
impl = new SandboxFilterImplContent();
#else
MOZ_CRASH("Content process sandboxing not supported in this build!");
#endif
break;
case kSandboxMediaPlugin:
#ifdef MOZ_GMP_SANDBOX
impl = new SandboxFilterImplGMP();
#else
MOZ_CRASH("Gecko Media Plugin process sandboxing not supported in this"
" build!");
#endif
break;
default:
MOZ_CRASH("Nonexistent sandbox type!");
}
impl->Build();
impl->Finish();
impl->Compile(&filterVec, aVerbose);
delete impl;
mProg = new sock_fprog;
mProg->len = filterVec.size();
mProg->filter = mFilter = new sock_filter[mProg->len];
for (size_t i = 0; i < mProg->len; ++i) {
mFilter[i] = filterVec[i];
}
*mStored = mProg;
}
SandboxFilter::~SandboxFilter()
{
*mStored = nullptr;
delete[] mFilter;
delete mProg;
}
}

View File

@ -11,17 +11,25 @@ struct sock_fprog;
struct sock_filter;
namespace mozilla {
class SandboxFilter {
sock_filter *mFilter;
sock_fprog *mProg;
const sock_fprog **mStored;
public:
enum SandboxType {
kSandboxContentProcess,
kSandboxMediaPlugin
};
class SandboxFilter {
sock_filter *mFilter;
sock_fprog *mProg;
const sock_fprog **mStored;
public:
// RAII: on construction, builds the filter and stores it in the
// provided variable (with optional logging); on destruction, frees
// the filter and nulls out the pointer.
SandboxFilter(const sock_fprog** aStored, bool aVerbose = false);
~SandboxFilter();
};
}
SandboxFilter(const sock_fprog** aStored, SandboxType aBox,
bool aVerbose = false);
~SandboxFilter();
};
} // namespace mozilla
#endif

View File

@ -6,7 +6,7 @@
if CONFIG['LIBXUL_SDK']:
error('toolkit.mozbuild is not compatible with --enable-libxul-sdk=')
if CONFIG['MOZ_CONTENT_SANDBOX'] or (CONFIG['OS_ARCH'] == 'WINNT'):
if CONFIG['MOZ_CONTENT_SANDBOX'] or CONFIG['MOZ_GMP_SANDBOX']:
add_tier_dir('sandbox', 'security/sandbox')
# Depends on NSS and NSPR, and must be built after sandbox or else B2G emulator