summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-29 22:38:23 +0300
committerPaul Buetow <paul@buetow.org>2026-05-29 22:38:23 +0300
commit8da6c62f617d6351a4ab6062bf2ea104b3b31ec2 (patch)
tree417797280abcbaa7946b1ad28043d63a895a2b81
parent18c8e8f5f3d7cc9bbbcb9b8b9be65477110363a7 (diff)
test(generate): lock in get_robust_list/set_robust_list classification
Audit of get_robust_list(2)/set_robust_list(2) found the existing classification already correct and consistent across classify.go, family.go, the generated C handlers, and docs/syscall-tracing-plan.md: - enter is KindNull: args[0] of get_robust_list is a PID, not an fd, and head_ptr/len_ptr are userspace output pointers (no fd/path), so the pid must not be picked up as a file descriptor. - exit is ret_event UNCLASSIFIED: both return 0/-1 with no byte count. - family is Misc, grouped with the per-thread sibling rseq rather than promoted to IPC like the futex_* shared-memory primitives. Add TestClassifyGetRobustListPidNotFd pinning these invariants (Kind, Family, Ret) plus a futex_* IPC contrast case, mirroring the prior sched_getattr/recvmsg audit lock-in tests. No behavior change. The IPC-vs-Misc family question for the robust-list pair is tracked as a separate follow-up task, not changed here (no clear correctness case). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
-rw-r--r--internal/generate/classify_test.go79
1 files changed, 79 insertions, 0 deletions
diff --git a/internal/generate/classify_test.go b/internal/generate/classify_test.go
index 25f1960..5301abb 100644
--- a/internal/generate/classify_test.go
+++ b/internal/generate/classify_test.go
@@ -2149,6 +2149,85 @@ func TestClassifySchedGetattrPidNotFd(t *testing.T) {
}
}
+// TestClassifyGetRobustListPidNotFd is a lock-in regression test for the
+// get_robust_list(2) / set_robust_list(2) audit. The signatures are:
+//
+// long get_robust_list(int pid, struct robust_list_head **head_ptr, size_t *len_ptr)
+// long set_robust_list(struct robust_list_head *head, size_t len)
+//
+// get_robust_list's args[0] is a PID (a thread/process identifier), NOT a file
+// descriptor, and head_ptr/len_ptr are userspace output pointers; set_robust_list
+// takes a userspace head pointer and a length. Neither syscall touches an fd or a
+// filesystem path, so both enter as KindNull (plain null_event). On success both
+// return 0 (-1 on error) and transfer no byte count, so their exits stay
+// UNCLASSIFIED.
+//
+// Invariants pinned here:
+// - enter classifies as KindNull (the pid arg must NOT be picked up as an fd),
+// - family is Misc — grouped with the per-thread sibling rseq, not promoted to
+// IPC like the futex_* shared-memory primitives (see family.go / family_test.go),
+// - return classifies as UNCLASSIFIED (0/-1, no byte transfer).
+//
+// NOTE: get_robust_list/set_robust_list are robust-futex bookkeeping and could
+// arguably sit with futex_* under IPC; that grouping question is tracked as a
+// follow-up rather than changed here, since the syscalls register/query a
+// per-thread pointer (like rseq) rather than operating on shared memory.
+func TestClassifyGetRobustListPidNotFd(t *testing.T) {
+ // Field layout mirrors the actual kernel tracepoint format for
+ // sys_enter_get_robust_list: int pid, struct robust_list_head **head_ptr,
+ // size_t *len_ptr.
+ r := ClassifyFormat(&Format{
+ Name: "sys_enter_get_robust_list",
+ ExternalFields: []Field{
+ {Type: "long", Name: "__syscall_nr"},
+ {Type: "int", Name: "pid"},
+ {Type: "struct robust_list_head **", Name: "head_ptr"},
+ {Type: "size_t *", Name: "len_ptr"},
+ },
+ })
+ if r.Kind != KindNull {
+ t.Fatalf("get_robust_list: got kind %d, want KindNull (pid arg must not be treated as fd)", r.Kind)
+ }
+
+ // set_robust_list takes a userspace head pointer and a length, no fd/path.
+ s := ClassifyFormat(&Format{
+ Name: "sys_enter_set_robust_list",
+ ExternalFields: []Field{
+ {Type: "long", Name: "__syscall_nr"},
+ {Type: "struct robust_list_head *", Name: "head"},
+ {Type: "size_t", Name: "len"},
+ },
+ })
+ if s.Kind != KindNull {
+ t.Fatalf("set_robust_list: got kind %d, want KindNull", s.Kind)
+ }
+
+ // Both syscalls are FamilyMisc, sharing the family with their per-thread
+ // sibling rseq (and NOT promoted to FamilyIPC like the futex_* primitives).
+ for _, name := range []string{
+ "sys_enter_get_robust_list",
+ "sys_enter_set_robust_list",
+ "sys_enter_rseq",
+ } {
+ if fam := ClassifySyscallFamily(name); fam != FamilyMisc {
+ t.Errorf("%s: got family %s, want FamilyMisc", name, fam)
+ }
+ }
+
+ // Contrast: the futex_* siblings ARE classified IPC, so the robust-list pair
+ // is deliberately NOT lumped in with them at the family level.
+ if fam := ClassifySyscallFamily("sys_enter_futex"); fam != FamilyIPC {
+ t.Errorf("futex: got family %s, want FamilyIPC (contrast case)", fam)
+ }
+
+ // Returns: both exits are UNCLASSIFIED (0/-1, no byte transfer).
+ for _, name := range []string{"get_robust_list", "set_robust_list"} {
+ if got := ClassifyRet("sys_exit_" + name); got != Unclassified {
+ t.Errorf("ClassifyRet(sys_exit_%s) = %q, want UNCLASSIFIED", name, got)
+ }
+ }
+}
+
// TestClassifyRecvmsgReadByteCount is a lock-in regression test for the
// recvmsg(2) audit. The syscall signature is:
//