]>
Commit | Line | Data |
---|---|---|
b6ab06ce | 1 | /* Support for reading /etc/ld.so.cache files written by Linux ldconfig. |
6d7e8eda | 2 | Copyright (C) 1996-2023 Free Software Foundation, Inc. |
b6ab06ce UD |
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 | |
59ba27a6 | 16 | License along with the GNU C Library; if not, see |
5a82c748 | 17 | <https://www.gnu.org/licenses/>. */ |
b6ab06ce UD |
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> | |
e054f494 | 25 | #include <stdint.h> |
eb96ffb0 | 26 | #include <_itoa.h> |
ff08fc59 | 27 | #include <dl-hwcaps.h> |
efbbd9c3 | 28 | #include <dl-isa-level.h> |
b6ab06ce UD |
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 | ||
600d9e0c FW |
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 | ||
de1a9197 FW |
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; | |
33237fe8 | 208 | uint64_t hwcap_mask = TUNABLE_GET (glibc, cpu, hwcap_mask, uint64_t, NULL); |
de1a9197 FW |
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; | |
600d9e0c FW |
216 | #ifdef SHARED |
217 | uint32_t best_priority = 0; | |
218 | #endif | |
de1a9197 FW |
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 | { | |
66db95b6 FW |
272 | /* Named/extension hwcaps get slightly different |
273 | treatment: We keep searching for a better | |
274 | match. */ | |
275 | bool named_hwcap = false; | |
600d9e0c | 276 | |
66db95b6 FW |
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; | |
de1a9197 | 283 | |
600d9e0c | 284 | #ifdef SHARED |
66db95b6 FW |
285 | named_hwcap = dl_cache_hwcap_extension (libnew); |
286 | if (named_hwcap | |
287 | && !dl_cache_hwcap_isa_level_compatible (libnew)) | |
288 | continue; | |
600d9e0c FW |
289 | #endif |
290 | ||
66db95b6 FW |
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; | |
600d9e0c | 297 | |
66db95b6 FW |
298 | if ((libnew->hwcap & hwcap_exclude) && !named_hwcap) |
299 | continue; | |
66db95b6 FW |
300 | if (_DL_PLATFORMS_COUNT |
301 | && (libnew->hwcap & _DL_HWCAP_PLATFORM) != 0 | |
302 | && ((libnew->hwcap & _DL_HWCAP_PLATFORM) | |
303 | != platform)) | |
304 | continue; | |
600d9e0c FW |
305 | |
306 | #ifdef SHARED | |
66db95b6 FW |
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; | |
de1a9197 | 326 | } |
66db95b6 FW |
327 | #endif /* SHARED */ |
328 | } | |
de1a9197 | 329 | |
66db95b6 | 330 | best = string_table + lib->value; |
de1a9197 | 331 | |
66db95b6 FW |
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; | |
de1a9197 FW |
340 | } |
341 | } | |
342 | while (++middle <= right); | |
343 | break; | |
344 | } | |
b6ab06ce | 345 | |
de1a9197 FW |
346 | if (cmpres < 0) |
347 | left = middle + 1; | |
348 | else | |
349 | right = middle - 1; | |
350 | } | |
351 | ||
352 | return best; | |
353 | } | |
b6ab06ce UD |
354 | |
355 | int | |
b6ab06ce UD |
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 | ||
b2d3c3be CD |
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 | |
ccdb048d CD |
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 * | |
b6ab06ce UD |
402 | _dl_load_cache_lookup (const char *name) |
403 | { | |
b6ab06ce | 404 | /* Print a message if the loading of libs is traced. */ |
a1ffb40e | 405 | if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS)) |
b6ab06ce UD |
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: | |
e221c512 | 415 | - only the new format |
b6ab06ce UD |
416 | - the old libc5/glibc2.0/2.1 format |
417 | - the old format with the new format in it | |
b6ab06ce | 418 | The following checks if the cache contains any of these formats. */ |
e221c512 FW |
419 | if (file != MAP_FAILED && cachesize > sizeof *cache_new |
420 | && memcmp (file, CACHEMAGIC_VERSION_NEW, | |
421 | sizeof CACHEMAGIC_VERSION_NEW - 1) == 0 | |
2954daf0 | 422 | /* Check for corruption, avoiding overflow. */ |
e221c512 FW |
423 | && ((cachesize - sizeof *cache_new) / sizeof (struct file_entry_new) |
424 | >= ((struct cache_file_new *) file)->nlibs)) | |
425 | { | |
84ba719b FW |
426 | if (! cache_file_new_matches_endian (file)) |
427 | { | |
428 | __munmap (file, cachesize); | |
429 | file = (void *) -1; | |
430 | } | |
e221c512 FW |
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)) | |
b6ab06ce UD |
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) | |
84ba719b FW |
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 | |
630da022 | 460 | endianness does not match.) */ |
84ba719b FW |
461 | cache = (void *) -1; |
462 | cache_new = (void *) -1; | |
463 | __munmap (file, cachesize); | |
464 | } | |
465 | } | |
b6ab06ce | 466 | } |
b6ab06ce UD |
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 | ||
de1a9197 | 481 | const char *best; |
b6ab06ce UD |
482 | if (cache_new != (void *) -1) |
483 | { | |
de1a9197 FW |
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); | |
b6ab06ce UD |
488 | } |
489 | else | |
490 | { | |
de1a9197 FW |
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); | |
b6ab06ce UD |
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 | ||
ccdb048d CD |
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; | |
483cfe1a NG |
512 | size_t best_len = strlen (best) + 1; |
513 | temp = alloca (best_len); | |
514 | memcpy (temp, best, best_len); | |
ae65d4f3 | 515 | return __strdup (temp); |
b6ab06ce UD |
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 | } | |
600d9e0c FW |
531 | #ifdef SHARED |
532 | /* This marks the glibc_hwcaps_priorities array as out-of-date. */ | |
533 | glibc_hwcaps_priorities_length = 0; | |
534 | #endif | |
b6ab06ce UD |
535 | } |
536 | #endif |