diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-30 22:30:26 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-30 22:30:26 +0300 |
| commit | 75d00f479d333476990ff18f7427905bb09d49f0 (patch) | |
| tree | a1aea501073236677a002685b921d7d6c7a42352 /internal/generate | |
| parent | 136c4dfb6846595b98cf2b04a93525ce91d86d5e (diff) | |
test(generate): lock in semctl handler as KindSysVOp/FamilyIPC
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>
Diffstat (limited to 'internal/generate')
| -rw-r--r-- | internal/generate/codegen_test.go | 82 |
1 files changed, 82 insertions, 0 deletions
diff --git a/internal/generate/codegen_test.go b/internal/generate/codegen_test.go index 474b3d5..a3d0500 100644 --- a/internal/generate/codegen_test.go +++ b/internal/generate/codegen_test.go @@ -442,6 +442,88 @@ func TestClassifyRetMsgctlUnclassified(t *testing.T) { } } +// TestGenerateSemctlHandler locks in how semctl(2) is generated. Per the man +// page: +// +// int semctl(int semid, int semnum, int op, ...) +// +// semctl performs the control operation (op) on the System V semaphore set +// identified by semid (or on the semnum-th semaphore of that set). CRITICAL: +// semid (args[0]) is a System V IPC identifier returned by semget — it is NOT a +// file descriptor. Capturing it as an fd would record a meaningless number in +// the fd column and corrupt the fd-resource view. args[1] (semnum) is a +// semaphore index, args[2] (op) is a command, and the optional fourth arg is a +// union semun (an int val or a userspace pointer to a control block / value +// array); none of these is a traced I/O resource. The return value is an int +// status (0, or a non-negative value for the GETVAL/GETPID/GETNCNT/GETZCNT/ +// IPC_INFO/SEM_INFO/SEM_STAT info ops, and -1 on error) — never a byte count. +// ior therefore classifies semctl as KindSysVOp in FamilyIPC, identical to its +// SysV control-syscall siblings msgctl and shmctl (and the rest of the sysv-op +// group). Consequently: +// - The enter handler emits a struct null_event and must NOT capture any arg — +// in particular it must NOT wire args[0] (the semid IPC id) as an fd. +// - The exit handler reports the raw int status as UNCLASSIFIED; it is not a +// byte count, so it must never be tagged READ/WRITE/TRANSFER. +// +// This guards against a regression to KindFd (which would misrecord the SysV +// IPC id as a file descriptor) and pins consistency with msgctl/shmctl. The +// sibling TestGenerateMsgctlHandler checks the same invariants on msgctl's +// generated output; this test exercises semctl's own generated handler body. +func TestGenerateSemctlHandler(t *testing.T) { + // Classification consistency: semctl must resolve to KindSysVOp in FamilyIPC, + // matching its SysV control-syscall siblings msgctl/shmctl. + res, ok := classifyNameOnly("sys_enter_semctl") + if !ok || res.Kind != KindSysVOp { + t.Fatalf("semctl classified as kind=%v ok=%v, want KindSysVOp", res.Kind, ok) + } + if fam := ClassifySyscallFamily("sys_enter_semctl"); fam != FamilyIPC { + t.Errorf("semctl family = %q, want FamilyIPC", fam) + } + + output := GenerateTracepointsC(mustParseAll(t, syntheticPair("semctl"))) + + enterSec := `SEC("tracepoint/syscalls/sys_enter_semctl")` + exitSec := `SEC("tracepoint/syscalls/sys_exit_semctl")` + requireContains(t, output, enterSec) + requireContains(t, output, "struct null_event *ev") + requireContains(t, output, "ev->event_type = ENTER_NULL_EVENT;") + requireContains(t, output, "ev->trace_id = SYS_ENTER_SEMCTL;") + + // The KindSysVOp enter handler must not wire any argument: args[0] (semid) is + // a SysV IPC id, NOT an fd; args[1] (semnum) is a semaphore index; args[2] + // (op) is a command; the optional union semun arg is an int/userspace + // pointer. Scope to the enter handler body (from the enter SEC up to the exit + // SEC) so we only check what the enter handler emits. + enterStart := strings.Index(output, enterSec) + exitStart := strings.Index(output, exitSec) + if enterStart < 0 || exitStart < 0 || exitStart <= enterStart { + t.Fatalf("semctl: handlers not found in expected order") + } + enterBody := output[enterStart:exitStart] + if strings.Contains(enterBody, "ctx->args[") { + t.Error("semctl must be KindSysVOp: enter handler must not capture any arg (args[0] semid is a SysV IPC id, not an fd)") + } + // Belt-and-suspenders: the enter handler must never assign ev->fd, which would + // mean the SysV semid was captured as a file descriptor. + requireNotContains(t, enterBody, "ev->fd") + + // The exit handler reports the raw int status as UNCLASSIFIED, not a byte count. + requireContains(t, output, exitSec) + requireContains(t, output, "ev->ret = ctx->ret;") + requireContains(t, output, "ev->ret_type = UNCLASSIFIED;") +} + +// TestClassifyRetSemctlUnclassified locks in that semctl's return value is +// UNCLASSIFIED. semctl returns an int status (0, or a non-negative value for the +// GETVAL/GETPID/GETNCNT/GETZCNT/IPC_INFO/SEM_INFO/SEM_STAT info ops, and -1 on +// error) — never a byte count — so it must never be tagged as a +// READ/WRITE/TRANSFER transfer size. +func TestClassifyRetSemctlUnclassified(t *testing.T) { + if got := ClassifyRet("sys_exit_semctl"); got != Unclassified { + t.Errorf("semctl ret classification = %q, want %q", got, Unclassified) + } +} + // TestGenerateClone3Handler locks in how clone3(2) is generated. Per the man // page: // |
