diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-01-31 16:33:43 +0100 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-02-06 16:33:22 +0000 |
commit | da51f56cc21233c2d30f0fe0d171727c3102b2e0 (patch) | |
tree | 4e579ab70ce4b19bee7984237f3ce05a96d59d83 /chromium/sandbox | |
parent | c8c2d1901aec01e934adf561a9fdf0cc776cdef8 (diff) | |
download | qtwebengine-chromium-da51f56cc21233c2d30f0fe0d171727c3102b2e0.tar.gz |
BASELINE: Update Chromium to 65.0.3525.40
Also imports missing submodules
Change-Id: I36901b7c6a325cda3d2c10cedb2186c25af3b79b
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
Diffstat (limited to 'chromium/sandbox')
48 files changed, 2408 insertions, 749 deletions
diff --git a/chromium/sandbox/OWNERS b/chromium/sandbox/OWNERS index 38a06dc7770..c7304374f32 100644 --- a/chromium/sandbox/OWNERS +++ b/chromium/sandbox/OWNERS @@ -1,7 +1,8 @@ -cpu@chromium.org -jln@chromium.org +jorgelo@chromium.org jschuh@chromium.org +palmer@chromium.org rsesek@chromium.org +tsepez@chromium.org # TEAM: security-dev@chromium.org # COMPONENT: Internals>Sandbox diff --git a/chromium/sandbox/linux/BUILD.gn b/chromium/sandbox/linux/BUILD.gn index 8c9cf2c9458..9d4726f89e4 100644 --- a/chromium/sandbox/linux/BUILD.gn +++ b/chromium/sandbox/linux/BUILD.gn @@ -358,13 +358,14 @@ component("sandbox_services") { "syscall_broker/broker_channel.h", "syscall_broker/broker_client.cc", "syscall_broker/broker_client.h", - "syscall_broker/broker_common.h", + "syscall_broker/broker_command.cc", + "syscall_broker/broker_command.h", "syscall_broker/broker_file_permission.cc", "syscall_broker/broker_file_permission.h", "syscall_broker/broker_host.cc", "syscall_broker/broker_host.h", - "syscall_broker/broker_policy.cc", - "syscall_broker/broker_policy.h", + "syscall_broker/broker_permission_list.cc", + "syscall_broker/broker_permission_list.h", "syscall_broker/broker_process.cc", "syscall_broker/broker_process.h", ] @@ -406,13 +407,14 @@ component("sandbox_services") { "syscall_broker/broker_channel.h", "syscall_broker/broker_client.cc", "syscall_broker/broker_client.h", - "syscall_broker/broker_common.h", + "syscall_broker/broker_command.cc", + "syscall_broker/broker_command.h", "syscall_broker/broker_file_permission.cc", "syscall_broker/broker_file_permission.h", "syscall_broker/broker_host.cc", "syscall_broker/broker_host.h", - "syscall_broker/broker_policy.cc", - "syscall_broker/broker_policy.h", + "syscall_broker/broker_permission_list.cc", + "syscall_broker/broker_permission_list.h", "syscall_broker/broker_process.cc", "syscall_broker/broker_process.h", ] diff --git a/chromium/sandbox/linux/OWNERS b/chromium/sandbox/linux/OWNERS index 35461850b4c..7ea24db8239 100644 --- a/chromium/sandbox/linux/OWNERS +++ b/chromium/sandbox/linux/OWNERS @@ -1,5 +1,5 @@ -jln@chromium.org jorgelo@chromium.org +palmer@chromium.org rsesek@chromium.org # TEAM: security-dev@chromium.org diff --git a/chromium/sandbox/linux/integration_tests/seccomp_broker_process_unittest.cc b/chromium/sandbox/linux/integration_tests/seccomp_broker_process_unittest.cc index 73dc58c92b8..b1990041425 100644 --- a/chromium/sandbox/linux/integration_tests/seccomp_broker_process_unittest.cc +++ b/chromium/sandbox/linux/integration_tests/seccomp_broker_process_unittest.cc @@ -20,6 +20,7 @@ #include "sandbox/linux/bpf_dsl/seccomp_macros.h" #include "sandbox/linux/seccomp-bpf/bpf_tests.h" #include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/syscall_broker/broker_command.h" #include "sandbox/linux/syscall_broker/broker_file_permission.h" #include "sandbox/linux/syscall_broker/broker_process.h" #include "sandbox/linux/system_headers/linux_syscalls.h" @@ -43,27 +44,27 @@ bool NoOpCallback() { class InitializedOpenBroker { public: InitializedOpenBroker() : initialized_(false) { - std::vector<syscall_broker::BrokerFilePermission> permissions; - permissions.push_back( - syscall_broker::BrokerFilePermission::ReadOnly("/proc/allowed")); - permissions.push_back( - syscall_broker::BrokerFilePermission::ReadOnly("/proc/cpuinfo")); - - broker_process_.reset( - new syscall_broker::BrokerProcess(EPERM, permissions)); - BPF_ASSERT(broker_process() != NULL); + syscall_broker::BrokerCommandSet command_set; + command_set.set(syscall_broker::COMMAND_OPEN); + command_set.set(syscall_broker::COMMAND_ACCESS); + std::vector<syscall_broker::BrokerFilePermission> permissions = { + syscall_broker::BrokerFilePermission::ReadOnly("/proc/allowed"), + syscall_broker::BrokerFilePermission::ReadOnly("/proc/cpuinfo")}; + broker_process_ = std::make_unique<syscall_broker::BrokerProcess>( + EPERM, command_set, permissions); BPF_ASSERT(broker_process_->Init(base::Bind(&NoOpCallback))); - initialized_ = true; } - bool initialized() { return initialized_; } - class syscall_broker::BrokerProcess* broker_process() { + + bool initialized() const { return initialized_; } + + syscall_broker::BrokerProcess* broker_process() const { return broker_process_.get(); } private: bool initialized_; - std::unique_ptr<class syscall_broker::BrokerProcess> broker_process_; + std::unique_ptr<syscall_broker::BrokerProcess> broker_process_; DISALLOW_COPY_AND_ASSIGN(InitializedOpenBroker); }; diff --git a/chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy.cc b/chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy.cc index d06e7653a0c..6aab37a88f4 100644 --- a/chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy.cc +++ b/chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy.cc @@ -268,9 +268,10 @@ ResultExpr EvaluateSyscallImpl(int fs_denied_errno, } // namespace. -// Unfortunately C++03 doesn't allow delegated constructors. -// Call other constructor when C++11 lands. -BaselinePolicy::BaselinePolicy() : BaselinePolicy(EPERM) {} +BaselinePolicy::BaselinePolicy() : BaselinePolicy(EPERM) { + // Allocate crash keys set by Seccomp signal handlers. + AllocateCrashKeys(); +} BaselinePolicy::BaselinePolicy(int fs_denied_errno) : fs_denied_errno_(fs_denied_errno), policy_pid_(sys_getpid()) { diff --git a/chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy_android.cc b/chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy_android.cc index ca6f08a2c2b..9a37225780b 100644 --- a/chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy_android.cc +++ b/chromium/sandbox/linux/seccomp-bpf-helpers/baseline_policy_android.cc @@ -205,6 +205,7 @@ ResultExpr BaselinePolicyAndroid::EvaluateSyscall(int sysno) const { return If(AllOf(level == SOL_SOCKET, AnyOf(option == SO_SNDTIMEO, option == SO_RCVTIMEO, + option == SO_SNDBUF, option == SO_REUSEADDR)), Allow()) .Else(BaselinePolicy::EvaluateSyscall(sysno)); diff --git a/chromium/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.cc b/chromium/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.cc index 68890d2536c..8386b0f906e 100644 --- a/chromium/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.cc +++ b/chromium/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.cc @@ -35,6 +35,10 @@ namespace { +#if !defined(OS_NACL_NONSFI) +base::debug::CrashKeyString* seccomp_crash_key = nullptr; +#endif + inline bool IsArchitectureX86_64() { #if defined(__x86_64__) return true; @@ -185,7 +189,7 @@ void SetSeccompCrashKey(const struct sandbox::arch_seccomp_data& args) { } } - base::debug::SetCrashKeyValue("seccomp-sigsys", crash_key); + base::debug::SetCrashKeyString(seccomp_crash_key, crash_key); #endif } @@ -362,6 +366,16 @@ bpf_dsl::ResultExpr RewriteSchedSIGSYS() { return bpf_dsl::Trap(SIGSYSSchedHandler, NULL); } +void AllocateCrashKeys() { +#if !defined(OS_NACL_NONSFI) + if (seccomp_crash_key) + return; + + seccomp_crash_key = base::debug::AllocateCrashKeyString( + "seccomp-sigsys", base::debug::CrashKeySize::Size256); +#endif +} + const char* GetErrorMessageContentForTests() { return SECCOMP_MESSAGE_COMMON_CONTENT; } diff --git a/chromium/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h b/chromium/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h index c64e994172e..b32fd6fedd4 100644 --- a/chromium/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h +++ b/chromium/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h @@ -68,6 +68,9 @@ SANDBOX_EXPORT bpf_dsl::ResultExpr CrashSIGSYSKill(); SANDBOX_EXPORT bpf_dsl::ResultExpr CrashSIGSYSFutex(); SANDBOX_EXPORT bpf_dsl::ResultExpr RewriteSchedSIGSYS(); +// Allocates a crash key so that Seccomp information can be recorded. +void AllocateCrashKeys(); + // Following four functions return substrings of error messages used // in the above four functions. They are useful in death tests. SANDBOX_EXPORT const char* GetErrorMessageContentForTests(); diff --git a/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc b/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc index 440ef20681f..ada0b8f23ae 100644 --- a/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc +++ b/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc @@ -341,8 +341,13 @@ ResultExpr RestrictGetrusage() { ResultExpr RestrictClockID() { static_assert(4 == sizeof(clockid_t), "clockid_t is not 32bit"); const Arg<clockid_t> clockid(0); - return Switch(clockid) - .CASES(( + + // Clock IDs < 0 are per pid/tid or are clockfds. + const unsigned int kIsPidBit = 1u<<31; + + return + If((clockid & kIsPidBit) == 0, + Switch(clockid).CASES(( #if defined(OS_ANDROID) CLOCK_BOOTTIME, #endif @@ -353,7 +358,12 @@ ResultExpr RestrictClockID() { CLOCK_REALTIME_COARSE, CLOCK_THREAD_CPUTIME_ID), Allow()) - .Default(CrashSIGSYS()); + .Default(CrashSIGSYS())) +#if defined(OS_ANDROID) + // Allow per-pid and per-tid clocks. + .ElseIf((clockid & CPUCLOCK_CLOCK_MASK) != CLOCKFD, Allow()) +#endif + .Else(CrashSIGSYS()); } #if !defined(GRND_NONBLOCK) diff --git a/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions_unittests.cc b/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions_unittests.cc index c068cd2d04f..b572a618ff7 100644 --- a/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions_unittests.cc +++ b/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions_unittests.cc @@ -86,6 +86,11 @@ BPF_TEST_C(ParameterRestrictions, CheckClock(CLOCK_REALTIME); CheckClock(CLOCK_REALTIME_COARSE); CheckClock(CLOCK_THREAD_CPUTIME_ID); +#if defined(OS_ANDROID) + clockid_t clock_id; + pthread_getcpuclockid(pthread_self(), &clock_id); + CheckClock(clock_id); +#endif } BPF_DEATH_TEST_C(ParameterRestrictions, diff --git a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.cc b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.cc index 9ff79d79658..72a79670d32 100644 --- a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.cc +++ b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.cc @@ -15,8 +15,6 @@ #include "base/logging.h" #include "base/macros.h" #include "base/posix/eintr_wrapper.h" -#include "base/third_party/dynamic_annotations/dynamic_annotations.h" -#include "base/third_party/valgrind/valgrind.h" #include "sandbox/linux/bpf_dsl/bpf_dsl.h" #include "sandbox/linux/bpf_dsl/codegen.h" #include "sandbox/linux/bpf_dsl/policy.h" @@ -122,12 +120,6 @@ SandboxBPF::~SandboxBPF() { // static bool SandboxBPF::SupportsSeccompSandbox(SeccompLevel level) { - // Never pretend to support seccomp with Valgrind, as it - // throws the tool off. - if (RunningOnValgrind()) { - return false; - } - switch (level) { case SeccompLevel::SINGLE_THREADED: return KernelSupportsSeccompBPF(); diff --git a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.cc b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.cc index 5f59cbbcc65..36f3744b76e 100644 --- a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.cc +++ b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.cc @@ -9,7 +9,6 @@ #include <memory> #include "base/logging.h" -#include "base/third_party/dynamic_annotations/dynamic_annotations.h" #include "sandbox/linux/bpf_dsl/policy.h" #include "sandbox/linux/seccomp-bpf/die.h" #include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" @@ -44,9 +43,9 @@ void SandboxBPFTestRunner::Run() { bpf_tester_delegate_->RunTestFunction(); } else { printf("This BPF test is not fully running in this configuration!\n"); - // Android and Valgrind are the only configurations where we accept not - // having kernel BPF support. - if (!IsAndroid() && !RunningOnValgrind()) { + // Android is the only configuration where we accept not having kernel + // BPF support. + if (!IsAndroid()) { const bool seccomp_bpf_is_supported = false; SANDBOX_ASSERT(seccomp_bpf_is_supported); } diff --git a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.h b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.h index 1cf1c2f856f..4fc3c5d1696 100644 --- a/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.h +++ b/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.h @@ -40,7 +40,7 @@ class BPFTesterDelegate { // run a test function (via |bpf_tester_delegate|) if the current kernel // configuration allows it. If it can not run the test under seccomp-bpf, // Run() will still compile the policy which should allow to get some coverage -// under tools such as Valgrind. +// under tools that behave like Valgrind. class SandboxBPFTestRunner : public SandboxTestRunner { public: // This constructor takes ownership of the |bpf_tester_delegate| object. diff --git a/chromium/sandbox/linux/seccomp-bpf/syscall.h b/chromium/sandbox/linux/seccomp-bpf/syscall.h index ccfc88dcb30..3b02a6723f5 100644 --- a/chromium/sandbox/linux/seccomp-bpf/syscall.h +++ b/chromium/sandbox/linux/seccomp-bpf/syscall.h @@ -28,7 +28,7 @@ class SANDBOX_EXPORT Syscall { // System calls can take up to six parameters (up to eight on some // architectures). Traditionally, glibc // implements this property by using variadic argument lists. This works, but - // confuses modern tools such as valgrind, because we are nominally passing + // confuses tools that behave like Valgrind, because we are nominally passing // uninitialized data whenever we call through this function and pass less // than the full six arguments. // So, instead, we use C++'s template system to achieve a very similar diff --git a/chromium/sandbox/linux/seccomp-bpf/syscall_unittest.cc b/chromium/sandbox/linux/seccomp-bpf/syscall_unittest.cc index 057f0d4b2d1..e96f51b00c8 100644 --- a/chromium/sandbox/linux/seccomp-bpf/syscall_unittest.cc +++ b/chromium/sandbox/linux/seccomp-bpf/syscall_unittest.cc @@ -18,6 +18,7 @@ #include "base/macros.h" #include "base/posix/eintr_wrapper.h" +#include "base/process/process_metrics.h" #include "build/build_config.h" #include "sandbox/linux/bpf_dsl/bpf_dsl.h" #include "sandbox/linux/bpf_dsl/policy.h" @@ -163,6 +164,8 @@ BPF_TEST(Syscall, TEST(Syscall, ComplexSyscallSixArgs) { int fd; + const size_t kPageSize = base::GetPageSize(); + ASSERT_LE(0, fd = Syscall::Call(__NR_openat, AT_FDCWD, "/dev/null", O_RDWR, 0L)); @@ -172,7 +175,7 @@ TEST(Syscall, ComplexSyscallSixArgs) { (char*)NULL, addr0 = reinterpret_cast<char*>(Syscall::Call(kMMapNr, (void*)NULL, - 4096, + kPageSize, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, fd, @@ -184,7 +187,7 @@ TEST(Syscall, ComplexSyscallSixArgs) { addr1 = reinterpret_cast<char*>( Syscall::Call(kMMapNr, addr0, - 4096L, + kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, fd, @@ -192,7 +195,7 @@ TEST(Syscall, ComplexSyscallSixArgs) { ++*addr1; // This should not seg fault // Clean up - EXPECT_EQ(0, Syscall::Call(__NR_munmap, addr1, 4096L)); + EXPECT_EQ(0, Syscall::Call(__NR_munmap, addr1, kPageSize)); EXPECT_EQ(0, IGNORE_EINTR(Syscall::Call(__NR_close, fd))); // Check that the offset argument (i.e. the sixth argument) is processed @@ -202,32 +205,42 @@ TEST(Syscall, ComplexSyscallSixArgs) { 0); char* addr2, *addr3; ASSERT_NE((char*)NULL, - addr2 = reinterpret_cast<char*>(Syscall::Call( - kMMapNr, (void*)NULL, 8192L, PROT_READ, MAP_PRIVATE, fd, 0L))); + addr2 = reinterpret_cast<char*>(Syscall::Call(kMMapNr, + (void*)NULL, + 2 * kPageSize, + PROT_READ, + MAP_PRIVATE, + fd, + 0L + ))); ASSERT_NE((char*)NULL, addr3 = reinterpret_cast<char*>(Syscall::Call(kMMapNr, (void*)NULL, - 4096L, + kPageSize, PROT_READ, MAP_PRIVATE, fd, #if defined(__NR_mmap2) 1L #else - 4096L + kPageSize #endif ))); - EXPECT_EQ(0, memcmp(addr2 + 4096, addr3, 4096)); + EXPECT_EQ(0, memcmp(addr2 + kPageSize, addr3, kPageSize)); // Just to be absolutely on the safe side, also verify that the file // contents matches what we are getting from a read() operation. - char buf[8192]; - EXPECT_EQ(8192, Syscall::Call(__NR_read, fd, buf, 8192L)); - EXPECT_EQ(0, memcmp(addr2, buf, 8192)); + char buf[2 * kPageSize]; + EXPECT_EQ(2 * kPageSize, static_cast<size_t>(Syscall::Call(__NR_read, + fd, + buf, + 2 * kPageSize + ))); + EXPECT_EQ(0, memcmp(addr2, buf, 2 * kPageSize)); // Clean up - EXPECT_EQ(0, Syscall::Call(__NR_munmap, addr2, 8192L)); - EXPECT_EQ(0, Syscall::Call(__NR_munmap, addr3, 4096L)); + EXPECT_EQ(0, Syscall::Call(__NR_munmap, addr2, 2 * kPageSize)); + EXPECT_EQ(0, Syscall::Call(__NR_munmap, addr3, kPageSize)); EXPECT_EQ(0, IGNORE_EINTR(Syscall::Call(__NR_close, fd))); } diff --git a/chromium/sandbox/linux/services/credentials.cc b/chromium/sandbox/linux/services/credentials.cc index d97f0946621..65b63adfe2f 100644 --- a/chromium/sandbox/linux/services/credentials.cc +++ b/chromium/sandbox/linux/services/credentials.cc @@ -23,8 +23,6 @@ #include "base/macros.h" #include "base/posix/eintr_wrapper.h" #include "base/process/launch.h" -#include "base/third_party/dynamic_annotations/dynamic_annotations.h" -#include "base/third_party/valgrind/valgrind.h" #include "build/build_config.h" #include "sandbox/linux/services/namespace_utils.h" #include "sandbox/linux/services/proc_util.h" @@ -115,10 +113,10 @@ bool ChrootToSafeEmptyDir() { void CheckCloneNewUserErrno(int error) { // EPERM can happen if already in a chroot. EUSERS if too many nested // namespaces are used. EINVAL for kernels that don't support the feature. - // Valgrind will ENOSYS unshare(). ENOSPC can occur when the system has - // reached its maximum configured number of user namespaces. + // ENOSPC can occur when the system has reached its maximum configured + // number of user namespaces. PCHECK(error == EPERM || error == EUSERS || error == EINVAL || - error == ENOSYS || error == ENOSPC); + error == ENOSPC); } // Converts a Capability to the corresponding Linux CAP_XXX value. @@ -256,12 +254,6 @@ bool Credentials::HasCapability(Capability cap) { // static bool Credentials::CanCreateProcessInNewUserNS() { - // Valgrind will let clone(2) pass-through, but doesn't support unshare(), - // so always consider UserNS unsupported there. - if (RunningOnValgrind()) { - return false; - } - #if defined(THREAD_SANITIZER) // With TSAN, processes will always have threads running and can never // enter a new user namespace with MoveToNewUserNS(). diff --git a/chromium/sandbox/linux/services/namespace_utils.cc b/chromium/sandbox/linux/services/namespace_utils.cc index 83749adf037..376b179f4f6 100644 --- a/chromium/sandbox/linux/services/namespace_utils.cc +++ b/chromium/sandbox/linux/services/namespace_utils.cc @@ -20,8 +20,6 @@ #include "base/posix/eintr_wrapper.h" #include "base/process/launch.h" #include "base/strings/safe_sprintf.h" -#include "base/third_party/dynamic_annotations/dynamic_annotations.h" -#include "base/third_party/valgrind/valgrind.h" namespace sandbox { @@ -52,12 +50,6 @@ bool NamespaceUtils::WriteToIdMapFile(const char* map_file, generic_id_t id) { // static bool NamespaceUtils::KernelSupportsUnprivilegedNamespace(int type) { - // Valgrind will let clone(2) pass-through, but doesn't support unshare(), - // so always consider namespaces unsupported there. - if (RunningOnValgrind()) { - return false; - } - // As of Linux 3.8, /proc/self/ns/* files exist for all namespace types. Since // user namespaces were added in 3.8, it is OK to rely on the existence of // /proc/self/ns/*. diff --git a/chromium/sandbox/linux/services/syscall_wrappers.cc b/chromium/sandbox/linux/services/syscall_wrappers.cc index 9c7727cee50..fcfd2aa129d 100644 --- a/chromium/sandbox/linux/services/syscall_wrappers.cc +++ b/chromium/sandbox/linux/services/syscall_wrappers.cc @@ -16,7 +16,6 @@ #include "base/compiler_specific.h" #include "base/logging.h" -#include "base/third_party/valgrind/valgrind.h" #include "build/build_config.h" #include "sandbox/linux/system_headers/capability.h" #include "sandbox/linux/system_headers/linux_signal.h" diff --git a/chromium/sandbox/linux/services/syscall_wrappers_unittest.cc b/chromium/sandbox/linux/services/syscall_wrappers_unittest.cc index 34ac7409307..b28e6381bff 100644 --- a/chromium/sandbox/linux/services/syscall_wrappers_unittest.cc +++ b/chromium/sandbox/linux/services/syscall_wrappers_unittest.cc @@ -13,7 +13,6 @@ #include "base/logging.h" #include "base/posix/eintr_wrapper.h" -#include "base/third_party/valgrind/valgrind.h" #include "build/build_config.h" #include "sandbox/linux/system_headers/linux_signal.h" #include "sandbox/linux/tests/test_utils.h" diff --git a/chromium/sandbox/linux/services/thread_helpers_unittests.cc b/chromium/sandbox/linux/services/thread_helpers_unittests.cc index 78e6d837e20..5e574599fcb 100644 --- a/chromium/sandbox/linux/services/thread_helpers_unittests.cc +++ b/chromium/sandbox/linux/services/thread_helpers_unittests.cc @@ -14,7 +14,6 @@ #include "base/macros.h" #include "base/posix/eintr_wrapper.h" #include "base/process/process_metrics.h" -#include "base/third_party/dynamic_annotations/dynamic_annotations.h" #include "base/threading/platform_thread.h" #include "base/threading/thread.h" #include "build/build_config.h" @@ -30,13 +29,7 @@ namespace { // These tests fail under ThreadSanitizer, see http://crbug.com/342305 #if !defined(THREAD_SANITIZER) -int GetRaceTestIterations() { - if (RunningOnValgrind()) { - return 2; - } else { - return 1000; - } -} +const int kRaceTestIterations = 1000; class ScopedProc { public: @@ -81,7 +74,7 @@ TEST(ThreadHelpers, IsSingleThreadedIterated) { ASSERT_TRUE(ThreadHelpers::IsSingleThreaded(proc_fd.fd())); // Iterate to check for race conditions. - for (int i = 0; i < GetRaceTestIterations(); ++i) { + for (int i = 0; i < kRaceTestIterations; ++i) { base::Thread thread("sandbox_tests"); ASSERT_TRUE( ThreadHelpers::StartThreadAndWatchProcFS(proc_fd.fd(), &thread)); @@ -98,7 +91,7 @@ TEST(ThreadHelpers, IsSingleThreadedStartAndStop) { base::Thread thread("sandbox_tests"); // This is testing for a race condition, so iterate. // Manually, this has been tested with more that 1M iterations. - for (int i = 0; i < GetRaceTestIterations(); ++i) { + for (int i = 0; i < kRaceTestIterations; ++i) { ASSERT_TRUE( ThreadHelpers::StartThreadAndWatchProcFS(proc_fd.fd(), &thread)); ASSERT_FALSE(ThreadHelpers::IsSingleThreaded(proc_fd.fd())); @@ -116,7 +109,7 @@ SANDBOX_TEST(ThreadHelpers, AssertSingleThreadedAfterThreadStopped) { base::Thread thread1("sandbox_tests"); base::Thread thread2("sandbox_tests"); - for (int i = 0; i < GetRaceTestIterations(); ++i) { + for (int i = 0; i < kRaceTestIterations; ++i) { SANDBOX_ASSERT( ThreadHelpers::StartThreadAndWatchProcFS(proc_fd.fd(), &thread1)); SANDBOX_ASSERT( diff --git a/chromium/sandbox/linux/syscall_broker/broker_client.cc b/chromium/sandbox/linux/syscall_broker/broker_client.cc index 277e61a2c4f..6ae93045b2a 100644 --- a/chromium/sandbox/linux/syscall_broker/broker_client.cc +++ b/chromium/sandbox/linux/syscall_broker/broker_client.cc @@ -13,150 +13,191 @@ #include <utility> #include "base/logging.h" -#include "base/pickle.h" #include "base/posix/unix_domain_socket.h" #include "build/build_config.h" #include "sandbox/linux/syscall_broker/broker_channel.h" -#include "sandbox/linux/syscall_broker/broker_common.h" -#include "sandbox/linux/syscall_broker/broker_policy.h" +#include "sandbox/linux/syscall_broker/broker_command.h" +#include "sandbox/linux/syscall_broker/broker_permission_list.h" #if defined(OS_ANDROID) && !defined(MSG_CMSG_CLOEXEC) #define MSG_CMSG_CLOEXEC 0x40000000 #endif namespace sandbox { - namespace syscall_broker { -// Make a remote system call over IPC for syscalls that take a path and flags -// as arguments, currently open() and access(). -// Will return -errno like a real system call. -// This function needs to be async signal safe. -int BrokerClient::PathAndFlagsSyscall(IPCCommand syscall_type, - const char* pathname, - int flags) const { - int recvmsg_flags = 0; - RAW_CHECK(syscall_type == COMMAND_OPEN || syscall_type == COMMAND_ACCESS); +BrokerClient::BrokerClient(const BrokerPermissionList& broker_permission_list, + BrokerChannel::EndPoint ipc_channel, + const BrokerCommandSet& allowed_command_set, + bool fast_check_in_client, + bool quiet_failures_for_tests) + : broker_permission_list_(broker_permission_list), + ipc_channel_(std::move(ipc_channel)), + allowed_command_set_(allowed_command_set), + fast_check_in_client_(fast_check_in_client), + quiet_failures_for_tests_(quiet_failures_for_tests) {} + +BrokerClient::~BrokerClient() {} + +int BrokerClient::Access(const char* pathname, int mode) const { if (!pathname) return -EFAULT; - // For this "remote system call" to work, we need to handle any flag that - // cannot be sent over a Unix socket in a special way. - // See the comments around kCurrentProcessOpenFlagsMask. - if (syscall_type == COMMAND_OPEN && (flags & kCurrentProcessOpenFlagsMask)) { - // This implementation only knows about O_CLOEXEC, someone needs to look at - // this code if other flags are added. - RAW_CHECK(kCurrentProcessOpenFlagsMask == O_CLOEXEC); - recvmsg_flags |= MSG_CMSG_CLOEXEC; - flags &= ~O_CLOEXEC; + if (fast_check_in_client_ && + !CommandAccessIsSafe(allowed_command_set_, broker_permission_list_, + pathname, mode, nullptr)) { + return -broker_permission_list_.denied_errno(); } + return PathAndFlagsSyscall(COMMAND_ACCESS, pathname, mode); +} - // There is no point in forwarding a request that we know will be denied. - // Of course, the real security check needs to be on the other side of the - // IPC. - if (fast_check_in_client_) { - if (syscall_type == COMMAND_OPEN && - !broker_policy_.GetFileNameIfAllowedToOpen( - pathname, flags, NULL /* file_to_open */, - NULL /* unlink_after_open */)) { - return -broker_policy_.denied_errno(); - } - if (syscall_type == COMMAND_ACCESS && - !broker_policy_.GetFileNameIfAllowedToAccess(pathname, flags, NULL)) { - return -broker_policy_.denied_errno(); - } +int BrokerClient::Mkdir(const char* pathname, int mode) const { + if (!pathname) + return -EFAULT; + + if (fast_check_in_client_ && + !CommandMkdirIsSafe(allowed_command_set_, broker_permission_list_, + pathname, nullptr)) { + return -broker_permission_list_.denied_errno(); + } + return PathAndFlagsSyscall(COMMAND_MKDIR, pathname, mode); +} + +int BrokerClient::Open(const char* pathname, int flags) const { + if (!pathname) + return -EFAULT; + + if (fast_check_in_client_ && + !CommandOpenIsSafe(allowed_command_set_, broker_permission_list_, + pathname, flags, nullptr, nullptr)) { + return -broker_permission_list_.denied_errno(); + } + return PathAndFlagsSyscallReturningFD(COMMAND_OPEN, pathname, flags); +} + +int BrokerClient::Readlink(const char* path, char* buf, size_t bufsize) const { + if (!path || !buf) + return -EFAULT; + + if (fast_check_in_client_ && + !CommandReadlinkIsSafe(allowed_command_set_, broker_permission_list_, + path, nullptr)) { + return -broker_permission_list_.denied_errno(); } base::Pickle write_pickle; - write_pickle.WriteInt(syscall_type); - write_pickle.WriteString(pathname); - write_pickle.WriteInt(flags); + write_pickle.WriteInt(COMMAND_READLINK); + write_pickle.WriteString(path); RAW_CHECK(write_pickle.size() <= kMaxMessageLength); int returned_fd = -1; uint8_t reply_buf[kMaxMessageLength]; + ssize_t msg_len = SendRecvRequest(write_pickle, 0, reply_buf, + sizeof(reply_buf), &returned_fd); + if (msg_len < 0) + return msg_len; - // Send a request (in write_pickle) as well that will include a new - // temporary socketpair (created internally by SendRecvMsg()). - // Then read the reply on this new socketpair in reply_buf and put an - // eventual attached file descriptor in |returned_fd|. - ssize_t msg_len = base::UnixDomainSocket::SendRecvMsgWithFlags( - ipc_channel_.get(), reply_buf, sizeof(reply_buf), recvmsg_flags, - &returned_fd, write_pickle); - if (msg_len <= 0) { - if (!quiet_failures_for_tests_) - RAW_LOG(ERROR, "Could not make request to broker process"); + base::Pickle read_pickle(reinterpret_cast<char*>(reply_buf), msg_len); + base::PickleIterator iter(read_pickle); + int return_value = -1; + int return_length = 0; + const char* return_data = nullptr; + if (!iter.ReadInt(&return_value)) return -ENOMEM; + if (return_value < 0) + return return_value; + if (!iter.ReadData(&return_data, &return_length)) + return -ENOMEM; + if (return_length < 0) + return -ENOMEM; + if (static_cast<size_t>(return_length) > bufsize) + return -ENAMETOOLONG; + memcpy(buf, return_data, return_length); + return return_value; +} + +int BrokerClient::Rename(const char* oldpath, const char* newpath) const { + if (!oldpath || !newpath) + return -EFAULT; + + if (fast_check_in_client_ && + !CommandRenameIsSafe(allowed_command_set_, broker_permission_list_, + oldpath, newpath, nullptr, nullptr)) { + return -broker_permission_list_.denied_errno(); } + base::Pickle write_pickle; + write_pickle.WriteInt(COMMAND_RENAME); + write_pickle.WriteString(oldpath); + write_pickle.WriteString(newpath); + RAW_CHECK(write_pickle.size() <= kMaxMessageLength); + + int returned_fd = -1; + uint8_t reply_buf[kMaxMessageLength]; + ssize_t msg_len = SendRecvRequest(write_pickle, 0, reply_buf, + sizeof(reply_buf), &returned_fd); + if (msg_len < 0) + return msg_len; + base::Pickle read_pickle(reinterpret_cast<char*>(reply_buf), msg_len); base::PickleIterator iter(read_pickle); int return_value = -1; - // Now deserialize the return value and eventually return the file - // descriptor. - if (iter.ReadInt(&return_value)) { - switch (syscall_type) { - case COMMAND_ACCESS: - // We should never have a fd to return. - RAW_CHECK(returned_fd == -1); - return return_value; - case COMMAND_OPEN: - if (return_value < 0) { - RAW_CHECK(returned_fd == -1); - return return_value; - } else { - // We have a real file descriptor to return. - RAW_CHECK(returned_fd >= 0); - return returned_fd; - } - default: - RAW_LOG(ERROR, "Unsupported command"); - return -ENOSYS; - } - } else { - RAW_LOG(ERROR, "Could not read pickle"); - NOTREACHED(); + if (!iter.ReadInt(&return_value)) return -ENOMEM; - } -} -BrokerClient::BrokerClient(const BrokerPolicy& broker_policy, - BrokerChannel::EndPoint ipc_channel, - bool fast_check_in_client, - bool quiet_failures_for_tests) - : broker_policy_(broker_policy), - ipc_channel_(std::move(ipc_channel)), - fast_check_in_client_(fast_check_in_client), - quiet_failures_for_tests_(quiet_failures_for_tests) {} - -BrokerClient::~BrokerClient() { + return return_value; } -int BrokerClient::Access(const char* pathname, int mode) const { - return PathAndFlagsSyscall(COMMAND_ACCESS, pathname, mode); -} +int BrokerClient::Rmdir(const char* path) const { + if (!path) + return -EFAULT; -int BrokerClient::Open(const char* pathname, int flags) const { - return PathAndFlagsSyscall(COMMAND_OPEN, pathname, flags); + if (fast_check_in_client_ && + !CommandRmdirIsSafe(allowed_command_set_, broker_permission_list_, path, + nullptr)) { + return -broker_permission_list_.denied_errno(); + } + return PathOnlySyscall(COMMAND_RMDIR, path); } -int BrokerClient::Stat(const char* pathname, struct stat* sb) { +int BrokerClient::Stat(const char* pathname, struct stat* sb) const { + if (!pathname || !sb) + return -EFAULT; + + if (fast_check_in_client_ && + !CommandStatIsSafe(allowed_command_set_, broker_permission_list_, + pathname, nullptr)) { + return -broker_permission_list_.denied_errno(); + } return StatFamilySyscall(COMMAND_STAT, pathname, sb, sizeof(*sb)); } -int BrokerClient::Stat64(const char* pathname, struct stat64* sb) { +int BrokerClient::Stat64(const char* pathname, struct stat64* sb) const { + if (!pathname || !sb) + return -EFAULT; + + if (fast_check_in_client_ && + !CommandStatIsSafe(allowed_command_set_, broker_permission_list_, + pathname, nullptr)) { + return -broker_permission_list_.denied_errno(); + } return StatFamilySyscall(COMMAND_STAT64, pathname, sb, sizeof(*sb)); } -int BrokerClient::StatFamilySyscall(IPCCommand syscall_type, - const char* pathname, - void* result_ptr, - size_t expected_result_size) const { +int BrokerClient::Unlink(const char* path) const { + if (!path) + return -EFAULT; + if (fast_check_in_client_ && - !broker_policy_.GetFileNameIfAllowedToAccess(pathname, R_OK, nullptr)) { - return -broker_policy_.denied_errno(); + !CommandUnlinkIsSafe(allowed_command_set_, broker_permission_list_, path, + nullptr)) { + return -broker_permission_list_.denied_errno(); } + return PathOnlySyscall(COMMAND_UNLINK, path); +} +int BrokerClient::PathOnlySyscall(BrokerCommand syscall_type, + const char* pathname) const { base::Pickle write_pickle; write_pickle.WriteInt(syscall_type); write_pickle.WriteString(pathname); @@ -164,70 +205,151 @@ int BrokerClient::StatFamilySyscall(IPCCommand syscall_type, int returned_fd = -1; uint8_t reply_buf[kMaxMessageLength]; - ssize_t msg_len = base::UnixDomainSocket::SendRecvMsg( - ipc_channel_.get(), reply_buf, sizeof(reply_buf), &returned_fd, - write_pickle); + ssize_t msg_len = SendRecvRequest(write_pickle, 0, reply_buf, + sizeof(reply_buf), &returned_fd); + if (msg_len < 0) + return msg_len; - if (msg_len <= 0) { - if (!quiet_failures_for_tests_) - RAW_LOG(ERROR, "Could not make request to broker process"); + base::Pickle read_pickle(reinterpret_cast<char*>(reply_buf), msg_len); + base::PickleIterator iter(read_pickle); + int return_value = -1; + if (!iter.ReadInt(&return_value)) return -ENOMEM; - } + return return_value; +} + +// Make a remote system call over IPC for syscalls that take a path and +// flags (currently access() and mkdir()) but do not return a FD. +// Will return -errno like a real system call. +// This function needs to be async signal safe. +int BrokerClient::PathAndFlagsSyscall(BrokerCommand syscall_type, + const char* pathname, + int flags) const { + base::Pickle write_pickle; + write_pickle.WriteInt(syscall_type); + write_pickle.WriteString(pathname); + write_pickle.WriteInt(flags); + RAW_CHECK(write_pickle.size() <= kMaxMessageLength); + + int returned_fd = -1; + uint8_t reply_buf[kMaxMessageLength]; + ssize_t msg_len = SendRecvRequest(write_pickle, 0, reply_buf, + sizeof(reply_buf), &returned_fd); + if (msg_len < 0) + return msg_len; base::Pickle read_pickle(reinterpret_cast<char*>(reply_buf), msg_len); base::PickleIterator iter(read_pickle); int return_value = -1; - int return_length = 0; - const char* return_data = nullptr; if (!iter.ReadInt(&return_value)) return -ENOMEM; - if (return_value < 0) - return return_value; - if (!iter.ReadData(&return_data, &return_length)) - return -ENOMEM; - if (static_cast<size_t>(return_length) != expected_result_size) - return -ENOMEM; - memcpy(result_ptr, return_data, expected_result_size); + return return_value; } -int BrokerClient::Rename(const char* oldpath, const char* newpath) { - if (fast_check_in_client_) { - bool ignore; - if (!broker_policy_.GetFileNameIfAllowedToOpen(oldpath, O_RDWR, nullptr, - &ignore) || - !broker_policy_.GetFileNameIfAllowedToOpen(newpath, O_RDWR, nullptr, - &ignore)) { - return -broker_policy_.denied_errno(); - } +// Make a remote system call over IPC for syscalls that take a path and flags +// as arguments and return FDs (currently open()). +// Will return -errno like a real system call. +// This function needs to be async signal safe. +int BrokerClient::PathAndFlagsSyscallReturningFD(BrokerCommand syscall_type, + const char* pathname, + int flags) const { + // For this "remote system call" to work, we need to handle any flag that + // cannot be sent over a Unix socket in a special way. + // See the comments around kCurrentProcessOpenFlagsMask. + int recvmsg_flags = 0; + if (syscall_type == COMMAND_OPEN && (flags & kCurrentProcessOpenFlagsMask)) { + // This implementation only knows about O_CLOEXEC, someone needs to look at + // this code if other flags are added. + static_assert(kCurrentProcessOpenFlagsMask == O_CLOEXEC, + "Must update broker client to handle other flags"); + + recvmsg_flags |= MSG_CMSG_CLOEXEC; + flags &= ~kCurrentProcessOpenFlagsMask; } base::Pickle write_pickle; - write_pickle.WriteInt(COMMAND_RENAME); - write_pickle.WriteString(oldpath); - write_pickle.WriteString(newpath); + write_pickle.WriteInt(syscall_type); + write_pickle.WriteString(pathname); + write_pickle.WriteInt(flags); RAW_CHECK(write_pickle.size() <= kMaxMessageLength); int returned_fd = -1; uint8_t reply_buf[kMaxMessageLength]; - ssize_t msg_len = base::UnixDomainSocket::SendRecvMsg( - ipc_channel_.get(), reply_buf, sizeof(reply_buf), &returned_fd, - write_pickle); + ssize_t msg_len = SendRecvRequest(write_pickle, recvmsg_flags, reply_buf, + sizeof(reply_buf), &returned_fd); + if (msg_len < 0) + return msg_len; - if (msg_len <= 0) { - if (!quiet_failures_for_tests_) - RAW_LOG(ERROR, "Could not make request to broker process"); + base::Pickle read_pickle(reinterpret_cast<char*>(reply_buf), msg_len); + base::PickleIterator iter(read_pickle); + int return_value = -1; + if (!iter.ReadInt(&return_value)) return -ENOMEM; - } + if (return_value < 0) + return return_value; + + // We have a real file descriptor to return. + RAW_CHECK(returned_fd >= 0); + return returned_fd; +} + +// Make a remote system call over IPC for syscalls that take a path +// and return stat buffers (currently stat() and stat64()). +// Will return -errno like a real system call. +// This function needs to be async signal safe. +int BrokerClient::StatFamilySyscall(BrokerCommand syscall_type, + const char* pathname, + void* result_ptr, + size_t expected_result_size) const { + base::Pickle write_pickle; + write_pickle.WriteInt(syscall_type); + write_pickle.WriteString(pathname); + RAW_CHECK(write_pickle.size() <= kMaxMessageLength); + + int returned_fd = -1; + uint8_t reply_buf[kMaxMessageLength]; + ssize_t msg_len = SendRecvRequest(write_pickle, 0, reply_buf, + sizeof(reply_buf), &returned_fd); + if (msg_len < 0) + return msg_len; base::Pickle read_pickle(reinterpret_cast<char*>(reply_buf), msg_len); base::PickleIterator iter(read_pickle); int return_value = -1; + int return_length = 0; + const char* return_data = nullptr; if (!iter.ReadInt(&return_value)) return -ENOMEM; - + if (return_value < 0) + return return_value; + if (!iter.ReadData(&return_data, &return_length)) + return -ENOMEM; + if (static_cast<size_t>(return_length) != expected_result_size) + return -ENOMEM; + memcpy(result_ptr, return_data, expected_result_size); return return_value; } +// Send a request (in request_pickle) that will include a new temporary +// socketpair as well (created internally by SendRecvMsg()). Then read the +// reply on this new socketpair in |reply_buf| and put an eventual attached +// file descriptor (if any) in |returned_fd|. +ssize_t BrokerClient::SendRecvRequest(const base::Pickle& request_pickle, + int recvmsg_flags, + uint8_t* reply_buf, + size_t reply_buf_size, + int* returned_fd) const { + ssize_t msg_len = base::UnixDomainSocket::SendRecvMsgWithFlags( + ipc_channel_.get(), reply_buf, reply_buf_size, recvmsg_flags, returned_fd, + request_pickle); + if (msg_len <= 0) { + if (!quiet_failures_for_tests_) + RAW_LOG(ERROR, "Could not make request to broker process"); + return -ENOMEM; + } + return msg_len; +} + } // namespace syscall_broker } // namespace sandbox diff --git a/chromium/sandbox/linux/syscall_broker/broker_client.h b/chromium/sandbox/linux/syscall_broker/broker_client.h index 9c818578235..fc108f1abb4 100644 --- a/chromium/sandbox/linux/syscall_broker/broker_client.h +++ b/chromium/sandbox/linux/syscall_broker/broker_client.h @@ -10,14 +10,14 @@ #include <unistd.h> #include "base/macros.h" +#include "base/pickle.h" #include "sandbox/linux/syscall_broker/broker_channel.h" -#include "sandbox/linux/syscall_broker/broker_common.h" +#include "sandbox/linux/syscall_broker/broker_command.h" namespace sandbox { - namespace syscall_broker { -class BrokerPolicy; +class BrokerPermissionList; // This class can be embedded in a sandboxed process and can be // used to perform certain system calls in another, presumably @@ -34,52 +34,74 @@ class BrokerClient { // |ipc_channel| needs to be a suitable SOCK_SEQPACKET unix socket. // |fast_check_in_client| should be set to true and // |quiet_failures_for_tests| to false unless you are writing tests. - BrokerClient(const BrokerPolicy& policy, + BrokerClient(const BrokerPermissionList& policy, BrokerChannel::EndPoint ipc_channel, + const BrokerCommandSet& allowed_command_set, bool fast_check_in_client, bool quiet_failures_for_tests); ~BrokerClient(); + // Get the file descriptor used for IPC. This is used for tests. + int GetIPCDescriptor() const { return ipc_channel_.get(); } + + // The following public methods can be used in place of the equivalently + // name system calls. They all return -errno on errors. They are all async + // signal safe so they may be called from a SIGSYS trap handler. + // Can be used in place of access(). // X_OK will always return an error in practice since the broker process // doesn't support execute permissions. - // It's similar to the access() system call and will return -errno on errors. - // This is async signal safe. int Access(const char* pathname, int mode) const; + // Can be used in place of mkdir(). + int Mkdir(const char* path, int mode) const; + // Can be used in place of open(). // The implementation only supports certain white listed flags and will // return -EPERM on other flags. - // It's similar to the open() system call and will return -errno on errors. - // This is async signal safe. int Open(const char* pathname, int flags) const; - // Can be used in place of stat()/stat64(). - // It's similar to the stat() system call and will return -errno on errors. - // This is async signal safe. - int Stat(const char* pathname, struct stat* sb); - int Stat64(const char* pathname, struct stat64* sb); + // Can be used in place of Readlink(). + int Readlink(const char* path, char* buf, size_t bufsize) const; // Can be used in place of rename(). - // It's similar to the rename() system call and will return -errno on errors. - // This is async signal safe. - int Rename(const char* oldpath, const char* newpath); + int Rename(const char* oldpath, const char* newpath) const; - // Get the file descriptor used for IPC. This is used for tests. - int GetIPCDescriptor() const { return ipc_channel_.get(); } + // Can be used in place of rmdir(). + int Rmdir(const char* path) const; + + // Can be used in place of stat()/stat64(). + int Stat(const char* pathname, struct stat* sb) const; + int Stat64(const char* pathname, struct stat64* sb) const; + + // Can be used in place of rmdir(). + int Unlink(const char* unlink) const; private: - int PathAndFlagsSyscall(IPCCommand syscall_type, + int PathOnlySyscall(BrokerCommand syscall_type, const char* pathname) const; + + int PathAndFlagsSyscall(BrokerCommand syscall_type, const char* pathname, int flags) const; - int StatFamilySyscall(IPCCommand syscall_type, + int PathAndFlagsSyscallReturningFD(BrokerCommand syscall_type, + const char* pathname, + int flags) const; + + int StatFamilySyscall(BrokerCommand syscall_type, const char* pathname, void* result_ptr, size_t expected_result_size) const; - const BrokerPolicy& broker_policy_; + ssize_t SendRecvRequest(const base::Pickle& request_pickle, + int recvmsg_flags, + uint8_t* reply_buf, + size_t reply_buf_size, + int* returned_fd) const; + + const BrokerPermissionList& broker_permission_list_; const BrokerChannel::EndPoint ipc_channel_; + const BrokerCommandSet allowed_command_set_; const bool fast_check_in_client_; // Whether to forward a request that we // know will be denied to the broker. (Used // for tests). diff --git a/chromium/sandbox/linux/syscall_broker/broker_command.cc b/chromium/sandbox/linux/syscall_broker/broker_command.cc new file mode 100644 index 00000000000..46533d381dc --- /dev/null +++ b/chromium/sandbox/linux/syscall_broker/broker_command.cc @@ -0,0 +1,99 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <unistd.h> + +#include "sandbox/linux/syscall_broker/broker_command.h" +#include "sandbox/linux/syscall_broker/broker_permission_list.h" + +namespace sandbox { +namespace syscall_broker { + +bool CommandAccessIsSafe(const BrokerCommandSet& command_set, + const BrokerPermissionList& policy, + const char* requested_filename, + int requested_mode, + const char** filename_to_use) { + return command_set.test(COMMAND_ACCESS) && + policy.GetFileNameIfAllowedToAccess(requested_filename, requested_mode, + filename_to_use); +} + +bool CommandMkdirIsSafe(const BrokerCommandSet& command_set, + const BrokerPermissionList& policy, + const char* requested_filename, + const char** filename_to_use) { + return command_set.test(COMMAND_MKDIR) && + policy.GetFileNameIfAllowedToOpen(requested_filename, + O_RDWR | O_CREAT | O_EXCL, + filename_to_use, nullptr); +} + +bool CommandOpenIsSafe(const BrokerCommandSet& command_set, + const BrokerPermissionList& policy, + const char* requested_filename, + int requested_flags, + const char** filename_to_use, + bool* unlink_after_open) { + return command_set.test(COMMAND_OPEN) && + policy.GetFileNameIfAllowedToOpen( + requested_filename, + requested_flags & ~kCurrentProcessOpenFlagsMask, filename_to_use, + unlink_after_open); +} + +bool CommandReadlinkIsSafe(const BrokerCommandSet& command_set, + const BrokerPermissionList& policy, + const char* requested_filename, + const char** filename_to_use) { + return command_set.test(COMMAND_READLINK) && + policy.GetFileNameIfAllowedToOpen(requested_filename, O_RDONLY, + filename_to_use, nullptr); +} + +bool CommandRenameIsSafe(const BrokerCommandSet& command_set, + const BrokerPermissionList& policy, + const char* old_filename, + const char* new_filename, + const char** old_filename_to_use, + const char** new_filename_to_use) { + return command_set.test(COMMAND_RENAME) && + policy.GetFileNameIfAllowedToOpen(old_filename, + O_RDWR | O_CREAT | O_EXCL, + old_filename_to_use, nullptr) && + policy.GetFileNameIfAllowedToOpen(new_filename, + O_RDWR | O_CREAT | O_EXCL, + new_filename_to_use, nullptr); +} + +bool CommandRmdirIsSafe(const BrokerCommandSet& command_set, + const BrokerPermissionList& policy, + const char* requested_filename, + const char** filename_to_use) { + return command_set.test(COMMAND_RMDIR) && + policy.GetFileNameIfAllowedToOpen(requested_filename, + O_RDWR | O_CREAT | O_EXCL, + filename_to_use, nullptr); +} + +bool CommandStatIsSafe(const BrokerCommandSet& command_set, + const BrokerPermissionList& policy, + const char* requested_filename, + const char** filename_to_use) { + return command_set.test(COMMAND_STAT) && + policy.GetFileNameIfAllowedToStat(requested_filename, filename_to_use); +} + +bool CommandUnlinkIsSafe(const BrokerCommandSet& command_set, + const BrokerPermissionList& policy, + const char* requested_filename, + const char** filename_to_use) { + return command_set.test(COMMAND_UNLINK) && + policy.GetFileNameIfAllowedToOpen(requested_filename, + O_RDWR | O_CREAT | O_EXCL, + filename_to_use, nullptr); +} + +} // namespace syscall_broker +} // namespace sandbox diff --git a/chromium/sandbox/linux/syscall_broker/broker_command.h b/chromium/sandbox/linux/syscall_broker/broker_command.h new file mode 100644 index 00000000000..aa22b94aba7 --- /dev/null +++ b/chromium/sandbox/linux/syscall_broker/broker_command.h @@ -0,0 +1,114 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SYSCALL_BROKER_BROKER_COMMAND_H_ +#define SANDBOX_LINUX_SYSCALL_BROKER_BROKER_COMMAND_H_ + +#include <fcntl.h> +#include <stddef.h> +#include <stdint.h> + +#include <bitset> +#include <initializer_list> + +namespace sandbox { +namespace syscall_broker { + +class BrokerPermissionList; + +constexpr size_t kMaxMessageLength = 4096; + +// Some flags are local to the current process and cannot be sent over a Unix +// socket. They need special treatment from the client. +// O_CLOEXEC is tricky because in theory another thread could call execve() +// before special treatment is made on the client, so a client needs to call +// recvmsg(2) with MSG_CMSG_CLOEXEC. +// To make things worse, there are two CLOEXEC related flags, FD_CLOEXEC (see +// F_GETFD in fcntl(2)) and O_CLOEXEC (see F_GETFL in fcntl(2)). O_CLOEXEC +// doesn't affect the semantics on execve(), it's merely a note that the +// descriptor was originally opened with O_CLOEXEC as a flag. And it is sent +// over unix sockets just fine, so a receiver that would (incorrectly) look at +// O_CLOEXEC instead of FD_CLOEXEC may be tricked in thinking that the file +// descriptor will or won't be closed on execve(). +constexpr int kCurrentProcessOpenFlagsMask = O_CLOEXEC; + +enum BrokerCommand { + COMMAND_INVALID = 0, + COMMAND_ACCESS, + COMMAND_MKDIR, + COMMAND_OPEN, + COMMAND_READLINK, + COMMAND_RENAME, + COMMAND_RMDIR, + COMMAND_STAT, + COMMAND_STAT64, + COMMAND_UNLINK, + + // NOTE: update when adding new commands. + COMMAND_MAX = COMMAND_UNLINK +}; + +using BrokerCommandSet = std::bitset<COMMAND_MAX + 1>; + +// Helper function since std::bitset lacks an initializer list constructor. +inline BrokerCommandSet MakeBrokerCommandSet( + const std::initializer_list<BrokerCommand>& args) { + BrokerCommandSet result; + for (const auto& arg : args) + result.set(arg); + return result; +} + +// Helper functions to perform the same permissions test on either side +// (client or broker process) of a broker IPC command. The implementations +// must be safe when called from an async signal handler. +bool CommandAccessIsSafe(const BrokerCommandSet& command_set, + const BrokerPermissionList& policy, + const char* requested_filename, + int requested_mode, // e.g. F_OK, R_OK, W_OK. + const char** filename_to_use); + +bool CommandMkdirIsSafe(const BrokerCommandSet& command_set, + const BrokerPermissionList& policy, + const char* requested_filename, + const char** filename_to_use); + +bool CommandOpenIsSafe(const BrokerCommandSet& command_set, + const BrokerPermissionList& policy, + const char* requested_filename, + int requested_flags, // e.g. O_RDONLY, O_RDWR. + const char** filename_to_use, + bool* unlink_after_open); + +bool CommandReadlinkIsSafe(const BrokerCommandSet& command_set, + const BrokerPermissionList& policy, + const char* requested_filename, + const char** filename_to_use); + +bool CommandRenameIsSafe(const BrokerCommandSet& command_set, + const BrokerPermissionList& policy, + const char* old_filename, + const char* new_filename, + const char** old_filename_to_use, + const char** new_filename_to_use); + +bool CommandRmdirIsSafe(const BrokerCommandSet& command_set, + const BrokerPermissionList& policy, + const char* requested_filename, + const char** filename_to_use); + +bool CommandStatIsSafe(const BrokerCommandSet& command_set, + const BrokerPermissionList& policy, + const char* requested_filename, + const char** filename_to_use); + +bool CommandUnlinkIsSafe(const BrokerCommandSet& command_set, + const BrokerPermissionList& policy, + const char* requested_filename, + const char** filename_to_use); + +} // namespace syscall_broker +} // namespace sandbox + +#endif // SANDBOX_LINUX_SYSCALL_BROKER_BROKER_COMMAND_H_ diff --git a/chromium/sandbox/linux/syscall_broker/broker_common.h b/chromium/sandbox/linux/syscall_broker/broker_common.h deleted file mode 100644 index 30b2ce19762..00000000000 --- a/chromium/sandbox/linux/syscall_broker/broker_common.h +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2014 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef SANDBOX_LINUX_SYSCALL_BROKER_BROKER_COMMON_H_ -#define SANDBOX_LINUX_SYSCALL_BROKER_BROKER_COMMON_H_ - -#include <fcntl.h> -#include <stddef.h> - -namespace sandbox { - -namespace syscall_broker { - -const size_t kMaxMessageLength = 4096; - -// Some flags are local to the current process and cannot be sent over a Unix -// socket. They need special treatment from the client. -// O_CLOEXEC is tricky because in theory another thread could call execve() -// before special treatment is made on the client, so a client needs to call -// recvmsg(2) with MSG_CMSG_CLOEXEC. -// To make things worse, there are two CLOEXEC related flags, FD_CLOEXEC (see -// F_GETFD in fcntl(2)) and O_CLOEXEC (see F_GETFL in fcntl(2)). O_CLOEXEC -// doesn't affect the semantics on execve(), it's merely a note that the -// descriptor was originally opened with O_CLOEXEC as a flag. And it is sent -// over unix sockets just fine, so a receiver that would (incorrectly) look at -// O_CLOEXEC instead of FD_CLOEXEC may be tricked in thinking that the file -// descriptor will or won't be closed on execve(). -const int kCurrentProcessOpenFlagsMask = O_CLOEXEC; - -enum IPCCommand { - COMMAND_INVALID = 0, - COMMAND_OPEN, - COMMAND_ACCESS, - COMMAND_STAT, - COMMAND_STAT64, - COMMAND_RENAME, -}; - -} // namespace syscall_broker - -} // namespace sandbox - -#endif // SANDBOX_LINUX_SYSCALL_BROKER_BROKER_COMMON_H_ diff --git a/chromium/sandbox/linux/syscall_broker/broker_file_permission.cc b/chromium/sandbox/linux/syscall_broker/broker_file_permission.cc index 3947145eaf6..05c783068ae 100644 --- a/chromium/sandbox/linux/syscall_broker/broker_file_permission.cc +++ b/chromium/sandbox/linux/syscall_broker/broker_file_permission.cc @@ -7,11 +7,12 @@ #include <fcntl.h> #include <stddef.h> #include <string.h> +#include <unistd.h> #include <string> #include "base/logging.h" -#include "sandbox/linux/syscall_broker/broker_common.h" +#include "sandbox/linux/syscall_broker/broker_command.h" namespace sandbox { namespace syscall_broker { @@ -83,6 +84,13 @@ bool BrokerFilePermission::CheckAccess(const char* requested_filename, if (!ValidatePath(requested_filename)) return false; + return CheckAccessInternal(requested_filename, mode, file_to_access); +} + +bool BrokerFilePermission::CheckAccessInternal( + const char* requested_filename, + int mode, + const char** file_to_access) const { if (!MatchPath(requested_filename)) return false; @@ -148,13 +156,9 @@ bool BrokerFilePermission::CheckOpen(const char* requested_filename, return false; } - // If O_CREAT is present, ensure O_EXCL - if ((flags & O_CREAT) && !(flags & O_EXCL)) { - return false; - } - - // If this file is to be temporary, ensure it's created. - if (temporary_only_ && !(flags & O_CREAT)) { + // If this file is to be temporary, ensure it is created, not pre-existing. + // See https://crbug.com/415681#c17 + if (temporary_only_ && (!(flags & O_CREAT) || !(flags & O_EXCL))) { return false; } @@ -173,7 +177,6 @@ bool BrokerFilePermission::CheckOpen(const char* requested_filename, const int unknown_flags = ~known_flags; const bool has_unknown_flags = creation_and_status_flags & unknown_flags; - if (has_unknown_flags) return false; @@ -186,6 +189,38 @@ bool BrokerFilePermission::CheckOpen(const char* requested_filename, return true; } +bool BrokerFilePermission::CheckStat(const char* requested_filename, + const char** file_to_access) const { + if (!ValidatePath(requested_filename)) + return false; + + // Ability to access implies ability to stat(). + if (CheckAccessInternal(requested_filename, F_OK, file_to_access)) + return true; + + // Allow stat() on leading directories if have create permission. + if (!allow_create_) + return false; + + // NOTE: ValidatePath proved requested_length != 0; + size_t requested_length = strlen(requested_filename); + CHECK(requested_length); + + // Special case for root: only one slash, otherwise must have a second + // slash in the right spot to avoid substring matches. + if ((requested_length == 1 && requested_filename[0] == '/') || + (requested_length < path_.length() && + memcmp(path_.c_str(), requested_filename, requested_length) == 0 && + path_.c_str()[requested_length] == '/')) { + if (file_to_access) + *file_to_access = requested_filename; + + return true; + } + + return false; +} + const char* BrokerFilePermission::GetErrorMessageForTests() { return "Invalid BrokerFilePermission"; } @@ -221,5 +256,4 @@ BrokerFilePermission::BrokerFilePermission(const std::string& path, } } // namespace syscall_broker - } // namespace sandbox diff --git a/chromium/sandbox/linux/syscall_broker/broker_file_permission.h b/chromium/sandbox/linux/syscall_broker/broker_file_permission.h index b39ac6ca1b9..4261eb357ac 100644 --- a/chromium/sandbox/linux/syscall_broker/broker_file_permission.h +++ b/chromium/sandbox/linux/syscall_broker/broker_file_permission.h @@ -49,11 +49,30 @@ class SANDBOX_EXPORT BrokerFilePermission { return BrokerFilePermission(path, true, false, true, true, true); } + // Temporary files must always be newly created and do not confer rights to + // use pre-existing files of the same name. + static BrokerFilePermission ReadWriteCreateTemporary( + const std::string& path) { + return BrokerFilePermission(path, false, true, true, true, true); + } + static BrokerFilePermission ReadWriteCreateTemporaryRecursive( const std::string& path) { return BrokerFilePermission(path, true, true, true, true, true); } + // Returns true if |requested_filename| is allowed to be accessed + // by this permission as per access(2). + // If |file_to_access| is not NULL, it is set to point to either + // the |requested_filename| in the case of a recursive match, + // or a pointer to the matched path in the whitelist if an absolute + // match. + // |mode| is per mode argument of access(2). + // Async signal safe if |file_to_access| is NULL + bool CheckAccess(const char* requested_filename, + int mode, + const char** file_to_access) const; + // Returns true if |requested_filename| is allowed to be opened // by this permission. // If |file_to_open| is not NULL it is set to point to either @@ -68,17 +87,17 @@ class SANDBOX_EXPORT BrokerFilePermission { const char** file_to_open, bool* unlink_after_open) const; - // Returns true if |requested_filename| is allowed to be accessed - // by this permission as per access(2). + // Returns true if |requested_filename| is allowed to be stat'd + // by this permission as per stat(2). Differs from CheckAccess() + // in that if create permission is granted to a file, we permit + // stat() on all of its leading components. // If |file_to_open| is not NULL, it is set to point to either // the |requested_filename| in the case of a recursive match, // or a pointer to the matched path in the whitelist if an absolute // match. - // |mode| is per mode argument of access(2). // Async signal safe if |file_to_access| is NULL - bool CheckAccess(const char* requested_filename, - int mode, - const char** file_to_access) const; + bool CheckStat(const char* requested_filename, + const char** file_to_access) const; private: friend class BrokerFilePermissionTester; @@ -101,6 +120,12 @@ class SANDBOX_EXPORT BrokerFilePermission { // MatchPath returns true if |requested_filename| is covered by this instance bool MatchPath(const char* requested_filename) const; + // Helper routine for CheckAccess() and CheckStat(). Must be safe to call + // from an async signal context. + bool CheckAccessInternal(const char* requested_filename, + int mode, + const char** file_to_access) const; + // Used in by BrokerFilePermissionTester for tests. static const char* GetErrorMessageForTests(); diff --git a/chromium/sandbox/linux/syscall_broker/broker_file_permission_unittest.cc b/chromium/sandbox/linux/syscall_broker/broker_file_permission_unittest.cc index dc56b4cd417..0071016758d 100644 --- a/chromium/sandbox/linux/syscall_broker/broker_file_permission_unittest.cc +++ b/chromium/sandbox/linux/syscall_broker/broker_file_permission_unittest.cc @@ -161,8 +161,9 @@ void CheckPerm(const BrokerFilePermission& perm, ASSERT_TRUE( perm.CheckOpen(path, access_flags | flag, &file_to_open, NULL)); break; - case O_CLOEXEC: case O_CREAT: + continue; // Handled below. + case O_CLOEXEC: default: ASSERT_FALSE( perm.CheckOpen(path, access_flags | flag, &file_to_open, NULL)); @@ -225,7 +226,6 @@ void CheckUnlink(BrokerFilePermission& perm, int access_flags) { bool unlink; ASSERT_FALSE(perm.CheckOpen(path, access_flags, NULL, &unlink)); - ASSERT_FALSE(perm.CheckOpen(path, access_flags | O_CREAT, NULL, &unlink)); ASSERT_TRUE( perm.CheckOpen(path, access_flags | O_CREAT | O_EXCL, NULL, &unlink)); ASSERT_TRUE(unlink); diff --git a/chromium/sandbox/linux/syscall_broker/broker_host.cc b/chromium/sandbox/linux/syscall_broker/broker_host.cc index 375747e19f2..bdfa8753a6d 100644 --- a/chromium/sandbox/linux/syscall_broker/broker_host.cc +++ b/chromium/sandbox/linux/syscall_broker/broker_host.cc @@ -6,6 +6,7 @@ #include <errno.h> #include <fcntl.h> +#include <limits.h> #include <stddef.h> #include <sys/socket.h> #include <sys/stat.h> @@ -22,14 +23,11 @@ #include "base/pickle.h" #include "base/posix/eintr_wrapper.h" #include "base/posix/unix_domain_socket.h" -#include "base/third_party/dynamic_annotations/dynamic_annotations.h" -#include "base/third_party/valgrind/valgrind.h" -#include "sandbox/linux/syscall_broker/broker_common.h" -#include "sandbox/linux/syscall_broker/broker_policy.h" +#include "sandbox/linux/syscall_broker/broker_command.h" +#include "sandbox/linux/syscall_broker/broker_permission_list.h" #include "sandbox/linux/system_headers/linux_syscalls.h" namespace sandbox { - namespace syscall_broker { namespace { @@ -40,82 +38,153 @@ namespace { int sys_open(const char* pathname, int flags) { // Hardcode mode to rw------- when creating files. int mode = (flags & O_CREAT) ? 0600 : 0; - if (RunningOnValgrind()) { - // Valgrind does not support AT_FDCWD, just use libc's open() in this case. - return open(pathname, flags, mode); - } return syscall(__NR_openat, AT_FDCWD, pathname, flags, mode); } -// Open |requested_filename| with |flags| if allowed by our policy. +// Perform access(2) on |requested_filename| with mode |mode| if allowed by our +// permission_list. Write the syscall return value (-errno) to |write_pickle|. +void AccessFileForIPC(const BrokerCommandSet& allowed_command_set, + const BrokerPermissionList& permission_list, + const std::string& requested_filename, + int mode, + base::Pickle* write_pickle) { + const char* file_to_access = NULL; + if (!CommandAccessIsSafe(allowed_command_set, permission_list, + requested_filename.c_str(), mode, &file_to_access)) { + write_pickle->WriteInt(-permission_list.denied_errno()); + return; + } + + CHECK(file_to_access); + if (access(file_to_access, mode) < 0) { + write_pickle->WriteInt(-errno); + return; + } + + write_pickle->WriteInt(0); +} + +void MkdirFileForIPC(const BrokerCommandSet& allowed_command_set, + const BrokerPermissionList& permission_list, + const std::string& filename, + int mode, + base::Pickle* write_pickle) { + const char* file_to_access = nullptr; + if (!CommandMkdirIsSafe(allowed_command_set, permission_list, + filename.c_str(), &file_to_access)) { + write_pickle->WriteInt(-permission_list.denied_errno()); + return; + } + if (mkdir(file_to_access, mode) < 0) { + write_pickle->WriteInt(-errno); + return; + } + write_pickle->WriteInt(0); +} + +// Open |requested_filename| with |flags| if allowed by our permission list. // Write the syscall return value (-errno) to |write_pickle| and append // a file descriptor to |opened_files| if relevant. -void OpenFileForIPC(const BrokerPolicy& policy, +void OpenFileForIPC(const BrokerCommandSet& allowed_command_set, + const BrokerPermissionList& permission_list, const std::string& requested_filename, int flags, base::Pickle* write_pickle, std::vector<int>* opened_files) { - DCHECK(write_pickle); - DCHECK(opened_files); const char* file_to_open = NULL; bool unlink_after_open = false; - const bool safe_to_open_file = policy.GetFileNameIfAllowedToOpen( - requested_filename.c_str(), flags, &file_to_open, &unlink_after_open); + if (!CommandOpenIsSafe(allowed_command_set, permission_list, + requested_filename.c_str(), flags, &file_to_open, + &unlink_after_open)) { + write_pickle->WriteInt(-permission_list.denied_errno()); + return; + } - if (safe_to_open_file) { - CHECK(file_to_open); - int opened_fd = sys_open(file_to_open, flags); - if (opened_fd < 0) { - write_pickle->WriteInt(-errno); - } else { - // Success. - if (unlink_after_open) { - unlink(file_to_open); - } - opened_files->push_back(opened_fd); - write_pickle->WriteInt(0); - } - } else { - write_pickle->WriteInt(-policy.denied_errno()); + CHECK(file_to_open); + int opened_fd = sys_open(file_to_open, flags); + if (opened_fd < 0) { + write_pickle->WriteInt(-errno); + return; } + + if (unlink_after_open) + unlink(file_to_open); + + opened_files->push_back(opened_fd); + write_pickle->WriteInt(0); } -// Perform access(2) on |requested_filename| with mode |mode| if allowed by our -// policy. Write the syscall return value (-errno) to |write_pickle|. -void AccessFileForIPC(const BrokerPolicy& policy, - const std::string& requested_filename, - int mode, +// Perform rename(2) on |old_filename| to |new_filename| and marshal the +// result to |write_pickle|. +void RenameFileForIPC(const BrokerCommandSet& allowed_command_set, + const BrokerPermissionList& permission_list, + const std::string& old_filename, + const std::string& new_filename, base::Pickle* write_pickle) { - DCHECK(write_pickle); - const char* file_to_access = NULL; - const bool safe_to_access_file = policy.GetFileNameIfAllowedToAccess( - requested_filename.c_str(), mode, &file_to_access); - - if (safe_to_access_file) { - CHECK(file_to_access); - int access_ret = access(file_to_access, mode); - int access_errno = errno; - if (!access_ret) - write_pickle->WriteInt(0); - else - write_pickle->WriteInt(-access_errno); - } else { - write_pickle->WriteInt(-policy.denied_errno()); + const char* old_file_to_access = nullptr; + const char* new_file_to_access = nullptr; + if (!CommandRenameIsSafe(allowed_command_set, permission_list, + old_filename.c_str(), new_filename.c_str(), + &old_file_to_access, &new_file_to_access)) { + write_pickle->WriteInt(-permission_list.denied_errno()); + return; + } + if (rename(old_file_to_access, new_file_to_access) < 0) { + write_pickle->WriteInt(-errno); + return; + } + write_pickle->WriteInt(0); +} + +// Perform readlink(2) on |filename| using a buffer of MAX_PATH bytes. +void ReadlinkFileForIPC(const BrokerCommandSet& allowed_command_set, + const BrokerPermissionList& permission_list, + const std::string& filename, + base::Pickle* write_pickle) { + const char* file_to_access = nullptr; + if (!CommandReadlinkIsSafe(allowed_command_set, permission_list, + filename.c_str(), &file_to_access)) { + write_pickle->WriteInt(-permission_list.denied_errno()); + return; + } + char buf[PATH_MAX]; + ssize_t result = readlink(file_to_access, buf, sizeof(buf)); + if (result < 0) { + write_pickle->WriteInt(-errno); + return; } + write_pickle->WriteInt(result); + write_pickle->WriteData(buf, result); +} + +void RmdirFileForIPC(const BrokerCommandSet& allowed_command_set, + const BrokerPermissionList& permission_list, + const std::string& requested_filename, + base::Pickle* write_pickle) { + const char* file_to_access = nullptr; + if (!CommandRmdirIsSafe(allowed_command_set, permission_list, + requested_filename.c_str(), &file_to_access)) { + write_pickle->WriteInt(-permission_list.denied_errno()); + return; + } + if (rmdir(file_to_access) < 0) { + write_pickle->WriteInt(-errno); + return; + } + write_pickle->WriteInt(0); } // Perform stat(2) on |requested_filename| and marshal the result to // |write_pickle|. -void StatFileForIPC(const BrokerPolicy& policy, - IPCCommand command_type, +void StatFileForIPC(const BrokerCommandSet& allowed_command_set, + const BrokerPermissionList& permission_list, + BrokerCommand command_type, const std::string& requested_filename, base::Pickle* write_pickle) { - DCHECK(write_pickle); - DCHECK(command_type == COMMAND_STAT || command_type == COMMAND_STAT64); const char* file_to_access = nullptr; - if (!policy.GetFileNameIfAllowedToAccess(requested_filename.c_str(), F_OK, - &file_to_access)) { - write_pickle->WriteInt(-policy.denied_errno()); + if (!CommandStatIsSafe(allowed_command_set, permission_list, + requested_filename.c_str(), &file_to_access)) { + write_pickle->WriteInt(-permission_list.denied_errno()); return; } if (command_type == COMMAND_STAT) { @@ -127,6 +196,13 @@ void StatFileForIPC(const BrokerPolicy& policy, write_pickle->WriteInt(0); write_pickle->WriteData(reinterpret_cast<char*>(&sb), sizeof(sb)); } else { + DCHECK(command_type == COMMAND_STAT64); +#if defined(__ANDROID_API__) && __ANDROID_API__ < 21 + // stat64 is not defined for older Android API versions in newer NDK + // versions. + write_pickle->WriteInt(-ENOSYS); + return; +#else struct stat64 sb; if (stat64(file_to_access, &sb) < 0) { write_pickle->WriteInt(-errno); @@ -134,27 +210,21 @@ void StatFileForIPC(const BrokerPolicy& policy, } write_pickle->WriteInt(0); write_pickle->WriteData(reinterpret_cast<char*>(&sb), sizeof(sb)); +#endif // defined(__ANDROID_API__) && __ANDROID_API__ < 21 } } -// Perform rename(2) on |old_filename| to |new_filename| and marshal the -// result to |write_pickle|. -void RenameFileForIPC(const BrokerPolicy& policy, - const std::string& old_filename, - const std::string& new_filename, +void UnlinkFileForIPC(const BrokerCommandSet& allowed_command_set, + const BrokerPermissionList& permission_list, + const std::string& requested_filename, base::Pickle* write_pickle) { - DCHECK(write_pickle); - bool ignore; - const char* old_file_to_access = nullptr; - const char* new_file_to_access = nullptr; - if (!policy.GetFileNameIfAllowedToOpen(old_filename.c_str(), O_RDWR, - &old_file_to_access, &ignore) || - !policy.GetFileNameIfAllowedToOpen(new_filename.c_str(), O_RDWR, - &new_file_to_access, &ignore)) { - write_pickle->WriteInt(-policy.denied_errno()); + const char* file_to_access = nullptr; + if (!CommandUnlinkIsSafe(allowed_command_set, permission_list, + requested_filename.c_str(), &file_to_access)) { + write_pickle->WriteInt(-permission_list.denied_errno()); return; } - if (rename(old_file_to_access, new_file_to_access) < 0) { + if (unlink(file_to_access) < 0) { write_pickle->WriteInt(-errno); return; } @@ -163,7 +233,8 @@ void RenameFileForIPC(const BrokerPolicy& policy, // Handle a |command_type| request contained in |iter| and write the reply // to |write_pickle|, adding any files opened to |opened_files|. -bool HandleRemoteCommand(const BrokerPolicy& policy, +bool HandleRemoteCommand(const BrokerCommandSet& allowed_command_set, + const BrokerPermissionList& permission_list, base::PickleIterator iter, base::Pickle* write_pickle, std::vector<int>* opened_files) { @@ -177,7 +248,17 @@ bool HandleRemoteCommand(const BrokerPolicy& policy, int flags = 0; if (!iter.ReadString(&requested_filename) || !iter.ReadInt(&flags)) return false; - AccessFileForIPC(policy, requested_filename, flags, write_pickle); + AccessFileForIPC(allowed_command_set, permission_list, requested_filename, + flags, write_pickle); + break; + } + case COMMAND_MKDIR: { + std::string requested_filename; + int mode = 0; + if (!iter.ReadString(&requested_filename) || !iter.ReadInt(&mode)) + return false; + MkdirFileForIPC(allowed_command_set, permission_list, requested_filename, + mode, write_pickle); break; } case COMMAND_OPEN: { @@ -185,8 +266,33 @@ bool HandleRemoteCommand(const BrokerPolicy& policy, int flags = 0; if (!iter.ReadString(&requested_filename) || !iter.ReadInt(&flags)) return false; - OpenFileForIPC(policy, requested_filename, flags, write_pickle, - opened_files); + OpenFileForIPC(allowed_command_set, permission_list, requested_filename, + flags, write_pickle, opened_files); + break; + } + case COMMAND_READLINK: { + std::string filename; + if (!iter.ReadString(&filename)) + return false; + ReadlinkFileForIPC(allowed_command_set, permission_list, filename, + write_pickle); + break; + } + case COMMAND_RENAME: { + std::string old_filename; + std::string new_filename; + if (!iter.ReadString(&old_filename) || !iter.ReadString(&new_filename)) + return false; + RenameFileForIPC(allowed_command_set, permission_list, old_filename, + new_filename, write_pickle); + break; + } + case COMMAND_RMDIR: { + std::string requested_filename; + if (!iter.ReadString(&requested_filename)) + return false; + RmdirFileForIPC(allowed_command_set, permission_list, requested_filename, + write_pickle); break; } case COMMAND_STAT: @@ -194,16 +300,17 @@ bool HandleRemoteCommand(const BrokerPolicy& policy, std::string requested_filename; if (!iter.ReadString(&requested_filename)) return false; - StatFileForIPC(policy, static_cast<IPCCommand>(command_type), + StatFileForIPC(allowed_command_set, permission_list, + static_cast<BrokerCommand>(command_type), requested_filename, write_pickle); break; } - case COMMAND_RENAME: { - std::string old_filename; - std::string new_filename; - if (!iter.ReadString(&old_filename) || !iter.ReadString(&new_filename)) + case COMMAND_UNLINK: { + std::string requested_filename; + if (!iter.ReadString(&requested_filename)) return false; - RenameFileForIPC(policy, old_filename, new_filename, write_pickle); + UnlinkFileForIPC(allowed_command_set, permission_list, requested_filename, + write_pickle); break; } default: @@ -215,12 +322,14 @@ bool HandleRemoteCommand(const BrokerPolicy& policy, } // namespace -BrokerHost::BrokerHost(const BrokerPolicy& broker_policy, +BrokerHost::BrokerHost(const BrokerPermissionList& broker_permission_list, + const BrokerCommandSet& allowed_command_set, BrokerChannel::EndPoint ipc_channel) - : broker_policy_(broker_policy), ipc_channel_(std::move(ipc_channel)) {} + : broker_permission_list_(broker_permission_list), + allowed_command_set_(allowed_command_set), + ipc_channel_(std::move(ipc_channel)) {} -BrokerHost::~BrokerHost() { -} +BrokerHost::~BrokerHost() {} // Handle a request on the IPC channel ipc_channel_. // A request should have a file descriptor attached on which we will reply and @@ -252,7 +361,8 @@ BrokerHost::RequestStatus BrokerHost::HandleRequest() const { base::Pickle write_pickle; std::vector<int> opened_files; bool result = - HandleRemoteCommand(broker_policy_, iter, &write_pickle, &opened_files); + HandleRemoteCommand(allowed_command_set_, broker_permission_list_, iter, + &write_pickle, &opened_files); if (result) { CHECK_LE(write_pickle.size(), kMaxMessageLength); diff --git a/chromium/sandbox/linux/syscall_broker/broker_host.h b/chromium/sandbox/linux/syscall_broker/broker_host.h index 9866507d1c6..b54e32d3cf7 100644 --- a/chromium/sandbox/linux/syscall_broker/broker_host.h +++ b/chromium/sandbox/linux/syscall_broker/broker_host.h @@ -7,28 +7,31 @@ #include "base/macros.h" #include "sandbox/linux/syscall_broker/broker_channel.h" +#include "sandbox/linux/syscall_broker/broker_command.h" namespace sandbox { namespace syscall_broker { -class BrokerPolicy; +class BrokerPermissionList; // The BrokerHost class should be embedded in a (presumably not sandboxed) // process. It will honor IPC requests from a BrokerClient sent over -// |ipc_channel| according to |broker_policy|. +// |ipc_channel| according to |broker_permission_list|. class BrokerHost { public: enum class RequestStatus { LOST_CLIENT = 0, SUCCESS, FAILURE }; - BrokerHost(const BrokerPolicy& broker_policy, + BrokerHost(const BrokerPermissionList& broker_permission_list, + const BrokerCommandSet& allowed_command_set, BrokerChannel::EndPoint ipc_channel); ~BrokerHost(); RequestStatus HandleRequest() const; private: - const BrokerPolicy& broker_policy_; + const BrokerPermissionList& broker_permission_list_; + const BrokerCommandSet allowed_command_set_; const BrokerChannel::EndPoint ipc_channel_; DISALLOW_COPY_AND_ASSIGN(BrokerHost); diff --git a/chromium/sandbox/linux/syscall_broker/broker_policy.cc b/chromium/sandbox/linux/syscall_broker/broker_permission_list.cc index cd09245d374..8032b3a9e56 100644 --- a/chromium/sandbox/linux/syscall_broker/broker_policy.cc +++ b/chromium/sandbox/linux/syscall_broker/broker_permission_list.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "sandbox/linux/syscall_broker/broker_policy.h" +#include "sandbox/linux/syscall_broker/broker_permission_list.h" #include <fcntl.h> #include <stddef.h> @@ -13,13 +13,29 @@ #include <vector> #include "base/logging.h" -#include "sandbox/linux/syscall_broker/broker_common.h" +#include "sandbox/linux/syscall_broker/broker_command.h" namespace sandbox { namespace syscall_broker { -BrokerPolicy::BrokerPolicy(int denied_errno, - const std::vector<BrokerFilePermission>& permissions) +namespace { + +bool CheckCallerArgs(const char** file_to_access) { + if (file_to_access && *file_to_access) { + // Make sure that callers never pass a non-empty string. In case callers + // wrongly forget to check the return value and look at the string + // instead, this could catch bugs. + RAW_LOG(FATAL, "*file_to_access should be NULL"); + return false; + } + return true; +} + +} // namespace + +BrokerPermissionList::BrokerPermissionList( + int denied_errno, + const std::vector<BrokerFilePermission>& permissions) : denied_errno_(denied_errno), permissions_(permissions), num_of_permissions_(permissions.size()) { @@ -33,8 +49,7 @@ BrokerPolicy::BrokerPolicy(int denied_errno, } } -BrokerPolicy::~BrokerPolicy() { -} +BrokerPermissionList::~BrokerPermissionList() {} // Check if calling access() should be allowed on |requested_filename| with // mode |requested_mode|. @@ -47,17 +62,13 @@ BrokerPolicy::~BrokerPolicy() { // return true if calling access() on this file should be allowed, false // otherwise. // Async signal safe if and only if |file_to_access| is NULL. -bool BrokerPolicy::GetFileNameIfAllowedToAccess( +bool BrokerPermissionList::GetFileNameIfAllowedToAccess( const char* requested_filename, int requested_mode, const char** file_to_access) const { - if (file_to_access && *file_to_access) { - // Make sure that callers never pass a non-empty string. In case callers - // wrongly forget to check the return value and look at the string - // instead, this could catch bugs. - RAW_LOG(FATAL, "*file_to_access should be NULL"); + if (!CheckCallerArgs(file_to_access)) return false; - } + for (size_t i = 0; i < num_of_permissions_; i++) { if (permissions_array_[i].CheckAccess(requested_filename, requested_mode, file_to_access)) { @@ -75,17 +86,14 @@ bool BrokerPolicy::GetFileNameIfAllowedToAccess( // string comparison mechanism. // Return true if opening should be allowed, false otherwise. // Async signal safe if and only if |file_to_open| is NULL. -bool BrokerPolicy::GetFileNameIfAllowedToOpen(const char* requested_filename, - int requested_flags, - const char** file_to_open, - bool* unlink_after_open) const { - if (file_to_open && *file_to_open) { - // Make sure that callers never pass a non-empty string. In case callers - // wrongly forget to check the return value and look at the string - // instead, this could catch bugs. - RAW_LOG(FATAL, "*file_to_open should be NULL"); +bool BrokerPermissionList::GetFileNameIfAllowedToOpen( + const char* requested_filename, + int requested_flags, + const char** file_to_open, + bool* unlink_after_open) const { + if (!CheckCallerArgs(file_to_open)) return false; - } + for (size_t i = 0; i < num_of_permissions_; i++) { if (permissions_array_[i].CheckOpen(requested_filename, requested_flags, file_to_open, unlink_after_open)) { @@ -95,6 +103,18 @@ bool BrokerPolicy::GetFileNameIfAllowedToOpen(const char* requested_filename, return false; } -} // namespace syscall_broker +bool BrokerPermissionList::GetFileNameIfAllowedToStat( + const char* requested_filename, + const char** file_to_stat) const { + if (!CheckCallerArgs(file_to_stat)) + return false; + + for (size_t i = 0; i < num_of_permissions_; i++) { + if (permissions_array_[i].CheckStat(requested_filename, file_to_stat)) + return true; + } + return false; +} +} // namespace syscall_broker } // namespace sandbox diff --git a/chromium/sandbox/linux/syscall_broker/broker_policy.h b/chromium/sandbox/linux/syscall_broker/broker_permission_list.h index 58bc29a76e8..2cb30f16141 100644 --- a/chromium/sandbox/linux/syscall_broker/broker_policy.h +++ b/chromium/sandbox/linux/syscall_broker/broker_permission_list.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef SANDBOX_LINUX_SYSCALL_BROKER_BROKER_POLICY_H_ -#define SANDBOX_LINUX_SYSCALL_BROKER_BROKER_POLICY_H_ +#ifndef SANDBOX_LINUX_SYSCALL_BROKER_BROKER_PERMISSION_LIST_H_ +#define SANDBOX_LINUX_SYSCALL_BROKER_BROKER_PERMISSION_LIST_H_ #include <stddef.h> @@ -17,22 +17,21 @@ namespace sandbox { namespace syscall_broker { -// BrokerPolicy allows to define the security policy enforced by a -// BrokerHost. The BrokerHost will evaluate requests sent over its -// IPC channel according to the BrokerPolicy. -// Some of the methods of this class can be used in an async-signal safe -// way. -class BrokerPolicy { +// BrokerPermissionList defines the file access control list enforced by +// a BrokerHost. The BrokerHost will evaluate requests to manipulate files +// sent over its IPC channel according to the BrokerPermissionList. +// The methods of this class must be usable in an async-signal safe manner. +class BrokerPermissionList { public: // |denied_errno| is the error code returned when IPC requests for system // calls such as open() or access() are denied because a file is not in the // whitelist. EACCESS would be a typical value. // |permissions| is a list of BrokerPermission objects that define // what the broker will allow. - BrokerPolicy(int denied_errno, - const std::vector<BrokerFilePermission>& permissions); + BrokerPermissionList(int denied_errno, + const std::vector<BrokerFilePermission>& permissions); - ~BrokerPolicy(); + ~BrokerPermissionList(); // Check if calling access() should be allowed on |requested_filename| with // mode |requested_mode|. @@ -66,6 +65,17 @@ class BrokerPolicy { int requested_flags, const char** file_to_open, bool* unlink_after_open) const; + + // Check if calling stat() should be allowed on |requested_filename|. + // Async signal safe if and only if |file_to_open| is NULL. This is + // similar to GetFileNameIfAllowedToAccess(), except that if we have + // create permission on file, we permit stat() on all its leading + // components, otherwise checking for missing intermediate directories + // can't happen proplery during a base::CreateDirectory() call. + // Async signal safe if and only if |file_to_open| is NULL. + bool GetFileNameIfAllowedToStat(const char* requested_filename, + const char** file_to_access) const; + int denied_errno() const { return denied_errno_; } private: @@ -79,11 +89,10 @@ class BrokerPolicy { const BrokerFilePermission* permissions_array_; const size_t num_of_permissions_; - DISALLOW_COPY_AND_ASSIGN(BrokerPolicy); + DISALLOW_COPY_AND_ASSIGN(BrokerPermissionList); }; } // namespace syscall_broker - } // namespace sandbox -#endif // SANDBOX_LINUX_SYSCALL_BROKER_BROKER_POLICY_H_ +#endif // SANDBOX_LINUX_SYSCALL_BROKER_BROKER_PERMISSION_LIST_H_ diff --git a/chromium/sandbox/linux/syscall_broker/broker_process.cc b/chromium/sandbox/linux/syscall_broker/broker_process.cc index 46e207ede34..8ebce31669e 100644 --- a/chromium/sandbox/linux/syscall_broker/broker_process.cc +++ b/chromium/sandbox/linux/syscall_broker/broker_process.cc @@ -32,14 +32,16 @@ namespace syscall_broker { BrokerProcess::BrokerProcess( int denied_errno, + const syscall_broker::BrokerCommandSet& allowed_command_set, const std::vector<syscall_broker::BrokerFilePermission>& permissions, bool fast_check_in_client, bool quiet_failures_for_tests) : initialized_(false), + broker_pid_(-1), fast_check_in_client_(fast_check_in_client), quiet_failures_for_tests_(quiet_failures_for_tests), - broker_pid_(-1), - broker_policy_(denied_errno, permissions) {} + allowed_command_set_(allowed_command_set), + broker_permission_list_(denied_errno, permissions) {} BrokerProcess::~BrokerProcess() { if (initialized_) { @@ -74,9 +76,9 @@ bool BrokerProcess::Init( // We are the parent and we have just forked our broker process. ipc_reader.reset(); broker_pid_ = child_pid; - broker_client_.reset(new BrokerClient(broker_policy_, std::move(ipc_writer), - fast_check_in_client_, - quiet_failures_for_tests_)); + broker_client_ = std::make_unique<BrokerClient>( + broker_permission_list_, std::move(ipc_writer), allowed_command_set_, + fast_check_in_client_, quiet_failures_for_tests_); initialized_ = true; return true; } @@ -85,7 +87,8 @@ bool BrokerProcess::Init( // we get notified if the client disappears. ipc_writer.reset(); CHECK(broker_process_init_callback.Run()); - BrokerHost broker_host(broker_policy_, std::move(ipc_reader)); + BrokerHost broker_host(broker_permission_list_, allowed_command_set_, + std::move(ipc_reader)); for (;;) { switch (broker_host.HandleRequest()) { case BrokerHost::RequestStatus::LOST_CLIENT: @@ -109,11 +112,31 @@ int BrokerProcess::Access(const char* pathname, int mode) const { return broker_client_->Access(pathname, mode); } +int BrokerProcess::Mkdir(const char* path, int mode) const { + RAW_CHECK(initialized_); + return broker_client_->Mkdir(path, mode); +} + int BrokerProcess::Open(const char* pathname, int flags) const { RAW_CHECK(initialized_); return broker_client_->Open(pathname, flags); } +int BrokerProcess::Readlink(const char* path, char* buf, size_t bufsize) const { + RAW_CHECK(initialized_); + return broker_client_->Readlink(path, buf, bufsize); +} + +int BrokerProcess::Rename(const char* oldpath, const char* newpath) const { + RAW_CHECK(initialized_); + return broker_client_->Rename(oldpath, newpath); +} + +int BrokerProcess::Rmdir(const char* pathname) const { + RAW_CHECK(initialized_); + return broker_client_->Rmdir(pathname); +} + int BrokerProcess::Stat(const char* pathname, struct stat* sb) const { RAW_CHECK(initialized_); return broker_client_->Stat(pathname, sb); @@ -124,9 +147,9 @@ int BrokerProcess::Stat64(const char* pathname, struct stat64* sb) const { return broker_client_->Stat64(pathname, sb); } -int BrokerProcess::Rename(const char* oldpath, const char* newpath) const { +int BrokerProcess::Unlink(const char* pathname) const { RAW_CHECK(initialized_); - return broker_client_->Rename(oldpath, newpath); + return broker_client_->Unlink(pathname); } #if defined(MEMORY_SANITIZER) @@ -146,24 +169,6 @@ intptr_t BrokerProcess::SIGSYS_Handler(const sandbox::arch_seccomp_data& args, return broker_process->Access(reinterpret_cast<const char*>(args.args[0]), static_cast<int>(args.args[1])); #endif -#if defined(__NR_open) - case __NR_open: - // http://crbug.com/372840 - BROKER_UNPOISON_STRING(reinterpret_cast<const char*>(args.args[0])); - return broker_process->Open(reinterpret_cast<const char*>(args.args[0]), - static_cast<int>(args.args[1])); -#endif -#if defined(__NR_stat) - case __NR_stat: - return broker_process->Stat(reinterpret_cast<const char*>(args.args[0]), - reinterpret_cast<struct stat*>(args.args[1])); -#endif -#if defined(__NR_stat64) - case __NR_stat64: - return broker_process->Stat64( - reinterpret_cast<const char*>(args.args[0]), - reinterpret_cast<struct stat64*>(args.args[1])); -#endif #if defined(__NR_faccessat) case __NR_faccessat: if (static_cast<int>(args.args[0]) != AT_FDCWD) @@ -171,30 +176,49 @@ intptr_t BrokerProcess::SIGSYS_Handler(const sandbox::arch_seccomp_data& args, return broker_process->Access(reinterpret_cast<const char*>(args.args[1]), static_cast<int>(args.args[2])); #endif +#if defined(__NR_mkdir) + case __NR_mkdir: + return broker_process->Mkdir(reinterpret_cast<const char*>(args.args[0]), + static_cast<int>(args.args[1])); +#endif +#if defined(__NR_mkdirat) + case __NR_mkdirat: + if (static_cast<int>(args.args[0]) != AT_FDCWD) + return -EPERM; + return broker_process->Mkdir(reinterpret_cast<const char*>(args.args[1]), + static_cast<int>(args.args[2])); +#endif +#if defined(__NR_open) + case __NR_open: + // http://crbug.com/372840 + BROKER_UNPOISON_STRING(reinterpret_cast<const char*>(args.args[0])); + return broker_process->Open(reinterpret_cast<const char*>(args.args[0]), + static_cast<int>(args.args[1])); +#endif #if defined(__NR_openat) case __NR_openat: if (static_cast<int>(args.args[0]) != AT_FDCWD) return -EPERM; + // http://crbug.com/372840 + BROKER_UNPOISON_STRING(reinterpret_cast<const char*>(args.args[1])); return broker_process->Open(reinterpret_cast<const char*>(args.args[1]), static_cast<int>(args.args[2])); #endif -#if defined(__NR_fstatat) - case __NR_fstatat: - if (static_cast<int>(args.args[0]) != AT_FDCWD) - return -EPERM; - if (static_cast<int>(args.args[3]) != 0) - return -EINVAL; - return broker_process->Stat(reinterpret_cast<const char*>(args.args[1]), - reinterpret_cast<struct stat*>(args.args[2])); +#if defined(__NR_readlink) + case __NR_readlink: + return broker_process->Readlink( + reinterpret_cast<const char*>(args.args[0]), + reinterpret_cast<char*>(args.args[1]), + static_cast<size_t>(args.args[2])); #endif -#if defined(__NR_newfstatat) - case __NR_newfstatat: +#if defined(__NR_readlinkat) + case __NR_readlinkat: if (static_cast<int>(args.args[0]) != AT_FDCWD) return -EPERM; - if (static_cast<int>(args.args[3]) != 0) - return -EINVAL; - return broker_process->Stat(reinterpret_cast<const char*>(args.args[1]), - reinterpret_cast<struct stat*>(args.args[2])); + return broker_process->Readlink( + reinterpret_cast<const char*>(args.args[1]), + reinterpret_cast<char*>(args.args[2]), + static_cast<size_t>(args.args[3])); #endif #if defined(__NR_rename) case __NR_rename: @@ -224,6 +248,52 @@ intptr_t BrokerProcess::SIGSYS_Handler(const sandbox::arch_seccomp_data& args, reinterpret_cast<const char*>(args.args[1]), reinterpret_cast<const char*>(args.args[3])); #endif +#if defined(__NR_rmdir) + case __NR_rmdir: + return broker_process->Rmdir(reinterpret_cast<const char*>(args.args[0])); +#endif +#if defined(__NR_stat) + case __NR_stat: + return broker_process->Stat(reinterpret_cast<const char*>(args.args[0]), + reinterpret_cast<struct stat*>(args.args[1])); +#endif +#if defined(__NR_stat64) + case __NR_stat64: + return broker_process->Stat64( + reinterpret_cast<const char*>(args.args[0]), + reinterpret_cast<struct stat64*>(args.args[1])); +#endif +#if defined(__NR_fstatat) + case __NR_fstatat: + if (static_cast<int>(args.args[0]) != AT_FDCWD) + return -EPERM; + if (static_cast<int>(args.args[3]) != 0) + return -EINVAL; + return broker_process->Stat(reinterpret_cast<const char*>(args.args[1]), + reinterpret_cast<struct stat*>(args.args[2])); +#endif +#if defined(__NR_newfstatat) + case __NR_newfstatat: + if (static_cast<int>(args.args[0]) != AT_FDCWD) + return -EPERM; + if (static_cast<int>(args.args[3]) != 0) + return -EINVAL; + return broker_process->Stat(reinterpret_cast<const char*>(args.args[1]), + reinterpret_cast<struct stat*>(args.args[2])); +#endif +#if defined(__NR_unlink) + case __NR_unlink: + return broker_process->Unlink( + reinterpret_cast<const char*>(args.args[0])); +#endif +#if defined(__NR_unlinkat) + case __NR_unlinkat: + // TODO(tsepez): does not support AT_REMOVEDIR flag. + if (static_cast<int>(args.args[0]) != AT_FDCWD) + return -EPERM; + return broker_process->Unlink( + reinterpret_cast<const char*>(args.args[1])); +#endif default: RAW_CHECK(false); return -ENOSYS; diff --git a/chromium/sandbox/linux/syscall_broker/broker_process.h b/chromium/sandbox/linux/syscall_broker/broker_process.h index efa84bb8342..f1dc0c606fb 100644 --- a/chromium/sandbox/linux/syscall_broker/broker_process.h +++ b/chromium/sandbox/linux/syscall_broker/broker_process.h @@ -14,7 +14,8 @@ #include "base/pickle.h" #include "base/process/process.h" #include "sandbox/linux/bpf_dsl/trap_registry.h" -#include "sandbox/linux/syscall_broker/broker_policy.h" +#include "sandbox/linux/syscall_broker/broker_command.h" +#include "sandbox/linux/syscall_broker/broker_permission_list.h" #include "sandbox/sandbox_export.h" namespace sandbox { @@ -36,9 +37,16 @@ class BrokerFilePermission; // 4. Use open_broker.Open() to open files. class SANDBOX_EXPORT BrokerProcess { public: + // Handler to be used with a bpf_dsl Trap() function to forward system calls + // to the methods below. + static intptr_t SIGSYS_Handler(const arch_seccomp_data& args, + void* aux_broker_process); + // |denied_errno| is the error code returned when methods such as Open() // or Access() are invoked on a file which is not in the whitelist (EACCESS - // would be a typical value). |permissions| describes the whitelisted set + // would be a typical value). |allowed_command_mask| is a bitwise-or of + // kBrokerCommand*Mask constants from broker_command.h that further restrict + // the syscalls to execute. |permissions| describes the whitelisted set // of files the broker is is allowed to access. |fast_check_in_client| // controls whether doomed requests are first filtered on the client side // before being proxied. Apart from tests, this should always be true since @@ -50,6 +58,7 @@ class SANDBOX_EXPORT BrokerProcess { // don't use it. BrokerProcess( int denied_errno, + const syscall_broker::BrokerCommandSet& allowed_command_set, const std::vector<syscall_broker::BrokerFilePermission>& permissions, bool fast_check_in_client = true, bool quiet_failures_for_tests = false); @@ -62,33 +71,42 @@ class SANDBOX_EXPORT BrokerProcess { // after fork() returns. bool Init(const base::Callback<bool(void)>& broker_process_init_callback); - // Can be used in place of access(). Will be async signal safe. + // Return the PID of the child created by Init(). + int broker_pid() const { return broker_pid_; } + + // The following methods are used in place of the equivalently-named + // syscalls by the trap handler. They, in turn, forward the call onto + // |broker_client_| for further processing. They will all be async signal + // safe. They all return -errno on errors. + + // Can be used in place of access(). // X_OK will always return an error in practice since the broker process // doesn't support execute permissions. - // It's similar to the access() system call and will return -errno on errors. int Access(const char* pathname, int mode) const; - // Can be used in place of open(). Will be async signal safe. + // Can be used in place of mkdir(). + int Mkdir(const char* path, int mode) const; + + // Can be used in place of open() // The implementation only supports certain white listed flags and will // return -EPERM on other flags. - // It's similar to the open() system call and will return -errno on errors. int Open(const char* pathname, int flags) const; - // Can be used in place of stat()/stat64(). Will be async signal safe. - // It's similar to the stat() system call and will return -errno on errors. - int Stat(const char* pathname, struct stat* sb) const; - int Stat64(const char* pathname, struct stat64* sb) const; + // Can be used in place of readlink(). + int Readlink(const char* path, char* buf, size_t bufsize) const; - // Can be used in place of rename(). Will be async signal safe. - // It's similar to the rename() system call and will return -errno on errors. + // Can be used in place of rename(). int Rename(const char* oldpath, const char* newpath) const; - int broker_pid() const { return broker_pid_; } + // Can be used in place of rmdir(). + int Rmdir(const char* path) const; - // Handler to be used with a bpf_dsl Trap() function to forward system calls - // to the methods above. - static intptr_t SIGSYS_Handler(const arch_seccomp_data& args, - void* aux_broker_process); + // Can be used in place of stat()/stat64(). + int Stat(const char* pathname, struct stat* sb) const; + int Stat64(const char* pathname, struct stat64* sb) const; + + // Can be used in place of unlink(). + int Unlink(const char* path) const; private: friend class BrokerProcessTestHelper; @@ -98,10 +116,12 @@ class SANDBOX_EXPORT BrokerProcess { void CloseChannel(); bool initialized_; // Whether we've been through Init() yet. + pid_t broker_pid_; // The PID of the broker (child) created in Init(). const bool fast_check_in_client_; const bool quiet_failures_for_tests_; - pid_t broker_pid_; // The PID of the broker (child). - syscall_broker::BrokerPolicy broker_policy_; // Access policy to enforce. + syscall_broker::BrokerCommandSet allowed_command_set_; + syscall_broker::BrokerPermissionList + broker_permission_list_; // File access whitelist. std::unique_ptr<syscall_broker::BrokerClient> broker_client_; DISALLOW_COPY_AND_ASSIGN(BrokerProcess); diff --git a/chromium/sandbox/linux/syscall_broker/broker_process_unittest.cc b/chromium/sandbox/linux/syscall_broker/broker_process_unittest.cc index a53600337a1..ee4754c0cf1 100644 --- a/chromium/sandbox/linux/syscall_broker/broker_process_unittest.cc +++ b/chromium/sandbox/linux/syscall_broker/broker_process_unittest.cc @@ -26,6 +26,7 @@ #include "base/macros.h" #include "base/posix/eintr_wrapper.h" #include "base/posix/unix_domain_socket.h" +#include "build/build_config.h" #include "sandbox/linux/syscall_broker/broker_client.h" #include "sandbox/linux/tests/scoped_temporary_file.h" #include "sandbox/linux/tests/test_utils.h" @@ -33,7 +34,6 @@ #include "testing/gtest/include/gtest/gtest.h" namespace sandbox { - namespace syscall_broker { class BrokerProcessTestHelper { @@ -48,6 +48,8 @@ class BrokerProcessTestHelper { namespace { +const int kFakeErrnoSentinel = 99999; + bool NoOpCallback() { return true; } @@ -55,23 +57,25 @@ bool NoOpCallback() { } // namespace TEST(BrokerProcess, CreateAndDestroy) { - std::vector<BrokerFilePermission> permissions; - permissions.push_back(BrokerFilePermission::ReadOnly("/proc/cpuinfo")); - - std::unique_ptr<BrokerProcess> open_broker( - new BrokerProcess(EPERM, permissions)); - ASSERT_TRUE(open_broker->Init(base::Bind(&NoOpCallback))); - - ASSERT_TRUE(TestUtils::CurrentProcessHasChildren()); + { + std::vector<BrokerFilePermission> permissions = { + BrokerFilePermission::ReadOnly("/proc/cpuinfo")}; + BrokerProcess open_broker(kFakeErrnoSentinel, BrokerCommandSet(), + permissions); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + ASSERT_TRUE(TestUtils::CurrentProcessHasChildren()); + } // Destroy the broker and check it has exited properly. - open_broker.reset(); ASSERT_FALSE(TestUtils::CurrentProcessHasChildren()); } TEST(BrokerProcess, TestOpenAccessNull) { + BrokerCommandSet command_set = + MakeBrokerCommandSet({COMMAND_ACCESS, COMMAND_OPEN}); + std::vector<BrokerFilePermission> empty; - BrokerProcess open_broker(EPERM, empty); - ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, empty); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); int fd = open_broker.Open(NULL, O_RDONLY); ASSERT_EQ(fd, -EFAULT); @@ -89,15 +93,17 @@ void TestOpenFilePerms(bool fast_check_in_client, int denied_errno) { const char kRW_WhiteListed[] = "/proc/DOESNOTEXIST3"; const char k_NotWhitelisted[] = "/proc/DOESNOTEXIST4"; - std::vector<BrokerFilePermission> permissions; - permissions.push_back(BrokerFilePermission::ReadOnly(kR_WhiteListed)); - permissions.push_back( - BrokerFilePermission::ReadOnly(kR_WhiteListedButDenied)); - permissions.push_back(BrokerFilePermission::WriteOnly(kW_WhiteListed)); - permissions.push_back(BrokerFilePermission::ReadWrite(kRW_WhiteListed)); + BrokerCommandSet command_set = + MakeBrokerCommandSet({COMMAND_ACCESS, COMMAND_OPEN}); - BrokerProcess open_broker(denied_errno, permissions, fast_check_in_client); - ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + std::vector<BrokerFilePermission> permissions = { + BrokerFilePermission::ReadOnly(kR_WhiteListed), + BrokerFilePermission::ReadOnly(kR_WhiteListedButDenied), + BrokerFilePermission::WriteOnly(kW_WhiteListed), + BrokerFilePermission::ReadWrite(kRW_WhiteListed)}; + BrokerProcess open_broker(denied_errno, command_set, permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); int fd = -1; fd = open_broker.Open(kR_WhiteListed, O_RDONLY); @@ -249,44 +255,47 @@ void TestBadPaths(bool fast_check_in_client) { const char kDotDotEnd[] = "/proc/.."; const char kTrailingSlash[] = "/proc/"; - std::vector<BrokerFilePermission> permissions; + BrokerCommandSet command_set = + MakeBrokerCommandSet({COMMAND_ACCESS, COMMAND_OPEN}); + + std::vector<BrokerFilePermission> permissions = { + BrokerFilePermission::ReadOnlyRecursive("/proc/")}; + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); - permissions.push_back(BrokerFilePermission::ReadOnlyRecursive("/proc/")); - std::unique_ptr<BrokerProcess> open_broker( - new BrokerProcess(EPERM, permissions, fast_check_in_client)); - ASSERT_TRUE(open_broker->Init(base::Bind(&NoOpCallback))); // Open cpuinfo via the broker. - int cpuinfo_fd = open_broker->Open(kFileCpuInfo, O_RDONLY); + int cpuinfo_fd = open_broker.Open(kFileCpuInfo, O_RDONLY); base::ScopedFD cpuinfo_fd_closer(cpuinfo_fd); ASSERT_GE(cpuinfo_fd, 0); int fd = -1; int can_access; - can_access = open_broker->Access(kNotAbsPath, R_OK); - ASSERT_EQ(can_access, -EPERM); - fd = open_broker->Open(kNotAbsPath, O_RDONLY); - ASSERT_EQ(fd, -EPERM); - - can_access = open_broker->Access(kDotDotStart, R_OK); - ASSERT_EQ(can_access, -EPERM); - fd = open_broker->Open(kDotDotStart, O_RDONLY); - ASSERT_EQ(fd, -EPERM); - - can_access = open_broker->Access(kDotDotMiddle, R_OK); - ASSERT_EQ(can_access, -EPERM); - fd = open_broker->Open(kDotDotMiddle, O_RDONLY); - ASSERT_EQ(fd, -EPERM); - - can_access = open_broker->Access(kDotDotEnd, R_OK); - ASSERT_EQ(can_access, -EPERM); - fd = open_broker->Open(kDotDotEnd, O_RDONLY); - ASSERT_EQ(fd, -EPERM); - - can_access = open_broker->Access(kTrailingSlash, R_OK); - ASSERT_EQ(can_access, -EPERM); - fd = open_broker->Open(kTrailingSlash, O_RDONLY); - ASSERT_EQ(fd, -EPERM); + can_access = open_broker.Access(kNotAbsPath, R_OK); + ASSERT_EQ(can_access, -kFakeErrnoSentinel); + fd = open_broker.Open(kNotAbsPath, O_RDONLY); + ASSERT_EQ(fd, -kFakeErrnoSentinel); + + can_access = open_broker.Access(kDotDotStart, R_OK); + ASSERT_EQ(can_access, -kFakeErrnoSentinel); + fd = open_broker.Open(kDotDotStart, O_RDONLY); + ASSERT_EQ(fd, -kFakeErrnoSentinel); + + can_access = open_broker.Access(kDotDotMiddle, R_OK); + ASSERT_EQ(can_access, -kFakeErrnoSentinel); + fd = open_broker.Open(kDotDotMiddle, O_RDONLY); + ASSERT_EQ(fd, -kFakeErrnoSentinel); + + can_access = open_broker.Access(kDotDotEnd, R_OK); + ASSERT_EQ(can_access, -kFakeErrnoSentinel); + fd = open_broker.Open(kDotDotEnd, O_RDONLY); + ASSERT_EQ(fd, -kFakeErrnoSentinel); + + can_access = open_broker.Access(kTrailingSlash, R_OK); + ASSERT_EQ(can_access, -kFakeErrnoSentinel); + fd = open_broker.Open(kTrailingSlash, O_RDONLY); + ASSERT_EQ(fd, -kFakeErrnoSentinel); } TEST(BrokerProcess, BadPathsClientCheck) { @@ -305,54 +314,56 @@ void TestOpenCpuinfo(bool fast_check_in_client, bool recursive) { const char kFileCpuInfo[] = "/proc/cpuinfo"; const char kDirProc[] = "/proc/"; - std::vector<BrokerFilePermission> permissions; - if (recursive) - permissions.push_back(BrokerFilePermission::ReadOnlyRecursive(kDirProc)); - else - permissions.push_back(BrokerFilePermission::ReadOnly(kFileCpuInfo)); - - std::unique_ptr<BrokerProcess> open_broker( - new BrokerProcess(EPERM, permissions, fast_check_in_client)); - ASSERT_TRUE(open_broker->Init(base::Bind(&NoOpCallback))); - - int fd = -1; - fd = open_broker->Open(kFileCpuInfo, O_RDWR); - base::ScopedFD fd_closer(fd); - ASSERT_EQ(fd, -EPERM); + { + BrokerCommandSet command_set = + MakeBrokerCommandSet({COMMAND_ACCESS, COMMAND_OPEN}); - // Check we can read /proc/cpuinfo. - int can_access = open_broker->Access(kFileCpuInfo, R_OK); - ASSERT_EQ(can_access, 0); - can_access = open_broker->Access(kFileCpuInfo, W_OK); - ASSERT_EQ(can_access, -EPERM); - // Check we can not write /proc/cpuinfo. + std::vector<BrokerFilePermission> permissions; + permissions.push_back( + recursive ? BrokerFilePermission::ReadOnlyRecursive(kDirProc) + : BrokerFilePermission::ReadOnly(kFileCpuInfo)); + + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + + int fd = open_broker.Open(kFileCpuInfo, O_RDWR); + ASSERT_EQ(fd, -kFakeErrnoSentinel); + + // Check we can read /proc/cpuinfo. + int can_access = open_broker.Access(kFileCpuInfo, R_OK); + EXPECT_EQ(can_access, 0); + can_access = open_broker.Access(kFileCpuInfo, W_OK); + EXPECT_EQ(can_access, -kFakeErrnoSentinel); + // Check we can not write /proc/cpuinfo. + + // Open cpuinfo via the broker. + int cpuinfo_fd = open_broker.Open(kFileCpuInfo, O_RDONLY); + base::ScopedFD cpuinfo_fd_closer(cpuinfo_fd); + EXPECT_GE(cpuinfo_fd, 0); + char buf[3]; + memset(buf, 0, sizeof(buf)); + int read_len1 = read(cpuinfo_fd, buf, sizeof(buf)); + EXPECT_GT(read_len1, 0); + + // Open cpuinfo directly. + int cpuinfo_fd2 = open(kFileCpuInfo, O_RDONLY); + base::ScopedFD cpuinfo_fd2_closer(cpuinfo_fd2); + EXPECT_GE(cpuinfo_fd2, 0); + char buf2[3]; + memset(buf2, 1, sizeof(buf2)); + int read_len2 = read(cpuinfo_fd2, buf2, sizeof(buf2)); + EXPECT_GT(read_len1, 0); + + // The following is not guaranteed true, but will be in practice. + EXPECT_EQ(read_len1, read_len2); + // Compare the cpuinfo as returned by the broker with the one we opened + // ourselves. + EXPECT_EQ(memcmp(buf, buf2, read_len1), 0); + + ASSERT_TRUE(TestUtils::CurrentProcessHasChildren()); + } - // Open cpuinfo via the broker. - int cpuinfo_fd = open_broker->Open(kFileCpuInfo, O_RDONLY); - base::ScopedFD cpuinfo_fd_closer(cpuinfo_fd); - ASSERT_GE(cpuinfo_fd, 0); - char buf[3]; - memset(buf, 0, sizeof(buf)); - int read_len1 = read(cpuinfo_fd, buf, sizeof(buf)); - ASSERT_GT(read_len1, 0); - - // Open cpuinfo directly. - int cpuinfo_fd2 = open(kFileCpuInfo, O_RDONLY); - base::ScopedFD cpuinfo_fd2_closer(cpuinfo_fd2); - ASSERT_GE(cpuinfo_fd2, 0); - char buf2[3]; - memset(buf2, 1, sizeof(buf2)); - int read_len2 = read(cpuinfo_fd2, buf2, sizeof(buf2)); - ASSERT_GT(read_len1, 0); - - // The following is not guaranteed true, but will be in practice. - ASSERT_EQ(read_len1, read_len2); - // Compare the cpuinfo as returned by the broker with the one we opened - // ourselves. - ASSERT_EQ(memcmp(buf, buf2, read_len1), 0); - - ASSERT_TRUE(TestUtils::CurrentProcessHasChildren()); - open_broker.reset(); ASSERT_FALSE(TestUtils::CurrentProcessHasChildren()); } @@ -386,11 +397,13 @@ TEST(BrokerProcess, OpenFileRW) { ScopedTemporaryFile tempfile; const char* tempfile_name = tempfile.full_file_name(); - std::vector<BrokerFilePermission> permissions; - permissions.push_back(BrokerFilePermission::ReadWrite(tempfile_name)); + BrokerCommandSet command_set = + MakeBrokerCommandSet({COMMAND_ACCESS, COMMAND_OPEN}); - BrokerProcess open_broker(EPERM, permissions); - ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + std::vector<BrokerFilePermission> permissions = { + BrokerFilePermission::ReadWrite(tempfile_name)}; + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); // Check we can access that file with read or write. int can_access = open_broker.Access(tempfile_name, R_OK | W_OK); @@ -420,12 +433,16 @@ TEST(BrokerProcess, OpenFileRW) { // and we want this to happen in a subprocess. SANDBOX_TEST(BrokerProcess, BrokerDied) { const char kCpuInfo[] = "/proc/cpuinfo"; - std::vector<BrokerFilePermission> permissions; - permissions.push_back(BrokerFilePermission::ReadOnly(kCpuInfo)); - BrokerProcess open_broker(EPERM, permissions, true /* fast_check_in_client */, + BrokerCommandSet command_set = + MakeBrokerCommandSet({COMMAND_ACCESS, COMMAND_OPEN}); + + std::vector<BrokerFilePermission> permissions = { + BrokerFilePermission::ReadOnly(kCpuInfo)}; + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, + true /* fast_check_in_client */, true /* quiet_failures_for_tests */); - SANDBOX_ASSERT(open_broker.Init(base::Bind(&NoOpCallback))); + SANDBOX_ASSERT(open_broker.Init(base::BindRepeating(&NoOpCallback))); const pid_t broker_pid = open_broker.broker_pid(); SANDBOX_ASSERT(kill(broker_pid, SIGKILL) == 0); @@ -445,11 +462,16 @@ SANDBOX_TEST(BrokerProcess, BrokerDied) { void TestOpenComplexFlags(bool fast_check_in_client) { const char kCpuInfo[] = "/proc/cpuinfo"; - std::vector<BrokerFilePermission> permissions; - permissions.push_back(BrokerFilePermission::ReadOnly(kCpuInfo)); - BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); - ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + BrokerCommandSet command_set = + MakeBrokerCommandSet({COMMAND_ACCESS, COMMAND_OPEN}); + + std::vector<BrokerFilePermission> permissions = { + BrokerFilePermission::ReadOnly(kCpuInfo)}; + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + // Test that we do the right thing for O_CLOEXEC and O_NONBLOCK. int fd = -1; int ret = 0; @@ -525,8 +547,6 @@ SANDBOX_TEST_ALLOW_NOISE(BrokerProcess, MAYBE_RecvMsgDescriptorLeak) { *std::max_element(available_fds, available_fds + arraysize(available_fds)); - // Valgrind doesn't allow changing the hard descriptor limit, so we only - // change the soft descriptor limit here. struct rlimit rlim; SANDBOX_ASSERT(0 == getrlimit(RLIMIT_NOFILE, &rlim)); SANDBOX_ASSERT(fd_limit <= rlim.rlim_cur); @@ -534,11 +554,14 @@ SANDBOX_TEST_ALLOW_NOISE(BrokerProcess, MAYBE_RecvMsgDescriptorLeak) { SANDBOX_ASSERT(0 == setrlimit(RLIMIT_NOFILE, &rlim)); static const char kCpuInfo[] = "/proc/cpuinfo"; - std::vector<BrokerFilePermission> permissions; - permissions.push_back(BrokerFilePermission::ReadOnly(kCpuInfo)); - BrokerProcess open_broker(EPERM, permissions); - SANDBOX_ASSERT(open_broker.Init(base::Bind(&NoOpCallback))); + BrokerCommandSet command_set = + MakeBrokerCommandSet({COMMAND_ACCESS, COMMAND_OPEN}); + + std::vector<BrokerFilePermission> permissions = { + BrokerFilePermission::ReadOnly(kCpuInfo)}; + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions); + SANDBOX_ASSERT(open_broker.Init(base::BindRepeating(&NoOpCallback))); const int ipc_fd = BrokerProcessTestHelper::GetIPCDescriptor(&open_broker); SANDBOX_ASSERT(ipc_fd >= 0); @@ -578,17 +601,21 @@ bool WaitForClosedPipeWriter(int reader, int timeout_in_ms) { // Closing the broker client's IPC channel should terminate the broker // process. TEST(BrokerProcess, BrokerDiesOnClosedChannel) { - std::vector<BrokerFilePermission> permissions; - permissions.push_back(BrokerFilePermission::ReadOnly("/proc/cpuinfo")); - // Get the writing end of a pipe into the broker (child) process so // that we can reliably detect when it dies. int lifeline_fds[2]; PCHECK(0 == pipe(lifeline_fds)); - BrokerProcess open_broker(EPERM, permissions, true /* fast_check_in_client */, + BrokerCommandSet command_set = + MakeBrokerCommandSet({COMMAND_ACCESS, COMMAND_OPEN}); + + std::vector<BrokerFilePermission> permissions = { + BrokerFilePermission::ReadOnly("/proc/cpuinfo")}; + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, + true /* fast_check_in_client */, false /* quiet_failures_for_tests */); - ASSERT_TRUE(open_broker.Init(base::Bind(&CloseFD, lifeline_fds[0]))); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&CloseFD, lifeline_fds[0]))); + // Make sure the writing end only exists in the broker process. CloseFD(lifeline_fds[1]); base::ScopedFD reader(lifeline_fds[0]); @@ -614,96 +641,206 @@ TEST(BrokerProcess, BrokerDiesOnClosedChannel) { TEST(BrokerProcess, CreateFile) { std::string temp_str; + std::string perm_str; { - ScopedTemporaryFile tmp_file; - temp_str = tmp_file.full_file_name(); + ScopedTemporaryFile temp_file; + ScopedTemporaryFile perm_file; + temp_str = temp_file.full_file_name(); + perm_str = perm_file.full_file_name(); } const char* tempfile_name = temp_str.c_str(); + const char* permfile_name = perm_str.c_str(); - std::vector<BrokerFilePermission> permissions; - permissions.push_back(BrokerFilePermission::ReadWriteCreate(tempfile_name)); + BrokerCommandSet command_set = + MakeBrokerCommandSet({COMMAND_ACCESS, COMMAND_OPEN}); - BrokerProcess open_broker(EPERM, permissions); - ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + std::vector<BrokerFilePermission> permissions = { + BrokerFilePermission::ReadWriteCreateTemporary(tempfile_name), + BrokerFilePermission::ReadWriteCreate(permfile_name), + }; + + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); int fd = -1; - // Try without O_EXCL + // Opening a temp file using O_CREAT but not O_EXCL must not be allowed + // by the broker so as to prevent spying on any pre-existing files. fd = open_broker.Open(tempfile_name, O_RDWR | O_CREAT); - ASSERT_EQ(fd, -EPERM); + ASSERT_EQ(fd, -kFakeErrnoSentinel); - const char kTestText[] = "TESTTESTTEST"; - // Create a file + // Opening a temp file in a normal way must not be allowed by the broker, + // either. + fd = open_broker.Open(tempfile_name, O_RDWR); + ASSERT_EQ(fd, -kFakeErrnoSentinel); + + // Opening a temp file with both O_CREAT and O_EXCL is allowed since the + // file is known not to exist outside the scope of ScopedTemporaryFile. fd = open_broker.Open(tempfile_name, O_RDWR | O_CREAT | O_EXCL); ASSERT_GE(fd, 0); - { - base::ScopedFD scoped_fd(fd); + close(fd); - // Confirm fail if file exists - int bad_fd = open_broker.Open(tempfile_name, O_RDWR | O_CREAT | O_EXCL); - ASSERT_EQ(bad_fd, -EEXIST); + // Manually create a conflict for the temp filename. + fd = open(tempfile_name, O_RDWR | O_CREAT, 0600); + ASSERT_GE(fd, 0); + close(fd); - // Write to the descriptor opened by the broker. + // Opening a temp file with both O_CREAT and O_EXCL is allowed but fails + // per the OS when there is a conflict with a pre-existing file. + fd = open_broker.Open(tempfile_name, O_RDWR | O_CREAT | O_EXCL); + ASSERT_EQ(fd, -EEXIST); + // Opening a new permanent file without specifying O_EXCL is allowed. + fd = open_broker.Open(permfile_name, O_RDWR | O_CREAT); + ASSERT_GE(fd, 0); + close(fd); + + // Opening an existing permanent file without specifying O_EXCL is allowed. + fd = open_broker.Open(permfile_name, O_RDWR | O_CREAT); + ASSERT_GE(fd, 0); + close(fd); + + // Opening an existing file with O_EXCL is allowed but fails per the OS. + fd = open_broker.Open(permfile_name, O_RDWR | O_CREAT | O_EXCL); + ASSERT_EQ(fd, -EEXIST); + + const char kTestText[] = "TESTTESTTEST"; + + fd = open_broker.Open(permfile_name, O_RDWR); + ASSERT_GE(fd, 0); + { + // Write to the descriptor opened by the broker and close. + base::ScopedFD scoped_fd(fd); ssize_t len = HANDLE_EINTR(write(fd, kTestText, sizeof(kTestText))); ASSERT_EQ(len, static_cast<ssize_t>(sizeof(kTestText))); } - int fd_check = open(tempfile_name, O_RDONLY); + int fd_check = open(permfile_name, O_RDONLY); ASSERT_GE(fd_check, 0); { base::ScopedFD scoped_fd(fd_check); char buf[1024]; ssize_t len = HANDLE_EINTR(read(fd_check, buf, sizeof(buf))); - ASSERT_EQ(len, static_cast<ssize_t>(sizeof(kTestText))); ASSERT_EQ(memcmp(kTestText, buf, sizeof(kTestText)), 0); } + + // Cleanup. + unlink(tempfile_name); + unlink(permfile_name); } -TEST(BrokerProcess, StatFile) { +void TestStatHelper(bool fast_check_in_client) { ScopedTemporaryFile tmp_file; EXPECT_EQ(12, write(tmp_file.fd(), "blahblahblah", 12)); std::string temp_str = tmp_file.full_file_name(); const char* tempfile_name = temp_str.c_str(); - const char* nonesuch_name = "/mbogo/nonesuch"; - const bool fast_check_in_client = false; + const char* nonesuch_name = "/mbogo/fictitious/nonesuch"; + const char* leading_path1 = "/mbogo/fictitious"; + const char* leading_path2 = "/mbogo"; + const char* leading_path3 = "/"; + const char* bad_leading_path1 = "/mbog"; + const char* bad_leading_path2 = "/mboga"; + const char* bad_leading_path3 = "/mbogos"; + const char* bad_leading_path4 = "/mbogo/fictitiou"; + const char* bad_leading_path5 = "/mbogo/fictitioux"; + const char* bad_leading_path6 = "/mbogo/fictitiousa"; + struct stat sb; + + { + // Actual file with permissions to see file but command not allowed. + std::vector<BrokerFilePermission> permissions = { + BrokerFilePermission::ReadOnly(tempfile_name)}; + BrokerProcess open_broker(kFakeErrnoSentinel, BrokerCommandSet(), + permissions, fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + + memset(&sb, 0, sizeof(sb)); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Stat(tempfile_name, &sb)); + } + + BrokerCommandSet command_set; + command_set.set(COMMAND_STAT); + { // Nonexistent file with no permissions to see file. std::vector<BrokerFilePermission> permissions; - BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); - ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); memset(&sb, 0, sizeof(sb)); - EXPECT_EQ(-EPERM, open_broker.Stat(nonesuch_name, &sb)); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Stat(nonesuch_name, &sb)); } { // Actual file with no permission to see file. std::vector<BrokerFilePermission> permissions; - BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); - ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); memset(&sb, 0, sizeof(sb)); - EXPECT_EQ(-EPERM, open_broker.Stat(tempfile_name, &sb)); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Stat(tempfile_name, &sb)); } { // Nonexistent file with permissions to see file. - std::vector<BrokerFilePermission> permissions; - permissions.push_back(BrokerFilePermission::ReadOnly(nonesuch_name)); - BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); - ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + std::vector<BrokerFilePermission> permissions = { + BrokerFilePermission::ReadOnly(nonesuch_name)}; + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + + memset(&sb, 0, sizeof(sb)); + EXPECT_EQ(-ENOENT, open_broker.Stat(nonesuch_name, &sb)); + + // Gets denied all the way back to root since no create permission. + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Stat(leading_path1, &sb)); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Stat(leading_path2, &sb)); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Stat(leading_path3, &sb)); + + // Not fooled by substrings. + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Stat(bad_leading_path1, &sb)); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Stat(bad_leading_path2, &sb)); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Stat(bad_leading_path3, &sb)); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Stat(bad_leading_path4, &sb)); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Stat(bad_leading_path5, &sb)); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Stat(bad_leading_path6, &sb)); + } + { + // Nonexistent file with permissions to create file. + std::vector<BrokerFilePermission> permissions = { + BrokerFilePermission::ReadWriteCreate(nonesuch_name)}; + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); memset(&sb, 0, sizeof(sb)); EXPECT_EQ(-ENOENT, open_broker.Stat(nonesuch_name, &sb)); + + // Gets ENOENT all the way back to root since it has create permission. + EXPECT_EQ(-ENOENT, open_broker.Stat(leading_path1, &sb)); + EXPECT_EQ(-ENOENT, open_broker.Stat(leading_path2, &sb)); + + // But can always get the root. + EXPECT_EQ(0, open_broker.Stat(leading_path3, &sb)); + + // Not fooled by substrings. + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Stat(bad_leading_path1, &sb)); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Stat(bad_leading_path2, &sb)); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Stat(bad_leading_path3, &sb)); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Stat(bad_leading_path4, &sb)); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Stat(bad_leading_path5, &sb)); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Stat(bad_leading_path6, &sb)); } { // Actual file with permissions to see file. - std::vector<BrokerFilePermission> permissions; - permissions.push_back(BrokerFilePermission::ReadOnly(tempfile_name)); - BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); - ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + std::vector<BrokerFilePermission> permissions = { + BrokerFilePermission::ReadOnly(tempfile_name)}; + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); memset(&sb, 0, sizeof(sb)); EXPECT_EQ(0, open_broker.Stat(tempfile_name, &sb)); @@ -713,11 +850,13 @@ TEST(BrokerProcess, StatFile) { EXPECT_NE(0u, static_cast<unsigned int>(sb.st_dev)); EXPECT_NE(0u, static_cast<unsigned int>(sb.st_ino)); EXPECT_NE(0u, static_cast<unsigned int>(sb.st_mode)); - EXPECT_NE(0u, static_cast<unsigned int>(sb.st_uid)); - EXPECT_NE(0u, static_cast<unsigned int>(sb.st_gid)); EXPECT_NE(0u, static_cast<unsigned int>(sb.st_blksize)); EXPECT_NE(0u, static_cast<unsigned int>(sb.st_blocks)); + // We are the ones that made the file. + EXPECT_EQ(geteuid(), sb.st_uid); + EXPECT_EQ(getegid(), sb.st_gid); + // Wrote 12 bytes above which should fit in one block. EXPECT_EQ(12, sb.st_size); @@ -728,7 +867,15 @@ TEST(BrokerProcess, StatFile) { } } -TEST(BrokerProcess, RenameFile) { +TEST(BrokerProcess, StatFileClient) { + TestStatHelper(true); +} + +TEST(BrokerProcess, StatFileHost) { + TestStatHelper(false); +} + +void TestRenameHelper(bool fast_check_in_client) { std::string oldpath; std::string newpath; { @@ -747,15 +894,35 @@ TEST(BrokerProcess, RenameFile) { EXPECT_TRUE(access(oldpath.c_str(), F_OK) == 0); EXPECT_TRUE(access(newpath.c_str(), F_OK) < 0); + std::vector<BrokerFilePermission> rwc_permissions = { + BrokerFilePermission::ReadWriteCreate(oldpath), + BrokerFilePermission::ReadWriteCreate(newpath)}; + { - // Check rename fails when no permission to new file. - std::vector<BrokerFilePermission> permissions; - permissions.push_back(BrokerFilePermission::ReadWrite(oldpath)); + // Check rename fails with write permissions to both files but command + // itself is not allowed. + BrokerProcess open_broker(kFakeErrnoSentinel, BrokerCommandSet(), + rwc_permissions, fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-kFakeErrnoSentinel, + open_broker.Rename(oldpath.c_str(), newpath.c_str())); - bool fast_check_in_client = false; - BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); - ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); - EXPECT_EQ(-EPERM, open_broker.Rename(oldpath.c_str(), newpath.c_str())); + // ... and no files moved around. + EXPECT_TRUE(access(oldpath.c_str(), F_OK) == 0); + EXPECT_TRUE(access(newpath.c_str(), F_OK) < 0); + } + + BrokerCommandSet command_set = MakeBrokerCommandSet({COMMAND_RENAME}); + + { + // Check rename fails when no permission to new file. + std::vector<BrokerFilePermission> permissions = { + BrokerFilePermission::ReadWriteCreate(oldpath)}; + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-kFakeErrnoSentinel, + open_broker.Rename(oldpath.c_str(), newpath.c_str())); // ... and no files moved around. EXPECT_TRUE(access(oldpath.c_str(), F_OK) == 0); @@ -763,13 +930,13 @@ TEST(BrokerProcess, RenameFile) { } { // Check rename fails when no permission to old file. - std::vector<BrokerFilePermission> permissions; - permissions.push_back(BrokerFilePermission::ReadWrite(newpath)); - - bool fast_check_in_client = false; - BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); - ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); - EXPECT_EQ(-EPERM, open_broker.Rename(oldpath.c_str(), newpath.c_str())); + std::vector<BrokerFilePermission> permissions = { + BrokerFilePermission::ReadWriteCreate(newpath)}; + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-kFakeErrnoSentinel, + open_broker.Rename(oldpath.c_str(), newpath.c_str())); // ... and no files moved around. EXPECT_TRUE(access(oldpath.c_str(), F_OK) == 0); @@ -777,29 +944,29 @@ TEST(BrokerProcess, RenameFile) { } { // Check rename fails when only read permission to first file. - std::vector<BrokerFilePermission> permissions; - permissions.push_back(BrokerFilePermission::ReadOnly(oldpath)); - permissions.push_back(BrokerFilePermission::ReadWrite(newpath)); - - bool fast_check_in_client = false; - BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); - ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); - EXPECT_EQ(-EPERM, open_broker.Rename(oldpath.c_str(), newpath.c_str())); + std::vector<BrokerFilePermission> permissions = { + BrokerFilePermission::ReadOnly(oldpath), + BrokerFilePermission::ReadWriteCreate(newpath)}; + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-kFakeErrnoSentinel, + open_broker.Rename(oldpath.c_str(), newpath.c_str())); // ... and no files moved around. EXPECT_TRUE(access(oldpath.c_str(), F_OK) == 0); EXPECT_TRUE(access(newpath.c_str(), F_OK) < 0); } { - // Check rename fails when only read permission to first file. - std::vector<BrokerFilePermission> permissions; - permissions.push_back(BrokerFilePermission::ReadWrite(oldpath)); - permissions.push_back(BrokerFilePermission::ReadOnly(newpath)); - - bool fast_check_in_client = false; - BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); - ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); - EXPECT_EQ(-EPERM, open_broker.Rename(oldpath.c_str(), newpath.c_str())); + // Check rename fails when only read permission to second file. + std::vector<BrokerFilePermission> permissions = { + BrokerFilePermission::ReadWriteCreate(oldpath), + BrokerFilePermission::ReadOnly(newpath)}; + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-kFakeErrnoSentinel, + open_broker.Rename(oldpath.c_str(), newpath.c_str())); // ... and no files moved around. EXPECT_TRUE(access(oldpath.c_str(), F_OK) == 0); @@ -807,13 +974,9 @@ TEST(BrokerProcess, RenameFile) { } { // Check rename passes with write permissions to both files. - std::vector<BrokerFilePermission> permissions; - permissions.push_back(BrokerFilePermission::ReadWrite(oldpath)); - permissions.push_back(BrokerFilePermission::ReadWrite(newpath)); - - bool fast_check_in_client = false; - BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); - ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, rwc_permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); EXPECT_EQ(0, open_broker.Rename(oldpath.c_str(), newpath.c_str())); // ... and files were moved around. @@ -825,5 +988,448 @@ TEST(BrokerProcess, RenameFile) { unlink(newpath.c_str()); } +TEST(BrokerProcess, RenameFileClient) { + TestRenameHelper(true); +} + +TEST(BrokerProcess, RenameFileHost) { + TestRenameHelper(false); +} + +void TestReadlinkHelper(bool fast_check_in_client) { + std::string oldpath; + std::string newpath; + { + // Just to generate names and ensure they do not exist upon scope exit. + ScopedTemporaryFile oldfile; + ScopedTemporaryFile newfile; + oldpath = oldfile.full_file_name(); + newpath = newfile.full_file_name(); + } + + // Now make a link from old to new path name. + EXPECT_TRUE(symlink(oldpath.c_str(), newpath.c_str()) == 0); + + const char* nonesuch_name = "/mbogo/nonesuch"; + const char* oldpath_name = oldpath.c_str(); + const char* newpath_name = newpath.c_str(); + char buf[1024]; + { + // Actual file with permissions to see file but command itself not allowed. + std::vector<BrokerFilePermission> permissions = { + BrokerFilePermission::ReadOnly(newpath_name)}; + BrokerProcess open_broker(kFakeErrnoSentinel, BrokerCommandSet(), + permissions, fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-kFakeErrnoSentinel, + open_broker.Readlink(newpath_name, buf, sizeof(buf))); + } + + BrokerCommandSet command_set; + command_set.set(COMMAND_READLINK); + + { + // Nonexistent file with no permissions to see file. + std::vector<BrokerFilePermission> permissions; + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-kFakeErrnoSentinel, + open_broker.Readlink(nonesuch_name, buf, sizeof(buf))); + } + { + // Actual file with no permissions to see file. + std::vector<BrokerFilePermission> permissions; + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-kFakeErrnoSentinel, + open_broker.Readlink(newpath_name, buf, sizeof(buf))); + } + { + // Nonexistent file with permissions to see file. + std::vector<BrokerFilePermission> permissions = { + BrokerFilePermission::ReadOnly(nonesuch_name)}; + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-ENOENT, open_broker.Readlink(nonesuch_name, buf, sizeof(buf))); + } + { + // Actual file with permissions to see file. + std::vector<BrokerFilePermission> permissions = { + BrokerFilePermission::ReadOnly(newpath_name)}; + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + ssize_t retlen = open_broker.Readlink(newpath_name, buf, sizeof(buf)); + EXPECT_TRUE(retlen == static_cast<ssize_t>(strlen(oldpath_name))); + EXPECT_EQ(0, memcmp(oldpath_name, buf, retlen)); + } + { + // Actual file with permissions to see file, but too small a buffer. + std::vector<BrokerFilePermission> permissions = { + BrokerFilePermission::ReadOnly(newpath_name)}; + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-ENAMETOOLONG, open_broker.Readlink(newpath_name, buf, 4)); + } + + // Cleanup both paths. + unlink(oldpath.c_str()); + unlink(newpath.c_str()); +} + +TEST(BrokerProcess, ReadlinkFileClient) { + TestReadlinkHelper(true); +} + +TEST(BrokerProcess, ReadlinkFileHost) { + TestReadlinkHelper(false); +} + +void TestMkdirHelper(bool fast_check_in_client) { + std::string path; + { + // Generate name and ensure it does not exist upon scope exit. + ScopedTemporaryFile file; + path = file.full_file_name(); + } + const char* path_name = path.c_str(); + const char* nonesuch_name = "/mbogo/nonesuch"; + + std::vector<BrokerFilePermission> no_permissions; + std::vector<BrokerFilePermission> ro_permissions = { + BrokerFilePermission::ReadOnly(path_name), + BrokerFilePermission::ReadOnly(nonesuch_name)}; + + std::vector<BrokerFilePermission> rw_permissions = { + BrokerFilePermission::ReadWrite(path_name), + BrokerFilePermission::ReadWrite(nonesuch_name)}; + + std::vector<BrokerFilePermission> rwc_permissions = { + BrokerFilePermission::ReadWriteCreate(path_name), + BrokerFilePermission::ReadWriteCreate(nonesuch_name)}; + + { + // Actual file with permissions to use but command itself not allowed. + BrokerProcess open_broker(kFakeErrnoSentinel, BrokerCommandSet(), + rw_permissions, fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Mkdir(path_name, 0600)); + } + + BrokerCommandSet command_set = MakeBrokerCommandSet({COMMAND_MKDIR}); + + { + // Nonexistent file with no permissions to see file. + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, no_permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Mkdir(nonesuch_name, 0600)); + } + { + // Actual file with no permissions to see file. + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, no_permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Mkdir(path_name, 0600)); + } + { + // Nonexistent file with insufficient permissions to see file. + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, ro_permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Mkdir(nonesuch_name, 0600)); + } + { + // Actual file with insufficient permissions to see file. + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, ro_permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Mkdir(path_name, 0600)); + } + { + // Nonexistent file with insufficient permissions to see file, case 2. + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, rw_permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Mkdir(nonesuch_name, 0600)); + } + { + // Actual file with insufficient permissions to see file, case 2. + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, rw_permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Mkdir(path_name, 0600)); + } + { + // Nonexistent file with permissions to see file. + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, rwc_permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-ENOENT, open_broker.Mkdir(nonesuch_name, 0600)); + } + { + // Actual file with permissions to see file. + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, rwc_permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(0, open_broker.Mkdir(path_name, 0600)); + } + + // Cleanup. + rmdir(path_name); +} + +TEST(BrokerProcess, MkdirClient) { + TestMkdirHelper(true); +} + +TEST(BrokerProcess, MkdirHost) { + TestMkdirHelper(false); +} + +void TestRmdirHelper(bool fast_check_in_client) { + std::string path; + { + // Generate name and ensure it does not exist upon scope exit. + ScopedTemporaryFile file; + path = file.full_file_name(); + } + const char* path_name = path.c_str(); + const char* nonesuch_name = "/mbogo/nonesuch"; + + EXPECT_EQ(0, mkdir(path_name, 0600)); + EXPECT_EQ(0, access(path_name, F_OK)); + + std::vector<BrokerFilePermission> no_permissions; + std::vector<BrokerFilePermission> ro_permissions = { + BrokerFilePermission::ReadOnly(path_name), + BrokerFilePermission::ReadOnly(nonesuch_name)}; + + std::vector<BrokerFilePermission> rw_permissions = { + BrokerFilePermission::ReadWrite(path_name), + BrokerFilePermission::ReadWrite(nonesuch_name)}; + + std::vector<BrokerFilePermission> rwc_permissions = { + BrokerFilePermission::ReadWriteCreate(path_name), + BrokerFilePermission::ReadWriteCreate(nonesuch_name)}; + + { + // Actual dir with permissions to use but command itself not allowed. + BrokerProcess open_broker(kFakeErrnoSentinel, BrokerCommandSet(), + rw_permissions, fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Rmdir(path_name)); + } + EXPECT_EQ(0, access(path_name, F_OK)); + + BrokerCommandSet command_set = MakeBrokerCommandSet({COMMAND_RMDIR}); + + { + // Nonexistent dir with no permissions to see dir. + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, no_permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Rmdir(nonesuch_name)); + } + EXPECT_EQ(0, access(path_name, F_OK)); + + { + // Actual dir with no permissions to see dir. + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, no_permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Rmdir(path_name)); + } + EXPECT_EQ(0, access(path_name, F_OK)); + + { + // Nonexistent dir with insufficient permissions to see dir. + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, ro_permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Rmdir(nonesuch_name)); + } + EXPECT_EQ(0, access(path_name, F_OK)); + + { + // Actual dir with insufficient permissions to see dir. + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, ro_permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Rmdir(path_name)); + } + EXPECT_EQ(0, access(path_name, F_OK)); + + { + // Nonexistent dir with insufficient permissions to see dir, case 2. + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, rw_permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Rmdir(nonesuch_name)); + } + EXPECT_EQ(0, access(path_name, F_OK)); + + { + // Actual dir with insufficient permissions to see dir, case 2. + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, rw_permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Rmdir(path_name)); + } + EXPECT_EQ(0, access(path_name, F_OK)); + + { + // Nonexistent dir with permissions to see dir. + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, rwc_permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_TRUE(open_broker.Rmdir(nonesuch_name) < 0); + } + EXPECT_EQ(0, access(path_name, F_OK)); + + { + // Actual dir with permissions to see dir. + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, rwc_permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(0, open_broker.Rmdir(path_name)); + } + // Confirm it was erased. + EXPECT_EQ(-1, access(path_name, F_OK)); +} + +TEST(BrokerProcess, RmdirClient) { + TestRmdirHelper(true); +} + +TEST(BrokerProcess, RmdirHost) { + TestRmdirHelper(false); +} + +void TestUnlinkHelper(bool fast_check_in_client) { + std::string path; + { + // Generate name and ensure it does not exist upon scope exit. + ScopedTemporaryFile file; + path = file.full_file_name(); + } + const char* path_name = path.c_str(); + const char* nonesuch_name = "/mbogo/nonesuch"; + + int fd = open(path_name, O_RDWR | O_CREAT, 0600); + EXPECT_TRUE(fd >= 0); + close(fd); + EXPECT_EQ(0, access(path_name, F_OK)); + + std::vector<BrokerFilePermission> no_permissions; + std::vector<BrokerFilePermission> ro_permissions = { + BrokerFilePermission::ReadOnly(path_name), + BrokerFilePermission::ReadOnly(nonesuch_name)}; + + std::vector<BrokerFilePermission> rw_permissions = { + BrokerFilePermission::ReadWrite(path_name), + BrokerFilePermission::ReadWrite(nonesuch_name)}; + + std::vector<BrokerFilePermission> rwc_permissions = { + BrokerFilePermission::ReadWriteCreate(path_name), + BrokerFilePermission::ReadWriteCreate(nonesuch_name)}; + + { + // Actual file with permissions to use but command itself not allowed. + BrokerProcess open_broker(kFakeErrnoSentinel, BrokerCommandSet(), + rwc_permissions, fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Unlink(path_name)); + } + EXPECT_EQ(0, access(path_name, F_OK)); + + BrokerCommandSet command_set = MakeBrokerCommandSet({COMMAND_UNLINK}); + + { + // Nonexistent file with no permissions to see file. + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, no_permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Unlink(nonesuch_name)); + } + EXPECT_EQ(0, access(path_name, F_OK)); + + { + // Actual file with no permissions to see file. + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, no_permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Unlink(path_name)); + } + EXPECT_EQ(0, access(path_name, F_OK)); + + { + // Nonexistent file with insufficient permissions to see file. + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, ro_permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Unlink(nonesuch_name)); + } + EXPECT_EQ(0, access(path_name, F_OK)); + + { + // Actual file with insufficient permissions to see file. + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, ro_permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Unlink(path_name)); + } + EXPECT_EQ(0, access(path_name, F_OK)); + + { + // Nonexistent file with insufficient permissions to see file, case 2. + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, rw_permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Unlink(nonesuch_name)); + } + EXPECT_EQ(0, access(path_name, F_OK)); + + { + // Actual file with insufficient permissions to see file, case 2. + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, rw_permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(-kFakeErrnoSentinel, open_broker.Unlink(path_name)); + } + EXPECT_EQ(0, access(path_name, F_OK)); + + { + // Nonexistent file with permissions to see file. + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, rwc_permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_TRUE(open_broker.Unlink(nonesuch_name) < 0); + } + EXPECT_EQ(0, access(path_name, F_OK)); + + { + // Actual file with permissions to see file. + BrokerProcess open_broker(kFakeErrnoSentinel, command_set, rwc_permissions, + fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::BindRepeating(&NoOpCallback))); + EXPECT_EQ(0, open_broker.Unlink(path_name)); + } + // Confirm it was erased. + EXPECT_EQ(-1, access(path_name, F_OK)); +} + +TEST(BrokerProcess, UnlinkClient) { + TestUnlinkHelper(true); +} + +TEST(BrokerProcess, UnlinkHost) { + TestUnlinkHelper(false); +} + } // namespace syscall_broker } // namespace sandbox diff --git a/chromium/sandbox/linux/system_headers/linux_time.h b/chromium/sandbox/linux/system_headers/linux_time.h index e6c8112b864..8de1cc100d2 100644 --- a/chromium/sandbox/linux/system_headers/linux_time.h +++ b/chromium/sandbox/linux/system_headers/linux_time.h @@ -7,6 +7,14 @@ #include <time.h> +#if !defined(CPUCLOCK_CLOCK_MASK) +#define CPUCLOCK_CLOCK_MASK 3 +#endif + +#if !defined(CLOCKFD) +#define CLOCKFD 3 +#endif + #if !defined(CLOCK_REALTIME_COARSE) #define CLOCK_REALTIME_COARSE 5 #endif diff --git a/chromium/sandbox/mac/OWNERS b/chromium/sandbox/mac/OWNERS index 6e9e9684954..636437fd110 100644 --- a/chromium/sandbox/mac/OWNERS +++ b/chromium/sandbox/mac/OWNERS @@ -1,3 +1,4 @@ +kerrnel@chromium.org mark@chromium.org rsesek@chromium.org diff --git a/chromium/sandbox/win/BUILD.gn b/chromium/sandbox/win/BUILD.gn index 48f4b1e76a0..01448d4e1e8 100644 --- a/chromium/sandbox/win/BUILD.gn +++ b/chromium/sandbox/win/BUILD.gn @@ -6,7 +6,7 @@ import("//testing/test.gni") # This needs to be a static library rather than a sources set because small # portions of this are used in some contexts (like chrome_elf), and it -# doesnn't seem to dead-code strip very well. This saves 12K on chrome_elf.dll, +# doesn't seem to dead-code strip very well. This saves 12K on chrome_elf.dll, # over a source set, for example. static_library("sandbox") { sources = [ diff --git a/chromium/sandbox/win/OWNERS b/chromium/sandbox/win/OWNERS index 4a33d017759..da58563c3d9 100644 --- a/chromium/sandbox/win/OWNERS +++ b/chromium/sandbox/win/OWNERS @@ -1,5 +1,7 @@ -cpu@chromium.org forshaw@chromium.org jschuh@chromium.org pennymac@chromium.org wfh@chromium.org + +# TEAM: security-dev@chromium.org +# COMPONENT: Internals>Sandbox diff --git a/chromium/sandbox/win/src/app_container_test.cc b/chromium/sandbox/win/src/app_container_test.cc index 06ca7885221..7167354b144 100644 --- a/chromium/sandbox/win/src/app_container_test.cc +++ b/chromium/sandbox/win/src/app_container_test.cc @@ -4,24 +4,187 @@ #include <windows.h> -#include "base/strings/string16.h" +#include <sddl.h> + +#include <memory> +#include <string> +#include <vector> + +#include "base/rand_util.h" +#include "base/strings/stringprintf.h" #include "base/win/scoped_handle.h" +#include "base/win/scoped_process_information.h" #include "base/win/windows_version.h" +#include "sandbox/win/src/app_container_profile.h" #include "sandbox/win/src/sync_policy_test.h" +#include "sandbox/win/src/win_utils.h" +#include "sandbox/win/tests/common/controller.h" +#include "sandbox/win/tests/common/test_utils.h" #include "testing/gtest/include/gtest/gtest.h" +namespace sandbox { + namespace { const wchar_t kAppContainerSid[] = L"S-1-15-2-3251537155-1984446955-2931258699-841473695-1938553385-" L"924012148-2839372144"; +std::wstring GenerateRandomPackageName() { + return base::StringPrintf(L"%016lX%016lX", base::RandUint64(), + base::RandUint64()); +} + +const char* TokenTypeToName(TOKEN_TYPE token_type) { + return token_type == ::TokenPrimary ? "Primary Token" : "Impersonation Token"; +} + +void CheckToken(HANDLE token, + TOKEN_TYPE token_type, + PSECURITY_CAPABILITIES security_capabilities, + BOOL restricted) { + ASSERT_EQ(restricted, ::IsTokenRestricted(token)) + << TokenTypeToName(token_type); + + DWORD appcontainer; + DWORD return_length; + ASSERT_TRUE(::GetTokenInformation(token, ::TokenIsAppContainer, &appcontainer, + sizeof(appcontainer), &return_length)) + << TokenTypeToName(token_type); + ASSERT_TRUE(appcontainer) << TokenTypeToName(token_type); + TOKEN_TYPE token_type_real; + ASSERT_TRUE(::GetTokenInformation(token, ::TokenType, &token_type_real, + sizeof(token_type_real), &return_length)) + << TokenTypeToName(token_type); + ASSERT_EQ(token_type_real, token_type) << TokenTypeToName(token_type); + if (token_type == ::TokenImpersonation) { + SECURITY_IMPERSONATION_LEVEL imp_level; + ASSERT_TRUE(::GetTokenInformation(token, ::TokenImpersonationLevel, + &imp_level, sizeof(imp_level), + &return_length)) + << TokenTypeToName(token_type); + ASSERT_EQ(imp_level, ::SecurityImpersonation) + << TokenTypeToName(token_type); + } + + std::unique_ptr<Sid> package_sid; + ASSERT_TRUE(GetTokenAppContainerSid(token, &package_sid)) + << TokenTypeToName(token_type); + EXPECT_TRUE(::EqualSid(security_capabilities->AppContainerSid, + package_sid->GetPSID())) + << TokenTypeToName(token_type); + + std::vector<SidAndAttributes> capabilities; + ASSERT_TRUE(GetTokenGroups(token, ::TokenCapabilities, &capabilities)) + << TokenTypeToName(token_type); + + ASSERT_EQ(capabilities.size(), security_capabilities->CapabilityCount) + << TokenTypeToName(token_type); + for (size_t index = 0; index < capabilities.size(); ++index) { + EXPECT_EQ(capabilities[index].GetAttributes(), + security_capabilities->Capabilities[index].Attributes) + << TokenTypeToName(token_type); + EXPECT_TRUE(::EqualSid(capabilities[index].GetPSID(), + security_capabilities->Capabilities[index].Sid)) + << TokenTypeToName(token_type); + } +} + +void CheckProcessToken(HANDLE process, + PSECURITY_CAPABILITIES security_capabilities, + bool restricted) { + HANDLE token_handle; + ASSERT_TRUE(::OpenProcessToken(process, TOKEN_ALL_ACCESS, &token_handle)); + base::win::ScopedHandle token(token_handle); + CheckToken(token_handle, ::TokenPrimary, security_capabilities, restricted); +} + +void CheckThreadToken(HANDLE thread, + PSECURITY_CAPABILITIES security_capabilities, + bool restricted) { + HANDLE token_handle; + ASSERT_TRUE(::OpenThreadToken(thread, TOKEN_ALL_ACCESS, TRUE, &token_handle)); + base::win::ScopedHandle token(token_handle); + CheckToken(token_handle, ::TokenImpersonation, security_capabilities, + restricted); +} + +// Check for LPAC using an access check. We could query for a security attribute +// but that's undocumented and has the potential to change. +void CheckLpacToken(HANDLE process) { + HANDLE token_handle; + ASSERT_TRUE(::OpenProcessToken(process, TOKEN_ALL_ACCESS, &token_handle)); + base::win::ScopedHandle token(token_handle); + ASSERT_TRUE( + ::DuplicateToken(token.Get(), ::SecurityImpersonation, &token_handle)); + token.Set(token_handle); + PSECURITY_DESCRIPTOR security_desc_ptr; + // AC is AllPackages, S-1-15-2-2 is AllRestrictedPackages. An LPAC token + // will get granted access of 2, where as a normal AC token will get 3. + ASSERT_TRUE(::ConvertStringSecurityDescriptorToSecurityDescriptor( + L"O:SYG:SYD:(A;;0x3;;;WD)(A;;0x1;;;AC)(A;;0x2;;;S-1-15-2-2)", + SDDL_REVISION_1, &security_desc_ptr, nullptr)); + std::unique_ptr<void, LocalFreeDeleter> security_desc(security_desc_ptr); + GENERIC_MAPPING generic_mapping = {}; + PRIVILEGE_SET priv_set = {}; + DWORD priv_set_length = sizeof(PRIVILEGE_SET); + DWORD granted_access; + BOOL access_status; + ASSERT_TRUE(::AccessCheck(security_desc_ptr, token.Get(), MAXIMUM_ALLOWED, + &generic_mapping, &priv_set, &priv_set_length, + &granted_access, &access_status)); + ASSERT_TRUE(access_status); + ASSERT_EQ(DWORD{2}, granted_access); +} + +class AppContainerProfileTest : public ::testing::Test { + public: + void SetUp() override { + if (base::win::GetVersion() < base::win::VERSION_WIN8) + return; + package_name_ = GenerateRandomPackageName(); + broker_services_ = GetBroker(); + profile_ = AppContainerProfile::Create(package_name_.c_str(), L"Name", + L"Description"); + policy_ = broker_services_->CreatePolicy(); + policy_->SetAppContainerProfile(profile_.get()); + } + + void TearDown() override { + if (scoped_process_info_.IsValid()) + ::TerminateProcess(scoped_process_info_.process_handle(), 0); + if (profile_) + AppContainerProfile::Delete(package_name_.c_str()); + } + + protected: + void CreateProcess() { + // Get the path to the sandboxed app. + wchar_t prog_name[MAX_PATH] = {}; + ASSERT_NE(DWORD{0}, ::GetModuleFileNameW(nullptr, prog_name, MAX_PATH)); + + PROCESS_INFORMATION process_info = {}; + ResultCode last_warning = SBOX_ALL_OK; + DWORD last_error = 0; + ResultCode result = broker_services_->SpawnTarget( + prog_name, prog_name, policy_, &last_warning, &last_error, + &process_info); + ASSERT_EQ(SBOX_ALL_OK, result) << "Last Error: " << last_error; + scoped_process_info_.Set(process_info); + } + + std::wstring package_name_; + BrokerServices* broker_services_; + scoped_refptr<AppContainerProfile> profile_; + scoped_refptr<TargetPolicy> policy_; + base::win::ScopedProcessInformation scoped_process_info_; +}; + } // namespace -namespace sandbox { TEST(AppContainerTest, DenyOpenEventForLowBox) { - if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8) + if (base::win::GetVersion() < base::win::VERSION_WIN8) return; TestRunner runner(JOB_UNPROTECTED, USER_UNPROTECTED, USER_UNPROTECTED); @@ -30,7 +193,7 @@ TEST(AppContainerTest, DenyOpenEventForLowBox) { // Run test once, this ensures the app container directory exists, we // ignore the result. runner.RunTest(L"Event_Open f test"); - base::string16 event_name = L"AppContainerNamedObjects\\"; + std::wstring event_name = L"AppContainerNamedObjects\\"; event_name += kAppContainerSid; event_name += L"\\test"; @@ -41,7 +204,120 @@ TEST(AppContainerTest, DenyOpenEventForLowBox) { EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_Open f test")); } -// TODO(shrikant): Please add some tests to prove usage of lowbox token like -// socket connection to local server in lock down mode. +TEST_F(AppContainerProfileTest, CheckIncompatibleOptions) { + if (!profile_) + return; + EXPECT_EQ(SBOX_ERROR_BAD_PARAMS, + policy_->SetIntegrityLevel(INTEGRITY_LEVEL_UNTRUSTED)); + EXPECT_EQ(SBOX_ERROR_BAD_PARAMS, policy_->SetLowBox(kAppContainerSid)); +} + +TEST_F(AppContainerProfileTest, NoCapabilities) { + if (!profile_) + return; + + policy_->SetTokenLevel(USER_UNPROTECTED, USER_UNPROTECTED); + policy_->SetJobLevel(JOB_NONE, 0); + + CreateProcess(); + auto security_capabilities = profile_->GetSecurityCapabilities(); + + CheckProcessToken(scoped_process_info_.process_handle(), + security_capabilities.get(), FALSE); + CheckThreadToken(scoped_process_info_.thread_handle(), + security_capabilities.get(), FALSE); +} + +TEST_F(AppContainerProfileTest, NoCapabilitiesRestricted) { + if (!profile_) + return; + + policy_->SetTokenLevel(USER_LOCKDOWN, USER_RESTRICTED_SAME_ACCESS); + policy_->SetJobLevel(JOB_NONE, 0); + + CreateProcess(); + auto security_capabilities = profile_->GetSecurityCapabilities(); + + CheckProcessToken(scoped_process_info_.process_handle(), + security_capabilities.get(), TRUE); + CheckThreadToken(scoped_process_info_.thread_handle(), + security_capabilities.get(), TRUE); +} + +TEST_F(AppContainerProfileTest, WithCapabilities) { + if (!profile_) + return; + + profile_->AddCapability(kInternetClient); + profile_->AddCapability(kInternetClientServer); + policy_->SetTokenLevel(USER_UNPROTECTED, USER_UNPROTECTED); + policy_->SetJobLevel(JOB_NONE, 0); + + CreateProcess(); + auto security_capabilities = profile_->GetSecurityCapabilities(); + + CheckProcessToken(scoped_process_info_.process_handle(), + security_capabilities.get(), FALSE); + CheckThreadToken(scoped_process_info_.thread_handle(), + security_capabilities.get(), FALSE); +} + +TEST_F(AppContainerProfileTest, WithCapabilitiesRestricted) { + if (!profile_) + return; + + profile_->AddCapability(kInternetClient); + profile_->AddCapability(kInternetClientServer); + policy_->SetTokenLevel(USER_LOCKDOWN, USER_RESTRICTED_SAME_ACCESS); + policy_->SetJobLevel(JOB_NONE, 0); + + CreateProcess(); + auto security_capabilities = profile_->GetSecurityCapabilities(); + + CheckProcessToken(scoped_process_info_.process_handle(), + security_capabilities.get(), TRUE); + CheckThreadToken(scoped_process_info_.thread_handle(), + security_capabilities.get(), TRUE); +} + +TEST_F(AppContainerProfileTest, WithImpersonationCapabilities) { + if (!profile_) + return; + + profile_->AddCapability(kInternetClient); + profile_->AddCapability(kInternetClientServer); + profile_->AddImpersonationCapability(kPrivateNetworkClientServer); + profile_->AddImpersonationCapability(kPicturesLibrary); + policy_->SetTokenLevel(USER_UNPROTECTED, USER_UNPROTECTED); + policy_->SetJobLevel(JOB_NONE, 0); + + CreateProcess(); + auto security_capabilities = profile_->GetSecurityCapabilities(); + + CheckProcessToken(scoped_process_info_.process_handle(), + security_capabilities.get(), FALSE); + SecurityCapabilities impersonation_security_capabilities( + profile_->GetPackageSid(), profile_->GetImpersonationCapabilities()); + CheckThreadToken(scoped_process_info_.thread_handle(), + &impersonation_security_capabilities, FALSE); +} + +TEST_F(AppContainerProfileTest, NoCapabilitiesLPAC) { + if (base::win::GetVersion() < base::win::VERSION_WIN10_RS1) + return; + + profile_->SetEnableLowPrivilegeAppContainer(true); + policy_->SetTokenLevel(USER_UNPROTECTED, USER_UNPROTECTED); + policy_->SetJobLevel(JOB_NONE, 0); + + CreateProcess(); + auto security_capabilities = profile_->GetSecurityCapabilities(); + + CheckProcessToken(scoped_process_info_.process_handle(), + security_capabilities.get(), FALSE); + CheckThreadToken(scoped_process_info_.thread_handle(), + security_capabilities.get(), FALSE); + CheckLpacToken(scoped_process_info_.process_handle()); +} } // namespace sandbox diff --git a/chromium/sandbox/win/src/broker_services.cc b/chromium/sandbox/win/src/broker_services.cc index 525531bc520..ab899956a88 100644 --- a/chromium/sandbox/win/src/broker_services.cc +++ b/chromium/sandbox/win/src/broker_services.cc @@ -18,6 +18,7 @@ #include "base/win/scoped_process_information.h" #include "base/win/startup_information.h" #include "base/win/windows_version.h" +#include "sandbox/win/src/app_container_profile.h" #include "sandbox/win/src/process_mitigations.h" #include "sandbox/win/src/sandbox.h" #include "sandbox/win/src/sandbox_policy_base.h" @@ -367,6 +368,20 @@ ResultCode BrokerServicesBase::SpawnTarget(const wchar_t* exe_path, if (inherited_handle_list.size()) ++attribute_count; + scoped_refptr<AppContainerProfile> profile = + policy_base->GetAppContainerProfile(); + if (profile) { + if (base::win::GetVersion() < base::win::VERSION_WIN8) + return SBOX_ERROR_BAD_PARAMS; + ++attribute_count; + if (profile->GetEnableLowPrivilegeAppContainer()) { + // LPAC first supported in RS1. + if (base::win::GetVersion() < base::win::VERSION_WIN10_RS1) + return SBOX_ERROR_BAD_PARAMS; + ++attribute_count; + } + } + if (!startup_info.InitializeProcThreadAttributeList(attribute_count)) return SBOX_ERROR_PROC_THREAD_ATTRIBUTES; @@ -401,6 +416,28 @@ ResultCode BrokerServicesBase::SpawnTarget(const wchar_t* exe_path, inherit_handles = true; } + // Declared here to ensure they stay in scope until after process creation. + std::unique_ptr<SecurityCapabilities> security_capabilities; + DWORD all_applications_package_policy = + PROCESS_CREATION_ALL_APPLICATION_PACKAGES_OPT_OUT; + + if (profile) { + security_capabilities = profile->GetSecurityCapabilities(); + if (!startup_info.UpdateProcThreadAttribute( + PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES, + security_capabilities.get(), sizeof(SECURITY_CAPABILITIES))) { + return SBOX_ERROR_PROC_THREAD_ATTRIBUTES; + } + if (profile->GetEnableLowPrivilegeAppContainer()) { + if (!startup_info.UpdateProcThreadAttribute( + PROC_THREAD_ATTRIBUTE_ALL_APPLICATION_PACKAGES_POLICY, + &all_applications_package_policy, + sizeof(all_applications_package_policy))) { + return SBOX_ERROR_PROC_THREAD_ATTRIBUTES; + } + } + } + // Construct the thread pool here in case it is expensive. // The thread pool is shared by all the targets if (!thread_pool_) @@ -409,9 +446,10 @@ ResultCode BrokerServicesBase::SpawnTarget(const wchar_t* exe_path, // Create the TargetProcess object and spawn the target suspended. Note that // Brokerservices does not own the target object. It is owned by the Policy. base::win::ScopedProcessInformation process_info; - TargetProcess* target = - new TargetProcess(std::move(initial_token), std::move(lockdown_token), - job.Get(), thread_pool_.get()); + TargetProcess* target = new TargetProcess( + std::move(initial_token), std::move(lockdown_token), job.Get(), + thread_pool_.get(), + profile ? profile->GetImpersonationCapabilities() : std::vector<Sid>()); result = target->Create(exe_path, command_line, inherit_handles, startup_info, &process_info, last_error); diff --git a/chromium/sandbox/win/src/crosscall_server.cc b/chromium/sandbox/win/src/crosscall_server.cc index 0125a52c457..15cfa5f4ea1 100644 --- a/chromium/sandbox/win/src/crosscall_server.cc +++ b/chromium/sandbox/win/src/crosscall_server.cc @@ -16,6 +16,10 @@ #include "sandbox/win/src/crosscall_client.h" #include "sandbox/win/src/crosscall_params.h" +// See comment in atomicops.h. This is needed any time windows.h is included +// after atomicops.h. +#undef MemoryBarrier + // This code performs the ipc message validation. Potential security flaws // on the ipc are likelier to be found in this code than in the rest of // the ipc code. diff --git a/chromium/sandbox/win/src/sandbox_policy.h b/chromium/sandbox/win/src/sandbox_policy.h index 29f8705bff7..4a110fa9d93 100644 --- a/chromium/sandbox/win/src/sandbox_policy.h +++ b/chromium/sandbox/win/src/sandbox_policy.h @@ -14,6 +14,8 @@ namespace sandbox { +class AppContainerProfile; + class TargetPolicy { public: // Windows subsystems that can have specific rules. @@ -251,6 +253,9 @@ class TargetPolicy { // Enable OPM API emulation when in Win32k lockdown. virtual bool GetEnableOPMRedirection() = 0; + // Configure policy to use an AppContainer profile. + virtual ResultCode SetAppContainerProfile(AppContainerProfile* profile) = 0; + protected: ~TargetPolicy() {} }; diff --git a/chromium/sandbox/win/src/sandbox_policy_base.cc b/chromium/sandbox/win/src/sandbox_policy_base.cc index 867a549651b..471fc75a325 100644 --- a/chromium/sandbox/win/src/sandbox_policy_base.cc +++ b/chromium/sandbox/win/src/sandbox_policy_base.cc @@ -267,6 +267,8 @@ void PolicyBase::DestroyAlternateDesktop() { } ResultCode PolicyBase::SetIntegrityLevel(IntegrityLevel integrity_level) { + if (_app_container_profile) + return SBOX_ERROR_BAD_PARAMS; integrity_level_ = integrity_level; return SBOX_ALL_OK; } @@ -282,11 +284,11 @@ ResultCode PolicyBase::SetDelayedIntegrityLevel( } ResultCode PolicyBase::SetLowBox(const wchar_t* sid) { - if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8) + if (base::win::GetVersion() < base::win::VERSION_WIN8) return SBOX_ERROR_UNSUPPORTED; DCHECK(sid); - if (lowbox_sid_) + if (lowbox_sid_ || _app_container_profile) return SBOX_ERROR_BAD_PARAMS; if (!ConvertStringSidToSid(sid, &lowbox_sid_)) @@ -594,6 +596,23 @@ bool PolicyBase::GetEnableOPMRedirection() { return enable_opm_redirection_; } +ResultCode PolicyBase::SetAppContainerProfile(AppContainerProfile* profile) { + if (base::win::GetVersion() < base::win::VERSION_WIN8) + return SBOX_ERROR_UNSUPPORTED; + + DCHECK(profile); + if (lowbox_sid_ || _app_container_profile || + integrity_level_ != INTEGRITY_LEVEL_LAST) + return SBOX_ERROR_BAD_PARAMS; + + _app_container_profile = profile; + return SBOX_ALL_OK; +} + +scoped_refptr<AppContainerProfile> PolicyBase::GetAppContainerProfile() { + return _app_container_profile; +} + ResultCode PolicyBase::SetupAllInterceptions(TargetProcess* target) { InterceptionManager manager(target, relaxed_interceptions_); diff --git a/chromium/sandbox/win/src/sandbox_policy_base.h b/chromium/sandbox/win/src/sandbox_policy_base.h index 61a66419ada..fddea2b2e19 100644 --- a/chromium/sandbox/win/src/sandbox_policy_base.h +++ b/chromium/sandbox/win/src/sandbox_policy_base.h @@ -19,6 +19,7 @@ #include "base/process/launch.h" #include "base/strings/string16.h" #include "base/win/scoped_handle.h" +#include "sandbox/win/src/app_container_profile.h" #include "sandbox/win/src/crosscall_server.h" #include "sandbox/win/src/handle_closer.h" #include "sandbox/win/src/ipc_tags.h" @@ -72,6 +73,7 @@ class PolicyBase final : public TargetPolicy { void SetLockdownDefaultDacl() override; void SetEnableOPMRedirection() override; bool GetEnableOPMRedirection() override; + ResultCode SetAppContainerProfile(AppContainerProfile* profile) override; // Creates a Job object with the level specified in a previous call to // SetJobLevel(). @@ -100,6 +102,8 @@ class PolicyBase final : public TargetPolicy { HANDLE GetStdoutHandle(); HANDLE GetStderrHandle(); + scoped_refptr<AppContainerProfile> GetAppContainerProfile(); + // Returns the list of handles being shared with the target process. const base::HandlesToInheritVector& GetHandlesBeingShared(); @@ -170,6 +174,8 @@ class PolicyBase final : public TargetPolicy { base::HandlesToInheritVector handles_to_share_; bool enable_opm_redirection_; + scoped_refptr<AppContainerProfile> _app_container_profile; + DISALLOW_COPY_AND_ASSIGN(PolicyBase); }; diff --git a/chromium/sandbox/win/src/service_resolver_unittest.cc b/chromium/sandbox/win/src/service_resolver_unittest.cc index 3d85e05eeb8..30b13c4a5ef 100644 --- a/chromium/sandbox/win/src/service_resolver_unittest.cc +++ b/chromium/sandbox/win/src/service_resolver_unittest.cc @@ -19,17 +19,30 @@ namespace { +class ResolverThunkTest { + public: + virtual ~ResolverThunkTest() {} + + virtual sandbox::ServiceResolverThunk* resolver() = 0; + + // Sets the interception target to the desired address. + void set_target(void* target) { fake_target_ = target; } + + protected: + // Holds the address of the fake target. + void* fake_target_; +}; + // This is the concrete resolver used to perform service-call type functions // inside ntdll.dll. template <typename T> -class ResolverThunkTest : public T { +class ResolverThunkTestImpl : public T, public ResolverThunkTest { public: // The service resolver needs a child process to write to. - explicit ResolverThunkTest(bool relaxed) + explicit ResolverThunkTestImpl(bool relaxed) : T(::GetCurrentProcess(), relaxed) {} - // Sets the interception target to the desired address. - void set_target(void* target) { fake_target_ = target; } + sandbox::ServiceResolverThunk* resolver() { return this; } protected: // Overrides Resolver::Init @@ -51,20 +64,18 @@ class ResolverThunkTest : public T { return ret; }; - private: - // Holds the address of the fake target. - void* fake_target_; - - DISALLOW_COPY_AND_ASSIGN(ResolverThunkTest); + DISALLOW_COPY_AND_ASSIGN(ResolverThunkTestImpl); }; -typedef ResolverThunkTest<sandbox::ServiceResolverThunk> WinXpResolverTest; +typedef ResolverThunkTestImpl<sandbox::ServiceResolverThunk> WinXpResolverTest; #if !defined(_WIN64) -typedef ResolverThunkTest<sandbox::Win8ResolverThunk> Win8ResolverTest; -typedef ResolverThunkTest<sandbox::Wow64ResolverThunk> Wow64ResolverTest; -typedef ResolverThunkTest<sandbox::Wow64W8ResolverThunk> Wow64W8ResolverTest; -typedef ResolverThunkTest<sandbox::Wow64W10ResolverThunk> Wow64W10ResolverTest; +typedef ResolverThunkTestImpl<sandbox::Win8ResolverThunk> Win8ResolverTest; +typedef ResolverThunkTestImpl<sandbox::Wow64ResolverThunk> Wow64ResolverTest; +typedef ResolverThunkTestImpl<sandbox::Wow64W8ResolverThunk> + Wow64W8ResolverTest; +typedef ResolverThunkTestImpl<sandbox::Wow64W10ResolverThunk> + Wow64W10ResolverTest; #endif const BYTE kJump32 = 0xE9; @@ -92,7 +103,7 @@ void CheckJump(void* source, void* target) { NTSTATUS PatchNtdllWithResolver(const char* function, bool relaxed, - sandbox::ServiceResolverThunk* resolver) { + ResolverThunkTest* thunk_test) { HMODULE ntdll_base = ::GetModuleHandle(L"ntdll.dll"); EXPECT_TRUE(ntdll_base); @@ -104,8 +115,9 @@ NTSTATUS PatchNtdllWithResolver(const char* function, BYTE service[50]; memcpy(service, target, sizeof(service)); - static_cast<WinXpResolverTest*>(resolver)->set_target(service); + thunk_test->set_target(service); + sandbox::ServiceResolverThunk* resolver = thunk_test->resolver(); // Any pointer will do as an interception_entry_point void* function_entry = resolver; size_t thunk_size = resolver->GetThunkSize(); @@ -134,32 +146,29 @@ NTSTATUS PatchNtdllWithResolver(const char* function, return ret; } -sandbox::ServiceResolverThunk* GetTestResolver(bool relaxed) { +std::unique_ptr<ResolverThunkTest> GetTestResolver(bool relaxed) { #if defined(_WIN64) - return new WinXpResolverTest(relaxed); + return std::make_unique<WinXpResolverTest>(relaxed); #else base::win::OSInfo* os_info = base::win::OSInfo::GetInstance(); if (os_info->wow64_status() == base::win::OSInfo::WOW64_ENABLED) { if (os_info->version() >= base::win::VERSION_WIN10) - return new Wow64W10ResolverTest(relaxed); + return std::make_unique<Wow64W10ResolverTest>(relaxed); if (os_info->version() >= base::win::VERSION_WIN8) - return new Wow64W8ResolverTest(relaxed); - return new Wow64ResolverTest(relaxed); + return std::make_unique<Wow64W8ResolverTest>(relaxed); + return std::make_unique<Wow64ResolverTest>(relaxed); } if (os_info->version() >= base::win::VERSION_WIN8) - return new Win8ResolverTest(relaxed); + return std::make_unique<Win8ResolverTest>(relaxed); - return new WinXpResolverTest(relaxed); + return std::make_unique<WinXpResolverTest>(relaxed); #endif } NTSTATUS PatchNtdll(const char* function, bool relaxed) { - sandbox::ServiceResolverThunk* resolver = GetTestResolver(relaxed); - - NTSTATUS ret = PatchNtdllWithResolver(function, relaxed, resolver); - delete resolver; - return ret; + std::unique_ptr<ResolverThunkTest> thunk_test = GetTestResolver(relaxed); + return PatchNtdllWithResolver(function, relaxed, thunk_test.get()); } TEST(ServiceResolverTest, PatchesServices) { @@ -210,27 +219,26 @@ TEST(ServiceResolverTest, PatchesPatchedServices) { TEST(ServiceResolverTest, MultiplePatchedServices) { // We don't support "relaxed mode" for Win64 apps. #if !defined(_WIN64) - sandbox::ServiceResolverThunk* resolver = GetTestResolver(true); - NTSTATUS ret = PatchNtdllWithResolver("NtClose", true, resolver); + std::unique_ptr<ResolverThunkTest> thunk_test = GetTestResolver(true); + NTSTATUS ret = PatchNtdllWithResolver("NtClose", true, thunk_test.get()); EXPECT_EQ(STATUS_SUCCESS, ret) << "NtClose, last error: " << ::GetLastError(); - ret = PatchNtdllWithResolver("NtCreateFile", true, resolver); + ret = PatchNtdllWithResolver("NtCreateFile", true, thunk_test.get()); EXPECT_EQ(STATUS_SUCCESS, ret) << "NtCreateFile, last error: " << ::GetLastError(); - ret = PatchNtdllWithResolver("NtCreateMutant", true, resolver); + ret = PatchNtdllWithResolver("NtCreateMutant", true, thunk_test.get()); EXPECT_EQ(STATUS_SUCCESS, ret) << "NtCreateMutant, last error: " << ::GetLastError(); - ret = PatchNtdllWithResolver("NtMapViewOfSection", true, resolver); + ret = PatchNtdllWithResolver("NtMapViewOfSection", true, thunk_test.get()); EXPECT_EQ(STATUS_SUCCESS, ret) << "NtMapViewOfSection, last error: " << ::GetLastError(); - delete resolver; #endif } TEST(ServiceResolverTest, LocalPatchesAllowed) { - sandbox::ServiceResolverThunk* resolver = GetTestResolver(true); + std::unique_ptr<ResolverThunkTest> thunk_test = GetTestResolver(true); HMODULE ntdll_base = ::GetModuleHandle(L"ntdll.dll"); ASSERT_TRUE(ntdll_base); @@ -242,8 +250,9 @@ TEST(ServiceResolverTest, LocalPatchesAllowed) { BYTE service[50]; memcpy(service, target, sizeof(service)); - static_cast<WinXpResolverTest*>(resolver)->set_target(service); + thunk_test->set_target(service); + sandbox::ServiceResolverThunk* resolver = thunk_test->resolver(); // Any pointer will do as an interception_entry_point void* function_entry = resolver; size_t thunk_size = resolver->GetThunkSize(); diff --git a/chromium/sandbox/win/src/target_process.cc b/chromium/sandbox/win/src/target_process.cc index 4f3e103352d..72b3e1bd382 100644 --- a/chromium/sandbox/win/src/target_process.cc +++ b/chromium/sandbox/win/src/target_process.cc @@ -9,18 +9,25 @@ #include <memory> #include <utility> +#include <vector> #include "base/macros.h" #include "base/memory/free_deleter.h" +#include "base/numerics/safe_conversions.h" #include "base/win/startup_information.h" #include "base/win/windows_version.h" #include "sandbox/win/src/crosscall_client.h" #include "sandbox/win/src/crosscall_server.h" #include "sandbox/win/src/policy_low_level.h" +#include "sandbox/win/src/restricted_token_utils.h" #include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/security_capabilities.h" #include "sandbox/win/src/sharedmem_ipc_server.h" +#include "sandbox/win/src/sid.h" #include "sandbox/win/src/win_utils.h" +namespace sandbox { + namespace { void CopyPolicyToTarget(const void* source, size_t size, void* dest) { @@ -41,9 +48,53 @@ void CopyPolicyToTarget(const void* source, size_t size, void* dest) { } } -} // namespace +bool GetTokenAppContainerSid(HANDLE token_handle, + std::unique_ptr<Sid>* app_container_sid) { + std::vector<char> app_container_info(sizeof(TOKEN_APPCONTAINER_INFORMATION) + + SECURITY_MAX_SID_SIZE); + DWORD return_length; + + if (!::GetTokenInformation( + token_handle, TokenAppContainerSid, app_container_info.data(), + base::checked_cast<DWORD>(app_container_info.size()), + &return_length)) { + return false; + } -namespace sandbox { + PTOKEN_APPCONTAINER_INFORMATION info = + reinterpret_cast<PTOKEN_APPCONTAINER_INFORMATION>( + app_container_info.data()); + if (!info->TokenAppContainer) + return false; + *app_container_sid = std::unique_ptr<Sid>(new Sid(info->TokenAppContainer)); + return true; +} + +bool GetProcessAppContainerSid(HANDLE process, + std::unique_ptr<Sid>* app_container_sid) { + HANDLE token_handle; + if (!::OpenProcessToken(process, TOKEN_QUERY, &token_handle)) + return false; + base::win::ScopedHandle process_token(token_handle); + + return GetTokenAppContainerSid(process_token.Get(), app_container_sid); +} + +bool GetAppContainerImpersonationToken( + HANDLE process, + HANDLE initial_token, + const std::vector<Sid>& capabilities, + base::win::ScopedHandle* impersonation_token) { + std::unique_ptr<Sid> app_container_sid; + if (!GetProcessAppContainerSid(process, &app_container_sid)) { + return false; + } + SecurityCapabilities security_caps(*app_container_sid, capabilities); + return CreateLowBoxToken(initial_token, IMPERSONATION, &security_caps, + nullptr, 0, impersonation_token) == ERROR_SUCCESS; +} + +} // namespace SANDBOX_INTERCEPT HANDLE g_shared_section; SANDBOX_INTERCEPT size_t g_shared_IPC_size; @@ -52,7 +103,8 @@ SANDBOX_INTERCEPT size_t g_shared_policy_size; TargetProcess::TargetProcess(base::win::ScopedHandle initial_token, base::win::ScopedHandle lockdown_token, HANDLE job, - ThreadProvider* thread_pool) + ThreadProvider* thread_pool, + const std::vector<Sid>& impersonation_capabilities) // This object owns everything initialized here except thread_pool and // the job_ handle. The Job handle is closed by BrokerServices and results // eventually in a call to our dtor. @@ -60,7 +112,8 @@ TargetProcess::TargetProcess(base::win::ScopedHandle initial_token, initial_token_(std::move(initial_token)), job_(job), thread_pool_(thread_pool), - base_address_(nullptr) {} + base_address_(nullptr), + impersonation_capabilities_(impersonation_capabilities) {} TargetProcess::~TargetProcess() { // Give a chance to the process to die. In most cases the JOB_KILL_ON_CLOSE @@ -132,14 +185,20 @@ ResultCode TargetProcess::Create( } if (initial_token_.IsValid()) { + HANDLE impersonation_token = initial_token_.Get(); + base::win::ScopedHandle app_container_token; + if (GetAppContainerImpersonationToken( + process_info.process_handle(), impersonation_token, + impersonation_capabilities_, &app_container_token)) { + impersonation_token = app_container_token.Get(); + } + // Change the token of the main thread of the new process for the // impersonation token with more rights. This allows the target to start; // otherwise it will crash too early for us to help. HANDLE temp_thread = process_info.thread_handle(); - if (!::SetThreadToken(&temp_thread, initial_token_.Get())) { + if (!::SetThreadToken(&temp_thread, impersonation_token)) { *win_error = ::GetLastError(); - // It might be a security breach if we let the target run outside the job - // so kill it before it causes damage. ::TerminateProcess(process_info.process_handle(), 0); return SBOX_ERROR_SET_THREAD_TOKEN; } @@ -311,8 +370,9 @@ ResultCode TargetProcess::AssignLowBoxToken( } TargetProcess* MakeTestTargetProcess(HANDLE process, HMODULE base_address) { - TargetProcess* target = new TargetProcess( - base::win::ScopedHandle(), base::win::ScopedHandle(), nullptr, nullptr); + TargetProcess* target = + new TargetProcess(base::win::ScopedHandle(), base::win::ScopedHandle(), + nullptr, nullptr, std::vector<Sid>()); PROCESS_INFORMATION process_info = {}; process_info.hProcess = process; target->sandbox_process_info_.Set(process_info); diff --git a/chromium/sandbox/win/src/target_process.h b/chromium/sandbox/win/src/target_process.h index d4b4b27d9df..24059c970f4 100644 --- a/chromium/sandbox/win/src/target_process.h +++ b/chromium/sandbox/win/src/target_process.h @@ -11,6 +11,7 @@ #include <stdint.h> #include <memory> +#include <vector> #include "base/macros.h" #include "base/memory/free_deleter.h" @@ -30,18 +31,19 @@ class StartupInformation; namespace sandbox { class SharedMemIPCServer; +class Sid; class ThreadProvider; // TargetProcess models a target instance (child process). Objects of this // class are owned by the Policy used to create them. class TargetProcess { public: - // The constructor takes ownership of |initial_token|, |lockdown_token| - // and |lowbox_token|. + // The constructor takes ownership of |initial_token| and |lockdown_token| TargetProcess(base::win::ScopedHandle initial_token, base::win::ScopedHandle lockdown_token, HANDLE job, - ThreadProvider* thread_pool); + ThreadProvider* thread_pool, + const std::vector<Sid>& impersonation_capabilities); ~TargetProcess(); // TODO(cpu): Currently there does not seem to be a reason to implement @@ -120,6 +122,8 @@ class TargetProcess { void* base_address_; // Full name of the target executable. std::unique_ptr<wchar_t, base::FreeDeleter> exe_name_; + /// List of capability sids for use when impersonating in an AC process. + std::vector<Sid> impersonation_capabilities_; // Function used for testing. friend TargetProcess* MakeTestTargetProcess(HANDLE process, |