diff --git a/CHANGES b/CHANGES index 6486347..f88a04c 100644 --- a/CHANGES +++ b/CHANGES @@ -97,3 +97,6 @@ Changes from 0.5 to 0.6 Changes from 0.6 to 0.7 ~~~~~~~~~~~~~~~~~~~~~~~ o BPF_CC is gone, just use CLANG='zig cc' now. + o Added mprotect() syscall events (QUARK_EV_MPROTECT, -u on quark-mon). + The eBPF probe emits successful calls requesting execute permission + (X, RX, WX, or RWX); non-executable masks and failed calls are suppressed. diff --git a/bpf_queue.c b/bpf_queue.c index ff2a7f6..bcde453 100644 --- a/bpf_queue.c +++ b/bpf_queue.c @@ -591,6 +591,24 @@ ebpf_events_to_raw(struct quark_queue *qq, struct ebpf_event_header *ev) break; } + case EBPF_EVENT_PROCESS_MPROTECT: { + struct ebpf_process_mprotect_event *mprotect; + struct quark_mprotect *qmprotect; + + mprotect = (struct ebpf_process_mprotect_event *)ev; + if ((raw = raw_event_alloc(RAW_MPROTECT)) == NULL) + goto bad; + + raw->pid = mprotect->pids.tgid; + raw->time = ev->ts; + + qmprotect = &raw->mprotect.quark_mprotect; + qmprotect->addr = mprotect->addr; + qmprotect->len = mprotect->len; + qmprotect->prot = mprotect->prot; + + break; + } case EBPF_EVENT_PROCESS_LOAD_MODULE: { struct ebpf_process_load_module_event *module; struct quark_module_load *qml; @@ -1255,6 +1273,11 @@ bpf_queue_open1(struct quark_queue *qq, int use_fentry) if (qq->flags & QQ_MODULE_LOAD) bpf_program__set_autoload(p->progs.module_load, 1); + if (qq->flags & QQ_MPROTECT) { + bpf_program__set_autoload(p->progs.tracepoint_syscalls_sys_enter_mprotect, 1); + bpf_program__set_autoload(p->progs.tracepoint_syscalls_sys_exit_mprotect, 1); + } + if (qq->flags & QQ_GETPID) bpf_program__set_autoload(p->progs.tracepoint_syscalls_sys_exit_getpid, 1); diff --git a/elastic-ebpf/GPL/Events/EbpfEventProto.h b/elastic-ebpf/GPL/Events/EbpfEventProto.h index f7216e2..1b1f285 100644 --- a/elastic-ebpf/GPL/Events/EbpfEventProto.h +++ b/elastic-ebpf/GPL/Events/EbpfEventProto.h @@ -46,6 +46,7 @@ enum ebpf_event_type { EBPF_EVENT_PROCESS_LOAD_MODULE = (1 << 19), EBPF_EVENT_NETWORK_DNS_PKT = (1 << 20), EBPF_EVENT_PROCESS_GETPID = (1 << 21), + EBPF_EVENT_PROCESS_MPROTECT = (1 << 22), }; struct ebpf_event_header { @@ -380,6 +381,14 @@ struct ebpf_process_load_module_event { struct ebpf_varlen_fields_start vl_fields; } __attribute__((packed)); +struct ebpf_process_mprotect_event { + struct ebpf_event_header hdr; + struct ebpf_pid_info pids; + uint64_t addr; + uint64_t len; + uint64_t prot; +} __attribute__((packed)); + enum ebpf_net_info_transport { EBPF_NETWORK_EVENT_TRANSPORT_TCP = 1, EBPF_NETWORK_EVENT_TRANSPORT_UDP = 2, diff --git a/elastic-ebpf/GPL/Events/Process/Probe.bpf.c b/elastic-ebpf/GPL/Events/Process/Probe.bpf.c index f93006d..8e7ebc4 100644 --- a/elastic-ebpf/GPL/Events/Process/Probe.bpf.c +++ b/elastic-ebpf/GPL/Events/Process/Probe.bpf.c @@ -24,6 +24,7 @@ DECL_FIELD_OFFSET(iov_iter, __iov); #define S_ISUID 0004000 #define S_ISGID 0002000 +#define PROT_EXEC 0x4 SEC("tp_btf/sched_process_fork") int BPF_PROG(sched_process_fork, const struct task_struct *parent, const struct task_struct *child) @@ -450,6 +451,85 @@ int BPF_KPROBE(kprobe__arch_ptrace, return r; } +SEC("tracepoint/syscalls/sys_enter_mprotect") +int tracepoint_syscalls_sys_enter_mprotect(struct syscall_trace_enter *ctx) +{ + preempt_disable(); + if (ebpf_events_is_trusted_pid()) + goto out; + + struct mprotect_args { + short common_type; + char common_flags; + char common_preempt_count; + int common_pid; + int __syscall_nr; + unsigned long start; + size_t len; + unsigned long prot; + }; + struct mprotect_args *ex_args = (struct mprotect_args *)ctx; + const struct task_struct *task = (struct task_struct *)bpf_get_current_task(); + + if (is_kernel_thread(task)) + goto out; + + if (!(ex_args->prot & PROT_EXEC)) + goto out; + + struct ebpf_events_state state = {}; + state.mprotect.addr = ex_args->start; + state.mprotect.len = ex_args->len; + state.mprotect.prot = ex_args->prot; + ebpf_events_state__set(EBPF_EVENTS_STATE_MPROTECT, &state); + +out: + preempt_enable(); + return 0; +} + +SEC("tracepoint/syscalls/sys_exit_mprotect") +int tracepoint_syscalls_sys_exit_mprotect(struct syscall_trace_exit *args) +{ + preempt_disable(); + + struct ebpf_events_state *state = ebpf_events_state__get(EBPF_EVENTS_STATE_MPROTECT); + if (!state) + goto out; + + u64 addr = state->mprotect.addr; + u64 len = state->mprotect.len; + u64 prot = state->mprotect.prot; + ebpf_events_state__del(EBPF_EVENTS_STATE_MPROTECT); + + if (BPF_CORE_READ(args, ret) != 0) + goto out; + + if (ebpf_events_is_trusted_pid()) + goto out; + + const struct task_struct *task = (struct task_struct *)bpf_get_current_task(); + if (is_kernel_thread(task)) + goto out; + + struct ebpf_process_mprotect_event *event = bpf_ringbuf_reserve(&ringbuf, sizeof(*event), 0); + if (!event) + goto out; + + event->hdr.type = EBPF_EVENT_PROCESS_MPROTECT; + event->hdr.ts = bpf_ktime_get_boot_ns(); + ebpf_pid_info__fill(&event->pids, task); + + event->addr = addr; + event->len = len; + event->prot = prot; + + bpf_ringbuf_submit(event, 0); +out: + preempt_enable(); + return 0; +} + SEC("tracepoint/syscalls/sys_enter_shmget") int tracepoint_syscalls_sys_enter_shmget(struct syscall_trace_enter *ctx) { diff --git a/elastic-ebpf/GPL/Events/State.h b/elastic-ebpf/GPL/Events/State.h index 55a100c..50e3944 100644 --- a/elastic-ebpf/GPL/Events/State.h +++ b/elastic-ebpf/GPL/Events/State.h @@ -23,6 +23,7 @@ enum ebpf_events_state_op { EBPF_EVENTS_STATE_CHOWN = 9, EBPF_EVENTS_STATE_FS_CREATE = 10, EBPF_EVENTS_STATE_MEMFD_CREATE = 11, + EBPF_EVENTS_STATE_MPROTECT = 12, }; struct ebpf_events_key { @@ -86,6 +87,12 @@ struct ebpf_events_memfd_create_state { unsigned int flags; }; +struct ebpf_events_mprotect_state { + u64 addr; + u64 len; + u64 prot; +}; + struct ebpf_events_state { union { struct ebpf_events_unlink_state unlink; @@ -98,6 +105,7 @@ struct ebpf_events_state { struct ebpf_events_writev_state writev; struct ebpf_events_chown_state chown; struct ebpf_events_memfd_create_state memfd; + struct ebpf_events_mprotect_state mprotect; /* struct ebpf_events_fs_create fs_create; nada */ }; }; diff --git a/quark-mon.8 b/quark-mon.8 index a8f503f..ed3bd37 100644 --- a/quark-mon.8 +++ b/quark-mon.8 @@ -6,7 +6,7 @@ .Nd monitor and print quark events .Sh SYNOPSIS .Nm quark-mon -.Op Fl BbDEeFGgHhkMNnSsTtv +.Op Fl BbDEeFGgHhkMNnSsTtuv .Op Fl C Ar filename .Op Fl K Ar kubeconfig .Op Fl l Ar maxlength @@ -130,6 +130,8 @@ Enable ptrace event tracing. .It Fl t Don't supress thread events, this is only useful for debugging and will likely be zapped in the future. +.It Fl u +Enable mprotect events. .It Fl v Increase verbosity, can be specified multiple times for more verbosity. .It Fl V diff --git a/quark-mon.c b/quark-mon.c index cf52262..6e4e044 100644 --- a/quark-mon.c +++ b/quark-mon.c @@ -117,7 +117,7 @@ static void usage(void) { fprintf(stderr, "usage: %s -h\n", program_invocation_short_name); - fprintf(stderr, "usage: %s [-BbDeFGgHhkLMNnSsTtv]\n", + fprintf(stderr, "usage: %s [-BbDeFGgHhkLMNnSsTtuv]\n", program_invocation_short_name); fprintf(stderr, "%16c [-C filename ] [-K kubeconfig] " "[-l maxlength] [-m maxnodes]\n", ' '); @@ -185,7 +185,7 @@ main(int argc, char *argv[]) !strcmp(argv[1], "help"))) display_man(); - while ((ch = getopt(argc, argv, "BbC:DEeFGgHhK:kLl:Mm:NnP:Ttr:SsvV")) != -1) { + while ((ch = getopt(argc, argv, "BbC:DEeFGgHhK:kLl:Mm:NnP:Ttr:SsuvV")) != -1) { const char *errstr; switch (ch) { @@ -309,6 +309,9 @@ main(int argc, char *argv[]) case 'T': qa.flags |= QQ_PTRACE; break; + case 'u': + qa.flags |= QQ_MPROTECT; + break; case 't': qa.flags |= QQ_THREAD_EVENTS; break; diff --git a/quark-test.c b/quark-test.c index 46b391f..5bb2525 100644 --- a/quark-test.c +++ b/quark-test.c @@ -1376,6 +1376,95 @@ t_shmget(const struct test *t, struct quark_queue_attr *qa) return (0); } +static int +t_mprotect(const struct test *t, struct quark_queue_attr *qa) +{ + struct quark_queue qq; + const struct quark_event *qev; + const struct quark_mprotect *qmprotect; + void *addr[4]; + void *suppressed_addr; + const size_t len = 4096; + const int expected[] = { + PROT_EXEC, + PROT_READ | PROT_EXEC, + PROT_WRITE | PROT_EXEC, + PROT_READ | PROT_WRITE | PROT_EXEC, + }; + const int suppressed[] = { + PROT_NONE, + PROT_READ, + PROT_WRITE, + PROT_READ | PROT_WRITE, + }; + int saw[nitems(expected)] = { 0 }; + size_t i, j; + int seen = 0; + + qa->flags |= QQ_MPROTECT; + + if (quark_queue_open(&qq, qa) != 0) + err(1, "quark_queue_open"); + + suppressed_addr = mmap(NULL, len, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (suppressed_addr == MAP_FAILED) + err(1, "mmap"); + + /* Non-executable protections must not enter the ring buffer. */ + for (i = 0; i < nitems(suppressed); i++) { + if (mprotect(suppressed_addr, len, suppressed[i]) == -1) + err(1, "mprotect"); + } + + /* Failed executable requests must not enter the ring buffer. */ + if (mprotect((void *)(uintptr_t)1, len, PROT_EXEC) != -1) + errx(1, "unaligned mprotect unexpectedly succeeded"); + + for (i = 0; i < nitems(expected); i++) { + addr[i] = mmap(NULL, len, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (addr[i] == MAP_FAILED) + err(1, "mmap"); + if (mprotect(addr[i], len, expected[i]) == -1) + err(1, "mprotect"); + } + + while (seen < (int)nitems(expected)) { + qev = drain_for_pid(&qq, getpid()); + if (!(qev->events & QUARK_EV_MPROTECT)) + continue; + qmprotect = &qev->mprotect; + for (i = 0; i < nitems(expected); i++) { + if (qmprotect->prot == (u64)expected[i] && + qmprotect->addr == (u64)(uintptr_t)addr[i]) + break; + } + if (i == nitems(expected)) { + errx(1, "unexpected mprotect prot 0x%llx", + (unsigned long long)qmprotect->prot); + } + assert(qmprotect->len == len); + assert(!saw[i]); + saw[i] = 1; + seen++; + } + + for (i = 0; i < nitems(saw); i++) + assert(saw[i]); + + if (munmap(suppressed_addr, len) == -1) + err(1, "munmap"); + for (j = 0; j < nitems(addr); j++) { + if (munmap(addr[j], len) == -1) + err(1, "munmap"); + } + + quark_queue_close(&qq); + + return (0); +} + static int t_shm_open(const struct test *t, struct quark_queue_attr *qa) { @@ -2441,6 +2530,7 @@ struct test all_tests[] = { T_EBPF(t_memfd), T_EBPF(t_memfd_exec), T_EBPF(t_shmget), + T_EBPF(t_mprotect), T_EBPF(t_shm_open), T_EBPF(t_tty_load), T_EBPF(t_tty), diff --git a/quark.c b/quark.c index 0471401..2ae6444 100644 --- a/quark.c +++ b/quark.c @@ -130,6 +130,7 @@ raw_event_alloc(int type) case RAW_PACKET: /* caller allocates */ case RAW_FILE: /* caller allocates */ case RAW_PTRACE: /* nada */ + case RAW_MPROTECT: /* nada */ case RAW_MODULE_LOAD: /* caller allocates */ case RAW_SHM: /* caller allocates */ case RAW_TTY: /* caller allocates */ @@ -169,6 +170,7 @@ raw_event_free(struct raw_event *raw) case RAW_COMM: /* nada */ case RAW_SOCK_CONN: /* nada */ case RAW_PTRACE: /* nada */ + case RAW_MPROTECT: /* nada */ break; case RAW_PACKET: free(raw->packet.quark_packet); @@ -389,6 +391,7 @@ event_storage_clear(struct quark_queue *qq) free(qq->event_storage.file); qq->event_storage.file = NULL; bzero(&qq->event_storage.ptrace, sizeof(qq->event_storage.ptrace)); + bzero(&qq->event_storage.mprotect, sizeof(qq->event_storage.mprotect)); if (qq->event_storage.module_load != NULL) { free(qq->event_storage.module_load->name); free(qq->event_storage.module_load->version); @@ -1728,6 +1731,8 @@ event_type_str(u64 event) return "TTY"; case QUARK_EV_GETPID: return "GETPID"; + case QUARK_EV_MPROTECT: + return "MPROTECT"; default: return "?"; } @@ -2067,6 +2072,28 @@ module_taints_str(u64 taints, char *buf, size_t len) buf[n] = 0; } +static void +mprotect_prot_str(u64 prot, char *buf, size_t len) +{ + size_t n; + + *buf = 0; + n = 0; + if (prot & 0x1) { + if (n + 1 < len) + buf[n++] = 'R'; + } + if (prot & 0x2) { + if (n + 1 < len) + buf[n++] = 'W'; + } + if (prot & 0x4) { + if (n + 1 < len) + buf[n++] = 'X'; + } + buf[n] = 0; +} + #define P(...) \ do { \ if (fprintf(f, __VA_ARGS__) < 0) \ @@ -2093,6 +2120,7 @@ quark_event_dump(const struct quark_event *qev, FILE *f) const struct quark_container *container; const struct quark_ptrace *ptrace; const struct quark_module_load *qml; + const struct quark_mprotect *mprotect; int pid; if (qev->events == QUARK_EV_BYPASS) { @@ -2191,6 +2219,15 @@ quark_event_dump(const struct quark_event *qev, FILE *f) qml->taints, buf); } + if (qev->events & QUARK_EV_MPROTECT) { + fl = "MPRO"; + + mprotect = &qev->mprotect; + mprotect_prot_str(mprotect->prot, buf, sizeof(buf)); + PF(fl, "addr=0x%llx len=%llu prot=0x%llx (%s)\n", + mprotect->addr, mprotect->len, mprotect->prot, buf); + } + if (qp == NULL) return (-1); @@ -4740,6 +4777,20 @@ raw_event_ptrace(struct quark_queue *qq, struct raw_event *raw) return (qev); } +static struct quark_event * +raw_event_mprotect(struct quark_queue *qq, struct raw_event *raw) +{ + struct quark_event *qev; + + qev = &qq->event_storage; + + qev->events = QUARK_EV_MPROTECT; + qev->process = quark_process_lookup(qq, raw->pid); + qev->mprotect = raw->mprotect.quark_mprotect; + + return (qev); +} + static struct quark_event * raw_event_module_load(struct quark_queue *qq, struct raw_event *raw) { @@ -5160,6 +5211,9 @@ quark_queue_get_event(struct quark_queue *qq) case RAW_PTRACE: qev = raw_event_ptrace(qq, raw); break; + case RAW_MPROTECT: + qev = raw_event_mprotect(qq, raw); + break; case RAW_MODULE_LOAD: qev = raw_event_module_load(qq, raw); break; diff --git a/quark.h b/quark.h index 89eaae4..8d74da7 100644 --- a/quark.h +++ b/quark.h @@ -252,6 +252,7 @@ enum raw_types { RAW_SHM, RAW_TTY, RAW_GETPID, + RAW_MPROTECT, RAW_NUM_TYPES /* must be last */ }; @@ -393,10 +394,20 @@ struct quark_ptrace { u64 data; }; +struct quark_mprotect { + u64 addr; + u64 len; + u64 prot; +}; + struct raw_ptrace { struct quark_ptrace quark_ptrace; }; +struct raw_mprotect { + struct quark_mprotect quark_mprotect; +}; + struct quark_module_load { char *name; char *version; @@ -469,6 +480,7 @@ struct raw_event { struct raw_packet packet; struct raw_file file; struct raw_ptrace ptrace; + struct raw_mprotect mprotect; struct raw_module_load module_load; struct raw_shm shm; struct raw_tty tty; @@ -505,6 +517,7 @@ struct quark_event { #define QUARK_EV_SHM (1 << 12) #define QUARK_EV_TTY (1 << 13) #define QUARK_EV_GETPID (1 << 14) +#define QUARK_EV_MPROTECT (1 << 15) u64 events; u64 time; const struct quark_process *process; @@ -513,6 +526,7 @@ struct quark_event { const void *bypass; struct quark_file *file; struct quark_ptrace ptrace; + struct quark_mprotect mprotect; struct quark_module_load *module_load; struct quark_shm *shm; struct quark_tty *tty; @@ -889,6 +903,7 @@ struct quark_queue_attr { * CLOCK_MONOTONIC, else CLOCK_BOOTTIME. */ #define QQ_MONOTONIC (1 << 15) +#define QQ_MPROTECT (1 << 16) #define QQ_ALL_BACKENDS (QQ_KPROBE | QQ_EBPF) /* QQ_NOVA excluded for now */ int flags; int max_length;