diff --git a/configure.in b/configure.in index af352a191cc..f37e7721c96 100644 --- a/configure.in +++ b/configure.in @@ -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 diff --git a/content/media/gmp/GMPChild.cpp b/content/media/gmp/GMPChild.cpp index 0adf7034e7e..64eb35649e1 100644 --- a/content/media/gmp/GMPChild.cpp +++ b/content/media/gmp/GMPChild.cpp @@ -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; diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index c9808a08be4..5cda5fb80ca 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -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 diff --git a/security/sandbox/linux/Sandbox.cpp b/security/sandbox/linux/Sandbox.cpp index ab85ae07212..095d6a150ef 100644 --- a/security/sandbox/linux/Sandbox.cpp +++ b/security/sandbox/linux/Sandbox.cpp @@ -19,12 +19,12 @@ #include #include #include +#include #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 #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(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) == sizeof(int), "mozilla::Atomic 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 diff --git a/security/sandbox/linux/Sandbox.h b/security/sandbox/linux/Sandbox.h index c9e2be7133f..d8a3891ceb1 100644 --- a/security/sandbox/linux/Sandbox.h +++ b/security/sandbox/linux/Sandbox.h @@ -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 diff --git a/security/sandbox/linux/SandboxFilter.cpp b/security/sandbox/linux/SandboxFilter.cpp index bbc0d6a1bd7..2514c5e68d3 100644 --- a/security/sandbox/linux/SandboxFilter.cpp +++ b/security/sandbox/linux/SandboxFilter.cpp @@ -14,49 +14,26 @@ #include "mozilla/NullPtr.h" #include -#include -#include #include +#include +#include +#include +#include +#include +#include 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 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 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; +} } diff --git a/security/sandbox/linux/SandboxFilter.h b/security/sandbox/linux/SandboxFilter.h index a00dea2cf4b..861fd12bb1d 100644 --- a/security/sandbox/linux/SandboxFilter.h +++ b/security/sandbox/linux/SandboxFilter.h @@ -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 diff --git a/toolkit/toolkit.mozbuild b/toolkit/toolkit.mozbuild index 20b9d686514..28e52f77acb 100644 --- a/toolkit/toolkit.mozbuild +++ b/toolkit/toolkit.mozbuild @@ -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