From 75ca2cf29ee7abf481728a6c4f0b164b6d99194e Mon Sep 17 00:00:00 2001 From: Connor-GH Date: Thu, 8 Jan 2026 17:56:49 -0600 Subject: [PATCH] Implement mkdirat Linux's variant uses the syscall as intended. Redox's variant uses fpath to build a path to pass to mkdir from the file descriptor plus the file name, which isn't atomic due to the fpath lookup being subject to TOCTOU when paired with mkdir. --- src/header/sys_stat/mod.rs | 9 +++++++++ src/platform/linux/mod.rs | 6 +++++- src/platform/pal/mod.rs | 2 ++ src/platform/redox/mod.rs | 18 ++++++++++++++++++ 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/header/sys_stat/mod.rs b/src/header/sys_stat/mod.rs index aa09ef21c0..6925cb5a81 100644 --- a/src/header/sys_stat/mod.rs +++ b/src/header/sys_stat/mod.rs @@ -160,6 +160,15 @@ pub unsafe extern "C" fn lstat(path: *const c_char, buf: *mut stat) -> c_int { res } +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn mkdirat(dirfd: c_int, path: *const c_char, mode: mode_t) -> c_int { + let path = CStr::from_ptr(path); + Sys::mkdirat(dirfd, path, mode) + .map(|()| 0) + .or_minus_one_errno() +} + /// See . #[unsafe(no_mangle)] pub unsafe extern "C" fn mkdir(path: *const c_char, mode: mode_t) -> c_int { diff --git a/src/platform/linux/mod.rs b/src/platform/linux/mod.rs index 9659a57298..dece9c8d21 100644 --- a/src/platform/linux/mod.rs +++ b/src/platform/linux/mod.rs @@ -502,8 +502,12 @@ impl Pal for Sys { e_raw(unsafe { syscall!(LSEEK, fildes, offset, whence) }).map(|o| o as off_t) } + fn mkdirat(dir_fildes: c_int, path: CStr, mode: mode_t) -> Result<()> { + e_raw(unsafe { syscall!(MKDIRAT, dir_fildes, path.as_ptr(), mode) }).map(|_| ()) + } + fn mkdir(path: CStr, mode: mode_t) -> Result<()> { - e_raw(unsafe { syscall!(MKDIRAT, AT_FDCWD, path.as_ptr(), mode) }).map(|_| ()) + Sys::mkdirat(AT_FDCWD, path, mode) } fn mknodat(dir_fildes: c_int, path: CStr, mode: mode_t, dev: dev_t) -> Result<()> { diff --git a/src/platform/pal/mod.rs b/src/platform/pal/mod.rs index b10b8053c0..0eb381bd76 100644 --- a/src/platform/pal/mod.rs +++ b/src/platform/pal/mod.rs @@ -170,6 +170,8 @@ pub trait Pal { fn lseek(fildes: c_int, offset: off_t, whence: c_int) -> Result; + fn mkdirat(fildes: c_int, path: CStr, mode: mode_t) -> Result<()>; + fn mkdir(path: CStr, mode: mode_t) -> Result<()>; fn mkfifo(path: CStr, mode: mode_t) -> Result<()>; diff --git a/src/platform/redox/mod.rs b/src/platform/redox/mod.rs index 9338a97508..2e8480a2da 100644 --- a/src/platform/redox/mod.rs +++ b/src/platform/redox/mod.rs @@ -680,6 +680,24 @@ impl Pal for Sys { Ok(syscall::lseek(fd as usize, offset as isize, whence as usize)? as off_t) } + fn mkdirat(dir_fd: c_int, path_name: CStr, mode: mode_t) -> Result<()> { + let mut dir_path_buf = [0; 4096]; + let res = Sys::fpath(dir_fd, &mut dir_path_buf)?; + + let dir_path = str::from_utf8(&dir_path_buf[..res as usize]).map_err(|_| Errno(EBADR))?; + + let resource_path = + path::canonicalize_using_cwd(Some(&dir_path), &path_name.to_string_lossy()) + // Since parent_dir_path is resolved by fpath, it is more likely that + // the problem was with path. + .ok_or(Errno(ENOENT))?; + + Sys::mkdir( + CStr::borrow(&CString::new(resource_path.as_bytes()).unwrap()), + mode, + ) + } + fn mkdir(path: CStr, mode: mode_t) -> Result<()> { File::create( path,