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
Comment From: neild
I propose we return an error only if there are bits we can't handle on that OS.
If we take that at face value, we'd need to return an error when creating a file with most mode bits on Windows, since we only handle (I think) the world-writable bit. So perhaps we should limit that to bits outside of 0o777 (rwxrwxrwx).
Comment From: aclements
So perhaps we should limit that to bits outside of 0o777 (rwxrwxrwx).
Sure, that's true. I suppose I would see those as "bits Go is aware of" even if there's no way to actually represent them to the OS.
Comment From: aclements
Just to note: The idea of returning an error for unrecognized bits also came up in #73676, this there is affects flags, while this proposal affects perm.
Comment From: aclements
Based on the discussion above, this proposal seems like a likely accept. — aclements for the proposal review group
TODO: Return an error on platforms where we can't atomically set the sticky bit. On platforms where we can, just pass it through.
Comment From: aclements
No change in consensus, so accepted. 🎉 This issue now tracks the work of implementing the proposal. — aclements for the proposal review group
The proposal is to drop Go's (racy) workaround for setting the sticky bit during file creation (OpenFile) and directory creation (Mkdir) on *BSD and Solaris systems. Instead, if sticky bits are set in perm and the OS does not natively support this, we will return an error. If the OS does natively support this, we will simply pass them to the OS. Setting GODEBUG=insecurestickybits=1 will restore the current behavior.