[Bug 1432378] Re: libresolv res_init() does not correctly inititalize internals

Joshua Rogers 1432378 at bugs.launchpad.net
Sat Aug 1 14:43:30 UTC 2015


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.

-- 
You received this bug notification because you are a member of Ubuntu
Foundations Bugs, which is subscribed to glibc in Ubuntu.
https://bugs.launchpad.net/bugs/1432378

Title:
  libresolv res_init() does not correctly inititalize internals

Status in glibc package in Ubuntu:
  Confirmed

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 */

To manage notifications about this bug go to:
https://bugs.launchpad.net/ubuntu/+source/glibc/+bug/1432378/+subscriptions



More information about the foundations-bugs mailing list