Well, this one required GDB to tell what was going on, but I managed
to find a workaround.

> Short version: For recent libc releases, and at least on Gentoo,
> getgrent() and endgrent() seem to magically set errno to EINVAL (I
> think),
> [...]
> s6-envuidgid [...] fails with a strange "invalid argument" error
> whenever it tries to set GIDLIST. s6-setuidgid invokes s6-envuidgid,
> so it also fails.

For those who don't know, in GNU libc, getgrent(3) and endgrent(3) are
implemented by __nss_getent_r() and __nss_endent(), respectively,
which are part of the Name Service Switch (NSS) mechanism. You know,
the one that Laurent's nsss project is a replacement of. My
/etc/nsswitch.conf, which I don't recall having ever modified, says:

group: db files

With this configuration, the first time __nss_getent_r() is called, it
tries to call the implementation of setgrent(3) from each of these
services, "db", implemented by module /lib64/libnss_db.so.2, and
"files", implemented by module /lib64/libnss_files.so.2. The first one
is _nss_db_setgrent(), which tries to open /var/db/group.db, fails
because on my machine that file does not exist, and returns an
'unavailable' status (NSS_STATUS_UNAVAIL). The second one is
_nss_files_setgrent(), which tries to open /etc/group, succeeds, and
returns a 'successful' status (NSS_STATUS_SUCCESS). From then on,
__nss_getent_r() always calls the implementation of getgrent() from
libnss_files.so, named _nss_files_getgrent_r(). Relevant output of an
strace of the test program in my OP:

openat(AT_FDCWD, "/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/etc/nsswitch.conf", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libnss_db.so.2", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib64/libnss_files.so.2", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/var/db/group.db", O_RDONLY|O_CLOEXEC) = -1 ENOENT
(No such file or directory)
openat(AT_FDCWD, "/etc/group", O_RDONLY|O_CLOEXEC) = 3

But it turns out that, with this configuration, __nss_endent() *also*
wants to call the implementation of endgrent(3) from each of these
services. And the one from libnss_db.so, named _nss_db_endgrent(), is
just a wrapper around a munmap(2) system call, via an intermediate
internal_endent() function:

* 
https://sourceware.org/git/?p=glibc.git;a=blob;f=nss/nss_db/db-open.c;h=8a83d6b9302b39a071d0ddca5ab686e6ecfd6178;hb=56c86f5dd516284558e106d04b92875d5b623b7a

In my case, this results in a 'munmap(NULL, 0)' call that… you
guessed, fails with EINVAL (remember that _nss_files_setgrent() said
the service was unavaliable?). And strace happens to see it:

write(1, "End of file or error (errno = Su"..., 39) = 39
munmap(NULL, 0)                         = -1 EINVAL (Invalid argument)
close(3)                                = 0
write(1, "errno = Invalid argument\n", 25) = 25

The implementation from libnss_files.so, _nss_files_endgrent(), is
also called, and succeeds, but errno is already set. So, the
workaround? I removed the "db" service for the group database in
/etc/nsswitch.conf:

group: files

With this change, the output of the test program looks exactly like
Casper's and Brett's, strace no longer shows openat() calls for
/lib64/libnss_db.so.2 and /var/db/group.db, and both s6-setuidgid and
s6-envuidgid work again.

I have no idea what changed, why this used to work before my upgrade
of the libc, or why it apparently never failed for anyone else not on
Gentoo.

G.

Reply via email to