Skip to content

Commit c8dd19a

Browse files
aristarhoskalChrisDryden
authored andcommitted
nohup: fix output permissions to 0600
1 parent 8e59663 commit c8dd19a

File tree

2 files changed

+104
-6
lines changed

2 files changed

+104
-6
lines changed

src/uu/nohup/src/nohup.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@ fn failure_code() -> i32 {
6363
}
6464
}
6565

66+
fn open_nohup_file(path: impl AsRef<Path>) -> std::io::Result<File> {
67+
OpenOptions::new()
68+
.create(true)
69+
.append(true)
70+
.mode(0o600)
71+
.open(path)
72+
}
73+
6674
#[uucore::main]
6775
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
6876
let matches = uucore::clap_localization::handle_clap_result_with_exit_code(
@@ -137,11 +145,7 @@ fn replace_fds() -> UResult<()> {
137145
fn find_stdout() -> UResult<File> {
138146
let internal_failure_code = failure_code();
139147

140-
match OpenOptions::new()
141-
.create(true)
142-
.append(true)
143-
.open(Path::new(NOHUP_OUT))
144-
{
148+
match open_nohup_file(NOHUP_OUT) {
145149
Ok(t) => {
146150
show_error!(
147151
"{}",
@@ -156,7 +160,7 @@ fn find_stdout() -> UResult<File> {
156160
let mut homeout = PathBuf::from(home);
157161
homeout.push(NOHUP_OUT);
158162
let homeout_str = homeout.to_str().unwrap();
159-
match OpenOptions::new().create(true).append(true).open(&homeout) {
163+
match open_nohup_file(&homeout) {
160164
Ok(t) => {
161165
show_error!(
162166
"{}",

tests/by-util/test_nohup.rs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,3 +247,97 @@ fn test_nohup_stderr_to_stdout() {
247247
assert!(content.contains("stdout message"));
248248
assert!(content.contains("stderr message"));
249249
}
250+
251+
// Test nohup.out has 0600 permissions
252+
#[test]
253+
#[cfg(any(
254+
target_os = "linux",
255+
target_os = "android",
256+
target_os = "freebsd",
257+
target_os = "openbsd",
258+
target_vendor = "apple"
259+
))]
260+
fn test_nohup_output_permissions() {
261+
use std::os::unix::fs::PermissionsExt;
262+
263+
let ts = TestScenario::new(util_name!());
264+
let at = &ts.fixtures;
265+
266+
ts.ucmd()
267+
.terminal_simulation(true)
268+
.args(&["echo", "perms"])
269+
.succeeds();
270+
271+
sleep(std::time::Duration::from_millis(10));
272+
273+
let metadata = std::fs::metadata(at.plus("nohup.out")).unwrap();
274+
let mode = metadata.permissions().mode();
275+
276+
assert_eq!(
277+
mode & 0o777,
278+
0o600,
279+
"nohup.out should have 0600 permissions"
280+
);
281+
}
282+
283+
// Test that the fallback nohup.out (in $HOME) also has 0600 permissions
284+
#[test]
285+
#[cfg(any(
286+
target_os = "linux",
287+
target_os = "android",
288+
target_os = "freebsd",
289+
target_os = "openbsd"
290+
))]
291+
fn test_nohup_fallback_output_permissions() {
292+
use std::fs;
293+
use std::os::unix::fs::PermissionsExt;
294+
295+
// Skip if root
296+
if unsafe { libc::geteuid() } == 0 {
297+
println!("Skipping test when running as root");
298+
return;
299+
}
300+
301+
let ts = TestScenario::new(util_name!());
302+
let at = &ts.fixtures;
303+
304+
// Create a fake HOME directory
305+
at.mkdir("home");
306+
let home_dir_str = at.plus_as_string("home");
307+
308+
// Create a read-only directory
309+
at.mkdir("readonly_dir");
310+
let readonly_path = at.plus("readonly_dir");
311+
312+
// Make directory read-only
313+
let mut perms = fs::metadata(&readonly_path).unwrap().permissions();
314+
perms.set_mode(0o555);
315+
fs::set_permissions(&readonly_path, perms).unwrap();
316+
317+
// Run nohup inside the read-only dir
318+
// This forces it to fail writing to CWD and fall back to custom HOME
319+
ts.ucmd()
320+
.env("HOME", &home_dir_str)
321+
.current_dir(&readonly_path)
322+
.terminal_simulation(true)
323+
.arg("true")
324+
.run();
325+
326+
// Restore permissions so the test runner can delete the folder later!
327+
let mut perms = fs::metadata(&readonly_path).unwrap().permissions();
328+
perms.set_mode(0o755);
329+
fs::set_permissions(&readonly_path, perms).unwrap();
330+
331+
sleep(std::time::Duration::from_millis(50));
332+
333+
// Verify the file exists in HOME and has 0600 permissions
334+
let home_nohup = at.plus("home/nohup.out");
335+
let metadata = fs::metadata(home_nohup).expect("nohup.out should have been created in HOME");
336+
let mode = metadata.permissions().mode();
337+
338+
assert_eq!(
339+
mode & 0o777,
340+
0o600,
341+
"Fallback nohup.out should have 0600 permissions"
342+
);
343+
}

0 commit comments

Comments
 (0)