-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
tmpfiles: unsafe handling of hard links and a race condition #7736
Comments
FWIW, OpenRC's checkpath helper had to deal with this same problem, and the workaround can be seen at https://github.com/OpenRC/openrc/blob/master/src/rc/checkpath.c#L161 |
systemd has been enabling |
Hmm, unless I am mistaken that code is full of TOCTOU, no? The "workaround" you are referring to is the I figure we could add a similar check unless protected_hardlinks is set. I see no way how we could ever deal with with this, without protected_hardinks and allow hardlinked stuff, right? |
Ugh, yes, and it looks like even the symlink check (which is exploitable with
It's up to you, I wasn't aware that systemd set
You can still mount a filesystem with the outlawed hard links on it even with More to the point, I don't know how to fix it properly in the scenario where the user re-runs tmpfiles and a For the race condition, instead of modifying the parent and then its children, could it be done the other way around? That's how GNU chown avoids the race. I haven't tested this yet (Merry Christmas :) but I'm referring to, STRV_FOREACH(fn, g.gl_pathv) {
k = action(i, *fn);
if (k < 0 && r == 0)
r = k;
if (recursive) {
k = item_do_children(i, *fn, action);
if (k < 0 && r == 0)
r = k;
}
} and FOREACH_DIRENT_ALL(de, d, r = -errno) {
...
q = action(i, p);
if (q < 0 && q != -ENOENT && r == 0)
r = q;
if (IN_SET(de->d_type, DT_UNKNOWN, DT_DIR)) {
q = item_do_children(i, p, action);
if (q < 0 && r == 0)
r = q;
}
} If you called chown on |
After some more digging, it looks like the existing
If that means that |
So, regarding this race condition: If I understood you right you mean the race between us figuring out whether a file needs chown()ing and is safe to chown() (i.e. nlinks is 1 and so on) and us actually chowning it? if the user has write access to the dir he could replace the file in between? I don't think this is really an issue for us: we open the inode with O_PATH, then do a stat() check on the open fd, and chown() the thing via /proc/self/fd/$fd. This means we have the guarantee that our safety checks and our chowning operate on the same inode which cannot be replaced in between. Or am I misunderstanding you? |
That whole comment was referring to the
The race condition I was referring to is (2). IIRC it's when between, k = action(i, *fn); and if (recursive) {
k = item_do_children(i, *fn, action); I'm able to add a child that is a hard link to |
…s protected_hardlinks sysctl is on Let's add some extra safety. Fixes: systemd#7736
I prepped a fix for this now, see #7964. PTAL. With that fix the race you are referring to is gone too, no? With that fix, we refuse to chown()/chmod()/ACL anything that has a hardlink count > 1 unless the protect hardlink features is on. |
I don't understand the First, the situation that I was worried about. After the PR, we're doing... fd = open(path, O_NOFOLLOW|O_CLOEXEC|O_PATH);
...
if (fstatat(fd, "", &st, AT_EMPTY_PATH) < 0)
...
if (hardlink_vulnerable(&st)) and I was concerned that the owner of the directory might be able to delete the hard link after you've acquired the FD, but before you stat it. But then I see... xsprintf(fn, "/proc/self/fd/%i", fd);
...
chown(fn, ...); which looks very clever, because if I delete the hard link, then the symlink will still point to the old path. But let's see what happens. I introduced these two lines right before the stat, printf("opened fd %i for %s, sleeping...\n", fd, path);
sleep(10);
if (fstatat(fd, "", &st, AT_EMPTY_PATH) < 0) And then I ran tmpfiles on this:
After the first run, I hardlinked
which is what you'd expect so far. And then in another terminal, I erased
which looks very promising! How can you call chown on that (deleted) path? It should fail, right? Well, as far as I can tell, it doesn't, because when tmpfiles is finished, I own Maybe I screwed up the test, or maybe there's some kernel documentation that explains what happened under |
Oh, note that /proc/$pid/fd/$fd is magic. It's not really a symlink, it's some magic shit that is just exposed as symlink. It's like /proc/$pid/root, which also shows up as symlink but in reality is some magic shit that is just exposed as one. And because it is that, /proc/$pid/fd/$fd always points to the original inode, regardless if it got renamed or deleted or anything. Hence you can can "pin" an inode through O_PATH, and then execute operations on it this way, without fearing that it will be invalidated while doing so. |
Ok, that explains what I saw. This is still an improvement because it makes the attacker win a race rather than just create a hard link whenever he feels like it. And I still have no idea how to fix it in general, so there's that. |
FYI |
…s protected_hardlinks sysctl is on Let's add some extra safety. Fixes: systemd#7736 (cherry picked from commit 5579f85) [fbui: fixes bsc#1077925] [fbui: fixes CVE-2017-18078]
…s protected_hardlinks sysctl is on Let's add some extra safety. Fixes: systemd#7736 (cherry picked from commit 5579f85) [fbui: fixes bsc#1077925] [fbui: fixes CVE-2017-18078]
…s protected_hardlinks sysctl is on Let's add some extra safety. Fixes: systemd#7736 Backport of commit 5579f85 to systemd-230
…s protected_hardlinks sysctl is on Let's add some extra safety. Fixes: systemd#7736 Backport of commit 5579f85 to systemd-230
…s protected_hardlinks sysctl is on Let's add some extra safety. Fixes: systemd#7736 Backport of commit 5579f85 to systemd-230
…s protected_hardlinks sysctl is on Let's add some extra safety. Fixes: systemd#7736 Backport of commit 5579f85 to systemd-230
These issues only affect a vanilla kernel, so for any of this to make sense on a patched distro kernel, you'll want to disable the following:
The tmpfiles.d specification for the
Z
type more or less implies some kind of recursive chown. The spec heads off one type of vulnerability by saying that symlinks should not be followed; however, hard links are still a problem. Consider the following:The first time that tmpfiles is run, everything is fine. But then my "mjo" user owns the directory in question, and I can create a hard link...
and re-run tmpfiles...
to take ownership of
/etc/passwd
:Now, I said that everything was fine the first time that tmpfiles was run, but I lied. The recursive chown moves from the top down, meaning that
systemd-exploit-recursive/x
is chowned aftersystemd-exploit-recursive
. There is a race condition there that can be exploited. In another terminal, you can run,and if you're lucky, the hard link will get created after you own the
systemd-exploit-recursive
directory, but before chown is called onx
. This particular race condition isn't unique to theZ
type. For another example, consider,Here, the same thing happens, and the "mjo" user has some time to replace
foo
with a hard link.The text was updated successfully, but these errors were encountered: