|
4 | 4 | // file that was distributed with this source code. |
5 | 5 |
|
6 | 6 | use clap::{Arg, ArgAction, Command, value_parser}; |
7 | | -use nix::sys::stat::Mode; |
8 | | -use nix::unistd::mkfifo; |
| 7 | +use rustix::fs::Mode; |
| 8 | +use rustix::process::umask; |
| 9 | +#[cfg(any(feature = "selinux", feature = "smack"))] |
9 | 10 | use std::fs; |
10 | | -use std::os::unix::fs::PermissionsExt; |
11 | 11 | use uucore::display::Quotable; |
12 | 12 | use uucore::error::{UResult, USimpleError}; |
13 | 13 | use uucore::translate; |
@@ -48,47 +48,48 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { |
48 | 48 | }; |
49 | 49 |
|
50 | 50 | for f in fifos { |
51 | | - if mkfifo(f.as_str(), Mode::from_bits_truncate(0o666)).is_err() { |
| 51 | + // Clear umask around mkfifo so the kernel applies the exact |
| 52 | + // requested mode atomically. Skipping the path-based chmod |
| 53 | + // that used to follow this call closes the TOCTOU window an |
| 54 | + // attacker could use to swap the FIFO for a symlink between |
| 55 | + // mkfifo and chmod (issue #10020). |
| 56 | + let prev_umask = umask(Mode::empty()); |
| 57 | + let mkfifo_result = create_fifo(f.as_str(), mode); |
| 58 | + umask(prev_umask); |
| 59 | + |
| 60 | + if mkfifo_result.is_err() { |
52 | 61 | show!(USimpleError::new( |
53 | 62 | 1, |
54 | 63 | translate!("mkfifo-error-cannot-create-fifo", "path" => f.quote()), |
55 | 64 | )); |
56 | | - continue; |
57 | | - } |
58 | | - |
59 | | - // Explicitly set the permissions to ignore umask |
60 | | - if let Err(e) = fs::set_permissions(&f, fs::Permissions::from_mode(mode)) { |
61 | | - return Err(USimpleError::new( |
62 | | - 1, |
63 | | - translate!("mkfifo-error-cannot-set-permissions", "path" => f.quote(), "error" => e), |
64 | | - )); |
65 | | - } |
66 | | - |
67 | | - // Apply SELinux context if requested |
68 | | - #[cfg(all(feature = "selinux", any(target_os = "linux", target_os = "android")))] |
69 | | - { |
70 | | - // Extract the SELinux related flags and options |
71 | | - let set_security_context = matches.get_flag(options::SECURITY_CONTEXT); |
72 | | - let context = matches.get_one::<String>(options::CONTEXT); |
73 | | - |
74 | | - if set_security_context || context.is_some() { |
75 | | - use std::path::Path; |
76 | | - if let Err(e) = |
77 | | - uucore::selinux::set_selinux_security_context(Path::new(&f), context) |
78 | | - { |
79 | | - let _ = fs::remove_file(f); |
80 | | - return Err(USimpleError::new(1, e.to_string())); |
| 65 | + } else { |
| 66 | + // Apply SELinux context if requested |
| 67 | + #[cfg(all(feature = "selinux", any(target_os = "linux", target_os = "android")))] |
| 68 | + { |
| 69 | + let set_security_context = matches.get_flag(options::SECURITY_CONTEXT); |
| 70 | + let context = matches.get_one::<String>(options::CONTEXT); |
| 71 | + |
| 72 | + if set_security_context || context.is_some() { |
| 73 | + use std::path::Path; |
| 74 | + if let Err(e) = |
| 75 | + uucore::selinux::set_selinux_security_context(Path::new(&f), context) |
| 76 | + { |
| 77 | + let _ = fs::remove_file(f); |
| 78 | + return Err(USimpleError::new(1, e.to_string())); |
| 79 | + } |
81 | 80 | } |
82 | 81 | } |
83 | | - } |
84 | 82 |
|
85 | | - // Apply SMACK context if requested |
86 | | - #[cfg(all(feature = "smack", target_os = "linux"))] |
87 | | - { |
88 | | - let set_security_context = matches.get_flag(options::SECURITY_CONTEXT); |
89 | | - let context = matches.get_one::<String>(options::CONTEXT); |
90 | | - if set_security_context || context.is_some() { |
91 | | - uucore::smack::set_smack_label_and_cleanup(&f, context, |p| fs::remove_file(p))?; |
| 83 | + // Apply SMACK context if requested |
| 84 | + #[cfg(all(feature = "smack", target_os = "linux"))] |
| 85 | + { |
| 86 | + let set_security_context = matches.get_flag(options::SECURITY_CONTEXT); |
| 87 | + let context = matches.get_one::<String>(options::CONTEXT); |
| 88 | + if set_security_context || context.is_some() { |
| 89 | + uucore::smack::set_smack_label_and_cleanup(&f, context, |p| { |
| 90 | + fs::remove_file(p) |
| 91 | + })?; |
| 92 | + } |
92 | 93 | } |
93 | 94 | } |
94 | 95 | } |
@@ -133,6 +134,25 @@ pub fn uu_app() -> Command { |
133 | 134 | ) |
134 | 135 | } |
135 | 136 |
|
| 137 | +// `rustix::fs::mkfifoat` is unavailable on Apple targets, so fall back to |
| 138 | +// libc's path-based `mkfifo` there. Both rely on the caller having cleared |
| 139 | +// the umask so the requested mode is applied atomically (see issue #10020). |
| 140 | +#[cfg(not(target_vendor = "apple"))] |
| 141 | +fn create_fifo(path: &str, mode: u32) -> Result<(), ()> { |
| 142 | + use rustix::fs::{CWD, mkfifoat}; |
| 143 | + mkfifoat(CWD, path, Mode::from_bits_truncate(mode)).map_err(|_| ()) |
| 144 | +} |
| 145 | + |
| 146 | +#[cfg(target_vendor = "apple")] |
| 147 | +fn create_fifo(path: &str, mode: u32) -> Result<(), ()> { |
| 148 | + use std::ffi::CString; |
| 149 | + let c_path = CString::new(path).map_err(|_| ())?; |
| 150 | + // SAFETY: `c_path` is a valid NUL-terminated C string and `mode` is a |
| 151 | + // standard mode_t bit pattern. |
| 152 | + let rc = unsafe { libc::mkfifo(c_path.as_ptr(), mode as libc::mode_t) }; |
| 153 | + if rc == 0 { Ok(()) } else { Err(()) } |
| 154 | +} |
| 155 | + |
136 | 156 | fn calculate_mode(mode_option: Option<&String>) -> Result<u32, String> { |
137 | 157 | let umask = uucore::mode::get_umask(); |
138 | 158 | let mode = 0o666; // Default mode for FIFOs |
|
0 commit comments