1 | /* On-demand PLT fixup for shared objects. |
2 | Copyright (C) 1995-2024 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 <alloca.h> |
20 | #include <assert.h> |
21 | #include <stdlib.h> |
22 | #include <unistd.h> |
23 | #include <sys/param.h> |
24 | #include <ldsodefs.h> |
25 | #include <sysdep-cancel.h> |
26 | #include "dynamic-link.h" |
27 | #include <tls.h> |
28 | #include <dl-irel.h> |
29 | #include <dl-runtime.h> |
30 | |
31 | |
32 | /* This function is called through a special trampoline from the PLT the |
33 | first time each PLT entry is called. We must perform the relocation |
34 | specified in the PLT of the given shared object, and return the resolved |
35 | function address to the trampoline, which will restart the original call |
36 | to that address. Future calls will bounce directly from the PLT to the |
37 | function. */ |
38 | |
39 | DL_FIXUP_VALUE_TYPE |
40 | attribute_hidden __attribute ((noinline)) DL_ARCH_FIXUP_ATTRIBUTE |
41 | _dl_fixup ( |
42 | # ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS |
43 | ELF_MACHINE_RUNTIME_FIXUP_ARGS, |
44 | # endif |
45 | struct link_map *l, ElfW(Word) reloc_arg) |
46 | { |
47 | const ElfW(Sym) *const symtab |
48 | = (const void *) D_PTR (l, l_info[DT_SYMTAB]); |
49 | const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]); |
50 | |
51 | const uintptr_t pltgot = (uintptr_t) D_PTR (l, l_info[DT_PLTGOT]); |
52 | |
53 | const PLTREL *const reloc |
54 | = (const void *) (D_PTR (l, l_info[DT_JMPREL]) |
55 | + reloc_offset (plt0: pltgot, pltn: reloc_arg)); |
56 | const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)]; |
57 | const ElfW(Sym) *refsym = sym; |
58 | void *const rel_addr = (void *)(l->l_addr + reloc->r_offset); |
59 | lookup_t result; |
60 | DL_FIXUP_VALUE_TYPE value; |
61 | |
62 | /* Sanity check that we're really looking at a PLT relocation. */ |
63 | assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT); |
64 | |
65 | /* Look up the target symbol. If the normal lookup rules are not |
66 | used don't look in the global scope. */ |
67 | if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0) |
68 | { |
69 | const struct r_found_version *version = NULL; |
70 | |
71 | if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL) |
72 | { |
73 | const ElfW(Half) *vernum = |
74 | (const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]); |
75 | ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff; |
76 | version = &l->l_versions[ndx]; |
77 | if (version->hash == 0) |
78 | version = NULL; |
79 | } |
80 | |
81 | /* We need to keep the scope around so do some locking. This is |
82 | not necessary for objects which cannot be unloaded or when |
83 | we are not using any threads (yet). */ |
84 | int flags = DL_LOOKUP_ADD_DEPENDENCY; |
85 | if (!RTLD_SINGLE_THREAD_P) |
86 | { |
87 | THREAD_GSCOPE_SET_FLAG (); |
88 | flags |= DL_LOOKUP_GSCOPE_LOCK; |
89 | } |
90 | |
91 | #ifdef RTLD_ENABLE_FOREIGN_CALL |
92 | RTLD_ENABLE_FOREIGN_CALL; |
93 | #endif |
94 | |
95 | result = _dl_lookup_symbol_x (undef: strtab + sym->st_name, undef_map: l, sym: &sym, symbol_scope: l->l_scope, |
96 | version, ELF_RTYPE_CLASS_PLT, flags, NULL); |
97 | |
98 | /* We are done with the global scope. */ |
99 | if (!RTLD_SINGLE_THREAD_P) |
100 | THREAD_GSCOPE_RESET_FLAG (); |
101 | |
102 | #ifdef RTLD_FINALIZE_FOREIGN_CALL |
103 | RTLD_FINALIZE_FOREIGN_CALL; |
104 | #endif |
105 | |
106 | /* Currently result contains the base load address (or link map) |
107 | of the object that defines sym. Now add in the symbol |
108 | offset. */ |
109 | value = DL_FIXUP_MAKE_VALUE (result, |
110 | SYMBOL_ADDRESS (result, sym, false)); |
111 | } |
112 | else |
113 | { |
114 | /* We already found the symbol. The module (and therefore its load |
115 | address) is also known. */ |
116 | value = DL_FIXUP_MAKE_VALUE (l, SYMBOL_ADDRESS (l, sym, true)); |
117 | result = l; |
118 | } |
119 | |
120 | /* And now perhaps the relocation addend. */ |
121 | value = elf_machine_plt_value (map: l, reloc, value); |
122 | |
123 | if (sym != NULL |
124 | && __builtin_expect (ELFW(ST_TYPE) (sym->st_info) == STT_GNU_IFUNC, 0)) |
125 | value = elf_ifunc_invoke (DL_FIXUP_VALUE_ADDR (value)); |
126 | |
127 | #ifdef SHARED |
128 | /* Auditing checkpoint: we have a new binding. Provide the auditing |
129 | libraries the possibility to change the value and tell us whether further |
130 | auditing is wanted. |
131 | The l_reloc_result is only allocated if there is an audit module which |
132 | provides a la_symbind. */ |
133 | if (l->l_reloc_result != NULL) |
134 | { |
135 | /* This is the address in the array where we store the result of previous |
136 | relocations. */ |
137 | struct reloc_result *reloc_result |
138 | = &l->l_reloc_result[reloc_index (pltgot, reloc_arg, sizeof (PLTREL))]; |
139 | unsigned int init = atomic_load_acquire (&reloc_result->init); |
140 | if (init == 0) |
141 | { |
142 | _dl_audit_symbind (l, reloc_result, reloc, sym, &value, result, true); |
143 | |
144 | /* Store the result for later runs. */ |
145 | if (__glibc_likely (! GLRO(dl_bind_not))) |
146 | { |
147 | reloc_result->addr = value; |
148 | /* Guarantee all previous writes complete before init is |
149 | updated. See CONCURRENCY NOTES below. */ |
150 | atomic_store_release (&reloc_result->init, 1); |
151 | } |
152 | } |
153 | else |
154 | value = reloc_result->addr; |
155 | } |
156 | #endif |
157 | |
158 | /* Finally, fix up the plt itself. */ |
159 | if (__glibc_unlikely (GLRO(dl_bind_not))) |
160 | return value; |
161 | |
162 | return elf_machine_fixup_plt (map: l, t: result, refsym, sym, reloc, reloc_addr: rel_addr, value); |
163 | } |
164 | |
165 | #if !defined PROF && defined SHARED |
166 | DL_FIXUP_VALUE_TYPE |
167 | __attribute ((noinline)) |
168 | DL_ARCH_FIXUP_ATTRIBUTE |
169 | _dl_profile_fixup ( |
170 | # ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS |
171 | ELF_MACHINE_RUNTIME_FIXUP_ARGS, |
172 | # endif |
173 | struct link_map *l, ElfW(Word) reloc_arg, |
174 | ElfW(Addr) retaddr, void *regs, long int *framesizep) |
175 | { |
176 | void (*mcount_fct) (ElfW(Addr), ElfW(Addr)) = _dl_mcount; |
177 | |
178 | if (l->l_reloc_result == NULL) |
179 | { |
180 | /* BZ #14843: ELF_DYNAMIC_RELOCATE is called before l_reloc_result |
181 | is allocated. We will get here if ELF_DYNAMIC_RELOCATE calls a |
182 | resolver function to resolve an IRELATIVE relocation and that |
183 | resolver calls a function that is not yet resolved (lazy). For |
184 | example, the resolver in x86-64 libm.so calls __get_cpu_features |
185 | defined in libc.so. Skip audit and resolve the external function |
186 | in this case. */ |
187 | *framesizep = -1; |
188 | return _dl_fixup ( |
189 | # ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS |
190 | # ifndef ELF_MACHINE_RUNTIME_FIXUP_PARAMS |
191 | # error Please define ELF_MACHINE_RUNTIME_FIXUP_PARAMS. |
192 | # endif |
193 | ELF_MACHINE_RUNTIME_FIXUP_PARAMS, |
194 | # endif |
195 | l, reloc_arg); |
196 | } |
197 | |
198 | const uintptr_t pltgot = (uintptr_t) D_PTR (l, l_info[DT_PLTGOT]); |
199 | |
200 | /* This is the address in the array where we store the result of previous |
201 | relocations. */ |
202 | struct reloc_result *reloc_result |
203 | = &l->l_reloc_result[reloc_index (pltgot, reloc_arg, sizeof (PLTREL))]; |
204 | |
205 | /* CONCURRENCY NOTES: |
206 | |
207 | Multiple threads may be calling the same PLT sequence and with |
208 | LD_AUDIT enabled they will be calling into _dl_profile_fixup to |
209 | update the reloc_result with the result of the lazy resolution. |
210 | The reloc_result guard variable is reloc_init, and we use |
211 | acquire/release loads and store to it to ensure that the results of |
212 | the structure are consistent with the loaded value of the guard. |
213 | This does not fix all of the data races that occur when two or more |
214 | threads read reloc_result->reloc_init with a value of zero and read |
215 | and write to that reloc_result concurrently. The expectation is |
216 | generally that while this is a data race it works because the |
217 | threads write the same values. Until the data races are fixed |
218 | there is a potential for problems to arise from these data races. |
219 | The reloc result updates should happen in parallel but there should |
220 | be an atomic RMW which does the final update to the real result |
221 | entry (see bug 23790). |
222 | |
223 | The following code uses reloc_result->init set to 0 to indicate if it is |
224 | the first time this object is being relocated, otherwise 1 which |
225 | indicates the object has already been relocated. |
226 | |
227 | Reading/Writing from/to reloc_result->reloc_init must not happen |
228 | before previous writes to reloc_result complete as they could |
229 | end-up with an incomplete struct. */ |
230 | DL_FIXUP_VALUE_TYPE value; |
231 | unsigned int init = atomic_load_acquire (&reloc_result->init); |
232 | |
233 | if (init == 0) |
234 | { |
235 | /* This is the first time we have to relocate this object. */ |
236 | const ElfW(Sym) *const symtab |
237 | = (const void *) D_PTR (l, l_info[DT_SYMTAB]); |
238 | const char *strtab = (const char *) D_PTR (l, l_info[DT_STRTAB]); |
239 | |
240 | const uintptr_t pltgot = (uintptr_t) D_PTR (l, l_info[DT_PLTGOT]); |
241 | |
242 | const PLTREL *const reloc |
243 | = (const void *) (D_PTR (l, l_info[DT_JMPREL]) |
244 | + reloc_offset (pltgot, reloc_arg)); |
245 | const ElfW(Sym) *refsym = &symtab[ELFW(R_SYM) (reloc->r_info)]; |
246 | const ElfW(Sym) *defsym = refsym; |
247 | lookup_t result; |
248 | |
249 | /* Sanity check that we're really looking at a PLT relocation. */ |
250 | assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT); |
251 | |
252 | /* Look up the target symbol. If the symbol is marked STV_PROTECTED |
253 | don't look in the global scope. */ |
254 | if (__builtin_expect (ELFW(ST_VISIBILITY) (refsym->st_other), 0) == 0) |
255 | { |
256 | const struct r_found_version *version = NULL; |
257 | |
258 | if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL) |
259 | { |
260 | const ElfW(Half) *vernum = |
261 | (const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]); |
262 | ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff; |
263 | version = &l->l_versions[ndx]; |
264 | if (version->hash == 0) |
265 | version = NULL; |
266 | } |
267 | |
268 | /* We need to keep the scope around so do some locking. This is |
269 | not necessary for objects which cannot be unloaded or when |
270 | we are not using any threads (yet). */ |
271 | int flags = DL_LOOKUP_ADD_DEPENDENCY; |
272 | if (!RTLD_SINGLE_THREAD_P) |
273 | { |
274 | THREAD_GSCOPE_SET_FLAG (); |
275 | flags |= DL_LOOKUP_GSCOPE_LOCK; |
276 | } |
277 | |
278 | result = _dl_lookup_symbol_x (strtab + refsym->st_name, l, |
279 | &defsym, l->l_scope, version, |
280 | ELF_RTYPE_CLASS_PLT, flags, NULL); |
281 | |
282 | /* We are done with the global scope. */ |
283 | if (!RTLD_SINGLE_THREAD_P) |
284 | THREAD_GSCOPE_RESET_FLAG (); |
285 | |
286 | /* Currently result contains the base load address (or link map) |
287 | of the object that defines sym. Now add in the symbol |
288 | offset. */ |
289 | value = DL_FIXUP_MAKE_VALUE (result, |
290 | SYMBOL_ADDRESS (result, defsym, false)); |
291 | |
292 | if (defsym != NULL |
293 | && __builtin_expect (ELFW(ST_TYPE) (defsym->st_info) |
294 | == STT_GNU_IFUNC, 0)) |
295 | value = elf_ifunc_invoke (DL_FIXUP_VALUE_ADDR (value)); |
296 | } |
297 | else |
298 | { |
299 | /* We already found the symbol. The module (and therefore its load |
300 | address) is also known. */ |
301 | value = DL_FIXUP_MAKE_VALUE (l, SYMBOL_ADDRESS (l, refsym, true)); |
302 | |
303 | if (__builtin_expect (ELFW(ST_TYPE) (refsym->st_info) |
304 | == STT_GNU_IFUNC, 0)) |
305 | value = elf_ifunc_invoke (DL_FIXUP_VALUE_ADDR (value)); |
306 | |
307 | result = l; |
308 | } |
309 | /* And now perhaps the relocation addend. */ |
310 | value = elf_machine_plt_value (l, reloc, value); |
311 | |
312 | /* Auditing checkpoint: we have a new binding. Provide the |
313 | auditing libraries the possibility to change the value and |
314 | tell us whether further auditing is wanted. */ |
315 | if (defsym != NULL && GLRO(dl_naudit) > 0) |
316 | _dl_audit_symbind (l, reloc_result, reloc, defsym, &value, result, |
317 | true); |
318 | |
319 | /* Store the result for later runs. */ |
320 | if (__glibc_likely (! GLRO(dl_bind_not))) |
321 | { |
322 | reloc_result->addr = value; |
323 | /* Guarantee all previous writes complete before |
324 | init is updated. See CONCURRENCY NOTES earlier */ |
325 | atomic_store_release (&reloc_result->init, 1); |
326 | } |
327 | init = 1; |
328 | } |
329 | else |
330 | value = reloc_result->addr; |
331 | |
332 | /* By default we do not call the pltexit function. */ |
333 | long int framesize = -1; |
334 | |
335 | |
336 | /* Auditing checkpoint: report the PLT entering and allow the |
337 | auditors to change the value. */ |
338 | _dl_audit_pltenter (l, reloc_result, &value, regs, &framesize); |
339 | |
340 | /* Store the frame size information. */ |
341 | *framesizep = framesize; |
342 | |
343 | (*mcount_fct) (retaddr, DL_FIXUP_VALUE_CODE_ADDR (value)); |
344 | |
345 | return value; |
346 | } |
347 | |
348 | #endif /* !defined PROF && defined SHARED */ |
349 | |