Component: src/rawposix/src/fs_calls.rs:3154 (dup2_syscall)
Severity: POSIX conformance
Found by: CONC-004 (#1304), conc_004_dup_close_fork_refcounts.c:708
Symptom
dup2(badfd, badfd) reports success and returns an fd number that was never open.
The caller then treats a closed descriptor as valid.
Root cause
// src/rawposix/src/fs_calls.rs:3154
if old_vfd_arg > MAXFD as u64 || new_vfd_arg > MAXFD as u64 {
return syscall_error(Errno::EBADF, "dup2", "Bad File Descriptor");
} else if old_vfd_arg == new_vfd_arg {
// Does nothing
return new_vfd_arg as i32; // <-- oldfd never validated
}
POSIX: "If oldfd is not a valid file descriptor, then the call fails, and newfd is
not closed." dup2() returns newfd unchanged only when oldfd is valid and
equal to it. The range check catches out-of-range numbers but not in-range-but-closed
ones.
There is a second defect in the same function: the get_specific_virtual_fd(...)
call on the success path ends in .unwrap(), panicking the host on failure.
Proposed fix
Move validation before the equality check, and replace the .unwrap() with EBADF:
if old_vfd_arg > MAXFD as u64 || new_vfd_arg > MAXFD as u64 {
return syscall_error(Errno::EBADF, "dup2", "Bad File Descriptor");
- } else if old_vfd_arg == new_vfd_arg {
- // Does nothing
+ }
+
+ // `oldfd` must be validated BEFORE the `oldfd == newfd` fast path.
+ let old_vfd = match fdtables::translate_virtual_fd(cageid, old_vfd_arg) {
+ Ok(entry) => entry,
+ Err(_e) => return syscall_error(Errno::EBADF, "dup2", "Bad File Descriptor"),
+ };
+
+ if old_vfd_arg == new_vfd_arg {
+ // oldfd is valid and equals newfd: no-op, newfd is NOT closed.
return new_vfd_arg as i32;
}
then the existing body, with .unwrap() replaced by a match returning EBADF.
Guest-visible change: dup2(badfd, badfd) now fails with EBADF; previously it
"succeeded".
Un-skip on merge
Remove process_tests/deterministic/conc_004_dup_close_fork_refcounts.c from
skip_test_cases.txt.
Component:
src/rawposix/src/fs_calls.rs:3154(dup2_syscall)Severity: POSIX conformance
Found by: CONC-004 (#1304),
conc_004_dup_close_fork_refcounts.c:708Symptom
dup2(badfd, badfd)reports success and returns an fd number that was never open.The caller then treats a closed descriptor as valid.
Root cause
POSIX: "If oldfd is not a valid file descriptor, then the call fails, and newfd is
not closed."
dup2()returnsnewfdunchanged only whenoldfdis valid andequal to it. The range check catches out-of-range numbers but not in-range-but-closed
ones.
There is a second defect in the same function: the
get_specific_virtual_fd(...)call on the success path ends in
.unwrap(), panicking the host on failure.Proposed fix
Move validation before the equality check, and replace the
.unwrap()withEBADF:if old_vfd_arg > MAXFD as u64 || new_vfd_arg > MAXFD as u64 { return syscall_error(Errno::EBADF, "dup2", "Bad File Descriptor"); - } else if old_vfd_arg == new_vfd_arg { - // Does nothing + } + + // `oldfd` must be validated BEFORE the `oldfd == newfd` fast path. + let old_vfd = match fdtables::translate_virtual_fd(cageid, old_vfd_arg) { + Ok(entry) => entry, + Err(_e) => return syscall_error(Errno::EBADF, "dup2", "Bad File Descriptor"), + }; + + if old_vfd_arg == new_vfd_arg { + // oldfd is valid and equals newfd: no-op, newfd is NOT closed. return new_vfd_arg as i32; }then the existing body, with
.unwrap()replaced by amatchreturningEBADF.Guest-visible change:
dup2(badfd, badfd)now fails withEBADF; previously it"succeeded".
Un-skip on merge
Remove
process_tests/deterministic/conc_004_dup_close_fork_refcounts.cfromskip_test_cases.txt.