← Back to context

Comment by geocar

14 days ago

> The root cause of some of the bugs seems to be the opaque nature of some of the Unix API.

Seems and smells is weasel words. The root cause is not thinking: Why is root chrooting into a directory they do not control?

Whatever you chroot into is under control of whoever made that chroot, and if you cannot understand this you have no business using chroot()

> To me such a get_user_by_name function is like a booby trap

> I'd say, either split getting the user data and loading any shared libraries in two separate functions, or somehow make it clear in the function name what it is doing.

You'd probably still be in the trap: there's usually very little difference between writing to newroot/etc/passwd and newroot/usr/lib/x86_64-linux-gnu/libnss_compat.so or newroot/bin/sh or anything else.

So I think there's no reason for /usr/sbin/chroot look up the user id in the first place (toybox chroot doesn't!), so I think the bug was doing anything at all.

> The root cause is not thinking: Why is root chrooting into a directory they do not control?

Because you can't call chroot(2) unless you're root. And "control a directory" is weasel words; root technically controls everything in one sense of the word. It can also gain full control (in a slightly different sense of the word) over a directory: kill every single process that's owned by the owner of that directory, then don't setuid into that user in this process and in any other process that the root currently executes, or will execute, until you're done with this directory. But that's just not useful for actual use, isn't it?

Secure things should be simple to do, and potentially unsafe things should be possible.

  • > And "control a directory" is weasel words;

    I did not choose the term to confuse you, that's from the definition document linked to the CVE:

    https://cwe.mitre.org/data/definitions/426.html

    The CVE itself uses the language "If the NEWROOT is writable by an attacker" which could refer to a shared library (as indicated in the report), or even a passwd file as would have been true since the origin of chroot()

    > root technically controls everything in one sense of the word.

    But not the sense we're talking about.

    > Because you can't call chroot(2) unless you're root

    Well you can[1], but this is /usr/sbin/chroot aka chroot(8) when used with a non-numeric --userspec, and the point is to drop root to a user that root controls with setuid(2). Something needs to map user names to the numeric userids that setuid(2) uses, and that something is typically the NSS database.

    Now: Which database should be used to map a username to a userid?

    - The one from before the chroot(2)?

    - Or the one that you're chroot(2)ing into

    If you're the author of the code in-question, you chose the latter, and that is totally obvious to anyone who can read because that's the order the code appears in, but it's also obvious that only the first one* is under control of root, and so only the first one could be correct.

    [1]: if you're curious: unshare(CLONE_USERNS|CLONE_FS) can be used. this is part of how rootless containers work.

    • > Well you can[1],

      No, you can't, it's an entirely different syscall that does something vaguely similar. IMHO there are a bit too many root-restricted operations that should not have been; but they are, so we're stuck with setuid-enabled "confused deputies" — arguably, it's the root that should be prohibited from calling chroot(2).

      > Now: Which database should be used to map a username to a userid? If you're the author of the code in-question, you chose the latter

      That's the problem: the choice is implicit. If the author moved setuid/setgid calls way up in the call order, the implicit choice would've also been the safe one but it was literally impossible.

      > unshare(CLONE_USERNS|CLONE_FS) can be used

      Wait, CLONE_USERNS? That's not a real flag. Did you mean CLONE_NEWUSER?

      1 reply →