summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-30 22:30:26 +0300
committerPaul Buetow <paul@buetow.org>2026-05-30 22:30:26 +0300
commit75d00f479d333476990ff18f7427905bb09d49f0 (patch)
treea1aea501073236677a002685b921d7d6c7a42352 /internal
parent136c4dfb6846595b98cf2b04a93525ce91d86d5e (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')
-rw-r--r--internal/generate/codegen_test.go82
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:
//