Proposal Details
On *BSD and Solaris systems, open(2)
and mkdir(2)
will not create a file with the sticky bit set. The FreeBSD sticky(8) manpage lists this as a bug. I don't know the historical reasons for this behavior.
8383 reported this as a bug in os.OpenFile
and os.Mkdir
, and subsequently CL 1673 changed OpenFile
to set the sticky bit after creation when the file mode includes ModeSticky
. (A later CL added the same behavior for Mkdir
.)
This operation is racy: When creating a file with ModeSticky
, we:
1. Check to see if the file exists.
2. Open the file (possibly creating it).
3. If the file did not exist, stat it to find its current mode.
4. If the file did not exist, chmod the file to add the sticky bit.
If the target file is created between steps 1 and 2, then we may chmod a file we did not create. I think this is of minimal concern.
If the target file, or some part of its path, is replaced with a symlink between steps 2 and 4, however, we will chmod the target of the link, which could be anything. This is difficult to exploit: It requires that a privileged process be creating a file with the sticky bit set in a location that is writable by an attacker. If an exploitable scenario exists, however, it should be fairly straightforward to convert to local privilege escalation.
We can avoid the race in OpenFile
by using File.Chmod
to chmod the actual file opened.
Avoiding the race in Mkdir
is harder, because mkdir(2)
doesn't return a handle to the new directory. We could work around it with a multi-phase dance of opening the parent directory, using mkdirat(2)
to create the new directory, and then using openat(2)
to open the newly-created directory. This still leaves a race condition--we might be opening a directory different than the one we just created--but at least we'll be certain that we're chmodding a directory in the exact same location as the one we created.
I propose that working around the OS behavior in this case is a mistake, and that rather than putting in complicated workarounds we should remove the behavior introduced by CL 1673, with a GODEBUG to reenable the original behavior, race conditions and all.
Comment From: gabyhelp
Related Issues and Documentation
- os: Mkdir and OpenFile permission inconsistency on BSD when using sticky bit #23120 (closed)
- os: OpenFile() and Mkdir() won't create a file/directory with the sticky bit on *BSD #8383 (closed)
(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)
Comment From: ianlancetaylor
Historically the reason that the *BSD open
and mkdir
ignore the sticky bit is simply that they take the mode argument and & 0o777
. The sticky bit is 0o1000
. POSIX leaves the issue unspecified (https://pubs.opengroup.org/onlinepubs/009695399/functions/open.html "When bits other than the file permission bits are set, the effect is unspecified.")
This of course draws attention to the setuid (0o4000
) and setgid (0o2000
) bits. Linux will create a file or directory with those bits. BSD will not. We go to extra effort to create a file with the sticky bit on BSD. We do not go to any effort to create a file with the setuid/setgid bits on *BSD. Why the inconsistency?
For that matter on all of these systems if a parent directory has the setgid bit set, then using the mkdir
system call to create a child directory will give the child directory the setgid bit regardless of the mode argument. There is no way to create a directory with the setgid bit off in that case (though of course one can chmod
it afterward).
So I agree with this proposal. In fact, since the os package is intended to be vaguely system independent, I suggest that we return an error on an attempt to create a file or directory with any of the 0o7000
bits set, on all systems. The GODEBUG can restore the current behavior.
The downside is that this means that callers who really want those bits set will have to work around the problem, and they are likely to work around it in the same slightly insecure way that the standard library currently does.
Comment From: aclements
I don't think we can start silently ignoring these flags on *BSD and Solaris (or anything else) for compatibility reasons.
Dropping support for these flags and returning an error seems like a good idea. However, I propose we return an error only if there are bits we can't handle on that OS. We've been passing the sticky (and setuid/setgid) bits through on Linux forever and it works, and it seems needless to make that stop working. But elsewhere, where we can't reliably support these flags, return an error. Code that does these sorts of things is often a bit OS-sensitive anyway. And while the os
package abstracts many differences between OSes, file flags are one place where it's most limited in its ability to do this.
Comment From: aclements
This proposal has been added to the active column of the proposals project and will now be reviewed at the weekly proposal review meetings. — aclements for the proposal review group