| Age | Commit message (Collapse) | Author |
|
Implement the sudo hardening plan so mage itself never runs as root.
Changes:
- Remove implicit sudo wrapping for go test (buildGoTestCmd, progress
ticker, drainTestEvents helpers removed).
- Add compileIntegrationTestBinary() + runIntegrationTestBinary() helpers.
The integration test binary is compiled unprivileged, then executed
under sudo -n -E from the integrationtests/ directory so relative paths
(../ior, ../ioworkload) resolve correctly.
- Harden sudoOutput() to prefix with sudo -n.
- Harden sudoRunWithEnv() to prefix with sudo -n env ...
- Update docs/sudo-hardening-plan.md and docs/sudo-rules-for-ior.txt
to document the working approach (sudo -n -E with SETENV flag).
|
|
Document the planned opt-in "follow forks" mode that would let ior trace a
target PID and all its descendants (needed for the landlock_restrict_self
integration case, task ci0, and for tracing forking workloads as a tree).
The plan covers the BPF descendant-set map, sched_process_fork/exit hooks,
the FOLLOW_FORK gate in filter(), userland flag/seeding/assertion changes,
and explicitly requires syscall-count aggregation to roll up across the
followed tree. Add a reference comment above filter() pointing to the plan.
Plan only — not implemented.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
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>
|
|
rename-family (rename/renameat/renameat2) and link-family (link/linkat/
symlink/symlinkat) capture BOTH paths in BPF (name_event.oldname at
args[1] for the AT-variants, after a dirfd, plus newname), but only
newname reached any persisted output. event.Pair.FileName() resolves to
oldnameNewnameFile.Name() == Newname, so the parquet `file` column, CSV,
and flamegraph Path all carried newname; the captured oldname survived
only in the TUI stream String() repr ("old:... ->new:..."). The oldname
capture (and its args[1] index for the AT-variants) was therefore never
validated end-to-end, and a wrong-oldname-index regression would surface
in no persisted output or test.
Surface oldname as one additive, backward-compatible column, mirroring
the dedicated optional-column convention (address_space_bytes,
requested_sleep_ns, epoll_*):
- old_file (String): source/old path for rename/link syscalls; empty for
every other syscall. The existing `file` column keeps its semantics
(newname for rename/link).
Data flows name_event.oldname -> event.Pair.Oldname (populated in
handleNameExit alongside the existing File) -> streamrow.Row.OldName ->
parquet.Record.OldFile. Docs (docs/parquet-querying.md) and the Magefile
parquetValidate column list updated in lockstep.
The new TestRenameRenameatOldnameInParquet integration test exercises the
renameat AT-variant (oldname at args[1] after the olddfd dirfd) and
asserts the parquet old_file column carries renameat-old.txt while file
carries renameat-new.txt, and that old_file stays empty for non-rename/
link rows -- locking in the args[1] oldname capture end-to-end.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
epoll_ctl's BPF handler already decodes the operation (args[1]),
target descriptor (args[2]), and requested event mask (args[3]->events)
into an EpollCtlEvent, but the single resolved-epfd `fd` column was the
only epoll detail reaching the output schema. Consumers could not see
which descriptor was registered nor the operation performed.
Surface the metadata as three additive, backward-compatible columns,
mirroring the existing dedicated optional-column convention used by
requested_sleep_ns and address_space_bytes:
- epoll_op (String): ADD/MOD/DEL, or the raw decimal for unknown ops;
empty for non-epoll_ctl rows.
- epoll_target_fd (Int32): registered descriptor (args[2]); 0 otherwise.
- epoll_events (UInt32): requested event mask; 0 otherwise.
Data flows EpollCtlEvent -> event.Pair (new EpollCtl/HasEpoll fields,
populated in handleEpollCtlExit) -> streamrow.Row -> parquet.Record.
The op-to-string mapping lives on event.EpollCtl.OpName.
Docs (docs/parquet-querying.md) and the Magefile parquetValidate column
list updated in lockstep (also adding the previously-undocumented
address_space_bytes/requested_sleep_ns columns). The polling parquet
integration test now asserts epoll_ctl rows carry a decoded op and a
valid target fd, and that other syscalls leave epoll_op empty.
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>
|
|
pidfd_getfd path expectation
signals: TestSignalsBasic issued but never asserted enter_tkill and
enter_rt_sigqueueinfo (both genuinely emitted by sendSelfSignals); add
both MinCount>=1 enter assertions. Both tracepoints were already in
signalsTraceArgs.
sched: TestSchedBasic omitted enter_sched_setaffinity even though
scenario_sched.go re-applies its affinity mask via SchedSetaffinity and
the tracepoint is in schedTraceArgs; add the MinCount>=1 enter assertion.
sysv: TestSysVMsgBasic only checked enter presence + duration for
msgrcv. msgrcv is READ_CLASSIFIED, so add assertEventBytesAtLeast >= 12
("ior-sysv-msg") mirroring the recvfrom/recvmsg byte assertions.
pidfd: verified TestPidfdGetfdSuccess already PASSES deterministically
with PathContains "pidfd-getfd-source.txt". The path is correct and
meaningful: pidfd_getfd is an fd-transfer op, so at exit
applyFdTransferOp re-points the event to the RETURNED dup fd, which for a
self-pidfd refers to the same open source file. Kept the assertion and
added a comment explaining why it resolves to the source file rather than
the pidfd's anon_inode. No BPF/handler change.
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>
|
|
TestCopyFileRangeBasic previously asserted only the enter event and source
path (copyrangesrc.txt) but not the copied byte count on the
TRANSFER_CLASSIFIED exit, unlike sibling sendfile64/splice/tee coverage in
retbytes_test.go. The basic scenario copies exactly the 32-byte payload
"copy_file_range integration data" in a single copy_file_range(2) call, so
the exit byte count is deterministic.
Switch to runScenarioResult and add assertEventBytesEqual(...,32) plus
assertEventDurationPositive to lock in the transfer attribution (FamilyFS,
fd_in@args[0], ret TransferClassified). Coverage hardening only; no defect.
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>
|
|
vmsplice(int fd, const struct iovec*, unsigned long nr_segs, unsigned int
flags) is the iovec<->pipe variant of splice(2) and belongs to the same fd
byte-mover cohort as its direct siblings splice/tee (and sendfile64/
copy_file_range). Its KIND (KindFd@arg0) and RET (TransferClassified, byte
count) already matched splice/tee — only the family was wrong.
Root cause: vmsplice was absent from the syscallFamilies map in
internal/generate/family.go; its name matches no fsNameMarkers and it is not
in fsSyscalls, so ClassifySyscallFamily fell through to FamilyMisc. This is
the same documented Misc-fall-through anti-pattern already fixed for
alarm/adjtimex/fanotify_init/fanotify_mark/file_getattr/file_setattr. The
established mj0 decision placed splice/tee in Network, so the minimal
sibling-consistent fix is vmsplice -> Network.
Added "vmsplice": FamilyNetwork next to splice/tee with an explanatory
comment, then re-ran `mage generate`. The regen is minimal and idempotent:
only the two vmsplice trace IDs flip Misc->Network in generated_types.go and
the vmsplice entry flips Misc->Network in generated_tracepoints.go; no
TraceId renumbering and no other syscalls change. The generated C tracepoints
are unaffected (family is a Go-side tag).
Also moved vmsplice from the Misc list to the Network list in
docs/syscall-tracing-plan.md (hand-maintained, docs-drift-validated), and
corrected the misc_test.go comments which described vmsplice as a Misc
syscall — it is still issued by the misc-basic workload and traced by name,
but its transfer/byte-count coverage lives in retbytes_test.go alongside
splice/tee. No vmsplice family assertion existed in the integration suite, so
no coverage was relocated, only comments corrected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Root cause: the generic field matcher classifyByField only maps an arg
literally named "fd" to KindFd. Several syscalls operate on an EXISTING
fd whose tracepoint arg0 is named something else, so they fell through
to KindNull -> null_event, capturing NO descriptor and dropping the fd
they act on:
- timerfd_gettime / timerfd_settime: arg0 is "int ufd" (the timerfd)
- splice: arg0 is "int fd_in" (source fd of an in-kernel transfer)
- tee: arg0 is "int fdin" (source fd of an in-kernel transfer)
Fix: add explicit KindFd overrides for these four sys_enter_* keys to
nameOnlyKindsTable so the enter handler captures arg0, mirroring the
established epoll_wait(epfd) / mq_*(mqdes) / sendfile64(out_fd) /
copy_file_range(fd_in) precedent. splice/tee were surfaced by a systemic
sweep of tracepoint formats for fd-typed arg0 named other than "fd" that
currently classify to null; they are TransferClassified siblings of
sendfile64/copy_file_range and clearly fd-operating. The *at() family
(dfd arg0) is intentionally untouched: it is path-classified, and
timerfd_create remains the KindEventfd fd CREATOR.
Regenerated artifacts (mage generate): the four enter handlers now emit
fd_event capturing ctx->args[0] instead of null_event; exit handlers stay
UNCLASSIFIED. Updated the generated kind maps, the golden result.txt, the
classify_test expectations, and docs/syscall-tracing-plan.md (moved the
four from kind "null" to kind "fd"; families IPC/Network unchanged).
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>
|
|
file_setattr(2) (Linux 6.13+) is the write counterpart of file_getattr:
it sets a file's extended attributes (struct file_attr) given dirfd@arg0
+ pathname@arg1 + attr-buffer + size + at_flags, and returns 0/-1 (not a
byte count). Its name matches none of the fsNameMarkers substrings
("stat"/"xattr"/"chmod"/"chown") and it is absent from the fsSyscalls
set, so it was falling through to FamilyMisc -- the same
alarm/fanotify/file_getattr-style misclassification. Add it to the
explicit family map for sibling consistency with file_getattr.
This also completes the file_getattr regeneration: the prior fix
(96de9ef) was generated with a partial target that updated
generated_tracepoints.go but did not propagate file_getattr into the
traceId2Family map in generated_types.go; a full `mage generate` here
reconciles both file_getattr (1058/1059) and file_setattr (1056/1057)
to FamilyFS. mage build, generate idempotency, and the
internal/generate, /tracepoints, /types unit tests (incl. docs-drift
sync) all pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
file_getattr(2) (Linux 6.13+) retrieves a file's extended attributes
(struct file_attr) given a dirfd + pathname + attr buffer + size +
at_flags. It is a path-based filesystem operation, the counterpart of
statx and the FS_IOC_FSGETXATTR ioctl, so it belongs in FamilyFS.
It was falling through to FamilyMisc because the fsNameMarkers substring
list keys on "stat"/"xattr"/"chmod"/"chown" — "getattr" matches none of
them — and the syscall is absent from the fsSyscalls set, the same
Misc-fall-through defect previously fixed for alarm/fanotify_init/
fanotify_mark. Add an explicit "file_getattr": FamilyFS entry to the
syscallFamilies map, regenerate the Go tracepoint map, and update the
docs/syscall-tracing-plan.md family listing to match.
KIND stays KindPathname (pathname@args[1], data-driven from the live
tracepoint) and the return is 0/-1, hence UNCLASSIFIED (not a byte
count) — both already correct, only the family was wrong.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
fanotify_mark(2) adds, removes, or modifies a mark on an fanotify
notification group. Its arg0 is the fanotify group fd returned by
fanotify_init(2); it carries a dirfd@arg3 and an optional pathname@arg4
and returns 0/-1 (not a byte count). It is the operation counterpart of
fanotify_init and the direct analog of inotify_add_watch (both register a
watch/mark on a filesystem object via the notification-group fd).
inotify_add_watch is FamilyIPC, and fanotify_init was just moved Misc->IPC
to sit with the fd-based event-notification primitives (eventfd, signalfd,
timerfd, userfaultfd, inotify_*). fanotify_mark, however, was still falling
through to FamilyMisc by omission from the explicit family table -- the same
alarm/adjtimex-style misclassification fixed for fanotify_init in 88769d4,
and flagged there as still-outstanding for fanotify_mark. Add it to the IPC
family map for sibling consistency and regenerate.
KIND is unchanged and correct: KindPathname capturing the optional
pathname at args[4]. This matches the *at() cohort convention (fchmodat,
fchownat, unlinkat, mkdirat, newfstatat, utimensat, name_to_handle_at all
carry a dirfd at arg0 yet capture the pathname), since fanotify_mark has a
dirfd@arg3 + pathname@arg4 pair. RET stays UNCLASSIFIED (returns 0/-1).
Docs plan updated to keep the docs-drift tests in sync.
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>
|
|
applyFdCloseState evicted the fd->path mapping (fdState + proc-fd cache)
on every close exit, ignoring the return value. A failed close — most
importantly EBADF ("fd isn't a valid open file descriptor"), but also
EINTR/EIO — did not release any descriptor we track, so dropping the
mapping there would let a later genuine close or a reuse of the fd
number resolve against stale/empty state.
Gate the eviction on ret==0, mirroring the ret==0 guard already used by
applyCloseRangeState for close_range. close's exit tracepoint is
generated as a ret_event (UNCLASSIFIED), so the exit carries the close
return value in RetEvent.Ret.
Tests: the close-exit event was being built as an fd_event, which does
not match the real BPF wire format (sys_exit_close emits EXIT_RET_EVENT).
Add a makeExitCloseEvent helper that emits the correct ret_event, route
all 18 close-exit call sites and TestHandleFdExitCloseClearsProcFdCache
through it, and add CloseFailureTest asserting a failed close (ret=-1)
leaves the fd tracked.
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>
|
|
fanotify_init(2) creates and initializes an fanotify notification group
and returns an event-queue file descriptor. It is the direct analog of
inotify_init1 (both are filesystem-event notification facilities whose
group-creating syscall is a flags-taking fd-creator). inotify_init/
inotify_init1 are FamilyIPC alongside the other fd-based event-notification
primitives (eventfd, signalfd, timerfd, userfaultfd), yet fanotify_init
fell through to FamilyMisc by omission from the explicit family table -- an
alarm/adjtimex-style misclassification inconsistent with its siblings.
Add fanotify_init to the IPC family map and regenerate. Kind
(KindEventfd, flags at args[0]) and ret (UNCLASSIFIED, returned fd captured
via the fd mechanism) were already correct and are unchanged. fanotify_mark
stays in Misc (path-marking, not fd creation). Docs plan updated to keep
the docs-drift test in sync.
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>
|
|
mq_timedsend(2)/mq_send(3) return 0 on success or -1 on error; the
payload size msg_len is an INPUT argument, never the return value. It was
wrongly listed in retClassifications as WriteClassified, which made
bytesFromRet attribute its 0 return as "bytes written" (the stats engine
WriteClassified path). Remove it so its return stays UNCLASSIFIED,
consistent with its POSIX mq sibling mq_timedreceive (which legitimately
stays ReadClassified because it returns the received byte count). This is
the exact same defect just fixed for SysV msgsnd (5057bd9) and mirrors
the msgrcv/msgsnd asymmetry.
Regenerated tracepoints/docs accordingly and updated the pre-existing
classify unit test and the TestPosixMqBasic integration assertion: the
mq_timedsend send no longer asserts a write byte count (now expects 0),
while mq_timedreceive keeps its received-byte-count assertion. Verified:
mage generate idempotent, mage build OK, internal/generate tests pass.
TestPosixMqBasic skips in this sandbox (mq_open: permission denied) but
compiles with the corrected assertions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
The xattr integration tests previously only asserted on getxattrat/
listxattrat (path/dirfd READ-classified variants). The path-based
setxattr(2) was traced by the existing xattr-getxattrat scenario
(workload calls syscall.Setxattr; xattrTraceArgs includes setxattr) but
never asserted. Add TestXattrSetxattr to verify end-to-end that:
- enter_setxattr captures the filesystem PATH at args[0] (kind=pathname),
never the xattr NAME at args[1];
- exit_setxattr is UNCLASSIFIED: setxattr returns a 0/-1 status, not a
byte count (the size arg is the INPUT value length), so accounted
bytes must be exactly zero. This guards against the msgsnd-style bug
of treating a status return as bytes written, and contrasts with
getxattr/listxattr which DO return byte counts (READ-classified).
This is the PATH-based set complement to filed task 8i0 (fd-based
fsetxattr/fgetxattr/flistxattr/fremovexattr); it does not duplicate it.
Classification (FamilyFS, KindPathname enter, UNCLASSIFIED ret) is
verified by inspection per task guidance, not by a unit test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
The sched-basic scenario already issues sched_rr_get_interval(0, &ts)
via schedQueryPriorityRange, but TestSchedBasic only asserted the other
sched_get* queries. Add an explicit enter_sched_rr_get_interval
assertion to lock in its KindNull/UNCLASSIFIED tracing end-to-end.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
msgsnd(2) returns 0 on success or -1 on error; the payload size msgsz is
an INPUT argument, never the return value. It was wrongly listed in
retClassifications as WriteClassified, which made the stats engine treat
its 0 return as "bytes written". Remove it so its return stays
UNCLASSIFIED, consistent with its SysV IPC siblings (msgrcv legitimately
stays ReadClassified because it returns a received byte count).
Regenerated tracepoints/docs accordingly. Verified: mage generate
idempotent, mage build OK, internal/generate tests pass, and the
TestSysVMsgBasic integration test (added in task 7i0) still passes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
alarm(2) arranges for a SIGALRM after a given number of seconds; it is a
simplified setitimer(ITIMER_REAL) and, per alarm(2) NOTES, "alarm() and
setitimer(2) share the same timer; calls to one will interfere with use of
the other." The syscallFamilies table omitted alarm, so it fell through to
FamilyMisc while its siblings setitimer/getitimer/timer_create were correctly
FamilyTime — an adjtimex-style misclassification (cf. 7243b7c). Add
alarm -> FamilyTime and move it from Misc to Time in the tracing plan;
regenerate the family maps (trace IDs 468/469 now FamilyTime,
"alarm": "Time").
Kind classification (KindNull/null_event: the single arg is an unsigned int
seconds, no fd/path) and the UNCLASSIFIED return (seconds remaining, not a
byte count; alarm never fails) were already correct.
Also harden the misc-basic integration test with a deterministic
enter_alarm assertion (alarm(0) is issued unconditionally by the scenario;
the syscall-entry tracepoint always fires) so the alarm enter path is
covered end-to-end even though alarm is now FamilyTime rather than Misc.
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>
|
|
TestCommPropagation and TestEventTypeFiltering/FdEventFiltering were
flaky in full `mage test` runs (passing in isolation). For synthetic
events whose tid was not in the comm cache, the event loop fell back to
commResolver's default resolveFn, which reads /proc/<tid>/comm on the
host. The fixed test pids/tids are small (e.g. defaultTid+100 == 111,
defaultPid+1 == 11) and collide with real transient kernel threads
(e.g. kworker/0:1-events), so the resolved comm depended on what
happened to be running on the host at that instant.
Fix: use commResolver's existing injectable resolveFn seam. Add a
newHermeticCommResolver() test helper whose resolveFn returns ("", nil)
and never touches /proc, and inject it into TestCommPropagation (via
eventLoopConfig.commResolver) and newEventLoopWithFilter (used by
TestEventTypeFiltering). No production code changes.
Assertions are unchanged: positive comm names still come from the
synthetic OpenEvent.Comm bytes; cache-miss tids now deterministically
resolve to empty regardless of host state. Updated the stale
"use a very large TID to avoid /proc collisions" comment accordingly.
Verified: -count=50 (affected tests) and -race -count=10 green, full
`mage test` and `mage build` green.
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>
|
|
The BPF side reserves sizeof(struct ...) via bpf_ringbuf_reserve, so the
userspace ringbuf sample length equals the C struct size INCLUDING trailing
alignment padding. For five event structs whose last field is 32-bit but
which also contain a __u64 (forcing 8-byte trailing pad), the *EventSize
fast-decode gate constants were set to the unpadded field-sum size instead
of sizeof:
open_event 300 -> 304
fd_event 28 -> 32
ret_event 36 -> 40
socket_event 36 -> 40
open_by_handle_at_event 28 -> 32
Because the kernel payload length never equalled those constants, the fast
explicit-offset decoders were silently bypassed and every such event
(including the very hot ret_event on each syscall exit, and fd_event on
read/write/etc enter) fell back to the slow reflection-based binary.Read
path. binary.Read reads only the field bytes and ignores trailing padding,
so values were always correct -- this was a performance regression, not a
correctness bug; verified by decoding padded payloads.
Fix mirrors the existing socketpair/accept/pipe/eventfd/poll V1/V2 handling:
the size gate now accepts both the kernel sizeof and the legacy field-sum
size (the latter still emitted by Go binary.Write in tests and Bytes()), and
the trailing pad bytes are ignored. Added kernel-layout tests feeding the
padded payload for all five fixed decoders.
Audit (task i20) confirmed every other event struct's Go layout, field order,
and fast-decode offsets match the C side byte-for-byte.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Classification correctness (which family/kind/return-class a syscall maps
to) is verified by inspection against the man pages and the classifier
rules, not by dedicated unit tests. The tracing-relevant outcome — which
fd/path/byte-count the generated BPF C actually captures — is covered by
the GenerateTracepointsC codegen tests and the end-to-end integration
tests, all of which are retained.
Removed:
- internal/generate/family_test.go (ClassifySyscallFamily / .Family table)
- internal/generate/retclassify_test.go (ClassifyRet read/write/transfer/
unclassified tables)
- ~70 pure-classification tests trimmed from classify_test.go, keeping only
the GenerateTracepointsC codegen/tracing tests plus the shared helpers
(mustParseAll, mqFormats, phaseAFormats, syntheticEnter/Exit, itoa) used
by codegen_test.go.
- pure-classification funcs interleaved in codegen_test.go
(TestClassifyRet*Unclassified, TestClassifyTkillFallsThroughToNull,
Test{Mkdirat,Rmdir}FamilyAndKindMatchSiblings).
Kept all TestGenerate* handler tests (they assert the generated BPF C
captures the correct fd/path/arg-index/return classification), the
isNoreturnSyscall tests, docs-drift guards, eventloop dispatch tests, and
the integration suite — so every affected syscall still has tracing
coverage. No tracing gaps discovered.
generate package: go test (incl. -race) green; mage build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
adjtimex(2) and clock_adjtime(2) share one man page: both tune or query
the kernel clock (clock_adjtime is adjtimex with an explicit clockid) and
return a clock-state code or -1. The syscallFamilies table omitted
adjtimex, so it fell through to FamilyMisc while its sibling clock_adjtime
was correctly FamilyTime. Add adjtimex -> FamilyTime and move it from
Misc to Time in the tracing plan; regenerate the family maps (trace IDs
418/419 now FamilyTime, "adjtimex": "Time").
Kind classification (KindNull/null_event) and the UNCLASSIFIED return
(a clock-state code, not a byte count) were already correct.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|