Example #1
0
static int
OpenDeletedDirectory()
{
  // We don't need this directory to persist between invocations of
  // the program (nor need it to be cleaned up if something goes wrong
  // here, because mkdtemp will choose a fresh name), so /tmp as
  // specified by FHS is adequate.
  char path[] = "/tmp/mozsandbox.XXXXXX";
  if (!mkdtemp(path)) {
    SANDBOX_LOG_ERROR("mkdtemp: %s", strerror(errno));
    return -1;
  }
  int fd = HANDLE_EINTR(open(path, O_RDONLY | O_DIRECTORY));
  if (fd < 0) {
    SANDBOX_LOG_ERROR("open %s: %s", path, strerror(errno));
    // Try to clean up.  Shouldn't fail, but livable if it does.
    DebugOnly<bool> ok = HANDLE_EINTR(rmdir(path)) == 0;
    MOZ_ASSERT(ok);
    return -1;
  }
  if (HANDLE_EINTR(rmdir(path)) != 0) {
    SANDBOX_LOG_ERROR("rmdir %s: %s", path, strerror(errno));
    AlwaysClose(fd);
    return -1;
  }
  return fd;
}
Example #2
0
bool
SandboxChroot::Prepare()
{
  LinuxCapabilities caps;
  if (!caps.GetCurrent() || !caps.Effective(CAP_SYS_CHROOT)) {
    SANDBOX_LOG_ERROR("don't have permission to chroot");
    return false;
  }
  mFd = OpenDeletedDirectory();
  if (mFd < 0) {
    SANDBOX_LOG_ERROR("failed to create empty directory for chroot");
    return false;
  }
  MOZ_ALWAYS_ZERO(pthread_mutex_lock(&mMutex));
  MOZ_ASSERT(mCommand == NO_THREAD);
  if (pthread_create(&mThread, nullptr, StaticThreadMain, this) != 0) {
    MOZ_ALWAYS_ZERO(pthread_mutex_unlock(&mMutex));
    SANDBOX_LOG_ERROR("pthread_create: %s", strerror(errno));
    return false;
  }
  while (mCommand != NO_COMMAND) {
    MOZ_ASSERT(mCommand == NO_THREAD);
    MOZ_ALWAYS_ZERO(pthread_cond_wait(&mWakeup, &mMutex));
  }
  MOZ_ALWAYS_ZERO(pthread_mutex_unlock(&mMutex));
  return true;
}
Example #3
0
/**
 * This is the SIGSYS handler function. It is used to report to the user
 * which system call has been denied by Seccomp.
 * This function also makes the process exit as denying the system call
 * will otherwise generally lead to unexpected behavior from the process,
 * since we don't know if all functions will handle such denials gracefully.
 *
 * @see InstallSyscallReporter() function.
 */
static void
Reporter(int nr, siginfo_t *info, void *void_context)
{
  ucontext_t *ctx = static_cast<ucontext_t*>(void_context);
  unsigned long syscall_nr, args[6];
  pid_t pid = getpid();

  if (nr != SIGSYS) {
    return;
  }
  if (info->si_code != SYS_SECCOMP) {
    return;
  }
  if (!ctx) {
    return;
  }

  syscall_nr = SECCOMP_SYSCALL(ctx);
  args[0] = SECCOMP_PARM1(ctx);
  args[1] = SECCOMP_PARM2(ctx);
  args[2] = SECCOMP_PARM3(ctx);
  args[3] = SECCOMP_PARM4(ctx);
  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) {
      SANDBOX_LOG_ERROR("non-read-only open of file %s attempted (flags=0%o)",
                        path, flags);
    } else if (strcmp(path, gMediaPluginFilePath) != 0) {
      SANDBOX_LOG_ERROR("attempt to open file %s which is not the media plugin"
                        " %s", path, gMediaPluginFilePath);
    } else if (gMediaPluginFileDesc == -1) {
      SANDBOX_LOG_ERROR("multiple opens of media plugin file unimplemented");
    } else {
      SECCOMP_RESULT(ctx) = gMediaPluginFileDesc;
      gMediaPluginFileDesc = -1;
      return;
    }
  }
#endif

  SANDBOX_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]);

  // Bug 1017393: record syscall number somewhere useful.
  info->si_addr = reinterpret_cast<void*>(syscall_nr);

  gSandboxCrashFunc(nr, info, void_context);
  _exit(127);
}
Example #4
0
static bool
ChrootToFileDesc(int fd)
{
  if (fchdir(fd) != 0) {
    SANDBOX_LOG_ERROR("fchdir: %s", strerror(errno));
    return false;
  }
  if (chroot(".") != 0) {
    SANDBOX_LOG_ERROR("chroot: %s", strerror(errno));
    return false;
  }
  return true;
}
Example #5
0
/**
 * This function installs the syscall filter, a.k.a. seccomp.
 * PR_SET_NO_NEW_PRIVS ensures that it is impossible to grant more
 * syscalls to the process beyond this point (even after fork()).
 * SECCOMP_MODE_FILTER is the "bpf" mode of seccomp which allows
 * to pass a bpf program (in our case, it contains a syscall
 * whitelist).
 *
 * Reports failure by crashing.
 *
 * @see sock_fprog (the seccomp_prog).
 */
static void
InstallSyscallFilter(const sock_fprog *prog)
{
  if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
    SANDBOX_LOG_ERROR("prctl(PR_SET_NO_NEW_PRIVS) failed: %s", strerror(errno));
    MOZ_CRASH("prctl(PR_SET_NO_NEW_PRIVS)");
  }

  if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, (unsigned long)prog, 0, 0)) {
    SANDBOX_LOG_ERROR("prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER) failed: %s",
                      strerror(errno));
    MOZ_CRASH("prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER)");
  }
}
Example #6
0
// Common code for sandbox startup.
static void
SetCurrentProcessSandbox(UniquePtr<sandbox::bpf_dsl::Policy> aPolicy)
{
    MOZ_ASSERT(gSandboxCrashFunc);

    // Note: PolicyCompiler borrows the policy and registry for its
    // lifetime, but does not take ownership of them.
    sandbox::bpf_dsl::PolicyCompiler compiler(aPolicy.get(),
            sandbox::Trap::Registry());
    auto program = compiler.Compile();
    if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
        sandbox::bpf_dsl::DumpBPF::PrintProgram(*program);
    }

    InstallSigSysHandler();

#ifdef MOZ_ASAN
    __sanitizer_sandbox_arguments asanArgs;
    asanArgs.coverage_sandboxed = 1;
    asanArgs.coverage_fd = -1;
    asanArgs.coverage_max_block_size = 0;
    __sanitizer_sandbox_on_notify(&asanArgs);
#endif

    // The syscall takes a C-style array, so copy the vector into one.
    size_t programLen = program->size();
    UniquePtr<sock_filter[]> flatProgram(new sock_filter[programLen]);
    for (auto i = program->begin(); i != program->end(); ++i) {
        flatProgram[i - program->begin()] = *i;
    }

    sock_fprog fprog;
    fprog.filter = flatProgram.get();
    fprog.len = static_cast<unsigned short>(programLen);
    MOZ_RELEASE_ASSERT(static_cast<size_t>(fprog.len) == programLen);

    const SandboxInfo info = SandboxInfo::Get();
    if (info.Test(SandboxInfo::kHasSeccompTSync)) {
        if (info.Test(SandboxInfo::kVerbose)) {
            SANDBOX_LOG_ERROR("using seccomp tsync");
        }
        ApplySandboxWithTSync(&fprog);
    } else {
        if (info.Test(SandboxInfo::kVerbose)) {
            SANDBOX_LOG_ERROR("no tsync support; using signal broadcast");
        }
        BroadcastSetThreadSandbox(&fprog);
    }
    MOZ_RELEASE_ASSERT(!gChrootHelper, "forgot to chroot");
}
Example #7
0
static void
AlwaysClose(int fd)
{
  if (IGNORE_EINTR(close(fd)) != 0) {
    SANDBOX_LOG_ERROR("close: %s", strerror(errno));
    MOZ_CRASH("failed to close()");
  }
}
int
SandboxOpenedFiles::GetDesc(const char* aPath) const
{
  for (const auto& file : mFiles) {
    if (strcmp(file.Path(), aPath) == 0) {
      return file.GetDesc();
    }
  }
  SANDBOX_LOG_ERROR("attempt to open unexpected file %s", aPath);
  return -1;
}
int
SandboxOpenedFile::GetDesc() const
{
  int fd = -1;
  if (mDup) {
    fd = mMaybeFd;
    if (fd >= 0) {
      fd = dup(fd);
      if (fd < 0) {
        SANDBOX_LOG_ERROR("dup: %s", strerror(errno));
      }
    }
  } else {
    fd = TakeDesc();
  }
  if (fd < 0 && !mExpectError) {
    SANDBOX_LOG_ERROR("unexpected multiple open of file %s", Path());
  }
  return fd;
}
Example #10
0
// Common code for sandbox startup.
static void
SetCurrentProcessSandbox(SandboxType aType)
{
  MOZ_ASSERT(gSandboxCrashFunc);

  if (InstallSyscallReporter()) {
    SANDBOX_LOG_ERROR("install_syscall_reporter() failed\n");
  }

  BroadcastSetThreadSandbox(aType);
}
Example #11
0
    static intptr_t OpenTrap(const sandbox::arch_seccomp_data& aArgs,
                             void* aux)
    {
        auto plugin = static_cast<SandboxOpenedFile*>(aux);
        const char* path;
        int flags;

        switch (aArgs.nr) {
#ifdef __NR_open
        case __NR_open:
            path = reinterpret_cast<const char*>(aArgs.args[0]);
            flags = static_cast<int>(aArgs.args[1]);
            break;
#endif
        case __NR_openat:
            // The path has to be absolute to match the pre-opened file (see
            // assertion in ctor) so the dirfd argument is ignored.
            path = reinterpret_cast<const char*>(aArgs.args[1]);
            flags = static_cast<int>(aArgs.args[2]);
            break;
        default:
            MOZ_CRASH("unexpected syscall number");
        }

        if ((flags & O_ACCMODE) != O_RDONLY) {
            SANDBOX_LOG_ERROR("non-read-only open of file %s attempted (flags=0%o)",
                              path, flags);
            return -ENOSYS;
        }
        if (strcmp(path, plugin->mPath) != 0) {
            SANDBOX_LOG_ERROR("attempt to open file %s which is not the media plugin"
                              " %s", path, plugin->mPath);
            return -ENOSYS;
        }
        int fd = plugin->mFd.exchange(-1);
        if (fd < 0) {
            SANDBOX_LOG_ERROR("multiple opens of media plugin file unimplemented");
            return -ENOSYS;
        }
        return fd;
    }
Example #12
0
static void
SandboxLogCStack()
{
  // Skip 3 frames: one for this module, one for the signal handler in
  // libmozsandbox, and one for the signal trampoline.
  //
  // Warning: this might not print any stack frames.  MozStackWalk
  // can't walk past the signal trampoline on ARM (bug 968531), and
  // x86 frame pointer walking may or may not work (bug 1082276).

  MozStackWalk(SandboxPrintStackFrame, /* skip */ 3, /* max */ 0, nullptr);
  SANDBOX_LOG_ERROR("end of stack.");
}
Example #13
0
static int
OpenDeletedDirectory()
{
    // We don't need this directory to persist between invocations of
    // the program (nor need it to be cleaned up if something goes wrong
    // here, because mkdtemp will choose a fresh name), so /tmp as
    // specified by FHS is adequate.
    //
    // However, this needs a filesystem where a deleted directory can
    // still be used, and /tmp is sometimes not that; e.g., aufs(5),
    // often used for containers, will cause the chroot() to fail with
    // ESTALE (bug 1162965).  So this uses /dev/shm if possible instead.
    char tmpPath[] = "/tmp/mozsandbox.XXXXXX";
    char shmPath[] = "/dev/shm/mozsandbox.XXXXXX";
    char* path;
    if (mkdtemp(shmPath)) {
        path = shmPath;
    } else if (mkdtemp(tmpPath)) {
        path = tmpPath;
    } else {
        SANDBOX_LOG_ERROR("mkdtemp: %s", strerror(errno));
        return -1;
    }
    int fd = HANDLE_EINTR(open(path, O_RDONLY | O_DIRECTORY));
    if (fd < 0) {
        SANDBOX_LOG_ERROR("open %s: %s", path, strerror(errno));
        // Try to clean up.  Shouldn't fail, but livable if it does.
        DebugOnly<bool> ok = HANDLE_EINTR(rmdir(path)) == 0;
        MOZ_ASSERT(ok);
        return -1;
    }
    if (HANDLE_EINTR(rmdir(path)) != 0) {
        SANDBOX_LOG_ERROR("rmdir %s: %s", path, strerror(errno));
        AlwaysClose(fd);
        return -1;
    }
    return fd;
}
Example #14
0
/**
 * This function installs the syscall filter, a.k.a. seccomp.  The
 * aUseTSync flag indicates whether this should apply to all threads
 * in the process -- which will fail if the kernel doesn't support
 * that -- or only the current thread.
 *
 * SECCOMP_MODE_FILTER is the "bpf" mode of seccomp which allows
 * to pass a bpf program (in our case, it contains a syscall
 * whitelist).
 *
 * PR_SET_NO_NEW_PRIVS ensures that it is impossible to grant more
 * syscalls to the process beyond this point (even after fork()), and
 * prevents gaining capabilities (e.g., by exec'ing a setuid root
 * program).  The kernel won't allow seccomp-bpf without doing this,
 * because otherwise it could be used for privilege escalation attacks.
 *
 * Returns false (and sets errno) on failure.
 *
 * @see SandboxInfo
 * @see BroadcastSetThreadSandbox
 */
static bool MOZ_WARN_UNUSED_RESULT
InstallSyscallFilter(const sock_fprog *aProg, bool aUseTSync)
{
    if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
        SANDBOX_LOG_ERROR("prctl(PR_SET_NO_NEW_PRIVS) failed: %s", strerror(errno));
        MOZ_CRASH("prctl(PR_SET_NO_NEW_PRIVS)");
    }

    if (aUseTSync) {
        if (syscall(__NR_seccomp, SECCOMP_SET_MODE_FILTER,
                    SECCOMP_FILTER_FLAG_TSYNC, aProg) != 0) {
            SANDBOX_LOG_ERROR("thread-synchronized seccomp failed: %s",
                              strerror(errno));
            return false;
        }
    } else {
        if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, (unsigned long)aProg, 0, 0)) {
            SANDBOX_LOG_ERROR("prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER) failed: %s",
                              strerror(errno));
            return false;
        }
    }
    return true;
}
Example #15
0
/**
 * 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 (gSandboxFlags.isDisabledForGMP) {
    return;
  }

  if (aFilePath) {
    gMediaPluginFilePath = strdup(aFilePath);
    gMediaPluginFileDesc = open(aFilePath, O_RDONLY | O_CLOEXEC);
    if (gMediaPluginFileDesc == -1) {
      SANDBOX_LOG_ERROR("failed to open plugin file %s: %s",
                        aFilePath, strerror(errno));
      MOZ_CRASH();
    }
  }
  // Finally, start the sandbox.
  SetCurrentProcessSandbox(kSandboxMediaPlugin);
}
Example #16
0
/**
 * This is the SIGSYS handler function.  It delegates to the Chromium
 * TrapRegistry handler (see InstallSigSysHandler, below) and, if the
 * trap handler installed by the policy would fail with ENOSYS,
 * crashes the process.  This allows unintentional policy failures to
 * be reported as crash dumps and fixed.  It also logs information
 * about the failed system call.
 *
 * Note that this could be invoked in parallel on multiple threads and
 * that it could be in async signal context (e.g., intercepting an
 * open() called from an async signal handler).
 */
static void
SigSysHandler(int nr, siginfo_t *info, void *void_context)
{
    ucontext_t *ctx = static_cast<ucontext_t*>(void_context);
    // This shouldn't ever be null, but the Chromium handler checks for
    // that and refrains from crashing, so let's not crash release builds:
    MOZ_DIAGNOSTIC_ASSERT(ctx);
    if (!ctx) {
        return;
    }

    // Save a copy of the context before invoking the trap handler,
    // which will overwrite one or more registers with the return value.
    ucontext_t savedCtx = *ctx;

    gChromiumSigSysHandler(nr, info, ctx);
    if (!ContextIsError(ctx, ENOSYS)) {
        return;
    }

    pid_t pid = getpid();
    unsigned long syscall_nr = SECCOMP_SYSCALL(&savedCtx);
    unsigned long args[6];
    args[0] = SECCOMP_PARM1(&savedCtx);
    args[1] = SECCOMP_PARM2(&savedCtx);
    args[2] = SECCOMP_PARM3(&savedCtx);
    args[3] = SECCOMP_PARM4(&savedCtx);
    args[4] = SECCOMP_PARM5(&savedCtx);
    args[5] = SECCOMP_PARM6(&savedCtx);

    // TODO, someday when this is enabled on MIPS: include the two extra
    // args in the error message.
    SANDBOX_LOG_ERROR("seccomp sandbox violation: pid %d, syscall %d,"
                      " args %d %d %d %d %d %d.  Killing process.",
                      pid, syscall_nr,
                      args[0], args[1], args[2], args[3], args[4], args[5]);

    // Bug 1017393: record syscall number somewhere useful.
    info->si_addr = reinterpret_cast<void*>(syscall_nr);

    gSandboxCrashFunc(nr, info, &savedCtx);
    _exit(127);
}
Example #17
0
// Common code for sandbox startup.
static void
SetCurrentProcessSandbox(SandboxType aType)
{
  MOZ_ASSERT(gSandboxCrashFunc);

  if (InstallSyscallReporter()) {
    SANDBOX_LOG_ERROR("install_syscall_reporter() failed\n");
  }

#ifdef MOZ_ASAN
  __sanitizer_sandbox_arguments asanArgs;
  asanArgs.coverage_sandboxed = 1;
  asanArgs.coverage_fd = -1;
  asanArgs.coverage_max_block_size = 0;
  __sanitizer_sandbox_on_notify(&asanArgs);
#endif

  BroadcastSetThreadSandbox(aType);
}
Example #18
0
static void
SandboxCrash(int nr, siginfo_t *info, void *void_context)
{
  pid_t pid = getpid(), tid = syscall(__NR_gettid);
  bool dumped = CrashReporter::WriteMinidumpForSigInfo(nr, info, void_context);

  if (!dumped) {
    SANDBOX_LOG_ERROR("crash reporter is disabled (or failed);"
                      " trying stack trace:");
    SandboxLogCStack();
  }

  // Do this last, in case it crashes or deadlocks.
  SandboxLogJSStack();

  // Try to reraise, so the parent sees that this process crashed.
  // (If tgkill is forbidden, then seccomp will raise SIGSYS, which
  // also accomplishes that goal.)
  signal(SIGSYS, SIG_DFL);
  syscall(__NR_tgkill, pid, tid, nr);
}
Example #19
0
void
SandboxChroot::ThreadMain()
{
  // First, drop everything that isn't CAP_SYS_CHROOT.  (This code
  // assumes that this thread already has effective CAP_SYS_CHROOT,
  // because Prepare() checked for it before creating this thread.)
  LinuxCapabilities caps;
  caps.Effective(CAP_SYS_CHROOT) = true;
  if (!caps.SetCurrent()) {
    SANDBOX_LOG_ERROR("capset: %s", strerror(errno));
    MOZ_CRASH("Can't limit chroot thread's capabilities");
  }

  MOZ_ALWAYS_ZERO(pthread_mutex_lock(&mMutex));
  MOZ_ASSERT(mCommand == NO_THREAD);
  mCommand = NO_COMMAND;
  MOZ_ALWAYS_ZERO(pthread_cond_signal(&mWakeup));
  while (mCommand == NO_COMMAND) {
    MOZ_ALWAYS_ZERO(pthread_cond_wait(&mWakeup, &mMutex));
  }
  if (mCommand == DO_CHROOT) {
    MOZ_ASSERT(mFd >= 0);
    if (!ChrootToFileDesc(mFd)) {
      MOZ_CRASH("Failed to chroot");
    }
  } else {
    MOZ_ASSERT(mCommand == JUST_EXIT);
  }
  if (mFd >= 0) {
    AlwaysClose(mFd);
    mFd = -1;
  }
  mCommand = NO_THREAD;
  MOZ_ALWAYS_ZERO(pthread_mutex_unlock(&mMutex));
  // Drop the remaining capabilities; see note in SandboxChroot.h
  // about the potential unreliability of pthread_join.
  if (!LinuxCapabilities().SetCurrent()) {
    MOZ_CRASH("can't drop capabilities");
  }
}
Example #20
0
/**
 * 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
 * read-only, once, 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 (!SandboxInfo::Get().Test(SandboxInfo::kEnabledForMedia)) {
        return;
    }

    MOZ_ASSERT(!gMediaPluginFile.mPath);
    if (aFilePath) {
        gMediaPluginFile.mPath = strdup(aFilePath);
        gMediaPluginFile.mFd = open(aFilePath, O_RDONLY | O_CLOEXEC);
        if (gMediaPluginFile.mFd == -1) {
            SANDBOX_LOG_ERROR("failed to open plugin file %s: %s",
                              aFilePath, strerror(errno));
            MOZ_CRASH();
        }
    } else {
        gMediaPluginFile.mFd = -1;
    }
    // Finally, start the sandbox.
    SetCurrentProcessSandbox(GetMediaSandboxPolicy(&gMediaPluginFile));
}
Example #21
0
/* static */ void
SandboxInfo::ThreadingCheck()
{
  // Allow MOZ_SANDBOX_UNEXPECTED_THREADS to be set manually for testing.
  if (IsSingleThreaded() &&
      !getenv("MOZ_SANDBOX_UNEXPECTED_THREADS")) {
    return;
  }
  SANDBOX_LOG_ERROR("unexpected multithreading found; this prevents using"
                    " namespace sandboxing.%s",
                    // getenv isn't thread-safe, but see below.
                    getenv("LD_PRELOAD") ? "  (If you're LD_PRELOAD'ing"
                    " nVidia GL: that's not necessary for Gecko.)" : "");

  // Propagate this information for use by child processes.  (setenv
  // isn't thread-safe, but other threads are from non-Gecko code so
  // they wouldn't be using NSPR; we have to hope for the best.)
  setenv("MOZ_SANDBOX_UNEXPECTED_THREADS", "1", 0);
  int flags = sSingleton.mFlags;
  flags |= kUnexpectedThreads;
  flags &= ~(kHasUserNamespaces | kHasPrivilegedUserNamespaces);
  sSingleton.mFlags = static_cast<Flags>(flags);
}
Example #22
0
void
SandboxEarlyInit(GeckoProcessType aType, bool aIsNuwa)
{
    // Bug 1168555: Nuwa isn't reliably single-threaded at this point;
    // it starts an IPC I/O thread and then shuts it down before calling
    // the plugin-container entry point, but that thread may not have
    // finished exiting.  If/when any type of sandboxing is used for the
    // Nuwa process (e.g., unsharing the network namespace there instead
    // of for each content process, to save memory), this will need to be
    // changed by moving the SandboxEarlyInit call to an earlier point.
    if (aIsNuwa) {
        return;
    }

    MOZ_RELEASE_ASSERT(IsSingleThreaded());
    const SandboxInfo info = SandboxInfo::Get();

    // Which kinds of resource isolation (of those that need to be set
    // up at this point) can be used by this process?
    bool canChroot = false;
    bool canUnshareNet = false;
    bool canUnshareIPC = false;

    switch (aType) {
    case GeckoProcessType_Default:
        MOZ_ASSERT(false, "SandboxEarlyInit in parent process");
        return;
#ifdef MOZ_GMP_SANDBOX
    case GeckoProcessType_GMPlugin:
        if (!info.Test(SandboxInfo::kEnabledForMedia)) {
            break;
        }
        canUnshareNet = true;
        canUnshareIPC = true;
        // Need seccomp-bpf to intercept open().
        canChroot = info.Test(SandboxInfo::kHasSeccompBPF);
        break;
#endif
    // In the future, content processes will be able to use some of
    // these.
    default:
        // Other cases intentionally left blank.
        break;
    }

    // If there's nothing to do, then we're done.
    if (!canChroot && !canUnshareNet && !canUnshareIPC) {
        return;
    }

    {
        LinuxCapabilities existingCaps;
        if (existingCaps.GetCurrent() && existingCaps.AnyEffective()) {
            SANDBOX_LOG_ERROR("PLEASE DO NOT RUN THIS AS ROOT.  Strange things may"
                              " happen when capabilities are dropped.");
        }
    }

    // If capabilities can't be gained, then nothing can be done.
    if (!info.Test(SandboxInfo::kHasUserNamespaces)) {
        // Drop any existing capabilities; unsharing the user namespace
        // would implicitly drop them, so if we're running in a broken
        // configuration where that would matter (e.g., running as root
        // from a non-root-owned mode-0700 directory) this means it will
        // break the same way on all kernels and be easier to troubleshoot.
        LinuxCapabilities().SetCurrent();
        return;
    }

    // The failure cases for the various unshares, and setting up the
    // chroot helper, don't strictly need to be fatal -- but they also
    // shouldn't fail on any reasonable system, so let's take the small
    // risk of breakage over the small risk of quietly providing less
    // security than we expect.  (Unlike in SandboxInfo, this is in the
    // child process, so crashing here isn't as severe a response to the
    // unexpected.)
    if (!UnshareUserNamespace()) {
        SANDBOX_LOG_ERROR("unshare(CLONE_NEWUSER): %s", strerror(errno));
        // If CanCreateUserNamespace (SandboxInfo.cpp) returns true, then
        // the unshare shouldn't have failed.
        MOZ_CRASH("unshare(CLONE_NEWUSER)");
    }
    // No early returns after this point!  We need to drop the
    // capabilities that were gained by unsharing the user namesapce.

    if (canUnshareIPC && syscall(__NR_unshare, CLONE_NEWIPC) != 0) {
        SANDBOX_LOG_ERROR("unshare(CLONE_NEWIPC): %s", strerror(errno));
        MOZ_CRASH("unshare(CLONE_NEWIPC)");
    }

    if (canUnshareNet && syscall(__NR_unshare, CLONE_NEWNET) != 0) {
        SANDBOX_LOG_ERROR("unshare(CLONE_NEWNET): %s", strerror(errno));
        MOZ_CRASH("unshare(CLONE_NEWNET)");
    }

    if (canChroot) {
        gChrootHelper = MakeUnique<SandboxChroot>();
        if (!gChrootHelper->Prepare()) {
            SANDBOX_LOG_ERROR("failed to set up chroot helper");
            MOZ_CRASH("SandboxChroot::Prepare");
        }
    }

    if (!LinuxCapabilities().SetCurrent()) {
        SANDBOX_LOG_ERROR("dropping capabilities: %s", strerror(errno));
        MOZ_CRASH("can't drop capabilities");
    }
}
Example #23
0
static void
BroadcastSetThreadSandbox(const sock_fprog* aFilter)
{
    int signum;
    pid_t pid, tid, myTid;
    DIR *taskdp;
    struct dirent *de;

    // This function does not own *aFilter, so this global needs to
    // always be zeroed before returning.
    gSetSandboxFilter = aFilter;

    static_assert(sizeof(mozilla::Atomic<int>) == sizeof(int),
                  "mozilla::Atomic<int> isn't represented by an int");
    pid = getpid();
    myTid = syscall(__NR_gettid);
    taskdp = opendir("/proc/self/task");
    if (taskdp == nullptr) {
        SANDBOX_LOG_ERROR("opendir /proc/self/task: %s\n", strerror(errno));
        MOZ_CRASH();
    }

    EnterChroot();

    signum = FindFreeSignalNumber();
    if (signum == 0) {
        SANDBOX_LOG_ERROR("No available signal numbers!");
        MOZ_CRASH();
    }
    void (*oldHandler)(int);
    oldHandler = signal(signum, SetThreadSandboxHandler);
    if (oldHandler != SIG_DFL) {
        // See the comment on FindFreeSignalNumber about race conditions.
        SANDBOX_LOG_ERROR("signal %d in use by handler %p!\n", signum, oldHandler);
        MOZ_CRASH();
    }

    // In case this races with a not-yet-deprivileged thread cloning
    // itself, repeat iterating over all threads until we find none
    // that are still privileged.
    bool sandboxProgress;
    do {
        sandboxProgress = false;
        // For each thread...
        while ((de = readdir(taskdp))) {
            char *endptr;
            tid = strtol(de->d_name, &endptr, 10);
            if (*endptr != '\0' || tid <= 0) {
                // Not a task ID.
                continue;
            }
            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.
            gSetSandboxDone = 0;
            if (syscall(__NR_tgkill, pid, tid, signum) != 0) {
                if (errno == ESRCH) {
                    SANDBOX_LOG_ERROR("Thread %d unexpectedly exited.", tid);
                    // Rescan threads, in case it forked before exiting.
                    sandboxProgress = true;
                    continue;
                }
                SANDBOX_LOG_ERROR("tgkill(%d,%d): %s\n", pid, tid, strerror(errno));
                MOZ_CRASH();
            }
            // It's unlikely, but if the thread somehow manages to exit
            // after receiving the signal but before entering the signal
            // handler, we need to avoid blocking forever.
            //
            // Using futex directly lets the signal handler send the wakeup
            // from an async signal handler (pthread mutex/condvar calls
            // aren't allowed), and to use a relative timeout that isn't
            // affected by changes to the system clock (not possible with
            // POSIX semaphores).
            //
            // If a thread doesn't respond within a reasonable amount of
            // time, but still exists, we crash -- the alternative is either
            // blocking forever or silently losing security, and it
            // shouldn't actually happen.
            static const int crashDelay = 10; // seconds
            struct timespec timeLimit;
            clock_gettime(CLOCK_MONOTONIC, &timeLimit);
            timeLimit.tv_sec += crashDelay;
            while (true) {
                static const struct timespec futexTimeout = { 0, 10*1000*1000 }; // 10ms
                // Atomically: if gSetSandboxDone == 0, then sleep.
                if (syscall(__NR_futex, reinterpret_cast<int*>(&gSetSandboxDone),
                            FUTEX_WAIT, 0, &futexTimeout) != 0) {
                    if (errno != EWOULDBLOCK && errno != ETIMEDOUT && errno != EINTR) {
                        SANDBOX_LOG_ERROR("FUTEX_WAIT: %s\n", strerror(errno));
                        MOZ_CRASH();
                    }
                }
                // Did the handler finish?
                if (gSetSandboxDone > 0) {
                    if (gSetSandboxDone == 2) {
                        sandboxProgress = true;
                    }
                    break;
                }
                // Has the thread ceased to exist?
                if (syscall(__NR_tgkill, pid, tid, 0) != 0) {
                    if (errno == ESRCH) {
                        SANDBOX_LOG_ERROR("Thread %d unexpectedly exited.", tid);
                    }
                    // Rescan threads, in case it forked before exiting.
                    // Also, if it somehow failed in a way that wasn't ESRCH,
                    // and still exists, that will be handled on the next pass.
                    sandboxProgress = true;
                    break;
                }
                struct timespec now;
                clock_gettime(CLOCK_MONOTONIC, &now);
                if (now.tv_sec > timeLimit.tv_sec ||
                        (now.tv_sec == timeLimit.tv_sec &&
                         now.tv_nsec > timeLimit.tv_nsec)) {
                    SANDBOX_LOG_ERROR("Thread %d unresponsive for %d seconds."
                                      "  Killing process.",
                                      tid, crashDelay);
                    MOZ_CRASH();
                }
            }
        }
        rewinddir(taskdp);
    } while (sandboxProgress);
    oldHandler = signal(signum, SIG_DFL);
    if (oldHandler != SetThreadSandboxHandler) {
        // See the comment on FindFreeSignalNumber about race conditions.
        SANDBOX_LOG_ERROR("handler for signal %d was changed to %p!",
                          signum, oldHandler);
        MOZ_CRASH();
    }
    Unused << closedir(taskdp);
    // And now, deprivilege the main thread:
    SetThreadSandbox();
    gSetSandboxFilter = nullptr;
}
Example #24
0
int
SandboxBrokerClient::DoCall(const Request* aReq, const char* aPath,
                            struct stat* aStat, bool expectFd)
{
  // Remap /proc/self to the actual pid, so that the broker can open
  // it.  This happens here instead of in the broker to follow the
  // principle of least privilege and keep the broker as simple as
  // possible.  (Note: when pid namespaces happen, this will also need
  // to remap the inner pid to the outer pid.)
  static const char kProcSelf[] = "/proc/self/";
  static const size_t kProcSelfLen = sizeof(kProcSelf) - 1;
  const char* path = aPath;
  // This buffer just needs to be large enough for any such path that
  // the policy would actually allow.  sizeof("/proc/2147483647/") == 18.
  char rewrittenPath[64];
  if (strncmp(aPath, kProcSelf, kProcSelfLen) == 0) {
    ssize_t len =
      base::strings::SafeSPrintf(rewrittenPath, "/proc/%d/%s",
                                 getpid(), aPath + kProcSelfLen);
    if (static_cast<size_t>(len) < sizeof(rewrittenPath)) {
      if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
        SANDBOX_LOG_ERROR("rewriting %s -> %s", aPath, rewrittenPath);
      }
      path = rewrittenPath;
    } else {
      SANDBOX_LOG_ERROR("not rewriting unexpectedly long path %s", aPath);
    }
  }

  struct iovec ios[2];
  int respFds[2];

  // Set up iovecs for request + path.
  ios[0].iov_base = const_cast<Request*>(aReq);
  ios[0].iov_len = sizeof(*aReq);
  ios[1].iov_base = const_cast<char*>(path);
  ios[1].iov_len = strlen(path);
  if (ios[1].iov_len > kMaxPathLen) {
    return -ENAMETOOLONG;
  }

  // Create response socket and send request.
  if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, respFds) < 0) {
    return -errno;
  }
  const ssize_t sent = SendWithFd(mFileDesc, ios, 2, respFds[1]);
  const int sendErrno = errno;
  MOZ_ASSERT(sent < 0 ||
	     static_cast<size_t>(sent) == ios[0].iov_len + ios[1].iov_len);
  close(respFds[1]);
  if (sent < 0) {
    close(respFds[0]);
    return -sendErrno;
  }

  // Set up iovecs for response.
  Response resp;
  ios[0].iov_base = &resp;
  ios[0].iov_len = sizeof(resp);
  if (aStat) {
    ios[1].iov_base = aStat;
    ios[1].iov_len = sizeof(*aStat);
  } else {
    ios[1].iov_base = nullptr;
    ios[1].iov_len = 0;
  }

  // Wait for response and return appropriately.
  int openedFd = -1;
  const ssize_t recvd = RecvWithFd(respFds[0], ios, aStat ? 2 : 1,
                                   expectFd ? &openedFd : nullptr);
  const int recvErrno = errno;
  close(respFds[0]);
  if (recvd < 0) {
    return -recvErrno;
  }
  if (recvd == 0) {
    SANDBOX_LOG_ERROR("Unexpected EOF, op %d flags 0%o path %s",
                      aReq->mOp, aReq->mFlags, path);
    return -EIO;
  }
  if (resp.mError != 0) {
    // If the operation fails, the return payload will be empty;
    // adjust the iov_len for the following assertion.
    ios[1].iov_len = 0;
  }
  MOZ_ASSERT(static_cast<size_t>(recvd) == ios[0].iov_len + ios[1].iov_len);
  if (resp.mError == 0) {
    // Success!
    if (expectFd) {
      MOZ_ASSERT(openedFd >= 0);
      return openedFd;
    }
    return 0;
  }
  if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
    // Keep in mind that "rejected" files can include ones that don't
    // actually exist, if it's something that's optional or part of a
    // search path (e.g., shared libraries).  In those cases, this
    // error message is expected.
    SANDBOX_LOG_ERROR("Rejected errno %d op %d flags 0%o path %s",
                      resp.mError, aReq->mOp, aReq->mFlags, path);
  }
  if (openedFd >= 0) {
    close(openedFd);
  }
  return -resp.mError;
}
Example #25
0
void
SandboxEarlyInit(GeckoProcessType aType)
{
  const SandboxInfo info = SandboxInfo::Get();
  if (info.Test(SandboxInfo::kUnexpectedThreads)) {
    return;
  }
  MOZ_RELEASE_ASSERT(IsSingleThreaded());

  // Which kinds of resource isolation (of those that need to be set
  // up at this point) can be used by this process?
  bool canChroot = false;
  bool canUnshareNet = false;
  bool canUnshareIPC = false;

  switch (aType) {
  case GeckoProcessType_Default:
    MOZ_ASSERT(false, "SandboxEarlyInit in parent process");
    return;
#ifdef MOZ_GMP_SANDBOX
  case GeckoProcessType_GMPlugin:
    if (!info.Test(SandboxInfo::kEnabledForMedia)) {
      break;
    }
    canUnshareNet = true;
    canUnshareIPC = true;
    // Need seccomp-bpf to intercept open().
    canChroot = info.Test(SandboxInfo::kHasSeccompBPF);
    break;
#endif
    // In the future, content processes will be able to use some of
    // these.
  default:
    // Other cases intentionally left blank.
    break;
  }

  // If TSYNC is not supported, set up signal handler
  // used to enable seccomp on each thread.
  if (!info.Test(SandboxInfo::kHasSeccompTSync)) {
    gSeccompTsyncBroadcastSignum = FindFreeSignalNumber();
    if (gSeccompTsyncBroadcastSignum == 0) {
      SANDBOX_LOG_ERROR("No available signal numbers!");
      MOZ_CRASH();
    }

    void (*oldHandler)(int);
    oldHandler = signal(gSeccompTsyncBroadcastSignum, SetThreadSandboxHandler);
    if (oldHandler != SIG_DFL) {
      // See the comment on FindFreeSignalNumber about race conditions.
      SANDBOX_LOG_ERROR("signal %d in use by handler %p!\n",
        gSeccompTsyncBroadcastSignum, oldHandler);
      MOZ_CRASH();
    }
  }

  // If there's nothing to do, then we're done.
  if (!canChroot && !canUnshareNet && !canUnshareIPC) {
    return;
  }

  {
    LinuxCapabilities existingCaps;
    if (existingCaps.GetCurrent() && existingCaps.AnyEffective()) {
      SANDBOX_LOG_ERROR("PLEASE DO NOT RUN THIS AS ROOT.  Strange things may"
                        " happen when capabilities are dropped.");
    }
  }

  // If capabilities can't be gained, then nothing can be done.
  if (!info.Test(SandboxInfo::kHasUserNamespaces)) {
    // Drop any existing capabilities; unsharing the user namespace
    // would implicitly drop them, so if we're running in a broken
    // configuration where that would matter (e.g., running as root
    // from a non-root-owned mode-0700 directory) this means it will
    // break the same way on all kernels and be easier to troubleshoot.
    LinuxCapabilities().SetCurrent();
    return;
  }

  // The failure cases for the various unshares, and setting up the
  // chroot helper, don't strictly need to be fatal -- but they also
  // shouldn't fail on any reasonable system, so let's take the small
  // risk of breakage over the small risk of quietly providing less
  // security than we expect.  (Unlike in SandboxInfo, this is in the
  // child process, so crashing here isn't as severe a response to the
  // unexpected.)
  if (!UnshareUserNamespace()) {
    SANDBOX_LOG_ERROR("unshare(CLONE_NEWUSER): %s", strerror(errno));
    // If CanCreateUserNamespace (SandboxInfo.cpp) returns true, then
    // the unshare shouldn't have failed.
    MOZ_CRASH("unshare(CLONE_NEWUSER)");
  }
  // No early returns after this point!  We need to drop the
  // capabilities that were gained by unsharing the user namesapce.

  if (canUnshareIPC && syscall(__NR_unshare, CLONE_NEWIPC) != 0) {
    SANDBOX_LOG_ERROR("unshare(CLONE_NEWIPC): %s", strerror(errno));
    MOZ_CRASH("unshare(CLONE_NEWIPC)");
  }

  if (canUnshareNet && syscall(__NR_unshare, CLONE_NEWNET) != 0) {
    SANDBOX_LOG_ERROR("unshare(CLONE_NEWNET): %s", strerror(errno));
    MOZ_CRASH("unshare(CLONE_NEWNET)");
  }

  if (canChroot) {
    gChrootHelper = MakeUnique<SandboxChroot>();
    if (!gChrootHelper->Prepare()) {
      SANDBOX_LOG_ERROR("failed to set up chroot helper");
      MOZ_CRASH("SandboxChroot::Prepare");
    }
  }

  if (!LinuxCapabilities().SetCurrent()) {
    SANDBOX_LOG_ERROR("dropping capabilities: %s", strerror(errno));
    MOZ_CRASH("can't drop capabilities");
  }
}