Skip to content

install: create destination file with safer modes before copy #6595

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 4, 2024

Conversation

nerdroychan
Copy link
Contributor

Fix #6435.

As is suggested by the original issue, the creation of the destination file is delegated to fs::copy which creates the file with default mode 0644. This makes the file readable by other users before the final chmod is done, which is potentially unsafe and inconsistent with GNU install.

Since the current implementation unconditionally removes the destination path before the copy, creating the file with a minimal u=rw permission would not hurt the copy. This patch implements this (for unix).

@sylvestre
Copy link
Contributor

Could you please add a test to make sure we don't regress? Thanks

@nerdroychan nerdroychan force-pushed the install_copy_permission branch 3 times, most recently from 11830cd to 2eb2c4c Compare July 27, 2024 23:13
@nerdroychan
Copy link
Contributor Author

Could you please add a test to make sure we don't regress? Thanks

There was indeed a regression: when the fs::copy call failed, the created destination file was not removed. I just pushed a new patch that fixes this.

I added a test to make sure that no file will be created when fs::copy fails (e.g., the source file cannot be read), and if it succeed, the destination file's mode is set correctly. This makes sure that it works as before no matter the copy succeeds or not.

I'm not sure how to use a test case to check whether the new file is created in mode 0600, since the creation is in the middle of an internal function, but the strace output confirms so If you have any suggestions please let me know. Thanks!

@nerdroychan nerdroychan force-pushed the install_copy_permission branch 3 times, most recently from c9ded01 to 5b9c0c6 Compare July 28, 2024 17:08
Copy link

GNU testsuite comparison:

Skip an intermittent issue tests/tail/inotify-dir-recreate (fails in this run but passes in the 'main' branch)

@nerdroychan nerdroychan force-pushed the install_copy_permission branch from dbe383a to 051539c Compare July 31, 2024 16:48
@sylvestre sylvestre force-pushed the install_copy_permission branch from 051539c to 43cca95 Compare September 5, 2024 16:41
Copy link

github-actions bot commented Sep 5, 2024

GNU testsuite comparison:

Skipping an intermittent issue tests/rm/rm1 (passes in this run but fails in the 'main' branch)

@sylvestre sylvestre force-pushed the install_copy_permission branch from 43cca95 to bdbef46 Compare September 13, 2024 06:49
Copy link

GNU testsuite comparison:

GNU test failed: tests/timeout/timeout. tests/timeout/timeout is passing on 'main'. Maybe you have to rebase?
Skipping an intermittent issue tests/tail/inotify-dir-recreate (passes in this run but fails in the 'main' branch)

@sylvestre sylvestre force-pushed the install_copy_permission branch from bdbef46 to 9fc58f9 Compare September 16, 2024 07:25
@sylvestre sylvestre force-pushed the install_copy_permission branch from 9fc58f9 to cbca15a Compare December 2, 2024 09:08
@sylvestre sylvestre force-pushed the install_copy_permission branch from cbca15a to a5867bd Compare December 4, 2024 09:02
@sylvestre sylvestre merged commit aa09a98 into uutils:main Dec 4, 2024
@sylvestre
Copy link
Contributor

thanks and sorry for the latency

@mjguzik
Copy link

mjguzik commented Dec 4, 2024

This did not fix the problem -- the magic fchmod to 644 is still there making the file temporarily accessible.

Moreover this introduces a regression where the target file gets unlinked even if the source does not exist. I verified install from gnu coreutils just exits in such a case without messing with the target.

below I'm annotating strace output from a setup as in the bug report, installing from /tmp/srcdir/foo to /tmp/dstdir/foo. there is tons of noise there which I elided for clarity.

unlink("/tmp/dstdir/foo")               = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/tmp/dstdir/foo", O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, 0600) = 3
close(3)                                = 0

This executes regardless if /tmp/srcdir/foo exists or can be copied, meaning by this point the content of the target is whacked. The new file does however have 600 perms. The file should remain unaltered if src can't be copied.

openat(AT_FDCWD, "/tmp/srcdir/foo", O_RDONLY|O_CLOEXEC) = 3
statx(3, "", AT_STATX_SYNC_AS_STAT|AT_EMPTY_PATH, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0644, stx_size=0, ...}) = 0
openat(AT_FDCWD, "/tmp/dstdir/foo", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0100644) = 4
statx(4, "", AT_STATX_SYNC_AS_STAT|AT_EMPTY_PATH, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFREG|0600, stx_size=0, ...}) = 0
fchmod(4, 0100644)                      = 0

Note the target is accessible through fd 4. Rust code just chmoded it to 644, making it again readable by everyone who can access the directory and defeating the original open.

copy_file_range(3, NULL, 4, NULL, 1073741824, 0) = 0
[..]
chmod("/tmp/dstdir/foo", 0700)          = 0

Only now the file goes back to the expected state.

All in all the fs::copy API is probably fine for some cases, but is a total non-starter for serious programs. I failed to find a variant which accepts file handles instead, Rust needs to grow one.

@sylvestre
Copy link
Contributor

ok, thanks for the very quick reaction. I will revert it then.

@mjguzik
Copy link

mjguzik commented Dec 4, 2024

Ye makes sense.

I'm going to prod the rust people about better fs::copy so that this can be properly fixed.

@nerdroychan
Copy link
Contributor Author

@mjguzik Thanks a lot for the feedback.

@mjguzik
Copy link

mjguzik commented Dec 6, 2024

I found that rust has a variant accepting file handles: https://doc.rust-lang.org/beta/std/io/fn.copy.html

this will do it

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: insecure mode handling
3 participants