From 98b280109fc344dbac3e6cd3bcf00e82dda007cc Mon Sep 17 00:00:00 2001 From: youknowdot Date: Fri, 9 Oct 2026 02:27:10 -0700 Subject: [PATCH] Return EBADF for negative stat file descriptors (#9008) * Reject negative file descriptors before fstat Return EBADF for negative ordinary descriptors before calling rustix, avoiding debug-build assertions on canonical invalid-fd tests. Keep directory-fd sentinel and absolute-path handling unchanged. Assisted-by: Codex:model-version-unavailable * Cover negative stat descriptors through the interpreter Exercise ordinary negative descriptors including values accepted by rustix as directory sentinels, without changing canonical tests or directory-fd parsing. Assisted-by: Codex:model-version-unavailable * Move negative stat regression into the os snippet Preserve the original Unix RustPython-only coverage and every regression assertion while removing the redundant Rust interpreter wrapper. CPython uses -1 as a path sentinel, so keep this regression scoped to its original runtime. Assisted-by: Codex:model-version-unavailable --- crates/host_env/src/fileutils.rs | 4 ++++ extra_tests/snippets/stdlib_os.py | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/crates/host_env/src/fileutils.rs b/crates/host_env/src/fileutils.rs index 8db9af895..1674c9074 100644 --- a/crates/host_env/src/fileutils.rs +++ b/crates/host_env/src/fileutils.rs @@ -11,6 +11,10 @@ pub use windows::{StatStruct, fstat}; #[cfg(not(windows))] pub fn fstat(fd: crate::crt_fd::Borrowed<'_>) -> std::io::Result { + // Unlike a directory fd, fstat accepts no negative descriptor sentinels. + if fd.as_raw() < 0 { + return Err(std::io::Error::from_raw_os_error(libc::EBADF)); + } rustix::fs::fstat(fd).map_err(Into::into) } diff --git a/extra_tests/snippets/stdlib_os.py b/extra_tests/snippets/stdlib_os.py index a1f40ef4c..2c673e3de 100644 --- a/extra_tests/snippets/stdlib_os.py +++ b/extra_tests/snippets/stdlib_os.py @@ -8,6 +8,26 @@ from testutils import assert_raises assert os.name == "posix" or os.name == "nt" + +def stat_rejects_negative_file_descriptors(): + import errno + import os + + for stat in (os.stat, os.fstat): + for fd in (-1, -5, -9, -100, -(2**31)): + try: + stat(fd) + except OSError as exc: + assert exc.errno == errno.EBADF, (stat, fd, exc) + else: + raise AssertionError((stat, fd, "accepted a negative file descriptor")) + + +# Keep the original RustPython-only coverage: CPython treats -1 as a path sentinel. +if os.name == "posix" and sys.implementation.name == "rustpython": + stat_rejects_negative_file_descriptors() + + fd = os.open("README.md", os.O_RDONLY) assert fd > 0