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
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>
40
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
46
47/* Global resolv.conf-related state. */
48struct resolv_conf_global
49{
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;
60
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;
66
67 /* Cached current configuration object for /etc/resolv.conf. */
68 struct resolv_conf *conf_current;
69
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;
76};
77
78/* Lazily allocated storage for struct resolv_conf_global. */
79static struct resolv_conf_global *global;
80
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);
84
85/* Ensure that GLOBAL is allocated and lock it. Return NULL if
86 memory allocation failes. */
87static struct resolv_conf_global *
88get_locked_global (void)
89{
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)
95 {
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);
101 }
102 return global_copy;
103}
104
105/* Relinquish the lock acquired by get_locked_global. */
106static void
107put_locked_global (struct resolv_conf_global *global_copy)
108{
109 __libc_lock_unlock (lock);
110}
111
112/* Decrement the reference counter. The caller must acquire the lock
113 around the function call. */
114static void
115conf_decrement (struct resolv_conf *conf)
116{
117 assert (conf->__refcount > 0);
118 if (--conf->__refcount == 0)
119 free (conf);
120}
121
122struct resolv_conf *
123__resolv_conf_get_current (void)
124{
125 struct stat64 st;
126 if (stat64 (_PATH_RESCONF, &st) != 0)
127 {
128 switch (errno)
129 {
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;
142 }
143 }
144
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
159 {
160 /* Parse configuration while holding the lock. This avoids
161 duplicate work. */
162 conf = __resolv_conf_load (NULL);
163 if (conf != NULL)
164 {
165 if (global_copy->conf_current != NULL)
166 conf_decrement (global_copy->conf_current);
167 global_copy->conf_current = conf; /* Takes ownership. */
168
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;
177 }
178 }
179
180 if (conf != NULL)
181 {
182 /* Return an additional reference. */
183 assert (conf->__refcount > 0);
184 ++conf->__refcount;
185 assert (conf->__refcount > 0);
186 }
187 put_locked_global (global_copy);
188 return conf;
189}
190
191/* Internal implementation of __resolv_conf_get, without validation
192 against *RESP. */
193static struct resolv_conf *
194resolv_conf_get_1 (const struct __res_state *resp)
195{
196 /* Not initialized, and therefore no assoicated context. */
197 if (!(resp->options & RES_INIT))
198 return NULL;
199
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))
208 {
209 uintptr_t *slot = resolv_conf_array_at (&global_copy->array, index);
210 if (!(*slot & 1))
211 {
212 conf = (struct resolv_conf *) *slot;
213 assert (conf->__refcount > 0);
214 ++conf->__refcount;
215 }
216 }
217 put_locked_global (global_copy);
218 return conf;
219}
220
221/* Return true if both IPv4 addresses are equal. */
222static bool
223same_address_v4 (const struct sockaddr_in *left,
224 const struct sockaddr_in *right)
225{
226 return left->sin_addr.s_addr == right->sin_addr.s_addr
227 && left->sin_port == right->sin_port;
228}
229
230/* Return true if both IPv6 addresses are equal. This ignores the
231 flow label. */
232static bool
233same_address_v6 (const struct sockaddr_in6 *left,
234 const struct sockaddr_in6 *right)
235{
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;
240}
241
242static bool
243same_address (const struct sockaddr *left, const struct sockaddr *right)
244{
245 if (left->sa_family != right->sa_family)
246 return false;
247 switch (left->sa_family)
248 {
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);
255 }
256 return false;
257}
258
259/* Check that *RESP and CONF match. Used by __resolv_conf_get. */
260static bool
261resolv_conf_matches (const struct __res_state *resp,
262 const struct resolv_conf *conf)
263{
264 /* NB: Do not compare the options, retrans, retry, ndots. These can
265 be changed by applicaiton. */
266
267 /* Check that the name servers in *RESP have not been modified by
268 the application. */
269 {
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)
278 {
279 if (resp->nsaddr_list[i].sin_family == 0)
280 {
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;
286 }
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;
292 }
293 }
294
295 /* Check that the search list in *RESP has not been modified by the
296 application. */
297 {
298 if (resp->dnsrch[0] == NULL)
299 {
300 /* Empty search list. No default domain name. */
301 return conf->search_list_size == 0 && resp->defdname[0] == '\0';
302 }
303
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;
308
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;
316
317 size_t search_list_size = 0;
318 for (size_t i = 0; i < conf->search_list_size; ++i)
319 {
320 if (resp->dnsrch[i] != NULL)
321 {
322 search_list_size += strlen (resp->dnsrch[i]) + 1;
323 if (strcmp (resp->dnsrch[i], conf->search_list[i]) != 0)
324 return false;
325 }
326 else
327 {
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;
336 }
337 }
338 }
339
340 /* Check that the sort list has not been modified. */
341 {
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;
351 }
352
353 return true;
354}
355
356struct resolv_conf *
357__resolv_conf_get (struct __res_state *resp)
358{
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;
366}
367
368void
369__resolv_conf_put (struct resolv_conf *conf)
370{
371 if (conf == NULL)
372 return;
373
374 __libc_lock_lock (lock);
375 conf_decrement (conf);
376 __libc_lock_unlock (lock);
377}
378
379struct resolv_conf *
380__resolv_conf_allocate (const struct resolv_conf *init)
381{
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");
391
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
398 {
399 assert (init->nameserver_list[i]->sa_family == AF_INET6);
400 address_space += sizeof (struct sockaddr_in6);
401 }
402
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;
407
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);
424
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;
431
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;
438
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;
443
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)
447 {
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;
452 }
453 else
454 {
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;
459 }
460
461 /* Allocate and fill the sort list array. */
462 {
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];
469 }
470
471 /* Fill the search list array. This must come last because the
472 strings are the least aligned part of the allocation. */
473 {
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]);
477 }
478
479 assert (!alloc_buffer_has_failed (&buffer));
480 return conf;
481}
482
483/* Update *RESP from the extended state. */
484static __attribute__ ((nonnull (1, 2), warn_unused_result)) bool
485update_from_conf (struct __res_state *resp, const struct resolv_conf *conf)
486{
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;
494
495 resp->retrans = conf->retrans;
496 resp->retry = conf->retry;
497 resp->options = conf->options;
498 resp->ndots = conf->ndots;
499
500 /* Copy the name server addresses. */
501 {
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++)
508 {
509 if (conf->nameserver_list[i]->sa_family == AF_INET)
510 {
511 resp->nsaddr_list[i]
512 = *(struct sockaddr_in *)conf->nameserver_list[i];
513 resp->_u._ext.nsaddrs[i] = NULL;
514 }
515 else
516 {
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)
523 {
524 for (size_t j = 0; j < i; ++j)
525 free (resp->_u._ext.nsaddrs[j]);
526 return false;
527 }
528 *sa = *(struct sockaddr_in6 *)conf->nameserver_list[i];
529 resp->_u._ext.nsaddrs[i] = sa;
530 }
531 resp->_u._ext.nssocks[i] = -1;
532 }
533 resp->nscount = nserv;
534 /* Leave resp->_u._ext.nscount at 0. res_send.c handles this. */
535 }
536
537 /* Fill in the prefix of the search list. It is truncated either at
538 MAXDNSRCH, or if reps->defdname has insufficient space. */
539 {
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)
545 {
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;
551 }
552 resp->dnsrch[i] = NULL;
553 }
554
555 /* Copy the sort list. */
556 {
557 size_t nsort = conf->sort_list_size;
558 if (nsort > MAXRESOLVSORT)
559 nsort = MAXRESOLVSORT;
560 for (size_t i = 0; i < nsort; ++i)
561 {
562 resp->sort_list[i].addr = conf->sort_list[i].addr;
563 resp->sort_list[i].mask = conf->sort_list[i].mask;
564 }
565 resp->nsort = nsort;
566 }
567
568 /* The overlapping parts of both configurations should agree after
569 initialization. */
570 assert (resolv_conf_matches (resp, conf));
571 return true;
572}
573
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. */
577static void
578decrement_at_index (struct resolv_conf_global *global_copy, size_t index)
579{
580 if (index < resolv_conf_array_size (&global_copy->array))
581 {
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))
586 {
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;
592 }
593 }
594}
595
596bool
597__resolv_conf_attach (struct __res_state *resp, struct resolv_conf *conf)
598{
599 assert (conf->__refcount > 0);
600
601 struct resolv_conf_global *global_copy = get_locked_global ();
602 if (global_copy == NULL)
603 return false;
604
605 /* Try to find an unused index in the array. */
606 size_t index;
607 {
608 if (global_copy->free_list_start & 1)
609 {
610 /* Unlink from the free list. */
611 index = global_copy->free_list_start >> 1;
612 uintptr_t *slot = resolv_conf_array_at (&global_copy->array, index);
613 global_copy->free_list_start = *slot;
614 assert (global_copy->free_list_start == 0
615 || global_copy->free_list_start & 1);
616 /* Install the configuration pointer. */
617 *slot = (uintptr_t) conf;
618 }
619 else
620 {
621 size_t size = resolv_conf_array_size (&global_copy->array);
622 /* No usable index found. Increase the array size. */
623 resolv_conf_array_add (&global_copy->array, (uintptr_t) conf);
624 if (resolv_conf_array_has_failed (&global_copy->array))
625 {
626 put_locked_global (global_copy);
627 __set_errno (ENOMEM);
628 return false;
629 }
630 /* The new array element was added at the end. */
631 index = size;
632 }
633 }
634
635 /* We have added a new reference to the object. */
636 ++conf->__refcount;
637 assert (conf->__refcount > 0);
638 put_locked_global (global_copy);
639
640 if (!update_from_conf (resp, conf))
641 {
642 /* Drop the reference we acquired. Reacquire the lock. The
643 object has already been allocated, so it cannot be NULL this
644 time. */
645 global_copy = get_locked_global ();
646 decrement_at_index (global_copy, index);
647 put_locked_global (global_copy);
648 return false;
649 }
650 resp->_u._ext.__glibc_extension_index = index ^ INDEX_MAGIC;
651
652 return true;
653}
654
655void
656__resolv_conf_detach (struct __res_state *resp)
657{
658 if (atomic_load_relaxed (&global) == NULL)
659 /* Detach operation after a shutdown, or without any prior
660 attachment. We cannot free the data (and there might not be
661 anything to free anyway). */
662 return;
663
664 struct resolv_conf_global *global_copy = get_locked_global ();
665 size_t index = resp->_u._ext.__glibc_extension_index ^ INDEX_MAGIC;
666 decrement_at_index (global_copy, index);
667
668 /* Clear the index field, so that accidental reuse is less
669 likely. */
670 resp->_u._ext.__glibc_extension_index = 0;
671
672 put_locked_global (global_copy);
673}
674
675/* Deallocate the global data. */
676static void __attribute__ ((section ("__libc_thread_freeres_fn")))
677freeres (void)
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}
701text_set_element (__libc_subfreeres, freeres);
702