Add option to block the ptrace system call instead of denying it.

PiperOrigin-RevId: 451347905
Change-Id: Iaed0f6f116bca3be4e6e7009dddd4dd6267823bb
This commit is contained in:
Sandboxed API Team 2022-05-27 02:57:03 -07:00 committed by Copybara-Service
parent 65487bca39
commit 5513e560eb
6 changed files with 43 additions and 4 deletions

View File

@ -106,10 +106,16 @@ std::vector<sock_filter> Policy::GetDefaultPolicy() const {
SANDBOX2_TRACE, SANDBOX2_TRACE,
LABEL(&l, past_execveat_l), LABEL(&l, past_execveat_l),
// Forbid ptrace because it's unsafe or too risky.
LOAD_SYSCALL_NR, LOAD_SYSCALL_NR,
JEQ32(__NR_ptrace, DENY),
}; };
// Forbid ptrace because it's unsafe or too risky. The user policy can only
// block (i.e. return an error instead of killing the process) but not allow
// ptrace. This uses LOAD_SYSCALL_NR from above.
if (!user_policy_handles_ptrace_) {
policy.insert(policy.end(), {JEQ32(__NR_ptrace, DENY)});
}
// If user policy doesn't mention it, then forbid bpf because it's unsafe or // If user policy doesn't mention it, then forbid bpf because it's unsafe or
// too risky. This uses LOAD_SYSCALL_NR from above. // too risky. This uses LOAD_SYSCALL_NR from above.
if (!user_policy_handles_bpf_) { if (!user_policy_handles_bpf_) {

View File

@ -107,6 +107,7 @@ class Policy final {
// The policy set by the user. // The policy set by the user.
std::vector<sock_filter> user_policy_; std::vector<sock_filter> user_policy_;
bool user_policy_handles_bpf_ = false; bool user_policy_handles_bpf_ = false;
bool user_policy_handles_ptrace_ = false;
// Contains a list of hosts the sandboxee is allowed to connect to. // Contains a list of hosts the sandboxee is allowed to connect to.
absl::optional<AllowedHosts> allowed_hosts_; absl::optional<AllowedHosts> allowed_hosts_;

View File

@ -114,6 +114,21 @@ TEST(PolicyTest, PtraceDisallowed) {
EXPECT_THAT(result.reason_code(), Eq(__NR_ptrace)); EXPECT_THAT(result.reason_code(), Eq(__NR_ptrace));
} }
TEST(PolicyTest, PtraceBlocked) {
SKIP_SANITIZERS_AND_COVERAGE;
const std::string path = GetTestSourcePath("sandbox2/testcases/policy");
std::vector<std::string> args = {path, "8"};
Sandbox2 s2(std::make_unique<Executor>(path, args),
CreatePolicyTestPolicyBuilder()
.BlockSyscallWithErrno(__NR_ptrace, EPERM)
.BuildOrDie());
auto result = s2.Run();
// The policy binary fails with an error if the system call is *not* blocked.
ASSERT_THAT(result.final_status(), Eq(Result::OK));
}
// Test that clone(2) with flag CLONE_UNTRACED is disallowed. // Test that clone(2) with flag CLONE_UNTRACED is disallowed.
TEST(PolicyTest, CloneUntracedDisallowed) { TEST(PolicyTest, CloneUntracedDisallowed) {
SKIP_SANITIZERS_AND_COVERAGE; SKIP_SANITIZERS_AND_COVERAGE;

View File

@ -113,6 +113,9 @@ PolicyBuilder& PolicyBuilder::BlockSyscallWithErrno(uint32_t num, int error) {
if (num == __NR_bpf) { if (num == __NR_bpf) {
user_policy_handles_bpf_ = true; user_policy_handles_bpf_ = true;
} }
if (num == __NR_ptrace) {
user_policy_handles_ptrace_ = true;
}
} }
return *this; return *this;
} }
@ -929,6 +932,7 @@ absl::StatusOr<std::unique_ptr<Policy>> PolicyBuilder::TryBuild() {
overridable_policy_.begin(), overridable_policy_.begin(),
overridable_policy_.end()); overridable_policy_.end());
output->user_policy_handles_bpf_ = user_policy_handles_bpf_; output->user_policy_handles_bpf_ = user_policy_handles_bpf_;
output->user_policy_handles_ptrace_ = user_policy_handles_ptrace_;
auto pb_description = absl::make_unique<PolicyBuilderDescription>(); auto pb_description = absl::make_unique<PolicyBuilderDescription>();

View File

@ -639,6 +639,7 @@ class PolicyBuilder final {
std::vector<sock_filter> user_policy_; std::vector<sock_filter> user_policy_;
std::vector<sock_filter> overridable_policy_; std::vector<sock_filter> overridable_policy_;
bool user_policy_handles_bpf_ = false; bool user_policy_handles_bpf_ = false;
bool user_policy_handles_ptrace_ = false;
absl::flat_hash_set<uint32_t> handled_syscalls_; absl::flat_hash_set<uint32_t> handled_syscalls_;
// Error handling // Error handling

View File

@ -58,13 +58,22 @@ void TestAMD64SyscallMismatchFs() {
} }
#endif #endif
void TestPtrace() { void TestPtraceDenied() {
ptrace(PTRACE_SEIZE, getppid(), 0, 0); ptrace(PTRACE_SEIZE, getppid(), 0, 0);
printf("Syscall violation should have been discovered by now\n"); printf("Syscall violation should have been discovered by now\n");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
void TestPtraceBlocked() {
int result = ptrace(PTRACE_SEIZE, getppid(), 0, 0);
if (result != -1 || errno != EPERM) {
printf("System call should have been blocked\n");
exit(EXIT_FAILURE);
}
}
void TestCloneUntraced() { void TestCloneUntraced() {
syscall(__NR_clone, static_cast<uintptr_t>(CLONE_UNTRACED), nullptr, nullptr, syscall(__NR_clone, static_cast<uintptr_t>(CLONE_UNTRACED), nullptr, nullptr,
nullptr, static_cast<uintptr_t>(0)); nullptr, static_cast<uintptr_t>(0));
@ -113,7 +122,7 @@ int main(int argc, char* argv[]) {
break; break;
#endif #endif
case 3: case 3:
TestPtrace(); TestPtraceDenied();
break; break;
case 4: case 4:
TestCloneUntraced(); TestCloneUntraced();
@ -127,6 +136,9 @@ int main(int argc, char* argv[]) {
case 7: case 7:
TestBpfError(); TestBpfError();
break; break;
case 8:
TestPtraceBlocked();
break;
default: default:
printf("Unknown test: %d\n", testno); printf("Unknown test: %d\n", testno);
return EXIT_FAILURE; return EXIT_FAILURE;