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
2 changes: 1 addition & 1 deletion src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ PORTAL_OBJS := $(patsubst $(src)/%.c,%.o,$(PORTAL_SRCS))

igloo-objs += igloo_hc.o hooks/syscalls_hc.o hooks/ioctl_hc.o \
hooks/sock_hc.o hooks/uname_hc.o hooks/block_mounts.o \
hooks/igloo_open.o hooks/signal_hc.o \
hooks/igloo_open.o hooks/signal_hc.o hooks/exit_hc.o \
hyperfs/hyperfs.o \
netdevs/igloonet.o \
$(PORTAL_OBJS)
Expand Down
111 changes: 111 additions & 0 deletions src/hooks/exit_hc.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/kprobes.h>
#include <linux/version.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include "igloo_hypercall.h" /* pulls in igloo_hypercall_consts.h (no guard) */
#include "igloo.h"
#include "exit_hc.h"
#include "portal/portal.h"
#include "portal/portal_internal.h"
#include "args.h"

/*
* do_exit() task-exit hook.
*
* A kprobe on do_exit() fires on EVERY task death -- normal exit/exit_group,
* fatal signals, OOM, kernel-forced -- which the exit/exit_group *syscall*
* hooks miss (a signal death never issues an exit syscall). This is the
* authoritative process-exit source for the `processes` model: it replaces the
* host-side signal-death heuristic (which can't tell a caught SIGSEGV from a
* fatal one) and gives the real, wait-status-encoded exit code.
*
* Gated by an enable flag toggled from the host (register/unregister ops) so it
* costs nothing unless a plugin opts in -- same shape as signal_hc's hook count.
*/

static struct kprobe exit_kp;
static bool exit_kp_registered; /* portal ops are serialized, so a plain bool */

static void do_exit_hyp(struct exit_event *event) {
igloo_portal(IGLOO_HYP_PROC_EXIT, (unsigned long)event, 0);
}

static int exit_pre_handler(struct kprobe *p, struct pt_regs *regs) {
struct task_struct *t = current;
struct exit_event event;

/* No enable-flag check: the probe is only installed while enabled (armed on
* the host register op, removed on unregister), so if we are here it is on.
* That makes the disabled case cost literally nothing -- no probe, no trap. */

/* User processes only; kernel threads are not part of the process model.
* At do_exit() entry the task's mm/flags are still intact (exit_mm runs
* later inside do_exit), so PF_KTHREAD is reliable here. */
if (t->flags & PF_KTHREAD)
return 0;

/* One event per process: report at thread-group-leader death, matching the
* leader-only for_each_process walk. exit_group tears down every thread
* including the leader, so a process death always reaches here via the
* leader; non-leader thread exits are intentionally not surfaced. */
if (!thread_group_leader(t))
return 0;

memset(&event, 0, sizeof(event));
event.pid = task_tgid_vnr(t);
event.tid = task_pid_vnr(t);
event.create_time = t->start_time; /* identity: pairs w/ ProcStart */
event.code = (int64_t)(long)regs_get_argument(regs, 0); /* do_exit(long code) */
strncpy(event.comm, t->comm, TASK_COMM_LEN);

do_exit_hyp(&event);
return 0;
}

int exit_hc_init(void) {
/* Nothing to arm at boot: the kprobe is installed lazily on the first host
* enable() so an un-opted-in run pays zero cost (no probe, no per-exit
* trap). Prep the probe descriptor only. */
memset(&exit_kp, 0, sizeof(exit_kp));
exit_kp.symbol_name = "do_exit";
exit_kp.pre_handler = exit_pre_handler;
exit_kp_registered = false;
return 0;
}

/* Host enable/disable. Lazily arm/disarm the kprobe so the disabled state is
* free. Portal op handlers run in the (sleepable) portal loop context, so
* register_kprobe()/unregister_kprobe() are safe to call here. No per-process
* filtering: the model wants every user-process death. */
void handle_op_register_exit_hook(portal_region *mem_region) {
int ret = 0;

if (!exit_kp_registered) {
ret = register_kprobe(&exit_kp);
if (ret < 0)
/* do_exit is NOKPROBE-blacklisted on some kernels; degrade
* gracefully (no exit events). If this ever bites a target kernel,
* switch symbol_name to the sched_process_exit tracepoint, which is
* never blacklisted. */
printk(KERN_ERR "IGLOO: Failed to register kprobe on do_exit: %d "
"(exit events unavailable)\n", ret);
else {
exit_kp_registered = true;
printk(KERN_INFO "IGLOO: Armed exit kprobe on do_exit\n");
}
}

mem_region->header.op = (ret < 0) ? HYPER_RESP_WRITE_FAIL
: HYPER_RESP_WRITE_OK;
}

void handle_op_unregister_exit_hook(portal_region *mem_region) {
if (exit_kp_registered) {
unregister_kprobe(&exit_kp);
exit_kp_registered = false;
}
mem_region->header.op = HYPER_RESP_WRITE_OK;
}
33 changes: 33 additions & 0 deletions src/hooks/exit_hc.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#ifndef _EXIT_HC_H
#define _EXIT_HC_H

#include <linux/types.h>
#include <linux/sched.h>
#include "portal/portal_types.h"

/*
* Emitted once per user-process death, from a kprobe on do_exit(). Unlike the
* exit/exit_group *syscall* hooks, this fires on the kernel task-exit path, so
* it also captures deaths that never issue an exit syscall -- fatal signals
* (SIGSEGV/SIGKILL/...), OOM kills, and kernel-forced exits -- with the real
* exit code. ``code`` is the raw do_exit() argument in wait(2) status encoding:
* (code & 0x7f) != 0 -> killed by signal (code & 0x7f); 0x80 bit = core dump
* else -> exited normally, status = (code >> 8) & 0xff
* so the host decodes WIFSIGNALED / WIFEXITED without guessing.
*
* ``create_time`` is task->start_time -- the same value osi_proc_node uses as
* process identity -- so a ProcExit pairs exactly with its ProcStart.
*/
struct exit_event {
uint64_t pid; /* thread-group id = process id (namespaced) */
uint64_t tid; /* task pid = thread id (namespaced) */
uint64_t create_time; /* task->start_time; identity, pairs w/ start */
int64_t code; /* raw do_exit() code, wait-status encoded */
char comm[TASK_COMM_LEN]; /* dying task comm */
};

int exit_hc_init(void);
void handle_op_register_exit_hook(portal_region *mem_region);
void handle_op_unregister_exit_hook(portal_region *mem_region);

#endif /* _EXIT_HC_H */
6 changes: 6 additions & 0 deletions src/igloo_hc.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ int uname_hc_init(void);
int block_mounts_init(void);
int igloo_open_init(void);
int signal_hc_init(void);
int exit_hc_init(void);

/* Register probes for mmap and munmap */
int init_module(void) {
Expand All @@ -58,6 +59,11 @@ int init_module(void) {
return ret;
}

if ((ret = exit_hc_init()) != 0) {
printk(KERN_ERR "Failed to register exit_hc returning %d\n", ret);
return ret;
}

if ((ret = ioctl_hc_init()) != 0) {
printk(KERN_ERR "Failed to register ioctl_hc returning %d\n", ret);
return ret;
Expand Down
1 change: 1 addition & 0 deletions src/igloo_hypercall_consts.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ enum igloo_hypercall_constants {
IGLOO_HYP_SYSCALL_RETURN = 0x1339,
IGLOO_HYP_SETUP_TASK_COMM = 0x133a,
IGLOO_HYP_SIGNAL_DELIVER = 0x133b,
IGLOO_HYP_PROC_EXIT = 0x133c,

/* Uprobe operations */
IGLOO_HYP_UPROBE_ENTER = 0x6901,
Expand Down
2 changes: 2 additions & 0 deletions src/portal/portal_op_list.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
X(set_portalcall_fastpath, SET_PORTALCALL_FASTPATH) \
X(register_signal_hook, REGISTER_SIGNAL_HOOK) \
X(unregister_signal_hook, UNREGISTER_SIGNAL_HOOK) \
X(register_exit_hook, REGISTER_EXIT_HOOK) \
X(unregister_exit_hook, UNREGISTER_EXIT_HOOK) \
X(ffi_exec, FFI_EXEC) \
X(kallsyms_lookup, KALLSYMS_LOOKUP) \
X(tramp_generate, TRAMP_GENERATE) \
Expand Down
Loading