OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "sandbox/linux/suid/client/setuid_sandbox_client.h" | 5 #include "sandbox/linux/suid/client/setuid_sandbox_client.h" |
6 | 6 |
7 #include <fcntl.h> | 7 #include <fcntl.h> |
8 #include <stdlib.h> | |
9 #include <sys/socket.h> | |
10 #include <sys/stat.h> | 8 #include <sys/stat.h> |
11 #include <sys/types.h> | |
12 #include <sys/wait.h> | 9 #include <sys/wait.h> |
13 #include <unistd.h> | 10 #include <unistd.h> |
14 | 11 |
15 #include "base/command_line.h" | 12 #include <string> |
| 13 |
16 #include "base/environment.h" | 14 #include "base/environment.h" |
17 #include "base/files/file_path.h" | |
18 #include "base/files/file_util.h" | |
19 #include "base/files/scoped_file.h" | 15 #include "base/files/scoped_file.h" |
20 #include "base/logging.h" | 16 #include "base/logging.h" |
21 #include "base/macros.h" | |
22 #include "base/memory/scoped_ptr.h" | |
23 #include "base/path_service.h" | |
24 #include "base/posix/eintr_wrapper.h" | 17 #include "base/posix/eintr_wrapper.h" |
25 #include "base/process/launch.h" | |
26 #include "base/process/process_metrics.h" | |
27 #include "base/strings/string_number_conversions.h" | 18 #include "base/strings/string_number_conversions.h" |
28 #include "sandbox/linux/services/init_process_reaper.h" | |
29 #include "sandbox/linux/suid/common/sandbox.h" | 19 #include "sandbox/linux/suid/common/sandbox.h" |
30 #include "sandbox/linux/suid/common/suid_unsafe_environment_variables.h" | |
31 | 20 |
32 namespace { | 21 namespace { |
33 | 22 |
34 bool IsFileSystemAccessDenied() { | 23 bool IsFileSystemAccessDenied() { |
35 base::ScopedFD self_exe(HANDLE_EINTR(open(base::kProcSelfExe, O_RDONLY))); | 24 base::ScopedFD root_dir(HANDLE_EINTR(open("/", O_RDONLY))); |
36 return !self_exe.is_valid(); | 25 return !root_dir.is_valid(); |
37 } | |
38 | |
39 // Set an environment variable that reflects the API version we expect from the | |
40 // setuid sandbox. Old versions of the sandbox will ignore this. | |
41 void SetSandboxAPIEnvironmentVariable(base::Environment* env) { | |
42 env->SetVar(sandbox::kSandboxEnvironmentApiRequest, | |
43 base::IntToString(sandbox::kSUIDSandboxApiNumber)); | |
44 } | |
45 | |
46 // Unset environment variables that are expected to be set by the setuid | |
47 // sandbox. This is to allow nesting of one instance of the SUID sandbox | |
48 // inside another. | |
49 void UnsetExpectedEnvironmentVariables(base::EnvironmentMap* env_map) { | |
50 DCHECK(env_map); | |
51 const base::NativeEnvironmentString environment_vars[] = { | |
52 sandbox::kSandboxDescriptorEnvironmentVarName, | |
53 sandbox::kSandboxHelperPidEnvironmentVarName, | |
54 sandbox::kSandboxEnvironmentApiProvides, | |
55 sandbox::kSandboxPIDNSEnvironmentVarName, | |
56 sandbox::kSandboxNETNSEnvironmentVarName, | |
57 }; | |
58 | |
59 for (size_t i = 0; i < arraysize(environment_vars); ++i) { | |
60 // Setting values in EnvironmentMap to an empty-string will make | |
61 // sure that they get unset from the environment via AlterEnvironment(). | |
62 (*env_map)[environment_vars[i]] = base::NativeEnvironmentString(); | |
63 } | |
64 } | |
65 | |
66 // Wrapper around a shared C function. | |
67 // Returns the "saved" environment variable name corresponding to |envvar| | |
68 // in a new string or NULL. | |
69 std::string* CreateSavedVariableName(const char* env_var) { | |
70 char* const saved_env_var = SandboxSavedEnvironmentVariable(env_var); | |
71 if (!saved_env_var) | |
72 return NULL; | |
73 std::string* saved_env_var_copy = new std::string(saved_env_var); | |
74 // SandboxSavedEnvironmentVariable is the C function that we wrap and uses | |
75 // malloc() to allocate memory. | |
76 free(saved_env_var); | |
77 return saved_env_var_copy; | |
78 } | |
79 | |
80 // The ELF loader will clear many environment variables so we save them to | |
81 // different names here so that the SUID sandbox can resolve them for the | |
82 // renderer. | |
83 void SaveSUIDUnsafeEnvironmentVariables(base::Environment* env) { | |
84 for (unsigned i = 0; kSUIDUnsafeEnvironmentVariables[i]; ++i) { | |
85 const char* env_var = kSUIDUnsafeEnvironmentVariables[i]; | |
86 // Get the saved environment variable corresponding to envvar. | |
87 scoped_ptr<std::string> saved_env_var(CreateSavedVariableName(env_var)); | |
88 if (saved_env_var == NULL) | |
89 continue; | |
90 | |
91 std::string value; | |
92 if (env->GetVar(env_var, &value)) | |
93 env->SetVar(saved_env_var->c_str(), value); | |
94 else | |
95 env->UnSetVar(saved_env_var->c_str()); | |
96 } | |
97 } | 26 } |
98 | 27 |
99 int GetHelperApi(base::Environment* env) { | 28 int GetHelperApi(base::Environment* env) { |
100 std::string api_string; | 29 std::string api_string; |
101 int api_number = 0; // Assume API version 0 if no environment was found. | 30 int api_number = 0; // Assume API version 0 if no environment was found. |
102 if (env->GetVar(sandbox::kSandboxEnvironmentApiProvides, &api_string) && | 31 if (env->GetVar(sandbox::kSandboxEnvironmentApiProvides, &api_string) && |
103 !base::StringToInt(api_string, &api_number)) { | 32 !base::StringToInt(api_string, &api_number)) { |
104 // It's an error if we could not convert the API number. | 33 // It's an error if we could not convert the API number. |
105 api_number = -1; | 34 api_number = -1; |
106 } | 35 } |
(...skipping 14 matching lines...) Expand all Loading... |
121 | 50 |
122 pid_t GetHelperPID(base::Environment* env) { | 51 pid_t GetHelperPID(base::Environment* env) { |
123 return EnvToInt(env, sandbox::kSandboxHelperPidEnvironmentVarName); | 52 return EnvToInt(env, sandbox::kSandboxHelperPidEnvironmentVarName); |
124 } | 53 } |
125 | 54 |
126 // Get the IPC file descriptor used to communicate with the setuid helper. | 55 // Get the IPC file descriptor used to communicate with the setuid helper. |
127 int GetIPCDescriptor(base::Environment* env) { | 56 int GetIPCDescriptor(base::Environment* env) { |
128 return EnvToInt(env, sandbox::kSandboxDescriptorEnvironmentVarName); | 57 return EnvToInt(env, sandbox::kSandboxDescriptorEnvironmentVarName); |
129 } | 58 } |
130 | 59 |
131 const char* GetDevelSandboxPath() { | |
132 return getenv("CHROME_DEVEL_SANDBOX"); | |
133 } | |
134 | |
135 } // namespace | 60 } // namespace |
136 | 61 |
137 namespace sandbox { | 62 namespace sandbox { |
138 | 63 |
139 SetuidSandboxClient* SetuidSandboxClient::Create() { | 64 SetuidSandboxClient* SetuidSandboxClient::Create() { |
140 base::Environment* environment(base::Environment::Create()); | 65 base::Environment* environment(base::Environment::Create()); |
141 SetuidSandboxClient* sandbox_client(new SetuidSandboxClient); | |
142 | |
143 CHECK(environment); | 66 CHECK(environment); |
144 sandbox_client->env_ = environment; | 67 return new SetuidSandboxClient(environment); |
145 return sandbox_client; | |
146 } | 68 } |
147 | 69 |
148 SetuidSandboxClient::SetuidSandboxClient() | 70 SetuidSandboxClient::SetuidSandboxClient(base::Environment* env) |
149 : env_(NULL), | 71 : env_(env), sandboxed_(false) { |
150 sandboxed_(false) { | |
151 } | 72 } |
152 | 73 |
153 SetuidSandboxClient::~SetuidSandboxClient() { | 74 SetuidSandboxClient::~SetuidSandboxClient() { |
154 delete env_; | |
155 } | 75 } |
156 | 76 |
157 void SetuidSandboxClient::CloseDummyFile() { | 77 void SetuidSandboxClient::CloseDummyFile() { |
158 // When we're launched through the setuid sandbox, SetupLaunchOptions | 78 // When we're launched through the setuid sandbox, SetupLaunchOptions |
159 // arranges for kZygoteIdFd to be a dummy file descriptor to satisfy an | 79 // arranges for kZygoteIdFd to be a dummy file descriptor to satisfy an |
160 // ancient setuid sandbox ABI requirement. However, the descriptor is no | 80 // ancient setuid sandbox ABI requirement. However, the descriptor is no |
161 // longer needed, so we can simply close it right away now. | 81 // longer needed, so we can simply close it right away now. |
162 CHECK(IsSuidSandboxChild()); | 82 CHECK(IsSuidSandboxChild()); |
163 | 83 |
164 // Sanity check that kZygoteIdFd refers to a pipe. | 84 // Sanity check that kZygoteIdFd refers to a pipe. |
165 struct stat st; | 85 struct stat st; |
166 PCHECK(0 == fstat(kZygoteIdFd, &st)); | 86 PCHECK(0 == fstat(kZygoteIdFd, &st)); |
167 CHECK(S_ISFIFO(st.st_mode)); | 87 CHECK(S_ISFIFO(st.st_mode)); |
168 | 88 |
169 PCHECK(0 == IGNORE_EINTR(close(kZygoteIdFd))); | 89 PCHECK(0 == IGNORE_EINTR(close(kZygoteIdFd))); |
170 } | 90 } |
171 | 91 |
172 bool SetuidSandboxClient::ChrootMe() { | 92 bool SetuidSandboxClient::ChrootMe() { |
173 int ipc_fd = GetIPCDescriptor(env_); | 93 int ipc_fd = GetIPCDescriptor(env_.get()); |
174 | 94 |
175 if (ipc_fd < 0) { | 95 if (ipc_fd < 0) { |
176 LOG(ERROR) << "Failed to obtain the sandbox IPC descriptor"; | 96 LOG(ERROR) << "Failed to obtain the sandbox IPC descriptor"; |
177 return false; | 97 return false; |
178 } | 98 } |
179 | 99 |
180 if (HANDLE_EINTR(write(ipc_fd, &kMsgChrootMe, 1)) != 1) { | 100 if (HANDLE_EINTR(write(ipc_fd, &kMsgChrootMe, 1)) != 1) { |
181 PLOG(ERROR) << "Failed to write to chroot pipe"; | 101 PLOG(ERROR) << "Failed to write to chroot pipe"; |
182 return false; | 102 return false; |
183 } | 103 } |
184 | 104 |
185 // We need to reap the chroot helper process in any event. | 105 // We need to reap the chroot helper process in any event. |
186 pid_t helper_pid = GetHelperPID(env_); | 106 pid_t helper_pid = GetHelperPID(env_.get()); |
187 // If helper_pid is -1 we wait for any child. | 107 // If helper_pid is -1 we wait for any child. |
188 if (HANDLE_EINTR(waitpid(helper_pid, NULL, 0)) < 0) { | 108 if (HANDLE_EINTR(waitpid(helper_pid, NULL, 0)) < 0) { |
189 PLOG(ERROR) << "Failed to wait for setuid helper to die"; | 109 PLOG(ERROR) << "Failed to wait for setuid helper to die"; |
190 return false; | 110 return false; |
191 } | 111 } |
192 | 112 |
193 char reply; | 113 char reply; |
194 if (HANDLE_EINTR(read(ipc_fd, &reply, 1)) != 1) { | 114 if (HANDLE_EINTR(read(ipc_fd, &reply, 1)) != 1) { |
195 PLOG(ERROR) << "Failed to read from chroot pipe"; | 115 PLOG(ERROR) << "Failed to read from chroot pipe"; |
196 return false; | 116 return false; |
197 } | 117 } |
198 | 118 |
199 if (reply != kMsgChrootSuccessful) { | 119 if (reply != kMsgChrootSuccessful) { |
200 LOG(ERROR) << "Error code reply from chroot helper"; | 120 LOG(ERROR) << "Error code reply from chroot helper"; |
201 return false; | 121 return false; |
202 } | 122 } |
203 | 123 |
204 // We now consider ourselves "fully sandboxed" as far as the | 124 // We now consider ourselves "fully sandboxed" as far as the |
205 // setuid sandbox is concerned. | 125 // setuid sandbox is concerned. |
206 CHECK(IsFileSystemAccessDenied()); | 126 CHECK(IsFileSystemAccessDenied()); |
207 sandboxed_ = true; | 127 sandboxed_ = true; |
208 return true; | 128 return true; |
209 } | 129 } |
210 | 130 |
211 bool SetuidSandboxClient::CreateInitProcessReaper( | |
212 base::Closure* post_fork_parent_callback) { | |
213 return sandbox::CreateInitProcessReaper(post_fork_parent_callback); | |
214 } | |
215 | |
216 bool SetuidSandboxClient::IsSuidSandboxUpToDate() const { | 131 bool SetuidSandboxClient::IsSuidSandboxUpToDate() const { |
217 return GetHelperApi(env_) == kSUIDSandboxApiNumber; | 132 return GetHelperApi(env_.get()) == kSUIDSandboxApiNumber; |
218 } | 133 } |
219 | 134 |
220 bool SetuidSandboxClient::IsSuidSandboxChild() const { | 135 bool SetuidSandboxClient::IsSuidSandboxChild() const { |
221 return GetIPCDescriptor(env_) >= 0; | 136 return GetIPCDescriptor(env_.get()) >= 0; |
222 } | 137 } |
223 | 138 |
224 bool SetuidSandboxClient::IsInNewPIDNamespace() const { | 139 bool SetuidSandboxClient::IsInNewPIDNamespace() const { |
225 return env_->HasVar(kSandboxPIDNSEnvironmentVarName); | 140 return env_->HasVar(kSandboxPIDNSEnvironmentVarName); |
226 } | 141 } |
227 | 142 |
228 bool SetuidSandboxClient::IsInNewNETNamespace() const { | 143 bool SetuidSandboxClient::IsInNewNETNamespace() const { |
229 return env_->HasVar(kSandboxNETNSEnvironmentVarName); | 144 return env_->HasVar(kSandboxNETNSEnvironmentVarName); |
230 } | 145 } |
231 | 146 |
232 bool SetuidSandboxClient::IsSandboxed() const { | 147 bool SetuidSandboxClient::IsSandboxed() const { |
233 return sandboxed_; | 148 return sandboxed_; |
234 } | 149 } |
235 | 150 |
236 // Check if CHROME_DEVEL_SANDBOX is set but empty. This currently disables | |
237 // the setuid sandbox. TODO(jln): fix this (crbug.com/245376). | |
238 bool SetuidSandboxClient::IsDisabledViaEnvironment() { | |
239 const char* devel_sandbox_path = GetDevelSandboxPath(); | |
240 if (devel_sandbox_path && '\0' == *devel_sandbox_path) { | |
241 return true; | |
242 } | |
243 return false; | |
244 } | |
245 | |
246 base::FilePath SetuidSandboxClient::GetSandboxBinaryPath() { | |
247 base::FilePath sandbox_binary; | |
248 base::FilePath exe_dir; | |
249 if (PathService::Get(base::DIR_EXE, &exe_dir)) { | |
250 base::FilePath sandbox_candidate = exe_dir.AppendASCII("chrome-sandbox"); | |
251 if (base::PathExists(sandbox_candidate)) | |
252 sandbox_binary = sandbox_candidate; | |
253 } | |
254 | |
255 // In user-managed builds, including development builds, an environment | |
256 // variable is required to enable the sandbox. See | |
257 // http://code.google.com/p/chromium/wiki/LinuxSUIDSandboxDevelopment | |
258 struct stat st; | |
259 if (sandbox_binary.empty() && stat(base::kProcSelfExe, &st) == 0 && | |
260 st.st_uid == getuid()) { | |
261 const char* devel_sandbox_path = GetDevelSandboxPath(); | |
262 if (devel_sandbox_path) { | |
263 sandbox_binary = base::FilePath(devel_sandbox_path); | |
264 } | |
265 } | |
266 | |
267 return sandbox_binary; | |
268 } | |
269 | |
270 void SetuidSandboxClient::PrependWrapper(base::CommandLine* cmd_line) { | |
271 std::string sandbox_binary(GetSandboxBinaryPath().value()); | |
272 struct stat st; | |
273 if (sandbox_binary.empty() || stat(sandbox_binary.c_str(), &st) != 0) { | |
274 LOG(FATAL) << "The SUID sandbox helper binary is missing: " | |
275 << sandbox_binary << " Aborting now. See " | |
276 "https://code.google.com/p/chromium/wiki/" | |
277 "LinuxSUIDSandboxDevelopment."; | |
278 } | |
279 | |
280 if (access(sandbox_binary.c_str(), X_OK) != 0 || (st.st_uid != 0) || | |
281 ((st.st_mode & S_ISUID) == 0) || ((st.st_mode & S_IXOTH)) == 0) { | |
282 LOG(FATAL) << "The SUID sandbox helper binary was found, but is not " | |
283 "configured correctly. Rather than run without sandboxing " | |
284 "I'm aborting now. You need to make sure that " | |
285 << sandbox_binary << " is owned by root and has mode 4755."; | |
286 } | |
287 | |
288 cmd_line->PrependWrapper(sandbox_binary); | |
289 } | |
290 | |
291 void SetuidSandboxClient::SetupLaunchOptions( | |
292 base::LaunchOptions* options, | |
293 base::FileHandleMappingVector* fds_to_remap, | |
294 base::ScopedFD* dummy_fd) { | |
295 DCHECK(options); | |
296 DCHECK(fds_to_remap); | |
297 | |
298 // Launching a setuid binary requires PR_SET_NO_NEW_PRIVS to not be used. | |
299 options->allow_new_privs = true; | |
300 UnsetExpectedEnvironmentVariables(&options->environ); | |
301 | |
302 // Set dummy_fd to the reading end of a closed pipe. | |
303 int pipe_fds[2]; | |
304 PCHECK(0 == pipe(pipe_fds)); | |
305 PCHECK(0 == IGNORE_EINTR(close(pipe_fds[1]))); | |
306 dummy_fd->reset(pipe_fds[0]); | |
307 | |
308 // We no longer need a dummy socket for discovering the child's PID, | |
309 // but the sandbox is still hard-coded to expect a file descriptor at | |
310 // kZygoteIdFd. Fixing this requires a sandbox API change. :( | |
311 fds_to_remap->push_back(std::make_pair(dummy_fd->get(), kZygoteIdFd)); | |
312 } | |
313 | |
314 void SetuidSandboxClient::SetupLaunchEnvironment() { | |
315 SaveSUIDUnsafeEnvironmentVariables(env_); | |
316 SetSandboxAPIEnvironmentVariable(env_); | |
317 } | |
318 | |
319 } // namespace sandbox | 151 } // namespace sandbox |
OLD | NEW |