diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-29 22:09:13 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-29 22:09:13 +0300 |
| commit | e08e4c166890889baa6ccd8da4086992298ab108 (patch) | |
| tree | ce717e1a1c45edc417276ab8351e2fb34ed77144 /internal | |
| parent | 8cc96b7425dfe825461fd5d6c52356e1ddfe4952 (diff) | |
test(ppoll): lock in poll-family arg layout, reject fd capture
Audit of the ppoll syscall confirmed the tracing implementation is
correct: ppoll(struct pollfd *fds, nfds_t nfds,
const struct timespec *tmo_p, const sigset_t *sigmask) is classified as
KindPoll (poll_event, sibling of poll/select/pselect6) in FamilyPolling,
with an UNCLASSIFIED ret_event exit. The enter handler captures nfds from
args[1] and the timeout from the args[2] timespec, and correctly never
reads args[0] (a pointer to an ARRAY of pollfd structs) as a file
descriptor.
Add a dedicated codegen lock-in test mirroring the poll/pselect6 tests,
including negative assertions that args[0] is not captured as an fd and
that no byte-transfer field is emitted (the return value is a ready-count
>=0 or -1, not a byte count). Introduce a requireNotContains helper for
these negative checks.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/generate/codegen_test.go | 39 |
1 files changed, 39 insertions, 0 deletions
diff --git a/internal/generate/codegen_test.go b/internal/generate/codegen_test.go index 1c498db..1d6214c 100644 --- a/internal/generate/codegen_test.go +++ b/internal/generate/codegen_test.go @@ -942,6 +942,35 @@ func TestGeneratePselect6HandlerCapturesTimeoutPointer(t *testing.T) { requireContains(t, output, "ev->timeout_ns = ts.tv_sec * 1000000000LL + ts.tv_nsec;") } +// TestGeneratePpollHandlerCapturesNfdsAndTimeoutPointer locks in the ppoll +// argument layout. ppoll(struct pollfd *fds, nfds_t nfds, +// const struct timespec *tmo_p, const sigset_t *sigmask): args[0] is a +// userspace pointer to an ARRAY of pollfd structs (NOT a file descriptor), +// nfds is args[1], and the timeout is a timespec pointer at args[2]. The +// handler must therefore capture nfds from args[1] and the timeout from the +// args[2] timespec, and must NEVER read args[0] as an fd (that would be a real +// bug: args[0] is a pointer, so an fd capture would record a garbage fd). +func TestGeneratePpollHandlerCapturesNfdsAndTimeoutPointer(t *testing.T) { + output := generateFromPair(t, FormatPpoll, FormatExitPpoll) + + // Enter: poll_event with nfds from args[1] and timespec timeout from args[2]. + requireContains(t, output, "struct poll_event *ev") + requireContains(t, output, "ev->event_type = ENTER_POLL_EVENT;") + requireContains(t, output, "ev->trace_id = SYS_ENTER_PPOLL;") + requireContains(t, output, "ev->nfds = (__s32)ctx->args[1];") + requireContains(t, output, "if (ctx->args[2] != 0) {") + requireContains(t, output, "ev->timeout_ns = ts.tv_sec * 1000000000LL + ts.tv_nsec;") + + // Negative: args[0] is a pollfd-array pointer and must never be captured + // as an fd, and the exit is an UNCLASSIFIED ret_event (ready count, not a + // byte transfer), so no bytes/fd fields are emitted. + requireNotContains(t, output, "ev->fd = (__s32)ctx->args[0];") + requireNotContains(t, output, "ev->bytes") + // Exit: plain ret_event recording the ready-count (>=0) or -1. + requireContains(t, output, "ev->event_type = EXIT_RET_EVENT;") + requireContains(t, output, "ev->ret = ctx->ret;") +} + func TestGenerateSleepHandlerCapturesRequestedTimespec(t *testing.T) { output := generateFromPair(t, FormatNanosleep, FormatExitNanosleep) @@ -1423,3 +1452,13 @@ func requireContains(t *testing.T, haystack, needle string) { t.Errorf("output missing expected string: %q", needle) } } + +// requireNotContains fails when haystack unexpectedly contains needle. Used for +// negative assertions, e.g. that a pointer argument is not misclassified as an +// fd capture. +func requireNotContains(t *testing.T, haystack, needle string) { + t.Helper() + if strings.Contains(haystack, needle) { + t.Errorf("output unexpectedly contains forbidden string: %q", needle) + } +} |
