From 7f18bf9aff34c3ef72b2285b09785e513c75101e Mon Sep 17 00:00:00 2001 From: Ivan Mironov Date: Thu, 21 May 2026 17:11:36 +0200 Subject: [PATCH] Fix panic in select.select() when too many FDs specified Also: * Add regression test into existing `extra_tests/snippets/stdlib_select.py` * Stop calculating nfds on Windows as it is ignored there Panic: thread 'main' (189598) panicked at /root/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/libc-0.2.186/src/unix/linux_like/mod.rs:1777:9: index out of bounds: the len is 16 but the index is 16 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace thread 'main' (189598) panicked at library/core/src/panicking.rs:225:5: panic in a function that cannot unwind stack backtrace: 0: 0xaaab763a0b88 - <::print::DisplayBacktrace as core[f1abae5f1257fe69]::fmt::Display>::fmt 1: 0xaaab75590ff0 - core[f1abae5f1257fe69]::fmt::write 2: 0xaaab763a94fc - ::write_fmt 3: 0xaaab7638b714 - std[1934960bf7f41d0a]::panicking::default_hook::{closure#0} 4: 0xaaab7639b288 - std[1934960bf7f41d0a]::panicking::default_hook 5: 0xaaab7639b478 - std[1934960bf7f41d0a]::panicking::panic_with_hook 6: 0xaaab7638b7ec - std[1934960bf7f41d0a]::panicking::panic_handler::{closure#0} 7: 0xaaab76382654 - std[1934960bf7f41d0a]::sys::backtrace::__rust_end_short_backtrace:: 8: 0xaaab7638c504 - __rustc[b7425922bef61dcf]::rust_begin_unwind 9: 0xaaab754f778c - core[f1abae5f1257fe69]::panicking::panic_nounwind_fmt 10: 0xaaab754f7714 - core[f1abae5f1257fe69]::panicking::panic_nounwind 11: 0xaaab754f786c - core[f1abae5f1257fe69]::panicking::panic_cannot_unwind 12: 0xaaab75a6283c - rustpython_vm::function::builtin::,rustpython_vm::function::builtin::OwnedParam,rustpython_vm::function::builtin::OwnedParam,rustpython_vm::function::builtin::OwnedParam),R,rustpython_vm::vm::VirtualMachine> for F>::call_::h2471c8e242c9b51d 13: 0xaaab75db1e68 - rustpython_vm::types::slot::Callable::slot_call::hd1c1ad0ad14f306b 14: 0xaaab762c0a50 - rustpython_vm::protocol::callable::PyCallable::invoke::h9f6d571fca351ca6 15: 0xaaab75c550e8 - rustpython_vm::protocol::callable::::call_with_args::hed1f4a61aba2dced 16: 0xaaab762e7c24 - rustpython_vm::frame::ExecutingFrame::execute_call::h0ad3490dd74ed1e3 17: 0xaaab762fed40 - rustpython_vm::frame::ExecutingFrame::run::hcf90f0950fc26812 18: 0xaaab761e6768 - rustpython_vm::vm::VirtualMachine::with_frame::hd49ba6fcdf2422e2 19: 0xaaab75c45398 - rustpython_vm::builtins::function::>::invoke_with_locals::h42de3d2316941ce2 20: 0xaaab76132a80 - rustpython_vm::builtins::function::vectorcall_function::h7331cb67b334e867 21: 0xaaab763369d8 - rustpython_vm::protocol::callable::::vectorcall::h9019c5d16685c89a 22: 0xaaab762f4b54 - rustpython_vm::frame::ExecutingFrame::execute_call_vectorcall::h120134e11a58c946 23: 0xaaab76302a7c - rustpython_vm::frame::ExecutingFrame::run::hcf90f0950fc26812 24: 0xaaab761e6768 - rustpython_vm::vm::VirtualMachine::with_frame::hd49ba6fcdf2422e2 25: 0xaaab761e7f24 - rustpython_vm::vm::VirtualMachine::run_code_obj::h354618be6e5cc553 26: 0xaaab761e2d18 - rustpython_vm::vm::python_run::file_run::::run_any_file::h783d3127fbc0b523 27: 0xaaab757d700c - rustpython::run_rustpython::h354efb8d817cefbf 28: 0xaaab757c79e0 - std::thread::local::LocalKey::with::hc9728e249843a926 29: 0xaaab757db860 - rustpython_vm::vm::interpreter::Interpreter::run::h42ac1fe9ed2287a2 30: 0xaaab757d7b30 - rustpython::run::hf14a209db5b4289c 31: 0xaaab757e2eb4 - rustpython::main::h1b59d8e13276ac48 32: 0xaaab757e2eec - std::sys::backtrace::__rust_begin_short_backtrace::h47e4b1f073f2155c 33: 0xaaab757e2ed4 - std::rt::lang_start::{{closure}}::h663a6c3dc7d80101 34: 0xaaab76399fd4 - std[1934960bf7f41d0a]::rt::lang_start_internal 35: 0xaaab757e2f44 - main 36: 0xfffed057655c - __libc_start_call_main 37: 0xfffed057663c - __libc_start_main@@GLIBC_2.34 38: 0xaaab755526f0 - _start 39: 0x0 - thread caused non-unwinding panic. aborting. Aborted (core dumped) cargo run --release -- extra_tests/snippets/stdlib_select.py --- crates/stdlib/src/select.rs | 30 +++++++++++++++++++++------ extra_tests/snippets/stdlib_select.py | 25 ++++++++++++++++++++++ 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/crates/stdlib/src/select.rs b/crates/stdlib/src/select.rs index 3ec4e62259a..12e55db57f2 100644 --- a/crates/stdlib/src/select.rs +++ b/crates/stdlib/src/select.rs @@ -5,7 +5,7 @@ pub(crate) use decl::module_def; use crate::vm::{ PyObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine, builtins::PyListRef, }; -use rustpython_host_env::select::{self as host_select, FdSet, RawFd}; +use rustpython_host_env::select::{self as host_select, FdSet, RawFd, platform::FD_SETSIZE}; use std::io; #[derive(Traverse)] @@ -81,8 +81,22 @@ mod decl { let seq2set = |list: &PyObject| -> PyResult<(Vec, FdSet)> { let v: Vec = list.try_to_value(vm)?; + + let too_many_fds = cfg_select! { + windows => v.len() > FD_SETSIZE as usize, + _ => v.len() > FD_SETSIZE, + }; + if too_many_fds { + return Err(vm.new_value_error("too many file descriptors in select()")); + } + let mut fds = FdSet::new(); for fd in &v { + #[cfg(unix)] + if fd.fno as usize >= FD_SETSIZE { + return Err(vm.new_value_error("file descriptor out of range in select()")); + } + fds.insert(fd.fno); } Ok((v, fds)) @@ -97,11 +111,15 @@ mod decl { return Ok((empty.clone(), empty.clone(), empty)); } - let nfds: i32 = [&mut r, &mut w, &mut x] - .iter_mut() - .filter_map(|set| set.highest()) - .max() - .map_or(0, |n| n + 1) as _; + let nfds = cfg_select! { + windows => 0, // value is ignored on windows + + _ => [&mut r, &mut w, &mut x] + .iter_mut() + .filter_map(|set| set.highest()) + .max() + .map_or(0, |n| n + 1) as _, + }; loop { let mut tv = timeout.map(host_select::sec_to_timeval); diff --git a/extra_tests/snippets/stdlib_select.py b/extra_tests/snippets/stdlib_select.py index d27bb82b1c3..9afd95beca7 100644 --- a/extra_tests/snippets/stdlib_select.py +++ b/extra_tests/snippets/stdlib_select.py @@ -4,6 +4,8 @@ from testutils import assert_raises +TOO_MANY_SELECT_FDS = 4096 + class Nope: pass @@ -42,3 +44,26 @@ def fileno(self): assert recvr in rres assert sendr in wres + +# Too many descriptors for select.select() +if sys.platform != "win32": + import resource + + soft_max_fds, hard_max_fds = resource.getrlimit(resource.RLIMIT_NOFILE) + if soft_max_fds != resource.RLIM_INFINITY: + # 100 additional fds should be enough for interpreter needs + need_fds = TOO_MANY_SELECT_FDS + 100 + + soft_max_fds = max(soft_max_fds, need_fds) + if hard_max_fds != resource.RLIM_INFINITY: + assert hard_max_fds >= soft_max_fds, ( + "Not enough file descriptors for this test" + ) + resource.setrlimit(resource.RLIMIT_NOFILE, (soft_max_fds, hard_max_fds)) +sockets = [s for _ in range(TOO_MANY_SELECT_FDS // 2) for s in socket.socketpair()] +assert_raises(ValueError, select.select, sockets, [], [], 0) +del sockets +a, b = socket.socketpair() +# CPython disallows this on *nix systems too. +assert_raises(ValueError, select.select, [a] * TOO_MANY_SELECT_FDS, [], [], 0) +del a, b