summaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-23 20:06:30 +0300
committerPaul Buetow <paul@buetow.org>2026-05-23 20:06:30 +0300
commita2151af07c58efa3bf541e0273702ae991eda799 (patch)
treebce91331822961365c19aa78337bd2e7542447b8 /cmd
parent4541ee21203f7c494530f142a6387244ac3a6c62 (diff)
1c guard ioworkload select fd_set against high fd values
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Diffstat (limited to 'cmd')
-rw-r--r--cmd/ioworkload/scenario_polling.go24
-rw-r--r--cmd/ioworkload/scenario_polling_test.go31
2 files changed, 51 insertions, 4 deletions
diff --git a/cmd/ioworkload/scenario_polling.go b/cmd/ioworkload/scenario_polling.go
index e41ec9a..f840e7c 100644
--- a/cmd/ioworkload/scenario_polling.go
+++ b/cmd/ioworkload/scenario_polling.go
@@ -198,17 +198,33 @@ func callPpoll(pipefd [2]int) (int, error) {
return int(r1), nil
}
+// fdSetBits is the maximum number of file descriptors an fdSet can represent.
+// This matches the Linux FD_SETSIZE constant (1024).
+const fdSetBits = 16 * 64
+
+// errFDOutOfRange is returned when a file descriptor is too large for the
+// fixed-size fd_set used by select/pselect6 (>= 1024).
+var errFDOutOfRange = fmt.Errorf("fd >= %d: too large for select fd_set", fdSetBits)
+
type fdSet [16]uint64
-func (s *fdSet) set(fd int) {
+// set marks fd in the bitset. It returns errFDOutOfRange when fd falls
+// outside the fixed 1024-bit array, preventing an index-out-of-bounds panic.
+func (s *fdSet) set(fd int) error {
+ if fd < 0 || fd >= fdSetBits {
+ return errFDOutOfRange
+ }
idx := fd / 64
bit := uint(fd % 64)
s[idx] |= 1 << bit
+ return nil
}
func callSelect(pipefd [2]int) (int, error) {
var readSet fdSet
- readSet.set(pipefd[0])
+ if err := readSet.set(pipefd[0]); err != nil {
+ return 0, fmt.Errorf("select: %w", err)
+ }
timeout := syscall.Timeval{Sec: 0, Usec: 100000}
r1, _, errno := syscall.RawSyscall6(
syscall.SYS_SELECT,
@@ -229,7 +245,9 @@ func callSelect(pipefd [2]int) (int, error) {
func callPselect6(pipefd [2]int) (int, error) {
var readSet fdSet
- readSet.set(pipefd[0])
+ if err := readSet.set(pipefd[0]); err != nil {
+ return 0, fmt.Errorf("pselect6: %w", err)
+ }
timeout := unix.Timespec{Sec: 0, Nsec: 100 * 1_000_000}
r1, _, errno := syscall.RawSyscall6(
unix.SYS_PSELECT6,
diff --git a/cmd/ioworkload/scenario_polling_test.go b/cmd/ioworkload/scenario_polling_test.go
index 304602c..ebb04d2 100644
--- a/cmd/ioworkload/scenario_polling_test.go
+++ b/cmd/ioworkload/scenario_polling_test.go
@@ -37,12 +37,41 @@ func TestFdSetSetMarksBit(t *testing.T) {
t.Parallel()
var set fdSet
- set.set(65)
+ if err := set.set(65); err != nil {
+ t.Fatalf("set(65) unexpected error: %v", err)
+ }
if set[1]&(1<<1) == 0 {
t.Fatalf("fd bit was not set: %#v", set)
}
}
+func TestFdSetSetRejectsOutOfRange(t *testing.T) {
+ t.Parallel()
+
+ tests := []struct {
+ name string
+ fd int
+ }{
+ {name: "exactly_1024", fd: 1024},
+ {name: "large_fd", fd: 4096},
+ {name: "negative_fd", fd: -1},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+ var set fdSet
+ err := set.set(tt.fd)
+ if err == nil {
+ t.Fatalf("set(%d) should return error, got nil", tt.fd)
+ }
+ if !errors.Is(err, errFDOutOfRange) {
+ t.Fatalf("set(%d) error = %v, want %v", tt.fd, err, errFDOutOfRange)
+ }
+ })
+ }
+}
+
func TestClassicPollingSyscallsReturnReadyCount(t *testing.T) {
var pipefd [2]int
if err := syscall.Pipe(pipefd[:]); err != nil {