1 /* -----------------------------------------------------------------------
2 tramp.c - Copyright (c) 2020 Madhavan T. Venkataraman
3
4 API and support functions for managing statically defined closure
5 trampolines.
6
7 Permission is hereby granted, free of charge, to any person obtaining
8 a copy of this software and associated documentation files (the
9 ``Software''), to deal in the Software without restriction, including
10 without limitation the rights to use, copy, modify, merge, publish,
11 distribute, sublicense, and/or sell copies of the Software, and to
12 permit persons to whom the Software is furnished to do so, subject to
13 the following conditions:
14
15 The above copyright notice and this permission notice shall be included
16 in all copies or substantial portions of the Software.
17
18 THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND,
19 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 DEALINGS IN THE SOFTWARE.
26 ----------------------------------------------------------------------- */
27
28 #include <fficonfig.h>
29
30 #ifdef FFI_EXEC_STATIC_TRAMP
31
32 /* -------------------------- Headers and Definitions ---------------------*/
33 /*
34 * Add support for other OSes later. For now, it is just Linux.
35 */
36
37 #if defined __linux__
38 #ifdef __linux__
39 #define _GNU_SOURCE 1
40 #endif
41 #include <stdio.h>
42 #include <unistd.h>
43 #include <stdlib.h>
44 #include <stdint.h>
45 #include <fcntl.h>
46 #include <pthread.h>
47 #include <sys/mman.h>
48 #include <tramp.h>
49 #ifdef __linux__
50 #include <linux/limits.h>
51 #include <linux/types.h>
52 #endif
53 #endif /* __linux__ */
54
55 /*
56 * Each architecture defines static code for a trampoline code table. The
57 * trampoline code table is mapped into the address space of a process.
58 *
59 * The following architecture specific function returns:
60 *
61 * - the address of the trampoline code table in the text segment
62 * - the size of each trampoline in the trampoline code table
63 * - the size of the mapping for the whole trampoline code table
64 */
65 void __attribute__((weak)) *ffi_tramp_arch (size_t *tramp_size,
66 size_t *map_size);
67
68 /* ------------------------- Trampoline Data Structures --------------------*/
69
70 struct tramp;
71
72 /*
73 * Trampoline table. Manages one trampoline code table and one trampoline
74 * parameter table.
75 *
76 * prev, next Links in the global trampoline table list.
77 * code_table Trampoline code table mapping.
78 * parm_table Trampoline parameter table mapping.
79 * array Array of trampolines malloced.
80 * free List of free trampolines.
81 * nfree Number of free trampolines.
82 */
83 struct tramp_table
84 {
85 struct tramp_table *prev;
86 struct tramp_table *next;
87 void *code_table;
88 void *parm_table;
89 struct tramp *array;
90 struct tramp *free;
91 int nfree;
92 };
93
94 /*
95 * Parameters for each trampoline.
96 *
97 * data
98 * Data for the target code that the trampoline jumps to.
99 * target
100 * Target code that the trampoline jumps to.
101 */
102 struct tramp_parm
103 {
104 void *data;
105 void *target;
106 };
107
108 /*
109 * Trampoline structure for each trampoline.
110 *
111 * prev, next Links in the trampoline free list of a trampoline table.
112 * table Trampoline table to which this trampoline belongs.
113 * code Address of this trampoline in the code table mapping.
114 * parm Address of this trampoline's parameters in the parameter
115 * table mapping.
116 */
117 struct tramp
118 {
119 struct tramp *prev;
120 struct tramp *next;
121 struct tramp_table *table;
122 void *code;
123 struct tramp_parm *parm;
124 };
125
126 enum tramp_globals_status {
127 TRAMP_GLOBALS_UNINITIALIZED = 0,
128 TRAMP_GLOBALS_PASSED,
129 TRAMP_GLOBALS_FAILED,
130 };
131
132 /*
133 * Trampoline globals.
134 *
135 * fd
136 * File descriptor of binary file that contains the trampoline code table.
137 * offset
138 * Offset of the trampoline code table in that file.
139 * text
140 * Address of the trampoline code table in the text segment.
141 * map_size
142 * Size of the trampoline code table mapping.
143 * size
144 * Size of one trampoline in the trampoline code table.
145 * ntramp
146 * Total number of trampolines in the trampoline code table.
147 * free_tables
148 * List of trampoline tables that contain free trampolines.
149 * nfree_tables
150 * Number of trampoline tables that contain free trampolines.
151 * status
152 * Initialization status.
153 */
154 struct tramp_globals
155 {
156 int fd;
157 off_t offset;
158 void *text;
159 size_t map_size;
160 size_t size;
161 int ntramp;
162 struct tramp_table *free_tables;
163 int nfree_tables;
164 enum tramp_globals_status status;
165 };
166
167 static struct tramp_globals tramp_globals;
168
169 /* --------------------- Trampoline File Initialization --------------------*/
170
171 /*
172 * The trampoline file is the file used to map the trampoline code table into
173 * the address space of a process. There are two ways to get this file:
174 *
175 * - From the OS. E.g., on Linux, /proc/<pid>/maps lists all the memory
176 * mappings for <pid>. For file-backed mappings, maps supplies the file name
177 * and the file offset. Using this, we can locate the mapping that maps
178 * libffi and get the path to the libffi binary. And, we can compute the
179 * offset of the trampoline code table within that binary.
180 *
181 * - Else, if we can create a temporary file, we can write the trampoline code
182 * table from the text segment into the temporary file.
183 *
184 * The first method is the preferred one. If the OS security subsystem
185 * disallows mapping unsigned files with PROT_EXEC, then the second method
186 * will fail.
187 *
188 * If an OS allows the trampoline code table in the text segment to be
189 * directly remapped (e.g., MACH vm_remap ()), then we don't need the
190 * trampoline file.
191 */
192 static int tramp_table_alloc (void);
193
194 #if defined __linux__
195
196 static int
197 ffi_tramp_get_libffi (void)
198 {
199 FILE *fp;
200 char file[PATH_MAX], line[PATH_MAX+100], perm[10], dev[10];
201 unsigned long start, end, offset, inode;
202 uintptr_t addr = (uintptr_t) tramp_globals.text;
203 int nfields, found;
204
205 snprintf (file, PATH_MAX, "/proc/%d/maps", getpid());
206 fp = fopen (file, "r");
207 if (fp == NULL)
208 return 0;
209
210 found = 0;
211 while (feof (fp) == 0) {
212 if (fgets (line, sizeof (line), fp) == 0)
213 break;
214
215 nfields = sscanf (line, "%lx-%lx %9s %lx %9s %ld %s",
216 &start, &end, perm, &offset, dev, &inode, file);
217 if (nfields != 7)
218 continue;
219
220 if (addr >= start && addr < end) {
221 tramp_globals.offset = offset + (addr - start);
222 found = 1;
223 break;
224 }
225 }
226 fclose (fp);
227
228 if (!found)
229 return 0;
230
231 tramp_globals.fd = open (file, O_RDONLY);
232 if (tramp_globals.fd == -1)
233 return 0;
234
235 /*
236 * Allocate a trampoline table just to make sure that the trampoline code
237 * table can be mapped.
238 */
239 if (!tramp_table_alloc ())
240 {
241 close (tramp_globals.fd);
242 tramp_globals.fd = -1;
243 return 0;
244 }
245 return 1;
246 }
247
248 #endif /* __linux__ */
249
250 #if defined __linux__
251
252 #if defined HAVE_MKSTEMP
253
254 static int
255 ffi_tramp_get_temp_file (void)
256 {
257 char template[12] = "/tmp/XXXXXX";
258 ssize_t count;
259
260 tramp_globals.offset = 0;
261 tramp_globals.fd = mkstemp (template);
262 if (tramp_globals.fd == -1)
263 return 0;
264
265 unlink (template);
266 /*
267 * Write the trampoline code table into the temporary file and allocate a
268 * trampoline table to make sure that the temporary file can be mapped.
269 */
270 count = write(tramp_globals.fd, tramp_globals.text, tramp_globals.map_size);
271 if (count == tramp_globals.map_size && tramp_table_alloc ())
272 return 1;
273
274 close (tramp_globals.fd);
275 tramp_globals.fd = -1;
276 return 0;
277 }
278
279 #else /* !defined HAVE_MKSTEMP */
280
281 /*
282 * TODO:
283 * src/closures.c contains code for finding temp file that has EXEC
284 * permissions. May be, some of that code can be shared with static
285 * trampolines.
286 */
287 static int
288 ffi_tramp_get_temp_file (void)
289 {
290 tramp_globals.offset = 0;
291 tramp_globals.fd = -1;
292 return 0;
293 }
294
295 #endif /* defined HAVE_MKSTEMP */
296
297 #endif /* __linux__ */
298
299 /* ------------------------ OS-specific Initialization ----------------------*/
300
301 #if defined __linux__
302
303 static int
304 ffi_tramp_init_os (void)
305 {
306 if (ffi_tramp_get_libffi ())
307 return 1;
308 return ffi_tramp_get_temp_file ();
309 }
310
311 #endif /* __linux__ */
312
313 /* --------------------------- OS-specific Locking -------------------------*/
314
315 #if defined __linux__
316
317 static pthread_mutex_t tramp_globals_mutex = PTHREAD_MUTEX_INITIALIZER;
318
319 static void
320 ffi_tramp_lock(void)
321 {
322 pthread_mutex_lock (&tramp_globals_mutex);
323 }
324
325 static void
326 ffi_tramp_unlock()
327 {
328 pthread_mutex_unlock (&tramp_globals_mutex);
329 }
330
331 #endif /* __linux__ */
332
333 /* ------------------------ OS-specific Memory Mapping ----------------------*/
334
335 /*
336 * Create a trampoline code table mapping and a trampoline parameter table
337 * mapping. The two mappings must be adjacent to each other for PC-relative
338 * access.
339 *
340 * For each trampoline in the code table, there is a corresponding parameter
341 * block in the parameter table. The size of the parameter block is the same
342 * as the size of the trampoline. This means that the parameter block is at
343 * a fixed offset from its trampoline making it easy for a trampoline to find
344 * its parameters using PC-relative access.
345 *
346 * The parameter block will contain a struct tramp_parm. This means that
347 * sizeof (struct tramp_parm) cannot exceed the size of a parameter block.
348 */
349
350 #if defined __linux__
351
352 static int
353 tramp_table_map (struct tramp_table *table)
354 {
355 char *addr;
356
357 /*
358 * Create an anonymous mapping twice the map size. The top half will be used
359 * for the code table. The bottom half will be used for the parameter table.
360 */
361 addr = mmap (NULL, tramp_globals.map_size * 2, PROT_READ | PROT_WRITE,
362 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
363 if (addr == MAP_FAILED)
364 return 0;
365
366 /*
367 * Replace the top half of the anonymous mapping with the code table mapping.
368 */
369 table->code_table = mmap (addr, tramp_globals.map_size, PROT_READ | PROT_EXEC,
370 MAP_PRIVATE | MAP_FIXED, tramp_globals.fd, tramp_globals.offset);
371 if (table->code_table == MAP_FAILED)
372 {
373 (void) munmap (addr, tramp_globals.map_size * 2);
374 return 0;
375 }
376 table->parm_table = table->code_table + tramp_globals.map_size;
377 return 1;
378 }
379
380 static void
381 tramp_table_unmap (struct tramp_table *table)
382 {
383 (void) munmap (table->code_table, tramp_globals.map_size);
384 (void) munmap (table->parm_table, tramp_globals.map_size);
385 }
386
387 #endif /* __linux__ */
388
389 /* ------------------------ Trampoline Initialization ----------------------*/
390
391 /*
392 * Initialize the static trampoline feature.
393 */
394 static int
395 ffi_tramp_init (void)
396 {
397 if (tramp_globals.status == TRAMP_GLOBALS_PASSED)
398 return 1;
399
400 if (tramp_globals.status == TRAMP_GLOBALS_FAILED)
401 return 0;
402
403 if (ffi_tramp_arch == NULL)
404 {
405 tramp_globals.status = TRAMP_GLOBALS_FAILED;
406 return 0;
407 }
408
409 tramp_globals.free_tables = NULL;
410 tramp_globals.nfree_tables = 0;
411
412 /*
413 * Get trampoline code table information from the architecture.
414 */
415 tramp_globals.text = ffi_tramp_arch (&tramp_globals.size,
416 &tramp_globals.map_size);
417 tramp_globals.ntramp = tramp_globals.map_size / tramp_globals.size;
418
419 if (sysconf (_SC_PAGESIZE) > tramp_globals.map_size)
420 return 0;
421
422 if (ffi_tramp_init_os ())
423 {
424 tramp_globals.status = TRAMP_GLOBALS_PASSED;
425 return 1;
426 }
427
428 tramp_globals.status = TRAMP_GLOBALS_FAILED;
429 return 0;
430 }
431
432 /* ---------------------- Trampoline Table functions ---------------------- */
433
434 /* This code assumes that malloc () is available on all OSes. */
435
436 static void tramp_add (struct tramp *tramp);
437
438 /*
439 * Allocate and initialize a trampoline table.
440 */
441 static int
442 tramp_table_alloc (void)
443 {
444 struct tramp_table *table;
445 struct tramp *tramp_array, *tramp;
446 size_t size;
447 char *code, *parm;
448 int i;
449
450 /*
451 * If we already have tables with free trampolines, there is no need to
452 * allocate a new table.
453 */
454 if (tramp_globals.nfree_tables > 0)
455 return 1;
456
457 /*
458 * Allocate a new trampoline table structure.
459 */
460 table = malloc (sizeof (*table));
461 if (table == NULL)
462 return 0;
463
464 /*
465 * Allocate new trampoline structures.
466 */
467 tramp_array = malloc (sizeof (*tramp) * tramp_globals.ntramp);
468 if (tramp_array == NULL)
469 goto free_table;
470
471 /*
472 * Map a code table and a parameter table into the caller's address space.
473 */
474 if (!tramp_table_map (table))
475 {
476 /*
477 * Failed to map the code and parameter tables.
478 */
479 goto free_tramp_array;
480 }
481
482 /*
483 * Initialize the trampoline table.
484 */
485 table->array = tramp_array;
486 table->free = NULL;
487 table->nfree = 0;
488
489 /*
490 * Populate the trampoline table free list. This will also add the trampoline
491 * table to the global list of trampoline tables.
492 */
493 size = tramp_globals.size;
494 code = table->code_table;
495 parm = table->parm_table;
496 for (i = 0; i < tramp_globals.ntramp; i++)
497 {
498 tramp = &tramp_array[i];
499 tramp->table = table;
500 tramp->code = code;
501 tramp->parm = (struct tramp_parm *) parm;
502 tramp_add (tramp);
503
504 code += size;
505 parm += size;
506 }
507 /* Success */
508 return 1;
509
510 /* Failure */
511 free_tramp_array:
512 free (tramp_array);
513 free_table:
514 free (table);
515 return 0;
516 }
517
518 /*
519 * Free a trampoline table.
520 */
521 static void
522 tramp_table_free (struct tramp_table *table)
523 {
524 tramp_table_unmap (table);
525 free (table->array);
526 free (table);
527 }
528
529 /*
530 * Add a new trampoline table to the global table list.
531 */
532 static void
533 tramp_table_add (struct tramp_table *table)
534 {
535 table->next = tramp_globals.free_tables;
536 table->prev = NULL;
537 if (tramp_globals.free_tables != NULL)
538 tramp_globals.free_tables->prev = table;
539 tramp_globals.free_tables = table;
540 tramp_globals.nfree_tables++;
541 }
542
543 /*
544 * Delete a trampoline table from the global table list.
545 */
546 static void
547 tramp_table_del (struct tramp_table *table)
548 {
549 tramp_globals.nfree_tables--;
550 if (table->prev != NULL)
551 table->prev->next = table->next;
552 if (table->next != NULL)
553 table->next->prev = table->prev;
554 if (tramp_globals.free_tables == table)
555 tramp_globals.free_tables = table->next;
556 }
557
558 /* ------------------------- Trampoline functions ------------------------- */
559
560 /*
561 * Add a trampoline to its trampoline table.
562 */
563 static void
564 tramp_add (struct tramp *tramp)
565 {
566 struct tramp_table *table = tramp->table;
567
568 tramp->next = table->free;
569 tramp->prev = NULL;
570 if (table->free != NULL)
571 table->free->prev = tramp;
572 table->free = tramp;
573 table->nfree++;
574
575 if (table->nfree == 1)
576 tramp_table_add (table);
577
578 /*
579 * We don't want to keep too many free trampoline tables lying around.
580 */
581 if (table->nfree == tramp_globals.ntramp &&
582 tramp_globals.nfree_tables > 1)
583 {
584 tramp_table_del (table);
585 tramp_table_free (table);
586 }
587 }
588
589 /*
590 * Remove a trampoline from its trampoline table.
591 */
592 static void
593 tramp_del (struct tramp *tramp)
594 {
595 struct tramp_table *table = tramp->table;
596
597 table->nfree--;
598 if (tramp->prev != NULL)
599 tramp->prev->next = tramp->next;
600 if (tramp->next != NULL)
601 tramp->next->prev = tramp->prev;
602 if (table->free == tramp)
603 table->free = tramp->next;
604
605 if (table->nfree == 0)
606 tramp_table_del (table);
607 }
608
609 /* ------------------------ Trampoline API functions ------------------------ */
610
611 int
612 ffi_tramp_is_supported(void)
613 {
614 int ret;
615
616 ffi_tramp_lock();
617 ret = ffi_tramp_init ();
618 ffi_tramp_unlock();
619 return ret;
620 }
621
622 /*
623 * Allocate a trampoline and return its opaque address.
624 */
625 void *
626 ffi_tramp_alloc (int flags)
627 {
628 struct tramp *tramp;
629
630 ffi_tramp_lock();
631
632 if (!ffi_tramp_init () || flags != 0)
633 {
634 ffi_tramp_unlock();
635 return NULL;
636 }
637
638 if (!tramp_table_alloc ())
639 {
640 ffi_tramp_unlock();
641 return NULL;
642 }
643
644 tramp = tramp_globals.free_tables->free;
645 tramp_del (tramp);
646
647 ffi_tramp_unlock();
648
649 return tramp;
650 }
651
652 /*
653 * Set the parameters for a trampoline.
654 */
655 void
656 ffi_tramp_set_parms (void *arg, void *target, void *data)
657 {
658 struct tramp *tramp = arg;
659
660 ffi_tramp_lock();
661 tramp->parm->target = target;
662 tramp->parm->data = data;
663 ffi_tramp_unlock();
664 }
665
666 /*
667 * Get the invocation address of a trampoline.
668 */
669 void *
670 ffi_tramp_get_addr (void *arg)
671 {
672 struct tramp *tramp = arg;
673 void *addr;
674
675 ffi_tramp_lock();
676 addr = tramp->code;
677 ffi_tramp_unlock();
678
679 return addr;
680 }
681
682 /*
683 * Free a trampoline.
684 */
685 void
686 ffi_tramp_free (void *arg)
687 {
688 struct tramp *tramp = arg;
689
690 ffi_tramp_lock();
691 tramp_add (tramp);
692 ffi_tramp_unlock();
693 }
694
695 /* ------------------------------------------------------------------------- */
696
697 #else /* !FFI_EXEC_STATIC_TRAMP */
698
699 #include <stddef.h>
700
701 int
702 ffi_tramp_is_supported(void)
703 {
704 return 0;
705 }
706
707 void *
708 ffi_tramp_alloc (int flags)
709 {
710 return NULL;
711 }
712
713 void
714 ffi_tramp_set_parms (void *arg, void *target, void *data)
715 {
716 }
717
718 void *
719 ffi_tramp_get_addr (void *arg)
720 {
721 return NULL;
722 }
723
724 void
725 ffi_tramp_free (void *arg)
726 {
727 }
728
729 #endif /* FFI_EXEC_STATIC_TRAMP */