Subject: [bug#75810] [PATCH v6 00/16] Rootless guix-daemon

From: Ludovic Courtès <[email-masked]>
Date: Thu, 20 Mar 2025 21:57:44 +0100

Hi,

Reepca Russelstein <[email-masked]> skribis:

>> The visible changes in the build environment are unfortunate; I’m hoping
>> they won’t have any practical impact, not any more than the other
>> parameters that may change currently (build UID, binfmt_misc, file
>> system, etc.)  We could test this hypothesis by rebuilding at least the
>> entire set of packages up to ‘hello’.  (I tried doing it just now in a
>> Debian VM but failed since the main partition cannot easily be extended;
>> it’ll be easier to do with Guix System.)
>>
>
> For what it's worth, the visible changes could be avoided with
> subordinate ids, as I wrote in
> https://debbugs.gnu.org/cgi/bugreport.cgi?bug=75810#86.

Yes.  The newuidmap/newgidmap mechanism looks like a hack to me so I’m
reluctant, but if we have to, we could take that route.

> We could try it as-is and see how it goes, and if there are problems
> with reproducibility add on using subordinate ids.  I would expect it to
> be a much smaller change than the root->rootless transition.

Yes.

>>   1. If /proc/self/exe points to (say) /usr/bin/guix-daemon, outside the
>>      store, execve fails with ENOENT because that file is not mounted in
>>      the chroot.
>
> No, like I wrote, /proc/self/exe, despite being reported as a symlink by
> stat, does not follow the usual symlink semantics.  This is much like
> how the files in /proc/self/fd work, e.g.:

Oh right; apologies, and thanks for taking the time to (re)explain and
to come up with a reproducer.

> From 'man 2 execve':
>
>        ENOENT The file pathname or a script *or ELF interpreter* does not exist.
>
> (emphasis mine).  The dynamic linker registered in guix-daemon's binary
> is not likely to exist in the container in this test, but an attacker
> could easily make it so as long as it's in the store.

Yes.  And it wouldn’t be so hard when one is running guix-daemon from
the store (from the ‘guix’ package or from ‘guix pull’).

>> +	    drv.builder = canonPath(drv.builder, true);
>> +	    printMsg(lvlError, format("builder is `%1%'") % drv.builder);
>> +	    if (!isInStore(drv.builder))
>> +		throw Error(format("derivation builder '%1%' is outside the store") % drv.builder);
>>  	}
>>  
>>          /* Fill in the arguments. */
>
> Note that we should still supply the original name or basename as
> argv[0].

Right, noted!

> While ensuring that what actually gets execve'd is in the store suffices
> to eliminate the vulnerability, it may be "conceptually purer" to
> require that the links pointing to it are all in the store as well.  For
> example, while a builder that is a symlink pointing to /proc/self/exe
> wouldn't be able to modify the daemon binary, it's still a piece of
> basically "undefined behavior" as far as the build environment is
> concerned, which could be closed up.  But that can come later just as
> well.

Yes.  But in practice, “normal” symlinks (i.e., not /proc/self/exe) will
lead ‘canonPath’ to throw if one component is outside the store, since
‘canonPath’ operates within the chroot.

> One more consideration I noticed when looking at v6's patch 14/16 (the
> guix-daemon.service one): we don't do anything to set the gid.  I know
> on guix system we usually use both dedicated privilege separation users
> and groups for services.  Should we use a dedicated group for
> guix-daemon as well?  Note that currently the chroot directories have
> 0750 permissions, so it's very important that their group not be
> accessible to others.

Quoth
<https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#User=>:

  User=, Group=
    Set the UNIX user or group that the processes are executed as,
    respectively. […] If no group is set, the default group of the user
    is used.

Since we don’t have ‘Group=’ in the .service file, the daemon runs as
the group of ‘guix-daemon’, also called ‘guix-daemon’ (created by
‘guix-install.sh’).

I confirmed in a VM that the process is indeed running as
guix-daemon:guix-daemon.

I’ve just sent v7 with the ‘canonPath’ change discussed above.

Thank you!

Ludo’.