hurd: fix resolv/tst-resolv-res_init-skeleton.c build
[glibc.git] / resolv / resolv_conf.c
blobf391d30c277bb3481ffef7ac74f9a198981ddecf
1 /* Extended resolver state separate from struct __res_state.
2 Copyright (C) 2017 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
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.
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.
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 <http://www.gnu.org/licenses/>. */
19 #include <resolv_conf.h>
21 #include <alloc_buffer.h>
22 #include <assert.h>
23 #include <libc-lock.h>
24 #include <resolv-internal.h>
25 #include <sys/stat.h>
27 /* _res._u._ext.__glibc_extension_index is used as an index into a
28 struct resolv_conf_array object. The intent of this construction
29 is to make reasonably sure that even if struct __res_state objects
30 are copied around and patched by applications, we can still detect
31 accesses to stale extended resolver state. The array elements are
32 either struct resolv_conf * pointers (if the LSB is cleared) or
33 free list entries (if the LSB is set). The free list is used to
34 speed up finding available entries in the array. */
35 #define DYNARRAY_STRUCT resolv_conf_array
36 #define DYNARRAY_ELEMENT uintptr_t
37 #define DYNARRAY_PREFIX resolv_conf_array_
38 #define DYNARRAY_INITIAL_SIZE 0
39 #include <malloc/dynarray-skeleton.c>
41 /* A magic constant for XORing the extension index
42 (_res._u._ext.__glibc_extension_index). This makes it less likely
43 that a valid index is created by accident. In particular, a zero
44 value leads to an invalid index. */
45 #define INDEX_MAGIC 0x26a8fa5e48af8061ULL
47 /* Global resolv.conf-related state. */
48 struct resolv_conf_global
50 /* struct __res_state objects contain the extension index
51 (_res._u._ext.__glibc_extension_index ^ INDEX_MAGIC), which
52 refers to an element of this array. When a struct resolv_conf
53 object (extended resolver state) is associated with a struct
54 __res_state object (legacy resolver state), its reference count
55 is increased and added to this array. Conversely, if the
56 extended state is detached from the basic state (during
57 reinitialization or deallocation), the index is decremented, and
58 the array element is overwritten with NULL. */
59 struct resolv_conf_array array;
61 /* Start of the free list in the array. Zero if the free list is
62 empty. Otherwise, free_list_start >> 1 is the first element of
63 the free list (and the free list entries all have their LSB set
64 and are shifted one to the left). */
65 uintptr_t free_list_start;
67 /* Cached current configuration object for /etc/resolv.conf. */
68 struct resolv_conf *conf_current;
70 /* These properties of /etc/resolv.conf are used to check if the
71 configuration needs reloading. */
72 struct timespec conf_mtime;
73 struct timespec conf_ctime;
74 off64_t conf_size;
75 ino64_t conf_ino;
78 /* Lazily allocated storage for struct resolv_conf_global. */
79 static struct resolv_conf_global *global;
81 /* The lock synchronizes access to global and *global. It also
82 protects the __refcount member of struct resolv_conf. */
83 __libc_lock_define_initialized (static, lock);
85 /* Ensure that GLOBAL is allocated and lock it. Return NULL if
86 memory allocation failes. */
87 static struct resolv_conf_global *
88 get_locked_global (void)
90 __libc_lock_lock (lock);
91 /* Use relaxed MO through because of load outside the lock in
92 __resolv_conf_detach. */
93 struct resolv_conf_global *global_copy = atomic_load_relaxed (&global);
94 if (global_copy == NULL)
96 global_copy = calloc (1, sizeof (*global));
97 if (global_copy == NULL)
98 return NULL;
99 atomic_store_relaxed (&global, global_copy);
100 resolv_conf_array_init (&global_copy->array);
102 return global_copy;
105 /* Relinquish the lock acquired by get_locked_global. */
106 static void
107 put_locked_global (struct resolv_conf_global *global_copy)
109 __libc_lock_unlock (lock);
112 /* Decrement the reference counter. The caller must acquire the lock
113 around the function call. */
114 static void
115 conf_decrement (struct resolv_conf *conf)
117 assert (conf->__refcount > 0);
118 if (--conf->__refcount == 0)
119 free (conf);
122 struct resolv_conf *
123 __resolv_conf_get_current (void)
125 struct stat64 st;
126 if (stat64 (_PATH_RESCONF, &st) != 0)
128 switch (errno)
130 case EACCES:
131 case EISDIR:
132 case ELOOP:
133 case ENOENT:
134 case ENOTDIR:
135 case EPERM:
136 /* Ignore errors due to file system contents. */
137 memset (&st, 0, sizeof (st));
138 break;
139 default:
140 /* Other errors are fatal. */
141 return NULL;
145 struct resolv_conf_global *global_copy = get_locked_global ();
146 if (global_copy == NULL)
147 return NULL;
148 struct resolv_conf *conf;
149 if (global_copy->conf_current != NULL
150 && (global_copy->conf_mtime.tv_sec == st.st_mtim.tv_sec
151 && global_copy->conf_mtime.tv_nsec == st.st_mtim.tv_nsec
152 && global_copy->conf_ctime.tv_sec == st.st_ctim.tv_sec
153 && global_copy->conf_ctime.tv_nsec == st.st_ctim.tv_nsec
154 && global_copy->conf_ino == st.st_ino
155 && global_copy->conf_size == st.st_size))
156 /* We can reuse the cached configuration object. */
157 conf = global_copy->conf_current;
158 else
160 /* Parse configuration while holding the lock. This avoids
161 duplicate work. */
162 conf = __resolv_conf_load (NULL);
163 if (conf != NULL)
165 if (global_copy->conf_current != NULL)
166 conf_decrement (global_copy->conf_current);
167 global_copy->conf_current = conf; /* Takes ownership. */
169 /* Update file modification stamps. The configuration we
170 read could be a newer version of the file, but this does
171 not matter because this will lead to an extraneous reload
172 later. */
173 global_copy->conf_mtime = st.st_mtim;
174 global_copy->conf_ctime = st.st_ctim;
175 global_copy->conf_ino = st.st_ino;
176 global_copy->conf_size = st.st_size;
180 if (conf != NULL)
182 /* Return an additional reference. */
183 assert (conf->__refcount > 0);
184 ++conf->__refcount;
185 assert (conf->__refcount > 0);
187 put_locked_global (global_copy);
188 return conf;
191 /* Internal implementation of __resolv_conf_get, without validation
192 against *RESP. */
193 static struct resolv_conf *
194 resolv_conf_get_1 (const struct __res_state *resp)
196 /* Not initialized, and therefore no assoicated context. */
197 if (!(resp->options & RES_INIT))
198 return NULL;
200 struct resolv_conf_global *global_copy = get_locked_global ();
201 if (global_copy == NULL)
202 /* A memory allocation failure here means that no associated
203 contexts exists, so returning NULL is correct. */
204 return NULL;
205 size_t index = resp->_u._ext.__glibc_extension_index ^ INDEX_MAGIC;
206 struct resolv_conf *conf = NULL;
207 if (index < resolv_conf_array_size (&global_copy->array))
209 uintptr_t *slot = resolv_conf_array_at (&global_copy->array, index);
210 if (!(*slot & 1))
212 conf = (struct resolv_conf *) *slot;
213 assert (conf->__refcount > 0);
214 ++conf->__refcount;
217 put_locked_global (global_copy);
218 return conf;
221 /* Return true if both IPv4 addresses are equal. */
222 static bool
223 same_address_v4 (const struct sockaddr_in *left,
224 const struct sockaddr_in *right)
226 return left->sin_addr.s_addr == right->sin_addr.s_addr
227 && left->sin_port == right->sin_port;
230 /* Return true if both IPv6 addresses are equal. This ignores the
231 flow label. */
232 static bool
233 same_address_v6 (const struct sockaddr_in6 *left,
234 const struct sockaddr_in6 *right)
236 return memcmp (&left->sin6_addr, &right->sin6_addr,
237 sizeof (left->sin6_addr)) == 0
238 && left->sin6_port == right->sin6_port
239 && left->sin6_scope_id == right->sin6_scope_id;
242 static bool
243 same_address (const struct sockaddr *left, const struct sockaddr *right)
245 if (left->sa_family != right->sa_family)
246 return false;
247 switch (left->sa_family)
249 case AF_INET:
250 return same_address_v4 ((const struct sockaddr_in *) left,
251 (const struct sockaddr_in *) right);
252 case AF_INET6:
253 return same_address_v6 ((const struct sockaddr_in6 *) left,
254 (const struct sockaddr_in6 *) right);
256 return false;
259 /* Check that *RESP and CONF match. Used by __resolv_conf_get. */
260 static bool
261 resolv_conf_matches (const struct __res_state *resp,
262 const struct resolv_conf *conf)
264 /* NB: Do not compare the options, retrans, retry, ndots. These can
265 be changed by applicaiton. */
267 /* Check that the name servers in *RESP have not been modified by
268 the application. */
270 size_t nserv = conf->nameserver_list_size;
271 if (nserv > MAXNS)
272 nserv = MAXNS;
273 /* _ext.nscount is 0 until initialized by res_send.c. */
274 if (resp->nscount != nserv
275 || (resp->_u._ext.nscount != 0 && resp->_u._ext.nscount != nserv))
276 return false;
277 for (size_t i = 0; i < nserv; ++i)
279 if (resp->nsaddr_list[i].sin_family == 0)
281 if (resp->_u._ext.nsaddrs[i]->sin6_family != AF_INET6)
282 return false;
283 if (!same_address ((struct sockaddr *) resp->_u._ext.nsaddrs[i],
284 conf->nameserver_list[i]))
285 return false;
287 else if (resp->nsaddr_list[i].sin_family != AF_INET)
288 return false;
289 else if (!same_address ((struct sockaddr *) &resp->nsaddr_list[i],
290 conf->nameserver_list[i]))
291 return false;
295 /* Check that the search list in *RESP has not been modified by the
296 application. */
298 if (resp->dnsrch[0] == NULL)
300 /* Empty search list. No default domain name. */
301 return conf->search_list_size == 0 && resp->defdname[0] == '\0';
304 if (resp->dnsrch[0] != resp->defdname)
305 /* If the search list is not empty, it must start with the
306 default domain name. */
307 return false;
309 size_t nsearch;
310 for (nsearch = 0; nsearch < MAXDNSRCH; ++nsearch)
311 if (resp->dnsrch[nsearch] == NULL)
312 break;
313 if (nsearch > MAXDNSRCH)
314 /* Search list is not null-terminated. */
315 return false;
317 size_t search_list_size = 0;
318 for (size_t i = 0; i < conf->search_list_size; ++i)
320 if (resp->dnsrch[i] != NULL)
322 search_list_size += strlen (resp->dnsrch[i]) + 1;
323 if (strcmp (resp->dnsrch[i], conf->search_list[i]) != 0)
324 return false;
326 else
328 /* resp->dnsrch is truncated if the number of elements
329 exceeds MAXDNSRCH, or if the combined storage space for
330 the search list exceeds what can be stored in
331 resp->defdname. */
332 if (i == MAXDNSRCH || search_list_size > sizeof (resp->dnsrch))
333 break;
334 /* Otherwise, a mismatch indicates a match failure. */
335 return false;
340 /* Check that the sort list has not been modified. */
342 size_t nsort = conf->sort_list_size;
343 if (nsort > MAXRESOLVSORT)
344 nsort = MAXRESOLVSORT;
345 if (resp->nsort != nsort)
346 return false;
347 for (size_t i = 0; i < nsort; ++i)
348 if (resp->sort_list[i].addr.s_addr != conf->sort_list[i].addr.s_addr
349 || resp->sort_list[i].mask != conf->sort_list[i].mask)
350 return false;
353 return true;
356 struct resolv_conf *
357 __resolv_conf_get (struct __res_state *resp)
359 struct resolv_conf *conf = resolv_conf_get_1 (resp);
360 if (conf == NULL)
361 return NULL;
362 if (resolv_conf_matches (resp, conf))
363 return conf;
364 __resolv_conf_put (conf);
365 return NULL;
368 void
369 __resolv_conf_put (struct resolv_conf *conf)
371 if (conf == NULL)
372 return;
374 __libc_lock_lock (lock);
375 conf_decrement (conf);
376 __libc_lock_unlock (lock);
379 struct resolv_conf *
380 __resolv_conf_allocate (const struct resolv_conf *init)
382 /* Allocate in decreasing order of alignment. */
383 _Static_assert (__alignof__ (const char *const *)
384 <= __alignof__ (struct resolv_conf), "alignment");
385 _Static_assert (__alignof__ (struct sockaddr_in6)
386 <= __alignof__ (const char *const *), "alignment");
387 _Static_assert (__alignof__ (struct sockaddr_in)
388 == __alignof__ (struct sockaddr_in6), "alignment");
389 _Static_assert (__alignof__ (struct resolv_sortlist_entry)
390 <= __alignof__ (struct sockaddr_in), "alignment");
392 /* Space needed by the nameserver addresses. */
393 size_t address_space = 0;
394 for (size_t i = 0; i < init->nameserver_list_size; ++i)
395 if (init->nameserver_list[i]->sa_family == AF_INET)
396 address_space += sizeof (struct sockaddr_in);
397 else
399 assert (init->nameserver_list[i]->sa_family == AF_INET6);
400 address_space += sizeof (struct sockaddr_in6);
403 /* Space needed by the search list strings. */
404 size_t string_space = 0;
405 for (size_t i = 0; i < init->search_list_size; ++i)
406 string_space += strlen (init->search_list[i]) + 1;
408 /* Allocate the buffer. */
409 void *ptr;
410 struct alloc_buffer buffer = alloc_buffer_allocate
411 (sizeof (struct resolv_conf)
412 + init->nameserver_list_size * sizeof (init->nameserver_list[0])
413 + address_space
414 + init->search_list_size * sizeof (init->search_list[0])
415 + init->sort_list_size * sizeof (init->sort_list[0])
416 + string_space,
417 &ptr);
418 struct resolv_conf *conf
419 = alloc_buffer_alloc (&buffer, struct resolv_conf);
420 if (conf == NULL)
421 /* Memory allocation failure. */
422 return NULL;
423 assert (conf == ptr);
425 /* Initialize the contents. */
426 conf->__refcount = 1;
427 conf->retrans = init->retrans;
428 conf->retry = init->retry;
429 conf->options = init->options;
430 conf->ndots = init->ndots;
432 /* Allocate the arrays with pointers. These must come first because
433 they have the highets alignment. */
434 conf->nameserver_list_size = init->nameserver_list_size;
435 const struct sockaddr **nameserver_array = alloc_buffer_alloc_array
436 (&buffer, const struct sockaddr *, init->nameserver_list_size);
437 conf->nameserver_list = nameserver_array;
439 conf->search_list_size = init->search_list_size;
440 const char **search_array = alloc_buffer_alloc_array
441 (&buffer, const char *, init->search_list_size);
442 conf->search_list = search_array;
444 /* Fill the name server list array. */
445 for (size_t i = 0; i < init->nameserver_list_size; ++i)
446 if (init->nameserver_list[i]->sa_family == AF_INET)
448 struct sockaddr_in *sa = alloc_buffer_alloc
449 (&buffer, struct sockaddr_in);
450 *sa = *(struct sockaddr_in *) init->nameserver_list[i];
451 nameserver_array[i] = (struct sockaddr *) sa;
453 else
455 struct sockaddr_in6 *sa = alloc_buffer_alloc
456 (&buffer, struct sockaddr_in6);
457 *sa = *(struct sockaddr_in6 *) init->nameserver_list[i];
458 nameserver_array[i] = (struct sockaddr *) sa;
461 /* Allocate and fill the sort list array. */
463 conf->sort_list_size = init->sort_list_size;
464 struct resolv_sortlist_entry *array = alloc_buffer_alloc_array
465 (&buffer, struct resolv_sortlist_entry, init->sort_list_size);
466 conf->sort_list = array;
467 for (size_t i = 0; i < init->sort_list_size; ++i)
468 array[i] = init->sort_list[i];
471 /* Fill the search list array. This must come last because the
472 strings are the least aligned part of the allocation. */
474 for (size_t i = 0; i < init->search_list_size; ++i)
475 search_array[i] = alloc_buffer_copy_string
476 (&buffer, init->search_list[i]);
479 assert (!alloc_buffer_has_failed (&buffer));
480 return conf;
483 /* Update *RESP from the extended state. */
484 static __attribute__ ((nonnull (1, 2), warn_unused_result)) bool
485 update_from_conf (struct __res_state *resp, const struct resolv_conf *conf)
487 resp->defdname[0] = '\0';
488 resp->pfcode = 0;
489 resp->_vcsock = -1;
490 resp->_flags = 0;
491 resp->ipv6_unavail = false;
492 resp->__glibc_unused_qhook = NULL;
493 resp->__glibc_unused_rhook = NULL;
495 resp->retrans = conf->retrans;
496 resp->retry = conf->retry;
497 resp->options = conf->options;
498 resp->ndots = conf->ndots;
500 /* Copy the name server addresses. */
502 resp->nscount = 0;
503 resp->_u._ext.nscount = 0;
504 size_t nserv = conf->nameserver_list_size;
505 if (nserv > MAXNS)
506 nserv = MAXNS;
507 for (size_t i = 0; i < nserv; i++)
509 if (conf->nameserver_list[i]->sa_family == AF_INET)
511 resp->nsaddr_list[i]
512 = *(struct sockaddr_in *)conf->nameserver_list[i];
513 resp->_u._ext.nsaddrs[i] = NULL;
515 else
517 assert (conf->nameserver_list[i]->sa_family == AF_INET6);
518 resp->nsaddr_list[i].sin_family = 0;
519 /* Make a defensive copy of the name server address, in
520 case the application overwrites it. */
521 struct sockaddr_in6 *sa = malloc (sizeof (*sa));
522 if (sa == NULL)
524 for (size_t j = 0; j < i; ++j)
525 free (resp->_u._ext.nsaddrs[j]);
526 return false;
528 *sa = *(struct sockaddr_in6 *)conf->nameserver_list[i];
529 resp->_u._ext.nsaddrs[i] = sa;
531 resp->_u._ext.nssocks[i] = -1;
533 resp->nscount = nserv;
534 /* Leave resp->_u._ext.nscount at 0. res_send.c handles this. */
537 /* Fill in the prefix of the search list. It is truncated either at
538 MAXDNSRCH, or if reps->defdname has insufficient space. */
540 struct alloc_buffer buffer
541 = alloc_buffer_create (resp->defdname, sizeof (resp->defdname));
542 size_t size = conf->search_list_size;
543 size_t i;
544 for (i = 0; i < size && i < MAXDNSRCH; ++i)
546 resp->dnsrch[i] = alloc_buffer_copy_string
547 (&buffer, conf->search_list[i]);
548 if (resp->dnsrch[i] == NULL)
549 /* No more space in resp->defdname. Truncate. */
550 break;
552 resp->dnsrch[i] = NULL;
555 /* Copy the sort list. */
557 size_t nsort = conf->sort_list_size;
558 if (nsort > MAXRESOLVSORT)
559 nsort = MAXRESOLVSORT;
560 for (size_t i = 0; i < nsort; ++i)
562 resp->sort_list[i].addr = conf->sort_list[i].addr;
563 resp->sort_list[i].mask = conf->sort_list[i].mask;
565 resp->nsort = nsort;
568 /* The overlapping parts of both configurations should agree after
569 initialization. */
570 assert (resolv_conf_matches (resp, conf));
571 return true;
574 /* Decrement the configuration object at INDEX and free it if the
575 reference counter reaches 0. *GLOBAL_COPY must be locked and
576 remains so. */
577 static void
578 decrement_at_index (struct resolv_conf_global *global_copy, size_t index)
580 if (index < resolv_conf_array_size (&global_copy->array))
582 /* Index found. */
583 uintptr_t *slot = resolv_conf_array_at (&global_copy->array, index);
584 /* Check that the slot is not already part of the free list. */
585 if (!(*slot & 1))
587 struct resolv_conf *conf = (struct resolv_conf *) *slot;
588 conf_decrement (conf);
589 /* Put the slot onto the free list. */
590 *slot = global_copy->free_list_start;
591 global_copy->free_list_start = (index << 1) | 1;
596 bool
597 __resolv_conf_attach (struct __res_state *resp, struct resolv_conf *conf)
599 assert (conf->__refcount > 0);
601 struct resolv_conf_global *global_copy = get_locked_global ();
602 if (global_copy == NULL)
604 free (conf);
605 return false;
608 /* Try to find an unused index in the array. */
609 size_t index;
611 if (global_copy->free_list_start & 1)
613 /* Unlink from the free list. */
614 index = global_copy->free_list_start >> 1;
615 uintptr_t *slot = resolv_conf_array_at (&global_copy->array, index);
616 global_copy->free_list_start = *slot;
617 assert (global_copy->free_list_start == 0
618 || global_copy->free_list_start & 1);
619 /* Install the configuration pointer. */
620 *slot = (uintptr_t) conf;
622 else
624 size_t size = resolv_conf_array_size (&global_copy->array);
625 /* No usable index found. Increase the array size. */
626 resolv_conf_array_add (&global_copy->array, (uintptr_t) conf);
627 if (resolv_conf_array_has_failed (&global_copy->array))
629 put_locked_global (global_copy);
630 __set_errno (ENOMEM);
631 return false;
633 /* The new array element was added at the end. */
634 index = size;
638 /* We have added a new reference to the object. */
639 ++conf->__refcount;
640 assert (conf->__refcount > 0);
641 put_locked_global (global_copy);
643 if (!update_from_conf (resp, conf))
645 /* Drop the reference we acquired. Reacquire the lock. The
646 object has already been allocated, so it cannot be NULL this
647 time. */
648 global_copy = get_locked_global ();
649 decrement_at_index (global_copy, index);
650 put_locked_global (global_copy);
651 return false;
653 resp->_u._ext.__glibc_extension_index = index ^ INDEX_MAGIC;
655 return true;
658 void
659 __resolv_conf_detach (struct __res_state *resp)
661 if (atomic_load_relaxed (&global) == NULL)
662 /* Detach operation after a shutdown, or without any prior
663 attachment. We cannot free the data (and there might not be
664 anything to free anyway). */
665 return;
667 struct resolv_conf_global *global_copy = get_locked_global ();
668 size_t index = resp->_u._ext.__glibc_extension_index ^ INDEX_MAGIC;
669 decrement_at_index (global_copy, index);
671 /* Clear the index field, so that accidental reuse is less
672 likely. */
673 resp->_u._ext.__glibc_extension_index = 0;
675 put_locked_global (global_copy);
678 /* Deallocate the global data. */
679 static void __attribute__ ((section ("__libc_thread_freeres_fn")))
680 freeres (void)
682 /* No locking because this function is supposed to be called when
683 the process has turned single-threaded. */
684 if (global == NULL)
685 return;
687 if (global->conf_current != NULL)
689 conf_decrement (global->conf_current);
690 global->conf_current = NULL;
693 /* Note that this frees only the array itself. The pointed-to
694 configuration objects should have been deallocated by res_nclose
695 and per-thread cleanup functions. */
696 resolv_conf_array_free (&global->array);
698 free (global);
700 /* Stop potential future __resolv_conf_detach calls from accessing
701 deallocated memory. */
702 global = NULL;
704 text_set_element (__libc_subfreeres, freeres);