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