From c20246ad0a9a85b61c4011a32d7dfaa7054cf2cb Mon Sep 17 00:00:00 2001 From: yexinw Date: Mon, 27 Jul 2026 00:10:11 +0000 Subject: [PATCH] unotify: trace statx Add statx to the seccomp user-notify traced-syscall table (unotify/syscall_defs.h::kTracedSyscalls). That table is the single source of truth -- the kafel policy string and the argument decoder are both derived from it -- so this one entry is the whole change. Modern glibc/coreutils (ls, stat) issue statx before falling back to newfstatat/openat when probing a path. Because statx was not traced, a stat of a path that is not present in the jail's mount namespace was never observed at all: under a restrictive mount set, `ls /opt` and `stat /opt` produced no access record, while openat-based reads (cat, head) were recorded normally. Tracing statx closes that gap. statx(dirfd, path, flags, mask, statxbuf) has arg0 = dirfd and arg1 = path, the same shape as newfstatat, so it reuses the existing DIRFD/PATH arg roles. Guarded by #ifdef __NR_statx (defined on x86_64 and arm64). kafel already knows the statx syscall, so no policy-side change is needed. Tested by running a command under --seccomp_unotify with a mount set that excludes /opt. Before: `stat /opt` and `ls /opt` did not appear in the report. After: /opt appears with exists_in_jail:false, exists_in_main:true via a statx syscall, while a genuinely missing path still reports exists_in_main:false and an allowlisted path still reports exists_in_jail:true. --- unotify/syscall_defs.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/unotify/syscall_defs.h b/unotify/syscall_defs.h index 701b8c0..754ca87 100644 --- a/unotify/syscall_defs.h +++ b/unotify/syscall_defs.h @@ -138,6 +138,16 @@ static constexpr SyscallDef kTracedSyscalls[] = { {__NR_newfstatat, "newfstatat", "newfstatat", SyscallCategory::FS, {A::DIRFD, A::PATH, A::SKIP, A::SKIP, A::SKIP, A::SKIP}}, #endif /* __NR_newfstatat */ + /* + * statx(dirfd, path, flags, mask, statxbuf). Modern glibc/coreutils + * (ls, stat) call statx before newfstatat/openat, so without this entry + * a stat of a path that is not mounted in the jail is never observed. + * arg1 is the path (arg0 is the dirfd), same shape as newfstatat. + */ +#ifdef __NR_statx + {__NR_statx, "statx", "statx", SyscallCategory::FS, + {A::DIRFD, A::PATH, A::SKIP, A::SKIP, A::SKIP, A::SKIP}}, +#endif /* __NR_statx */ #ifdef __NR_faccessat {__NR_faccessat, "faccessat", "faccessat", SyscallCategory::FS, {A::DIRFD, A::PATH, A::ACCESS, A::SKIP, A::SKIP, A::SKIP}},