1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "SandboxInfo.h"
#include "SandboxLogging.h"
#include "LinuxSched.h"
#include <errno.h>
#include <stdlib.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <unistd.h>
#include "base/posix/eintr_wrapper.h"
#include "mozilla/Assertions.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/SandboxSettings.h"
#include "sandbox/linux/system_headers/linux_seccomp.h"
#include "sandbox/linux/system_headers/linux_syscalls.h"
#ifdef MOZ_VALGRIND
# include <valgrind/valgrind.h>
#endif
// A note about assertions: in general, the worst thing this module
// should be able to do is disable sandboxing features, so release
// asserts or MOZ_CRASH should be avoided, even for seeming
// impossibilities like an unimplemented syscall returning success
// (which has happened: https://crbug.com/439795 ).
//
// MOZ_DIAGNOSTIC_ASSERT (debug builds, plus Nightly/Aurora non-debug)
// is probably the best choice for conditions that shouldn't be able
// to fail without the help of bugs in the kernel or system libraries.
//
// Regardless of assertion type, whatever condition caused it to fail
// should generally also disable the corresponding feature on builds
// that omit the assertion.
namespace mozilla {
static bool HasSeccompBPF() {
// Allow simulating the absence of seccomp-bpf support, for testing.
if (getenv("MOZ_FAKE_NO_SANDBOX")) {
return false;
}
// Valgrind and the sandbox don't interact well, probably because Valgrind
// does various system calls which aren't allowed, even if Firefox itself
// is playing by the rules.
#if defined(MOZ_VALGRIND)
if (RUNNING_ON_VALGRIND) {
return false;
}
#endif
// Determine whether seccomp-bpf is supported by trying to
// enable it with an invalid pointer for the filter. This will
// fail with EFAULT if supported and EINVAL if not, without
// changing the process's state.
int rv = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, nullptr);
MOZ_DIAGNOSTIC_ASSERT(rv == -1,
"prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER,"
" nullptr) didn't fail");
MOZ_DIAGNOSTIC_ASSERT(errno == EFAULT || errno == EINVAL);
return rv == -1 && errno == EFAULT;
}
static bool HasSeccompTSync() {
// Similar to above, but for thread-sync mode. See also Chromium's
// sandbox::SandboxBPF::SupportsSeccompThreadFilterSynchronization
if (getenv("MOZ_FAKE_NO_SECCOMP_TSYNC")) {
return false;
}
int rv = syscall(__NR_seccomp, SECCOMP_SET_MODE_FILTER,
SECCOMP_FILTER_FLAG_TSYNC, nullptr);
MOZ_DIAGNOSTIC_ASSERT(rv == -1,
"seccomp(..., SECCOMP_FILTER_FLAG_TSYNC,"
" nullptr) didn't fail");
MOZ_DIAGNOSTIC_ASSERT(errno == EFAULT || errno == EINVAL || errno == ENOSYS);
return rv == -1 && errno == EFAULT;
}
static bool HasUserNamespaceSupport() {
// Note: the /proc/<pid>/ns/* files track setns(2) support, which in
// some cases (e.g., pid) significantly postdates kernel support for
// the namespace type, so in general this type of check could be a
// false negative. However, for user namespaces, any kernel new
// enough for the feature to be usable for us has setns support
// (v3.8), so this is okay.
//
// The non-user namespaces all default to "y" in init/Kconfig, but
// check them explicitly in case someone has a weird custom config.
static const char* const paths[] = {
"/proc/self/ns/user",
"/proc/self/ns/pid",
"/proc/self/ns/net",
"/proc/self/ns/ipc",
};
for (size_t i = 0; i < ArrayLength(paths); ++i) {
if (access(paths[i], F_OK) == -1) {
MOZ_ASSERT(errno == ENOENT);
return false;
}
}
return true;
}
static bool CanCreateUserNamespace() {
// Unfortunately, the only way to verify that this process can
// create a new user namespace is to actually create one; because
// this process's namespaces shouldn't be side-effected (yet), it's
// necessary to clone (and collect) a child process. See also
// Chromium's sandbox::Credentials::SupportsNewUserNS.
//
// This is somewhat more expensive than the other tests, so it's
// cached in the environment to prevent child processes from having
// to re-run the test.
//
// This is run at static initializer time, while single-threaded, so
// locking isn't needed to access the environment.
static const char kCacheEnvName[] = "MOZ_ASSUME_USER_NS";
const char* cached = getenv(kCacheEnvName);
if (cached) {
return cached[0] > '0';
}
// Bug 1434528: In addition to CLONE_NEWUSER, do something that uses
// the new capabilities (in this case, cloning another namespace) to
// detect AppArmor policies that allow CLONE_NEWUSER but don't allow
// doing anything useful with it.
//
// Bug 1884347: There's a new AppArmor feature which can result in
// unsharing NEWUSER and NEWPID (or NEWNET etc.) in one syscall
// being allowed, but further use of capabilities will be blocked
// afterwards. That may be a bug, but we need to handle it.
pid_t pid = syscall(__NR_clone, SIGCHLD | CLONE_NEWUSER, nullptr, nullptr,
nullptr, nullptr);
if (pid == 0) {
// The exact meaning of `unshare(CLONE_NEWPID)` is slightly
// counterintuitive but in this case it doesn't matter. This just
// needs to be some operation that attempts to use capabilities,
// to check if it's blocked by an LSM.
int rv = unshare(CLONE_NEWPID);
// Exit with status 0 on success, 1 on failure.
_exit(rv == 0 ? 0 : 1);
}
if (pid == -1) {
// Failure.
MOZ_ASSERT(errno == EINVAL || // unsupported
errno == EPERM || // root-only, or we're already chrooted
errno == EUSERS); // already at user namespace nesting limit
setenv(kCacheEnvName, "0", 1);
return false;
}
// Otherwise, in the parent and successful.
int wstatus;
bool waitpid_ok = HANDLE_EINTR(waitpid(pid, &wstatus, 0)) == pid;
MOZ_ASSERT(waitpid_ok);
if (!waitpid_ok) {
return false;
}
// Check for failures reported by the child process.
if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) {
setenv(kCacheEnvName, "0", 1);
return false;
}
setenv(kCacheEnvName, "1", 1);
return true;
}
/* static */
const SandboxInfo SandboxInfo::sSingleton = SandboxInfo();
SandboxInfo::SandboxInfo() {
int flags = 0;
static_assert(sizeof(flags) >= sizeof(Flags), "enum Flags fits in an int");
if (HasSeccompBPF()) {
flags |= kHasSeccompBPF;
if (HasSeccompTSync()) {
flags |= kHasSeccompTSync;
}
}
if (HasUserNamespaceSupport()) {
flags |= kHasPrivilegedUserNamespaces;
if (CanCreateUserNamespace()) {
flags |= kHasUserNamespaces;
}
}
// We can't use mozilla::IsContentSandboxEnabled() here because a)
// libmozsandbox can't depend on libxul, and b) this is called in a static
// initializer before the prefences service is ready.
if (!getenv("MOZ_DISABLE_CONTENT_SANDBOX")) {
flags |= kEnabledForContent;
}
if (getenv("MOZ_PERMISSIVE_CONTENT_SANDBOX")) {
flags |= kPermissive;
}
if (!getenv("MOZ_DISABLE_GMP_SANDBOX")) {
flags |= kEnabledForMedia;
}
if (getenv("MOZ_SANDBOX_LOGGING")) {
flags |= kVerbose;
}
mFlags = static_cast<Flags>(flags);
}
} // namespace mozilla
|