blob: a467a6af611c22a10cc295c28bdb6c6e621b731c [file] [log] [blame]
// Copyright (c) 2010 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 <asm/unistd.h>
#include "mutex.h"
#include "sandbox_impl.h"
// This is a C++ implementation of trusted_thread.cc. Since it trusts
// the contents of the stack, it is not secure. It is intended to be
// a reference implementation. This code can be used as an aid to
// understanding what the real trusted thread does, since this code
// should be easier to read than assembly code. It can also be used
// as a test bed for changes to the trusted thread.
namespace playground {
void die(const char *msg) {
sys_write(2, msg, strlen(msg));
sys_exit_group(1);
}
#define TO_STRING_1(x) #x
#define TO_STRING(x) TO_STRING_1(x)
#define assert(expr) { \
if (!(expr)) die("Sandbox violation detected, program aborted\n" \
"assertion failed at " __FILE__ ":" TO_STRING(__LINE__) \
": " #expr "\n"); }
// Perform a syscall given an array of syscall arguments.
extern "C" long DoSyscall(unsigned long regs[7]);
asm(
".pushsection .text, \"ax\", @progbits\n"
".internal DoSyscall\n"
".global DoSyscall\n"
"DoSyscall:\n"
#if defined(__x86_64__)
"push %rdi\n"
"push %rsi\n"
"push %rdx\n"
"push %r10\n"
"push %r8\n"
"push %r9\n"
// Set up syscall arguments
"mov 0x00(%rdi), %rax\n"
// Skip 0x08 (%rdi): this comes last
"mov 0x10(%rdi), %rsi\n"
"mov 0x18(%rdi), %rdx\n"
"mov 0x20(%rdi), %r10\n"
"mov 0x28(%rdi), %r8\n"
"mov 0x30(%rdi), %r9\n"
"mov 0x08(%rdi), %rdi\n"
"syscall\n"
"pop %r9\n"
"pop %r8\n"
"pop %r10\n"
"pop %rdx\n"
"pop %rsi\n"
"pop %rdi\n"
"ret\n"
#elif defined(__i386__)
"push %ebx\n"
"push %ecx\n"
"push %edx\n"
"push %esi\n"
"push %edi\n"
"push %ebp\n"
"mov 4+24(%esp), %ecx\n"
// Set up syscall arguments
"mov 0x00(%ecx), %eax\n"
"mov 0x04(%ecx), %ebx\n"
// Skip 0x08 (%ecx): this comes last
"mov 0x0c(%ecx), %edx\n"
"mov 0x10(%ecx), %esi\n"
"mov 0x14(%ecx), %edi\n"
"mov 0x18(%ecx), %ebp\n"
"mov 0x08(%ecx), %ecx\n"
"int $0x80\n"
"pop %ebp\n"
"pop %edi\n"
"pop %esi\n"
"pop %edx\n"
"pop %ecx\n"
"pop %ebx\n"
"ret\n"
#else
#error Unsupported target platform
#endif
".popsection\n"
);
void InitCustomTLS(void *addr) {
Sandbox::SysCalls sys;
#if defined(__x86_64__)
int rc = sys.arch_prctl(ARCH_SET_GS, addr);
assert(rc == 0);
#elif defined(__i386__)
struct user_desc u;
u.entry_number = (typeof u.entry_number)-1;
u.base_addr = (long) addr;
u.limit = 0xfffff;
u.seg_32bit = 1;
u.contents = 0;
u.read_exec_only = 0;
u.limit_in_pages = 1;
u.seg_not_present = 0;
u.useable = 1;
int rc = sys.set_thread_area(&u);
assert(rc == 0);
asm volatile("movw %w0, %%fs"
: : "q"(8 * u.entry_number + 3));
#else
#error Unsupported target platform
#endif
}
void UnlockSyscallMutex(SecureMem::Args *secureMem) {
Sandbox::SysCalls sys;
// TODO(mseaborn): Use clone() to be the same as trusted_thread.cc.
int pid = sys.fork();
assert(pid >= 0);
if (pid == 0) {
int rc = sys.mprotect(secureMem, 0x1000, PROT_READ | PROT_WRITE);
assert(rc == 0);
Mutex::unlockMutex(&secureMem->syscallMutex);
sys._exit(0);
}
int status;
int rc = sys.waitpid(pid, &status, 0);
assert(rc == pid);
assert(status == 0);
}
void SaveShmgetResult(SecureMem::Args *secureMem, int shmid) {
Sandbox::SysCalls sys;
// TODO(mseaborn): Use clone() to be the same as trusted_thread.cc.
int pid = sys.fork();
assert(pid >= 0);
if (pid == 0) {
int rc = sys.mprotect(secureMem, 0x1000, PROT_READ | PROT_WRITE);
assert(rc == 0);
secureMem->shmId = shmid;
sys._exit(0);
}
int status;
int rc = sys.waitpid(pid, &status, 0);
assert(rc == pid);
assert(status == 0);
}
// Allocate a stack that is never freed.
char *AllocateStack() {
Sandbox::SysCalls sys;
int stack_size = 0x1000;
#if defined(__i386__)
void *stack = sys.mmap2(NULL, stack_size, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
#else
void *stack = sys.mmap(NULL, stack_size, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
#endif
assert(stack != MAP_FAILED);
return (char *) stack + stack_size;
}
void ReturnFromCloneSyscall(void *signal_frame) {
#if defined(__x86_64__)
asm("mov %0, %%rsp\n"
"syscall"
: : "m"(signal_frame), "a"(__NR_rt_sigreturn));
#elif defined(__i386__)
asm("mov %0, %%esp\n"
"int $0x80"
: : "m"(signal_frame), "a"(__NR_sigreturn));
#else
#error Unsupported target platform
#endif
}
int HandleNewThread(void *arg) {
SecureMem::Args *secureMem = (SecureMem::Args *) arg;
// Copy arg2 before the lock on secureMem is released.
void *signal_frame = secureMem->arg2;
CreateReferenceTrustedThread(secureMem->newSecureMem);
ReturnFromCloneSyscall(signal_frame);
return 0;
}
struct ThreadArgs {
SecureMem::Args *secureMem;
int threadFd;
};
int TrustedThread(void *arg) {
struct ThreadArgs *args = (struct ThreadArgs *) arg;
SecureMem::Args *secureMem = args->secureMem;
int fd = args->threadFd;
Sandbox::SysCalls sys;
int sequence_no = 2;
while (1) {
unsigned long syscall_args[7];
memset(syscall_args, 0, sizeof(syscall_args));
int got = sys.read(fd, syscall_args, 4);
assert(got == 4);
long syscall_result;
int sysnum = syscall_args[0];
if (sysnum == -1 || sysnum == -2) {
// Syscall where the registers have been checked by the trusted
// process, e.g. munmap() ranges must be OK.
// We check the sequence number before and after reading the
// syscall register data, in case this data is changed as we
// read it.
assert(secureMem->sequence == sequence_no);
memcpy(syscall_args, &secureMem->syscallNum, sizeof(syscall_args));
assert(secureMem->sequence == sequence_no);
sequence_no += 2;
if (syscall_args[0] == __NR_exit) {
assert(sysnum == -2);
int rc = sys.close(fd);
assert(rc == 0);
rc = sys.close(secureMem->threadFdPub);
assert(rc == 0);
// Make the thread's memory area inaccessible as a sanity check.
rc = sys.mprotect(secureMem, 0x2000, PROT_NONE);
assert(rc == 0);
// Although the thread exit syscall does not read from the
// secure memory area, we use the mutex for it to ensure that
// the trusted process and trusted thread are synchronised.
// We do not want the trusted process to think that the
// thread's memory area has been freed while the trusted
// thread is still reading from it.
UnlockSyscallMutex(secureMem);
sys._exit(1);
}
else if (syscall_args[0] == __NR_clone) {
assert(sysnum == -1);
// Note that HandleNewThread() does UnlockSyscallMutex() for us.
long clone_flags = (long) secureMem->arg1;
int *pid_ptr = (int *) secureMem->arg3;
// clone() argument ordering differs between platforms.
#if defined(__x86_64__)
int *tid_ptr = (int *) secureMem->arg4;
void *tls_info = secureMem->arg5;
#elif defined(__i386__)
int *tid_ptr = (int *) secureMem->arg5;
void *tls_info = secureMem->arg4;
#else
#error Unsupported target platform
#endif
syscall_result = sys.clone(HandleNewThread, (void *) AllocateStack(),
clone_flags, (void *) secureMem,
pid_ptr, tls_info, tid_ptr);
assert(syscall_result > 0);
int sent = sys.write(fd, &syscall_result, sizeof(syscall_result));
assert(sent == sizeof(syscall_result));
continue;
}
}
else if (sysnum == -3) {
// RDTSC request. Send back a dummy answer.
int timestamp[2] = {0, 0};
int sent = sys.write(fd, (char *) timestamp, sizeof(timestamp));
assert(sent == 8);
continue;
}
else {
int rest_size = sizeof(syscall_args) - sizeof(long);
got = sys.read(fd, &syscall_args[1], rest_size);
assert(got == rest_size);
}
syscall_result = DoSyscall(syscall_args);
#if defined(__NR_shmget)
if (syscall_args[0] == __NR_shmget) {
SaveShmgetResult(secureMem, syscall_result);
}
#elif defined(__NR_ipc)
if (syscall_args[0] == __NR_ipc && syscall_args[1] == SHMGET) {
SaveShmgetResult(secureMem, syscall_result);
}
#endif
if (sysnum == -2) {
// This syscall involves reading from the secure memory area for
// the thread. We should only unlock this area when the syscall
// has completed. Otherwise, the trusted process might
// overwrite the data while the kernel is still reading it.
UnlockSyscallMutex(secureMem);
}
int sent = sys.write(fd, &syscall_result, sizeof(syscall_result));
assert(sent == sizeof(syscall_result));
}
return 0;
}
void CreateReferenceTrustedThread(SecureMem::Args *secureMem) {
// We are in the nascent thread.
Sandbox::SysCalls sys;
int socks[2];
int rc = sys.socketpair(AF_UNIX, SOCK_STREAM, 0, socks);
assert(rc == 0);
int threadFdPub = socks[0];
int threadFd = socks[1];
// Create trusted thread.
// We omit CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID.
int flags = CLONE_VM | CLONE_FS | CLONE_FILES |
CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM;
// Assumes that the stack grows down.
char *stack_top = AllocateStack() - sizeof(struct ThreadArgs);
struct ThreadArgs *thread_args = (struct ThreadArgs *) stack_top;
thread_args->threadFd = threadFd;
thread_args->secureMem = secureMem;
rc = sys.clone(TrustedThread, stack_top, flags, thread_args,
NULL, NULL, NULL);
assert(rc > 0);
// Make the thread state pages usable.
rc = sys.mprotect(secureMem, 0x1000, PROT_READ);
assert(rc == 0);
rc = sys.mprotect(((char *) secureMem) + 0x1000, 0x1000,
PROT_READ | PROT_WRITE);
assert(rc == 0);
// Using cookie as the start is a little odd because other code in
// syscall.c works around this when addressing from %fs.
void *tls = (void*) &secureMem->cookie;
InitCustomTLS(tls);
int tid = sys.gettid();
assert(tid > 0);
// Send the socket FDs to the trusted process.
// TODO(mseaborn): Don't duplicate this struct in trusted_process.cc
struct {
SecureMem::Args* self;
int tid;
int fdPub;
} __attribute__((packed)) data;
data.self = secureMem;
data.tid = tid;
data.fdPub = threadFdPub;
bool ok = Sandbox::sendFd(Sandbox::cloneFdPub_, threadFdPub, threadFd,
(void *) &data, sizeof(data));
assert(ok);
// Wait for the trusted process to fill out the thread state for us.
char byte_message;
int got = sys.read(threadFdPub, &byte_message, 1);
assert(got == 1);
assert(byte_message == 0);
// Switch to seccomp mode.
rc = sys.prctl(PR_SET_SECCOMP, 1);
assert(rc == 0);
}
}