| Age | Commit message (Collapse) | Author |
|
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>
|
|
readahead(2) was traced (KindFd enter fd_event from args[0], UNCLASSIFIED
exit ret_event) but had no integration or ioworkload scenario coverage,
unlike its sibling sync_file_range. Add readwrite-readahead and
readwrite-readahead-ebadf scenarios plus TestReadwriteReadahead /
TestReadwriteReadaheadEbadf, asserting enter_readahead capture with path
attribution, zero attributed bytes (readahead returns 0/-1, not a byte
count, so it is correctly UNCLASSIFIED), and positive end-to-end duration.
No classification change: inspection confirms KindFd / UNCLASSIFIED is
correct per man 2 readahead; bytesFromRet returns 0 for UNCLASSIFIED so
the 0/-1 return is never misattributed as bytes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
listxattrat(2) (Linux 6.13+) returns the size in bytes of the list of
extended attribute names, exactly like listxattr/llistxattr/flistxattr,
but its exit was classified UNCLASSIFIED, so its read bytes were dropped
from I/O totals. Classify it as ReadClassified and regenerate the BPF
handler (ret_type now READ_CLASSIFIED). This mirrors the getxattrat fix
(task ku, commit c3177bd) and completes xattr-family consistency:
get-family and list-family are READ_CLASSIFIED while set-family and
remove-family stay UNCLASSIFIED (they return 0/-1).
Update the docs ReadClassified list and the retclassify expectation, and
add an ioworkload scenario plus integration test: the workload sets a
user xattr then lists names via the raw listxattrat(2) syscall with
AT_FDCWD, and the test asserts enter_listxattrat captures the file path
and accounts the returned name-list size as read bytes.
Task: r20
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
getxattrat(2) (Linux 6.13+) returns the xattr value size in bytes,
exactly like getxattr/lgetxattr/fgetxattr, but its exit was classified
UNCLASSIFIED, so its read bytes were dropped from I/O totals. Classify
it as ReadClassified and regenerate the BPF handler (ret_type now
READ_CLASSIFIED). Path extraction (args[1], after the dirfd) and the
name-not-captured-as-path behaviour were already correct.
Update the docs ReadClassified list and the retclassify expectation,
and add the first xattr integration coverage: an ioworkload scenario
that sets then getxattrat-reads a user xattr on tmpfs, plus a test that
asserts enter_getxattrat captures the file path (not the xattr name)
and accounts the returned value size as read bytes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
The POSIX per-process timer family (timer_create, timer_settime,
timer_gettime, timer_getoverrun, timer_delete) had no end-to-end
integration coverage; only the unrelated fd-returning sibling
timerfd_create was exercised. Add a posix-timer-lifecycle workload
scenario and TestPosixTimerLifecycle to validate these are traced as
null_events, and guard against timer_create being misclassified like
timerfd_create (timer_create returns a timer_t via an output pointer,
not an fd, so its records must carry no 'timerfd:' descriptor path).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
The retbytes integration coverage exercised read/write/sendto/etc but the
positional read p-variants only had presence assertions (pread64) or no
coverage at all (preadv/preadv2), so their READ_CLASSIFIED byte accounting
was validated only by unit tests, not end-to-end.
Add a positive byte-count assertion to TestReadwritePread and new
readwrite-preadv / readwrite-preadv2 workload scenarios plus integration
tests that read a known payload and assert the attributed byte count,
mirroring the existing pwrite64 assertion. preadv2 lacks a Go
syscall.SYS_PREADV2 constant, so its number is provided per-GOARCH
(amd64=327, arm64=286) following the securitySyscallNumbers pattern.
Addresses the read side of b20.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
openat2(2) is the one open-family syscall with a structurally distinct
argument layout: flags/mode live inside the open_how struct (args[2]),
not as a plain int, and args[3] is the struct size. The tracer correctly
reads the path from args[1] and omits flags (ev->flags = -1) rather than
misreading the struct ptr/size, and registers the returned fd->path
mapping via the shared handleOpenExit path. This was verified by
inspection but had no integration scenario, unlike open/openat/creat/
open_by_handle_at.
Add an open-openat2 ioworkload scenario issuing the raw openat2 syscall
(Go has no wrapper and routes Open through openat) and a TestOpenOpenat2
integration test asserting the enter_openat2 tracepoint captures the
path. Verified passing as root.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
The mountfs-management integration scenario covered the new mount API
syscalls fsmount/move_mount/mount/umount/pivot_root but not fsopen, the
API's entry point and a direct eventfd-kind sibling of fsmount. Add a
best-effort fsopen("tmpfs", FSOPEN_CLOEXEC) call (closing the returned
context fd on success) and assert enter_fsopen is traced.
fsopen's tracing is otherwise correct: args[1] flags captured, args[0]
fsname (a filesystem TYPE, not a path) deliberately not treated as a
pathname, returned fd registered as the 'fsopenfd:<flags>' descriptor.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
Audit of io_submit tracing (task 0v) confirmed the tracer is correct by
inspection: KindNull (sys_enter_io_ prefix rule) so ctx_id/nr/iocbpp are
opaque and no fd/path is captured; FamilyAIO; return is UNCLASSIFIED (the
return is a count of iocbs submitted, not a byte count, so it must not
inflate READ/WRITE/TRANSFER totals). Enter/exit are paired and timed. No
implementation discrepancy and no docs drift.
Add a genuine end-to-end test: new aio-submit ioworkload scenario sets up
an AIO context and submits one real IOCB_CMD_PWRITE iocb against a temp
file via raw syscalls, then tears the context down. TestAioSubmit asserts
the enter_io_submit tracepoint fires for the AIO family workload.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
The classic Linux AIO family (io_setup/io_submit/io_getevents/io_cancel/
io_destroy) had no integration coverage: family_test.go exercises only
FS/Memory/IPC/Network/Process/Sched/Time, and iouring_test.go covers only
the distinct io_uring_* family. io_setup is classified KindNull/FamilyAIO,
which is correct by inspection against man 2 io_setup (nr_events is a count,
ctx_idp an output pointer, so no fd/path is captured), so the tracer itself
needed no change.
Add an ioworkload AIO scenario that drives io_setup(2)/io_destroy(2) raw
(no privileges, no libaio) plus an EINVAL variant, and integration tests
that assert ior records the enter_io_setup tracepoint end-to-end, mirroring
the existing iouring scenario/test pattern.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
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>
|