1

I am writing a program to mimic the cp utility. However, I cannot get the file permissions to work correctly. I know that they are stored in the structure stat and stored in the st_mode field with stat.

My issue is that I do not get the write permission for the group or other categories, i.e. I get -rwxr-xr-x as the permissions for the file even though the source file is -rwxrwxrwx. The statement where I set the permissions is below.

if ( (dest_fd = open(dest_file, O_WRONLY|O_CREAT, (stats.st_mode & S_IRUSR)|(stats.st_mode & S_IWUSR)|(stats.st_mode & S_IXUSR)|(stats.st_mode & S_IRGRP)|(stats.st_mode & S_IWGRP)|(stats.st_mode & S_IXGRP)|(stats.st_mode & S_IROTH)|(stats.st_mode & S_IWOTH)| (stats.st_mode & S_IXOTH))) < 0)
    {
            printf("There was a problem opening the destination file.");
            exit(EXIT_FAILURE);
    }//ends the if statement opening the destination file.
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
tpar44
  • 1,431
  • 4
  • 22
  • 35
  • 1
    Is the `umask` screwing with you? Regardless of the permissions you set in the `open`, kernel will apply the `unmask` and (probably) remove some of the permissions. Try it after setting `umask` to 000. – cdarke Sep 12 '12 at 15:12
  • 1
    `"There was a problem opening the destination file."` is the canonical example of a useless error message. `man perror` – William Pursell Sep 12 '12 at 16:05

4 Answers4

4

The answers so far are right that the problem is umask, but rather than clearing the umask (this is dangerous if your program is multi-threaded or if you might be calling any library functions that create files) I would treat the umask as a user configuration variable you are not allowed to modify, and instead call fchmod on the files after creating them to give them the final permissions you want. This may be necessary anyway to give certain permissions like suid/sgid, which some kernels remove whenever the file is modified. I would also initially create the file with mode 0600, so that there's no race condition between opening it and changing permissions during which another user could get an open handle on the file.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • Note however that my solution does in theory have slightly worse performance since it adds an additional system call. I think the safety is worth it, at least in most cases. You could optimize it out in cases where it's not necessary by checking the `umask` in advance and saving it, and then only calling `fchmod` if the `umask` would affect the mode passed to `open`. – R.. GitHub STOP HELPING ICE Sep 12 '12 at 15:18
  • its my understanding, and I may be wrong because I'm just learning, that if you open a file with the "open" with O_CREAT, there is no race condition because its an atomic procedure. They took out the CREAT function to eliminate that race condition and added it to the open call. – tpar44 Sep 12 '12 at 15:19
  • oldmask = umask(newmask); open(..., ...); umask(oldmask); -- that should improve efficiency (especially if there are multiple files to create) (and I'm assuming the process is not running additional threads which might change the mask) – mah Sep 12 '12 at 15:19
  • @mah: My whole point was that doing that is unsafe. Also, two `umask` syscalls probably cost as much as one `fchmod` syscall... – R.. GitHub STOP HELPING ICE Sep 12 '12 at 15:20
  • @tpar44: The race condition I'm talking about is that if you create a file with mode `0644` then immediately `fchmod` to mode `0600` before you start writing it, there's still a window during which another user could obtain an open file descriptor for reading, and that file descriptor will continue to be usable even after the `fchmod`, allowing the attacker to read the contents of your file. – R.. GitHub STOP HELPING ICE Sep 12 '12 at 15:22
  • @R.. That definitely makes sense but if you're copying a file using `cp` (for instance) that has permissions `-r-xr-xr-x`, the original file was already readable by the world and thus the new file should be readable by the world as well. Is that what you're saying should be avoided or are you saying the file should only be readable after the contents is written to the file? I can see the latter being more useful – tpar44 Sep 12 '12 at 15:25
  • @R - if only copying a single file then yes, but copying only 2 files lets you break even and more than two puts you ahead -- since you would simply clear the mask before the first copy, and restore it after all copying is complete. This also eliminates the race condition you describe (and lets face it, even if creating only one file, the overhead of the extra system call is a small price to pay to avoid a security race condition.) – mah Sep 12 '12 at 15:25
  • @mah: And it makes your code non-thread-safe (`umask` is global state). – R.. GitHub STOP HELPING ICE Sep 12 '12 at 15:30
  • By the way, instead of using initial permissions of `0600` like I suggested, it would be just as safe to use the desired final permissions, and then only call `fchmod` if the effective umask would have masked off bits from the desired permissions. When I recommended `0600`, I was thinking of it in contrast to using some other constant argument to `open` (usually I use the constant `0666` and let the `umask` take effect in normal programs that don't want to do their own permissions handling, but that would be a bad idea here). – R.. GitHub STOP HELPING ICE Sep 12 '12 at 15:52
3

The cause of the problem is

The permissions of the created file are (mode & ~umask)

Typically, umask is 022, so that prohibits creating world-writable files.

Daniel Fischer
  • 181,706
  • 17
  • 308
  • 431
1

*nix masks out mode bits in files you create, but you can change the mask using the umask() function. man umask (perhaps man 2 umask) for details.

mah
  • 39,056
  • 9
  • 76
  • 93
1

You can use the chmod(2) syscall to change the permissions of an existing file or directory or fchmod(2) to set the permissions given an open file descriptor.

To be more secure and to prevent exploitation of possible race conditions, you can use a very restrictive set of permissions while creating the file and then use chmod(2) to restore the original permissions. This is what cp -a does (except that it creates the file with the default permissions):

$ strace cp -a file file1
...
open("file1", O_WRONLY|O_TRUNC)       = 4
...
fchmod(4, 0100640)                    = 0
...

chmod(2) and fchmod(2) are not affected by the value of the umask.

Hristo Iliev
  • 72,659
  • 12
  • 135
  • 186