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
|
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/351564777): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "sandbox/linux/suid/client/setuid_sandbox_host.h"
#include <fcntl.h>
#include <stddef.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <array>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include "base/command_line.h"
#include "base/environment.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_file.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/path_service.h"
#include "base/posix/eintr_wrapper.h"
#include "base/process/launch.h"
#include "base/process/process_metrics.h"
#include "base/strings/cstring_view.h"
#include "base/strings/string_number_conversions.h"
#include "sandbox/linux/suid/common/sandbox.h"
#include "sandbox/linux/suid/common/suid_unsafe_environment_variables.h"
namespace sandbox {
namespace {
// Set an environment variable that reflects the API version we expect from the
// setuid sandbox. Old versions of the sandbox will ignore this.
void SetSandboxAPIEnvironmentVariable(base::Environment* env) {
env->SetVar(kSandboxEnvironmentApiRequest,
base::NumberToString(kSUIDSandboxApiNumber));
}
// Unset environment variables that are expected to be set by the setuid
// sandbox. This is to allow nesting of one instance of the SUID sandbox
// inside another.
void UnsetExpectedEnvironmentVariables(base::EnvironmentMap* env_map) {
DCHECK(env_map);
const auto environment_vars = std::to_array<base::NativeEnvironmentString>({
kSandboxDescriptorEnvironmentVarName,
kSandboxHelperPidEnvironmentVarName,
kSandboxEnvironmentApiProvides,
kSandboxPIDNSEnvironmentVarName,
kSandboxNETNSEnvironmentVarName,
});
for (size_t i = 0; i < std::size(environment_vars); ++i) {
// Setting values in EnvironmentMap to an empty-string will make
// sure that they get unset from the environment via AlterEnvironment().
(*env_map)[environment_vars[i]] = base::NativeEnvironmentString();
}
}
// Wrapper around a shared C function.
// Returns the "saved" environment variable name corresponding to |envvar|
// in a new string or NULL.
std::string* CreateSavedVariableName(base::cstring_view env_var) {
char* const saved_env_var = SandboxSavedEnvironmentVariable(env_var.c_str());
if (!saved_env_var)
return nullptr;
std::string* saved_env_var_copy = new std::string(saved_env_var);
// SandboxSavedEnvironmentVariable is the C function that we wrap and uses
// malloc() to allocate memory.
free(saved_env_var);
return saved_env_var_copy;
}
// The ELF loader will clear many environment variables so we save them to
// different names here so that the SUID sandbox can resolve them for the
// renderer.
void SaveSUIDUnsafeEnvironmentVariables(base::Environment* env) {
for (unsigned i = 0; kSUIDUnsafeEnvironmentVariables[i]; ++i) {
const base::cstring_view env_var(kSUIDUnsafeEnvironmentVariables[i]);
// Get the saved environment variable corresponding to envvar.
std::unique_ptr<std::string> saved_env_var(
CreateSavedVariableName(env_var));
if (!saved_env_var)
continue;
std::optional<std::string> value = env->GetVar(env_var);
if (value.has_value()) {
env->SetVar(*saved_env_var, *value);
} else {
env->UnSetVar(*saved_env_var);
}
}
}
const char* GetDevelSandboxPath() {
return getenv("CHROME_DEVEL_SANDBOX");
}
} // namespace
std::unique_ptr<SetuidSandboxHost> SetuidSandboxHost::Create() {
// Private constructor.
return base::WrapUnique(new SetuidSandboxHost(base::Environment::Create()));
}
SetuidSandboxHost::SetuidSandboxHost(std::unique_ptr<base::Environment> env)
: env_(std::move(env)) {
DCHECK(env_);
}
SetuidSandboxHost::~SetuidSandboxHost() = default;
// Check if CHROME_DEVEL_SANDBOX is set but empty. This currently disables
// the setuid sandbox. TODO(jln): fix this (crbug.com/245376).
bool SetuidSandboxHost::IsDisabledViaEnvironment() {
const char* devel_sandbox_path = GetDevelSandboxPath();
return devel_sandbox_path && (*devel_sandbox_path == '\0');
}
base::FilePath SetuidSandboxHost::GetSandboxBinaryPath() {
base::FilePath sandbox_binary;
base::FilePath exe_dir;
if (base::PathService::Get(base::DIR_EXE, &exe_dir)) {
base::FilePath sandbox_candidate = exe_dir.AppendASCII("chrome-sandbox");
if (base::PathExists(sandbox_candidate))
sandbox_binary = sandbox_candidate;
}
// In user-managed builds, including development builds, an environment
// variable is required to enable the sandbox. See
// https://chromium.googlesource.com/chromium/src/+/main/docs/linux/suid_sandbox_development.md
struct stat st;
if (sandbox_binary.empty() && stat(base::kProcSelfExe, &st) == 0 &&
st.st_uid == getuid()) {
const char* devel_sandbox_path = GetDevelSandboxPath();
if (devel_sandbox_path) {
sandbox_binary = base::FilePath(devel_sandbox_path);
}
}
return sandbox_binary;
}
void SetuidSandboxHost::PrependWrapper(base::CommandLine* cmd_line) {
std::string sandbox_binary(GetSandboxBinaryPath().value());
struct stat st;
if (sandbox_binary.empty() || stat(sandbox_binary.c_str(), &st) != 0) {
LOG(FATAL) << "The SUID sandbox helper binary is missing: "
<< sandbox_binary
<< " Aborting now. See "
"https://chromium.googlesource.com/"
"chromium/src/+/master/docs/"
"linux/suid_sandbox_development.md.";
}
if (access(sandbox_binary.c_str(), X_OK) != 0 || (st.st_uid != 0) ||
((st.st_mode & S_ISUID) == 0) || ((st.st_mode & S_IXOTH)) == 0) {
LOG(FATAL) << "The SUID sandbox helper binary was found, but is not "
"configured correctly. Rather than run without sandboxing "
"I'm aborting now. You need to make sure that "
<< sandbox_binary << " is owned by root and has mode 4755.";
}
cmd_line->PrependWrapper(sandbox_binary);
}
void SetuidSandboxHost::SetupLaunchOptions(
base::LaunchOptions* options,
base::ScopedFD* dummy_fd) {
DCHECK(options);
// Launching a setuid binary requires PR_SET_NO_NEW_PRIVS to not be used.
options->allow_new_privs = true;
UnsetExpectedEnvironmentVariables(&options->environment);
// Set dummy_fd to the reading end of a closed pipe.
int pipe_fds[2];
PCHECK(0 == pipe(pipe_fds));
PCHECK(0 == IGNORE_EINTR(close(pipe_fds[1])));
dummy_fd->reset(pipe_fds[0]);
// We no longer need a dummy socket for discovering the child's PID,
// but the sandbox is still hard-coded to expect a file descriptor at
// kZygoteIdFd. Fixing this requires a sandbox API change. :(
options->fds_to_remap.push_back(std::make_pair(dummy_fd->get(), kZygoteIdFd));
}
void SetuidSandboxHost::SetupLaunchEnvironment() {
SaveSUIDUnsafeEnvironmentVariables(env_.get());
SetSandboxAPIEnvironmentVariable(env_.get());
}
} // namespace sandbox
|