libresolv res_init() does not correctly inititalize internals

Bug #1432378 reported by Joshua Rogers
6
This bug affects 1 person
Affects Status Importance Assigned to Milestone
glibc (Ubuntu)
Confirmed
Undecided
Unassigned

Bug Description

As reported here: https://sourceware.org/bugzilla/show_bug.cgi?id=18126

The bug, however, is not in the sourceware sourcecode, but in the Ubuntu one.

https://sourceware.org/git/?p=glibc.git;a=blob;f=resolv/res_libc.c;h=ee3fa2114b7051b86f6f9676f1151d1435dedb9d;hb=HEAD#l97

Contrary to what one would think, res_init() does not correctly inititialize the internals for further use by the libresolv family, and others.

When you call res_init(), it correctly "keeps" these:

        if (!_res.retrans)
                _res.retrans = RES_TIMEOUT;
        if (!_res.retry)
                _res.retry = 4;
        if (!(_res.options & RES_INIT))
                _res.options = RES_DEFAULT;
        else if (_res.nscount > 0)
                __res_iclose (&_res, true); /* Close any VC sockets. */

then calls __res_vinit():

        return (__res_vinit(&_res, 1));

However, programs that use the libresolv family and others, use the hidden function, "__res_maybe_init".

__res_maybe_init determines if res_init(__res_vinit()) needs to be called or not.

It does this:

        static time_t last_mtime;
        struct stat statbuf;
        int ret;

        if (resp->options & RES_INIT) {
                ret = stat (_PATH_RESCONF, &statbuf);
                __libc_lock_lock (lock);
                if ((ret == 0) && (last_mtime != statbuf.st_mtime)) {
                        last_mtime = statbuf.st_mtime;
                        atomicinc (__res_initstamp);
                }
                __libc_lock_unlock (lock);
                if (__res_initstamp != resp->_u._ext.initstamp) {
                        if (resp->nscount > 0)
                                __res_iclose (resp, true);
                        return __res_vinit (resp, 1);
                }
                return 0;

Since the internals have been initialized by res_init(), we don't need to reinitalize, normally. The program checks if we do need to reinitalize, such as due to the change in modifcation date of /etc/resolv.conf.

However, "last_mtime" is never set when using res_init(), so upon the first run of __res_maybe_init(), it will always run __res_vinit(). This will wipe all changes except for the ones that are kept, mentioned above.

"last_mtime" should be taken into consideration and handled, when calling res_init().

(for reference)
Only these are kept on res_init(), and thus are only kept with the first call to __res_maybe_init:
        int retrans; /* retransmition time interval */
        int retry; /* number of times to retransmit */
        u_long options; /* option flags - see below. */

These are wiped, due to this bug:

        int nscount; /* number of name servers */
        struct sockaddr_in
                nsaddr_list[MAXNS]; /* address of name server */
# define nsaddr nsaddr_list[0] /* for backward compatibility */
        u_short id; /* current message id */
        /* 2 byte hole here. */
        char *dnsrch[MAXDNSRCH+1]; /* components of domain to search */
        char defdname[256]; /* default domain (deprecated) */
        u_long pfcode; /* RES_PRF_ flags - see below. */
        unsigned ndots:4; /* threshold for initial abs. query */
        unsigned nsort:4; /* number of elements in sort_list[] */
        unsigned ipv6_unavail:1; /* connecting to IPv6 server failed */

Tags: libc libresolv
Revision history for this message
Joshua Rogers (megamansec) wrote :
Revision history for this message
Brad Figg (brad-figg) wrote : Missing required logs.

This bug is missing log files that will aid in diagnosing the problem. From a terminal window please run:

apport-collect 1432378

and then change the status of the bug to 'Confirmed'.

If, due to the nature of the issue you have encountered, you are unable to run this command, please add a comment stating that fact and change the bug status to 'Confirmed'.

This change has been made by an automated script, maintained by the Ubuntu Kernel Team.

Changed in linux (Ubuntu):
status: New → Incomplete
Revision history for this message
Joshua Rogers (megamansec) wrote :

No logs required.

Changed in linux (Ubuntu):
status: Incomplete → Confirmed
affects: linux (Ubuntu) → glibc (Ubuntu)
Revision history for this message
Joshua Rogers (megamansec) wrote :

tl;dr:

res_init() does not correctly initialize the _res struct.

The code:
        res_init();

        if(_res.options & RES_INIT) {
                printf("RES_INIT set.\n");
        } else {
                printf("RES_INIT not set.\n");
        }

outputs "RES_INIT set." correctly, and that bit is set. res_init() does all of its setting through res_init.c, https://sourceware.org/git/?p=glibc.git;a=blob;f=resolv/res_init.c;h=66561ffac2ffc039707676ed8b4bf36ee50ee889;hb=HEAD#l151
That's all fine and dandy. -- (The actual resetting happens through the function __res_vinit())

However, res_init() neglects to set things that the ubuntu-specific eglibc requires for it to be a valid struct, such as the last modified time of /etc/resolv.conf -- "last_mtime".
Thus, the first call to res_query() resets the _res struct, effectively making any changes before it and after the original res_init(), useless.

As we can see in res_data.c, https://sourceware.org/git/?p=glibc.git;a=blob;f=resolv/res_data.c;h=81c9ae5bfd7ef71ebb986b5c9572c1859684ba39;hb=HEAD#l185 , res_query() calls __res_maybe_init (), which decides whether we need to re-call res_init() or not.
In the comments for that __res_maybe_init() function:
/* Initialize resp if RES_INIT is not yet set or if res_init in some other
   thread requested re-initializing. */

On the first run of __res_maybe_init()[thus, effectively, res_query), it will always call __res_vinit(), because res_init() does not set the 'last_mtime'. -- That's because the last_mtime is an Ubuntu-specific feature, likely added for security reasons.

   >>>>>> if ((ret == 0) && (last_mtime != statbuf.st_mtime)) { <<<<<<<
                        last_mtime = statbuf.st_mtime;
                        atomicinc (__res_initstamp);
                }
                __libc_lock_unlock (lock);
                if (__res_initstamp != resp->_u._ext.initstamp) {
                        if (resp->nscount > 0)
                                __res_iclose (resp, true);
                        return __res_vinit (resp, 1);
                }

even shorter tl;dr:
'last_mtime' is an ubuntu-specific feature added to eglibc's resolv library, which is only set inside the __res_maybe_init() function.
When calling res_init(), it does not set 'last_mtime', as that uses __res_vinit(), not __res_maybe_init().
When calling res_query() for the first time, all the changes made to the _res struct are wiped, with the exception of int retrans, int retry, u_long options. This is because res_query uses __res_maybe_init(), which will reset _res if 'last_mtime' has not been set(or is old)

A quick fix is to replace __res_vinit()'s usage within the res_query() function with __res_maybe_init(), which takes the exact same parameters.

Revision history for this message
Joshua Rogers (megamansec) wrote :
Revision history for this message
Joshua Rogers (megamansec) wrote :
To post a comment you must log in.
This report contains Public information  
Everyone can see this information.

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.