diff --git a/sandboxed_api/BUILD.bazel b/sandboxed_api/BUILD.bazel index b618fa3..d255a21 100644 --- a/sandboxed_api/BUILD.bazel +++ b/sandboxed_api/BUILD.bazel @@ -23,9 +23,12 @@ exports_files(["LICENSE"]) cc_library( name = "config", + srcs = ["config.cc"], hdrs = ["config.h"], copts = sapi_platform_copts(), - deps = ["@com_google_absl//absl/base:config"], + deps = [ + "@com_google_absl//absl/base:config", + ], ) sapi_proto_library( diff --git a/sandboxed_api/CMakeLists.txt b/sandboxed_api/CMakeLists.txt index 6cd07b7..11eba3c 100644 --- a/sandboxed_api/CMakeLists.txt +++ b/sandboxed_api/CMakeLists.txt @@ -22,12 +22,13 @@ add_subdirectory(examples) # sandboxed_api:config add_library(sapi_config ${SAPI_LIB_TYPE} + config.cc config.h ) add_library(sapi::config ALIAS sapi_config) target_link_libraries(sapi_config PRIVATE sapi::base - INTERFACE absl::config + PUBLIC absl::config ) # sandboxed_api:proto_arg diff --git a/sandboxed_api/config.cc b/sandboxed_api/config.cc new file mode 100644 index 0000000..28b7931 --- /dev/null +++ b/sandboxed_api/config.cc @@ -0,0 +1,11 @@ +#include "sandboxed_api/config.h" + +#include + +namespace sapi { + +bool IsCoverageRun() { + return getenv("COVERAGE") != nullptr; +} + +} // namespace sapi diff --git a/sandboxed_api/config.h b/sandboxed_api/config.h index caa8966..e8c61d1 100644 --- a/sandboxed_api/config.h +++ b/sandboxed_api/config.h @@ -17,9 +17,8 @@ #include #include -#include -#include "absl/base/config.h" +#include "absl/base/config.h" // IWYU pragma: keep // GCC/Clang define __x86_64__, Visual Studio uses _M_X64 #if defined(__x86_64__) || defined(_M_X64) @@ -43,6 +42,9 @@ namespace sapi { +// Returns whether the executable running under code coverage. +bool IsCoverageRun(); + namespace cpu { // CPU architectures known to Sandbox2 diff --git a/sandboxed_api/sandbox2/policy_test.cc b/sandboxed_api/sandbox2/policy_test.cc index affd6a4..422d658 100644 --- a/sandboxed_api/sandbox2/policy_test.cc +++ b/sandboxed_api/sandbox2/policy_test.cc @@ -136,7 +136,7 @@ TEST(PolicyTest, BpfPtracePermissionDenied) { } TEST(PolicyTest, IsattyAllowed) { - SKIP_SANITIZERS_AND_COVERAGE; + SKIP_SANITIZERS; sandbox2::PolicyBuilder builder; if constexpr (sapi::host_os::IsAndroid()) { builder.DisableNamespaces().AllowDynamicStartup(); @@ -145,7 +145,8 @@ TEST(PolicyTest, IsattyAllowed) { .AllowExit() .AllowRead() .AllowWrite() - .AllowTCGETS(); + .AllowTCGETS() + .AllowLlvmCoverage(); const std::string path = GetTestSourcePath("sandbox2/testcases/policy"); std::vector args = {path, "6"}; SAPI_ASSERT_OK_AND_ASSIGN(auto policy, builder.TryBuild()); @@ -155,7 +156,7 @@ TEST(PolicyTest, IsattyAllowed) { ASSERT_THAT(result.final_status(), Eq(Result::OK)); } -std::unique_ptr MinimalTestcasePolicy() { +std::unique_ptr MinimalTestcasePolicy(absl::string_view path = "") { sandbox2::PolicyBuilder builder; if constexpr (sapi::host_os::IsAndroid()) { @@ -163,7 +164,7 @@ std::unique_ptr MinimalTestcasePolicy() { builder.DisableNamespaces(); } - builder.AllowStaticStartup().AllowExit(); + builder.AllowStaticStartup().AllowExit().AllowLlvmCoverage(); return builder.BuildOrDie(); } @@ -172,10 +173,11 @@ std::unique_ptr MinimalTestcasePolicy() { // compile static binaries, and we need to update the policy just above. TEST(MinimalTest, MinimalBinaryWorks) { SKIP_ANDROID; - SKIP_SANITIZERS_AND_COVERAGE; + SKIP_SANITIZERS; const std::string path = GetTestSourcePath("sandbox2/testcases/minimal"); std::vector args = {path}; - Sandbox2 s2(std::make_unique(path, args), MinimalTestcasePolicy()); + Sandbox2 s2(std::make_unique(path, args), + MinimalTestcasePolicy(path)); auto result = s2.Run(); ASSERT_THAT(result.final_status(), Eq(Result::OK)); @@ -184,7 +186,7 @@ TEST(MinimalTest, MinimalBinaryWorks) { // Test that we can sandbox a minimal non-static binary returning 0. TEST(MinimalTest, MinimalSharedBinaryWorks) { - SKIP_SANITIZERS_AND_COVERAGE; + SKIP_SANITIZERS; const std::string path = GetTestSourcePath("sandbox2/testcases/minimal_dynamic"); std::vector args = {path}; @@ -197,7 +199,7 @@ TEST(MinimalTest, MinimalSharedBinaryWorks) { builder.AddLibrariesForBinary(path); } - builder.AllowDynamicStartup().AllowExit(); + builder.AllowDynamicStartup().AllowExit().AllowLlvmCoverage(); auto policy = builder.BuildOrDie(); Sandbox2 s2(std::make_unique(path, args), std::move(policy)); @@ -209,7 +211,7 @@ TEST(MinimalTest, MinimalSharedBinaryWorks) { // Test that the AllowSystemMalloc helper works as expected. TEST(MallocTest, SystemMallocWorks) { - SKIP_SANITIZERS_AND_COVERAGE; + SKIP_SANITIZERS; const std::string path = GetTestSourcePath("sandbox2/testcases/malloc_system"); std::vector args = {path}; @@ -224,7 +226,10 @@ TEST(MallocTest, SystemMallocWorks) { }); } - builder.AllowStaticStartup().AllowSystemMalloc().AllowExit(); + builder.AllowStaticStartup() + .AllowSystemMalloc() + .AllowExit() + .AllowLlvmCoverage(); auto policy = builder.BuildOrDie(); Sandbox2 s2(std::make_unique(path, args), std::move(policy)); diff --git a/sandboxed_api/sandbox2/policybuilder.cc b/sandboxed_api/sandbox2/policybuilder.cc index 5ad1dce..7b6c667 100644 --- a/sandboxed_api/sandbox2/policybuilder.cc +++ b/sandboxed_api/sandbox2/policybuilder.cc @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -327,47 +328,49 @@ PolicyBuilder& PolicyBuilder::AllowSystemMalloc() { } PolicyBuilder& PolicyBuilder::AllowLlvmSanitizers() { - if constexpr (sapi::sanitizers::IsAny()) { - // *san use a custom allocator that runs mmap/unmap under the hood. For - // example: - // https://github.com/llvm/llvm-project/blob/596d534ac3524052df210be8d3c01a33b2260a42/compiler-rt/lib/asan/asan_allocator.cpp#L980 - // https://github.com/llvm/llvm-project/blob/62ec4ac90738a5f2d209ed28c822223e58aaaeb7/compiler-rt/lib/sanitizer_common/sanitizer_allocator_secondary.h#L98 - AllowMmap(); - AllowSyscall(__NR_munmap); - - // https://github.com/llvm/llvm-project/blob/4bbc3290a25c0dc26007912a96e0f77b2092ee56/compiler-rt/lib/sanitizer_common/sanitizer_stack_store.cpp#L293 - AddPolicyOnSyscall(__NR_mprotect, - { - ARG_32(2), - BPF_STMT(BPF_AND | BPF_ALU | BPF_K, - ~uint32_t{PROT_READ | PROT_WRITE}), - JEQ32(PROT_NONE, ALLOW), - }); - - AddPolicyOnSyscall(__NR_madvise, { - ARG_32(2), - JEQ32(MADV_DONTDUMP, ALLOW), - JEQ32(MADV_NOHUGEPAGE, ALLOW), - }); - // Sanitizers read from /proc. For example: - // https://github.com/llvm/llvm-project/blob/634da7a1c61ee8c173e90a841eb1f4ea03caa20b/compiler-rt/lib/sanitizer_common/sanitizer_linux.cpp#L1155 - AddDirectoryIfNamespaced("/proc"); - AllowOpen(); - // Sanitizers need pid for reports. For example: - // https://github.com/llvm/llvm-project/blob/634da7a1c61ee8c173e90a841eb1f4ea03caa20b/compiler-rt/lib/sanitizer_common/sanitizer_linux.cpp#L740 - AllowGetPIDs(); - // Sanitizers may try color output. For example: - // https://github.com/llvm/llvm-project/blob/87dd3d350c4ce0115b2cdf91d85ddd05ae2661aa/compiler-rt/lib/sanitizer_common/sanitizer_posix_libcdep.cpp#L157 - OverridableBlockSyscallWithErrno(__NR_ioctl, EPERM); - // https://github.com/llvm/llvm-project/blob/9aa39481d9eb718e872993791547053a3c1f16d5/compiler-rt/lib/sanitizer_common/sanitizer_linux_libcdep.cpp#L150 - // https://sourceware.org/git/?p=glibc.git;a=blob;f=nptl/pthread_getattr_np.c;h=de7edfa0928224eb8375e2fe894d6677570fbb3b;hb=HEAD#l188 - OverridableBlockSyscallWithErrno(__NR_sched_getaffinity, EPERM); - // https://github.com/llvm/llvm-project/blob/02c2b472b510ff55679844c087b66e7837e13dc2/compiler-rt/lib/sanitizer_common/sanitizer_linux.cpp#L434 -#ifdef __NR_readlink - OverridableBlockSyscallWithErrno(__NR_readlink, ENOENT); -#endif - OverridableBlockSyscallWithErrno(__NR_readlinkat, ENOENT); + if constexpr (!sapi::sanitizers::IsAny()) { + return *this; } + // *san use a custom allocator that runs mmap/unmap under the hood. For + // example: + // https://github.com/llvm/llvm-project/blob/596d534ac3524052df210be8d3c01a33b2260a42/compiler-rt/lib/asan/asan_allocator.cpp#L980 + // https://github.com/llvm/llvm-project/blob/62ec4ac90738a5f2d209ed28c822223e58aaaeb7/compiler-rt/lib/sanitizer_common/sanitizer_allocator_secondary.h#L98 + AllowMmap(); + AllowSyscall(__NR_munmap); + AllowSyscall(__NR_sched_yield); + + // https://github.com/llvm/llvm-project/blob/4bbc3290a25c0dc26007912a96e0f77b2092ee56/compiler-rt/lib/sanitizer_common/sanitizer_stack_store.cpp#L293 + AddPolicyOnSyscall(__NR_mprotect, + { + ARG_32(2), + BPF_STMT(BPF_AND | BPF_ALU | BPF_K, + ~uint32_t{PROT_READ | PROT_WRITE}), + JEQ32(PROT_NONE, ALLOW), + }); + + AddPolicyOnSyscall(__NR_madvise, { + ARG_32(2), + JEQ32(MADV_DONTDUMP, ALLOW), + JEQ32(MADV_NOHUGEPAGE, ALLOW), + }); + // Sanitizers read from /proc. For example: + // https://github.com/llvm/llvm-project/blob/634da7a1c61ee8c173e90a841eb1f4ea03caa20b/compiler-rt/lib/sanitizer_common/sanitizer_linux.cpp#L1155 + AddDirectoryIfNamespaced("/proc"); + AllowOpen(); + // Sanitizers need pid for reports. For example: + // https://github.com/llvm/llvm-project/blob/634da7a1c61ee8c173e90a841eb1f4ea03caa20b/compiler-rt/lib/sanitizer_common/sanitizer_linux.cpp#L740 + AllowGetPIDs(); + // Sanitizers may try color output. For example: + // https://github.com/llvm/llvm-project/blob/87dd3d350c4ce0115b2cdf91d85ddd05ae2661aa/compiler-rt/lib/sanitizer_common/sanitizer_posix_libcdep.cpp#L157 + OverridableBlockSyscallWithErrno(__NR_ioctl, EPERM); + // https://github.com/llvm/llvm-project/blob/9aa39481d9eb718e872993791547053a3c1f16d5/compiler-rt/lib/sanitizer_common/sanitizer_linux_libcdep.cpp#L150 + // https://sourceware.org/git/?p=glibc.git;a=blob;f=nptl/pthread_getattr_np.c;h=de7edfa0928224eb8375e2fe894d6677570fbb3b;hb=HEAD#l188 + OverridableBlockSyscallWithErrno(__NR_sched_getaffinity, EPERM); + // https://github.com/llvm/llvm-project/blob/02c2b472b510ff55679844c087b66e7837e13dc2/compiler-rt/lib/sanitizer_common/sanitizer_linux.cpp#L434 +#ifdef __NR_readlink + OverridableBlockSyscallWithErrno(__NR_readlink, ENOENT); +#endif + OverridableBlockSyscallWithErrno(__NR_readlinkat, ENOENT); if constexpr (sapi::sanitizers::IsASan()) { AllowSyscall(__NR_sigaltstack); } @@ -377,6 +380,37 @@ PolicyBuilder& PolicyBuilder::AllowLlvmSanitizers() { return *this; } +PolicyBuilder& PolicyBuilder::AllowLlvmCoverage() { + if (!sapi::IsCoverageRun()) { + return *this; + } + AllowStat(); + AllowGetPIDs(); + AllowOpen(); + AllowRead(); + AllowWrite(); + AllowMkdir(); + AllowSafeFcntl(); + AllowSyscalls({ + __NR_munmap, __NR_close, __NR_lseek, +#ifdef __NR__llseek + __NR__llseek, // Newer glibc on PPC +#endif + }); + AllowTcMalloc(); + AddPolicyOnMmap([](bpf_labels& labels) -> std::vector { + return { + ARG_32(2), // prot + JNE32(PROT_READ | PROT_WRITE, JUMP(&labels, mmap_end)), + ARG_32(3), // flags + JEQ32(MAP_SHARED, ALLOW), + LABEL(&labels, mmap_end), + }; + }); + AddDirectoryIfNamespaced(getenv("COVERAGE_DIR"), /*is_ro=*/false); + return *this; +} + PolicyBuilder& PolicyBuilder::AllowLimitedMadvise() { return AddPolicyOnSyscall(__NR_madvise, { ARG_32(2), diff --git a/sandboxed_api/sandbox2/policybuilder.h b/sandboxed_api/sandbox2/policybuilder.h index 6637c23..c832d00 100644 --- a/sandboxed_api/sandbox2/policybuilder.h +++ b/sandboxed_api/sandbox2/policybuilder.h @@ -239,6 +239,10 @@ class PolicyBuilder final { // all binaries. PolicyBuilder& AllowLlvmSanitizers(); + // Allows system calls typically used by the LLVM coverage. + // This method is intended as a best effort. + PolicyBuilder& AllowLlvmCoverage(); + // Appends code to allow mmap. Specifically this allows mmap and mmap2 syscall // on architectures where this syscalls exist. PolicyBuilder& AllowMmap(); diff --git a/sandboxed_api/testing.cc b/sandboxed_api/testing.cc index 9f463dc..1f3e986 100644 --- a/sandboxed_api/testing.cc +++ b/sandboxed_api/testing.cc @@ -20,14 +20,11 @@ #include "absl/strings/string_view.h" #include "sandboxed_api/config.h" #include "sandboxed_api/sandbox2/allow_all_syscalls.h" +#include "sandboxed_api/sandbox2/policybuilder.h" #include "sandboxed_api/util/path.h" namespace sapi { -bool IsCoverageRun() { - return getenv("COVERAGE") != nullptr; -} - sandbox2::PolicyBuilder CreateDefaultPermissiveTestPolicy( absl::string_view bin_path) { sandbox2::PolicyBuilder builder; @@ -35,6 +32,7 @@ sandbox2::PolicyBuilder CreateDefaultPermissiveTestPolicy( builder.DefaultAction(sandbox2::AllowAllSyscalls()); if (sapi::host_os::IsAndroid()) { builder.DisableNamespaces(); + return builder; } if (IsCoverageRun()) { builder.AddDirectory(getenv("COVERAGE_DIR"), /*is_ro=*/false); diff --git a/sandboxed_api/testing.h b/sandboxed_api/testing.h index 3d01881..e196bea 100644 --- a/sandboxed_api/testing.h +++ b/sandboxed_api/testing.h @@ -18,7 +18,7 @@ #include #include "absl/strings/string_view.h" -#include "sandboxed_api/config.h" +#include "sandboxed_api/config.h" // IWYU pragma: export #include "sandboxed_api/sandbox2/policybuilder.h" // The macro SKIP_ANDROID can be used in tests to skip running a @@ -66,10 +66,14 @@ } \ } while (0) -namespace sapi { +#define SKIP_SANITIZERS \ + do { \ + if (sapi::sanitizers::IsAny()) { \ + return; \ + } \ + } while (0) -// Returns whether the executable running under code coverage. -bool IsCoverageRun(); +namespace sapi { sandbox2::PolicyBuilder CreateDefaultPermissiveTestPolicy( absl::string_view bin_path);