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