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