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