From dc41de7b81ba0b28da5ceb1a357df767dcf4b3f9 Mon Sep 17 00:00:00 2001 From: MsfPablo Date: Sat, 8 Aug 2026 21:32:53 +0200 Subject: [PATCH] mktemp: strip OS error code from permission-denied message mktemp leaked the raw `Permission denied (os error 13) at path "..."` text from the underlying tempfile crate when creating a temporary file/dir in an unwritable directory, instead of the clean `Permission denied` message used elsewhere in uutils. Route the io::Error through a new MkTempError::Io variant that renders with strip_errno, as maintainer sylvestre suggested in #13720, so the `(os error N)` suffix and the internal `at path ...` detail are dropped. Add a regression test (skipped under root) that asserts the clean message for both the file and directory cases. Closes #13720 --- src/uu/mktemp/src/mktemp.rs | 9 +++++--- tests/by-util/test_mktemp.rs | 42 ++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index 1f872714f09..f4cb420aad1 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -8,7 +8,7 @@ use clap::builder::{TypedValueParser, ValueParserFactory}; use clap::{Arg, ArgAction, ArgMatches, Command}; use uucore::display::{Quotable, println_verbatim}; -use uucore::error::{FromIo, UError, UResult, UUsageError}; +use uucore::error::{FromIo, UError, UResult, UUsageError, strip_errno}; use uucore::format_usage; use uucore::translate; @@ -74,6 +74,9 @@ enum MkTempError { #[error("{}", translate!("mktemp-error-not-found", "template_type" => .0.clone(), "template" => .1.quote()))] NotFound(String, PathBuf), + + #[error("{}", strip_errno(.0))] + Io(std::io::Error), } impl UError for MkTempError { @@ -570,7 +573,7 @@ fn make_temp_dir(dir: &Path, prefix: &str, rand: usize, suffix: &str) -> UResult let path = Path::new(dir).join(filename); Err(MkTempError::NotFound(translate!("mktemp-template-type-directory"), path).into()) } - Err(e) => Err(e.into()), + Err(e) => Err(MkTempError::Io(e).into()), } } @@ -599,7 +602,7 @@ fn make_temp_file(dir: &Path, prefix: &str, rand: usize, suffix: &str) -> UResul let path = Path::new(dir).join(filename); Err(MkTempError::NotFound(translate!("mktemp-template-type-file"), path).into()) } - Err(e) => Err(e.into()), + Err(e) => Err(MkTempError::Io(e).into()), } } diff --git a/tests/by-util/test_mktemp.rs b/tests/by-util/test_mktemp.rs index 0fa2896bb6f..fbbb7b4cd54 100644 --- a/tests/by-util/test_mktemp.rs +++ b/tests/by-util/test_mktemp.rs @@ -1209,3 +1209,45 @@ fn test_mktemp_hidden_file_single_dot() { template_name.len() ); } + +/// Creating a temporary file or directory in an unwritable directory must fail +/// with a clean `Permission denied` message, without leaking the raw +/// `(os error N)` suffix or the internal `at path ...` detail that the +/// underlying `tempfile` crate would otherwise embed. +#[cfg(unix)] +#[test] +fn test_permission_denied_clean_message() { + use std::fs; + use std::os::unix::fs::PermissionsExt; + use uucore::process::geteuid; + + // chmod 000 does not block root, so the assertion would not hold. + if geteuid() == 0 { + return; + } + + let scene = TestScenario::new(util_name!()); + let dir = scene.fixtures.plus("noperm"); + fs::create_dir_all(&dir).unwrap(); + fs::set_permissions(&dir, fs::Permissions::from_mode(0o000)).unwrap(); + + // File case: `mktemp -p ` (default template) creates inside . + scene + .ucmd() + .arg("-p") + .arg(&dir) + .fails() + .stderr_only("mktemp: Permission denied\n"); + + // Directory case: `mktemp -d -p `. + scene + .ucmd() + .arg("-d") + .arg("-p") + .arg(&dir) + .fails() + .stderr_only("mktemp: Permission denied\n"); + + // Restore perms so the fixture tempdir cleanup can remove the entry. + let _ = fs::set_permissions(&dir, fs::Permissions::from_mode(0o755)); +}