Esempio n. 1
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;
}
Esempio n. 2
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");
  }
}