openat2: fix internal assumption causing EINVAL

Inside of `openat2`, we are assuming that if we are passed
`AT_SYMLINK_NOFOLLOW` that we are also passed a symlink. This is simply
not the case, as POSIX defines the flag to only have a noticable effect
if the resolved path is a symlink. Therefore, we cannot assume that we
have a symlink if we see `AT_SYMLINK_NOFOLLOW`. The previous behavior
caused an `EINVAL` in redoxfs because we do a consistency check to
error out if we are passed `O_SYMLINK` (which was added because
`AT_SYMLINK_NOFOLLOW` was observed) and aren't a symlink.

Hmm, maybe a special errno like `ENOTLNK` should be deployed for this? It's
specific enough that it could possibly be added to a future POSIX.
This commit is contained in:
Connor-GH
2026-05-01 17:01:39 -05:00
parent 19d380f198
commit 76d801f7cd
+9 -5
View File
@@ -485,6 +485,14 @@ pub(super) fn openat2_path(dirfd: c_int, path: &str, at_flags: c_int) -> Result<
}
}
fn at_flags_to_open_flags(at_flags: c_int) -> c_int {
let mut out: c_int = 0;
if at_flags & fcntl::AT_SYMLINK_NOFOLLOW == fcntl::AT_SYMLINK_NOFOLLOW {
out |= fcntl::O_NOFOLLOW;
}
out
}
/// Canonicalize and open `path` with respect to `dirfd`.
///
/// This unexported openat2 is similar to the Linux syscall but with a different interface. The
@@ -512,11 +520,7 @@ pub(super) fn openat2(
oflags: c_int,
) -> Result<File, Errno> {
// Translate at flags into open flags; openat will do this on its own most likely.
let oflags = if at_flags & fcntl::AT_SYMLINK_NOFOLLOW == fcntl::AT_SYMLINK_NOFOLLOW {
fcntl::O_CLOEXEC | fcntl::O_NOFOLLOW | fcntl::O_PATH | fcntl::O_SYMLINK | oflags
} else {
fcntl::O_CLOEXEC | oflags
};
let oflags = at_flags_to_open_flags(at_flags) | fcntl::O_CLOEXEC | oflags;
let c_path = CString::new(path).map_err(|_| Errno(EINVAL))?;
File::openat(dirfd, c_path.as_c_str().into(), oflags)
}