Commit a88e133e authored by rickyz@google.com's avatar rickyz@google.com

Linux Sandbox: Add support for SECCOMP_RET_TRACE.

To use, make a BPF program return ErrorCode(ERR_TRACE + ret_data), where
ret_data is a 16 bit value that will be available to the tracing process
via PTRACE_GETEVENTMSG.

BUG=231000

Review URL: https://codereview.chromium.org/278583005

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@276595 0039d316-1c4b-4281-b951-d872f2087c98
parent 4695e1ff
...@@ -106,6 +106,8 @@ void CodeGen::PrintProgram(const SandboxBPF::Program& program) { ...@@ -106,6 +106,8 @@ void CodeGen::PrintProgram(const SandboxBPF::Program& program) {
fprintf(stderr, "Trap #%d\n", iter->k & SECCOMP_RET_DATA); fprintf(stderr, "Trap #%d\n", iter->k & SECCOMP_RET_DATA);
} else if ((iter->k & SECCOMP_RET_ACTION) == SECCOMP_RET_ERRNO) { } else if ((iter->k & SECCOMP_RET_ACTION) == SECCOMP_RET_ERRNO) {
fprintf(stderr, "errno = %d\n", iter->k & SECCOMP_RET_DATA); fprintf(stderr, "errno = %d\n", iter->k & SECCOMP_RET_DATA);
} else if ((iter->k & SECCOMP_RET_ACTION) == SECCOMP_RET_TRACE) {
fprintf(stderr, "Trace #%d\n", iter->k & SECCOMP_RET_DATA);
} else if (iter->k == SECCOMP_RET_ALLOW) { } else if (iter->k == SECCOMP_RET_ALLOW) {
fprintf(stderr, "Allowed\n"); fprintf(stderr, "Allowed\n");
} else { } else {
......
...@@ -18,6 +18,11 @@ ErrorCode::ErrorCode(int err) { ...@@ -18,6 +18,11 @@ ErrorCode::ErrorCode(int err) {
error_type_ = ET_SIMPLE; error_type_ = ET_SIMPLE;
break; break;
default: default:
if ((err & ~SECCOMP_RET_DATA) == ERR_TRACE) {
err_ = SECCOMP_RET_TRACE + (err & SECCOMP_RET_DATA);
error_type_ = ET_SIMPLE;
break;
}
SANDBOX_DIE("Invalid use of ErrorCode object"); SANDBOX_DIE("Invalid use of ErrorCode object");
} }
} }
......
...@@ -30,6 +30,12 @@ class SANDBOX_EXPORT ErrorCode { ...@@ -30,6 +30,12 @@ class SANDBOX_EXPORT ErrorCode {
// "errno" (see below) value instead. // "errno" (see below) value instead.
ERR_ALLOWED = 0x04000000, ERR_ALLOWED = 0x04000000,
// If the progress is being ptraced with PTRACE_O_TRACESECCOMP, then the
// tracer will be notified of a PTRACE_EVENT_SECCOMP and allowed to change
// or skip the system call. The lower 16 bits of err will be available to
// the tracer via PTRACE_GETEVENTMSG.
ERR_TRACE = 0x08000000,
// Deny the system call with a particular "errno" value. // Deny the system call with a particular "errno" value.
// N.B.: It is also possible to return "0" here. That would normally // N.B.: It is also possible to return "0" here. That would normally
// indicate success, but it won't actually run the system call. // indicate success, but it won't actually run the system call.
......
...@@ -24,6 +24,17 @@ SANDBOX_TEST(ErrorCode, ErrnoConstructor) { ...@@ -24,6 +24,17 @@ SANDBOX_TEST(ErrorCode, ErrnoConstructor) {
SandboxBPF sandbox; SandboxBPF sandbox;
ErrorCode e3 = sandbox.Trap(NULL, NULL); ErrorCode e3 = sandbox.Trap(NULL, NULL);
SANDBOX_ASSERT((e3.err() & SECCOMP_RET_ACTION) == SECCOMP_RET_TRAP); SANDBOX_ASSERT((e3.err() & SECCOMP_RET_ACTION) == SECCOMP_RET_TRAP);
uint16_t data = 0xdead;
ErrorCode e4(ErrorCode::ERR_TRACE + data);
SANDBOX_ASSERT(e4.err() == SECCOMP_RET_TRACE + data);
}
SANDBOX_DEATH_TEST(ErrorCode,
InvalidSeccompRetTrace,
DEATH_MESSAGE("Invalid use of ErrorCode object")) {
// Should die if the trace data does not fit in 16 bits.
ErrorCode e(ErrorCode::ERR_TRACE + (1 << 16));
} }
SANDBOX_TEST(ErrorCode, Trap) { SANDBOX_TEST(ErrorCode, Trap) {
......
...@@ -16,6 +16,13 @@ ...@@ -16,6 +16,13 @@
#include <asm/unistd.h> #include <asm/unistd.h>
#include <linux/filter.h> #include <linux/filter.h>
// Old Bionic versions do not have sys/user.h. The if can be removed once we no
// longer need to support these old Bionic versions.
#include <sys/cdefs.h>
#if !defined(__BIONIC__)
#include <sys/user.h>
#endif
// For audit.h // For audit.h
#ifndef EM_ARM #ifndef EM_ARM
#define EM_ARM 40 #define EM_ARM 40
...@@ -124,6 +131,17 @@ ...@@ -124,6 +131,17 @@
#define SECCOMP_ARG_LSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \ #define SECCOMP_ARG_LSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \
8*(nr) + 0) 8*(nr) + 0)
typedef user_regs_struct regs_struct;
#define SECCOMP_PT_RESULT(_regs) (_regs).eax
#define SECCOMP_PT_SYSCALL(_regs) (_regs).orig_eax
#define SECCOMP_PT_IP(_regs) (_regs).eip
#define SECCOMP_PT_PARM1(_regs) (_regs).ebx
#define SECCOMP_PT_PARM2(_regs) (_regs).ecx
#define SECCOMP_PT_PARM3(_regs) (_regs).edx
#define SECCOMP_PT_PARM4(_regs) (_regs).esi
#define SECCOMP_PT_PARM5(_regs) (_regs).edi
#define SECCOMP_PT_PARM6(_regs) (_regs).ebp
#elif defined(__x86_64__) #elif defined(__x86_64__)
#define MIN_SYSCALL 0u #define MIN_SYSCALL 0u
#define MAX_PUBLIC_SYSCALL 1024u #define MAX_PUBLIC_SYSCALL 1024u
...@@ -151,6 +169,17 @@ ...@@ -151,6 +169,17 @@
#define SECCOMP_ARG_LSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \ #define SECCOMP_ARG_LSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \
8*(nr) + 0) 8*(nr) + 0)
typedef user_regs_struct regs_struct;
#define SECCOMP_PT_RESULT(_regs) (_regs).rax
#define SECCOMP_PT_SYSCALL(_regs) (_regs).orig_rax
#define SECCOMP_PT_IP(_regs) (_regs).rip
#define SECCOMP_PT_PARM1(_regs) (_regs).rdi
#define SECCOMP_PT_PARM2(_regs) (_regs).rsi
#define SECCOMP_PT_PARM3(_regs) (_regs).rdx
#define SECCOMP_PT_PARM4(_regs) (_regs).r10
#define SECCOMP_PT_PARM5(_regs) (_regs).r8
#define SECCOMP_PT_PARM6(_regs) (_regs).r9
#elif defined(__arm__) && (defined(__thumb__) || defined(__ARM_EABI__)) #elif defined(__arm__) && (defined(__thumb__) || defined(__ARM_EABI__))
// ARM EABI includes "ARM private" system calls starting at |__ARM_NR_BASE|, // ARM EABI includes "ARM private" system calls starting at |__ARM_NR_BASE|,
// and a "ghost syscall private to the kernel", cmpxchg, // and a "ghost syscall private to the kernel", cmpxchg,
...@@ -189,6 +218,46 @@ ...@@ -189,6 +218,46 @@
#define SECCOMP_ARG_LSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \ #define SECCOMP_ARG_LSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \
8*(nr) + 0) 8*(nr) + 0)
#if defined(__BIONIC__)
// Old Bionic versions don't have sys/user.h, so we just define regs_struct
// directly. This can be removed once we no longer need to support these old
// Bionic versions.
struct regs_struct {
unsigned long uregs[18];
};
#else
typedef user_regs regs_struct;
#endif
#define REG_cpsr uregs[16]
#define REG_pc uregs[15]
#define REG_lr uregs[14]
#define REG_sp uregs[13]
#define REG_ip uregs[12]
#define REG_fp uregs[11]
#define REG_r10 uregs[10]
#define REG_r9 uregs[9]
#define REG_r8 uregs[8]
#define REG_r7 uregs[7]
#define REG_r6 uregs[6]
#define REG_r5 uregs[5]
#define REG_r4 uregs[4]
#define REG_r3 uregs[3]
#define REG_r2 uregs[2]
#define REG_r1 uregs[1]
#define REG_r0 uregs[0]
#define REG_ORIG_r0 uregs[17]
#define SECCOMP_PT_RESULT(_regs) (_regs).REG_r0
#define SECCOMP_PT_SYSCALL(_regs) (_regs).REG_r7
#define SECCOMP_PT_IP(_regs) (_regs).REG_pc
#define SECCOMP_PT_PARM1(_regs) (_regs).REG_r0
#define SECCOMP_PT_PARM2(_regs) (_regs).REG_r1
#define SECCOMP_PT_PARM3(_regs) (_regs).REG_r2
#define SECCOMP_PT_PARM4(_regs) (_regs).REG_r3
#define SECCOMP_PT_PARM5(_regs) (_regs).REG_r4
#define SECCOMP_PT_PARM6(_regs) (_regs).REG_r5
#else #else
#error Unsupported target platform #error Unsupported target platform
......
...@@ -5,7 +5,9 @@ ...@@ -5,7 +5,9 @@
#include <errno.h> #include <errno.h>
#include <pthread.h> #include <pthread.h>
#include <sched.h> #include <sched.h>
#include <signal.h>
#include <sys/prctl.h> #include <sys/prctl.h>
#include <sys/ptrace.h>
#include <sys/syscall.h> #include <sys/syscall.h>
#include <sys/time.h> #include <sys/time.h>
#include <sys/types.h> #include <sys/types.h>
...@@ -24,6 +26,7 @@ ...@@ -24,6 +26,7 @@
#include "base/logging.h" #include "base/logging.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/scoped_ptr.h" #include "base/memory/scoped_ptr.h"
#include "base/posix/eintr_wrapper.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "sandbox/linux/seccomp-bpf/bpf_tests.h" #include "sandbox/linux/seccomp-bpf/bpf_tests.h"
#include "sandbox/linux/seccomp-bpf/syscall.h" #include "sandbox/linux/seccomp-bpf/syscall.h"
...@@ -1908,6 +1911,122 @@ BPF_TEST_C(SandboxBPF, PthreadBitMask, PthreadPolicyBitMask) { ...@@ -1908,6 +1911,122 @@ BPF_TEST_C(SandboxBPF, PthreadBitMask, PthreadPolicyBitMask) {
PthreadTest(); PthreadTest();
} }
// libc might not define these even though the kernel supports it.
#ifndef PTRACE_O_TRACESECCOMP
#define PTRACE_O_TRACESECCOMP 0x00000080
#endif
#ifdef PTRACE_EVENT_SECCOMP
#define IS_SECCOMP_EVENT(status) ((status >> 16) == PTRACE_EVENT_SECCOMP)
#else
// When Debian/Ubuntu backported seccomp-bpf support into earlier kernels, they
// changed the value of PTRACE_EVENT_SECCOMP from 7 to 8, since 7 was taken by
// PTRACE_EVENT_STOP (upstream chose to renumber PTRACE_EVENT_STOP to 128). If
// PTRACE_EVENT_SECCOMP isn't defined, we have no choice but to consider both
// values here.
#define IS_SECCOMP_EVENT(status) ((status >> 16) == 7 || (status >> 16) == 8)
#endif
const uint16_t kTraceData = 0xcc;
class TraceAllPolicy : public SandboxBPFPolicy {
public:
TraceAllPolicy() {}
virtual ~TraceAllPolicy() {}
virtual ErrorCode EvaluateSyscall(SandboxBPF* sandbox_compiler,
int system_call_number) const OVERRIDE {
return ErrorCode(ErrorCode::ERR_TRACE + kTraceData);
}
private:
DISALLOW_COPY_AND_ASSIGN(TraceAllPolicy);
};
SANDBOX_TEST(SandboxBPF, DISABLE_ON_TSAN(SeccompRetTrace)) {
if (SandboxBPF::SupportsSeccompSandbox(-1) !=
sandbox::SandboxBPF::STATUS_AVAILABLE) {
return;
}
pid_t pid = fork();
BPF_ASSERT_NE(-1, pid);
if (pid == 0) {
pid_t my_pid = getpid();
BPF_ASSERT_NE(-1, ptrace(PTRACE_TRACEME, -1, NULL, NULL));
BPF_ASSERT_EQ(0, raise(SIGSTOP));
SandboxBPF sandbox;
sandbox.SetSandboxPolicy(new TraceAllPolicy);
BPF_ASSERT(sandbox.StartSandbox(SandboxBPF::PROCESS_SINGLE_THREADED));
// getpid is allowed.
BPF_ASSERT_EQ(my_pid, syscall(__NR_getpid));
// write to stdout is skipped and returns a fake value.
BPF_ASSERT_EQ(kExpectedReturnValue,
syscall(__NR_write, STDOUT_FILENO, "A", 1));
// kill is rewritten to exit(kExpectedReturnValue).
syscall(__NR_kill, my_pid, SIGKILL);
// Should not be reached.
BPF_ASSERT(false);
}
int status;
BPF_ASSERT(HANDLE_EINTR(waitpid(pid, &status, WUNTRACED)) != -1);
BPF_ASSERT(WIFSTOPPED(status));
BPF_ASSERT_NE(-1, ptrace(PTRACE_SETOPTIONS, pid, NULL,
reinterpret_cast<void*>(PTRACE_O_TRACESECCOMP)));
BPF_ASSERT_NE(-1, ptrace(PTRACE_CONT, pid, NULL, NULL));
while (true) {
BPF_ASSERT(HANDLE_EINTR(waitpid(pid, &status, 0)) != -1);
if (WIFEXITED(status) || WIFSIGNALED(status)) {
BPF_ASSERT(WIFEXITED(status));
BPF_ASSERT_EQ(kExpectedReturnValue, WEXITSTATUS(status));
break;
}
if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGTRAP ||
!IS_SECCOMP_EVENT(status)) {
BPF_ASSERT_NE(-1, ptrace(PTRACE_CONT, pid, NULL, NULL));
continue;
}
unsigned long data;
BPF_ASSERT_NE(-1, ptrace(PTRACE_GETEVENTMSG, pid, NULL, &data));
BPF_ASSERT_EQ(kTraceData, data);
regs_struct regs;
BPF_ASSERT_NE(-1, ptrace(PTRACE_GETREGS, pid, NULL, &regs));
switch (SECCOMP_PT_SYSCALL(regs)) {
case __NR_write:
// Skip writes to stdout, make it return kExpectedReturnValue. Allow
// writes to stderr so that BPF_ASSERT messages show up.
if (SECCOMP_PT_PARM1(regs) == STDOUT_FILENO) {
SECCOMP_PT_SYSCALL(regs) = -1;
SECCOMP_PT_RESULT(regs) = kExpectedReturnValue;
BPF_ASSERT_NE(-1, ptrace(PTRACE_SETREGS, pid, NULL, &regs));
}
break;
case __NR_kill:
// Rewrite to exit(kExpectedReturnValue).
SECCOMP_PT_SYSCALL(regs) = __NR_exit;
SECCOMP_PT_PARM1(regs) = kExpectedReturnValue;
BPF_ASSERT_NE(-1, ptrace(PTRACE_SETREGS, pid, NULL, &regs));
break;
default:
// Allow all other syscalls.
break;
}
BPF_ASSERT_NE(-1, ptrace(PTRACE_CONT, pid, NULL, NULL));
}
}
} // namespace } // namespace
} // namespace sandbox } // namespace sandbox
...@@ -423,10 +423,10 @@ uint32_t Verifier::EvaluateBPF(const std::vector<struct sock_filter>& program, ...@@ -423,10 +423,10 @@ uint32_t Verifier::EvaluateBPF(const std::vector<struct sock_filter>& program,
switch (r & SECCOMP_RET_ACTION) { switch (r & SECCOMP_RET_ACTION) {
case SECCOMP_RET_TRAP: case SECCOMP_RET_TRAP:
case SECCOMP_RET_ERRNO: case SECCOMP_RET_ERRNO:
case SECCOMP_RET_TRACE:
case SECCOMP_RET_ALLOW: case SECCOMP_RET_ALLOW:
break; break;
case SECCOMP_RET_KILL: // We don't ever generate this case SECCOMP_RET_KILL: // We don't ever generate this
case SECCOMP_RET_TRACE: // We don't ever generate this
case SECCOMP_RET_INVALID: // Should never show up in BPF program case SECCOMP_RET_INVALID: // Should never show up in BPF program
default: default:
*err = "Unexpected return code found in BPF program"; *err = "Unexpected return code found in BPF program";
......
...@@ -23,11 +23,21 @@ void RunPostTestsChecks() { ...@@ -23,11 +23,21 @@ void RunPostTestsChecks() {
} // namespace } // namespace
} // namespace sandbox } // namespace sandbox
#if defined(OS_ANDROID)
void UnitTestAssertHandler(const std::string& str) {
_exit(1);
}
#endif
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
#if defined(OS_ANDROID) #if defined(OS_ANDROID)
// The use of Callbacks requires an AtExitManager. // The use of Callbacks requires an AtExitManager.
base::AtExitManager exit_manager; base::AtExitManager exit_manager;
testing::InitGoogleTest(&argc, argv); testing::InitGoogleTest(&argc, argv);
// Death tests rely on LOG(FATAL) triggering an exit (the default behavior is
// SIGABRT). The normal test launcher does this at initialization, but since
// we still do not use this on Android, we must install the handler ourselves.
logging::SetLogAssertHandler(UnitTestAssertHandler);
#endif #endif
// Always go through re-execution for death tests. // Always go through re-execution for death tests.
// This makes gtest only marginally slower for us and has the // This makes gtest only marginally slower for us and has the
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment