blob: 74bda2262ffe2eb3aa37957694601bc8e9223ad0 [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>
#define CHECK_SYSCALL_ZERO test %eax, %eax; jnz fatal_error
.internal playground$runTrustedThread
.global playground$runTrustedThread
playground$runTrustedThread:
mov 4(%esp), %edi // 1st arg: SecureMem::Args*
mov 8(%esp), %esi // 2nd arg: segment selector for %fs
push %ebx
push %ebp
// Signal handlers are process-wide. This means that for security
// reasons, we cannot allow that the trusted thread ever executes any
// signal handlers.
// We prevent the execution of signal handlers by setting a signal
// mask that blocks all signals. In addition, we make sure that the
// stack pointer is invalid.
// We cannot reset the signal mask until after we have enabled
// Seccomp mode. Our sigprocmask() wrapper would normally do this by
// raising a signal, modifying the signal mask in the kernel-generated
// signal frame, and then calling sigreturn(). This presents a bit of
// a Catch-22, as all signals are masked and we can therefore not
// raise any signal that would allow us to generate the signal stack
// frame.
// Instead, we have to create the signal stack frame prior to entering
// Seccomp mode. This incidentally also helps us to restore the
// signal mask to the same value that it had prior to entering the
// sandbox.
// The signal wrapper for clone() is the second entry point into this
// code (by means of sending an IPC to its trusted thread). It goes
// through the same steps of creating a signal stack frame on the
// newly created thread's stacks prior to cloning. See clone.cc for
// details.
mov $__NR_clone + 0xF000, %eax
mov %esp, %ebp
int $0 // push a signal stack frame (see clone.cc)
movw %si, %fs
mov %esi, 0x4(%esp) // set up %fs upon call to sigreturn()
mov %ebp, 0x1C(%esp) // pop stack upon call to sigreturn()
call 0f // determine %eip for PIC addressing
0:pop %ebp
movd %ebp, %mm1
add $(_GLOBAL_OFFSET_TABLE_+(.-0b)), %ebp
mov playground$cloneFdPub@GOT(%ebp), %ebp
movd 0(%ebp), %mm3
movd %mm1, %ebp
add $(999f-0b), %ebp
mov %ebp, 0x38(%esp) // return address: continue in same thread
mov %esp, %ebp
mov $2, %ebx // how = SIG_SETMASK
pushl $-1
pushl $-1
mov %esp, %ecx // set = full mask
xor %edx, %edx // old_set = NULL
mov $8, %esi // mask all 64 signals
mov $__NR_rt_sigprocmask, %eax
int $0x80
CHECK_SYSCALL_ZERO
mov $__NR_sigprocmask, %eax
int $0x80
CHECK_SYSCALL_ZERO
xor %esp, %esp // invalidate the stack in all trusted code
movd %edi, %mm6 // %mm6 = args
xor %edi, %edi // initial sequence number
movd %edi, %mm2
jmp 20f // create trusted thread
// TODO(markus): Coalesce the read() operations by reading into a
// bigger buffer.
// Parameters:
// %mm0: thread's side of threadFd
// %mm1: base address used for position independent code
// %mm3: cloneFdPub
// %mm5: secure memory region
// the page following this one contains the scratch space
// Local variables:
// %mm2: sequence number for trusted calls
// %mm4: thread id
// Temporary variables:
// %ebp: system call number
// %mm6: secure memory of previous thread
// %mm7: temporary variable for spilling data
// Layout of secure shared memory region (c.f. securemem.h):
// 0x00: pointer to the secure shared memory region (i.e. self)
// 0x04: sequence number; must match %mm2
// 0x08: call type; must match %eax, iff %eax == -1 || %eax == -2
// 0x0C: system call number; passed to syscall in %eax
// 0x10: first argument; passed to syscall in %ebx
// 0x14: second argument; passed to syscall in %ecx
// 0x18: third argument; passed to syscall in %edx
// 0x1C: fourth argument; passed to syscall in %esi
// 0x20: fifth argument; passed to syscall in %edi
// 0x24: sixth argument; passed to syscall in %ebp
// 0x28-0x40: no longer used
// 0x44: new shared memory for clone()
// 0x48: no longer used
// 0x4C: no longer used
// 0x50: set to non-zero, if in debugging mode
// 0x54: most recent SHM id returned by shmget(IPC_PRIVATE)
// 0x58: cookie assigned to us by the trusted process (TLS_COOKIE)
// 0x60: thread id (TLS_TID)
// 0x68: threadFdPub (TLS_THREAD_FD)
// 0x70: syscallMutex
// 0x74: maxSyscall
// 0x78: syscallTable
// 0x200-0x1000: securely passed verified file name(s)
// Layout of (untrusted) scratch space:
// 0x00: syscall number; passed in %eax
// 0x04: first argument; passed in %ebx
// 0x08: second argument; passed in %ecx
// 0x0C: third argument; passed in %edx
// 0x10: fourth argument; passed in %esi
// 0x14: fifth argument; passed in %edi
// 0x18: sixth argument; passed in %ebp
// 0x1C: return value
// 0x20: RDTSCP result (%eax)
// 0x24: RDTSCP result (%edx)
// 0x28: RDTSCP result (%ecx)
// 0x2C: last system call (updated in syscall.cc)
// 0x30: number of consecutive calls to a time fnc (e.g.gettimeofday)
// 0x34: nesting level of system calls (for debugging purposes only)
// 0x38: signal mask
// 0x40: in SEGV handler
1:xor %esp, %esp
mov $2, %eax // %mm2 = initial sequence number
movd %eax, %mm2
// Read request from untrusted thread, or from trusted process. In
// either case, the data that we read has to be considered untrusted.
// read(threadFd, &scratch, 4)
2:mov $__NR_read, %eax
movd %mm0, %ebx // fd = threadFd
movd %mm5, %ecx // secure_mem
add $0x1000, %ecx // buf = &scratch
mov $4, %edx // len = 4
3:int $0x80
cmp $-4, %eax // EINTR
jz 3b
cmp %edx, %eax
jnz fatal_error
// Retrieve system call number. It is crucial that we only dereference
// 0x1000(%mm5) exactly once. Afterwards, memory becomes untrusted and
// we must use the value that we have read the first time.
mov 0(%ecx), %eax
// If syscall number is -1, execute an unlocked system call from the
// secure memory area
cmp $-1, %eax
jnz 5f
movd %mm2, %ebp
cmp %ebp, 0x4-0x1000(%ecx)
jne fatal_error
cmp 0x08-0x1000(%ecx), %eax
jne fatal_error
mov 0x0C-0x1000(%ecx), %eax
mov 0x10-0x1000(%ecx), %ebx
mov 0x18-0x1000(%ecx), %edx
mov 0x1C-0x1000(%ecx), %esi
mov 0x20-0x1000(%ecx), %edi
mov 0x24-0x1000(%ecx), %ebp
mov 0x14-0x1000(%ecx), %ecx
movd %edi, %mm4
movd %ebp, %mm7
movd %mm2, %ebp
movd %mm5, %edi
cmp %ebp, 4(%edi)
jne fatal_error
add $2, %ebp
movd %ebp, %mm2
movd %mm4, %edi
movd %mm7, %ebp
// clone() has unusual calling conventions and must be handled
// specially
cmp $__NR_clone, %eax
jz 19f
// shmget() gets some special treatment. Whenever we return from this
// system call, we remember the most recently returned SysV shm id.
cmp $__NR_ipc, %eax
jnz 4f
cmp $23, %ebx // shmget()
jnz 4f
int $0x80
mov %eax, %ebp
mov $__NR_clone, %eax
mov $17, %ebx // flags = SIGCHLD
mov $1, %ecx // stack = 1
int $0x80
test %eax, %eax
js fatal_error
mov %eax, %ebx
jnz 8f // wait for child, then return result
movd %mm5, %ebx // start = secure_mem
mov $4096, %ecx // len = 4096
mov $3, %edx // prot = PROT_READ | PROT_WRITE
mov $__NR_mprotect, %eax
int $0x80
CHECK_SYSCALL_ZERO
mov %ebp, 0x54(%ebx) // set most recently returned SysV shm id
xor %ebx, %ebx
// When debugging messages are enabled, warn about expensive system
// calls
#ifndef NDEBUG
movd %mm5, %ecx
cmpw $0, 0x50(%ecx) // debug mode
jz 26f
mov $__NR_write, %eax
mov $2, %ebx // fd = stderr
movd %mm1, %ecx
add $(101f-0b), %ecx // "This is an expensive system call"
mov $102f-101f, %edx // len = strlen(msg)
int $0x80
xor %ebx, %ebx
#endif
jmp 26f // exit program, no message
4:int $0x80
jmp 15f // return result
// If syscall number is -2, execute locked system call from the
// secure memory area
5:jg 12f
cmp $-2, %eax
jnz 9f
movd %mm2, %ebp
cmp %ebp, 0x4-0x1000(%ecx)
jne fatal_error
cmp %eax, 0x8-0x1000(%ecx)
jne fatal_error
// When debugging messages are enabled, warn about expensive system
// calls
#ifndef NDEBUG
cmpw $0, 0x50-0x1000(%ecx)
jz 6f // debug mode
mov %ecx, %ebp
mov $__NR_write, %eax
mov $2, %ebx // fd = stderr
movd %mm1, %ecx
add $(101f-0b), %ecx // "This is an expensive system call"
mov $102f-101f, %edx // len = strlen(msg)
int $0x80
mov %ebp, %ecx
6:
#endif
mov 0x0C-0x1000(%ecx), %eax
mov 0x10-0x1000(%ecx), %ebx
mov 0x18-0x1000(%ecx), %edx
mov 0x1C-0x1000(%ecx), %esi
mov 0x20-0x1000(%ecx), %edi
mov 0x24-0x1000(%ecx), %ebp
mov 0x14-0x1000(%ecx), %ecx
movd %edi, %mm4
movd %ebp, %mm7
movd %mm2, %ebp
movd %mm5, %edi
cmp %ebp, 4(%edi)
jne fatal_error
// exit() terminates trusted thread
cmp $__NR_exit, %eax
jz 18f
// Perform requested system call
movd %mm4, %edi
movd %mm7, %ebp
int $0x80
// Unlock mutex
7:movd %mm2, %ebp
movd %mm5, %edi
cmp %ebp, 4(%edi)
jne fatal_error
add $2, %ebp
movd %ebp, %mm2
mov %eax, %ebp
mov $__NR_clone, %eax
mov $17, %ebx // flags = SIGCHLD
mov $1, %ecx // stack = 1
int $0x80
test %eax, %eax
js fatal_error
jz 22f // unlock and exit
mov %eax, %ebx
8:xor %ecx, %ecx
xor %edx, %edx
mov $__NR_waitpid, %eax
int $0x80
cmp $-4, %eax // EINTR
jz 8b
mov %ebp, %eax
jmp 15f // return result
// If syscall number is -3, read the time stamp counter
9:cmp $-3, %eax
jnz 10f
rdtsc // sets %edx:%eax
xor %ecx, %ecx
jmp 11f
10:cmp $-4, %eax
jnz 12f
rdtscp // sets %edx:%eax and %ecx
11:movd %mm5, %ebx
add $0x1020, %ebx
mov %eax, 0(%ebx)
mov %edx, 4(%ebx)
mov %ecx, 8(%ebx)
mov %ebx, %ecx
mov $12, %edx
jmp 16f // return result
// Check in syscallTable whether this system call is unrestricted
12:mov %eax, %ebp
#ifndef NDEBUG
cmpw $0, 0x50-0x1000(%ecx)
jnz 13f // debug mode
#endif
movd %mm5, %ebx
cmp 0x74(%ebx), %eax // maxSyscall
ja fatal_error
shl $3, %eax
add 0x78(%ebx), %eax // syscallTable
mov 0(%eax), %eax
cmp $1, %eax
jne fatal_error
// Default behavior for unrestricted system calls is to just execute
// them. Read the remaining arguments first.
13:mov $__NR_read, %eax
movd %mm0, %ebx // fd = threadFd
add $4, %ecx // buf = &scratch + 4
mov $24, %edx // len = 6*sizeof(void *)
14:int $0x80
cmp $-4, %eax // EINTR
jz 14b
cmp %edx, %eax
jnz fatal_error
mov %ebp, %eax
mov 0x00(%ecx), %ebx
mov 0x08(%ecx), %edx
mov 0x0C(%ecx), %esi
mov 0x10(%ecx), %edi
mov 0x14(%ecx), %ebp
mov 0x04(%ecx), %ecx
cmp $__NR_exit_group, %eax
jz 26f // exit program, no message
int $0x80
// Return result of system call to sandboxed thread
15:movd %mm5, %ecx // secure_mem
add $0x101C, %ecx // buf = &scratch + 28
mov %eax, (%ecx)
mov $4, %edx // len = 4
16:movd %mm0, %ebx // fd = threadFd
mov $__NR_write, %eax
17:int $0x80
cmp %edx, %eax
jz 2b
cmp $-4, %eax // EINTR
jz 17b
jmp fatal_error
// NR_exit:
// Exit trusted thread after cleaning up resources
18:mov %edi, %ecx // secure_mem
mov 0x68(%ecx), %ebx // fd = threadFdPub
mov $__NR_close, %eax
int $0x80
CHECK_SYSCALL_ZERO
mov %ecx, %ebx // start = secure_mem
mov $8192, %ecx // length = 8192
xor %edx, %edx // prot = PROT_NONE
mov $__NR_mprotect, %eax
int $0x80
CHECK_SYSCALL_ZERO
movd %mm0, %ebx // fd = threadFd
mov $__NR_close, %eax
int $0x80
CHECK_SYSCALL_ZERO
mov $__NR_clone, %eax
mov $17, %ebx // flags = SIGCHLD
mov $1, %ecx // stack = 1
int $0x80
mov %eax, %ebx
test %eax, %eax
js fatal_error
jne 21f // reap helper, exit thread
jmp 22f // unlock mutex
// NR_clone:
// Original trusted thread calls clone() to create new nascent
// thread. This thread is (typically) fully privileged and shares all
// resources with the caller (i.e. the previous trusted thread),
// and by extension it shares all resources with the sandbox'd
// threads.
19:movd %mm5, %edi
movd %edi, %mm6 // %mm6 = old_shared_mem
movd %mm4, %edi // child_tidptr
mov %ecx, %ebp // remember child stack
mov $1, %ecx // stack = 1
int $0x80 // calls NR_clone
cmp $-4095, %eax // return codes -1..-4095 are errno values
jae 7b // unlock mutex, return result
test %eax, %eax
jne 15b // return result
// In nascent thread, now.
// Undo sequence number increase that was made for the general case.
movd %mm2, %edi
sub $2, %edi
movd %edi, %mm2
// We want to maintain an invalid %esp whenver we access untrusted
// memory. This ensures that even if an attacker can trick us into
// triggering a SIGSEGV, we will never successfully execute a signal
// handler.
// Signal handlers are inherently dangerous, as an attacker could trick
// us into returning to the wrong address by adjusting the signal stack
// right before the handler returns.
// N.B. While POSIX is curiously silent about this, it appears that on
// Linux, alternate signal stacks are a per-thread property. That is
// good. It means that this security mechanism works, even if the
// sandboxed thread manages to set up an alternate signal stack.
//
// TODO(markus): We currently do not support emulating calls to
// sys_clone() with a zero (i.e. copy) stack parameter. See clone.cc
// for a discussion on how to fix this, if this ever becomes neccessary
// Get thread id of nascent thread
20:mov $__NR_gettid, %eax
int $0x80
movd %eax, %mm4
// Nascent thread creates socketpair() for sending requests to
// trusted thread.
// We can create the filehandles on the child's stack. Filehandles are
// always treated as untrusted.
// socketpair(AF_UNIX, SOCK_STREAM, 0, fds)
mov $__NR_socketcall, %eax
mov $8, %ebx // socketpair
sub $8, %ebp // sv = child_stack
mov %ebp, -0x04(%ebp)
movl $0, -0x08(%ebp) // protocol = 0
movl $1, -0x0C(%ebp) // type = SOCK_STREAM
movl $1, -0x10(%ebp) // domain = AF_UNIX
lea -0x10(%ebp), %ecx
int $0x80
test %eax, %eax
jz 27f
// If things went wrong, we don't have an (easy) way of signaling
// the parent. For our purposes, it is sufficient to fail with a
// fatal error.
jmp fatal_error
21:xor %ecx, %ecx
xor %edx, %edx
mov $__NR_waitpid, %eax
int $0x80
cmp $-4, %eax // EINTR
jz 21b
jmp 23f // exit thread (no message)
// Unlock syscallMutex and exit.
22:movd %mm5, %ebx
mov $4096, %ecx
mov $3, %edx // prot = PROT_READ | PROT_WRITE
mov $__NR_mprotect, %eax
int $0x80
CHECK_SYSCALL_ZERO
addl $0x70, %ebx
lock; addl $0x80000000, (%ebx)
jz 23f // exit thread
mov $1, %edx
mov %edx, %ecx // FUTEX_WAKE
mov $__NR_futex, %eax
int $0x80
23:mov $__NR_exit, %eax
mov $1, %ebx // status = 1
24:int $0x80
fatal_error:
mov $__NR_write, %eax
mov $2, %ebx // fd = stderr
movd %mm1, %ecx
add $(100f-0b), %ecx // "Sandbox violation detected"
mov $101f-100f, %edx // len = strlen(msg)
int $0x80
25:mov $1, %ebx
26:mov $__NR_exit_group, %eax
jmp 24b
// The first page is mapped read-only for use as securely shared memory
27:movd %mm6, %edi // %edi = old_shared_mem
mov 0x44(%edi), %ebx // addr = secure_mem
movd %ebx, %mm5 // %mm5 = secure_mem
movd %mm2, %esi
cmp %esi, 4(%edi)
jne fatal_error
mov $__NR_mprotect, %eax
mov $4096, %ecx // len = 4096
mov $1, %edx // prot = PROT_READ
int $0x80
CHECK_SYSCALL_ZERO
// The second page is used as scratch space by the trusted thread.
// Make it writable.
mov $__NR_mprotect, %eax
add $4096, %ebx // addr = secure_mem + 4096
mov $3, %edx // prot = PROT_READ | PROT_WRITE
int $0x80
CHECK_SYSCALL_ZERO
// Call clone() to create new trusted thread().
// clone(CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|
// CLONE_SYSVSEM|CLONE_UNTRACED, stack, NULL, NULL, NULL)
mov 4(%ebp), %eax // threadFd (on child's stack)
movd %eax, %mm0 // %mm0 = threadFd
mov $__NR_clone, %eax
mov $0x850F00, %ebx // flags = VM|FS|FILES|SIGH|THR|SYSV|UTR
mov $1, %ecx // stack = 1
cmp %esi, 4(%edi)
jne fatal_error
int $0x80
test %eax, %eax
js fatal_error
jz 1b // invoke trustedThreadFnc()
// Set up thread local storage
mov $0x51, %eax // seg_32bit, limit_in_pages, useable
mov %eax, -0x04(%ebp)
mov $0xFFFFF, %eax // limit
mov %eax, -0x08(%ebp)
movd %mm5, %eax
add $0x58, %eax
mov %eax, -0x0C(%ebp) // base_addr = &secure_mem.TLS
mov %fs, %eax
shr $3, %eax
mov %eax, -0x10(%ebp) // entry_number
mov $__NR_set_thread_area, %eax
lea -0x10(%ebp), %ebx
int $0x80
CHECK_SYSCALL_ZERO
// Copy the caller's signal mask
movd %mm5, %edx
mov 0x1038(%edi), %eax
mov %eax, 0x1038(%edx)
mov 0x103C(%edi), %eax
mov %eax, 0x103C(%edx)
// Done creating trusted thread. We can get ready to return to caller
mov 0(%ebp), %esi // %esi = threadFdPub
add $8, %ebp
// Check the sequence number
movd %mm2, %edx
cmp %edx, 4(%edi)
jne fatal_error
// Nascent thread launches a helper that doesn't share any of our
// resources, except for pages mapped as MAP_SHARED.
// clone(SIGCHLD, stack=1)
mov $__NR_clone, %eax
mov $17, %ebx // flags = SIGCHLD
mov $1, %ecx // stack = 1
int $0x80
test %eax, %eax
js fatal_error
jne 28f
// Use sendmsg() to send to the trusted process the file handles for
// communicating with the new trusted thread. We also send the address
// of the secure memory area (for sanity checks) and the thread id.
cmp %edx, 4(%edi)
jne fatal_error
// 0x00 socketcall:
// 0x00 socket (cloneFdPub)
// 0x04 msg (%ecx + 0x0C)
// 0x08 flags ($0)
// 0x0C msg:
// 0x0C msg_name ($0)
// 0x10 msg_namelen ($0)
// 0x14 msg_iov (%ecx + 0x34)
// 0x18 msg_iovlen ($1)
// 0x1C msg_control (%ecx + 0x3C)
// 0x20 msg_controllen ($0x14)
// 0x24 data:
// 0x24 msg_flags/err ($0)
// 0x28 secure_mem (%mm5)
// 0x2C threadId (%mm4)
// 0x30 threadFdPub (%esi)
// 0x34 iov:
// 0x34 iov_base (%ecx + 0x24)
// 0x38 iov_len ($0x10)
// 0x3C cmsg:
// 0x3C cmsg_len ($0x14)
// 0x40 cmsg_level ($1, SOL_SOCKET)
// 0x44 cmsg_type ($1, SCM_RIGHTS)
// 0x48 threadFdPub (%esi)
// 0x4C threadFd (%mm0)
// 0x50
movd %mm1, %ecx
add $(sendmsg_data-0b), %ecx
xor %eax, %eax
mov %eax, 0x08(%ecx) // flags
mov %eax, 0x0C(%ecx) // msg_name
mov %eax, 0x10(%ecx) // msg_namelen
mov %eax, 0x24(%ecx) // msg_flags
inc %eax
mov %eax, 0x18(%ecx) // msg_iovlen
mov %eax, 0x40(%ecx) // cmsg_level
mov %eax, 0x44(%ecx) // cmsg_type
movl $0x10, 0x38(%ecx) // iov_len
mov $0x14, %eax
mov %eax, 0x20(%ecx) // msg_controllen
mov %eax, 0x3C(%ecx) // cmsg_len
movd %mm3, %eax // cloneFdPub
mov %eax, 0x00(%ecx) // socket
lea 0x0C(%ecx), %eax
mov %eax, 0x04(%ecx) // msg
add $0x18, %eax
mov %eax, 0x34(%ecx) // iov_base
add $0x10, %eax
mov %eax, 0x14(%ecx) // msg_iov
add $8, %eax
mov %eax, 0x1C(%ecx) // msg_control
mov %esi, 0x30(%ecx) // threadFdPub
mov %esi, 0x48(%ecx) // threadFdPub
movd %mm5, %eax
mov %eax, 0x28(%ecx) // secure_mem
movd %mm4, %eax
mov %eax, 0x2C(%ecx) // threadId
movd %mm0, %eax
mov %eax, 0x4C(%ecx) // threadFd
mov $16, %ebx // sendmsg()
mov $__NR_socketcall, %eax
int $0x80
xor %ebx, %ebx
jmp 26b // exit process (no error message)
// Reap helper
28:mov %eax, %ebx
29:lea -4(%ebp), %ecx
xor %edx, %edx
mov $__NR_waitpid, %eax
int $0x80
cmp $-4, %eax // EINTR
jz 29b
mov -4(%ebp), %eax
test %eax, %eax
jnz 25b // exit process (no error message)
// Release privileges by entering seccomp mode.
mov $__NR_prctl, %eax
mov $22, %ebx // PR_SET_SECCOMP
mov $1, %ecx
int $0x80
CHECK_SYSCALL_ZERO
// We can finally start using the stack. Signal handlers no longer pose
// a threat to us.
mov %ebp, %esp
// Back in the newly created sandboxed thread, wait for trusted process
// to receive request. It is possible for an attacker to make us
// continue even before the trusted process is done. This is OK. It'll
// result in us putting stale values into the new thread's TLS. But
// that data is considered untrusted anyway.
push %eax
mov $1, %edx // len = 1
mov %esp, %ecx // buf = %esp
mov %esi, %ebx // fd = threadFdPub
30:mov $__NR_read, %eax
int $0x80
cmp $-4, %eax // EINTR
jz 30b
cmp %edx, %eax
jne fatal_error
pop %eax
// Returning to the place where clone() had been called. We rely on
// using sigreturn() for restoring our registers. The caller already
// created a signal stack frame and patched the register values
// with the ones that were in effect prior to calling sandbox_clone().
mov $__NR_sigreturn, %eax
int $0x80
.pushsection ".rodata"
100:.ascii "Sandbox violation detected, program aborted\n"
101:.ascii "WARNING! This is an expensive system call\n"
102:
.popsection
999:pop %ebp
pop %ebx
ret
.bss
// Reserve space for sendmsg() data. This is used in a fork()'d
// helper process, so in principle this could safely overlap and
// overwrite other data, but it is such a small amount of memory
// that it is not worth trying to do that. The only requirement
// is that this must be in a MAP_PRIVATE mapping so that an
// untrusted thread cannot modify the forked subprocess's copy.
sendmsg_data:
.space 0x50