Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/uu/stdbuf/locales/en-US.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ stdbuf-error-line-buffering-stdin-meaningless = line buffering stdin is meaningl
stdbuf-error-invalid-mode = invalid mode {$error}
stdbuf-error-value-too-large = invalid mode '{$value}': Value too large for defined data type
stdbuf-error-external-libstdbuf-not-found = External libstdbuf not found at configured path: {$path}
stdbuf-error-preload-path-separator = libstdbuf path {$path} contains ':', which cannot be represented in {$var}
stdbuf: set TMPDIR to a directory whose path has no ':'
stdbuf-error-permission-denied = failed to execute process: Permission denied
stdbuf-error-no-such-file = failed to execute process: No such file or directory
stdbuf-error-failed-to-execute = failed to execute process: {$error}
Expand Down
2 changes: 2 additions & 0 deletions src/uu/stdbuf/locales/fr-FR.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ stdbuf-error-line-buffering-stdin-meaningless = la mise en mémoire tampon par l
stdbuf-error-invalid-mode = mode invalide {$error}
stdbuf-error-value-too-large = mode invalide '{$value}' : Valeur trop grande pour le type de données défini
stdbuf-error-external-libstdbuf-not-found = libstdbuf externe introuvable au chemin configuré : {$path}
stdbuf-error-preload-path-separator = le chemin de libstdbuf {$path} contient ':', ce qui ne peut pas être représenté dans {$var}
stdbuf: définissez TMPDIR vers un répertoire dont le chemin ne contient pas ':'
stdbuf-error-permission-denied = échec de l'exécution du processus : Permission refusée
stdbuf-error-no-such-file = échec de l'exécution du processus : Aucun fichier ou répertoire de ce type
stdbuf-error-failed-to-execute = échec de l'exécution du processus : {$error}
Expand Down
13 changes: 13 additions & 0 deletions src/uu/stdbuf/src/stdbuf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use std::process;
use tempfile::TempDir;
use tempfile::tempdir;
use thiserror::Error;
use uucore::display::Quotable;
use uucore::error::{UResult, USimpleError, UUsageError};
use uucore::format_usage;
use uucore::parser::parse_size::parse_size_u64;
Expand Down Expand Up @@ -205,6 +206,18 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let tmp_dir = tempdir()
.map_err(|e| UUsageError::new(125, format!("failed to create temp directory: {e}")))?;
let (preload_env, libstdbuf) = get_preload_env(&tmp_dir)?;
// The preload variable is a colon-separated list with no escaping mechanism,
// so a path containing ':' does not round-trip: the dynamic loader splits it
// and treats the leading component as a library to load. Since the temp
// directory is derived from $TMPDIR, that component would be attacker-chosen
// whenever TMPDIR crosses a privilege boundary. Refuse instead of preloading
// something we did not select.
if libstdbuf.as_os_str().as_encoded_bytes().contains(&b':') {
return Err(USimpleError::new(
125,
translate!("stdbuf-error-preload-path-separator", "path" => libstdbuf.quote(), "var" => preload_env),
));
}
command.env(preload_env, libstdbuf);
set_command_env(&mut command, "_STDBUF_I", &options.stdin);
set_command_env(&mut command, "_STDBUF_O", &options.stdout);
Expand Down
19 changes: 19 additions & 0 deletions tests/by-util/test_stdbuf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,25 @@ fn invalid_input() {
new_ucmd!().arg("-/").fails_with_code(125);
}

// linux-gated to match the `at_and_ucmd` import above; the check itself is not
// platform-specific.
#[cfg(all(target_os = "linux", not(feature = "feat_external_libstdbuf")))]
#[test]
fn test_tmpdir_with_colon_is_rejected() {
// The preload variable is a colon-separated list with no escaping, so a
// libstdbuf path containing ':' would be split by the loader and its leading
// component loaded as a library of its own. With $TMPDIR under an attacker's
// control that component is attacker-chosen, so refuse instead.
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("evil.so:x");

ucmd.env("TMPDIR", at.plus("evil.so:x"))
.arg("-o0")
.arg("true")
.fails_with_code(125)
.stderr_contains("contains ':'");
}

#[cfg(all(unix, not(feature = "feat_external_libstdbuf")))]
#[test]
fn test_permission() {
Expand Down
Loading