| Age | Commit message (Collapse) | Author |
|
The memory-locking cluster (FamilyMemory) had no end-to-end integration
coverage; mmap_test.go only exercised mmap/msync/mremap/munmap. These
syscalls return UNCLASSIFIED, so enter-tracepoint presence is the right
end-to-end check (mlock/mlock2/munlock are KindMem, mlockall/munlockall
are KindNull).
Add a mmap-memory-lock scenario that, on a single anonymous page (small
enough to stay under RLIMIT_MEMLOCK), issues raw syscalls for mlock,
munlock, mlock2(addr,len,0), mlockall(MCL_CURRENT) and munlockall so the
exact sys_enter_ tracepoints fire. mlock/mlock2/mlockall tolerate
EPERM/ENOMEM best-effort since the enter tracepoint fires before the
error; munlock/munlockall always succeed and clean up.
TestMmapMemoryLock asserts enter_mlock, enter_munlock, enter_mlock2,
enter_mlockall and enter_munlockall each fire (MinCount>=1, Comm
ioworkload). All five captured under sudo.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
mount_setattr(2) was the only new-mount-API sibling not exercised
end-to-end (unlike move_mount/fsmount/fspick/open_tree), and vmsplice(2)
had zero end-to-end assertion despite being TRANSFER_CLASSIFIED.
uj0 (mount_setattr): add a best-effort RawSyscall6(SYS_MOUNT_SETATTR)
call to mountfsManagement() aimed at the scenario mount point with
AT_FDCWD and a MountAttr requesting MOUNT_ATTR_RDONLY. It needs
CAP_SYS_ADMIN (Linux 5.12+) and the path is not a mount, so it returns
EPERM/EINVAL, but its sys_enter_ tracepoint fires on kernel entry before
any check -- the same best-effort pattern used for the other mount-API
calls. Add mount_setattr to mountfsTraceArgs and assert
enter_mount_setattr MinCount>=1 in TestMountFsManagementSyscalls.
bl0 (vmsplice): add a deterministic retbytesVmsplice driver to the
phase-A workload (mirroring the getdents64/readlinkat drivers): a spaced
retry loop that gathers a fixed 18-byte user iovec into a fresh pipe via
vmsplice and drains it each iteration. vmsplice is TRANSFER_CLASSIFIED,
so the exit reports ctx->ret = bytes moved. Add vmsplice to
retbytesTraceArgs and assert enter_vmsplice presence plus
assertEventBytesAtLeast(payloadLen=18) and a positive duration in
TestRetbytesPhaseA, locking in the TRANSFER byte attribution like its
splice/tee siblings.
Coverage hardening only; classification/tracing verified correct by
inspection (mount_setattr=FamilyFS/KindPathname/UNCLASSIFIED,
vmsplice=FamilyNetwork/KindFd/TRANSFER_CLASSIFIED).
Verified: TEST_NAME=TestMountFsManagementSyscalls mage testWithName PASS
(enter_mount_setattr captured); TEST_NAME=TestRetbytesPhaseA mage
testWithName PASS (vmsplice bytes>=18 asserted).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
ioctl (FamilyFS, KindFd@arg0) previously only fired implicitly via the Go
runtime/terminal. Add scenario_ioctl.go issuing a benign FIONREAD ioctl on
an opened temp file (registered as ioctl-basic) and ioctl_test.go asserting
enter_ioctl resolves to the temp file path, mirroring the fcntl suite.
quotactl_fd (FamilyFS, KindFd@arg0) had no coverage while its sibling
quotactl was tested in mountfs. Add a best-effort RawSyscall6 SYS_QUOTACTL_FD
call on an fd opened on the mount point in scenario_mountfs.go, extend
mountfsTraceArgs, and assert enter_quotactl_fd (MinCount>=1). The sys_enter
tracepoint fires on kernel entry regardless of privilege/quota support.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
pidfd_send_signal (FamilyIPC, KindFd@arg0) and fadvise64 (KindFd,
UNCLASSIFIED fd-based hint) previously had no end-to-end integration
coverage despite correct classification/tracing.
pidfd_send_signal: add a pidfd-send-signal ioworkload scenario that
opens a pidfd for the current process and issues a sig-0 liveness probe
(delivers nothing, safe to target self) via syscall.Syscall6 with the
per-arch nr 424. TestPidfdSendSignal asserts enter_pidfd_send_signal is
captured; pidfd_send_signal added to the pidfd -trace-syscalls list.
fadvise64: add readwrite-fadvise64 and readwrite-fadvise64-ebadf
scenarios using unix.Fadvise(fd, 0, 0, FADV_NORMAL), mirroring the
readahead tests. TestReadwriteFadvise64 asserts enter_fadvise64 with
Bytes==0 (UNCLASSIFIED: offset/len are hints, not bytes transferred) and
positive duration; the ebadf variant asserts enter capture with Bytes==0
on the failing call.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Add end-to-end integration scenarios and tests for two previously
untested syscalls:
- mknodat(2): new dir-mknodat-fifo scenario creates an unprivileged
FIFO node (S_IFIFO, no CAP_MKNOD) via unix.Mknodat under AT_FDCWD
and unlinks it. TestDirMknodatFifo asserts enter_mknodat fires with
pathname@args[1] (after dirfd@args[0]), proven by a PathContains
match on the distinct fifo name, mirroring the mkdirat coverage.
- ioprio_get(2)/ioprio_set(2): new ioprio-basic scenario (the
I/O-priority analogues of getpriority/setpriority) issues the raw
syscalls (no x/sys wrapper exists), reading the current self I/O
priority and re-applying it, or a harmless unprivileged best-effort
value when none is set. TestIoprioBasic asserts enter_ioprio_get and
enter_ioprio_set fire (null enters, UNCLASSIFIED ret), mirroring
priority-basic. Realtime class is deliberately avoided as it needs
CAP_SYS_ADMIN.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
setitimer/getitimer (di0): no scenario previously exercised the classic
interval-timer family. Add intervalTimerNoop, which calls
setitimer(ITIMER_REAL, &{0,0,0,0}, NULL) with an all-zero itimerval so the
timer is disarmed and NO SIGALRM is ever scheduled (mirrors miscAlarmCancel's
alarm(0) and posixTimerLifecycle's never-firing pattern), followed by a safe
getitimer read. Both are KindNull on enter / UNCLASSIFIED on exit, so
TestIntervalTimerNoop asserts enter_setitimer and enter_getitimer presence.
statfs/fstatfs (7j0): stat_test.go covered stat/fstat/lstat/newfstatat/statx
but not the statfs family. Add statStatfs, which calls syscall.Statfs(path)
(enter_statfs path_event captures the pathname) and syscall.Fstatfs(fd)
(enter_fstatfs fd_event). TestStatStatfs asserts enter_statfs PathContains the
filename and enter_fstatfs presence. Covers audits it (fstatfs) and e00 (statfs).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Two previously-untested syscalls now have integration coverage:
- getrandom (Security family, READ_CLASSIFIED): new security-getrandom
scenario fills a 32-byte buffer via unix.Getrandom, looping past any
signal-interrupted short reads so the cumulative byte count is strictly
positive. TestSecurityGetrandom asserts enter_getrandom MinCount>=1,
bytes>=1 (locking in the READ byte-count classification end-to-end), and
a positive duration.
- flock (FamilyFS, KindFd@args[0], UNCLASSIFIED): new flock-basic scenario
opens a temp file, takes LOCK_EX then LOCK_UN via syscall.Flock, and
closes it. TestFlockBasic asserts enter_flock with PathContains the temp
filename, confirming the fd resolves to the file path via the procfd
cache.
Both scenarios use raw unix/syscall calls so the exact sys_enter tracepoints
fire, and are registered in cmd/ioworkload/scenarios.go.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Close the integration-test gaps for two classic-AIO syscalls that the
existing scenario never exercised. The AIO workload only drove
io_setup/io_submit/io_destroy, so io_getevents (nr 208) and io_cancel
(nr 210) had no end-to-end coverage despite their tracer classification
(FamilyAIO, KindNull enter, UNCLASSIFIED ret) being correct by inspection.
cmd/ioworkload/scenario_aio.go:
- Factor the submit scaffolding (temp dir, target file, AIO context) into
withAioTarget so each scenario stays short.
- ioSubmitWrite now returns the submitted iocb pointer (io_cancel needs it).
- aio-getevents: submit, then reap the completion with a blocking
io_getevents (min_nr=1, NULL timeout); asserts the return is a count.
- aio-cancel: submit, then best-effort io_cancel (return ignored: it races
the I/O completion and often yields -EINVAL/-EAGAIN, but enter still
fires), then drain the ring non-blockingly (min_nr=0) so io_destroy has
nothing in flight and we never hang when the cancel left no completion.
integrationtests/aio_test.go:
- TestAioGetevents asserts enter_io_getevents (MinCount 1), mirroring
TestAioSubmit, with io_getevents added to the trace-arg set.
- TestAioCancel asserts ONLY enter_io_cancel (MinCount 1) — never success —
because io_cancel's return is non-deterministic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Extend the existing security-landlock scenario to also exercise
landlock_add_rule in-process. After creating the ruleset fd, the
scenario builds a struct landlock_path_beneath_attr (allowed_access =
LANDLOCK_ACCESS_FS_READ_FILE, parent_fd = open("/", O_PATH)) and calls
landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, &attr, 0)
(syscall nr 445), then closes both fds.
landlock_add_rule is unprivileged and has no process-wide side effects
(it only builds a ruleset that is never enforced), so it is safe to run
in the shared workload process. The call is issued unconditionally even
when ruleset creation fails: sys_enter_landlock_add_rule fires before
the kernel validates the fd, so the enter tracepoint is captured
regardless of whether Landlock is enabled, matching create_ruleset
coverage. ret is UNCLASSIFIED (0/-1, not a byte count).
TestSecurityLandlockCreateRuleset now adds landlock_add_rule to the
trace-arg set and asserts enter_landlock_add_rule MinCount>=1 plus a
positive event duration, capturing ruleset_fd at args[0] (KindFd).
landlock_restrict_self (ci0) is intentionally NOT covered here: it would
require a traced child subprocess, which the integration harness cannot
support (it filters by the single workload PID at the BPF layer).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
utime_test.go previously covered only utime/utimes/ENOENT. Add scenarios
and tests for the dirfd-relative siblings futimesat(2) and utimensat(2),
which take a dirfd at args[0] and the pathname at args[1]. Both scenarios
use raw syscalls with AT_FDCWD as the dirfd so the exact enter_futimesat
and enter_utimensat tracepoints fire, and the tests assert PathContains
the filename, proving ior captures the path from args[1] (after the
dirfd). Classification/tracing were already verified by audits qt/f10;
this is pure coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
variants
Extend the xattr scenario + integration test to exercise end-to-end the
xattr syscall variants that previously had no coverage (only setxattr and
the -at get/list/remove variants were tested). Classifications were already
verified correct by inspection; this is pure coverage hardening.
READ-classified variants (assert enter path/fd + bytes>=1):
- getxattr(path,name,...) READ, KindPathname@arg0 (ej0)
- lgetxattr(path,name,...) READ, KindPathname@arg0 (oj0)
- listxattr(path,...) READ, KindPathname@arg0 (rj0)
- llistxattr(path,...) READ, KindPathname@arg0 (rj0)
- fgetxattr(fd,...) READ, KindFd@arg0 (8i0)
- flistxattr(fd,...) READ, KindFd@arg0 (8i0)
UNCLASSIFIED variants (assert enter path/fd + bytes==0):
- lsetxattr(path,...) KindPathname@arg0 (cl0)
- setxattrat(dirfd,path,...) KindPathname@arg1 (vj0)
- removexattr(path,name) KindPathname@arg0 (kj0)
- lremovexattr(path,name) KindPathname@arg0 (kj0)
- fsetxattr(fd,...) KindFd@arg0 (8i0)
- fremovexattr(fd,...) KindFd@arg0 (8i0)
The l* GET/LIST/SET/REMOVE variants target a regular file (not a bare
symlink) so they fire deterministically: user.* xattrs on a symlink itself
are kernel-restricted (EPERM). setxattrat uses the raw syscall (nr 463,
Linux 6.13+) since Go does not export it; this kernel (7.0.9) supports it.
New scenarios use golang.org/x/sys/unix (raw syscalls, no glibc redirect)
so the exact tracepoints fire. New tests scope -trace-syscalls to exactly
the variant under test to avoid substring-match cross-contamination.
All 13 TestXattr* integration tests PASS under root (mage testWithName).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
getpriority/setpriority (FamilyProcess, KindNull enter, UNCLASSIFIED ret)
were untested end-to-end: no ioworkload scenario invoked them. Add a safe,
unprivileged, non-disruptive priority-basic scenario that reads the current
nice value via getpriority(PRIO_PROCESS, 0) and re-applies that exact value
via setpriority(PRIO_PROCESS, 0, currentNice) — a byte-for-byte no-op,
mirroring the existing schedRoundtripAffinity round-trip. Note getpriority
returns 20-nice, so the value is converted back before re-applying.
Register the scenario in scenarios.go and add TestPriorityBasic, which
asserts enter_getpriority and enter_setpriority appear as null_event enters
attributed to the ioworkload process. Enter-tracepoint presence is the right
check given KindNull/UNCLASSIFIED (no fd/path/bytes to assert). Coverage
only — classification verified correct in audits 6u and pz.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
The fd-from-air-eventfd-users workload only called timerfd_create then
closed the fd, so timerfd_settime/timerfd_gettime were never exercised
by any integration test. Commit 6ac9fa4 fixed those two syscalls to
KindFd@arg0 (capturing the operating timerfd via fd_event instead of a
null_event), but that fix had no end-to-end coverage.
Extend the workload to arm the still-open timerfd via timerfd_settime
(1s relative expiry, so it never fires) and read it back via
timerfd_gettime before closing. Assert in TestFdFromAirEventfdUsers that
both enter handlers fire (MinCount>=1) and resolve to the "timerfd:"
path prefix, proving arg0 fd is captured rather than null. Locks in the
6ac9fa4 KindFd fix.
splice/tee are NOT touched: retbytes_test.go already asserts
enter_splice/enter_tee plus positive transfer byte counts, which
inherently exercises their arg0 fd capture, so no new coverage is
needed there.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
chown/lchown/fchownat/fchown previously had no integration coverage: no
scenario in cmd/ioworkload/ and no test in integrationtests/. All four are
correctly classified (chown/lchown KindPathname args[0]; fchownat KindPathname
args[1]; fchown KindFd args[0]; all FamilyFS, all UNCLASSIFIED ret) but nothing
exercised them end-to-end.
Add a chown-basic scenario that, on a temp file and a symlink the caller owns,
issues raw chown(path,-1,-1), lchown(symlink,-1,-1), fchownat(AT_FDCWD,path,
-1,-1,0) and fchown(fd,-1,-1). Owner/group -1/-1 means "leave both ids
unchanged", which the kernel accepts without CAP_CHOWN, so the scenario is
fully UNPRIVILEGED and nothing is actually modified. Raw syscalls are used so
each distinct tracepoint fires rather than glibc redirecting chown to fchownat.
TestChownBasic asserts enter_chown and enter_fchownat capture the regular file
path (chownfile.txt), enter_lchown captures the symlink (chownlink), and
enter_fchown fires (KindFd, no path). Mirrors the chmod coverage
(scenario_chmod.go / chmod_test.go).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
chmod/fchmod/fchmodat/fchmodat2 previously had no integration coverage:
no scenario in cmd/ioworkload/ and no test in integrationtests/. All four
are correctly classified (chmod KindPathname args[0]; fchmodat/fchmodat2
KindPathname args[1]; fchmod KindFd args[0]; all FamilyFS, all UNCLASSIFIED
ret) but nothing exercised them end-to-end.
Add a chmod-basic scenario that, on a temp file the caller owns (so all
calls are unprivileged), issues raw chmod(path, 0640), fchmodat(AT_FDCWD,
path, 0644, 0), fchmod(fd, 0644), and best-effort fchmodat2(AT_FDCWD, path,
0640, 0) (raw syscall 452, ENOSYS tolerated on kernels < 6.6). Raw syscalls
are used so each distinct tracepoint fires rather than glibc redirecting
chmod to fchmodat.
TestChmodBasic asserts enter_chmod and enter_fchmodat capture the file path
(chmodfile.txt) and enter_fchmod fires (KindFd, no path). fchmodat2 is not
asserted since it is version-gated, though it does fire on current kernels.
Mirrors the utime coverage (scenario_utime.go / utime_test.go).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
readlink/readlinkat are READ_CLASSIFIED (exit ctx->ret = link-target byte
count), but the integration suite only asserted the enter_readlinkat
pathname tracepoint via MinCount in link_test.go. The exit byte
classification and positive duration were never validated end-to-end,
unlike sibling READ-classified syscalls (read/recvfrom/getxattrat/
getdents64) in retbytes_test.go.
Add retbytesReadlinkat to the phase-A workload: it creates a symlink with
a known non-empty absolute target, opens the parent O_DIRECTORY, and
re-issues SYS_READLINKAT in a short spaced window (mirroring the
getdents64 driver) so ior can attach and capture an enter/exit pair under
parallel load. Each call re-resolves the same link, so ctx->ret stays
equal to the target length and is strictly positive.
Add readlinkat (and symlink, used to build the link without mixing
tracepoints) to retbytesTraceArgs, assert enter_readlinkat presence
(MinCount) plus bytes>=1 via assertEventBytesAtLeast and a positive
duration. bytes>=1 (not an exact target length) because the resolved
path varies across temp dirs; >=1 is the safest invariant.
Coverage hardening only; classify.go readlink/readlinkat=ReadClassified
and the BPF arg capture (args[1]=pathname for readlinkat) are correct.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
getdents64 was listed in retbytesTraceArgs but the retbytes workload
never issued it and TestRetbytesPhaseA never asserted its exit byte
count, leaving the READ_CLASSIFIED ctx->ret path for getdents/getdents64
unverified end-to-end (unlike sibling read/recvfrom/getxattrat).
Add retbytesGetdents to the phase-A workload: it populates a temp
directory with several files, opens it O_DIRECTORY, and re-issues
getdents64 in a short window (lseek-rewind each iteration) so ior can
attach and capture an enter/exit pair under parallel load. A non-empty
directory guarantees ctx->ret > 0.
Assert enter_getdents64 presence (MinCount) plus bytes>=1 via
assertEventBytesAtLeast and a positive duration, mirroring the existing
READ-classified siblings. Bytes>=1 (not an exact payload length) because
dirent size varies with filename length and alignment.
Coverage hardening only; classify.go getdents/getdents64=ReadClassified
is correct.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Add a Security-family end-to-end scenario + test for
landlock_create_ruleset, which was previously untested.
The new securityLandlockCreateRuleset scenario (registered as
"security-landlock") builds a minimal valid struct
landlock_ruleset_attr{handled_access_fs=LANDLOCK_ACCESS_FS_READ_FILE},
calls landlock_create_ruleset(&attr, sizeof(attr), 0) via raw syscall
(nr=444 on amd64/arm64), and closes the returned ruleset fd. It
tolerates ENOSYS/EOPNOTSUPP (kernel < 5.13 or Landlock LSM disabled)
since the sys_enter tracepoint fires before any such error. It
deliberately never calls landlock_restrict_self, which would
irreversibly sandbox the shared integration-test runner.
TestSecurityLandlockCreateRuleset asserts enter_landlock_create_ruleset
MinCount>=1 and positive duration unconditionally, plus conditional
"landlockfd:" path-prefix assertions on the create/close pair with an
open/close path-stability check.
Verified: TEST_NAME=TestSecurityLandlockCreateRuleset mage testWithName
PASS (kernel 7.0.9); mage build, go build ./cmd/ioworkload/, and
go vet ./integrationtests/ all clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
cachestat(2) had no integration coverage. Add a readwrite-cachestat
ioworkload scenario and TestReadwriteCachestat mirroring the readahead
precedent: open a temp file, write data to populate the page cache, then
issue cachestat via a raw syscall (no glibc/unix wrapper) with a whole-file
cachestat_range and zeroed cachestat output buffer, flags=0. ENOSYS on
kernels < 6.5 is tolerated for portability.
The test asserts enter_cachestat is captured with the fd-resolved file
path, that the UNCLASSIFIED return attributes zero bytes, and that the
syscall duration is positive. golang.org/x/sys is promoted to a direct
dependency. Verified PASS under sudo on kernel 7.0.9.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Add an inotify-basic ioworkload scenario and an end-to-end integration
test covering the inotify IPC family, which previously had no integration
coverage (only inotify_init1 had a unit-level eventloop test).
The scenario issues inotify_init1(IN_CLOEXEC) -> inotify_add_watch on a
temp file (IN_CREATE|IN_DELETE|IN_MODIFY) -> inotify_rm_watch -> close.
It is non-blocking: it registers and removes the watch without reading
events, and cleans up the temp dir on return.
TestInotifyBasic asserts enter_inotify_init1, enter_inotify_add_watch,
enter_inotify_rm_watch and enter_close each fire at least once, with
positive durations and PID/comm hermetic guards. The init1 instance fd
resolves to the inotifyfd: path label; add_watch/rm_watch capture the
instance fd@arg0 which resolves to the same registered label.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
removexattrat(2) (Linux 6.13+) was the one xattr *at-variant lacking
integration coverage: xattr_test.go exercised getxattrat/listxattrat
(READ-classified byte counts) and the path-based setxattr, but never the
REMOVE *at variant. Unlike its getxattrat/listxattrat siblings,
removexattrat returns a 0/-1 status (not a byte count), so its exit must
be UNCLASSIFIED — matching removexattr/lremovexattr/fremovexattr.
Add an ioworkload scenario (xattr-removexattrat) that setxattr's a
user.* attribute via a real path then removes it via raw
removexattrat(AT_FDCWD, path, 0, name), plus TestXattrRemovexattrat
asserting the path (args[1], after the dirfd) is captured (never the
xattr name at args[3]) and that accounted bytes are exactly zero
(guarding against wrongly READ-classifying it like getxattrat/
listxattrat). Distinct from the fd-based fremovexattr gap (task 8i0).
Classification verified by inspection: FamilyFS (xattr marker),
KindPathname at the pathname field (ctx->args[1]), and absent from the
ret-classification table => UNCLASSIFIED. No generator change needed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Extend the mountfs-management scenario with best-effort fsconfig (KindFd),
fspick (KindPathname), and open_tree (KindOpen) calls to complete
new-mount-API end-to-end coverage. fsconfig reuses the fscontext fd from
fsopen (FSCONFIG_SET_STRING + FSCONFIG_CMD_CREATE), fspick targets "/"
with FSPICK_NO_AUTOMOUNT, and open_tree clones the scenario mount point
with OPEN_TREE_CLONE|OPEN_TREE_CLOEXEC. All returned fds are closed and
all errno values are ignored, so ENOSYS/EPERM/EINVAL/EBADF are tolerated;
the sys_enter_ tracepoints fire on kernel entry regardless, creating no
mounts on the host.
Assert enter_fsconfig/enter_fspick/enter_open_tree (MinCount>=1) in
TestMountFsManagementSyscalls and add the three syscalls to the trace
filter. Gating is unchanged (root-only via the shared harness).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Add sysv-msg-basic and sysv-sem-basic ioworkload scenarios that exercise
the SysV message-queue and semaphore families end-to-end via raw syscalls,
mirroring the existing sysv-shm-basic scenario.
sysv-msg-basic: msgget(IPC_PRIVATE) -> msgsnd -> msgrcv -> msgctl(IPC_RMID),
using a struct msgbuf {int64 mtype; [16]byte mtext} and msgsz = body length
(excluding mtype). sysv-sem-basic: semget(IPC_PRIVATE, 1) -> semop(+1) ->
semop(-1) -> semctl(IPC_RMID), incrementing before decrementing so the
operation can never block. Both defer IPC_RMID right after the get so no
kernel IPC object leaks even on partial failure.
Add TestSysVMsgBasic and TestSysVSemBasic asserting the enter_ events for
msgget/msgsnd/msgrcv/msgctl and semget/semop/semctl are traced with
MinCount>=1 and positive duration, plus PID/comm hermetic guards.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Add a self-targeted, non-disruptive sched-basic ioworkload scenario and a
dedicated TestSchedBasic integration test. The scenario pins to one OS thread
(LockOSThread) and exercises only safe Sched syscalls: sched_yield;
sched_getaffinity then sched_setaffinity re-applying the identical mask (a
no-op); and read-only sched_getscheduler, sched_getparam, sched_getattr,
sched_get_priority_max/min, and sched_rr_get_interval. sched_setscheduler,
sched_setattr, and sched_setparam are intentionally excluded.
The test scopes -trace-syscalls to the sched_* family, guards on PID and comm,
and asserts enter_ tracepoints fire (MinCount>=1) for sched_yield,
sched_getaffinity, sched_getscheduler, and sched_getparam.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Add a misc-basic ioworkload scenario and an end-to-end integration test
for the previously-uncovered Misc syscall family.
The scenario exercises only the safe, unprivileged, non-blocking,
side-effect-free Misc syscalls: getcpu (raw SYS_GETCPU), uname /
sys_newuname (unix.Uname), sysinfo (unix.Sysinfo), vmsplice into a
self-created and self-drained pipe with a tiny buffer, and alarm(0) to
cancel any pending alarm. Code comments document why the remaining Misc
syscalls are intentionally excluded (CAP_SYS_ADMIN / global host
mutation, CAP_SYS_RAWIO / x86-only, Linux 6.13+ availability,
runtime-managed, or not user-callable).
misc_test.go asserts enter_getcpu, enter_newuname, and enter_sysinfo are
each traced at least once for the ioworkload process, restricting tracing
to the issued syscalls and keeping the existing PID/comm hermetic guards.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
The SIGNALS syscall family previously had zero end-to-end coverage
(signalfd/signalfd4 are IPC-family fd creators, not Signals). Add a
self-targeting ioworkload scenario and an integration test that assert
the family's tracepoints fire.
scenario_signals.go (signals-basic) issues, all self-directed so it
mutates no other process:
- rt_sigaction : install SIG_IGN disposition for SIGUSR1
- rt_sigprocmask: BLOCK SIGUSR1 before sending, so self-delivery only
marks it pending (never runs a handler / kills us)
- sigaltstack : set then disable an alternate signal stack
- kill/tgkill/tkill/rt_sigqueueinfo: send SIGUSR1 to self four ways
- rt_sigpending : query the pending mask
- rt_sigtimedwait: reap the pending signal with a SHORT 100ms timeout
(hang guard); EAGAIN tolerated
Safety: signal is blocked before any send; rt_sigtimedwait uses a 100ms
timeout so it cannot hang; the original signal mask and SIGUSR1
disposition are restored and the alt stack disabled on exit. The
goroutine is pinned with LockOSThread so gettid() matches the
tgkill/tkill target and the per-thread mask applies to the waiting
thread. Raw syscalls are issued directly so the tracepoints fire
regardless of the Go runtime's own signal handling. pause (noreturn) and
rt_sigreturn (handler-return only) are deliberately excluded.
signals_test.go asserts enter_ tracepoints with MinCount>=1 for
rt_sigaction, rt_sigprocmask, rt_sigpending, sigaltstack, kill, tgkill,
and rt_sigtimedwait, plus a positive duration for the rt_sigtimedwait
enter/exit pair.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
The SysV shared-memory family (shmget/shmat/shmdt/shmctl) had no
end-to-end integration coverage. Add an ioworkload `sysv-shm-basic`
scenario that, without privileges, runs shmget(IPC_PRIVATE) -> shmat ->
write into the mapped segment -> shmdt -> shmctl(IPC_RMID), always
issuing IPC_RMID (via defer) so no kernel segment leaks.
Add TestSysVShmBasic asserting enter_shmget/enter_shmat/enter_shmdt/
enter_shmctl are each traced with a positive (paired enter/exit)
duration.
msg/sem coverage is scoped out and tracked as a follow-up task (7i0).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Add write-side positional vectored coverage to close the remaining gap
in the pwrite64-family byte-accounting validation. The retbytes/readwrite
integration suite already exercised pwrite64 (scalar) and the read-side
preadv/preadv2, but the WRITE_CLASSIFIED byte attribution for the
vectored positional writes pwritev/pwritev2 was only covered by unit
tests, not end-to-end.
New ioworkload scenarios:
- readwrite-pwritev: issues pwritev (syscall.SYS_PWRITEV) writing a known
two-iovec payload at offset 0 to a temp file.
- readwrite-pwritev2: issues pwritev2 via the explicit syscall number
(328 amd64 / 287 arm64, mirroring preadv2SyscallNr) with offset 0 and
no flags.
New integration tests assert enter_pwritev/enter_pwritev2 fired and that
the attributed retbytes equal the exact iovec total, validating
WRITE_CLASSIFIED end-to-end. Both pass as root.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
readahead(2) was traced (KindFd enter fd_event from args[0], UNCLASSIFIED
exit ret_event) but had no integration or ioworkload scenario coverage,
unlike its sibling sync_file_range. Add readwrite-readahead and
readwrite-readahead-ebadf scenarios plus TestReadwriteReadahead /
TestReadwriteReadaheadEbadf, asserting enter_readahead capture with path
attribution, zero attributed bytes (readahead returns 0/-1, not a byte
count, so it is correctly UNCLASSIFIED), and positive end-to-end duration.
No classification change: inspection confirms KindFd / UNCLASSIFIED is
correct per man 2 readahead; bytesFromRet returns 0 for UNCLASSIFIED so
the 0/-1 return is never misattributed as bytes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
listxattrat(2) (Linux 6.13+) returns the size in bytes of the list of
extended attribute names, exactly like listxattr/llistxattr/flistxattr,
but its exit was classified UNCLASSIFIED, so its read bytes were dropped
from I/O totals. Classify it as ReadClassified and regenerate the BPF
handler (ret_type now READ_CLASSIFIED). This mirrors the getxattrat fix
(task ku, commit c3177bd) and completes xattr-family consistency:
get-family and list-family are READ_CLASSIFIED while set-family and
remove-family stay UNCLASSIFIED (they return 0/-1).
Update the docs ReadClassified list and the retclassify expectation, and
add an ioworkload scenario plus integration test: the workload sets a
user xattr then lists names via the raw listxattrat(2) syscall with
AT_FDCWD, and the test asserts enter_listxattrat captures the file path
and accounts the returned name-list size as read bytes.
Task: r20
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
getxattrat(2) (Linux 6.13+) returns the xattr value size in bytes,
exactly like getxattr/lgetxattr/fgetxattr, but its exit was classified
UNCLASSIFIED, so its read bytes were dropped from I/O totals. Classify
it as ReadClassified and regenerate the BPF handler (ret_type now
READ_CLASSIFIED). Path extraction (args[1], after the dirfd) and the
name-not-captured-as-path behaviour were already correct.
Update the docs ReadClassified list and the retclassify expectation,
and add the first xattr integration coverage: an ioworkload scenario
that sets then getxattrat-reads a user xattr on tmpfs, plus a test that
asserts enter_getxattrat captures the file path (not the xattr name)
and accounts the returned value size as read bytes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
The POSIX per-process timer family (timer_create, timer_settime,
timer_gettime, timer_getoverrun, timer_delete) had no end-to-end
integration coverage; only the unrelated fd-returning sibling
timerfd_create was exercised. Add a posix-timer-lifecycle workload
scenario and TestPosixTimerLifecycle to validate these are traced as
null_events, and guard against timer_create being misclassified like
timerfd_create (timer_create returns a timer_t via an output pointer,
not an fd, so its records must carry no 'timerfd:' descriptor path).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
The retbytes integration coverage exercised read/write/sendto/etc but the
positional read p-variants only had presence assertions (pread64) or no
coverage at all (preadv/preadv2), so their READ_CLASSIFIED byte accounting
was validated only by unit tests, not end-to-end.
Add a positive byte-count assertion to TestReadwritePread and new
readwrite-preadv / readwrite-preadv2 workload scenarios plus integration
tests that read a known payload and assert the attributed byte count,
mirroring the existing pwrite64 assertion. preadv2 lacks a Go
syscall.SYS_PREADV2 constant, so its number is provided per-GOARCH
(amd64=327, arm64=286) following the securitySyscallNumbers pattern.
Addresses the read side of b20.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
openat2(2) is the one open-family syscall with a structurally distinct
argument layout: flags/mode live inside the open_how struct (args[2]),
not as a plain int, and args[3] is the struct size. The tracer correctly
reads the path from args[1] and omits flags (ev->flags = -1) rather than
misreading the struct ptr/size, and registers the returned fd->path
mapping via the shared handleOpenExit path. This was verified by
inspection but had no integration scenario, unlike open/openat/creat/
open_by_handle_at.
Add an open-openat2 ioworkload scenario issuing the raw openat2 syscall
(Go has no wrapper and routes Open through openat) and a TestOpenOpenat2
integration test asserting the enter_openat2 tracepoint captures the
path. Verified passing as root.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
The mountfs-management integration scenario covered the new mount API
syscalls fsmount/move_mount/mount/umount/pivot_root but not fsopen, the
API's entry point and a direct eventfd-kind sibling of fsmount. Add a
best-effort fsopen("tmpfs", FSOPEN_CLOEXEC) call (closing the returned
context fd on success) and assert enter_fsopen is traced.
fsopen's tracing is otherwise correct: args[1] flags captured, args[0]
fsname (a filesystem TYPE, not a path) deliberately not treated as a
pathname, returned fd registered as the 'fsopenfd:<flags>' descriptor.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Audit of io_submit tracing (task 0v) confirmed the tracer is correct by
inspection: KindNull (sys_enter_io_ prefix rule) so ctx_id/nr/iocbpp are
opaque and no fd/path is captured; FamilyAIO; return is UNCLASSIFIED (the
return is a count of iocbs submitted, not a byte count, so it must not
inflate READ/WRITE/TRANSFER totals). Enter/exit are paired and timed. No
implementation discrepancy and no docs drift.
Add a genuine end-to-end test: new aio-submit ioworkload scenario sets up
an AIO context and submits one real IOCB_CMD_PWRITE iocb against a temp
file via raw syscalls, then tears the context down. TestAioSubmit asserts
the enter_io_submit tracepoint fires for the AIO family workload.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
The classic Linux AIO family (io_setup/io_submit/io_getevents/io_cancel/
io_destroy) had no integration coverage: family_test.go exercises only
FS/Memory/IPC/Network/Process/Sched/Time, and iouring_test.go covers only
the distinct io_uring_* family. io_setup is classified KindNull/FamilyAIO,
which is correct by inspection against man 2 io_setup (nr_events is a count,
ctx_idp an output pointer, so no fd/path is captured), so the tracer itself
needed no change.
Add an ioworkload AIO scenario that drives io_setup(2)/io_destroy(2) raw
(no privileges, no libaio) plus an EINVAL variant, and integration tests
that assert ior records the enter_io_setup tracepoint end-to-end, mirroring
the existing iouring scenario/test pattern.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
clock_nanosleep with the TIMER_ABSTIME flag passes an ABSOLUTE wakeup
time in the request timespec, not a relative duration. The generated BPF
sleep handler computed requested_ns = tv_sec*1e9 + tv_nsec
unconditionally, so absolute sleeps exported a bogus multi-decade
"sleep duration" in CSV/parquet/stream.
generateExtraSleep now carries an optional flags-argument expression per
sleep syscall. For clock_nanosleep the generated handler checks
args[1] & TIMER_ABSTIME (value 1) and only computes the relative
duration when the flag is clear; absolute sleeps keep the existing -1
sentinel (same value used for null/unreadable timespec pointers).
nanosleep is always relative and stays unconditional (no flags arg).
- Regenerated internal/c/generated_tracepoints.c (mage generate idempotent).
- Added codegen tests asserting the TIMER_ABSTIME guard for clock_nanosleep
and its absence for nanosleep.
- Extended the ioworkload sleep scenario to issue an absolute clock_nanosleep
and the sleep parquet integration test to assert it is reported as -1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
utime(2) and utimes(2) change a file's access/modification times by a real
filesystem path (filename at args[0]). The path was already captured
(KindPathname), but both syscalls fell through to FamilyMisc instead of
joining their siblings utimensat/futimesat in FamilyFS. Add them to
fsSyscalls and regenerate; the only generated change is trace IDs
1034-1037 flipping FamilyMisc -> FamilyFS.
Lock-in coverage:
- family_test.go asserts utime/utimes/utimensat/futimesat are all FamilyFS.
- classify_test.go + FormatUtime fixture assert utime is KindPathname with
PathnameField "filename" (path captured even though it is a char* string,
unlike domain/host name args).
- New ioworkload scenarios utime-basic/utimes/enoent and integration tests
TestUtimeBasic/Utimes/Enoent verify the path is captured at runtime,
including on the ENOENT error path.
Docs updated: moved utime/utimes from Misc to FS in
docs/syscall-tracing-plan.md to keep the drift tests green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
close_range was captured as a single-fd fd_event carrying only first, so
the runtime evicted every tracked fd >= first, ignoring the last upper
bound and the flags. Bounded calls wrongly dropped still-open higher fds,
and CLOSE_RANGE_CLOEXEC (which keeps fds open) was treated as a full close.
Reclassify close_range to the two_fd_event kind, mapping fd_a/fd_b/extra to
first/last/flags. The runtime now closes only the inclusive [first, last]
range (a negative last from ~0U means unbounded) and skips eviction when
CLOSE_RANGE_CLOEXEC is set or the syscall fails.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
|
|
|
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|