Suppress spurious error when config dir creation fails due to TOCTOU
Some checks failed
Rust checks / rustfmt (push) Has been cancelled
Rust checks / clippy (push) Has been cancelled
make test / ubuntu (push) Has been cancelled
make test / ubuntu-32bit-static-pcre2 (push) Has been cancelled
make test / ubuntu-asan (push) Has been cancelled
make test / macos (push) Has been cancelled

Our recursive create_dir() first calls stat() to check if the directory
already exists and then mkdir() trying to create it. If another (fish)
process creates the same directory after our stat() but before our
mkdir(), then our mkdir() fails with EEXIST. This error is spurious
if there is already a directory at this path (and permissions are
correct).

Let's switch to the stdlib version, which promises to solve this issue.
They currently do it by running mkdir() first and ask stat() later.

This implies that they will only return success even if we don't have
any of rwx permissions on the directory, but that was already a problem
before this change. We silently don't write history in that case..

Fixes #10813
This commit is contained in:
Johannes Altmanninger 2024-10-31 07:35:29 +01:00
parent 9e01981bb9
commit 3710e98d65
2 changed files with 4 additions and 31 deletions

View File

@ -2,14 +2,14 @@
//! for testing if a command with a given name can be found in the PATH, and various other
//! path-related issues.
use crate::common::{is_windows_subsystem_for_linux as is_wsl, wcs2zstring, WSL};
use crate::common::{is_windows_subsystem_for_linux as is_wsl, wcs2osstring, wcs2zstring, WSL};
use crate::env::{EnvMode, EnvStack, Environment};
use crate::expand::{expand_tilde, HOME_DIRECTORY};
use crate::flog::{FLOG, FLOGF};
#[cfg(not(target_os = "linux"))]
use crate::libc::{MNT_LOCAL, ST_LOCAL};
use crate::wchar::prelude::*;
use crate::wutil::{normalize_path, path_normalize_for_cd, waccess, wdirname, wmkdir, wstat};
use crate::wutil::{normalize_path, path_normalize_for_cd, waccess, wdirname, wstat};
use errno::{errno, set_errno, Errno};
use libc::{EACCES, ENOENT, ENOTDIR, F_OK, X_OK};
use once_cell::sync::Lazy;
@ -667,8 +667,8 @@ fn make_base_directory(xdg_var: &wstr, non_xdg_homepath: &wstr) -> BaseDirectory
let mut remoteness = DirRemoteness::unknown;
if path.is_empty() {
err = ENOENT;
} else if !create_directory(&path) {
err = errno().0;
} else if let Err(io_error) = std::fs::create_dir_all(wcs2osstring(&path)) {
err = io_error.raw_os_error().unwrap_or_default();
} else {
err = 0;
// Need to append a trailing slash to check the contents of the directory, not its parent.
@ -685,27 +685,6 @@ fn make_base_directory(xdg_var: &wstr, non_xdg_homepath: &wstr) -> BaseDirectory
}
}
/// Make sure the specified directory exists. If needed, try to create it and any currently not
/// existing parent directories, like mkdir -p,.
///
/// Return 0 if, at the time of function return the directory exists, -1 otherwise.
fn create_directory(d: &wstr) -> bool {
let md = loop {
match wstat(d) {
Err(md) if md.kind() == ErrorKind::Interrupted => continue,
md => break md,
}
};
match md {
Ok(md) if md.is_dir() => true,
Err(e) if e.kind() == ErrorKind::NotFound => {
let dir: &wstr = wdirname(d);
return create_directory(dir) && wmkdir(d, 0o700) == 0;
}
_ => false,
}
}
/// Return whether the given path is on a remote filesystem.
fn path_remoteness(path: &wstr) -> DirRemoteness {
let narrow = wcs2zstring(path);

View File

@ -501,12 +501,6 @@ pub fn wbasename(mut path: &wstr) -> &wstr {
path
}
/// Wide character version of mkdir.
pub fn wmkdir(name: &wstr, mode: libc::mode_t) -> libc::c_int {
let name_narrow = wcs2zstring(name);
unsafe { libc::mkdir(name_narrow.as_ptr(), mode) }
}
/// Wide character version of rename.
pub fn wrename(old_name: &wstr, new_name: &wstr) -> libc::c_int {
let old_narrow = wcs2zstring(old_name);