1 /* 2 * seccomp syscall tracer for ujail 3 * 4 * Copyright (C) 2026 Daniel Golle <daniel@makrotopia.org> 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU Lesser General Public License version 2.1 8 * as published by the Free Software Foundation 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 */ 15 #define _GNU_SOURCE 16 17 #include <errno.h> 18 #include <fcntl.h> 19 #include <signal.h> 20 #include <stdint.h> 21 #include <stdio.h> 22 #include <stdlib.h> 23 #include <string.h> 24 #include <unistd.h> 25 #include <sys/ptrace.h> 26 #include <sys/syscall.h> 27 #include <sys/types.h> 28 #include <sys/wait.h> 29 30 #include <libubox/blob.h> 31 #include <libubox/blobmsg.h> 32 #include <libubox/blobmsg_json.h> 33 #include <libubox/list.h> 34 #include <libubox/utils.h> 35 36 #include <udebug.h> 37 38 #include "log.h" 39 #include "seccomp-inject.h" 40 #include "seccomp-trace.h" 41 #include "../syscall-names.h" 42 43 #ifndef __WALL 44 #define __WALL 0x40000000 45 #endif 46 47 #ifndef PTRACE_EVENT_EXEC 48 #define PTRACE_EVENT_EXEC 4 49 #endif 50 51 #ifndef PTRACE_EVENT_SECCOMP 52 #define PTRACE_EVENT_SECCOMP 7 53 #endif 54 55 #define SECCOMP_TRACE_KILL_DATA 0xffffU 56 57 enum trace_phase { 58 PHASE_LINKER = 0, 59 PHASE_INIT, 60 PHASE_APP, 61 }; 62 63 #define TRACE_NSYS 1024 64 65 struct trace_proc { 66 struct list_head list; 67 pid_t pid; 68 int phase; 69 int in_syscall; 70 int expect_main; 71 int main_argidx; 72 int errno_pending; 73 int pending_errno; 74 char comm[24]; 75 struct seccomp_bp bp_at_entry; 76 struct seccomp_bp bp_lsm; 77 struct seccomp_bp bp_main; 78 }; 79 80 static LIST_HEAD(trace_procs); 81 static int trace_nprocs; 82 83 static struct udebug ud; 84 static struct udebug_buf udb; 85 static struct udebug_buf_meta ring_meta; 86 static char ring_name[64]; 87 static int udebug_ready; 88 89 static struct blob_buf event_b; 90 static int trace_mode; 91 static int trace_log_fd = -1; 92 static int trace_dedup; 93 static int trace_main_boundary; 94 95 static uint32_t seen[3][TRACE_NSYS / 32]; 96 97 static const char *phase_name(int phase) 98 { 99 switch (phase) { 100 case PHASE_LINKER: 101 return "linker"; 102 case PHASE_INIT: 103 return "init"; 104 default: 105 return "app"; 106 } 107 } 108 109 static int dedup_seen(int phase, long nr) 110 { 111 uint32_t bit; 112 113 if (!trace_dedup) 114 return 0; 115 if (nr < 0 || nr >= TRACE_NSYS || phase < 0 || phase > 2) 116 return 0; 117 118 bit = 1u << (nr & 31); 119 if (seen[phase][nr / 32] & bit) 120 return 1; 121 122 seen[phase][nr / 32] |= bit; 123 return 0; 124 } 125 126 static void read_comm(pid_t pid, char *buf, size_t len) 127 { 128 char path[32]; 129 ssize_t rd; 130 int fd; 131 132 buf[0] = '\0'; 133 snprintf(path, sizeof(path), "/proc/%d/comm", (int)pid); 134 fd = open(path, O_RDONLY); 135 if (fd < 0) 136 return; 137 138 rd = read(fd, buf, len - 1); 139 close(fd); 140 if (rd <= 0) { 141 buf[0] = '\0'; 142 return; 143 } 144 145 buf[rd] = '\0'; 146 if (buf[rd - 1] == '\n') 147 buf[rd - 1] = '\0'; 148 } 149 150 static void trace_emit(void) 151 { 152 char *json; 153 154 if (udebug_ready) { 155 udebug_entry_init(&udb); 156 udebug_entry_append(&udb, blob_data(event_b.head), 157 blob_len(event_b.head)); 158 udebug_entry_add(&udb); 159 } 160 161 if (trace_log_fd >= 0) { 162 json = blobmsg_format_json(event_b.head, true); 163 if (json) { 164 dprintf(trace_log_fd, "%s\n", json); 165 free(json); 166 } 167 } 168 } 169 170 static void emit_marker(struct trace_proc *p, const char *event) 171 { 172 blob_buf_init(&event_b, 0); 173 blobmsg_add_string(&event_b, "event", event); 174 blobmsg_add_u32(&event_b, "pid", p->pid); 175 if (p->comm[0]) 176 blobmsg_add_string(&event_b, "comm", p->comm); 177 trace_emit(); 178 } 179 180 static void emit_event(struct trace_proc *p, long nr, long *args, 181 const char *action, int errnoval) 182 { 183 const char *name; 184 void *arr; 185 int i; 186 187 blob_buf_init(&event_b, 0); 188 blobmsg_add_string(&event_b, "event", "syscall"); 189 blobmsg_add_u32(&event_b, "pid", p->pid); 190 if (p->comm[0]) 191 blobmsg_add_string(&event_b, "comm", p->comm); 192 blobmsg_add_string(&event_b, "phase", phase_name(p->phase)); 193 blobmsg_add_u32(&event_b, "nr", (uint32_t)nr); 194 195 name = syscall_name((unsigned)nr); 196 if (name) 197 blobmsg_add_string(&event_b, "syscall", name); 198 199 arr = blobmsg_open_array(&event_b, "args"); 200 for (i = 0; i < 6; i++) 201 blobmsg_add_u64(&event_b, NULL, (uint64_t)(unsigned long)args[i]); 202 blobmsg_close_array(&event_b, arr); 203 204 blobmsg_add_string(&event_b, "action", action); 205 if (errnoval >= 0) 206 blobmsg_add_u32(&event_b, "errno", (uint32_t)errnoval); 207 trace_emit(); 208 } 209 210 static void emit_syscall(struct trace_proc *p) 211 { 212 long nr, args[6]; 213 214 if (seccomp_read_syscall(p->pid, &nr, args)) 215 return; 216 if (dedup_seen(p->phase, nr)) 217 return; 218 219 emit_event(p, nr, args, "allow", -1); 220 } 221 222 static struct trace_proc *proc_get(pid_t pid) 223 { 224 struct trace_proc *p; 225 226 list_for_each_entry(p, &trace_procs, list) 227 if (p->pid == pid) 228 return p; 229 230 return NULL; 231 } 232 233 static struct trace_proc *proc_new(pid_t pid) 234 { 235 struct trace_proc *p; 236 237 p = calloc(1, sizeof(*p)); 238 if (!p) 239 return NULL; 240 241 p->pid = pid; 242 p->phase = PHASE_APP; 243 list_add_tail(&p->list, &trace_procs); 244 trace_nprocs++; 245 246 return p; 247 } 248 249 static void proc_del(struct trace_proc *p) 250 { 251 list_del(&p->list); 252 free(p); 253 trace_nprocs--; 254 } 255 256 static void proc_arm_markers(struct trace_proc *p) 257 { 258 unsigned long at_entry, lsm; 259 260 p->expect_main = 0; 261 p->main_argidx = 0; 262 p->bp_at_entry.armed = 0; 263 p->bp_lsm.armed = 0; 264 p->bp_main.armed = 0; 265 266 if (seccomp_marker_addrs(p->pid, &at_entry, &lsm, &p->main_argidx)) 267 return; 268 269 if (at_entry) 270 seccomp_bp_arm(p->pid, at_entry, &p->bp_at_entry); 271 } 272 273 static void proc_setopts(struct trace_proc *p) 274 { 275 unsigned long opt = PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEFORK | 276 PTRACE_O_TRACEVFORK | PTRACE_O_TRACECLONE | 277 PTRACE_O_TRACEEXEC; 278 279 if (trace_mode != SECCOMP_MODE_TRACE) 280 opt |= PTRACE_O_TRACESECCOMP; 281 282 ptrace(PTRACE_SETOPTIONS, p->pid, 0, (void *)opt); 283 } 284 285 static void proc_start_root(struct trace_proc *p) 286 { 287 proc_setopts(p); 288 read_comm(p->pid, p->comm, sizeof(p->comm)); 289 p->in_syscall = 0; 290 291 if (trace_mode == SECCOMP_MODE_TRACE) { 292 p->phase = PHASE_LINKER; 293 proc_arm_markers(p); 294 } else { 295 p->phase = PHASE_APP; 296 } 297 } 298 299 static void proc_start_child(struct trace_proc *p) 300 { 301 proc_setopts(p); 302 read_comm(p->pid, p->comm, sizeof(p->comm)); 303 p->phase = PHASE_APP; 304 p->in_syscall = 0; 305 } 306 307 static void proc_exec(struct trace_proc *p) 308 { 309 read_comm(p->pid, p->comm, sizeof(p->comm)); 310 p->in_syscall = 0; 311 312 if (trace_mode == SECCOMP_MODE_TRACE) { 313 p->phase = PHASE_LINKER; 314 proc_arm_markers(p); 315 } 316 } 317 318 static void handle_bp(struct trace_proc *p) 319 { 320 struct seccomp_bp *bps[3]; 321 long nr, args[6]; 322 unsigned long mainaddr, at_entry, lsm; 323 int hit; 324 325 bps[0] = &p->bp_at_entry; 326 bps[1] = &p->bp_lsm; 327 bps[2] = &p->bp_main; 328 329 hit = seccomp_bp_match(p->pid, bps, 3); 330 switch (hit) { 331 case 0: 332 emit_marker(p, "at_entry"); 333 if (trace_main_boundary && 334 !seccomp_marker_addrs(p->pid, &at_entry, &lsm, &p->main_argidx) && 335 lsm && !seccomp_bp_arm(p->pid, lsm, &p->bp_lsm)) 336 p->expect_main = 1; 337 p->phase = p->expect_main ? PHASE_INIT : PHASE_APP; 338 break; 339 case 1: 340 if (seccomp_read_syscall(p->pid, &nr, args)) 341 break; 342 mainaddr = (unsigned long)args[p->main_argidx]; 343 if (mainaddr) 344 seccomp_bp_arm(p->pid, mainaddr, &p->bp_main); 345 break; 346 case 2: 347 emit_marker(p, "main"); 348 p->phase = PHASE_APP; 349 break; 350 default: 351 break; 352 } 353 } 354 355 enum seccomp_resume { 356 SECCOMP_RESUME_NORMAL = 0, 357 SECCOMP_RESUME_STEP_EXIT, 358 }; 359 360 static int handle_seccomp(struct trace_proc *p) 361 { 362 long nr, args[6]; 363 unsigned long data = 0; 364 int err, rc; 365 366 ptrace(PTRACE_GETEVENTMSG, p->pid, 0, &data); 367 if (seccomp_read_syscall(p->pid, &nr, args)) 368 return SECCOMP_RESUME_NORMAL; 369 370 if (data == SECCOMP_TRACE_KILL_DATA) { 371 emit_event(p, nr, args, "kill", -1); 372 if (trace_mode == SECCOMP_MODE_AUDIT) 373 kill(p->pid, SIGKILL); 374 return SECCOMP_RESUME_NORMAL; 375 } 376 377 err = (int)data; 378 emit_event(p, nr, args, "deny", err); 379 380 if (trace_mode != SECCOMP_MODE_AUDIT) 381 return SECCOMP_RESUME_NORMAL; 382 383 rc = seccomp_force_errno(p->pid, err); 384 if (rc < 0) { 385 kill(p->pid, SIGKILL); 386 return SECCOMP_RESUME_NORMAL; 387 } 388 if (rc == 0) 389 return SECCOMP_RESUME_NORMAL; 390 391 p->errno_pending = 1; 392 p->pending_errno = err; 393 394 return SECCOMP_RESUME_STEP_EXIT; 395 } 396 397 static void trace_resume(pid_t pid, int sig) 398 { 399 if (trace_mode == SECCOMP_MODE_TRACE) 400 ptrace(PTRACE_SYSCALL, pid, 0, (void *)(long)sig); 401 else 402 ptrace(PTRACE_CONT, pid, 0, (void *)(long)sig); 403 } 404 405 static int udebug_setup(const char *name) 406 { 407 snprintf(ring_name, sizeof(ring_name), "ujail:%s", name ? name : "trace"); 408 ring_meta.name = ring_name; 409 ring_meta.format = UDEBUG_FORMAT_BLOBMSG; 410 411 udebug_init(&ud); 412 udebug_auto_connect(&ud, NULL); 413 if (udebug_buf_init(&udb, 1024, 256 * 1024)) 414 return -1; 415 if (udebug_buf_add(&ud, &udb, &ring_meta)) 416 return -1; 417 418 return 0; 419 } 420 421 static void udebug_teardown(void) 422 { 423 if (!udebug_ready) 424 return; 425 426 udebug_buf_free(&udb); 427 udebug_free(&ud); 428 udebug_ready = 0; 429 } 430 431 int seccomp_trace_run(pid_t pid, const struct seccomp_trace_opts *o) 432 { 433 struct trace_proc *p, *root; 434 int status; 435 pid_t wpid; 436 int sig, ev; 437 438 trace_mode = o->mode; 439 trace_log_fd = o->log_fd; 440 trace_dedup = o->dedup; 441 trace_main_boundary = o->main_boundary; 442 memset(seen, 0, sizeof(seen)); 443 444 if (!udebug_setup(o->name)) 445 udebug_ready = 1; 446 447 root = proc_new(pid); 448 if (!root) { 449 udebug_teardown(); 450 return -1; 451 } 452 453 proc_start_root(root); 454 trace_resume(pid, 0); 455 456 while (trace_nprocs > 0) { 457 wpid = waitpid(-1, &status, __WALL); 458 if (wpid < 0) { 459 if (errno == EINTR) 460 continue; 461 break; 462 } 463 464 p = proc_get(wpid); 465 if (!p) { 466 p = proc_new(wpid); 467 if (!p) 468 continue; 469 proc_start_child(p); 470 trace_resume(wpid, 0); 471 continue; 472 } 473 474 if (WIFEXITED(status) || WIFSIGNALED(status)) { 475 proc_del(p); 476 continue; 477 } 478 479 if (!WIFSTOPPED(status)) { 480 trace_resume(wpid, 0); 481 continue; 482 } 483 484 sig = WSTOPSIG(status); 485 ev = (status >> 16) & 0xff; 486 487 if (ev) { 488 if (ev == PTRACE_EVENT_SECCOMP) { 489 if (handle_seccomp(p) == SECCOMP_RESUME_STEP_EXIT) { 490 ptrace(PTRACE_SYSCALL, wpid, 0, 0); 491 continue; 492 } 493 } else if (ev == PTRACE_EVENT_EXEC) { 494 proc_exec(p); 495 } 496 trace_resume(wpid, 0); 497 continue; 498 } 499 500 if (sig == (SIGTRAP | 0x80)) { 501 if (p->errno_pending) { 502 if (seccomp_force_errno_exit(wpid, p->pending_errno)) 503 kill(wpid, SIGKILL); 504 p->errno_pending = 0; 505 trace_resume(wpid, 0); 506 continue; 507 } 508 if (!p->in_syscall) 509 emit_syscall(p); 510 p->in_syscall = !p->in_syscall; 511 trace_resume(wpid, 0); 512 continue; 513 } 514 515 if (sig == SIGTRAP) { 516 handle_bp(p); 517 trace_resume(wpid, 0); 518 continue; 519 } 520 521 trace_resume(wpid, (int)sig); 522 } 523 524 while (!list_empty(&trace_procs)) { 525 p = list_first_entry(&trace_procs, struct trace_proc, list); 526 proc_del(p); 527 } 528 529 udebug_teardown(); 530 531 return 0; 532 } 533
This page was automatically generated by LXR 0.3.1. • OpenWrt