Skip to content

Commit be0abaf

Browse files
committed
fix: Don't attempt +x if it was replaced by a non-regular file
This does not make a difference in typical cases, and anytime it matters something has probably gone unexpectedly, but this narrows the window of time during which a race condition could occur where a regular file has been replaced with something else at the same path (e.g. a directory) by some other process. An example of why it's valuable to avoid this is that if the entry is a directory then executable permissions have a different meaning and adding them could increase access unintentionally. Likewise, when we set executable permissions we remove setuid, setgid, and sticky bits, which also have different meanings for directories; in particular, on a directory the sticky bit restricts deletion. (This also automatically avoids some problems in the case of `finalize_entry` being called with a path to set executable that was never a regular file in the first place. That should not happen, though, and allowing that is not a goal of this change.)
1 parent bf33d2b commit be0abaf

File tree

1 file changed

+85
-57
lines changed

1 file changed

+85
-57
lines changed

gix-worktree-state/src/checkout/entry.rs

Lines changed: 85 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::{
22
borrow::Cow,
3-
fs::OpenOptions,
3+
fs::{OpenOptions, Permissions},
44
io::Write,
55
path::{Path, PathBuf},
66
};
@@ -286,9 +286,10 @@ pub(crate) fn finalize_entry(
286286
// For possibly existing, overwritten files, we must change the file mode explicitly.
287287
#[cfg(unix)]
288288
if let Some(path) = set_executable_after_creation {
289-
let mut perm = std::fs::symlink_metadata(path)?.permissions();
290-
set_mode_executable(&mut perm);
291-
std::fs::set_permissions(path, perm)?;
289+
let old_perm = std::fs::symlink_metadata(path)?.permissions();
290+
if let Some(new_perm) = set_mode_executable(old_perm) {
291+
std::fs::set_permissions(path, new_perm)?;
292+
}
292293
}
293294
// NOTE: we don't call `file.sync_all()` here knowing that some filesystems don't handle this well.
294295
// revisit this once there is a bug to fix.
@@ -298,73 +299,100 @@ pub(crate) fn finalize_entry(
298299
}
299300

300301
#[cfg(unix)]
301-
fn set_mode_executable(perm: &mut std::fs::Permissions) {
302+
fn set_mode_executable(mut perm: Permissions) -> Option<Permissions> {
302303
use std::os::unix::fs::PermissionsExt;
303304
let mut mode = perm.mode();
305+
if mode & 0o170000 != 0o100000 {
306+
return None; // Stop if we don't have a regular file anymore.
307+
}
304308
mode &= 0o777; // Clear non-rwx bits (setuid, setgid, sticky).
305309
mode |= (mode & 0o444) >> 2; // Let readers also execute.
306310
perm.set_mode(mode);
311+
Some(perm)
307312
}
308313

309-
#[cfg(test)]
314+
#[cfg(all(test, unix))]
310315
mod tests {
316+
fn pretty(maybe_mode: Option<u32>) -> String {
317+
match maybe_mode {
318+
Some(mode) => format!("Some({mode:04o})"),
319+
None => "None".into(),
320+
}
321+
}
322+
311323
#[test]
312-
#[cfg(unix)]
313324
fn set_mode_executable() {
314325
let cases = [
315-
// Common cases.
316-
(0o100755, 0o755),
317-
(0o100644, 0o755),
318-
(0o100750, 0o750),
319-
(0o100640, 0o750),
320-
(0o100700, 0o700),
321-
(0o100600, 0o700),
322-
(0o100775, 0o775),
323-
(0o100664, 0o775),
324-
(0o100770, 0o770),
325-
(0o100660, 0o770),
326-
(0o100764, 0o775),
327-
(0o100760, 0o770),
328-
// Some less common cases.
329-
(0o100674, 0o775),
330-
(0o100670, 0o770),
331-
(0o100000, 0o000),
332-
(0o100400, 0o500),
333-
(0o100440, 0o550),
334-
(0o100444, 0o555),
335-
(0o100462, 0o572),
336-
(0o100242, 0o252),
337-
(0o100167, 0o177),
338-
// Some cases with set-user-ID, set-group-ID, and sticky bits.
339-
(0o104755, 0o755),
340-
(0o104644, 0o755),
341-
(0o102755, 0o755),
342-
(0o102644, 0o755),
343-
(0o101755, 0o755),
344-
(0o101644, 0o755),
345-
(0o106755, 0o755),
346-
(0o106644, 0o755),
347-
(0o104750, 0o750),
348-
(0o104640, 0o750),
349-
(0o102750, 0o750),
350-
(0o102640, 0o750),
351-
(0o101750, 0o750),
352-
(0o101640, 0o750),
353-
(0o106750, 0o750),
354-
(0o106640, 0o750),
355-
(0o107644, 0o755),
356-
(0o107000, 0o000),
357-
(0o106400, 0o500),
358-
(0o102462, 0o572),
326+
// Common cases:
327+
(0o100755, Some(0o755)),
328+
(0o100644, Some(0o755)),
329+
(0o100750, Some(0o750)),
330+
(0o100640, Some(0o750)),
331+
(0o100700, Some(0o700)),
332+
(0o100600, Some(0o700)),
333+
(0o100775, Some(0o775)),
334+
(0o100664, Some(0o775)),
335+
(0o100770, Some(0o770)),
336+
(0o100660, Some(0o770)),
337+
(0o100764, Some(0o775)),
338+
(0o100760, Some(0o770)),
339+
// Less common:
340+
(0o100674, Some(0o775)),
341+
(0o100670, Some(0o770)),
342+
(0o100000, Some(0o000)),
343+
(0o100400, Some(0o500)),
344+
(0o100440, Some(0o550)),
345+
(0o100444, Some(0o555)),
346+
(0o100462, Some(0o572)),
347+
(0o100242, Some(0o252)),
348+
(0o100167, Some(0o177)),
349+
// With set-user-ID, set-group-ID, and sticky bits:
350+
(0o104755, Some(0o755)),
351+
(0o104644, Some(0o755)),
352+
(0o102755, Some(0o755)),
353+
(0o102644, Some(0o755)),
354+
(0o101755, Some(0o755)),
355+
(0o101644, Some(0o755)),
356+
(0o106755, Some(0o755)),
357+
(0o106644, Some(0o755)),
358+
(0o104750, Some(0o750)),
359+
(0o104640, Some(0o750)),
360+
(0o102750, Some(0o750)),
361+
(0o102640, Some(0o750)),
362+
(0o101750, Some(0o750)),
363+
(0o101640, Some(0o750)),
364+
(0o106750, Some(0o750)),
365+
(0o106640, Some(0o750)),
366+
(0o107644, Some(0o755)),
367+
(0o107000, Some(0o000)),
368+
(0o106400, Some(0o500)),
369+
(0o102462, Some(0o572)),
370+
// Where it was replaced with a directory due to a race:
371+
(0o040755, None),
372+
(0o040644, None),
373+
(0o040600, None),
374+
(0o041755, None),
375+
(0o041644, None),
376+
(0o046644, None),
377+
// Where it was replaced with a symlink due to a race:
378+
(0o120777, None),
379+
(0o120644, None),
380+
// Where it was replaced with some other non-regular file due to a race:
381+
(0o140644, None),
382+
(0o060644, None),
383+
(0o020644, None),
384+
(0o010644, None),
359385
];
360-
for (old, expected) in cases {
386+
for (old_mode, expected) in cases {
361387
use std::os::unix::fs::PermissionsExt;
362-
let mut perm = std::fs::Permissions::from_mode(old);
363-
super::set_mode_executable(&mut perm);
364-
let actual = perm.mode();
388+
let old_perm = std::fs::Permissions::from_mode(old_mode);
389+
let actual = super::set_mode_executable(old_perm).map(|perm| perm.mode());
365390
assert_eq!(
366-
actual, expected,
367-
"{old:06o} should become {expected:04o} but became {actual:04o}"
391+
actual,
392+
expected,
393+
"{old_mode:06o} should become {}, became {}",
394+
pretty(expected),
395+
pretty(actual)
368396
);
369397
}
370398
}

0 commit comments

Comments
 (0)