Medium severity4.4NVD Advisory· Published Apr 22, 2026· Updated May 4, 2026
CVE-2026-35358
CVE-2026-35358
Description
The cp utility in uutils coreutils, when performing recursive copies (-R), incorrectly treats character and block device nodes as stream sources rather than preserving them. Because the implementation reads bytes into regular files at the destination instead of using mknod, device semantics are destroyed (e.g., /dev/null becomes a regular file). This behavior can lead to runtime denial of service through disk exhaustion or process hangs when reading from unbounded device nodes.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
coreutilscrates.io | < 0.7.0 | 0.7.0 |
Affected products
2Patches
1e6a3bb596f14cp: handle special files (#11163)
6 files changed · +179 −53
Cargo.lock+1 −0 modified@@ -3293,6 +3293,7 @@ dependencies = [ "fluent", "indicatif", "libc", + "nix", "selinux", "tempfile", "thiserror 2.0.18",
src/uu/cp/Cargo.toml+1 −0 modified@@ -41,6 +41,7 @@ fluent = { workspace = true } [target.'cfg(unix)'.dependencies] exacl = { workspace = true, optional = true } +nix = { workspace = true, features = ["fs"] } [[bin]] name = "cp"
src/uu/cp/locales/en-US.ftl+1 −0 modified@@ -87,6 +87,7 @@ cp-error-selinux-get-context = failed to get security context of { $path } cp-error-selinux-error = SELinux error: { $error } cp-error-selinux-context-conflict = cannot combine --context (-Z) with --preserve=context cp-error-cannot-create-fifo = cannot create fifo { $path }: File exists +cp-error-cannot-create-special-file = cannot create special file { $path }: { $error } cp-error-invalid-attribute = invalid attribute { $value } cp-error-failed-to-create-whole-tree = failed to create whole tree cp-error-failed-to-create-directory = Failed to create directory: { $error }
src/uu/cp/locales/fr-FR.ftl+1 −0 modified@@ -87,6 +87,7 @@ cp-error-selinux-get-context = échec de l'obtention du contexte de sécurité d cp-error-selinux-error = Erreur SELinux : { $error } cp-error-selinux-context-conflict = impossible de combiner --context (-Z) avec --preserve=context cp-error-cannot-create-fifo = impossible de créer le fifo { $path } : Le fichier existe +cp-error-cannot-create-special-file = impossible de créer le fichier spécial { $path } : { $error } cp-error-invalid-attribute = attribut invalide { $value } cp-error-failed-to-create-whole-tree = échec de la création de l'arborescence complète cp-error-failed-to-create-directory = Échec de la création du répertoire : { $error }
src/uu/cp/src/cp.rs+46 −53 modified@@ -2,15 +2,15 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (ToDO) copydir ficlone fiemap ftruncate linkgs lstat nlink nlinks pathbuf pwrite reflink strs xattrs symlinked deduplicated advcpmv nushell IRWXG IRWXO IRWXU IRWXUGO IRWXU IRWXG IRWXO IRWXUGO +// spell-checker:ignore (ToDO) copydir ficlone fiemap ftruncate linkgs lstat nlink nlinks pathbuf pwrite reflink strs xattrs symlinked deduplicated advcpmv nushell IRWXG IRWXO IRWXU IRWXUGO IRWXU IRWXG IRWXO IRWXUGO sflag use std::cmp::Ordering; use std::collections::{HashMap, HashSet}; use std::ffi::OsString; use std::fmt::Display; use std::fs::{self, Metadata, OpenOptions, Permissions}; #[cfg(unix)] -use std::os::unix::fs::{FileTypeExt, PermissionsExt}; +use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt}; #[cfg(unix)] use std::os::unix::net::UnixListener; use std::path::{Path, PathBuf, StripPrefixError}; @@ -22,6 +22,8 @@ use uucore::translate; use clap::{Arg, ArgAction, ArgMatches, Command, builder::ValueParser, value_parser}; use filetime::FileTime; use indicatif::{ProgressBar, ProgressStyle}; +#[cfg(unix)] +use nix::sys::stat::{Mode, SFlag, dev_t, mknod as nix_mknod, mode_t}; use thiserror::Error; use platform::copy_on_write; @@ -2227,13 +2229,8 @@ fn handle_copy_mode( source_metadata: &Metadata, symlinked_files: &mut HashSet<FileInformation>, source_in_command_line: bool, - source_is_fifo: bool, - source_is_socket: bool, created_parent_dirs: &mut HashSet<PathBuf>, - #[cfg(unix)] source_is_stream: bool, ) -> CopyResult<PerformedAction> { - let source_is_symlink = source_metadata.is_symlink(); - match options.copy_mode { CopyMode::Link => { if dest.exists() { @@ -2267,13 +2264,9 @@ fn handle_copy_mode( dest, options, context, - source_is_symlink, - source_is_fifo, - source_is_socket, + source_metadata, symlinked_files, created_parent_dirs, - #[cfg(unix)] - source_is_stream, )?; } CopyMode::SymLink => { @@ -2291,13 +2284,9 @@ fn handle_copy_mode( dest, options, context, - source_is_symlink, - source_is_fifo, - source_is_socket, + source_metadata, symlinked_files, created_parent_dirs, - #[cfg(unix)] - source_is_stream, )?; } UpdateMode::None => { @@ -2328,13 +2317,9 @@ fn handle_copy_mode( dest, options, context, - source_is_symlink, - source_is_fifo, - source_is_socket, + source_metadata, symlinked_files, created_parent_dirs, - #[cfg(unix)] - source_is_stream, )?; } } @@ -2344,13 +2329,9 @@ fn handle_copy_mode( dest, options, context, - source_is_symlink, - source_is_fifo, - source_is_socket, + source_metadata, symlinked_files, created_parent_dirs, - #[cfg(unix)] - source_is_stream, )?; } } @@ -2582,15 +2563,6 @@ fn copy_file( context, ); - #[cfg(unix)] - let source_is_fifo = source_metadata.file_type().is_fifo(); - #[cfg(unix)] - let source_is_socket = source_metadata.file_type().is_socket(); - #[cfg(not(unix))] - let source_is_fifo = false; - #[cfg(not(unix))] - let source_is_socket = false; - let source_is_stream = is_stream(&source_metadata); let performed_action = handle_copy_mode( @@ -2601,11 +2573,7 @@ fn copy_file( &source_metadata, symlinked_files, source_in_command_line, - source_is_fifo, - source_is_socket, created_parent_dirs, - #[cfg(unix)] - source_is_stream, )?; if options.verbose && performed_action != PerformedAction::Skipped { @@ -2741,18 +2709,14 @@ fn handle_no_preserve_mode(options: &Options, org_mode: u32) -> u32 { /// Copy the file from `source` to `dest` either using the normal `fs::copy` or a /// copy-on-write scheme if --reflink is specified and the filesystem supports it. -#[allow(clippy::too_many_arguments)] fn copy_helper( source: &Path, dest: &Path, options: &Options, context: &str, - source_is_symlink: bool, - source_is_fifo: bool, - source_is_socket: bool, + source_metadata: &Metadata, symlinked_files: &mut HashSet<FileInformation>, created_parent_dirs: &mut HashSet<PathBuf>, - #[cfg(unix)] source_is_stream: bool, ) -> CopyResult<()> { if options.parents { let parent = dest.parent().unwrap_or(dest); @@ -2765,13 +2729,21 @@ fn copy_helper( return Err(CpError::NotADirectory(dest.to_path_buf())); } - if source_is_socket && options.recursive && !options.copy_contents { - #[cfg(unix)] - copy_socket(dest, options.overwrite, options.debug)?; - } else if source_is_fifo && options.recursive && !options.copy_contents { - #[cfg(unix)] - copy_fifo(dest, options.overwrite, options.debug)?; - } else if source_is_symlink { + #[cfg(unix)] + if options.recursive && !options.copy_contents { + let ft = source_metadata.file_type(); + if ft.is_socket() { + return copy_socket(dest, options.overwrite, options.debug); + } + if ft.is_fifo() { + return copy_fifo(dest, options.overwrite, options.debug); + } + if ft.is_char_device() || ft.is_block_device() { + return copy_node(dest, source_metadata, options.overwrite, options.debug); + } + } + + if source_metadata.is_symlink() { copy_link(source, dest, symlinked_files, options)?; } else { let copy_debug = copy_on_write( @@ -2781,7 +2753,7 @@ fn copy_helper( options.sparse_mode, context, #[cfg(unix)] - source_is_stream, + is_stream(source_metadata), )?; if !options.attributes_only && options.debug { @@ -2816,6 +2788,27 @@ fn copy_socket(dest: &Path, overwrite: OverwriteMode, debug: bool) -> CopyResult Ok(()) } +#[cfg(unix)] +fn copy_node( + dest: &Path, + source_metadata: &Metadata, + overwrite: OverwriteMode, + debug: bool, +) -> CopyResult<()> { + if dest.exists() { + overwrite.verify(dest, debug)?; + fs::remove_file(dest)?; + } + let sflag = if source_metadata.file_type().is_char_device() { + SFlag::S_IFCHR + } else { + SFlag::S_IFBLK + }; + let mode = Mode::from_bits_truncate(source_metadata.mode() as mode_t); + nix_mknod(dest, sflag, mode, source_metadata.rdev() as dev_t) + .map_err(|e| translate!("cp-error-cannot-create-special-file", "path" => dest.quote(), "error" => e.desc()).into()) +} + fn copy_link( source: &Path, dest: &Path,
tests/by-util/test_cp.rs+129 −0 modified@@ -5,6 +5,8 @@ // spell-checker:ignore (flags) reflink (fs) tmpfs (linux) rlimit Rlim NOFILE clob btrfs neve ROOTDIR USERDIR outfile uufs xattrs // spell-checker:ignore bdfl hlsl IRWXO IRWXG nconfined matchpathcon libselinux-devel prwx doesnotexist reftests subdirs mksocket srwx +#[cfg(unix)] +use rstest::rstest; use uucore::display::Quotable; #[cfg(feature = "feat_selinux")] use uucore::selinux::get_getfattr_output; @@ -3171,6 +3173,133 @@ fn test_cp_fifo() { assert_eq!(permission, "prwx-wx--x".to_string()); } +#[rstest] +#[case::recursive("-R")] +#[case::archive("-a")] +#[cfg(unix)] +fn test_cp_recursive_char_device(#[case] flag: &str) { + use nix::sys::stat::{Mode, SFlag, mknod as nix_mknod}; + use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt}; + use uutests::util::run_ucmd_as_root; + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + let mode = Mode::S_IRUSR | Mode::S_IWUSR | Mode::S_IRGRP; + if nix_mknod( + at.plus("null").as_path(), + SFlag::S_IFCHR, + mode, + uucore::fs::makedev(1, 3), + ) + .is_err() + { + print!("Test skipped; creating a char device node requires CAP_MKNOD"); + return; + } + if let Ok(result) = run_ucmd_as_root(&ts, &[flag, "null", "null2"]) { + result.success(); + let null2_metadata = at.plus("null2").metadata().unwrap(); + assert!(null2_metadata.file_type().is_char_device()); + assert_eq!( + null2_metadata.rdev(), + at.plus("null").metadata().unwrap().rdev() + ); + assert_eq!( + null2_metadata.permissions().mode() & 0o777, + mode.bits() as _ + ); + } else { + print!("Test skipped; copying a char device node requires CAP_MKNOD"); + } +} + +#[rstest] +#[case::recursive("-R")] +#[case::archive("-a")] +#[cfg(unix)] +fn test_cp_recursive_char_device_no_permission(#[case] flag: &str) { + new_ucmd!() + .args(&[flag, "/dev/null", "null2"]) + .fails() + .stderr_is("cp: cannot create special file 'null2': Operation not permitted\n"); +} + +#[test] +#[cfg(unix)] +fn test_cp_recursive_char_device_copy_contents() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["-R", "--copy-contents", "/dev/null", "null2"]) + .succeeds() + .no_stderr(); + assert!(at.plus("null2").metadata().unwrap().file_type().is_file()); + assert_eq!(at.read("null2"), ""); +} + +#[test] +#[cfg(unix)] +fn test_cp_char_device() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["/dev/null", "null2"]).succeeds().no_stderr(); + assert!(at.plus("null2").metadata().unwrap().file_type().is_file()); + assert_eq!(at.read("null2"), ""); +} + +#[rstest] +#[case::recursive("-R")] +#[case::archive("-a")] +#[cfg(unix)] +fn test_cp_recursive_block_device(#[case] flag: &str) { + use nix::sys::stat::{Mode, SFlag, mknod as nix_mknod}; + use std::os::unix::fs::{FileTypeExt, MetadataExt}; + use uutests::util::run_ucmd_as_root; + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + if nix_mknod( + at.plus("sda").as_path(), + SFlag::S_IFBLK, + Mode::S_IRUSR | Mode::S_IWUSR, + uucore::fs::makedev(8, 0), + ) + .is_err() + { + print!("Test skipped; creating a block device node requires CAP_MKNOD"); + return; + } + if let Ok(result) = run_ucmd_as_root(&ts, &[flag, "sda", "sda2"]) { + result.success(); + let sda2_metadata = at.plus("sda2").metadata().unwrap(); + assert!(sda2_metadata.file_type().is_block_device()); + assert_eq!( + sda2_metadata.rdev(), + at.plus("sda").metadata().unwrap().rdev() + ); + } else { + print!("Test skipped; copying a block device node requires CAP_MKNOD"); + } +} + +#[test] +#[cfg(unix)] +fn test_cp_block_device_no_permission() { + use nix::sys::stat::{Mode, SFlag, mknod as nix_mknod}; + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + if nix_mknod( + at.plus("sda").as_path(), + SFlag::S_IFBLK, + Mode::S_IRUSR | Mode::S_IWUSR, + uucore::fs::makedev(8, 0), + ) + .is_err() + { + print!("Test skipped; creating a block device node requires CAP_MKNOD"); + return; + } + ts.ucmd() + .args(&["-R", "sda", "sda2"]) + .fails() + .stderr_is("cp: cannot create special file 'sda2': Operation not permitted\n"); +} + #[test] #[cfg(unix)] fn test_cp_socket() {
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/uutils/coreutils/pull/11163nvdIssue TrackingPatchWEB
- github.com/uutils/coreutils/issues/9746nvdExploitIssue TrackingWEB
- github.com/advisories/GHSA-67hp-f6hq-2h6gghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-35358ghsaADVISORY
- github.com/uutils/coreutils/commit/e6a3bb596f149628ba973eec3d099f3bb69f2464ghsaWEB
- github.com/uutils/coreutils/releases/tag/0.7.0nvdRelease NotesWEB
News mentions
0No linked articles in our index yet.