| Age | Commit message (Collapse) | Author |
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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 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>
|
|
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>
|
|
Audit of semctl(2) confirmed the implementation is correct: it is
classified KindSysVOp in FamilyIPC, consistent with its SysV
control-syscall siblings msgctl/shmctl (and semget/semop/semtimedop).
The enter handler emits a null_event and captures no argument, so the
semid at args[0] -- a System V IPC identifier, NOT a file descriptor --
is correctly not recorded as an fd. The exit handler reports the raw
op-dependent int status (value or -1) as UNCLASSIFIED, never a byte
count.
The classification table already covered semctl, but only msgctl's
generated handler body was directly asserted. Add dedicated lock-in
tests mirroring TestGenerateMsgctlHandler:
- TestGenerateSemctlHandler: enter emits null_event, no ctx->args[]
capture, no ev->fd; exit ret_type UNCLASSIFIED.
- TestClassifyRetSemctlUnclassified: ret is UNCLASSIFIED.
No classification, generated C, docs, or runtime behavior changed
(mage generate produces no diff), so this is a test-only addition.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
rt_sigreturn(2) restores the pre-signal execution context off the signal
stack frame and resumes the interrupted instruction; it never returns to
the instruction after the syscall. man sigreturn(2) states plainly that
"sigreturn() never returns", and tracing against /sys/kernel/tracing
confirms it: sys_enter_rt_sigreturn fires once per signal-handler return
while sys_exit_rt_sigreturn never fires.
The generator previously emitted a dead handle_sys_exit_rt_sigreturn (it
can never run) and recorded a per-tid syscall_enter_state_map entry on the
enter path that nothing would ever delete (no exit fires), leaking entries
in the bounded map on every signal-handler return.
Add rt_sigreturn to noreturnSyscalls so codegen suppresses the dead exit
handler and routes the enter handler through ior_on_noreturn_syscall_enter
(sampling decision only, no map write), exactly like exit/exit_group. The
enter null_event is still emitted, and the FamilySignals/KindNull
classification is unchanged. Regenerated the C/Go artifacts and the result
baseline accordingly, and generalized the related comments.
Lock-in tests: TestRtSigreturnIsNoreturn asserts rt_sigreturn is noreturn;
TestRtSigSiblingsAreNotNoreturn guards that the returning rt_sig* siblings
are not; TestGenerateExitNoreturnHandlers now also covers rt_sigreturn.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Audit of pwritev(2) confirmed the existing classification is correct:
pwritev returns the number of bytes written, so its exit is
WRITE_CLASSIFIED (matching write/pwrite64/writev/pwritev2), fd is at
args[0] (KindFd), and it lives in the FS family. The read-side sibling
preadv stays READ_CLASSIFIED. No implementation changes were needed.
Add TestClassifyPwritevWriteByteCount as a lock-in test mirroring the
prior pwritev2/pwrite64 audits, with a preadv off-by-one contrast guard
and transfer/unclassified negative checks across the whole p/readv/writev
family so any stray reclassification trips the test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Audit of pipe(2)/pipe2(2) (task dx) confirmed the tracing implementation is
correct: KindPipe (not KindFd, since args[0] is an output ptr to int[2], not an
fd), FamilyIPC, and an UNCLASSIFIED int return. Enter stashes the output ptr
(flags=0 for pipe, args[1] for pipe2); exit reads the fd pair via
bpf_probe_read_user guarded by ret==0, mirroring the socketpair pipe-like
pattern. The only gaps were missing lock-in tests, now added:
- codegen: assert the exit handler reads the fd pair from the stashed output
buffer (ret==0 guard, bpf_probe_read_user, fd0/fd1) and that the flag-less
pipe variant hardcodes flags=0 and never reads args[1].
- classify: pipe/pipe2 are never KindFd and stay UNCLASSIFIED on ret.
- runtime: a failed pipe (ret==-1) tracks no descriptors and attaches no file.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Audited perf_event_open(2) against the man page: it returns a new fd (or
-1), args[0] is a struct perf_event_attr* userspace pointer (NOT an fd),
args[1] is a monitored pid, and only args[3] group_fd is a real fd.
The existing implementation is correct (KindPerfOpen by name, not KindFd;
FamilySecurity; exit as UNCLASSIFIED RetEvent). Add lock-in tests:
- codegen: assert args[0] is read via bpf_probe_read_user as the attr
struct and never captured as an fd (negative assertions on args[0]/args[1]).
- eventloop: a failed return (-1) registers no fd in fdState.
- perfDescriptorName format pin (perf: prefix).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
msgctl(2) operates on a System V message queue identified by msqid
(args[0]). msqid is a SysV IPC id returned by msgget, NOT a file
descriptor; capturing it as an fd would corrupt the fd-resource view.
ior already classifies msgctl as KindSysVOp in FamilyIPC (consistent
with siblings semctl/shmctl/msgget/msgsnd/msgrcv), emits a null_event
with no arg capture, and reports the int status as UNCLASSIFIED.
Add lock-in tests pinning this behavior:
- TestGenerateMsgctlHandler: KindSysVOp for msgctl/semctl/shmctl, all
FamilyIPC; generated enter handler emits null_event and captures no
args (asserts no ctx->args[ and no ev->fd); exit ret is UNCLASSIFIED.
- TestClassifyRetMsgctlUnclassified: msgctl ret is never a byte count.
No implementation change needed; mage generate produces no diff.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Audit of mlockall(2) (task 6w). mlockall(int flags) locks ALL process
memory and takes a single flags bitmask (MCL_CURRENT/MCL_FUTURE/
MCL_ONFAULT) with NO address range, unlike its KindMem siblings
mlock/mlock2/munlock (which take addr+len). It is therefore correctly
classified KindNull in FamilyMemory, matching its sibling munlockall(2).
All existing classification (classify.go, family.go, generated artifacts,
docs plan) already match; no fixes needed. Add two lock-in tests
documenting the reasoning: TestGenerateNullHandlerMlockall asserts the
enter handler emits a null_event and never captures the flags int as an
addr/fd/path, and TestClassifyRetMlockallUnclassified asserts the 0/-1
return is UNCLASSIFIED (not a byte count).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Audit of dup(2) found the tracing implementation already correct and
consistent with its dup2/dup3 siblings: dup(int oldfd) takes a single fd
argument (the sys_enter_dup tracepoint exposes it as field "fildes",
unsigned int, at args[0]). It is classified KindFd (a plain fd_event),
the enter handler captures ev->fd from args[0] per the KindFd
convention, it is in the FS family (fd grouping), and its exit returns
the new (lowest-numbered unused) descriptor or -1 as a plain UNCLASSIFIED
ret_event (never a byte-count transfer). Like dup2, dup carries no flags
and clears FD_CLOEXEC on the duplicate; the eventloop registerDup path
registers the returned newfd onto the same underlying file with flags=0,
which it already honors (applyFdTransferOp handles SYS_ENTER_DUP).
Docs (FS, fd) and the drift tests are in sync; existing coverage already
includes TestClassifyDup, the makeFdDupTestData full-lifecycle eventloop
test, and integration TestDupBasic/TestDupInvalidFd.
No discrepancies were found, so add a lock-in test (matching the dup2
audit) asserting the generated BPF C for dup captures fd from args[0]
(not args[1]), emits an fd_event (not a dup3_event), wires no flags, and
classifies the exit UNCLASSIFIED. Adds FormatExitDup testdata to drive
the exit handler assertions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Audit of the arch_prctl(2) syscall found the tracing implementation
already correct and consistent with the man page:
- enter classifies as KindNull (op/addr never captured as fd/path)
- exit is a ret_event with UNCLASSIFIED ret_type (int 0/-1 status)
- family is Process (deliberately, unlike its x86 siblings
ioperm/iopl/modify_ldt which are Misc), in sync with the docs and
the tracepoints drift tests
Add dedicated lock-in tests mirroring the prior iopl audit, using the
real kernel tracepoint fields (option/arg2 on enter, ret on exit) so
the heuristics are proven safe even without the name-only mapping.
Also add explicit FamilyProcess assertions for arch_prctl and
personality to guard against drift toward Misc.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Audit of syscall listen(2): int listen(int sockfd, int backlog). Confirmed
the tracing implementation already matches the man page and its socket
siblings (bind/connect/accept/getsockname/getpeername):
- KindFd, capturing ev->fd = args[0] (sockfd)
- FamilyNetwork
- exit ret_event UNCLASSIFIED (returns 0/-1, no byte count)
listen was already covered by the name-based classify/family/retclassify
tests but lacked a dedicated generated-handler lock-in test like its
bind/getsockname siblings. Add FormatListen/FormatExitListen tracepoint
fixtures and TestGenerateListenHandler asserting the enter captures
fd=args[0] (and never backlog at args[1]) and the exit stays UNCLASSIFIED.
No classification or generated-code changes; mage generate produces no diff.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Audit of kexec_file_load(2) against the man page confirmed the existing
classification is already correct and consistent: KindFd capturing
kernel_fd at args[0], FamilySecurity (matching its sibling kexec_load
after task 6v), and an UNCLASSIFIED ret_event exit (returns 0/-1).
The cmdline argument (args[3]) is a kernel command-line STRING, not a
filesystem path, and is correctly never read as a path; the second fd
initrd_fd (args[1]) is not captured, per the single-fd KindFd convention.
Add a dedicated lock-in test plus real-kernel-format fixtures so future
refactors cannot silently regress the fd wiring: assert ev->fd=args[0],
no args[1] fd capture, no bpf_probe_read_user_str on the cmdline, and an
UNCLASSIFIED (never READ/WRITE/TRANSFER) exit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Audit of socketpair(2) found the tracing implementation already correct:
KindSocketpair captures the two output fds from the sv[2] buffer (args[3])
at exit and never treats args[0] (the address-family/domain constant) as a
file descriptor. Family=Network and UNCLASSIFIED ret are consistent with the
socket/accept siblings and the docs.
Add regression lock-in tests so a future field-shape or classification change
cannot silently regress to recording the domain integer as a bogus fd:
- TestClassifySocketpairNotFd: pins the name-based override so socketpair is
KindSocketpair, never the generic KindFd path that reads args[0].
- TestHandleSocketpairExitDoesNotTrackDomainAsFd: uses AF_INET6 (10), distinct
from the returned fds, and asserts fd 10 is never tracked while sv0/sv1 are.
- TestHandleSocketpairExitDropsFdsOnError: on ret!=0 no descriptors are tracked.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Audit of setsockopt(2) found the tracing implementation already correct:
sockfd captured at ctx->args[0] (KindFd), exit ret_event UNCLASSIFIED, and
FamilyNetwork — matching the man page and the bind/connect/getsockname/
getpeername/getsockopt siblings, with generated C/Go and docs all consistent.
Add lock-in tests mirroring prior per-syscall audits:
- TestClassifySetsockoptEnterFd: enter is KindFd with no pathname capture,
asserted against the real sockfd/level/optname/optval/optlen fields.
- TestClassifyExitSetsockoptUnclassifiedRet: exit is KindRet + UNCLASSIFIED
(0/-1 status, not a byte count).
- TestClassifyExitGetsockoptUnclassifiedRet: same for the read-side sibling.
- TestClassifySyscallFamily: pin setsockopt (enter+exit) and getsockopt to
FamilyNetwork.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Audit of the rmdir(2) syscall found the tracing implementation already
correct and fully consistent with its siblings: rmdir is in the FS family,
classified KindPathname with the pathname captured from args[0] (its generated
BPF C handler is byte-identical to unlink's), and its exit is a ret_event with
UNCLASSIFIED ret_type (rmdir returns int 0/-1, not a byte count). The docs and
drift tests, integration tests (unlink-rmdir success and unlink-rmdir-notempty
ENOTEMPTY failure), and retclassify coverage all already match.
To guard against future drift, add a dedicated rmdir lock-in:
- FormatRmdir tracepoint fixture (single const char * pathname at args[0],
mirroring the real sys_enter_rmdir format and unlink's shape).
- TestGenerateRmdirHandlerCapturesPathFromArgs0: asserts the generated handler
reads the path from args[0] (with a negative guard against args[1], since
rmdir has no dirfd) and that the exit stays UNCLASSIFIED.
- TestRmdirFamilyAndKindMatchSiblings: asserts rmdir shares FamilyFS and
KindPathname/pathname with unlink/unlinkat/mkdir.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Audit of request_key(2) found the tracing implementation already
correct and consistent with the man page and the prior keyctl audit
(task 7v): request_key classifies as KindKeyctl/FamilySecurity, the
BPF handler captures option=-2 sentinel and key_serial=dest_keyring
(args[3]) with no path/string capture of the const char *
type/description/callout_info key-metadata args, and the exit returns
a key serial / -1 that stays UNCLASSIFIED.
Strengthen the dedicated TestClassifyRequestKey beyond a bare kind
check to also assert PathnameField stays empty (string args are key
metadata, not paths), family is Security on enter and exit, and the
return is UNCLASSIFIED — bringing it to parity with the add_key
contrast assertion. No code/generated changes; mage generate produces
no diff.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Add TestClassifyPwrite64WriteByteCount pinning the pwrite64(2) audit:
fd at args[0] (KindFd), FS family, and WRITE_CLASSIFIED return (the
syscall returns the number of bytes written). Asserts pread64 stays
READ_CLASSIFIED as the read-side positional contrast, guards against
transfer/unclassified misclassification, and checks the write/pread
sibling group so a stray reclassification trips the test.
No implementation changes: classify.go, family.go, generated C/Go, and
docs/syscall-tracing-plan.md were already consistent and correct.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Audit of lseek(2) confirmed the tracing implementation is already correct:
enter is a KindFd fd_event capturing the fd from args[0], the syscall is
FamilyFS alongside its read/write/fsync siblings, and the exit is a plain
ret_event that stays UNCLASSIFIED. lseek returns the RESULTING file offset
(off_t, bytes from the start of the file), which is a file position, NOT a
count of bytes transferred — so it must never be READ/WRITE/TRANSFER
classified, which would wrongly inflate I/O byte totals.
Add lock-in tests pinning that behaviour so a future reclassification trips:
- FormatLseek/FormatExitLseek tracepoint fixtures.
- TestClassifyFdLseek: enter resolves to KindFd (fd at args[0]).
- TestClassifyRetExitLseek: exit is KindRet and ClassifyRet stays UNCLASSIFIED.
- lseek entry in TestClassifySyscallPairAccepted (end-to-end pair).
- FS-family asserts for sys_enter/exit_lseek in family_test.
- Enriched UNCLASSIFIED comment in retclassify_test explaining offset != bytes.
No generated-artifact changes (mage generate produces no diff); no in-scope
bugs and no out-of-scope follow-ups found.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
kexec_load(2) and kexec_file_load are documented together on the same
man page and both load a new kernel for later execution by reboot(2).
kexec_file_load was already FamilySecurity, but kexec_load fell through
to FamilyMisc. Move kexec_load to FamilySecurity so the siblings share
a family. Kind classification was already correct: kexec_load takes raw
user pointers (KindNull, no fd/path) while kexec_file_load takes fds
(KindFd); the return value (long 0/-1, no byte count) stays UNCLASSIFIED.
Update docs/syscall-tracing-plan.md to match, regenerate artifacts, and
add lock-in tests for the family and UNCLASSIFIED return of both kexec
syscalls plus reboot.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Audit of io_uring_register(2) confirmed the existing tracing is correct:
KindFd with the io_uring fd captured at args[0], FamilyAIO (matching
io_uring_setup/io_uring_enter), and an UNCLASSIFIED ret_event exit. The
sys_enter_io_ KindNull prefix rule does NOT mis-catch it because
classifyNameOnly consults the exact nameOnlyKindsTable (KindFd) before the
prefix list.
Add two lock-in tests to guard these invariants:
- TestIoUringRegisterTablePrecedenceOverIoPrefix: the explicit KindFd table
entry wins over the sys_enter_io_ KindNull prefix rule (with an io_submit
sanity check that the prefix rule still yields KindNull for fd-less AIO
siblings).
- TestIoUringRegisterReturnUnclassified: the exit returns 0/small-positive,
never a byte count, so the io_uring group stays out of retClassifications.
No code, docs, or generated artifacts changed; mage generate produces no diff.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Audit of getsockname(2) confirmed correct tracing: enter is KindFd with
the sockfd captured from args[0], family is FamilyNetwork, and the exit
ret_event is UNCLASSIFIED (0/-1, no byte count) — matching the man page
and its bind/connect/listen/accept/getpeername siblings. Integration
coverage already exists (ioworkload calls Getsockname; TestSocketIntro-
spection asserts enter_getsockname).
Add lock-in tests symmetric with the existing getpeername coverage:
- TestClassifyExitGetsockname: exit tracepoint maps to KindRet.
- TestGenerateGetsocknameHandler: enter captures fd=args[0]; the addr
output pointer (args[1]) and addrlen in/out pointer (args[2]) are not
captured, and the exit stays UNCLASSIFIED.
- FormatGetsockname/FormatExitGetsockname fixtures copied verbatim from
the real kernel tracepoint format (third arg is a pointer, unlike
bind's by-value addrlen).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Audit of the getcwd(2) tracing path. getcwd's args[0] is a char *buf
OUTPUT buffer: the kernel writes the absolute cwd path into it and the
contents are only valid AFTER the syscall returns. Reading it at enter
would capture an empty/garbage string, so getcwd is correctly KindNull
at enter and the cwd is resolved at EXIT from /proc/<tid>/cwd when the
return value is positive (handleNullExit). Family FS, docs and drift
tests already aligned; no behavior change required.
Add lock-in tests pinning the correct behavior:
- generate: strengthen TestClassifyNullGetcwd to assert the enter kind
is never KindPathname/KindName and no pathname field is captured;
add TestClassifyByFieldGetcwdBufNotPath proving the generic field
classifier never treats char *buf as a path (defense-in-depth).
- eventloop: add GetcwdFailureEventTest asserting that a failed getcwd
(negative errno, e.g. -ERANGE) attaches no cwd path, and document the
output-buffer nuance in the success-case test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Audit of kill(2) (pid_t pid, int sig): the pid at args[0] is a process/
process-group identifier and sig a signal number, neither an fd nor a
path, so the enter tracepoint is KindNull and the int 0/-1 return is a
status code (UNCLASSIFIED), not a transferred byte count. Classification
and docs (Signals/null) already matched and need no change.
Add TestClassifyExitKillUnclassifiedRet (the return-value lock-in its
signal siblings tkill/tgkill/rt_sigqueueinfo already have) and harden
TestClassifyKillExplicitNull to assert no PathnameField is captured,
documenting the deliberate contrast with pidfd_send_signal (KindFd/IPC,
args[0] is a real pidfd).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Audit of iopl(2) (task wu). iopl(int level) changes the x86 I/O
privilege level of the calling thread and returns int 0/-1. The
existing coverage only asserted KindNull via a synthetic arg0 field
(TestClassifyE7NullNameOnlyKinds) and the FamilyMisc family tag (from
the prior ioperm audit, task uu). Add dedicated lock-in tests that use
the real 'int level' tracepoint field to prove it is never captured as
an fd or path, and that the sys_exit_iopl ret stays KindRet/UNCLASSIFIED
(a status code, not a transferred byte count). No implementation,
generated-artifact, or docs changes were needed - everything already
matched the man page.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Audit of gettid(2) ('pid_t gettid(void)', no args, always succeeds) found
the classification correct and consistent with its no-arg id-returning
siblings getpid/getppid/getuid/getgid (FamilyProcess, KindNull enter,
ret_event UNCLASSIFIED exit), and mage generate produces no diff. However
gettid lacked dedicated lock-in coverage and was missing entirely from the
family_test.go Process table despite its siblings being asserted there.
Add TestClassifyGettidNullEnter and TestClassifyExitGettidUnclassifiedRet
(mirroring the getgid pattern: enter null_event capturing nothing, exit ret
classified UNCLASSIFIED so the returned tid is never mistaken for a byte
count) plus gettid enter+exit FamilyProcess assertions in family_test.go.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Audit of getgid(2) found its classification already correct: family
Process, enter KindNull (gid_t getgid(void) takes no arguments), exit
KindRet with UNCLASSIFIED ret_type (returns a gid, never a byte count,
and always succeeds). Family, kind, generated C handler, and docs all
matched its no-arg id-returning siblings getuid/geteuid/getegid/getpid/
gettid/getppid, so no implementation or doc changes were needed.
Add two dedicated lock-in tests using the real tracepoint fields,
mirroring the setuid/setpgid audit pattern, so a stray reclassification
of getgid trips a test:
- TestClassifyGetgidNullEnter: enter is KindNull, no path/fd capture.
- TestClassifyExitGetgidUnclassifiedRet: exit is KindRet, UNCLASSIFIED.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
The finit_module audit (task 8t) confirmed the tracing implementation
matches man 2 finit_module: KindFd with fd at args[0], param_values
string never captured as a path, exit UNCLASSIFIED, and FamilySecurity
alongside init_module/delete_module. No implementation discrepancies
were found.
Extend TestClassifyInitModuleVsFinitModule to also assert the
previously-untested dimensions so the classification stays pinned:
- finit_module captures no path (empty PathnameField), like init_module
- both module-loading syscalls are FamilySecurity
- both exits are UNCLASSIFIED (0/-1 return, no byte count)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Audit of bind(2): int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen). Verified the existing classification is correct and
consistent with its socket-setup siblings connect/listen/accept/
getsockname/getpeername:
- KindFd, capturing ev->fd = args[0] (the sockfd); the addr pointer and
addrlen are not captured.
- FamilyNetwork.
- Exit is UNCLASSIFIED (returns 0/-1, no transferred byte count).
No implementation or doc changes were needed (docs/syscall-tracing-plan.md
already lists bind under Network and fd; drift test green). Added
regression coverage:
- FormatBind/FormatExitBind fixtures mirroring the real kernel tracepoint.
- TestGenerateBindHandler with negative guards (no probe_read on the
sockaddr, no fd capture from args[1]/args[2], exit stays UNCLASSIFIED).
- bind + connect/listen/getsockname/getpeername added to the
family (FamilyNetwork) and ret-classification (UNCLASSIFIED) lock-in
lists.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Audit of signalfd4(2) confirmed the tracing is correct: classified as
KindEventfd in FamilyIPC like its fd-creating siblings (eventfd2,
timerfd_create, inotify_init1, signalfd), flags captured from args[3]
per signalfd4(ufd, mask, sizemask, flags), and the return value left
Unclassified (it is an fd, not a byte count).
Add testdata fixtures FormatSignalfd4/FormatExitSignalfd4 (real Linux 7.0
tracepoint data) and a codegen lock-in test asserting the generated
handler reads flags from args[3], with negative guards against args[0]
(ufd), args[1] (mask pointer) and args[2] (sizemask).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Audit of syscall select confirmed the tracing is already correct:
select is KindPoll/FamilyPolling like poll/ppoll/pselect6, the enter
handler captures nfds from args[0] as a count (not as an fd) and the
timeout from the args[4] timeval, and the exit is an UNCLASSIFIED
ret_event (ready-fd count, not a byte transfer).
Add TestGenerateSelectHandlerCapturesNfdsAndTimevalTimeout mirroring the
ppoll lock-in test, with negative assertions that no argument is ever
captured as an fd and that the exit carries no bytes/fd fields. This
guards against regressing nfds (a count) into a KindFd fd capture.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
sendfile64(out_fd, in_fd, offset, count) transfers bytes between two file
descriptors in the kernel and returns the number of bytes written to out_fd.
Its tracepoint fields carry no field literally named "fd", so it fell through
to KindNull and captured no descriptor at all - inconsistent with its sibling
copy_file_range (KindFd) and the read/write/sendto/recvfrom families.
Add an explicit sys_enter_sendfile64 -> KindFd override that captures out_fd
(args[0], the destination the bytes are written to), matching the single-fd
KindFd convention. The return value stays TransferClassified, consistent with
copy_file_range/splice/tee/vmsplice. Family stays Network (sendfile is
historically socket-oriented; copy_file_range=FS is pure file-to-file).
Update docs/syscall-tracing-plan.md (move sendfile64 from null to fd kind),
regenerate C/Go artifacts, fix the phase-A classify assertion, and add
TestClassifySendfile64CapturesOutFd as a lock-in + negative test. The existing
TestRetbytesPhaseA integration test still passes with the runtime change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Add TestClassifySchedSetparamPidNotFd as a dedicated regression test for
the sched_setparam(2) audit. The syscall takes a pid_t (args[0], NOT an
fd; 0 = calling thread) and a userspace const struct sched_param *, so
the enter must classify as KindNull and the exit as KindRet/UNCLASSIFIED
(returns 0/-1, no byte transfer), matching family Sched.
Implementation, docs, and generated C/Go artifacts already matched the
man page; sched_setparam was previously only asserted as a sibling check
inside the sched_getparam test. This pins its full behavior directly,
consistent with prior sched_getparam/sched_getattr audits.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|