Skip to content
Merged
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: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ vmlinux.h
*.o
*.skel.h
sigsegv_monitor
sample_segfault
__pycache__
28 changes: 24 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,19 @@
CLANG ?= clang
BPFTOOL ?= bpftool

# Git version info (build fails if not available)
GIT_REV := $(shell git rev-parse --short HEAD)
GIT_DATE := $(shell git log -1 --format=%cI)
ifeq ($(GIT_REV),)
$(error GIT_REV is not set - not in a git repository?)
endif
ifeq ($(GIT_DATE),)
$(error GIT_DATE is not set - not in a git repository?)
endif

# Output executable name
APP = sigsegv_monitor
SAMPLE = sample_segfault

# Source files
BPF_SRC = sigsegv-monitor.bpf.c
Expand All @@ -17,15 +28,20 @@ VMLINUX = vmlinux.h
# Compiler flags
# -g: Debug info (required for BTF)
# -O2: Optimization (required for BPF)
CFLAGS := -g -O2 -Wall
CFLAGS := -g -O2 -Wall -DGIT_REV=\"$(GIT_REV)\" -DGIT_DATE=\"$(GIT_DATE)\"
BPF_CFLAGS := -g -O2 -target bpf -D__TARGET_ARCH_x86

# Libs to link
LIBS := -lbpf -lelf -lz

.PHONY: all clean
.PHONY: all clean sample test

all: $(APP) $(SAMPLE)

sample: $(SAMPLE)

all: $(APP)
test: $(SAMPLE)
sudo python3 verify_monitor.py

.DELETE_ON_ERROR:

Expand All @@ -45,6 +61,10 @@ $(APP): $(USER_SRC) $(SKEL_OBJ)
@echo " CC $@"
$(CLANG) $(CFLAGS) $(USER_SRC) $(LIBS) -o $@

$(SAMPLE): sample_segfault.c
@echo " CC $@"
$(CLANG) -g -O0 -Wall $< -o $@

clean:
@echo " CLEAN"
rm -f $(APP) $(BPF_OBJ) $(SKEL_OBJ) $(VMLINUX)
rm -f $(APP) $(BPF_OBJ) $(SKEL_OBJ) $(VMLINUX) $(SAMPLE)
79 changes: 79 additions & 0 deletions sample_segfault.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Sample program for sigsegv-monitor testing.
*
* This program:
* 1. Allocates several memory pages using mmap()
* 2. Touches each page to trigger page faults
* 3. Dereferences a null pointer to trigger SIGSEGV
*
* The sigsegv-monitor should capture:
* - Multiple page fault events (recorded in page_faults array)
* - One SIGSEGV event with cr2 = 0 (null pointer)
*/

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>

#define NUM_PAGES 4

/* Prevent compiler from optimizing away memory accesses */
volatile int sink;

int main(int argc, char *argv[]) {
void *pages[NUM_PAGES];
long page_size = sysconf(_SC_PAGESIZE);

fprintf(stderr, "[sample_segfault] PID: %d\n", getpid());
fprintf(stderr, "[sample_segfault] System page size: %ld\n", page_size);
fprintf(stderr, "[sample_segfault] Allocating %d pages...\n", NUM_PAGES);

/* Allocate pages - these won't cause page faults yet (no physical memory assigned) */
for (int i = 0; i < NUM_PAGES; i++) {
pages[i] = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (pages[i] == MAP_FAILED) {
perror("mmap");
return 1;
}
fprintf(stderr, "[sample_segfault] Page %d allocated at %p\n", i, pages[i]);
}

fprintf(stderr, "[sample_segfault] Touching pages to trigger page faults...\n");

/* Touch each page to trigger page faults (first access causes PF) */
for (int i = 0; i < NUM_PAGES; i++) {
volatile char *ptr = (volatile char *)pages[i];
/* Write to trigger page fault */
*ptr = (char)(i + 1);
/* Read to ensure the write actually happened */
sink = *ptr;
fprintf(stderr, "[sample_segfault] Page %d touched at %p (wrote %d)\n",
i, pages[i], i + 1);
}

/* Output JSON on stdout with the expected page addresses for verification */
printf("{\"page_size\":%ld,\"pages\":[", page_size);
for (int i = 0; i < NUM_PAGES; i++) {
if (i > 0) printf(",");
printf("\"0x%lx\"", (unsigned long)pages[i]);
}
printf("],\"segfault_addr\":\"0x0\"}\n");
fflush(stdout);

/* Small delay to ensure page faults are recorded */
usleep(10000);

fprintf(stderr, "[sample_segfault] Triggering SIGSEGV via null pointer dereference...\n");

/* Trigger SIGSEGV by dereferencing null pointer */
volatile int *null_ptr = NULL;
sink = *null_ptr; /* This will cause SIGSEGV with cr2 = 0 */

/* Should never reach here */
fprintf(stderr, "[sample_segfault] ERROR: Should have crashed!\n");
return 1;
}
48 changes: 27 additions & 21 deletions sigsegv-monitor.bpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,19 +57,14 @@ inline void cr2stats_push(struct cr2_stats* stats, struct cr2_stat* value) {

// The `index` parameter here is not an index in the array, but an index in the ring buffer,
// i.e. passing an index 0 would return the oldest element in the ring buffer.
inline struct cr2_stat* cr2stats_get(struct cr2_stats* stats, u32 index) {
inline struct cr2_stat* cr2stats_get(struct cr2_stats* stats, u64 index) {
if (stats->count == MAX_USER_PF_ENTRIES) {
index += stats->head;
if (index >= MAX_USER_PF_ENTRIES) {
index -= MAX_USER_PF_ENTRIES;
}
}

if (index < MAX_USER_PF_ENTRIES) {
return stats->stat + index;
index += stats->head; // this makes index unbounded to the verifier
}

return NULL;
// establish bound for index; also helps if above index += ... needs to wrap around
index %= MAX_USER_PF_ENTRIES;
return stats->stat + index;
}
#endif

Expand Down Expand Up @@ -166,12 +161,24 @@ int trace_sigsegv(struct trace_event_raw_signal_generate *ctx) {
}

event->pf_count = 0;
#ifdef TRACE_PF_CR2
u32 pid = task->pid;
#ifdef TRACE_PF_CR2
u32 const pid = task->pid;
struct cr2_stats *cr2stats = bpf_map_lookup_elem(&pid_cr2, &pid);

if (cr2stats) {
for (u32 i = 0; i < cr2stats->count && i < MAX_USER_PF_ENTRIES; i++) {
/* If we use a u32 for i, the verifier loses track of its value and rejects the program:
* 151: (bf) r4 = r5 ; R4_w=scalar(id=4) R5_w=scalar(id=4)
* ...
* 156: (67) r4 <<= 32 ; R4_w=scalar(smax=9223372032559808512,umax=18446744069414584320,var_off=(0x0; 0xffffffff00000000),s32_min=0,s32_max=0,u32_max=0)
* 157: (77) r4 >>= 32 ; R4_w=scalar(umax=4294967295,var_off=(0x0; 0xffffffff))
* 158: (27) r4 *= 24 ; R4_w=scalar(umax=103079215080,var_off=(0x0; 0x1ffffffff8),s32_max=2147483640,u32_max=-8)
* 159: (bf) r5 = r0 ; R0=map_value(off=0,ks=4,vs=400,imm=0) R5_w=map_value(off=0,ks=4,vs=400,imm=0)
* 160: (0f) r5 += r4 ; R4_w=scalar(umax=103079215080,var_off=(0x0; 0x1ffffffff8),s32_max=2147483640,u32_max=-8) R5_w=map_value(off=0,ks=4,vs=400,umax=103079215080,var_off=(0x0; 0x1ffffffff8),s32_max=2147483640,u32_max=-8)
* ; event->pf[i].cr2 = stat->cr2;
* 161: (79) r4 = *(u64 *)(r5 +0)
* R5 unbounded memory access, make sure to bounds check any such access
*/
for (u64 i = 0; i < cr2stats->count && i < MAX_USER_PF_ENTRIES; i++) {
struct cr2_stat* stat = cr2stats_get(cr2stats, i);
if (stat) {
event->pf[i].cr2 = stat->cr2;
Expand All @@ -184,7 +191,7 @@ int trace_sigsegv(struct trace_event_raw_signal_generate *ctx) {

bpf_map_delete_elem(&pid_cr2, &pid);
}
#endif
#endif

// TODO: when is this snapshot taken? or does the CPU not do LBR in the kernel?
long ret = bpf_get_branch_snapshot(&event->lbr, sizeof(event->lbr), 0);
Expand All @@ -203,13 +210,12 @@ int trace_sigsegv(struct trace_event_raw_signal_generate *ctx) {
#ifdef TRACE_PF_CR2
SEC("tracepoint/exceptions/page_fault_user")
int trace_page_fault(struct trace_event_raw_page_fault_user *ctx) {
struct cr2_stat stat;
u32 pid;

stat.cr2 = ctx->address;
stat.err = ctx->error_code;
stat.tai = bpf_ktime_get_tai_ns();
pid = (u32)bpf_get_current_pid_tgid();
struct cr2_stat stat = {
.cr2 = ctx->address,
.err = ctx->error_code,
.tai = bpf_ktime_get_tai_ns()
};
u32 const pid = (u32)bpf_get_current_pid_tgid();

struct cr2_stats *cr2stats = bpf_map_lookup_elem(&pid_cr2, &pid);
if (cr2stats) {
Expand Down
14 changes: 13 additions & 1 deletion sigsegv-monitor.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
Expand Down Expand Up @@ -140,7 +141,18 @@ void clean() {
free(cpus_fd);
}

int main() {
void print_version(char const* prefix, FILE* out) {
fprintf(out, "%scommit %s committed %s\n", prefix, GIT_REV, GIT_DATE);
}

int main(int argc, char *argv[]) {
if (argc > 1 && (strcmp(argv[1], "-v") == 0 || strcmp(argv[1], "--version") == 0)) {
print_version("", stdout);
return 0;
} else {
print_version("[*] version ", stderr);
}

struct sigsegv_monitor_bpf *skel;
struct perf_buffer *pb = NULL;

Expand Down
Loading