Skip to content

fix(install): zero umask at startup so ancestor dirs are created at exact 0755 - #12715

Open
abendrothj wants to merge 4 commits into
uutils:mainfrom
abendrothj:fix/install-zero-umask-ancestor-dirs
Open

fix(install): zero umask at startup so ancestor dirs are created at exact 0755#12715
abendrothj wants to merge 4 commits into
uutils:mainfrom
abendrothj:fix/install-zero-umask-ancestor-dirs

Conversation

@abendrothj

@abendrothj abendrothj commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Fixes #12714
Related: #11363, #12713

Root cause

GNU install calls umask(0) early in main() so that ancestor directories created by -D/-d always get exactly DEFAULT_MODE (0755) regardless of the caller's umask. uutils passed DEFAULT_MODE to mkdirat/fs::create_dir_all without zeroing umask first, so restrictive umasks (e.g. 0027, 0077, 0111) produced wrong ancestor modes.

The existing tests masked this because they used a mkdir-based probe to capture the expected permissions. With umask 0002 the probe yields 0775 while install produces 0755 — causing a spurious test failure that looked like a bug in the tests, not the implementation.

Changes

src/uucore/src/lib/features/mode.rs

  • Add zero_umask() using the existing rustix dependency — sets process umask to 0 and returns the old value

src/uu/install/src/install.rs

  • Call uucore::mode::zero_umask() at the top of uumain (unix only), matching GNU's behaviour
  • Fix the -d code path (directory()) to use DirBuilder::mode(DEFAULT_MODE) instead of fs::create_dir_all, which defaults to mode 0777 and would create 0777 ancestors now that the umask is zeroed

tests/by-util/test_install.rs

  • Replace the broken probe-based assertions in test_install_ancestors_mode_directories and test_install_ancestors_mode_directories_with_file with assert_eq!(0o40_755_u32, ...), which is the value GNU actually guarantees

Test plan

  • cargo test --features unix test_install — all 92 install tests pass
  • Verify manually with a non-022 umask that ancestor dirs land at exactly 0755

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown

GNU testsuite comparison:

Skip an intermittent issue tests/date/date-locale-hour (fails in this run but passes in the 'main' branch)
Skip an intermittent issue tests/pr/bounded-memory (fails in this run but passes in the 'main' branch)
Note: The gnu test tests/printf/printf-surprise is now being skipped but was previously passing.
Congrats! The gnu test tests/rm/many-dir-entries-vs-OOM is now passing!

@codspeed-hq

codspeed-hq Bot commented Jun 12, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 353 untouched benchmarks
⏩ 50 skipped benchmarks1


Comparing abendrothj:fix/install-zero-umask-ancestor-dirs (b73847d) with main (f99a1fb)

Open in CodSpeed

Footnotes

  1. 50 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@abendrothj
abendrothj force-pushed the fix/install-zero-umask-ancestor-dirs branch from 9cfc786 to 11d3c90 Compare July 4, 2026 00:00
@abendrothj

abendrothj commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto current main, dropped an empty ci: retrigger CI commit and resolved a real merge conflict in directory(): main independently added the "existing path is not a directory" check since this branch was opened, so I kept that guard and moved this PR's DirBuilder-with-explicit-mode fix into its else (creation) branch, preserving both behaviors. All 96 install tests pass (up from 92 — main added 4 more since), fmt/clippy clean, and I manually re-verified the actual fix: under umask 0027, install -D now creates ancestor dirs at 0755 instead of 0750.

…xact 0755

GNU install calls umask(0) early in main() so that ancestor directories
created by -D/-d always get exactly DEFAULT_MODE (0755) regardless of the
caller's umask. Our implementation passed DEFAULT_MODE to mkdirat /
fs::create_dir_all without zeroing umask first, so restrictive umasks
(e.g. 0027, 0077, 0111) produced wrong ancestor modes.

Changes:
- Add zero_umask() to uucore::mode using the existing rustix dependency
- Call it at the top of install's uumain (unix only)
- Fix the -d code path to use DirBuilder::mode(DEFAULT_MODE) instead of
  fs::create_dir_all, which defaults to 0777 and would create 0777
  ancestors with umask now zeroed
- Replace the broken probe-based assertions in two tests with direct
  assert_eq!(0o40_755) checks, matching the GNU-guaranteed value

Fixes uutils#12714
Related: uutils#11363, uutils#12713
@abendrothj
abendrothj force-pushed the fix/install-zero-umask-ancestor-dirs branch from 11d3c90 to 81625e6 Compare August 17, 2026 03:46
Copilot AI lite review requested due to automatic review settings August 17, 2026 03:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aligns install with GNU behavior by zeroing the process umask early so that ancestor directories created via -D/-d are reliably created at DEFAULT_MODE (0755), independent of the caller’s umask.

Changes:

  • Add uucore::mode::zero_umask() (unix-only) to set umask to 0.
  • Call zero_umask() at the start of install::uumain and adjust -d directory creation to use DirBuilder::mode(DEFAULT_MODE) (instead of create_dir_all).
  • Update ancestor-mode tests to assert GNU’s guaranteed 0755 behavior for ancestor directories.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
tests/by-util/test_install.rs Updates ancestor permission assertions to match GNU’s fixed 0755 guarantee.
src/uucore/src/lib/features/safe_traversal.rs Updates docs to clarify umask interaction and the need to zero umask for exact modes.
src/uucore/src/lib/features/mode.rs Adds a unix-only zero_umask() helper based on rustix.
src/uu/install/src/install.rs Zeros umask early; switches -d directory creation to DirBuilder::mode(DEFAULT_MODE); updates comments for new semantics.
Suppressed comments (2)

tests/by-util/test_install.rs:108

  • These assertions now hard-code the expected ancestor mode, but the test no longer exercises the original bug (behavior under a restrictive inherited umask). On typical CI umask=0022, ancestors would still be 0755 even if install didn’t zero umask, so this can pass while regressing. Set a restrictive umask for the spawned install process (and keep the 0755 assertions) to ensure the test fails if zero-umask behavior is removed.

This issue also appears on line 143 of the same file.

    ucmd.args(&[mode_arg, directories_arg, target_dir])
        .succeeds()
        .no_stderr();

tests/by-util/test_install.rs:146

  • This test hard-codes 0755 for ancestor dirs, but it doesn’t currently validate the fix under a restrictive inherited umask. On common umask=0022, ancestors would still be 0755 even if install stopped zeroing umask, so this could pass while regressing. Force a restrictive umask for the spawned install process to ensure the test actually exercises the behavior change.
    // GNU install zeros umask at startup and creates ancestor dirs at exactly
    // 0755 (DEFAULT_MODE). --mode applies only to the final target.
    assert_eq!(0o40_755_u32, at.metadata(ancestor1).permissions().mode());
    assert_eq!(0o40_755_u32, at.metadata(ancestor2).permissions().mode());

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +195 to +199
// target files are created with exact modes rather than umask-modified ones.
// chmod is applied explicitly for the target, and ancestors always use
// DEFAULT_MODE (0755) without umask interference.
#[cfg(unix)]
uucore::mode::zero_umask();
Copilot AI review requested due to automatic review settings August 17, 2026 09:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/uucore/src/lib/features/safe_traversal.rs:1307

  • This test asserts the created file mode is exactly 0o600, but file creation modes are always masked by the process umask; a restrictive umask could legitimately clear bits and make this assertion fail even if open_file_at_with_mode honors the requested mode. To keep the test robust across environments, assert that no permission bits outside the requested mode are set (umask can only remove bits).
        assert_eq!(mode, 0o600);

src/uucore/src/lib/features/safe_traversal.rs:453

  • The new open_file_at_with_mode docs imply the provided mode is applied as-is, but the kernel still applies the process umask to O_CREAT modes. Calling this out helps prevent callers from assuming they can get an exact mode without zeroing/restoring umask.

This issue also appears on line 1307 of the same file.

    /// The mode is used only when creating a new file; an existing file keeps
    /// its current permissions. Callers that will apply final permissions
    /// later should use a restrictive initial mode so failures cannot leave a
    /// partially-created file overly permissive.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

install: ancestor directories respect process umask instead of using fixed 0755 (diverges from GNU)

2 participants