While doing a code audit, I noticed that netgroups queries resulted in warnings in valgrind on my system:
==14597== Invalid read of size 1 ==14597== at 0xBB735E0: _nss_files_setnetgrent (files-netgrp.c:106) ==14597== by 0x4F4954E: __internal_setnetgrent_reuse (getnetgrent_r.c:139) ==14597== by 0x4F49879: setnetgrent (getnetgrent_r.c:181) ==14597== by 0x4033EA: netgroup_keys (getent.c:493) ==14597== by 0x402370: main (getent.c:1011) ==14597== Address 0x51fe63f is 1 bytes before a block of size 120 alloc'd ==14597== at 0x4C2A45D: malloc (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so) ==14597== by 0x4EA403D: getdelim (iogetdelim.c:66) ==14597== by 0xBB73575: _nss_files_setnetgrent (stdio.h:117) ==14597== by 0x4F4954E: __internal_setnetgrent_reuse (getnetgrent_r.c:139) ==14597== by 0x4F49879: setnetgrent (getnetgrent_r.c:181) ==14597== by 0x4033EA: netgroup_keys (getent.c:493) ==14597== by 0x402370: main (getent.c:1011)
The code was very obviously buggy too:
... while (line[curlen - 1] == '\n' && line[curlen - 2] == '\\') { /* Yes, we have a continuation line. */ if (found) ...
So if line
has just the newline character, curlen
will be 1 and hence, line[curlen - 2]
will be the byte before the array. This kind of thing is never caught normally because of two factors:
- The byte before
line
is part of the malloc metadata - The byte is read and not written to, hence there's no question of corrupting data and triggering one of malloc's consistency checking mechanisms.
Hence the code never crashes and we think that everything is just fine until the byte preceding line
just happens to be 0x5b
, i.e. ‘\‘
. That will again never happen in the normal case since it would mean that the size of the line is at least 0x5b00000000000000 on a 64-bit system. However, even if this happens, it may not do a lot of harm since all it does is resulting in concatenating an empty string to the subsequent netgroup line.
However, consider a scenario where a netgroups file was generated in a kickstart like this:
{ for i in foo bar baz; do echo "${i}_netgroup \\" for j in $(seq 1 10); do echo "($i, user_$j,) \\" done # Guess what! I can get away with the \ in the end if I leave a blank line! # How Cool is that! echo "" done } > /etc/netgroup
Here, the administrator has found a ‘feature’ where leaving blank lines allows them to get away with their very simple script to generate netgroups with trailing backslashes. Now what happens if line
happens to have a preceding 0x5b? The groups separated by the blank lines will get concatenated and all of a sudden you have one group with all of the entries being members of the first group!
So if an attacker manages to control this preceding byte in a program (possibly through a different heap overflow bug), (s)he could manipulate netgroup memberships and cause a breach. So we have a security vulnerability on our hands! Or do we?
Not such a big deal after all
So while it is fun to imagine scenarios to exploit code (who knows when you’ll get a hit!), this one seems like just a harmless buglet. Here’s why:
Controlling the preceding byte in such a manner is extremely difficult, since the malloc consistency checker should kick in before line
is even allocated this offending chunk of memory. One may argue that malloc could be overridden and the preceding byte could be tailored to ones needs, but that immediately takes out the interesting attack scenarios, i.e. the setuid-root programs. If you’re doing authentication using a non-setuid command line program then you’ve got more serious issues anyway because one could simply override getnetgrent_r or similar user/group/netgroup browsing functions and bypass your check.
Finally, this ‘exploit’ depends on the netgroups file being wrong, which is again extremely unlikely and even if it is, that’s a problem with the configuration and not a vulnerability in the code.
In the end it is just a trivial off-by-one bug which must be fixed, so it was.
Comments are closed.