1 /* Extended resolver state separate from struct __res_state.
2    Copyright (C) 2017-2022 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4 
5    The GNU C Library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9 
10    The GNU C Library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14 
15    You should have received a copy of the GNU Lesser General Public
16    License along with the GNU C Library; if not, see
17    <https://www.gnu.org/licenses/>.  */
18 
19 #include <resolv_conf.h>
20 
21 #include <alloc_buffer.h>
22 #include <assert.h>
23 #include <libc-lock.h>
24 #include <resolv-internal.h>
25 #include <sys/stat.h>
26 #include <libc-symbols.h>
27 #include <file_change_detection.h>
28 
29 /* _res._u._ext.__glibc_extension_index is used as an index into a
30    struct resolv_conf_array object.  The intent of this construction
31    is to make reasonably sure that even if struct __res_state objects
32    are copied around and patched by applications, we can still detect
33    accesses to stale extended resolver state.  The array elements are
34    either struct resolv_conf * pointers (if the LSB is cleared) or
35    free list entries (if the LSB is set).  The free list is used to
36    speed up finding available entries in the array.  */
37 #define DYNARRAY_STRUCT resolv_conf_array
38 #define DYNARRAY_ELEMENT uintptr_t
39 #define DYNARRAY_PREFIX resolv_conf_array_
40 #define DYNARRAY_INITIAL_SIZE 0
41 #include <malloc/dynarray-skeleton.c>
42 
43 /* A magic constant for XORing the extension index
44    (_res._u._ext.__glibc_extension_index).  This makes it less likely
45    that a valid index is created by accident.  In particular, a zero
46    value leads to an invalid index.  */
47 #define INDEX_MAGIC 0x26a8fa5e48af8061ULL
48 
49 /* Global resolv.conf-related state.  */
50 struct resolv_conf_global
51 {
52   /* struct __res_state objects contain the extension index
53      (_res._u._ext.__glibc_extension_index ^ INDEX_MAGIC), which
54      refers to an element of this array.  When a struct resolv_conf
55      object (extended resolver state) is associated with a struct
56      __res_state object (legacy resolver state), its reference count
57      is increased and added to this array.  Conversely, if the
58      extended state is detached from the basic state (during
59      reinitialization or deallocation), the index is decremented, and
60      the array element is overwritten with NULL.  */
61   struct resolv_conf_array array;
62 
63   /* Start of the free list in the array.  Zero if the free list is
64      empty.  Otherwise, free_list_start >> 1 is the first element of
65      the free list (and the free list entries all have their LSB set
66      and are shifted one to the left).  */
67   uintptr_t free_list_start;
68 
69   /* Cached current configuration object for /etc/resolv.conf.  */
70   struct resolv_conf *conf_current;
71 
72   /* File system identification for /etc/resolv.conf.  */
73   struct file_change_detection file_resolve_conf;
74 };
75 
76 /* Lazily allocated storage for struct resolv_conf_global.  */
77 static struct resolv_conf_global *global;
78 
79 /* The lock synchronizes access to global and *global.  It also
80    protects the __refcount member of struct resolv_conf.  */
81 __libc_lock_define_initialized (static, lock);
82 
83 /* Ensure that GLOBAL is allocated and lock it.  Return NULL if
84    memory allocation failes.  */
85 static struct resolv_conf_global *
get_locked_global(void)86 get_locked_global (void)
87 {
88   __libc_lock_lock (lock);
89   /* Use relaxed MO through because of load outside the lock in
90      __resolv_conf_detach.  */
91   struct resolv_conf_global *global_copy = atomic_load_relaxed (&global);
92   if (global_copy == NULL)
93     {
94       global_copy = calloc (1, sizeof (*global));
95       if (global_copy == NULL)
96         return NULL;
97       atomic_store_relaxed (&global, global_copy);
98       resolv_conf_array_init (&global_copy->array);
99     }
100   return global_copy;
101 }
102 
103 /* Relinquish the lock acquired by get_locked_global.  */
104 static void
put_locked_global(struct resolv_conf_global * global_copy)105 put_locked_global (struct resolv_conf_global *global_copy)
106 {
107   __libc_lock_unlock (lock);
108 }
109 
110 /* Decrement the reference counter.  The caller must acquire the lock
111    around the function call.  */
112 static void
conf_decrement(struct resolv_conf * conf)113 conf_decrement (struct resolv_conf *conf)
114 {
115   assert (conf->__refcount > 0);
116   if (--conf->__refcount == 0)
117     free (conf);
118 }
119 
120 struct resolv_conf *
__resolv_conf_get_current(void)121 __resolv_conf_get_current (void)
122 {
123   struct file_change_detection initial;
124   if (!__file_change_detection_for_path (&initial, _PATH_RESCONF))
125     return NULL;
126 
127   struct resolv_conf_global *global_copy = get_locked_global ();
128   if (global_copy == NULL)
129     return NULL;
130   struct resolv_conf *conf;
131   if (global_copy->conf_current != NULL
132       && __file_is_unchanged (&initial, &global_copy->file_resolve_conf))
133     /* We can reuse the cached configuration object.  */
134     conf = global_copy->conf_current;
135   else
136     {
137       /* Parse configuration while holding the lock.  This avoids
138          duplicate work.  */
139       struct file_change_detection after_load;
140       conf = __resolv_conf_load (NULL, &after_load);
141       if (conf != NULL)
142         {
143           if (global_copy->conf_current != NULL)
144             conf_decrement (global_copy->conf_current);
145           global_copy->conf_current = conf; /* Takes ownership.  */
146 
147           /* Update file change detection data, but only if it matches
148              the initial measurement.  This avoids an ABA race in case
149              /etc/resolv.conf is temporarily replaced while the file
150              is read (after the initial measurement), and restored to
151              the initial version later.  */
152           if (__file_is_unchanged (&initial, &after_load))
153             global_copy->file_resolve_conf = after_load;
154           else
155             /* If there is a discrepancy, trigger a reload during the
156                next use.  */
157             global_copy->file_resolve_conf.size = -1;
158         }
159     }
160 
161   if (conf != NULL)
162     {
163       /* Return an additional reference.  */
164       assert (conf->__refcount > 0);
165       ++conf->__refcount;
166       assert (conf->__refcount > 0);
167     }
168   put_locked_global (global_copy);
169   return conf;
170 }
171 
172 /* Internal implementation of __resolv_conf_get, without validation
173    against *RESP.  */
174 static struct resolv_conf *
resolv_conf_get_1(const struct __res_state * resp)175 resolv_conf_get_1 (const struct __res_state *resp)
176 {
177   /* Not initialized, and therefore no assoicated context.  */
178   if (!(resp->options & RES_INIT))
179     return NULL;
180 
181   struct resolv_conf_global *global_copy = get_locked_global ();
182   if (global_copy == NULL)
183     /* A memory allocation failure here means that no associated
184        contexts exists, so returning NULL is correct.  */
185     return NULL;
186   size_t index = resp->_u._ext.__glibc_extension_index ^ INDEX_MAGIC;
187   struct resolv_conf *conf = NULL;
188   if (index < resolv_conf_array_size (&global_copy->array))
189     {
190       uintptr_t *slot = resolv_conf_array_at (&global_copy->array, index);
191       if (!(*slot & 1))
192         {
193           conf = (struct resolv_conf *) *slot;
194           assert (conf->__refcount > 0);
195           ++conf->__refcount;
196         }
197     }
198   put_locked_global (global_copy);
199   return conf;
200 }
201 
202 /* Return true if both IPv4 addresses are equal.  */
203 static bool
same_address_v4(const struct sockaddr_in * left,const struct sockaddr_in * right)204 same_address_v4 (const struct sockaddr_in *left,
205                  const struct sockaddr_in *right)
206 {
207   return left->sin_addr.s_addr == right->sin_addr.s_addr
208     && left->sin_port == right->sin_port;
209 }
210 
211 /* Return true if both IPv6 addresses are equal.  This ignores the
212    flow label.  */
213 static bool
same_address_v6(const struct sockaddr_in6 * left,const struct sockaddr_in6 * right)214 same_address_v6 (const struct sockaddr_in6 *left,
215                  const struct sockaddr_in6 *right)
216 {
217   return memcmp (&left->sin6_addr, &right->sin6_addr,
218                  sizeof (left->sin6_addr)) == 0
219     && left->sin6_port == right->sin6_port
220     && left->sin6_scope_id == right->sin6_scope_id;
221 }
222 
223 static bool
same_address(const struct sockaddr * left,const struct sockaddr * right)224 same_address (const struct sockaddr *left, const struct sockaddr *right)
225 {
226   if (left->sa_family != right->sa_family)
227     return false;
228   switch (left->sa_family)
229     {
230     case AF_INET:
231       return same_address_v4 ((const struct sockaddr_in *) left,
232                               (const struct sockaddr_in *) right);
233     case AF_INET6:
234       return same_address_v6 ((const struct sockaddr_in6 *) left,
235                               (const struct sockaddr_in6 *) right);
236     }
237   return false;
238 }
239 
240 /* Check that *RESP and CONF match.  Used by __resolv_conf_get.  */
241 static bool
resolv_conf_matches(const struct __res_state * resp,const struct resolv_conf * conf)242 resolv_conf_matches (const struct __res_state *resp,
243                      const struct resolv_conf *conf)
244 {
245   /* NB: Do not compare the options, retrans, retry, ndots.  These can
246      be changed by applicaiton.  */
247 
248   /* Check that the name servers in *RESP have not been modified by
249      the application.  */
250   {
251     size_t nserv = conf->nameserver_list_size;
252     if (nserv > MAXNS)
253       nserv = MAXNS;
254     /* _ext.nscount is 0 until initialized by res_send.c.  */
255     if (resp->nscount != nserv
256         || (resp->_u._ext.nscount != 0 && resp->_u._ext.nscount != nserv))
257       return false;
258     for (size_t i = 0; i < nserv; ++i)
259       {
260         if (resp->nsaddr_list[i].sin_family == 0)
261           {
262             if (resp->_u._ext.nsaddrs[i]->sin6_family != AF_INET6)
263               return false;
264             if (!same_address ((struct sockaddr *) resp->_u._ext.nsaddrs[i],
265                                conf->nameserver_list[i]))
266               return false;
267           }
268         else if (resp->nsaddr_list[i].sin_family != AF_INET)
269           return false;
270         else if (!same_address ((struct sockaddr *) &resp->nsaddr_list[i],
271                                 conf->nameserver_list[i]))
272           return false;
273       }
274   }
275 
276   /* Check that the search list in *RESP has not been modified by the
277      application.  */
278   {
279     if (resp->dnsrch[0] == NULL)
280       {
281         /* Empty search list.  No default domain name.  */
282         return conf->search_list_size == 0 && resp->defdname[0] == '\0';
283       }
284 
285     if (resp->dnsrch[0] != resp->defdname)
286       /* If the search list is not empty, it must start with the
287          default domain name.  */
288       return false;
289 
290     size_t nsearch;
291     for (nsearch = 0; nsearch < MAXDNSRCH; ++nsearch)
292       if (resp->dnsrch[nsearch] == NULL)
293         break;
294     if (nsearch > MAXDNSRCH)
295       /* Search list is not null-terminated.  */
296       return false;
297 
298     size_t search_list_size = 0;
299     for (size_t i = 0; i < conf->search_list_size; ++i)
300       {
301         if (resp->dnsrch[i] != NULL)
302           {
303             search_list_size += strlen (resp->dnsrch[i]) + 1;
304             if (strcmp (resp->dnsrch[i], conf->search_list[i]) != 0)
305               return false;
306           }
307         else
308           {
309             /* resp->dnsrch is truncated if the number of elements
310                exceeds MAXDNSRCH, or if the combined storage space for
311                the search list exceeds what can be stored in
312                resp->defdname.  */
313             if (i == MAXDNSRCH || search_list_size > sizeof (resp->dnsrch))
314               break;
315             /* Otherwise, a mismatch indicates a match failure.  */
316             return false;
317           }
318       }
319   }
320 
321   /* Check that the sort list has not been modified.  */
322   {
323     size_t nsort = conf->sort_list_size;
324     if (nsort > MAXRESOLVSORT)
325       nsort = MAXRESOLVSORT;
326     if (resp->nsort != nsort)
327       return false;
328     for (size_t i = 0; i < nsort; ++i)
329       if (resp->sort_list[i].addr.s_addr != conf->sort_list[i].addr.s_addr
330           || resp->sort_list[i].mask != conf->sort_list[i].mask)
331         return false;
332   }
333 
334   return true;
335 }
336 
337 struct resolv_conf *
__resolv_conf_get(struct __res_state * resp)338 __resolv_conf_get (struct __res_state *resp)
339 {
340   struct resolv_conf *conf = resolv_conf_get_1 (resp);
341   if (conf == NULL)
342     return NULL;
343   if (resolv_conf_matches (resp, conf))
344     return conf;
345   __resolv_conf_put (conf);
346   return NULL;
347 }
348 
349 void
__resolv_conf_put(struct resolv_conf * conf)350 __resolv_conf_put (struct resolv_conf *conf)
351 {
352   if (conf == NULL)
353     return;
354 
355   __libc_lock_lock (lock);
356   conf_decrement (conf);
357   __libc_lock_unlock (lock);
358 }
359 
360 struct resolv_conf *
__resolv_conf_allocate(const struct resolv_conf * init)361 __resolv_conf_allocate (const struct resolv_conf *init)
362 {
363   /* Allocate in decreasing order of alignment.  */
364   _Static_assert (__alignof__ (const char *const *)
365                   <= __alignof__ (struct resolv_conf), "alignment");
366   _Static_assert (__alignof__ (struct sockaddr_in6)
367                   <= __alignof__ (const char *const *), "alignment");
368   _Static_assert (__alignof__ (struct sockaddr_in)
369                   ==  __alignof__ (struct sockaddr_in6), "alignment");
370   _Static_assert (__alignof__ (struct resolv_sortlist_entry)
371                   <= __alignof__ (struct sockaddr_in), "alignment");
372 
373   /* Space needed by the nameserver addresses.  */
374   size_t address_space = 0;
375   for (size_t i = 0; i < init->nameserver_list_size; ++i)
376     if (init->nameserver_list[i]->sa_family == AF_INET)
377       address_space += sizeof (struct sockaddr_in);
378     else
379       {
380         assert (init->nameserver_list[i]->sa_family == AF_INET6);
381         address_space += sizeof (struct sockaddr_in6);
382       }
383 
384   /* Space needed by the search list strings.  */
385   size_t string_space = 0;
386   for (size_t i = 0; i < init->search_list_size; ++i)
387     string_space += strlen (init->search_list[i]) + 1;
388 
389   /* Allocate the buffer.  */
390   void *ptr;
391   struct alloc_buffer buffer = alloc_buffer_allocate
392     (sizeof (struct resolv_conf)
393      + init->nameserver_list_size * sizeof (init->nameserver_list[0])
394      + address_space
395      + init->search_list_size * sizeof (init->search_list[0])
396      + init->sort_list_size * sizeof (init->sort_list[0])
397      + string_space,
398      &ptr);
399   struct resolv_conf *conf
400     = alloc_buffer_alloc (&buffer, struct resolv_conf);
401   if (conf == NULL)
402     /* Memory allocation failure.  */
403     return NULL;
404   assert (conf == ptr);
405 
406   /* Initialize the contents.  */
407   conf->__refcount = 1;
408   conf->retrans = init->retrans;
409   conf->retry = init->retry;
410   conf->options = init->options;
411   conf->ndots = init->ndots;
412 
413   /* Allocate the arrays with pointers.  These must come first because
414      they have the highets alignment.  */
415   conf->nameserver_list_size = init->nameserver_list_size;
416   const struct sockaddr **nameserver_array = alloc_buffer_alloc_array
417     (&buffer, const struct sockaddr *, init->nameserver_list_size);
418   conf->nameserver_list = nameserver_array;
419 
420   conf->search_list_size = init->search_list_size;
421   const char **search_array = alloc_buffer_alloc_array
422     (&buffer, const char *, init->search_list_size);
423   conf->search_list = search_array;
424 
425   /* Fill the name server list array.  */
426   for (size_t i = 0; i < init->nameserver_list_size; ++i)
427     if (init->nameserver_list[i]->sa_family == AF_INET)
428       {
429         struct sockaddr_in *sa = alloc_buffer_alloc
430           (&buffer, struct sockaddr_in);
431         *sa = *(struct sockaddr_in *) init->nameserver_list[i];
432         nameserver_array[i] = (struct sockaddr *) sa;
433       }
434     else
435       {
436         struct sockaddr_in6 *sa = alloc_buffer_alloc
437           (&buffer, struct sockaddr_in6);
438         *sa = *(struct sockaddr_in6 *) init->nameserver_list[i];
439         nameserver_array[i] = (struct sockaddr *) sa;
440       }
441 
442   /* Allocate and fill the sort list array.  */
443   {
444     conf->sort_list_size = init->sort_list_size;
445     struct resolv_sortlist_entry *array = alloc_buffer_alloc_array
446       (&buffer, struct resolv_sortlist_entry, init->sort_list_size);
447     conf->sort_list = array;
448     for (size_t i = 0; i < init->sort_list_size; ++i)
449       array[i] = init->sort_list[i];
450   }
451 
452   /* Fill the search list array.  This must come last because the
453      strings are the least aligned part of the allocation.  */
454   {
455     for (size_t i = 0; i < init->search_list_size; ++i)
456       search_array[i] = alloc_buffer_copy_string
457         (&buffer, init->search_list[i]);
458   }
459 
460   assert (!alloc_buffer_has_failed (&buffer));
461   return conf;
462 }
463 
464 /* Update *RESP from the extended state.  */
465 static __attribute__ ((nonnull (1, 2), warn_unused_result)) bool
update_from_conf(struct __res_state * resp,const struct resolv_conf * conf)466 update_from_conf (struct __res_state *resp, const struct resolv_conf *conf)
467 {
468   resp->defdname[0] = '\0';
469   resp->pfcode = 0;
470   resp->_vcsock = -1;
471   resp->_flags = 0;
472   resp->ipv6_unavail = false;
473   resp->__glibc_unused_qhook = NULL;
474   resp->__glibc_unused_rhook = NULL;
475 
476   resp->retrans = conf->retrans;
477   resp->retry = conf->retry;
478   resp->options = conf->options;
479   resp->ndots = conf->ndots;
480 
481   /* Copy the name server addresses.  */
482   {
483     resp->nscount = 0;
484     resp->_u._ext.nscount = 0;
485     size_t nserv = conf->nameserver_list_size;
486     if (nserv > MAXNS)
487       nserv = MAXNS;
488     for (size_t i = 0; i < nserv; i++)
489       {
490         if (conf->nameserver_list[i]->sa_family == AF_INET)
491           {
492             resp->nsaddr_list[i]
493               = *(struct sockaddr_in *)conf->nameserver_list[i];
494             resp->_u._ext.nsaddrs[i] = NULL;
495           }
496         else
497           {
498             assert (conf->nameserver_list[i]->sa_family == AF_INET6);
499             resp->nsaddr_list[i].sin_family = 0;
500             /* Make a defensive copy of the name server address, in
501                case the application overwrites it.  */
502             struct sockaddr_in6 *sa = malloc (sizeof (*sa));
503             if (sa == NULL)
504               {
505                 for (size_t j = 0; j < i; ++j)
506                   free (resp->_u._ext.nsaddrs[j]);
507                 return false;
508               }
509             *sa = *(struct sockaddr_in6 *)conf->nameserver_list[i];
510             resp->_u._ext.nsaddrs[i] = sa;
511           }
512         resp->_u._ext.nssocks[i] = -1;
513       }
514     resp->nscount = nserv;
515     /* Leave resp->_u._ext.nscount at 0.  res_send.c handles this.  */
516   }
517 
518   /* Fill in the prefix of the search list.  It is truncated either at
519      MAXDNSRCH, or if reps->defdname has insufficient space.  */
520   {
521     struct alloc_buffer buffer
522       = alloc_buffer_create (resp->defdname, sizeof (resp->defdname));
523     size_t size = conf->search_list_size;
524     size_t i;
525     for (i = 0; i < size && i < MAXDNSRCH; ++i)
526       {
527         resp->dnsrch[i] = alloc_buffer_copy_string
528           (&buffer, conf->search_list[i]);
529         if (resp->dnsrch[i] == NULL)
530           /* No more space in resp->defdname.  Truncate.  */
531           break;
532       }
533     resp->dnsrch[i] = NULL;
534   }
535 
536   /* Copy the sort list.  */
537   {
538     size_t nsort = conf->sort_list_size;
539     if (nsort > MAXRESOLVSORT)
540       nsort = MAXRESOLVSORT;
541     for (size_t i = 0; i < nsort; ++i)
542       {
543         resp->sort_list[i].addr = conf->sort_list[i].addr;
544         resp->sort_list[i].mask = conf->sort_list[i].mask;
545       }
546     resp->nsort = nsort;
547   }
548 
549   /* The overlapping parts of both configurations should agree after
550      initialization.  */
551   assert (resolv_conf_matches (resp, conf));
552   return true;
553 }
554 
555 /* Decrement the configuration object at INDEX and free it if the
556    reference counter reaches 0.  *GLOBAL_COPY must be locked and
557    remains so.  */
558 static void
decrement_at_index(struct resolv_conf_global * global_copy,size_t index)559 decrement_at_index (struct resolv_conf_global *global_copy, size_t index)
560 {
561   if (index < resolv_conf_array_size (&global_copy->array))
562     {
563       /* Index found.  */
564       uintptr_t *slot = resolv_conf_array_at (&global_copy->array, index);
565       /* Check that the slot is not already part of the free list.  */
566       if (!(*slot & 1))
567         {
568           struct resolv_conf *conf = (struct resolv_conf *) *slot;
569           conf_decrement (conf);
570           /* Put the slot onto the free list.  */
571           *slot = global_copy->free_list_start;
572           global_copy->free_list_start = (index << 1) | 1;
573         }
574     }
575 }
576 
577 bool
__resolv_conf_attach(struct __res_state * resp,struct resolv_conf * conf)578 __resolv_conf_attach (struct __res_state *resp, struct resolv_conf *conf)
579 {
580   assert (conf->__refcount > 0);
581 
582   struct resolv_conf_global *global_copy = get_locked_global ();
583   if (global_copy == NULL)
584     return false;
585 
586   /* Try to find an unused index in the array.  */
587   size_t index;
588   {
589     if (global_copy->free_list_start & 1)
590       {
591         /* Unlink from the free list.  */
592         index = global_copy->free_list_start >> 1;
593         uintptr_t *slot = resolv_conf_array_at (&global_copy->array, index);
594         global_copy->free_list_start = *slot;
595         assert (global_copy->free_list_start == 0
596                 || global_copy->free_list_start & 1);
597         /* Install the configuration pointer.  */
598         *slot = (uintptr_t) conf;
599       }
600     else
601       {
602         size_t size = resolv_conf_array_size (&global_copy->array);
603         /* No usable index found.  Increase the array size.  */
604         resolv_conf_array_add (&global_copy->array, (uintptr_t) conf);
605         if (resolv_conf_array_has_failed (&global_copy->array))
606           {
607             put_locked_global (global_copy);
608             __set_errno (ENOMEM);
609             return false;
610           }
611         /* The new array element was added at the end.  */
612         index = size;
613       }
614   }
615 
616   /* We have added a new reference to the object.  */
617   ++conf->__refcount;
618   assert (conf->__refcount > 0);
619   put_locked_global (global_copy);
620 
621   if (!update_from_conf (resp, conf))
622     {
623       /* Drop the reference we acquired.  Reacquire the lock.  The
624          object has already been allocated, so it cannot be NULL this
625          time.  */
626       global_copy = get_locked_global ();
627       decrement_at_index (global_copy, index);
628       put_locked_global (global_copy);
629       return false;
630     }
631   resp->_u._ext.__glibc_extension_index = index ^ INDEX_MAGIC;
632 
633   return true;
634 }
635 
636 void
__resolv_conf_detach(struct __res_state * resp)637 __resolv_conf_detach (struct __res_state *resp)
638 {
639   if (atomic_load_relaxed (&global) == NULL)
640     /* Detach operation after a shutdown, or without any prior
641        attachment.  We cannot free the data (and there might not be
642        anything to free anyway).  */
643     return;
644 
645   struct resolv_conf_global *global_copy = get_locked_global ();
646   size_t index = resp->_u._ext.__glibc_extension_index ^ INDEX_MAGIC;
647   decrement_at_index (global_copy, index);
648 
649   /* Clear the index field, so that accidental reuse is less
650      likely.  */
651   resp->_u._ext.__glibc_extension_index = 0;
652 
653   put_locked_global (global_copy);
654 }
655 
656 /* Deallocate the global data.  */
libc_freeres_fn(freeres)657 libc_freeres_fn (freeres)
658 {
659   /* No locking because this function is supposed to be called when
660      the process has turned single-threaded.  */
661   if (global == NULL)
662     return;
663 
664   if (global->conf_current != NULL)
665     {
666       conf_decrement (global->conf_current);
667       global->conf_current = NULL;
668     }
669 
670   /* Note that this frees only the array itself.  The pointed-to
671      configuration objects should have been deallocated by res_nclose
672      and per-thread cleanup functions.  */
673   resolv_conf_array_free (&global->array);
674 
675   free (global);
676 
677   /* Stop potential future __resolv_conf_detach calls from accessing
678      deallocated memory.  */
679   global = NULL;
680 }
681