From 8f24f2a4f07b4d89492b33808e5d0be592b59662 Mon Sep 17 00:00:00 2001 From: Wiktor Garbacz Date: Mon, 30 Jan 2023 05:08:42 -0800 Subject: [PATCH] Split PtraceMonitor into separate file PiperOrigin-RevId: 505660957 Change-Id: I6b8fcbb86c9fef294b6d19e2d1ec7120415f843b --- sandboxed_api/sandbox2/BUILD.bazel | 45 +- sandboxed_api/sandbox2/CMakeLists.txt | 38 +- sandboxed_api/sandbox2/monitor_base.cc | 395 ++++++++++++++++++ sandboxed_api/sandbox2/monitor_base.h | 140 +++++++ .../{monitor.cc => monitor_ptrace.cc} | 371 +--------------- .../sandbox2/{monitor.h => monitor_ptrace.h} | 113 +---- sandboxed_api/sandbox2/sandbox2.cc | 2 +- sandboxed_api/sandbox2/sandbox2.h | 2 +- 8 files changed, 619 insertions(+), 487 deletions(-) create mode 100644 sandboxed_api/sandbox2/monitor_base.cc create mode 100644 sandboxed_api/sandbox2/monitor_base.h rename sandboxed_api/sandbox2/{monitor.cc => monitor_ptrace.cc} (75%) rename sandboxed_api/sandbox2/{monitor.h => monitor_ptrace.h} (63%) diff --git a/sandboxed_api/sandbox2/BUILD.bazel b/sandboxed_api/sandbox2/BUILD.bazel index 736bc52..7d6db74 100644 --- a/sandboxed_api/sandbox2/BUILD.bazel +++ b/sandboxed_api/sandbox2/BUILD.bazel @@ -309,8 +309,8 @@ cc_library( cc_library( name = "sandbox2", srcs = [ - "monitor.cc", - "monitor.h", + "monitor_ptrace.cc", + "monitor_ptrace.h", "sandbox2.cc", "stack_trace.cc", "stack_trace.h", @@ -334,11 +334,10 @@ cc_library( ":comms", ":executor", ":fork_client", - ":forkserver_cc_proto", - ":global_forkserver", ":ipc", ":limits", ":logsink", + ":monitor_base", ":mounts", ":namespace", ":notify", @@ -353,7 +352,6 @@ cc_library( "//sandboxed_api:config", "//sandboxed_api/sandbox2/network_proxy:client", "//sandboxed_api/sandbox2/network_proxy:filtering", - "//sandboxed_api/sandbox2/network_proxy:server", "//sandboxed_api/sandbox2/unwind", "//sandboxed_api/sandbox2/unwind:unwind_cc_proto", "//sandboxed_api/sandbox2/util:bpf_helper", @@ -375,7 +373,6 @@ cc_library( "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", - "@com_google_absl//absl/strings:str_format", "@com_google_absl//absl/synchronization", "@com_google_absl//absl/time", "@com_google_absl//absl/types:optional", @@ -384,6 +381,42 @@ cc_library( ], ) +cc_library( + name = "monitor_base", + srcs = ["monitor_base.cc"], + hdrs = ["monitor_base.h"], + copts = sapi_platform_copts(), + deps = [ + ":client", + ":comms", + ":executor", + ":ipc", + ":limits", + ":mounts", + ":namespace", + ":notify", + ":policy", + ":result", + ":syscall", + ":util", + "//sandboxed_api/sandbox2/network_proxy:server", + "//sandboxed_api/util:file_helpers", + "//sandboxed_api/util:raw_logging", + "//sandboxed_api/util:strerror", + "//sandboxed_api/util:temp_file", + "@com_google_absl//absl/cleanup", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/log", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/memory", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/synchronization", + "@com_google_absl//absl/time", + ], +) + cc_library( name = "policybuilder", srcs = ["policybuilder.cc"], diff --git a/sandboxed_api/sandbox2/CMakeLists.txt b/sandboxed_api/sandbox2/CMakeLists.txt index e1cf1a5..c7f3ddf 100644 --- a/sandboxed_api/sandbox2/CMakeLists.txt +++ b/sandboxed_api/sandbox2/CMakeLists.txt @@ -286,8 +286,8 @@ target_link_libraries(sandbox2_executor # sandboxed_api/sandbox2:sandbox2 add_library(sandbox2_sandbox2 ${SAPI_LIB_TYPE} - monitor.cc - monitor.h + monitor_ptrace.cc + monitor_ptrace.h sandbox2.cc sandbox2.h stack_trace.cc @@ -298,6 +298,7 @@ target_link_libraries(sandbox2_sandbox2 PRIVATE absl::core_headers absl::cleanup absl::flat_hash_set + absl::memory absl::optional absl::str_format absl::strings @@ -318,11 +319,11 @@ target_link_libraries(sandbox2_sandbox2 sandbox2::comms sandbox2::executor sandbox2::fork_client - sandbox2::forkserver_proto sandbox2::global_forkserver sandbox2::ipc sandbox2::limits sandbox2::logsink + sandbox2::monitor_base sandbox2::mounts sandbox2::mount_tree_proto sandbox2::namespace @@ -341,6 +342,37 @@ target_link_libraries(sandbox2_sandbox2 sandbox2::violation_proto ) +# sandboxed_api/sandbox2:monitor_base +add_library(sandbox2_monitor_base ${SAPI_LIB_TYPE} + monitor_base.cc + monitor_base.h +) +add_library(sandbox2::monitor_base ALIAS sandbox2_monitor_base) +target_link_libraries(sandbox2_monitor_base + PRIVATE absl::status + absl::synchronization + sandbox2::comms + sandbox2::executor + sandbox2::ipc + sandbox2::network_proxy_server + sandbox2::notify + sandbox2::policy + sandbox2::result + sandbox2::syscall + sapi::base + sapi::raw_logging + PUBLIC absl::cleanup + absl::statusor + absl::time + sapi::file_helpers + sapi::temp_file + sandbox2::client + sandbox2::limits + sandbox2::mounts + sandbox2::namespace + sandbox2::util +) + # sandboxed_api/sandbox2:policybuilder add_library(sandbox2_policybuilder ${SAPI_LIB_TYPE} policybuilder.cc diff --git a/sandboxed_api/sandbox2/monitor_base.cc b/sandboxed_api/sandbox2/monitor_base.cc new file mode 100644 index 0000000..1ece66e --- /dev/null +++ b/sandboxed_api/sandbox2/monitor_base.cc @@ -0,0 +1,395 @@ +// Copyright 2023 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. + +// Implementation file for the sandbox2::MonitorBase class. + +#include "sandboxed_api/sandbox2/monitor_base.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "absl/cleanup/cleanup.h" +#include "absl/flags/declare.h" +#include "absl/flags/flag.h" +#include "absl/log/log.h" +#include "absl/status/status.h" +#include "absl/strings/match.h" +#include "absl/strings/str_cat.h" +#include "absl/time/time.h" +#include "sandboxed_api/sandbox2/client.h" +#include "sandboxed_api/sandbox2/comms.h" +#include "sandboxed_api/sandbox2/executor.h" +#include "sandboxed_api/sandbox2/limits.h" +#include "sandboxed_api/sandbox2/mounts.h" +#include "sandboxed_api/sandbox2/namespace.h" +#include "sandboxed_api/sandbox2/network_proxy/server.h" +#include "sandboxed_api/sandbox2/policy.h" +#include "sandboxed_api/sandbox2/result.h" +#include "sandboxed_api/sandbox2/syscall.h" +#include "sandboxed_api/sandbox2/util.h" +#include "sandboxed_api/util/file_helpers.h" +#include "sandboxed_api/util/raw_logging.h" +#include "sandboxed_api/util/strerror.h" +#include "sandboxed_api/util/temp_file.h" + +ABSL_FLAG(bool, sandbox2_report_on_sandboxee_signal, true, + "Report sandbox2 sandboxee deaths caused by signals"); + +ABSL_FLAG(bool, sandbox2_report_on_sandboxee_timeout, true, + "Report sandbox2 sandboxee timeouts"); + +ABSL_DECLARE_FLAG(bool, sandbox2_danger_danger_permit_all); +ABSL_DECLARE_FLAG(std::string, sandbox2_danger_danger_permit_all_and_log); + +namespace sandbox2 { +namespace { + +void MaybeEnableTomoyoLsmWorkaround(Mounts& mounts, std::string& comms_fd_dev) { + static auto tomoyo_active = []() -> bool { + std::string lsm_list; + if (auto status = sapi::file::GetContents( + "/sys/kernel/security/lsm", &lsm_list, sapi::file::Defaults()); + !status.ok() && !absl::IsNotFound(status)) { + VLOG(1) << "Checking active LSMs failed: " << status.message() << ": " + << sapi::StrError(errno); + return false; + } + return absl::StrContains(lsm_list, "tomoyo"); + }(); + + if (!tomoyo_active) { + return; + } + VLOG(1) << "Tomoyo LSM active, enabling workaround"; + + if (mounts.ResolvePath("/dev").ok() || mounts.ResolvePath("/dev/fd").ok()) { + // Avoid shadowing /dev/fd/1022 below if /dev or /dev/fd is already mapped. + VLOG(1) << "Parent dir already mapped, skipping"; + return; + } + + auto temp_file = sapi::CreateNamedTempFileAndClose("/tmp/"); + if (!temp_file.ok()) { + LOG(WARNING) << "Failed to create empty temp file: " << temp_file.status(); + return; + } + comms_fd_dev = std::move(*temp_file); + + // Ignore errors here, as the file itself might already be mapped. + if (auto status = mounts.AddFileAt( + comms_fd_dev, absl::StrCat("/dev/fd/", Comms::kSandbox2TargetExecFD), + false); + !status.ok()) { + VLOG(1) << "Mapping comms FD: %s" << status.message(); + } +} + +void LogContainer(const std::vector& container) { + for (size_t i = 0; i < container.size(); ++i) { + LOG(INFO) << "[" << std::setfill('0') << std::setw(4) << i + << "]=" << container[i]; + } +} + +} // namespace + +MonitorBase::MonitorBase(Executor* executor, Policy* policy, Notify* notify) + : executor_(executor), + notify_(notify), + policy_(policy), + // NOLINTNEXTLINE clang-diagnostic-deprecated-declarations + comms_(executor_->ipc()->comms()), + ipc_(executor_->ipc()) { + // It's a pre-connected Comms channel, no need to accept new connection. + CHECK(comms_->IsConnected()); + std::string path = + absl::GetFlag(FLAGS_sandbox2_danger_danger_permit_all_and_log); + if (!path.empty()) { + log_file_ = std::fopen(path.c_str(), "a+"); + PCHECK(log_file_ != nullptr) << "Failed to open log file '" << path << "'"; + } + + if (auto* ns = policy_->GetNamespace(); ns) { + // Check for the Tomoyo LSM, which is active by default in several common + // distribution kernels (esp. Debian). + MaybeEnableTomoyoLsmWorkaround(ns->mounts(), comms_fd_dev_); + } +} + +MonitorBase::~MonitorBase() { + if (!comms_fd_dev_.empty()) { + std::remove(comms_fd_dev_.c_str()); + } + if (log_file_) { + std::fclose(log_file_); + } + if (network_proxy_server_) { + network_proxy_thread_.join(); + } +} + +void MonitorBase::OnDone() { + if (done_notification_.HasBeenNotified()) { + return; + } + + notify_->EventFinished(result_); + ipc_->InternalCleanupFdMap(); + done_notification_.Notify(); +} + +void MonitorBase::Launch() { + + absl::Cleanup process_cleanup = [this] { + if (process_.init_pid > 0) { + kill(process_.init_pid, SIGKILL); + } else if (process_.main_pid > 0) { + kill(process_.main_pid, SIGKILL); + } + }; + absl::Cleanup monitor_done = [this] { OnDone(); }; + + Namespace* ns = policy_->GetNamespace(); + if (SAPI_VLOG_IS_ON(1) && ns != nullptr) { + std::vector outside_entries; + std::vector inside_entries; + ns->mounts().RecursivelyListMounts( + /*outside_entries=*/&outside_entries, + /*inside_entries=*/&inside_entries); + VLOG(1) << "Outside entries mapped to chroot:"; + LogContainer(outside_entries); + VLOG(1) << "Inside entries as they appear in chroot:"; + LogContainer(inside_entries); + } + + // Don't trace the child: it will allow to use 'strace -f' with the whole + // sandbox master/monitor, which ptrace_attach'es to the child. + int clone_flags = CLONE_UNTRACED; + + if (policy_->allowed_hosts_) { + EnableNetworkProxyServer(); + } + + // Get PID of the sandboxee. + bool should_have_init = ns && (ns->GetCloneFlags() & CLONE_NEWPID); + absl::StatusOr process = + executor_->StartSubProcess(clone_flags, ns, policy_->capabilities()); + + if (!process.ok()) { + LOG(ERROR) << "Starting sandboxed subprocess failed: " << process.status(); + SetExitStatusCode(Result::SETUP_ERROR, Result::FAILED_SUBPROCESS); + return; + } + + process_ = *std::move(process); + + if (process_.main_pid <= 0 || (should_have_init && process_.main_pid <= 0)) { + SetExitStatusCode(Result::SETUP_ERROR, Result::FAILED_SUBPROCESS); + return; + } + + if (!notify_->EventStarted(process_.main_pid, comms_)) { + SetExitStatusCode(Result::SETUP_ERROR, Result::FAILED_NOTIFY); + return; + } + if (!InitSendIPC()) { + SetExitStatusCode(Result::SETUP_ERROR, Result::FAILED_IPC); + return; + } + if (!InitSendCwd()) { + SetExitStatusCode(Result::SETUP_ERROR, Result::FAILED_CWD); + return; + } + if (!InitSendPolicy()) { + SetExitStatusCode(Result::SETUP_ERROR, Result::FAILED_POLICY); + return; + } + if (!WaitForSandboxReady()) { + SetExitStatusCode(Result::SETUP_ERROR, Result::FAILED_WAIT); + return; + } + if (!InitApplyLimits()) { + SetExitStatusCode(Result::SETUP_ERROR, Result::FAILED_LIMITS); + return; + } + std::move(process_cleanup).Cancel(); + + RunInternal(); + std::move(monitor_done).Cancel(); +} + +absl::StatusOr MonitorBase::AwaitResultWithTimeout( + absl::Duration timeout) { + auto done = done_notification_.WaitForNotificationWithTimeout(timeout); + if (!done) { + return absl::DeadlineExceededError("Sandbox did not finish within timeout"); + } + + Join(); + return result_; +} + +void MonitorBase::SetExitStatusCode(Result::StatusEnum final_status, + uintptr_t reason_code) { + CHECK(result_.final_status() == Result::UNSET); + result_.SetExitStatusCode(final_status, reason_code); +} + +bool MonitorBase::InitSendPolicy() { + if (!policy_->SendPolicy(comms_)) { + LOG(ERROR) << "Couldn't send policy"; + return false; + } + + return true; +} + +bool MonitorBase::InitSendCwd() { + if (!comms_->SendString(executor_->cwd_)) { + PLOG(ERROR) << "Couldn't send cwd"; + return false; + } + + return true; +} + +bool MonitorBase::InitApplyLimit(pid_t pid, int resource, + const rlimit64& rlim) const { +#if defined(__ANDROID__) + using RlimitResource = int; +#else + using RlimitResource = __rlimit_resource; +#endif + + rlimit64 curr_limit; + if (prlimit64(pid, static_cast(resource), nullptr, + &curr_limit) == -1) { + PLOG(ERROR) << "prlimit64(" << pid << ", " << util::GetRlimitName(resource) + << ")"; + } else if (rlim.rlim_cur > curr_limit.rlim_max) { + // In such case, don't update the limits, as it will fail. Just stick to the + // current ones (which are already lower than intended). + LOG(ERROR) << util::GetRlimitName(resource) + << ": new.current > current.max (" << rlim.rlim_cur << " > " + << curr_limit.rlim_max << "), skipping"; + return true; + } + + if (prlimit64(pid, static_cast(resource), &rlim, nullptr) == + -1) { + PLOG(ERROR) << "prlimit64(" << pid << ", " << util::GetRlimitName(resource) + << ", " << rlim.rlim_cur << ")"; + return false; + } + + return true; +} + +bool MonitorBase::InitApplyLimits() { + Limits* limits = executor_->limits(); + return InitApplyLimit(process_.main_pid, RLIMIT_AS, limits->rlimit_as()) && + InitApplyLimit(process_.main_pid, RLIMIT_CPU, limits->rlimit_cpu()) && + InitApplyLimit(process_.main_pid, RLIMIT_FSIZE, + limits->rlimit_fsize()) && + InitApplyLimit(process_.main_pid, RLIMIT_NOFILE, + limits->rlimit_nofile()) && + InitApplyLimit(process_.main_pid, RLIMIT_CORE, limits->rlimit_core()); +} + +bool MonitorBase::InitSendIPC() { return ipc_->SendFdsOverComms(); } + +bool MonitorBase::WaitForSandboxReady() { + uint32_t tmp; + if (!comms_->RecvUint32(&tmp)) { + LOG(ERROR) << "Couldn't receive 'Client::kClient2SandboxReady' message"; + return false; + } + if (tmp != Client::kClient2SandboxReady) { + LOG(ERROR) << "Received " << tmp << " != Client::kClient2SandboxReady (" + << Client::kClient2SandboxReady << ")"; + return false; + } + return true; +} + +void MonitorBase::LogSyscallViolation(const Syscall& syscall) const { + // Do not unwind libunwind. + if (executor_->libunwind_sbox_for_pid_ != 0) { + LOG(ERROR) << "Sandbox violation during execution of libunwind: " + << syscall.GetDescription(); + return; + } + + // So, this is an invalid syscall. Will be killed by seccomp-bpf policies as + // well, but we should be on a safe side here as well. + LOG(ERROR) << "SANDBOX VIOLATION : PID: " << syscall.pid() << ", PROG: '" + << util::GetProgName(syscall.pid()) + << "' : " << syscall.GetDescription(); + if (SAPI_VLOG_IS_ON(1)) { + VLOG(1) << "Cmdline: " << util::GetCmdLine(syscall.pid()); + VLOG(1) << "Task Name: " << util::GetProcStatusLine(syscall.pid(), "Name"); + VLOG(1) << "Tgid: " << util::GetProcStatusLine(syscall.pid(), "Tgid"); + } + + LogSyscallViolationExplanation(syscall); +} + +void MonitorBase::LogSyscallViolationExplanation(const Syscall& syscall) const { + const uintptr_t syscall_nr = syscall.nr(); + const uintptr_t arg0 = syscall.args()[0]; + + // This follows policy in Policy::GetDefaultPolicy - keep it in sync. + if (syscall.arch() != Syscall::GetHostArch()) { + LOG(ERROR) + << "This is a violation because the syscall was issued because the" + << " sandboxee and executor architectures are different."; + return; + } + if (syscall_nr == __NR_ptrace) { + LOG(ERROR) + << "This is a violation because the ptrace syscall would be unsafe in" + << " sandbox2, so it has been blocked."; + return; + } + if (syscall_nr == __NR_bpf) { + LOG(ERROR) + << "This is a violation because the bpf syscall would be risky in" + << " a sandbox, so it has been blocked."; + return; + } + if (syscall_nr == __NR_clone && ((arg0 & CLONE_UNTRACED) != 0)) { + LOG(ERROR) << "This is a violation because calling clone with CLONE_UNTRACE" + << " would be unsafe in sandbox2, so it has been blocked."; + return; + } +} + +void MonitorBase::EnableNetworkProxyServer() { + int fd = ipc_->ReceiveFd(NetworkProxyClient::kFDName); + + network_proxy_server_ = std::make_unique( + fd, &policy_->allowed_hosts_.value(), pthread_self()); + + network_proxy_thread_ = std::thread(&NetworkProxyServer::Run, + network_proxy_server_.get()); +} +} // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/monitor_base.h b/sandboxed_api/sandbox2/monitor_base.h new file mode 100644 index 0000000..3a279ba --- /dev/null +++ b/sandboxed_api/sandbox2/monitor_base.h @@ -0,0 +1,140 @@ +// Copyright 2023 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. + +// The sandbox2::Monitor class is responsible for tracking the processes, and +// displaying their current statuses (syscalls, states, violations). + +#ifndef SANDBOXED_API_SANDBOX2_MONITOR_BASE_H_ +#define SANDBOXED_API_SANDBOX2_MONITOR_BASE_H_ + +#include + +#include +#include +#include +#include + +#include "absl/status/statusor.h" +#include "absl/synchronization/notification.h" +#include "sandboxed_api/sandbox2/comms.h" +#include "sandboxed_api/sandbox2/executor.h" +#include "sandboxed_api/sandbox2/ipc.h" +#include "sandboxed_api/sandbox2/network_proxy/server.h" +#include "sandboxed_api/sandbox2/notify.h" +#include "sandboxed_api/sandbox2/policy.h" +#include "sandboxed_api/sandbox2/result.h" +#include "sandboxed_api/sandbox2/syscall.h" + +namespace sandbox2 { + +class MonitorBase { + public: + // executor, policy and notify are not owned by the Monitor + MonitorBase(Executor* executor, Policy* policy, Notify* notify); + + MonitorBase(const MonitorBase&) = delete; + MonitorBase& operator=(const MonitorBase&) = delete; + + virtual ~MonitorBase(); + + // Starts the Monitor. + void Launch(); + + // Getters for private fields. + bool IsDone() const { return done_notification_.HasBeenNotified(); } + + // Enable network proxy server, this will start a thread in the sandbox + // that waits for connection requests from the sandboxee. + void EnableNetworkProxyServer(); + + pid_t pid() const { return process_.main_pid; } + + const Result& result() const { return result_; } + + absl::StatusOr AwaitResultWithTimeout(absl::Duration timeout); + + virtual void Kill() = 0; + virtual void DumpStackTrace() = 0; + virtual void SetWallTimeLimit(absl::Duration limit) = 0; + + protected: + void OnDone(); + // Sets basic info status and reason code in the result object. + void SetExitStatusCode(Result::StatusEnum final_status, + uintptr_t reason_code); + // Logs a SANDBOX VIOLATION message based on the registers and additional + // explanation for the reason of the violation. + void LogSyscallViolation(const Syscall& syscall) const; + + // Internal objects, owned by the Sandbox2 object. + Executor* executor_; + Notify* notify_; + Policy* policy_; + // The sandboxee process. + Executor::Process process_; + Result result_; + // Comms channel ptr, copied from the Executor object for convenience. + Comms* comms_; + // Log file specified by + // --sandbox_danger_danger_permit_all_and_log flag. + FILE* log_file_ = nullptr; + // Handle to the class responsible for proxying and validating connect() + // requests. + std::unique_ptr network_proxy_server_; + + private: + // Sends Policy to the Client. + // Returns success/failure status. + bool InitSendPolicy(); + + // Waits for the SandboxReady signal from the client. + // Returns success/failure status. + bool WaitForSandboxReady(); + + // Sends information about data exchange channels. + bool InitSendIPC(); + + // Sends information about the current working directory. + bool InitSendCwd(); + + // Applies limits on the sandboxee. + bool InitApplyLimits(); + + // Applies individual limit on the sandboxee. + bool InitApplyLimit(pid_t pid, int resource, const rlimit64& rlim) const; + + // Logs an additional explanation for the possible reason of the violation + // based on the registers. + void LogSyscallViolationExplanation(const Syscall& syscall) const; + + virtual void RunInternal() = 0; + virtual void Join() = 0; + + // IPC ptr, used for exchanging data with the sandboxee. + IPC* ipc_; + + // The field indicates whether the sandboxing task has been completed (either + // successfully or with error). + absl::Notification done_notification_; + + // Empty temp file used for mapping the comms fd when the Tomoyo LSM is + // active. + std::string comms_fd_dev_; + + std::thread network_proxy_thread_; +}; + +} // namespace sandbox2 + +#endif // SANDBOXED_API_SANDBOX2_MONITOR_BASE_H_ diff --git a/sandboxed_api/sandbox2/monitor.cc b/sandboxed_api/sandbox2/monitor_ptrace.cc similarity index 75% rename from sandboxed_api/sandbox2/monitor.cc rename to sandboxed_api/sandbox2/monitor_ptrace.cc index 99dfaed..d56e2c1 100644 --- a/sandboxed_api/sandbox2/monitor.cc +++ b/sandboxed_api/sandbox2/monitor_ptrace.cc @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC +// Copyright 2023 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,34 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Implementation file for the sandbox2::Monitor class. +// Implementation file for the sandbox2::PtraceMonitor class. -#include "sandboxed_api/sandbox2/monitor.h" +#include "sandboxed_api/sandbox2/monitor_ptrace.h" -// clang-format off -#include // NOLINT: Needs to come before linux/ipc.h -#include -// clang-format on -#include -#include #include -#include #include #include #include -#include #include #include #include -#include -#include -#include -#include #include #include #include -#include #include #include #include @@ -48,20 +35,13 @@ #include "absl/container/flat_hash_set.h" #include "absl/flags/declare.h" #include "absl/flags/flag.h" -#include "absl/log/log.h" #include "absl/status/status.h" -#include "absl/strings/match.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/sandbox2/client.h" #include "sandboxed_api/sandbox2/comms.h" #include "sandboxed_api/sandbox2/executor.h" -#include "sandboxed_api/sandbox2/limits.h" -#include "sandboxed_api/sandbox2/mounts.h" -#include "sandboxed_api/sandbox2/namespace.h" -#include "sandboxed_api/sandbox2/network_proxy/server.h" #include "sandboxed_api/sandbox2/policy.h" #include "sandboxed_api/sandbox2/regs.h" #include "sandboxed_api/sandbox2/result.h" @@ -69,19 +49,8 @@ #include "sandboxed_api/sandbox2/stack_trace.h" #include "sandboxed_api/sandbox2/syscall.h" #include "sandboxed_api/sandbox2/util.h" -#include "sandboxed_api/util/file_helpers.h" #include "sandboxed_api/util/raw_logging.h" #include "sandboxed_api/util/status_macros.h" -#include "sandboxed_api/util/strerror.h" -#include "sandboxed_api/util/temp_file.h" - -using std::string; - -ABSL_FLAG(bool, sandbox2_report_on_sandboxee_signal, true, - "Report sandbox2 sandboxee deaths caused by signals"); - -ABSL_FLAG(bool, sandbox2_report_on_sandboxee_timeout, true, - "Report sandbox2 sandboxee timeouts"); ABSL_FLAG(bool, sandbox2_log_all_stack_traces, false, "If set, sandbox2 monitor will log stack traces of all monitored " @@ -95,7 +64,6 @@ ABSL_FLAG(absl::Duration, sandbox2_stack_traces_collection_timeout, ABSL_DECLARE_FLAG(bool, sandbox2_danger_danger_permit_all); ABSL_DECLARE_FLAG(bool, sandbox_libunwind_crash_handler); -ABSL_DECLARE_FLAG(string, sandbox2_danger_danger_permit_all_and_log); namespace sandbox2 { namespace { @@ -206,78 +174,14 @@ void CompleteSyscall(pid_t pid, int signo) { } } -void MaybeEnableTomoyoLsmWorkaround(Mounts& mounts, std::string& comms_fd_dev) { - static auto tomoyo_active = []() -> bool { - std::string lsm_list; - if (auto status = sapi::file::GetContents( - "/sys/kernel/security/lsm", &lsm_list, sapi::file::Defaults()); - !status.ok() && !absl::IsNotFound(status)) { - VLOG(1) << "Checking active LSMs failed: " << status.message() << ": " - << sapi::StrError(errno); - return false; - } - return absl::StrContains(lsm_list, "tomoyo"); - }(); - - if (!tomoyo_active) { - return; - } - VLOG(1) << "Tomoyo LSM active, enabling workaround"; - - if (mounts.ResolvePath("/dev").ok() || mounts.ResolvePath("/dev/fd").ok()) { - // Avoid shadowing /dev/fd/1022 below if /dev or /dev/fd is already mapped. - VLOG(1) << "Parent dir already mapped, skipping"; - return; - } - - auto temp_file = sapi::CreateNamedTempFileAndClose("/tmp/"); - if (!temp_file.ok()) { - LOG(WARNING) << "Failed to create empty temp file: " << temp_file.status(); - return; - } - comms_fd_dev = std::move(*temp_file); - - // Ignore errors here, as the file itself might already be mapped. - if (auto status = mounts.AddFileAt( - comms_fd_dev, absl::StrCat("/dev/fd/", Comms::kSandbox2TargetExecFD), - false); - !status.ok()) { - VLOG(1) << "Mapping comms FD: %s" << status.message(); - } -} - } // namespace -MonitorBase::MonitorBase(Executor* executor, Policy* policy, Notify* notify) - : executor_(executor), - notify_(notify), - policy_(policy), - // NOLINTNEXTLINE clang-diagnostic-deprecated-declarations - comms_(executor_->ipc()->comms()), - ipc_(executor_->ipc()) { - // It's a pre-connected Comms channel, no need to accept new connection. - CHECK(comms_->IsConnected()); - std::string path = - absl::GetFlag(FLAGS_sandbox2_danger_danger_permit_all_and_log); - if (!path.empty()) { - log_file_ = std::fopen(path.c_str(), "a+"); - PCHECK(log_file_ != nullptr) << "Failed to open log file '" << path << "'"; - } - - if (auto* ns = policy_->GetNamespace(); ns) { - // Check for the Tomoyo LSM, which is active by default in several common - // distribution kernels (esp. Debian). - MaybeEnableTomoyoLsmWorkaround(ns->mounts(), comms_fd_dev_); - } -} - PtraceMonitor::PtraceMonitor(Executor* executor, Policy* policy, Notify* notify) : MonitorBase(executor, policy, notify), wait_for_execve_(executor->enable_sandboxing_pre_execve_), uses_custom_forkserver_(executor_->fork_client_ != nullptr) { if (executor_->limits()->wall_time_limit() != absl::ZeroDuration()) { auto deadline = absl::Now() + executor_->limits()->wall_time_limit(); - LOG(INFO) << executor_->limits()->wall_time_limit(); deadline_millis_.store(absl::ToUnixMillis(deadline), std::memory_order_relaxed); } @@ -285,130 +189,6 @@ PtraceMonitor::PtraceMonitor(Executor* executor, Policy* policy, Notify* notify) dump_stack_request_flag_.test_and_set(std::memory_order_relaxed); } -MonitorBase::~MonitorBase() { - if (!comms_fd_dev_.empty()) { - std::remove(comms_fd_dev_.c_str()); - } - if (log_file_) { - std::fclose(log_file_); - } - if (network_proxy_server_) { - network_proxy_thread_.join(); - } -} - -namespace { - -void LogContainer(const std::vector& container) { - for (size_t i = 0; i < container.size(); ++i) { - LOG(INFO) << "[" << std::setfill('0') << std::setw(4) << i - << "]=" << container[i]; - } -} - -} // namespace - -void MonitorBase::OnDone() { - if (done_notification_.HasBeenNotified()) { - return; - } - - notify_->EventFinished(result_); - ipc_->InternalCleanupFdMap(); - done_notification_.Notify(); -} - -void MonitorBase::Launch() { - - absl::Cleanup process_cleanup = [this] { - if (process_.init_pid > 0) { - kill(process_.init_pid, SIGKILL); - } else if (process_.main_pid > 0) { - kill(process_.main_pid, SIGKILL); - } - }; - absl::Cleanup monitor_done = [this] { OnDone(); }; - - Namespace* ns = policy_->GetNamespace(); - if (SAPI_VLOG_IS_ON(1) && ns != nullptr) { - std::vector outside_entries; - std::vector inside_entries; - ns->mounts().RecursivelyListMounts( - /*outside_entries=*/&outside_entries, - /*inside_entries=*/&inside_entries); - VLOG(1) << "Outside entries mapped to chroot:"; - LogContainer(outside_entries); - VLOG(1) << "Inside entries as they appear in chroot:"; - LogContainer(inside_entries); - } - - // Don't trace the child: it will allow to use 'strace -f' with the whole - // sandbox master/monitor, which ptrace_attach'es to the child. - int clone_flags = CLONE_UNTRACED; - - if (policy_->allowed_hosts_) { - EnableNetworkProxyServer(); - } - - // Get PID of the sandboxee. - bool should_have_init = ns && (ns->GetCloneFlags() & CLONE_NEWPID); - absl::StatusOr process = - executor_->StartSubProcess(clone_flags, ns, policy_->capabilities()); - - if (!process.ok()) { - LOG(ERROR) << "Starting sandboxed subprocess failed: " << process.status(); - SetExitStatusCode(Result::SETUP_ERROR, Result::FAILED_SUBPROCESS); - return; - } - - process_ = *std::move(process); - - if (process_.main_pid <= 0 || (should_have_init && process_.main_pid <= 0)) { - SetExitStatusCode(Result::SETUP_ERROR, Result::FAILED_SUBPROCESS); - return; - } - - if (!notify_->EventStarted(process_.main_pid, comms_)) { - SetExitStatusCode(Result::SETUP_ERROR, Result::FAILED_NOTIFY); - return; - } - if (!InitSendIPC()) { - SetExitStatusCode(Result::SETUP_ERROR, Result::FAILED_IPC); - return; - } - if (!InitSendCwd()) { - SetExitStatusCode(Result::SETUP_ERROR, Result::FAILED_CWD); - return; - } - if (!InitSendPolicy()) { - SetExitStatusCode(Result::SETUP_ERROR, Result::FAILED_POLICY); - return; - } - if (!WaitForSandboxReady()) { - SetExitStatusCode(Result::SETUP_ERROR, Result::FAILED_WAIT); - return; - } - if (!InitApplyLimits()) { - SetExitStatusCode(Result::SETUP_ERROR, Result::FAILED_LIMITS); - return; - } - std::move(process_cleanup).Cancel(); - - RunInternal(); - std::move(monitor_done).Cancel(); -} - -absl::StatusOr MonitorBase::AwaitResultWithTimeout( - absl::Duration timeout) { - auto done = done_notification_.WaitForNotificationWithTimeout(timeout); - if (!done) { - return absl::DeadlineExceededError("Sandbox did not finish within timeout"); - } - - Join(); - return result_; -} - bool PtraceMonitor::IsActivelyMonitoring() { // If we're still waiting for execve(), then we allow all syscalls. return !wait_for_execve_; @@ -416,12 +196,6 @@ bool PtraceMonitor::IsActivelyMonitoring() { void PtraceMonitor::SetActivelyMonitoring() { wait_for_execve_ = false; } -void MonitorBase::SetExitStatusCode(Result::StatusEnum final_status, - uintptr_t reason_code) { - CHECK(result_.final_status() == Result::UNSET); - result_.SetExitStatusCode(final_status, reason_code); -} - bool PtraceMonitor::StackTraceCollectionPossible() const { // Only get the stacktrace if we are not in the libunwind sandbox (avoid // recursion). @@ -765,83 +539,6 @@ bool PtraceMonitor::InitSetupSignals() { return true; } -bool MonitorBase::InitSendPolicy() { - if (!policy_->SendPolicy(comms_)) { - LOG(ERROR) << "Couldn't send policy"; - return false; - } - - return true; -} - -bool MonitorBase::InitSendCwd() { - if (!comms_->SendString(executor_->cwd_)) { - PLOG(ERROR) << "Couldn't send cwd"; - return false; - } - - return true; -} - -bool MonitorBase::InitApplyLimit(pid_t pid, int resource, - const rlimit64& rlim) const { -#if defined(__ANDROID__) - using RlimitResource = int; -#else - using RlimitResource = __rlimit_resource; -#endif - - rlimit64 curr_limit; - if (prlimit64(pid, static_cast(resource), nullptr, - &curr_limit) == -1) { - PLOG(ERROR) << "prlimit64(" << pid << ", " << util::GetRlimitName(resource) - << ")"; - } else if (rlim.rlim_cur > curr_limit.rlim_max) { - // In such case, don't update the limits, as it will fail. Just stick to the - // current ones (which are already lower than intended). - LOG(ERROR) << util::GetRlimitName(resource) - << ": new.current > current.max (" << rlim.rlim_cur << " > " - << curr_limit.rlim_max << "), skipping"; - return true; - } - - if (prlimit64(pid, static_cast(resource), &rlim, nullptr) == - -1) { - PLOG(ERROR) << "prlimit64(" << pid << ", " << util::GetRlimitName(resource) - << ", " << rlim.rlim_cur << ")"; - return false; - } - - return true; -} - -bool MonitorBase::InitApplyLimits() { - Limits* limits = executor_->limits(); - return InitApplyLimit(process_.main_pid, RLIMIT_AS, limits->rlimit_as()) && - InitApplyLimit(process_.main_pid, RLIMIT_CPU, limits->rlimit_cpu()) && - InitApplyLimit(process_.main_pid, RLIMIT_FSIZE, - limits->rlimit_fsize()) && - InitApplyLimit(process_.main_pid, RLIMIT_NOFILE, - limits->rlimit_nofile()) && - InitApplyLimit(process_.main_pid, RLIMIT_CORE, limits->rlimit_core()); -} - -bool MonitorBase::InitSendIPC() { return ipc_->SendFdsOverComms(); } - -bool MonitorBase::WaitForSandboxReady() { - uint32_t tmp; - if (!comms_->RecvUint32(&tmp)) { - LOG(ERROR) << "Couldn't receive 'Client::kClient2SandboxReady' message"; - return false; - } - if (tmp != Client::kClient2SandboxReady) { - LOG(ERROR) << "Received " << tmp << " != Client::kClient2SandboxReady (" - << Client::kClient2SandboxReady << ")"; - return false; - } - return true; -} - bool PtraceMonitor::InitPtraceAttach() { sanitizer::WaitForSanitizer(); @@ -1035,28 +732,6 @@ void PtraceMonitor::ActionProcessSyscallViolation( } } -void MonitorBase::LogSyscallViolation(const Syscall& syscall) const { - // Do not unwind libunwind. - if (executor_->libunwind_sbox_for_pid_ != 0) { - LOG(ERROR) << "Sandbox violation during execution of libunwind: " - << syscall.GetDescription(); - return; - } - - // So, this is an invalid syscall. Will be killed by seccomp-bpf policies as - // well, but we should be on a safe side here as well. - LOG(ERROR) << "SANDBOX VIOLATION : PID: " << syscall.pid() << ", PROG: '" - << util::GetProgName(syscall.pid()) - << "' : " << syscall.GetDescription(); - if (SAPI_VLOG_IS_ON(1)) { - VLOG(1) << "Cmdline: " << util::GetCmdLine(syscall.pid()); - VLOG(1) << "Task Name: " << util::GetProcStatusLine(syscall.pid(), "Name"); - VLOG(1) << "Tgid: " << util::GetProcStatusLine(syscall.pid(), "Tgid"); - } - - LogSyscallViolationExplanation(syscall); -} - void PtraceMonitor::EventPtraceSeccomp(pid_t pid, int event_msg) { if (event_msg < sapi::cpu::Architecture::kUnknown || event_msg > sapi::cpu::Architecture::kMax) { @@ -1361,44 +1036,4 @@ void PtraceMonitor::StateProcessStopped(pid_t pid, int status) { } } -void MonitorBase::LogSyscallViolationExplanation(const Syscall& syscall) const { - const uintptr_t syscall_nr = syscall.nr(); - const uintptr_t arg0 = syscall.args()[0]; - - // This follows policy in Policy::GetDefaultPolicy - keep it in sync. - if (syscall.arch() != Syscall::GetHostArch()) { - LOG(ERROR) - << "This is a violation because the syscall was issued because the" - << " sandboxee and executor architectures are different."; - return; - } - if (syscall_nr == __NR_ptrace) { - LOG(ERROR) - << "This is a violation because the ptrace syscall would be unsafe in" - << " sandbox2, so it has been blocked."; - return; - } - if (syscall_nr == __NR_bpf) { - LOG(ERROR) - << "This is a violation because the bpf syscall would be risky in" - << " a sandbox, so it has been blocked."; - return; - } - if (syscall_nr == __NR_clone && ((arg0 & CLONE_UNTRACED) != 0)) { - LOG(ERROR) << "This is a violation because calling clone with CLONE_UNTRACE" - << " would be unsafe in sandbox2, so it has been blocked."; - return; - } -} - -void MonitorBase::EnableNetworkProxyServer() { - int fd = ipc_->ReceiveFd(NetworkProxyClient::kFDName); - - network_proxy_server_ = std::make_unique( - fd, &policy_->allowed_hosts_.value(), pthread_self()); - - network_proxy_thread_ = std::thread(&NetworkProxyServer::Run, - network_proxy_server_.get()); -} - } // namespace sandbox2 diff --git a/sandboxed_api/sandbox2/monitor.h b/sandboxed_api/sandbox2/monitor_ptrace.h similarity index 63% rename from sandboxed_api/sandbox2/monitor.h rename to sandboxed_api/sandbox2/monitor_ptrace.h index 6d11815..8bcac20 100644 --- a/sandboxed_api/sandbox2/monitor.h +++ b/sandboxed_api/sandbox2/monitor_ptrace.h @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC +// Copyright 2023 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,16 +15,12 @@ // The sandbox2::Monitor class is responsible for tracking the processes, and // displaying their current statuses (syscalls, states, violations). -#ifndef SANDBOXED_API_SANDBOX2_MONITOR_H_ -#define SANDBOXED_API_SANDBOX2_MONITOR_H_ - -#include +#ifndef SANDBOXED_API_SANDBOX2_MONITOR_PTRACE_H_ +#define SANDBOXED_API_SANDBOX2_MONITOR_PTRACE_H_ #include #include #include -#include -#include #include #include @@ -32,10 +28,8 @@ #include "absl/status/statusor.h" #include "absl/synchronization/mutex.h" #include "absl/synchronization/notification.h" -#include "sandboxed_api/sandbox2/comms.h" #include "sandboxed_api/sandbox2/executor.h" -#include "sandboxed_api/sandbox2/ipc.h" -#include "sandboxed_api/sandbox2/network_proxy/server.h" +#include "sandboxed_api/sandbox2/monitor_base.h" #include "sandboxed_api/sandbox2/notify.h" #include "sandboxed_api/sandbox2/policy.h" #include "sandboxed_api/sandbox2/regs.h" @@ -45,103 +39,6 @@ namespace sandbox2 { -class MonitorBase { - public: - // executor, policy and notify are not owned by the Monitor - MonitorBase(Executor* executor, Policy* policy, Notify* notify); - - MonitorBase(const MonitorBase&) = delete; - MonitorBase& operator=(const MonitorBase&) = delete; - - virtual ~MonitorBase(); - - // Starts the Monitor. - void Launch(); - - // Getters for private fields. - bool IsDone() const { return done_notification_.HasBeenNotified(); } - - // Enable network proxy server, this will start a thread in the sandbox - // that waits for connection requests from the sandboxee. - void EnableNetworkProxyServer(); - - pid_t pid() const { return process_.main_pid; } - - const Result& result() const { return result_; } - - absl::StatusOr AwaitResultWithTimeout(absl::Duration timeout); - - virtual void Kill() = 0; - virtual void DumpStackTrace() = 0; - virtual void SetWallTimeLimit(absl::Duration limit) = 0; - - protected: - void OnDone(); - // Sets basic info status and reason code in the result object. - void SetExitStatusCode(Result::StatusEnum final_status, - uintptr_t reason_code); - // Logs a SANDBOX VIOLATION message based on the registers and additional - // explanation for the reason of the violation. - void LogSyscallViolation(const Syscall& syscall) const; - - // Internal objects, owned by the Sandbox2 object. - Executor* executor_; - Notify* notify_; - Policy* policy_; - // The sandboxee process. - Executor::Process process_; - Result result_; - // Comms channel ptr, copied from the Executor object for convenience. - Comms* comms_; - // Log file specified by - // --sandbox_danger_danger_permit_all_and_log flag. - FILE* log_file_ = nullptr; - // Handle to the class responsible for proxying and validating connect() - // requests. - std::unique_ptr network_proxy_server_; - - private: - // Sends Policy to the Client. - // Returns success/failure status. - bool InitSendPolicy(); - - // Waits for the SandboxReady signal from the client. - // Returns success/failure status. - bool WaitForSandboxReady(); - - // Sends information about data exchange channels. - bool InitSendIPC(); - - // Sends information about the current working directory. - bool InitSendCwd(); - - // Applies limits on the sandboxee. - bool InitApplyLimits(); - - // Applies individual limit on the sandboxee. - bool InitApplyLimit(pid_t pid, int resource, const rlimit64& rlim) const; - - // Logs an additional explanation for the possible reason of the violation - // based on the registers. - void LogSyscallViolationExplanation(const Syscall& syscall) const; - - virtual void RunInternal() = 0; - virtual void Join() = 0; - - // IPC ptr, used for exchanging data with the sandboxee. - IPC* ipc_; - - // The field indicates whether the sandboxing task has been completed (either - // successfully or with error). - absl::Notification done_notification_; - - // Empty temp file used for mapping the comms fd when the Tomoyo LSM is - // active. - std::string comms_fd_dev_; - - std::thread network_proxy_thread_; -}; - class PtraceMonitor : public MonitorBase { public: PtraceMonitor(Executor* executor, Policy* policy, Notify* notify); @@ -281,4 +178,4 @@ class PtraceMonitor : public MonitorBase { } // namespace sandbox2 -#endif // SANDBOXED_API_SANDBOX2_MONITOR_H_ +#endif // SANDBOXED_API_SANDBOX2_MONITOR_BASE_H_ diff --git a/sandboxed_api/sandbox2/sandbox2.cc b/sandboxed_api/sandbox2/sandbox2.cc index d3abcd8..469bac3 100644 --- a/sandboxed_api/sandbox2/sandbox2.cc +++ b/sandboxed_api/sandbox2/sandbox2.cc @@ -23,7 +23,7 @@ #include "absl/log/check.h" #include "absl/status/statusor.h" #include "absl/time/time.h" -#include "sandboxed_api/sandbox2/monitor.h" +#include "sandboxed_api/sandbox2/monitor_ptrace.h" #include "sandboxed_api/sandbox2/result.h" namespace sandbox2 { diff --git a/sandboxed_api/sandbox2/sandbox2.h b/sandboxed_api/sandbox2/sandbox2.h index 204b72f..386c04b 100644 --- a/sandboxed_api/sandbox2/sandbox2.h +++ b/sandboxed_api/sandbox2/sandbox2.h @@ -27,7 +27,7 @@ #include "sandboxed_api/sandbox2/comms.h" #include "sandboxed_api/sandbox2/executor.h" #include "sandboxed_api/sandbox2/ipc.h" -#include "sandboxed_api/sandbox2/monitor.h" +#include "sandboxed_api/sandbox2/monitor_base.h" #include "sandboxed_api/sandbox2/notify.h" #include "sandboxed_api/sandbox2/policy.h" #include "sandboxed_api/sandbox2/result.h"