Skip to content
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

std::fs::copy sets permissions too late #26933

Closed
ben0x539 opened this issue Jul 10, 2015 · 9 comments
Closed

std::fs::copy sets permissions too late #26933

ben0x539 opened this issue Jul 10, 2015 · 9 comments
Labels
C-feature-accepted Category: A feature request that has been accepted pending implementation. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.

Comments

@ben0x539
Copy link
Contributor

std::fs::copy makes a new file, reads permissions of the old file, copies contents of the old file to the new file, and then sets permissions of the new file to those it just read. If used to copy a file that is only readable to the current user into a public directory, there's an opportunity for another user to get ahold of the just-created file before permissions are set and read all the secret data. I think it needs to create the file with the right permissions to begin with.

@Thiez
Copy link
Contributor

Thiez commented Jul 10, 2015

How would this work when copying a file that has no write permissions for anyone? :)

@Diggsey
Copy link
Contributor

Diggsey commented Jul 10, 2015

It will also fail to preserve any additional metadata or alternate data streams attached to the file. The only reliable way I could find of doing this on *nix was to invoke cp which is rather unfortunate...

@Stebalien
Copy link
Contributor

@Diggsey cp also copies permissions after copying the data (so does python incidentally).

However, cp will try to create the target file with the source's mode if it doesn't exist (good). Unfortunately, even if you copy the permissions first, anyone who has the file open will still be able to read it (at least on linux) and, as ACLs can actually remove access to a file, creating the destination file with the source file's mode isn't actually secure. The secure way to do this is to (on linux):

  1. Create a temporary file with mode 0. (or 0600 but 0 is simpler)
  2. Copy the ACLs. Fail if this doesn't work.
  3. Copy the mode. Fail if this doesn't work.
  4. rename the temporary file over the target file.
  5. Copy the data.

Unfortunately,

  1. The whole "create a temporary file and rename" part differs significantly from what cp does and won't work if the user doesn't have write permissions on the target directory (while overwriting the target file might).
  2. Copying a file from a filesystem that supports ACLs to a file system that doesn't may fail unexpectedly.

cp specification if anyone is interested: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/cp.html

@nagisa
Copy link
Member

nagisa commented Jul 10, 2015

How would this work when copying a file that has no write permissions for anyone? :)

Open a file for writing and change permissions before writing anything. While there’s still a window for a malicious party to open the file, it is much smaller.

@Stebalien
Copy link
Contributor

Open a file for writing and change permissions before writing anything.

  1. If the file already exists you should just fail and return an error instead of trying to overwrite a read-only file.
  2. (On linux, I don't know about windows) If the file doesn't exist, it doesn't matter what mode you create the file with, you will always be able to write to it (as long as you open it for writing):
use std::fs::File;
use std::fs::OpenOptions;
use std::os::unix::fs::OpenOptionsExt;
use std::io::Write;

fn main() {
    let mut f = OpenOptions::new().write(true).create(true).truncate(true).mode(0).open("/tmp/my_f").unwrap();
    f.write(b"test str");
    f.flush();
}

While there’s still a window for a malicious party to open the file, it is much smaller.

<ta mode>
Forget this entire concept; this is not how computer security works. A small window just means that the attacker might have to try a couple of times or, even better, spin up a hundred threads trying to open the file in while loops. You don't minimize races, you eliminate them. I can't express how important this is not only in computer system security but computer systems in general. If something can go wrong, it probably will; if it can go wrong often enough, it definitely will.
</ta mode>

Regardless, in this case this discussion is moot assuming you only make the file writable to the current user because the attacker would have to run as the current user to exploit this (at which point they could do just about anything...).

@Diggsey
Copy link
Contributor

Diggsey commented Jul 11, 2015

This isn't an issue on windows because it provides CopyFile, CopyFileEx which do the job properly, including preserving metadata and alternate data streams.

@retep998
Copy link
Member

retep998 commented Jul 11, 2015

@Diggsey We actually do use CopyFileEx on Windows as of #26751, so this is purely a non-Windows issue now.

@steveklabnik
Copy link
Member

/cc @rust-lang/libs

@steveklabnik steveklabnik added T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. and removed A-libs labels Mar 24, 2017
@Mark-Simulacrum Mark-Simulacrum added the C-feature-request Category: A feature request, i.e: not implemented / a PR. label Jul 22, 2017
@dtolnay dtolnay added C-feature-accepted Category: A feature request that has been accepted pending implementation. and removed C-feature-request Category: A feature request, i.e: not implemented / a PR. labels Nov 17, 2017
@dtolnay
Copy link
Member

dtolnay commented Nov 17, 2017

I agree that the current handling of permissions in std::fs::copy on non-WIndows seems incorrect. I would be prepared to consider a PR that fixes this.

haraldh added a commit to haraldh/rust-1 that referenced this issue Feb 22, 2019
A convenience method like fs::copy() should try to prevent pitfalls a
normal user doesn't think about.

In case of an empty umask, setting the file mode early prevents
temporarily world readable or even writeable files,
because the default mode is 0o666.

In case the target is a named pipe or special device node, setting the
file mode can lead to unwanted side effects, like setting permissons on
`/dev/stdout` or for root setting permissions on `/dev/null`.

Not handling sparse files could fill up the users disk very quickly.

Fixes:
rust-lang#26933
rust-lang#37885
rust-lang#58635
haraldh added a commit to haraldh/rust-1 that referenced this issue Mar 1, 2019
A convenience method like fs::copy() should try to prevent pitfalls a
normal user doesn't think about.

In case of an empty umask, setting the file mode early prevents
temporarily world readable or even writeable files,
because the default mode is 0o666.

In case the target is a named pipe or special device node, setting the
file mode can lead to unwanted side effects, like setting permissons on
`/dev/stdout` or for root setting permissions on `/dev/null`.

copy_file_range() returns EINVAL, if the destination is a FIFO/pipe or
a device like "/dev/null", so fallback to io::copy, too.

Fixes: rust-lang#26933
Fixed: rust-lang#37885
haraldh added a commit to haraldh/rust-1 that referenced this issue Mar 5, 2019
A convenience method like fs::copy() should try to prevent pitfalls a
normal user doesn't think about.

In case of an empty umask, setting the file mode early prevents
temporarily world readable or even writeable files,
because the default mode is 0o666.

In case the target is a named pipe or special device node, setting the
file mode can lead to unwanted side effects, like setting permissons on
`/dev/stdout` or for root setting permissions on `/dev/null`.

copy_file_range() returns EINVAL, if the destination is a FIFO/pipe or
a device like "/dev/null", so fallback to io::copy, too.

Fixes: rust-lang#26933
Fixed: rust-lang#37885
Centril added a commit to Centril/rust that referenced this issue Mar 10, 2019
fs::copy() linux: set file mode early

A convenience method like fs::copy() should try to prevent pitfalls a
normal user doesn't think about.

In case of an empty umask, setting the file mode early prevents
temporarily world readable or even writeable files,
because the default mode is 0o666.

In case the target is a named pipe or special device node, setting the
file mode can lead to unwanted side effects, like setting permissons on
`/dev/stdout` or for root setting permissions on `/dev/null`.

copy_file_range() returns EINVAL, if the destination is a FIFO/pipe or
a device like "/dev/null", so fallback to io::copy, too.

Fixes: rust-lang#26933
Fixed: rust-lang#37885
Centril added a commit to Centril/rust that referenced this issue Mar 10, 2019
fs::copy() linux: set file mode early

A convenience method like fs::copy() should try to prevent pitfalls a
normal user doesn't think about.

In case of an empty umask, setting the file mode early prevents
temporarily world readable or even writeable files,
because the default mode is 0o666.

In case the target is a named pipe or special device node, setting the
file mode can lead to unwanted side effects, like setting permissons on
`/dev/stdout` or for root setting permissions on `/dev/null`.

copy_file_range() returns EINVAL, if the destination is a FIFO/pipe or
a device like "/dev/null", so fallback to io::copy, too.

Fixes: rust-lang#26933
Fixed: rust-lang#37885
Centril added a commit to Centril/rust that referenced this issue Mar 10, 2019
fs::copy() linux: set file mode early

A convenience method like fs::copy() should try to prevent pitfalls a
normal user doesn't think about.

In case of an empty umask, setting the file mode early prevents
temporarily world readable or even writeable files,
because the default mode is 0o666.

In case the target is a named pipe or special device node, setting the
file mode can lead to unwanted side effects, like setting permissons on
`/dev/stdout` or for root setting permissions on `/dev/null`.

copy_file_range() returns EINVAL, if the destination is a FIFO/pipe or
a device like "/dev/null", so fallback to io::copy, too.

Fixes: rust-lang#26933
Fixed: rust-lang#37885
Centril added a commit to Centril/rust that referenced this issue Mar 10, 2019
fs::copy() linux: set file mode early

A convenience method like fs::copy() should try to prevent pitfalls a
normal user doesn't think about.

In case of an empty umask, setting the file mode early prevents
temporarily world readable or even writeable files,
because the default mode is 0o666.

In case the target is a named pipe or special device node, setting the
file mode can lead to unwanted side effects, like setting permissons on
`/dev/stdout` or for root setting permissions on `/dev/null`.

copy_file_range() returns EINVAL, if the destination is a FIFO/pipe or
a device like "/dev/null", so fallback to io::copy, too.

Fixes: rust-lang#26933
Fixed: rust-lang#37885
kennytm added a commit to kennytm/rust that referenced this issue Mar 11, 2019
fs::copy() linux: set file mode early

A convenience method like fs::copy() should try to prevent pitfalls a
normal user doesn't think about.

In case of an empty umask, setting the file mode early prevents
temporarily world readable or even writeable files,
because the default mode is 0o666.

In case the target is a named pipe or special device node, setting the
file mode can lead to unwanted side effects, like setting permissons on
`/dev/stdout` or for root setting permissions on `/dev/null`.

copy_file_range() returns EINVAL, if the destination is a FIFO/pipe or
a device like "/dev/null", so fallback to io::copy, too.

Fixes: rust-lang#26933
Fixed: rust-lang#37885
pietroalbini added a commit to pietroalbini/rust that referenced this issue Mar 12, 2019
fs::copy() unix: set file mode early

A convenience method like fs::copy() should try to prevent pitfalls a
normal user doesn't think about.

In case of an empty umask, setting the file mode early prevents
temporarily world readable or even writeable files,
because the default mode is 0o666.

In case the target is a named pipe or special device node, setting the
file mode can lead to unwanted side effects, like setting permissons on
`/dev/stdout` or for root setting permissions on `/dev/null`.

copy_file_range() returns EINVAL, if the destination is a FIFO/pipe or
a device like "/dev/null", so fallback to io::copy, too.

Fixes: rust-lang#26933
Fixed: rust-lang#37885
kennytm added a commit to kennytm/rust that referenced this issue Mar 15, 2019
fs::copy() unix: set file mode early

A convenience method like fs::copy() should try to prevent pitfalls a
normal user doesn't think about.

In case of an empty umask, setting the file mode early prevents
temporarily world readable or even writeable files,
because the default mode is 0o666.

In case the target is a named pipe or special device node, setting the
file mode can lead to unwanted side effects, like setting permissons on
`/dev/stdout` or for root setting permissions on `/dev/null`.

copy_file_range() returns EINVAL, if the destination is a FIFO/pipe or
a device like "/dev/null", so fallback to io::copy, too.

Fixes: rust-lang#26933
Fixed: rust-lang#37885
haraldh added a commit to haraldh/rust-1 that referenced this issue Mar 23, 2019
A convenience method like fs::copy() should try to prevent pitfalls a
normal user doesn't think about.

In case of an empty umask, setting the file mode early prevents
temporarily world readable or even writeable files,
because the default mode is 0o666.

In case the target is a named pipe or special device node, setting the
file mode can lead to unwanted side effects, like setting permissons on
`/dev/stdout` or for root setting permissions on `/dev/null`.

copy_file_range() returns EINVAL, if the destination is a FIFO/pipe or
a device like "/dev/null", so fallback to io::copy, too.

Use `fcopyfile` on MacOS instead of `copyfile`.

Fixes: rust-lang#26933
Fixed: rust-lang#37885
Centril added a commit to Centril/rust that referenced this issue Mar 28, 2019
fs::copy() unix: set file mode early

A convenience method like fs::copy() should try to prevent pitfalls a
normal user doesn't think about.

In case of an empty umask, setting the file mode early prevents
temporarily world readable or even writeable files,
because the default mode is 0o666.

In case the target is a named pipe or special device node, setting the
file mode can lead to unwanted side effects, like setting permissons on
`/dev/stdout` or for root setting permissions on `/dev/null`.

copy_file_range() returns EINVAL, if the destination is a FIFO/pipe or
a device like "/dev/null", so fallback to io::copy, too.

Fixes: rust-lang#26933
Fixed: rust-lang#37885
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-feature-accepted Category: A feature request that has been accepted pending implementation. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

9 participants