mirror of
https://github.com/google/sandboxed-api.git
synced 2024-03-22 13:11:30 +08:00
29a3b8cd39
The syscalls are fairly common and low risk. PiperOrigin-RevId: 603312020 Change-Id: Id06bddc4e7fcc879cad567361ae5b0bad9533142
495 lines
15 KiB
C++
495 lines
15 KiB
C++
// 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
|
|
//
|
|
// https://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 <sys/resource.h>
|
|
#include <sys/types.h>
|
|
#include <sys/uio.h>
|
|
#include <syscall.h>
|
|
|
|
#include <cstdio>
|
|
#include <initializer_list>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "absl/base/dynamic_annotations.h"
|
|
#include "absl/base/macros.h"
|
|
#include "absl/log/log.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/result.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/raw_logging.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()
|
|
.AllowGetPIDs()
|
|
.AllowSleep()
|
|
.AllowReadlink()
|
|
.AllowAccess()
|
|
.AllowSyscalls({
|
|
__NR_recvmsg,
|
|
__NR_sendmsg,
|
|
__NR_futex,
|
|
__NR_close,
|
|
__NR_lseek,
|
|
__NR_uname,
|
|
__NR_kill,
|
|
__NR_tgkill,
|
|
__NR_tkill,
|
|
});
|
|
|
|
#ifdef __NR_arch_prctl // x86-64 only
|
|
builder->AllowSyscall(__NR_arch_prctl);
|
|
#endif
|
|
|
|
if constexpr (sanitizers::IsAny()) {
|
|
LOG(WARNING) << "Allowing additional calls to support the LLVM "
|
|
<< "(ASAN/MSAN/TSAN) sanitizer";
|
|
builder->AllowLlvmSanitizers();
|
|
}
|
|
builder->AddFile("/etc/localtime")
|
|
.AddTmpfs("/tmp", 1ULL << 30 /* 1GiB tmpfs (max size */);
|
|
}
|
|
|
|
void Sandbox::Terminate(bool attempt_graceful_exit) {
|
|
if (!is_active()) {
|
|
return;
|
|
}
|
|
|
|
absl::StatusOr<sandbox2::Result> result;
|
|
if (attempt_graceful_exit) {
|
|
if (absl::Status requested_exit = rpc_channel_->Exit();
|
|
!requested_exit.ok()) {
|
|
LOG(WARNING)
|
|
<< "rpc_channel->Exit() failed, calling AwaitResultWithTimeout(1) "
|
|
<< requested_exit;
|
|
}
|
|
result = s2_->AwaitResultWithTimeout(absl::Seconds(1));
|
|
if (!result.ok()) {
|
|
LOG(WARNING) << "s2_->AwaitResultWithTimeout failed, status: "
|
|
<< result.status() << " Killing PID: " << pid();
|
|
}
|
|
}
|
|
|
|
if (!attempt_graceful_exit || !result.ok()) {
|
|
s2_->Kill();
|
|
result = s2_->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(bool use_unotify_monitor) {
|
|
// 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_ && !sapi::host_os::IsAndroid()) {
|
|
embed_lib_fd = EmbedFile::instance()->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<std::string> args = {lib_path};
|
|
// Additional arguments, if needed.
|
|
GetArgs(&args);
|
|
std::vector<std::string> envs{};
|
|
// Additional envvars, if needed.
|
|
GetEnvs(&envs);
|
|
|
|
forkserver_executor_ =
|
|
(embed_lib_fd >= 0)
|
|
? std::make_unique<sandbox2::Executor>(embed_lib_fd, args, envs)
|
|
: std::make_unique<sandbox2::Executor>(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);
|
|
if (use_unotify_monitor) {
|
|
policy_builder.CollectStacktracesOnSignal(false);
|
|
}
|
|
auto s2p = ModifyPolicy(&policy_builder);
|
|
|
|
// Spawn new process from the forkserver.
|
|
auto executor = std::make_unique<sandbox2::Executor>(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);
|
|
|
|
// Modify the executor, e.g. by setting custom limits and IPC.
|
|
ModifyExecutor(executor.get());
|
|
|
|
s2_ = std::make_unique<sandbox2::Sandbox2>(std::move(executor),
|
|
std::move(s2p), CreateNotifier());
|
|
if (use_unotify_monitor) {
|
|
SAPI_RETURN_IF_ERROR(s2_->EnableUnotifyMonitor());
|
|
}
|
|
s2_awaited_ = false;
|
|
auto res = s2_->RunAsync();
|
|
|
|
comms_ = s2_->comms();
|
|
pid_ = s2_->pid();
|
|
|
|
rpc_channel_ = std::make_unique<RPCChannel>(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(rpc_channel(), automatic_free);
|
|
}
|
|
|
|
absl::Status Sandbox::Free(v::Var* var) {
|
|
if (!is_active()) {
|
|
return absl::UnavailableError("Sandbox not active");
|
|
}
|
|
return var->Free(rpc_channel());
|
|
}
|
|
|
|
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<v::Ptr*>(ptr);
|
|
// NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations)
|
|
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.
|
|
// NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations)
|
|
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(rpc_channel(), 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<v::Ptr*>(ptr);
|
|
// NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations)
|
|
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(rpc_channel(), pid());
|
|
}
|
|
|
|
absl::Status Sandbox::Call(const std::string& func, v::Callable* ret,
|
|
std::initializer_list<v::Callable*> 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) {
|
|
if (arg == nullptr) {
|
|
rfcall.arg_type[i] = v::Type::kPointer;
|
|
rfcall.arg_size[i] = sizeof(void*);
|
|
rfcall.args[i].arg_int = 0;
|
|
VLOG(1) << "CALL ARG: (" << i << "): nullptr";
|
|
++i;
|
|
continue;
|
|
}
|
|
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<v::Ptr*>(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));
|
|
// Make MSAN happy with long double.
|
|
ABSL_ANNOTATE_MEMORY_IS_INITIALIZED(&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<v::Fd*>(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(
|
|
rpc_channel()->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<v::Fd*>(ret)));
|
|
}
|
|
|
|
// Synchronize all pointers after the call if it's needed.
|
|
for (auto* arg : args) {
|
|
if (arg != nullptr) {
|
|
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(rpc_channel(), pid());
|
|
}
|
|
|
|
absl::Status Sandbox::TransferFromSandboxee(v::Var* var) {
|
|
if (!is_active()) {
|
|
return absl::UnavailableError("Sandbox not active");
|
|
}
|
|
return var->TransferFromSandboxee(rpc_channel(), pid());
|
|
}
|
|
|
|
absl::StatusOr<std::string> 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, rpc_channel()->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_ && !s2_awaited_) {
|
|
result_ = s2_->AwaitResult();
|
|
s2_awaited_ = true;
|
|
}
|
|
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();
|
|
}
|
|
|
|
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<sandbox2::Policy> Sandbox::ModifyPolicy(
|
|
sandbox2::PolicyBuilder* builder) {
|
|
return builder->BuildOrDie();
|
|
}
|
|
|
|
} // namespace sapi
|