// Copyright 2019 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "sandboxed_api/sandbox.h" #include #include #include #include #include #include #include #include "absl/base/casts.h" #include "absl/base/macros.h" #include "absl/memory/memory.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/time/time.h" #include "sandboxed_api/config.h" #include "sandboxed_api/embed_file.h" #include "sandboxed_api/rpcchannel.h" #include "sandboxed_api/sandbox2/executor.h" #include "sandboxed_api/sandbox2/policy.h" #include "sandboxed_api/sandbox2/policybuilder.h" #include "sandboxed_api/sandbox2/sandbox2.h" #include "sandboxed_api/sandbox2/util/bpf_helper.h" #include "sandboxed_api/util/fileops.h" #include "sandboxed_api/util/path.h" #include "sandboxed_api/util/runfiles.h" #include "sandboxed_api/util/status_macros.h" namespace sapi { Sandbox::~Sandbox() { Terminate(); // The forkserver will die automatically when the executor goes out of scope // and closes the comms object. } // A generic policy which should work with majority of typical libraries, which // are single-threaded and require ~30 basic syscalls. void InitDefaultPolicyBuilder(sandbox2::PolicyBuilder* builder) { (*builder) .AllowRead() .AllowWrite() .AllowExit() .AllowGetRlimit() .AllowGetIDs() .AllowTCGETS() .AllowTime() .AllowOpen() .AllowStat() .AllowHandleSignals() .AllowSystemMalloc() .AllowSafeFcntl() .AllowSyscalls({ __NR_recvmsg, __NR_sendmsg, __NR_futex, __NR_close, __NR_lseek, __NR_getpid, __NR_getppid, __NR_gettid, __NR_clock_nanosleep, __NR_nanosleep, __NR_uname, __NR_getrandom, __NR_kill, __NR_tgkill, __NR_tkill, #ifdef __NR_readlink __NR_readlink, #endif #ifdef __NR_arch_prctl // x86-64 only __NR_arch_prctl, #endif }) .AddFile("/etc/localtime") .AddTmpfs("/tmp", 1ULL << 30 /* 1GiB tmpfs (max size) */); if constexpr (sanitizers::IsAny()) { LOG(WARNING) << "Allowing additional calls to support the LLVM " << "(ASAN/MSAN/TSAN) sanitizer"; builder->AllowLlvmSanitizers(); } } void Sandbox::Terminate(bool attempt_graceful_exit) { if (!is_active()) { return; } if (attempt_graceful_exit) { // Gracefully ask it to exit (with 1 second limit) first, then kill it. Exit(); } else { // Kill it straight away s2_->Kill(); } const auto& result = AwaitResult(); if (result.final_status() == sandbox2::Result::OK && result.reason_code() == 0) { VLOG(2) << "Sandbox2 finished with: " << result.ToString(); } else { LOG(WARNING) << "Sandbox2 finished with: " << result.ToString(); } } static std::string PathToSAPILib(const std::string& lib_path) { return file::IsAbsolutePath(lib_path) ? lib_path : GetDataDependencyFilePath(lib_path); } absl::Status Sandbox::Init() { // It's already initialized if (is_active()) { return absl::OkStatus(); } // Initialize the forkserver if it is not already running. if (!fork_client_) { // If FileToc was specified, it will be used over any paths to the SAPI // library. std::string lib_path; int embed_lib_fd = -1; if (embed_lib_toc_) { embed_lib_fd = EmbedFile::GetEmbedFileSingleton()->GetDupFdForFileToc( embed_lib_toc_); if (embed_lib_fd == -1) { PLOG(ERROR) << "Cannot create executable FD for TOC:'" << embed_lib_toc_->name << "'"; return absl::UnavailableError("Could not create executable FD"); } lib_path = embed_lib_toc_->name; } else { lib_path = PathToSAPILib(GetLibPath()); if (lib_path.empty()) { LOG(ERROR) << "SAPI library path is empty"; return absl::FailedPreconditionError("No SAPI library path given"); } } std::vector args = {lib_path}; // Additional arguments, if needed. GetArgs(&args); std::vector envs{}; // Additional envvars, if needed. GetEnvs(&envs); forkserver_executor_ = (embed_lib_fd >= 0) ? absl::make_unique(embed_lib_fd, args, envs) : absl::make_unique(lib_path, args, envs); fork_client_ = forkserver_executor_->StartForkServer(); if (!fork_client_) { LOG(ERROR) << "Could not start forkserver"; return absl::UnavailableError("Could not start the forkserver"); } } sandbox2::PolicyBuilder policy_builder; InitDefaultPolicyBuilder(&policy_builder); auto s2p = ModifyPolicy(&policy_builder); // Spawn new process from the forkserver. auto executor = absl::make_unique(fork_client_.get()); executor // The client.cc code is capable of enabling sandboxing on its own. ->set_enable_sandbox_before_exec(false) // By default, set cwd to "/", can be changed in ModifyExecutor(). .set_cwd("/") .limits() // Disable time limits. ->set_walltime_limit(absl::ZeroDuration()) .set_rlimit_cpu(RLIM64_INFINITY) // Needed by the Scudo Allocator, and by various *SAN options. .set_rlimit_as(RLIM64_INFINITY); // Modify the executor, e.g. by setting custom limits and IPC. ModifyExecutor(executor.get()); s2_ = absl::make_unique(std::move(executor), std::move(s2p), CreateNotifier()); auto res = s2_->RunAsync(); comms_ = s2_->comms(); pid_ = s2_->GetPid(); rpc_channel_ = absl::make_unique(comms_); if (!res) { Terminate(); return absl::UnavailableError("Could not start the sandbox"); } return absl::OkStatus(); } bool Sandbox::is_active() const { return s2_ && !s2_->IsTerminated(); } absl::Status Sandbox::Allocate(v::Var* var, bool automatic_free) { if (!is_active()) { return absl::UnavailableError("Sandbox not active"); } return var->Allocate(GetRpcChannel(), automatic_free); } absl::Status Sandbox::Free(v::Var* var) { if (!is_active()) { return absl::UnavailableError("Sandbox not active"); } return var->Free(GetRpcChannel()); } absl::Status Sandbox::SynchronizePtrBefore(v::Callable* ptr) { if (!is_active()) { return absl::UnavailableError("Sandbox not active"); } if (ptr->GetType() != v::Type::kPointer) { return absl::OkStatus(); } // Cast is safe, since type is v::Type::kPointer auto* p = static_cast(ptr); if (p->GetSyncType() == v::Pointable::kSyncNone) { return absl::OkStatus(); } if (p->GetPointedVar()->GetRemote() == nullptr) { // Allocate the memory, and make it automatically free-able, upon this // object's (p->GetPointedVar()) end of life-time. SAPI_RETURN_IF_ERROR(Allocate(p->GetPointedVar(), /*automatic_free=*/true)); } // Allocation occurs during both before/after synchronization modes. But the // memory is transferred to the sandboxee only if v::Pointable::kSyncBefore // was requested. if ((p->GetSyncType() & v::Pointable::kSyncBefore) == 0) { return absl::OkStatus(); } VLOG(3) << "Synchronization (TO), ptr " << p << ", Type: " << p->GetSyncType() << " for var: " << p->GetPointedVar()->ToString(); return p->GetPointedVar()->TransferToSandboxee(GetRpcChannel(), pid()); } absl::Status Sandbox::SynchronizePtrAfter(v::Callable* ptr) const { if (!is_active()) { return absl::UnavailableError("Sandbox not active"); } if (ptr->GetType() != v::Type::kPointer) { return absl::OkStatus(); } v::Ptr* p = reinterpret_cast(ptr); if ((p->GetSyncType() & v::Pointable::kSyncAfter) == 0) { return absl::OkStatus(); } VLOG(3) << "Synchronization (FROM), ptr " << p << ", Type: " << p->GetSyncType() << " for var: " << p->GetPointedVar()->ToString(); if (p->GetPointedVar()->GetRemote() == nullptr) { LOG(ERROR) << "Trying to synchronize a variable which is not allocated in " << "the sandboxee p=" << p->ToString(); return absl::FailedPreconditionError(absl::StrCat( "Trying to synchronize a variable which is not allocated in the " "sandboxee p=", p->ToString())); } return p->GetPointedVar()->TransferFromSandboxee(GetRpcChannel(), pid()); } absl::Status Sandbox::Call(const std::string& func, v::Callable* ret, std::initializer_list args) { if (!is_active()) { return absl::UnavailableError("Sandbox not active"); } // Send data. FuncCall rfcall{}; rfcall.argc = args.size(); absl::SNPrintF(rfcall.func, ABSL_ARRAYSIZE(rfcall.func), "%s", func); VLOG(1) << "CALL ENTRY: '" << func << "' with " << args.size() << " argument(s)"; // Copy all arguments into rfcall. int i = 0; for (auto* arg : args) { rfcall.arg_size[i] = arg->GetSize(); rfcall.arg_type[i] = arg->GetType(); // For pointers, set the auxiliary type and size. if (rfcall.arg_type[i] == v::Type::kPointer) { // Cast is safe, since type is v::Type::kPointer auto* p = static_cast(arg); rfcall.aux_type[i] = p->GetPointedVar()->GetType(); rfcall.aux_size[i] = p->GetPointedVar()->GetSize(); } // Synchronize all pointers before the call if it's needed. SAPI_RETURN_IF_ERROR(SynchronizePtrBefore(arg)); if (arg->GetType() == v::Type::kFloat) { arg->GetDataFromPtr(&rfcall.args[i].arg_float, sizeof(rfcall.args[0].arg_float)); } else { arg->GetDataFromPtr(&rfcall.args[i].arg_int, sizeof(rfcall.args[0].arg_int)); } if (rfcall.arg_type[i] == v::Type::kFd) { // Cast is safe, since type is v::Type::kFd auto* fd = static_cast(arg); if (fd->GetRemoteFd() < 0) { SAPI_RETURN_IF_ERROR(TransferToSandboxee(fd)); } rfcall.args[i].arg_int = fd->GetRemoteFd(); } VLOG(1) << "CALL ARG: (" << i << "), Type: " << arg->GetTypeString() << ", Size: " << arg->GetSize() << ", Val: " << arg->ToString(); ++i; } rfcall.ret_type = ret->GetType(); rfcall.ret_size = ret->GetSize(); // Call & receive data. FuncRet fret; SAPI_RETURN_IF_ERROR( GetRpcChannel()->Call(rfcall, comms::kMsgCall, &fret, rfcall.ret_type)); if (fret.ret_type == v::Type::kFloat) { ret->SetDataFromPtr(&fret.float_val, sizeof(fret.float_val)); } else { ret->SetDataFromPtr(&fret.int_val, sizeof(fret.int_val)); } if (fret.ret_type == v::Type::kFd) { SAPI_RETURN_IF_ERROR(TransferFromSandboxee(reinterpret_cast(ret))); } // Synchronize all pointers after the call if it's needed. for (auto* arg : args) { SAPI_RETURN_IF_ERROR(SynchronizePtrAfter(arg)); } VLOG(1) << "CALL EXIT: Type: " << ret->GetTypeString() << ", Size: " << ret->GetSize() << ", Val: " << ret->ToString(); return absl::OkStatus(); } absl::Status Sandbox::Symbol(const char* symname, void** addr) { if (!is_active()) { return absl::UnavailableError("Sandbox not active"); } return rpc_channel_->Symbol(symname, addr); } absl::Status Sandbox::TransferToSandboxee(v::Var* var) { if (!is_active()) { return absl::UnavailableError("Sandbox not active"); } return var->TransferToSandboxee(GetRpcChannel(), pid()); } absl::Status Sandbox::TransferFromSandboxee(v::Var* var) { if (!is_active()) { return absl::UnavailableError("Sandbox not active"); } return var->TransferFromSandboxee(GetRpcChannel(), pid()); } absl::StatusOr Sandbox::GetCString(const v::RemotePtr& str, size_t max_length) { if (!is_active()) { return absl::UnavailableError("Sandbox not active"); } SAPI_ASSIGN_OR_RETURN(auto len, GetRpcChannel()->Strlen(str.GetValue())); if (len > max_length) { return absl::InvalidArgumentError( absl::StrCat("Target string too large: ", len, " > ", max_length)); } std::string buffer(len, '\0'); struct iovec local = { .iov_base = &buffer[0], .iov_len = len, }; struct iovec remote = { .iov_base = str.GetValue(), .iov_len = len, }; ssize_t ret = process_vm_readv(pid_, &local, 1, &remote, 1, 0); if (ret == -1) { PLOG(WARNING) << "reading c-string failed: process_vm_readv(pid: " << pid_ << " raddr: " << str.GetValue() << " size: " << len << ")"; return absl::UnavailableError("process_vm_readv failed"); } if (ret != len) { LOG(WARNING) << "partial read when reading c-string: process_vm_readv(pid: " << pid_ << " raddr: " << str.GetValue() << " size: " << len << ") transferred " << ret << " bytes"; return absl::UnavailableError("process_vm_readv succeeded partially"); } return buffer; } const sandbox2::Result& Sandbox::AwaitResult() { if (s2_) { result_ = s2_->AwaitResult(); s2_.reset(nullptr); } return result_; } absl::Status Sandbox::SetWallTimeLimit(absl::Duration limit) const { if (!is_active()) { return absl::UnavailableError("Sandbox not active"); } s2_->set_walltime_limit(limit); return absl::OkStatus(); } absl::Status Sandbox::SetWallTimeLimit(time_t limit) const { return SetWallTimeLimit(absl::Seconds(limit)); } void Sandbox::Exit() const { if (!is_active()) { return; } s2_->set_walltime_limit(absl::Seconds(1)); if (!rpc_channel_->Exit().ok()) { LOG(WARNING) << "rpc_channel->Exit() failed, killing PID: " << pid(); s2_->Kill(); } } std::unique_ptr Sandbox::ModifyPolicy( sandbox2::PolicyBuilder* builder) { return builder->BuildOrDie(); } } // namespace sapi