Skip to content

bug: dup2_syscall validates oldfd after the equality fast path #1374

Description

@Shounak-Ghosh

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions