summary refs log tree commit diff
path: root/pkgs/applications/networking/browsers/chromium/sandbox_userns_29.patch
blob: c1987c43e150c34790bf7c280d7345ab98d8d781 (plain) (blame)
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
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
commit 557daf9cc9c02b8f17e6ee84f9b1ae6e6132d478
Author: aszlig <aszlig@redmoonstudios.org>
Date:   Thu May 16 14:17:56 2013 +0200

    zygote: Add support for user namespaces on Linux.
    
    The implementation is done by patching the Zygote host to execute the sandbox
    binary with CLONE_NEWUSER and setting the uid and gid mapping so that the child
    process is using uid 0 and gid 0 which map to the current user of the parent.
    Afterwards, the sandbox will continue as if it was called as a setuid binary.
    
    In addition, this adds new_user_namespace as an option in process_util in order
    to set the UID and GID mapping correctly. The reason for this is that just
    passing CLONE_NEWUSER to clone_flags doesn't help in LaunchProcess(), because
    without setting the mappings exec*() will clear the process's capability sets.
    
    If the kernel doesn't support unprivileged user namespaces and the sandbox
    binary doesn't have the setuid flag, the Zygote main process will run without a
    sandbox. This is to mimic the behaviour if no SUID sandbox binary path is set.
    
    Signed-off-by: aszlig <aszlig@redmoonstudios.org>

diff --git a/base/process_util.h b/base/process_util.h
index 0bec8e0..6b6f566 100644
--- a/base/process_util.h
+++ b/base/process_util.h
@@ -175,6 +175,7 @@ struct LaunchOptions {
         new_process_group(false)
 #if defined(OS_LINUX)
         , clone_flags(0)
+        , new_user_namespace(false)
 #endif  // OS_LINUX
 #if defined(OS_CHROMEOS)
         , ctrl_terminal_fd(-1)
@@ -249,6 +250,9 @@ struct LaunchOptions {
 #if defined(OS_LINUX)
   // If non-zero, start the process using clone(), using flags as provided.
   int clone_flags;
+
+  // If true, start the process in a new user namespace.
+  bool new_user_namespace;
 #endif  // defined(OS_LINUX)
 
 #if defined(OS_CHROMEOS)
diff --git a/base/process_util_posix.cc b/base/process_util_posix.cc
index 83afe44..e529b2b 100644
--- a/base/process_util_posix.cc
+++ b/base/process_util_posix.cc
@@ -34,6 +34,13 @@
 #include "base/threading/platform_thread.h"
 #include "base/threading/thread_restrictions.h"
 
+#if defined(OS_LINUX)
+#include <sched.h>
+#if !defined(CLONE_NEWUSER)
+#define CLONE_NEWUSER 0x10000000
+#endif
+#endif
+
 #if defined(OS_CHROMEOS)
 #include <sys/ioctl.h>
 #endif
@@ -621,8 +628,19 @@ bool LaunchProcess(const std::vector<std::string>& argv,
 
   pid_t pid;
 #if defined(OS_LINUX)
-  if (options.clone_flags) {
-    pid = syscall(__NR_clone, options.clone_flags, 0, 0, 0);
+  int map_pipe_fd[2];
+  int flags = options.clone_flags;
+
+  if (options.new_user_namespace) {
+    flags |= CLONE_NEWUSER;
+    if (pipe(map_pipe_fd) < 0) {
+      DPLOG(ERROR) << "user namespace pipe";
+      return false;
+    }
+  }
+
+  if (options.clone_flags || options.new_user_namespace) {
+    pid = syscall(__NR_clone, flags, 0, 0, 0);
   } else
 #endif
   {
@@ -635,6 +653,21 @@ bool LaunchProcess(const std::vector<std::string>& argv,
   } else if (pid == 0) {
     // Child process
 
+#if defined(OS_LINUX)
+    if (options.new_user_namespace) {
+      // Close the write end of the pipe so we get an EOF when the parent closes
+      // the FD. This is to avoid race conditions when the UID/GID mappings are
+      // written _after_ execvp().
+      close(map_pipe_fd[1]);
+
+      char dummy;
+      if (HANDLE_EINTR(read(map_pipe_fd[0], &dummy, 1)) != 0) {
+        RAW_LOG(ERROR, "Unexpected input in uid/gid mapping pipe.");
+        _exit(127);
+      }
+    }
+#endif
+
     // DANGER: fork() rule: in the child, if you don't end up doing exec*(),
     // you call _exit() instead of exit(). This is because _exit() does not
     // call any previously-registered (in the parent) exit handlers, which
@@ -749,6 +782,40 @@ bool LaunchProcess(const std::vector<std::string>& argv,
     _exit(127);
   } else {
     // Parent process
+#if defined(OS_LINUX)
+    if (options.new_user_namespace) {
+      // We need to write UID/GID mapping here to map the current user outside
+      // the namespace to the root user inside the namespace in order to
+      // correctly "fool" the child process.
+      char buf[256];
+      int map_fd, map_len;
+
+      snprintf(buf, sizeof(buf), "/proc/%d/uid_map", pid);
+      map_fd = open(buf, O_RDWR);
+      DPCHECK(map_fd >= 0);
+      snprintf(buf, sizeof(buf), "0 %d 1", geteuid());
+      map_len = strlen(buf);
+      if (write(map_fd, buf, map_len) != map_len) {
+        RAW_LOG(WARNING, "Can't write to uid_map.");
+      }
+      close(map_fd);
+
+      snprintf(buf, sizeof(buf), "/proc/%d/gid_map", pid);
+      map_fd = open(buf, O_RDWR);
+      DPCHECK(map_fd >= 0);
+      snprintf(buf, sizeof(buf), "0 %d 1", getegid());
+      map_len = strlen(buf);
+      if (write(map_fd, buf, map_len) != map_len) {
+        RAW_LOG(WARNING, "Can't write to gid_map.");
+      }
+      close(map_fd);
+
+      // Close the pipe on the parent, so the child can continue doing the
+      // execvp() call.
+      close(map_pipe_fd[1]);
+    }
+#endif
+
     if (options.wait) {
       // While this isn't strictly disk IO, waiting for another process to
       // finish is the sort of thing ThreadRestrictions is trying to prevent.
diff --git a/content/browser/zygote_host/zygote_host_impl_linux.cc b/content/browser/zygote_host/zygote_host_impl_linux.cc
index 130f44a..c1232d4 100644
--- a/content/browser/zygote_host/zygote_host_impl_linux.cc
+++ b/content/browser/zygote_host/zygote_host_impl_linux.cc
@@ -118,25 +118,31 @@ void ZygoteHostImpl::Init(const std::string& sandbox_cmd) {
 
   sandbox_binary_ = sandbox_cmd.c_str();
 
-  // A non empty sandbox_cmd means we want a SUID sandbox.
-  using_suid_sandbox_ = !sandbox_cmd.empty();
+  bool userns_sandbox = false;
+  const std::vector<std::string> cmd_line_unwrapped(cmd_line.argv());
 
-  if (using_suid_sandbox_) {
+  if (!sandbox_cmd.empty()) {
     struct stat st;
     if (stat(sandbox_binary_.c_str(), &st) != 0) {
       LOG(FATAL) << "The SUID sandbox helper binary is missing: "
                  << sandbox_binary_ << " Aborting now.";
     }
 
-    if (access(sandbox_binary_.c_str(), X_OK) == 0 &&
-        (st.st_uid == 0) &&
-        (st.st_mode & S_ISUID) &&
-        (st.st_mode & S_IXOTH)) {
+    if (access(sandbox_binary_.c_str(), X_OK) == 0) {
+      using_suid_sandbox_ = true;
+
       cmd_line.PrependWrapper(sandbox_binary_);
 
       scoped_ptr<sandbox::SetuidSandboxClient>
           sandbox_client(sandbox::SetuidSandboxClient::Create());
       sandbox_client->SetupLaunchEnvironment();
+
+      if (!((st.st_uid == 0) &&
+            (st.st_mode & S_ISUID) &&
+            (st.st_mode & S_IXOTH))) {
+        userns_sandbox = true;
+        sandbox_client->SetNoSuid();
+      }
     } else {
       LOG(FATAL) << "The SUID sandbox helper binary was found, but is not "
                     "configured correctly. Rather than run without sandboxing "
@@ -160,7 +166,19 @@ void ZygoteHostImpl::Init(const std::string& sandbox_cmd) {
   base::ProcessHandle process = -1;
   base::LaunchOptions options;
   options.fds_to_remap = &fds_to_map;
+  if (userns_sandbox)
+    options.new_user_namespace = true;
   base::LaunchProcess(cmd_line.argv(), options, &process);
+
+  if (process == -1 && userns_sandbox) {
+    LOG(ERROR) << "User namespace sandbox failed to start, running without "
+               << "sandbox! You need at least kernel 3.8.0 with CONFIG_USER_NS "
+               << "enabled in order to use the sandbox without setuid bit.";
+    using_suid_sandbox_ = false;
+    options.new_user_namespace = false;
+    base::LaunchProcess(cmd_line_unwrapped, options, &process);
+  }
+
   CHECK(process != -1) << "Failed to launch zygote process";
 
   if (using_suid_sandbox_) {
diff --git a/content/zygote/zygote_main_linux.cc b/content/zygote/zygote_main_linux.cc
index 7d01722..2f445ef 100644
--- a/content/zygote/zygote_main_linux.cc
+++ b/content/zygote/zygote_main_linux.cc
@@ -395,6 +395,13 @@ static bool EnterSandbox(sandbox::SetuidSandboxClient* setuid_sandbox,
       *has_started_new_init = true;
     }
 
+    // Don't set non-dumpable, as it causes trouble when the host tries to find
+    // the zygote process (XXX: Not quite sure why this happens with user
+    // namespaces). Fortunately, we also have the seccomp filter sandbox which
+    // should disallow the use of ptrace.
+    if (setuid_sandbox->IsNoSuid())
+      return true;
+
 #if !defined(OS_OPENBSD)
     // Previously, we required that the binary be non-readable. This causes the
     // kernel to mark the process as non-dumpable at startup. The thinking was
diff --git a/sandbox/linux/suid/client/setuid_sandbox_client.cc b/sandbox/linux/suid/client/setuid_sandbox_client.cc
index 34231d4..36e3201 100644
--- a/sandbox/linux/suid/client/setuid_sandbox_client.cc
+++ b/sandbox/linux/suid/client/setuid_sandbox_client.cc
@@ -166,6 +166,10 @@ bool SetuidSandboxClient::IsInNewNETNamespace() const {
   return env_->HasVar(kSandboxNETNSEnvironmentVarName);
 }
 
+bool SetuidSandboxClient::IsNoSuid() const {
+  return env_->HasVar(kSandboxNoSuidVarName);
+}
+
 bool SetuidSandboxClient::IsSandboxed() const {
   return sandboxed_;
 }
@@ -175,5 +179,9 @@ void SetuidSandboxClient::SetupLaunchEnvironment() {
   SetSandboxAPIEnvironmentVariable(env_);
 }
 
+void SetuidSandboxClient::SetNoSuid() {
+  env_->SetVar(kSandboxNoSuidVarName, "1");
+}
+
 }  // namespace sandbox
 
diff --git a/sandbox/linux/suid/client/setuid_sandbox_client.h b/sandbox/linux/suid/client/setuid_sandbox_client.h
index a9f6536..2e8113a 100644
--- a/sandbox/linux/suid/client/setuid_sandbox_client.h
+++ b/sandbox/linux/suid/client/setuid_sandbox_client.h
@@ -39,6 +39,8 @@ class SetuidSandboxClient {
   bool IsInNewPIDNamespace() const;
   // Did the setuid helper create a new network namespace ?
   bool IsInNewNETNamespace() const;
+  // Is sandboxed without SUID binary ?
+  bool IsNoSuid() const;
   // Are we done and fully sandboxed ?
   bool IsSandboxed() const;
 
@@ -46,6 +48,8 @@ class SetuidSandboxClient {
   // helper.
   void SetupLaunchEnvironment();
 
+  void SetNoSuid();
+
  private:
   // Holds the environment. Will never be NULL.
   base::Environment* env_;
diff --git a/sandbox/linux/suid/common/sandbox.h b/sandbox/linux/suid/common/sandbox.h
index aad4ff8..bd710d5 100644
--- a/sandbox/linux/suid/common/sandbox.h
+++ b/sandbox/linux/suid/common/sandbox.h
@@ -18,6 +18,7 @@ static const char kAdjustLowMemMarginSwitch[] = "--adjust-low-mem";
 
 static const char kSandboxDescriptorEnvironmentVarName[] = "SBX_D";
 static const char kSandboxHelperPidEnvironmentVarName[] = "SBX_HELPER_PID";
+static const char kSandboxNoSuidVarName[] = "SBX_NO_SUID";
 
 static const long kSUIDSandboxApiNumber = 1;
 static const char kSandboxEnvironmentApiRequest[] = "SBX_CHROME_API_RQ";