summaryrefslogtreecommitdiffstats
path: root/security/sandbox/linux/SandboxInfo.cpp
blob: 2eb65e39c0a426cef815985869bc59dd81ed179b (plain)
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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
/* -*- 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 "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 {

// Bug 1229136: this is copied from ../SandboxUtil.cpp to avoid
// complicated build issues; renamespaced to avoid the possibility of
// symbol conflict.
namespace {

static bool
IsSingleThreaded()
{
  // This detects the thread count indirectly.  /proc/<pid>/task has a
  // subdirectory for each thread in <pid>'s thread group, and the
  // link count on the "task" directory follows Unix expectations: the
  // link from its parent, the "." link from itself, and the ".." link
  // from each subdirectory; thus, 2+N links for N threads.
  struct stat sb;
  if (stat("/proc/self/task", &sb) < 0) {
    MOZ_DIAGNOSTIC_ASSERT(false, "Couldn't access /proc/self/task!");
    return false;
  }
  MOZ_DIAGNOSTIC_ASSERT(sb.st_nlink >= 3);
  return sb.st_nlink == 3;
}

} // anonymous namespace

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';
  }

  // Valgrind might allow the clone, but doesn't know what to do with
  // unshare.  Check for that by unsharing nothing.  (Valgrind will
  // probably need sandboxing disabled entirely, but no need to break
  // things worse than strictly necessary.)
  if (syscall(__NR_unshare, 0) != 0) {
#ifdef MOZ_VALGRIND
    MOZ_ASSERT(errno == ENOSYS);
#else
    // If something else can cause that call to fail, we's like to know
    // about it; the right way to handle it might not be the same.
    MOZ_ASSERT(false);
#endif
    return false;
  }

  pid_t pid = syscall(__NR_clone, SIGCHLD | CLONE_NEWUSER,
                      nullptr, nullptr, nullptr, nullptr);
  if (pid == 0) {
    // In the child.  Do as little as possible.
    _exit(0);
  }
  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.
  bool waitpid_ok = HANDLE_EINTR(waitpid(pid, nullptr, 0)) == pid;
  MOZ_ASSERT(waitpid_ok);
  if (!waitpid_ok) {
    return false;
  }
  setenv(kCacheEnvName, "1", 1);
  return true;
}

/* static */
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;
    }
  }

  // Detect the threading-problem signal from the parent process.
  if (getenv("MOZ_SANDBOX_UNEXPECTED_THREADS")) {
    flags |= kUnexpectedThreads;
  } else {
    if (HasUserNamespaceSupport()) {
      flags |= kHasPrivilegedUserNamespaces;
      if (CanCreateUserNamespace()) {
        flags |= kHasUserNamespaces;
      }
    }
  }

  if (getenv("MOZ_SANDBOX_VERBOSE")) {
    flags |= kVerbose;
  }

  mFlags = static_cast<Flags>(flags);
}

/* 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);
}

} // namespace mozilla