Replace Bash fix-permissions script with Go #1219
Merged
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
But perhaps most importantly: this change prevents shenanigans between validating the path and chown-ing files.
With the previous Bash script, a determined attacker could occasionally manage to replace parts of the path in between validating (that the args and the path are fine), and running chown(1) at the end.
Various chown flags won't help:
-h
changes what chown does to symlinks when it finds them, and-P
prevents recursion traversing them, but you can always pass it a path where intermediate segments are symlinks, and it resolves those.The new binary is based on openat2(2). This lovely syscall can simultaneously ensure (with various flags) that there are no symlinks, magic links, cross-device accesses, etc in the path being opened, and ensure that the (relative) path is treated as a subpath of a given file descriptor. Basing the access on a file descriptor defeats shenanigans, because file descriptors refer to inodes, not paths. An attacker can replace what the path resolves to as much as they like, but can't change which inode the file descriptor refers to once it is opened.
Aside from openat2, fchownat(2) works in a similar way for changing ownership. (fchownat is wrapped in a method called "Lchown" because
AT_SYMLINK_NOFOLLOW
makes it operate like lchown.)These syscalls are wrapped into an
io/fs.FS
implementation I've calledfdfs
("file descriptor file system"),which is used to driveThat's very cute, but there's still a gap of time between walking a path (at that moment it has no intermediate symlinks) and passing it to fchownat (when it could have changed). Sofs.WalkDir
.fs.WalkDir
doesn't traverse symlinks when it finds them, with the exception of the root it starts walking from - but we eliminated that with openat2.fdfs
also implements a recursive lchown.The script-level logic (a.k.a. "business logic") is in the
fixer
subpackage, for testability.The main downside to openat2 is that it is Linux specific. There are probably ways to hold openat (original flavour) on other platforms to do similar things.