1 | /* Support for reading /etc/ld.so.cache files written by Linux ldconfig. |
2 | Copyright (C) 1996-2023 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 <assert.h> |
20 | #include <unistd.h> |
21 | #include <ldsodefs.h> |
22 | #include <sys/mman.h> |
23 | #include <dl-cache.h> |
24 | #include <dl-procinfo.h> |
25 | #include <stdint.h> |
26 | #include <_itoa.h> |
27 | #include <dl-hwcaps.h> |
28 | #include <dl-isa-level.h> |
29 | |
30 | #ifndef _DL_PLATFORMS_COUNT |
31 | # define _DL_PLATFORMS_COUNT 0 |
32 | #endif |
33 | |
34 | /* This is the starting address and the size of the mmap()ed file. */ |
35 | static struct cache_file *cache; |
36 | static struct cache_file_new *cache_new; |
37 | static size_t cachesize; |
38 | |
39 | #ifdef SHARED |
40 | /* This is used to cache the priorities of glibc-hwcaps |
41 | subdirectories. The elements of _dl_cache_priorities correspond to |
42 | the strings in the cache_extension_tag_glibc_hwcaps section. */ |
43 | static uint32_t *glibc_hwcaps_priorities; |
44 | static uint32_t glibc_hwcaps_priorities_length; |
45 | static uint32_t glibc_hwcaps_priorities_allocated; |
46 | |
47 | /* True if the full malloc was used to allocated the array. */ |
48 | static bool glibc_hwcaps_priorities_malloced; |
49 | |
50 | /* Deallocate the glibc_hwcaps_priorities array. */ |
51 | static void |
52 | glibc_hwcaps_priorities_free (void) |
53 | { |
54 | /* When the minimal malloc is in use, free does not do anything, |
55 | so it does not make sense to call it. */ |
56 | if (glibc_hwcaps_priorities_malloced) |
57 | free (glibc_hwcaps_priorities); |
58 | glibc_hwcaps_priorities = NULL; |
59 | glibc_hwcaps_priorities_allocated = 0; |
60 | } |
61 | |
62 | /* Ordered comparison of a hwcaps string from the cache on the left |
63 | (identified by its string table index) and a _dl_hwcaps_priorities |
64 | element on the right. */ |
65 | static int |
66 | glibc_hwcaps_compare (uint32_t left_index, struct dl_hwcaps_priority *right) |
67 | { |
68 | const char *left_name = (const char *) cache + left_index; |
69 | uint32_t left_name_length = strlen (left_name); |
70 | uint32_t to_compare; |
71 | if (left_name_length < right->name_length) |
72 | to_compare = left_name_length; |
73 | else |
74 | to_compare = right->name_length; |
75 | int cmp = memcmp (left_name, right->name, to_compare); |
76 | if (cmp != 0) |
77 | return cmp; |
78 | if (left_name_length < right->name_length) |
79 | return -1; |
80 | else if (left_name_length > right->name_length) |
81 | return 1; |
82 | else |
83 | return 0; |
84 | } |
85 | |
86 | /* Initialize the glibc_hwcaps_priorities array and its length, |
87 | glibc_hwcaps_priorities_length. */ |
88 | static void |
89 | glibc_hwcaps_priorities_init (void) |
90 | { |
91 | struct cache_extension_all_loaded ext; |
92 | if (!cache_extension_load (cache_new, cache, cachesize, &ext)) |
93 | return; |
94 | |
95 | uint32_t length = (ext.sections[cache_extension_tag_glibc_hwcaps].size |
96 | / sizeof (uint32_t)); |
97 | if (length > glibc_hwcaps_priorities_allocated) |
98 | { |
99 | glibc_hwcaps_priorities_free (); |
100 | |
101 | uint32_t *new_allocation = malloc (length * sizeof (uint32_t)); |
102 | if (new_allocation == NULL) |
103 | /* This effectively disables hwcaps on memory allocation |
104 | errors. */ |
105 | return; |
106 | |
107 | glibc_hwcaps_priorities = new_allocation; |
108 | glibc_hwcaps_priorities_allocated = length; |
109 | glibc_hwcaps_priorities_malloced = __rtld_malloc_is_complete (); |
110 | } |
111 | |
112 | /* Compute the priorities for the subdirectories by merging the |
113 | array in the cache with the dl_hwcaps_priorities array. */ |
114 | const uint32_t *left = ext.sections[cache_extension_tag_glibc_hwcaps].base; |
115 | const uint32_t *left_end = left + length; |
116 | struct dl_hwcaps_priority *right = _dl_hwcaps_priorities; |
117 | struct dl_hwcaps_priority *right_end = right + _dl_hwcaps_priorities_length; |
118 | uint32_t *result = glibc_hwcaps_priorities; |
119 | |
120 | while (left < left_end && right < right_end) |
121 | { |
122 | if (*left < cachesize) |
123 | { |
124 | int cmp = glibc_hwcaps_compare (*left, right); |
125 | if (cmp == 0) |
126 | { |
127 | *result = right->priority; |
128 | ++result; |
129 | ++left; |
130 | ++right; |
131 | } |
132 | else if (cmp < 0) |
133 | { |
134 | *result = 0; |
135 | ++result; |
136 | ++left; |
137 | } |
138 | else |
139 | ++right; |
140 | } |
141 | else |
142 | { |
143 | *result = 0; |
144 | ++result; |
145 | } |
146 | } |
147 | while (left < left_end) |
148 | { |
149 | *result = 0; |
150 | ++result; |
151 | ++left; |
152 | } |
153 | |
154 | glibc_hwcaps_priorities_length = length; |
155 | } |
156 | |
157 | /* Return the priority of the cache_extension_tag_glibc_hwcaps section |
158 | entry at INDEX. Zero means do not use. Otherwise, lower values |
159 | indicate greater preference. */ |
160 | static uint32_t |
161 | glibc_hwcaps_priority (uint32_t index) |
162 | { |
163 | /* This does not need to repeated initialization attempts because |
164 | this function is only called if there is glibc-hwcaps data in the |
165 | cache, so the first call initializes the glibc_hwcaps_priorities |
166 | array. */ |
167 | if (glibc_hwcaps_priorities_length == 0) |
168 | glibc_hwcaps_priorities_init (); |
169 | |
170 | if (index < glibc_hwcaps_priorities_length) |
171 | return glibc_hwcaps_priorities[index]; |
172 | else |
173 | return 0; |
174 | } |
175 | #endif /* SHARED */ |
176 | |
177 | /* True if PTR is a valid string table index. */ |
178 | static inline bool |
179 | _dl_cache_verify_ptr (uint32_t ptr, size_t string_table_size) |
180 | { |
181 | return ptr < string_table_size; |
182 | } |
183 | |
184 | /* Compute the address of the element INDEX of the array at LIBS. |
185 | Conceptually, this is &LIBS[INDEX], but use ENTRY_SIZE for the size |
186 | of *LIBS. */ |
187 | static inline const struct file_entry * |
188 | _dl_cache_file_entry (const struct file_entry *libs, size_t entry_size, |
189 | size_t index) |
190 | { |
191 | return (const void *) libs + index * entry_size; |
192 | } |
193 | |
194 | /* We use binary search since the table is sorted in the cache file. |
195 | The first matching entry in the table is returned. It is important |
196 | to use the same algorithm as used while generating the cache file. |
197 | STRING_TABLE_SIZE indicates the maximum offset in STRING_TABLE at |
198 | which data is mapped; it is not exact. */ |
199 | static const char * |
200 | search_cache (const char *string_table, uint32_t string_table_size, |
201 | struct file_entry *libs, uint32_t nlibs, uint32_t entry_size, |
202 | const char *name) |
203 | { |
204 | /* Used by the HWCAP check in the struct file_entry_new case. */ |
205 | uint64_t platform = _dl_string_platform (GLRO (dl_platform)); |
206 | if (platform != (uint64_t) -1) |
207 | platform = 1ULL << platform; |
208 | uint64_t hwcap_mask = TUNABLE_GET (glibc, cpu, hwcap_mask, uint64_t, NULL); |
209 | #define _DL_HWCAP_TLS_MASK (1LL << 63) |
210 | uint64_t hwcap_exclude = ~((GLRO (dl_hwcap) & hwcap_mask) |
211 | | _DL_HWCAP_PLATFORM | _DL_HWCAP_TLS_MASK); |
212 | |
213 | int left = 0; |
214 | int right = nlibs - 1; |
215 | const char *best = NULL; |
216 | #ifdef SHARED |
217 | uint32_t best_priority = 0; |
218 | #endif |
219 | |
220 | while (left <= right) |
221 | { |
222 | int middle = (left + right) / 2; |
223 | uint32_t key = _dl_cache_file_entry (libs, entry_size, middle)->key; |
224 | |
225 | /* Make sure string table indices are not bogus before using |
226 | them. */ |
227 | if (!_dl_cache_verify_ptr (key, string_table_size)) |
228 | return NULL; |
229 | |
230 | /* Actually compare the entry with the key. */ |
231 | int cmpres = _dl_cache_libcmp (name, string_table + key); |
232 | if (__glibc_unlikely (cmpres == 0)) |
233 | { |
234 | /* Found it. LEFT now marks the last entry for which we |
235 | know the name is correct. */ |
236 | left = middle; |
237 | |
238 | /* There might be entries with this name before the one we |
239 | found. So we have to find the beginning. */ |
240 | while (middle > 0) |
241 | { |
242 | key = _dl_cache_file_entry (libs, entry_size, middle - 1)->key; |
243 | /* Make sure string table indices are not bogus before |
244 | using them. */ |
245 | if (!_dl_cache_verify_ptr (key, string_table_size) |
246 | /* Actually compare the entry. */ |
247 | || _dl_cache_libcmp (name, string_table + key) != 0) |
248 | break; |
249 | --middle; |
250 | } |
251 | |
252 | do |
253 | { |
254 | int flags; |
255 | const struct file_entry *lib |
256 | = _dl_cache_file_entry (libs, entry_size, middle); |
257 | |
258 | /* Only perform the name test if necessary. */ |
259 | if (middle > left |
260 | /* We haven't seen this string so far. Test whether the |
261 | index is ok and whether the name matches. Otherwise |
262 | we are done. */ |
263 | && (! _dl_cache_verify_ptr (lib->key, string_table_size) |
264 | || (_dl_cache_libcmp (name, string_table + lib->key) |
265 | != 0))) |
266 | break; |
267 | |
268 | flags = lib->flags; |
269 | if (_dl_cache_check_flags (flags) |
270 | && _dl_cache_verify_ptr (lib->value, string_table_size)) |
271 | { |
272 | /* Named/extension hwcaps get slightly different |
273 | treatment: We keep searching for a better |
274 | match. */ |
275 | bool named_hwcap = false; |
276 | |
277 | if (entry_size >= sizeof (struct file_entry_new)) |
278 | { |
279 | /* The entry is large enough to include |
280 | HWCAP data. Check it. */ |
281 | struct file_entry_new *libnew |
282 | = (struct file_entry_new *) lib; |
283 | |
284 | #ifdef SHARED |
285 | named_hwcap = dl_cache_hwcap_extension (libnew); |
286 | if (named_hwcap |
287 | && !dl_cache_hwcap_isa_level_compatible (libnew)) |
288 | continue; |
289 | #endif |
290 | |
291 | /* The entries with named/extension hwcaps have |
292 | been exhausted (they are listed before all |
293 | other entries). Return the best match |
294 | encountered so far if there is one. */ |
295 | if (!named_hwcap && best != NULL) |
296 | break; |
297 | |
298 | if ((libnew->hwcap & hwcap_exclude) && !named_hwcap) |
299 | continue; |
300 | if (_DL_PLATFORMS_COUNT |
301 | && (libnew->hwcap & _DL_HWCAP_PLATFORM) != 0 |
302 | && ((libnew->hwcap & _DL_HWCAP_PLATFORM) |
303 | != platform)) |
304 | continue; |
305 | |
306 | #ifdef SHARED |
307 | /* For named hwcaps, determine the priority and |
308 | see if beats what has been found so far. */ |
309 | if (named_hwcap) |
310 | { |
311 | uint32_t entry_priority |
312 | = glibc_hwcaps_priority (libnew->hwcap); |
313 | if (entry_priority == 0) |
314 | /* Not usable at all. Skip. */ |
315 | continue; |
316 | else if (best == NULL |
317 | || entry_priority < best_priority) |
318 | /* This entry is of higher priority |
319 | than the previous one, or it is the |
320 | first entry. */ |
321 | best_priority = entry_priority; |
322 | else |
323 | /* An entry has already been found, |
324 | but it is a better match. */ |
325 | continue; |
326 | } |
327 | #endif /* SHARED */ |
328 | } |
329 | |
330 | best = string_table + lib->value; |
331 | |
332 | if (!named_hwcap && flags == _DL_CACHE_DEFAULT_ID) |
333 | /* With named hwcaps, we need to keep searching to |
334 | see if we find a better match. A better match |
335 | is also possible if the flags of the current |
336 | entry do not match the expected cache flags. |
337 | But if the flags match, no better entry will be |
338 | found. */ |
339 | break; |
340 | } |
341 | } |
342 | while (++middle <= right); |
343 | break; |
344 | } |
345 | |
346 | if (cmpres < 0) |
347 | left = middle + 1; |
348 | else |
349 | right = middle - 1; |
350 | } |
351 | |
352 | return best; |
353 | } |
354 | |
355 | int |
356 | _dl_cache_libcmp (const char *p1, const char *p2) |
357 | { |
358 | while (*p1 != '\0') |
359 | { |
360 | if (*p1 >= '0' && *p1 <= '9') |
361 | { |
362 | if (*p2 >= '0' && *p2 <= '9') |
363 | { |
364 | /* Must compare this numerically. */ |
365 | int val1; |
366 | int val2; |
367 | |
368 | val1 = *p1++ - '0'; |
369 | val2 = *p2++ - '0'; |
370 | while (*p1 >= '0' && *p1 <= '9') |
371 | val1 = val1 * 10 + *p1++ - '0'; |
372 | while (*p2 >= '0' && *p2 <= '9') |
373 | val2 = val2 * 10 + *p2++ - '0'; |
374 | if (val1 != val2) |
375 | return val1 - val2; |
376 | } |
377 | else |
378 | return 1; |
379 | } |
380 | else if (*p2 >= '0' && *p2 <= '9') |
381 | return -1; |
382 | else if (*p1 != *p2) |
383 | return *p1 - *p2; |
384 | else |
385 | { |
386 | ++p1; |
387 | ++p2; |
388 | } |
389 | } |
390 | return *p1 - *p2; |
391 | } |
392 | |
393 | |
394 | /* Look up NAME in ld.so.cache and return the file name stored there, or null |
395 | if none is found. The cache is loaded if it was not already. If loading |
396 | the cache previously failed there will be no more attempts to load it. |
397 | The caller is responsible for freeing the returned string. The ld.so.cache |
398 | may be unmapped at any time by a completing recursive dlopen and |
399 | this function must take care that it does not return references to |
400 | any data in the mapping. */ |
401 | char * |
402 | _dl_load_cache_lookup (const char *name) |
403 | { |
404 | /* Print a message if the loading of libs is traced. */ |
405 | if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS)) |
406 | _dl_debug_printf (" search cache=%s\n" , LD_SO_CACHE); |
407 | |
408 | if (cache == NULL) |
409 | { |
410 | /* Read the contents of the file. */ |
411 | void *file = _dl_sysdep_read_whole_file (LD_SO_CACHE, &cachesize, |
412 | PROT_READ); |
413 | |
414 | /* We can handle three different cache file formats here: |
415 | - only the new format |
416 | - the old libc5/glibc2.0/2.1 format |
417 | - the old format with the new format in it |
418 | The following checks if the cache contains any of these formats. */ |
419 | if (file != MAP_FAILED && cachesize > sizeof *cache_new |
420 | && memcmp (file, CACHEMAGIC_VERSION_NEW, |
421 | sizeof CACHEMAGIC_VERSION_NEW - 1) == 0 |
422 | /* Check for corruption, avoiding overflow. */ |
423 | && ((cachesize - sizeof *cache_new) / sizeof (struct file_entry_new) |
424 | >= ((struct cache_file_new *) file)->nlibs)) |
425 | { |
426 | if (! cache_file_new_matches_endian (file)) |
427 | { |
428 | __munmap (file, cachesize); |
429 | file = (void *) -1; |
430 | } |
431 | cache_new = file; |
432 | cache = file; |
433 | } |
434 | else if (file != MAP_FAILED && cachesize > sizeof *cache |
435 | && memcmp (file, CACHEMAGIC, sizeof CACHEMAGIC - 1) == 0 |
436 | /* Check for corruption, avoiding overflow. */ |
437 | && ((cachesize - sizeof *cache) / sizeof (struct file_entry) |
438 | >= ((struct cache_file *) file)->nlibs)) |
439 | { |
440 | size_t offset; |
441 | /* Looks ok. */ |
442 | cache = file; |
443 | |
444 | /* Check for new version. */ |
445 | offset = ALIGN_CACHE (sizeof (struct cache_file) |
446 | + cache->nlibs * sizeof (struct file_entry)); |
447 | |
448 | cache_new = (struct cache_file_new *) ((void *) cache + offset); |
449 | if (cachesize < (offset + sizeof (struct cache_file_new)) |
450 | || memcmp (cache_new->magic, CACHEMAGIC_VERSION_NEW, |
451 | sizeof CACHEMAGIC_VERSION_NEW - 1) != 0) |
452 | cache_new = (void *) -1; |
453 | else |
454 | { |
455 | if (! cache_file_new_matches_endian (cache_new)) |
456 | { |
457 | /* The old-format part of the cache is bogus as well |
458 | if the endianness does not match. (But it is |
459 | unclear how the new header can be located if the |
460 | endianness does not match.) */ |
461 | cache = (void *) -1; |
462 | cache_new = (void *) -1; |
463 | __munmap (file, cachesize); |
464 | } |
465 | } |
466 | } |
467 | else |
468 | { |
469 | if (file != MAP_FAILED) |
470 | __munmap (file, cachesize); |
471 | cache = (void *) -1; |
472 | } |
473 | |
474 | assert (cache != NULL); |
475 | } |
476 | |
477 | if (cache == (void *) -1) |
478 | /* Previously looked for the cache file and didn't find it. */ |
479 | return NULL; |
480 | |
481 | const char *best; |
482 | if (cache_new != (void *) -1) |
483 | { |
484 | const char *string_table = (const char *) cache_new; |
485 | best = search_cache (string_table, cachesize, |
486 | &cache_new->libs[0].entry, cache_new->nlibs, |
487 | sizeof (cache_new->libs[0]), name); |
488 | } |
489 | else |
490 | { |
491 | const char *string_table = (const char *) &cache->libs[cache->nlibs]; |
492 | uint32_t string_table_size |
493 | = (const char *) cache + cachesize - string_table; |
494 | best = search_cache (string_table, string_table_size, |
495 | &cache->libs[0], cache->nlibs, |
496 | sizeof (cache->libs[0]), name); |
497 | } |
498 | |
499 | /* Print our result if wanted. */ |
500 | if (__builtin_expect (GLRO(dl_debug_mask) & DL_DEBUG_LIBS, 0) |
501 | && best != NULL) |
502 | _dl_debug_printf (" trying file=%s\n" , best); |
503 | |
504 | if (best == NULL) |
505 | return NULL; |
506 | |
507 | /* The double copy is *required* since malloc may be interposed |
508 | and call dlopen itself whose completion would unmap the data |
509 | we are accessing. Therefore we must make the copy of the |
510 | mapping data without using malloc. */ |
511 | char *temp; |
512 | size_t best_len = strlen (best) + 1; |
513 | temp = alloca (best_len); |
514 | memcpy (temp, best, best_len); |
515 | return __strdup (temp); |
516 | } |
517 | |
518 | #ifndef MAP_COPY |
519 | /* If the system does not support MAP_COPY we cannot leave the file open |
520 | all the time since this would create problems when the file is replaced. |
521 | Therefore we provide this function to close the file and open it again |
522 | once needed. */ |
523 | void |
524 | _dl_unload_cache (void) |
525 | { |
526 | if (cache != NULL && cache != (struct cache_file *) -1) |
527 | { |
528 | __munmap (cache, cachesize); |
529 | cache = NULL; |
530 | } |
531 | #ifdef SHARED |
532 | /* This marks the glibc_hwcaps_priorities array as out-of-date. */ |
533 | glibc_hwcaps_priorities_length = 0; |
534 | #endif |
535 | } |
536 | #endif |
537 | |