Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions include/systrap.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ void replace_instruction_with(unsigned char *pos, unsigned len,
unsigned char const *replacement, unsigned replacement_len);
void replace_syscall_with_ud2(unsigned char *pos, unsigned len);

void __systrap_set_sigill_handler(void (*handler)(int signum, void *info, void *uctxt));
void (*__systrap_get_sigill_handler(void))(int, void *, void *);

/* really funky clients, e.g. a ld.so, might not run their own constructor, so ... */
void __libsystrap_force_init(void) __attribute__((visibility("hidden")));

Expand Down Expand Up @@ -57,8 +60,22 @@ typedef void /*(__attribute__((noreturn))*/ syscall_replacement/*)*/(

extern syscall_replacement *replaced_syscalls[SYSCALL_MAX];

/* When non-zero, a clone/clone3 child resumes by doing rt_sigreturn directly
* from the copied sigframe rather than unwinding the SIGILL handler's epilogue.
* Default 0 (legacy emulation). Clients that hit the unwind race (e.g. liballocs) (hopefully) set this to 1.
*/
extern int __systrap_clone_child_direct_sigreturn;

void __systrap_pre_handling(struct generic_syscall *gsp);
void __systrap_post_handling(struct generic_syscall *gsp, long int ret, _Bool do_sigframe_resume);
void __libsystrap_noop_post_handling(struct generic_syscall *gsp, long int ret, _Bool do_sigframe_resume);

/* While a thread is inside handle_sigill emulating a syscall, these return the
* saved application stack pointer / instruction pointer at the syscall site;
*/
void *__systrap_saved_sp_internal(void);
void *__systrap_saved_ip_internal(void);
void *__systrap_current_saved_sp(void);
void *__systrap_current_saved_ip(void);

#endif
6 changes: 6 additions & 0 deletions src/do-syscall.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ __attribute__((alias("__libsystrap_noop_post_handling")));
#endif
syscall_replacement *replaced_syscalls[SYSCALL_MAX] = { NULL };

/* When non-zero, the clone/clone3 child resumes by doing rt_sigreturn directly
* from the copied sigframe, instead of unwinding handle_sigill's epilogue.
* Off by default.
*/
int __systrap_clone_child_direct_sigreturn = 0;

void *generic_syscall_get_ip(struct generic_syscall *gsp)
{
return (void*) gsp->saved_context->uc.uc_mcontext.MC_REG_IP;
Expand Down
58 changes: 58 additions & 0 deletions src/do-syscall.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ extern int debug_level;
// make a noncommital declaration of __assert_fail
void __assert_fail() __attribute__((noreturn));

extern __thread struct ibcs_sigframe *__systrap_current_frame;

extern uintptr_t our_load_address;

/* FIX_STACK_ALIGNMENT is in raw-syscalls-impl.h, included above */
Expand Down Expand Up @@ -641,6 +643,27 @@ Z||Z'|________|/ incl. saved ip = clone site (1) |________|/ incl saved
* We can detect that using ret_op i.e. clone()'s return value. */
(ret_op == 0) ? (void*) (gsp->args[1] /* see fake_arg1 comment above */)
: NULL);
#if defined(__x86_64__)
if (ret_op == 0 && __systrap_clone_child_direct_sigreturn)
{
/* Child: rt_sigreturn directly from the copied sigframe instead of
* unwinding handle_sigill's epilogue.
* Use the child's own current SP (on the new thread stack),
* 16-byte aligned; RSP for the syscall must be &uc (kernel reads the frame at RSP-8).
*/
uintptr_t child_sp;
__asm__ volatile ("movq %%rsp, %0" : "=r"(child_sp));
gsp->saved_context->uc.uc_mcontext.MC_REG_SP = child_sp & ~(uintptr_t) 0xf;
/* Resuming into application code; this thread is no longer in systrap. */
__systrap_current_frame = (struct ibcs_sigframe *) 0;
void *uc_addr = (void*) &gsp->saved_context->uc;
__asm__ volatile ("movq %0, %%rsp\n\t"
"movq $15, %%rax\n\t" /* __NR_rt_sigreturn */
"syscall\n\t"
:: "r"(uc_addr) : "memory");
__builtin_unreachable();
}
#endif
return ret_op;
}

Expand Down Expand Up @@ -808,6 +831,38 @@ do_clone3(struct generic_syscall *gsp)
(uintptr_t)(((struct clone_args *) gsp->args[0])->stack)
+ (uintptr_t)(((struct clone_args *) gsp->args[0])->stack_size)
): NULL);
#if defined(__x86_64__)
if (ret_op == 0 && __systrap_clone_child_direct_sigreturn)
{
/* Child: rt_sigreturn directly from the copied sigframe instead of
* unwinding handle_sigill's compiler-generated epilogue. The unwind
* depends on the RBP-swizzle above and the -O2 epilogue being byte-exact
* relative to the relocated frames, which races under some clone-site
* stack layouts (e.g. Alaska's pthread_create interposer). copy_to_new_-
* stack already relocated gsp->saved_context to point at the copied
* sigframe, and fixup_sigframe_for_return set its rax/IP/SP. The kernel's
* rt_sigreturn reads the frame at RSP-8, so set RSP = &uc.
*
* Do NOT trust the resume SP that fixup_sigframe_for_return derived from
* clone_args: clone_args lives on the shared (CLONE_VM) parent stack and
* the parent overwrites that frame once clone3 returns, so reading it
* here races. Use the child's own current SP, which is on the new thread
* stack, and 16-byte align it (x86-64 ABI; else start_thread's SSE-using
* snprintf in name_stack_maps faults).
*/
uintptr_t child_sp;
__asm__ volatile ("movq %%rsp, %0" : "=r"(child_sp));
gsp->saved_context->uc.uc_mcontext.MC_REG_SP = child_sp & ~(uintptr_t) 0xf;
/* Resuming into application code; this thread is no longer in systrap. */
__systrap_current_frame = (struct ibcs_sigframe *) 0;
void *uc_addr = (void*) &gsp->saved_context->uc;
__asm__ volatile ("movq %0, %%rsp\n\t"
"movq $15, %%rax\n\t" /* __NR_rt_sigreturn */
"syscall\n\t"
:: "r"(uc_addr) : "memory");
__builtin_unreachable();
}
#endif
return ret_op;
}

Expand Down Expand Up @@ -835,6 +890,9 @@ do_sigreturn(struct generic_syscall *gsp)
* sigframe that that first signal's handler was trying to resume with.
*/
long int ret_op = (long int) gsp->syscall_number; /* either sigreturn or rt_sigreturn */
/* We never reach handle_sigill's out: from here; clear the in-systrap marker
* (we are about to sigreturn into application code). */
__systrap_current_frame = (struct ibcs_sigframe *) 0;
__asm__ volatile (
#if defined(__x86_64__)
"mov %[orig_sigframe_sp], %%rsp \n" /* stack pointer at the trapped sigreturn syscall */
Expand Down
49 changes: 48 additions & 1 deletion src/sigill.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,27 @@ unsigned sysinfo_sysenter_offset __attribute__((visibility("hidden")));
#endif
void *fake_sysinfo __attribute__((visibility("hidden")));

/* Per-thread pointer to the application sigframe of the syscall we are currently
* emulating in handle_sigill, or NULL when this thread is not inside an
* emulation. Published with DEFAULT visibility so another preloaded runtime
* (e.g. Alaska) can tell that a thread is parked inside a systrap syscall
* (-- hence at a GC-safe point --) and collect its roots from the saved application
* context (below) instead of unwinding across the nested handler/kernel signal
* frames (which corrupts state). Saved/restored, not just set/cleared, so a
* nested emulation restores the outer frame on the way out. */
__thread struct ibcs_sigframe *__systrap_current_frame __attribute__((visibility("default")));

void *__systrap_saved_sp_internal(void)
{
struct ibcs_sigframe *f = __systrap_current_frame;
return f ? (void*) f->uc.uc_mcontext.MC_REG_SP : (void*) 0;
}
void *__systrap_saved_ip_internal(void)
{
struct ibcs_sigframe *f = __systrap_current_frame;
return f ? (void*) f->uc.uc_mcontext.MC_REG_IP : (void*) 0;
}

/* We may or may not have syscall names linked in.
* This is just to avoid a dependency on our syscall interface spec. */
extern const char *syscall_names[SYSCALL_MAX + 1] __attribute__((weak));
Expand All @@ -50,7 +71,27 @@ void handle_sigill(int n)
* not, abort. */
unsigned long *frame_base = __builtin_frame_address(0);
struct ibcs_sigframe *p_frame = (struct ibcs_sigframe *) (frame_base + 1);
if (!is_ud2((void*) p_frame->uc.uc_mcontext.MC_REG_IP)) raw_exit(128 + SIGABRT);
/* For the in-systrap marker (see __systrap_current_frame): remember the outer
* value so nested emulations restore it. Captured here (before any goto out)
* so the restore at out: is well-defined; only set below once we know this is
* our trap site and we are about to emulate. */
struct ibcs_sigframe *systrap_outer_frame = __systrap_current_frame;
/* Is this one of OUR syscall trap sites? It must be a ud2 AND an address we
* recorded when replacing a syscall. Any other SIGILL -- a foreign ud2
* is not ours: forward it to the client's chained SIGILL handler if one is set.
* The chained handler is called SA_SIGINFO-style and shares this sigframe's ucontext, so any context
* changes it makes take effect on our normal return (via sigreturn). */
uintptr_t fault_ip = (uintptr_t) p_frame->uc.uc_mcontext.MC_REG_IP;
if (!is_ud2((void*) fault_ip) || !__systrap_is_trap_site(fault_ip))
{
if (__systrap_chained_sigill_handler)
{
__systrap_chained_sigill_handler((int) SIGILL,
(void*) &p_frame->info, (void*) &p_frame->uc);
return;
}
raw_exit(128 + SIGABRT);
}
#if defined(__i386__)
unsigned char *tls;
__asm__("mov %%gs:0x0,%0" : "=r"(tls));
Expand Down Expand Up @@ -240,6 +281,10 @@ void handle_sigill(int n)
* from emulating the return-to-__restore_rt path, i.e. the exit path from
* this function.
*/
/* From here we are emulating a real syscall on behalf of the application:
* publish the saved application context so a concurrent barrier signal
* sees this thread as in-systrap (see __systrap_current_frame). */
__systrap_current_frame = p_frame;
__systrap_pre_handling(&gsp);
if (replaced_syscalls[gsp.syscall_number])
{
Expand Down Expand Up @@ -271,6 +316,8 @@ void handle_sigill(int n)
do_generic_syscall_and_fixup(&gsp);
}
out:
/* No longer emulating on this thread */
__systrap_current_frame = systrap_outer_frame;
_handle_sigill_debug_printf(1, "Resuming from instruction at %p\n", p_frame->uc.uc_mcontext.MC_REG_IP);
#if defined(__i386__)
*(void**)(tls+16) = saved_sysinfo;
Expand Down
10 changes: 10 additions & 0 deletions src/systrap_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@
extern int systrap_debug_level __attribute__((visibility("hidden")));
extern _Bool is_ud2(const unsigned char *ins) __attribute__((visibility("hidden")));

#include <stdint.h>
/* Trap-site registry: the set of addresses where WE replaced an instruction
* with ud2. Used to distinguish our syscall traps from foreign ud2s (e.g.
* Alaska's safepoint poll points). See trap.c and sigill.c. */
void __systrap_record_trap_site(uintptr_t addr) __attribute__((visibility("hidden")));
_Bool __systrap_is_trap_site(uintptr_t addr) __attribute__((visibility("hidden")));
/* The client SIGILL handler to chain to for non-trap-site SIGILLs (or NULL). */
extern void (*__systrap_chained_sigill_handler)(int, void *, void *)
__attribute__((visibility("hidden")));

#ifdef SYSTRAP_DEFINE_FILE
struct _IO_FILE;
typedef struct _IO_FILE FILE;
Expand Down
60 changes: 60 additions & 0 deletions src/trap.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,63 @@
* we allow them to set a test trap. */
void *__libsystrap_ignore_ud2_addr;

#define TRAP_SITES_CAPACITY (1u << 17) /* 131072 slots; real syscall sites are far fewer */
static uintptr_t *trap_sites; /* mmap'd; TRAP_SITES_CAPACITY entries, 0 == empty */
static unsigned long trap_sites_count;

void (*__systrap_chained_sigill_handler)(int, void *, void *)
__attribute__((visibility("hidden")));

static inline unsigned long trap_site_slot(uintptr_t a)
{
/* Fibonacci hashing of the address (shifted past the always-zero low bit). */
return (unsigned long) (((a >> 1) * 0x9E3779B97F4A7C15ull)
>> (64 - 17)) & (TRAP_SITES_CAPACITY - 1);
}

void __systrap_record_trap_site(uintptr_t addr)
{
if (!addr) return;
if (!trap_sites)
{
void *m = raw_mmap(NULL, TRAP_SITES_CAPACITY * sizeof (uintptr_t),
PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
if (MMAP_RETURN_IS_ERROR(m)) abort();
trap_sites = (uintptr_t *) m;
}
unsigned long i = trap_site_slot(addr);
while (trap_sites[i] != 0)
{
if (trap_sites[i] == addr) return; /* already present */
i = (i + 1) & (TRAP_SITES_CAPACITY - 1);
}
trap_sites[i] = addr;
++trap_sites_count;
/* If we ever approach capacity the O(1) assumption breaks; that would be a bug. */
assert(trap_sites_count < (TRAP_SITES_CAPACITY / 4) * 3);
}

_Bool __systrap_is_trap_site(uintptr_t addr)
{
if (!trap_sites || !addr) return 0;
unsigned long i = trap_site_slot(addr);
while (trap_sites[i] != 0)
{
if (trap_sites[i] == addr) return 1;
i = (i + 1) & (TRAP_SITES_CAPACITY - 1);
}
return 0;
}

void __systrap_set_sigill_handler(void (*handler)(int, void *, void *))
{
__systrap_chained_sigill_handler = handler;
}
void (*__systrap_get_sigill_handler(void))(int, void *, void *)
{
return __systrap_chained_sigill_handler;
}

static int sleep_for_seconds;
static int stop_self;
static int self_pid;
Expand Down Expand Up @@ -115,6 +172,9 @@ void replace_syscall_with_ud2(unsigned char *pos, unsigned len)
}
}
#endif
/* Record this site BEFORE writing the ud2, so that by the time any thread
* can fault here, the SIGILL handler will recognise it as ours. */
__systrap_record_trap_site((uintptr_t) pos);
replace_instruction_with(pos, len, replacement, sizeof replacement);
}

Expand Down