• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* -----------------------------------------------------------------------
2    closures.c - Copyright (c) 2007  Red Hat, Inc.
3    Copyright (C) 2007 Free Software Foundation, Inc
4 
5    Code to allocate and deallocate memory for closures.
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 #if defined __linux__ && !defined _GNU_SOURCE
29 #define _GNU_SOURCE 1
30 #endif
31 
32 #include <ffi.h>
33 #include <ffi_common.h>
34 
35 #ifndef FFI_MMAP_EXEC_WRIT
36 # if __gnu_linux__
37 /* This macro indicates it may be forbidden to map anonymous memory
38    with both write and execute permission.  Code compiled when this
39    option is defined will attempt to map such pages once, but if it
40    fails, it falls back to creating a temporary file in a writable and
41    executable filesystem and mapping pages from it into separate
42    locations in the virtual memory space, one location writable and
43    another executable.  */
44 #  define FFI_MMAP_EXEC_WRIT 1
45 # endif
46 #endif
47 
48 #if FFI_MMAP_EXEC_WRIT && !defined FFI_MMAP_EXEC_SELINUX
49 # ifdef __linux__
50 /* When defined to 1 check for SELinux and if SELinux is active,
51    don't attempt PROT_EXEC|PROT_WRITE mapping at all, as that
52    might cause audit messages.  */
53 #  define FFI_MMAP_EXEC_SELINUX 1
54 # endif
55 #endif
56 
57 #if FFI_CLOSURES
58 
59 # if FFI_MMAP_EXEC_WRIT
60 
61 #define USE_LOCKS 1
62 #define USE_DL_PREFIX 1
63 #define USE_BUILTIN_FFS 1
64 
65 /* We need to use mmap, not sbrk.  */
66 #define HAVE_MORECORE 0
67 
68 /* We could, in theory, support mremap, but it wouldn't buy us anything.  */
69 #define HAVE_MREMAP 0
70 
71 /* We have no use for this, so save some code and data.  */
72 #define NO_MALLINFO 1
73 
74 /* We need all allocations to be in regular segments, otherwise we
75    lose track of the corresponding code address.  */
76 #define DEFAULT_MMAP_THRESHOLD MAX_SIZE_T
77 
78 /* Don't allocate more than a page unless needed.  */
79 #define DEFAULT_GRANULARITY ((size_t)malloc_getpagesize)
80 
81 #if FFI_CLOSURE_TEST
82 /* Don't release single pages, to avoid a worst-case scenario of
83    continuously allocating and releasing single pages, but release
84    pairs of pages, which should do just as well given that allocations
85    are likely to be small.  */
86 #define DEFAULT_TRIM_THRESHOLD ((size_t)malloc_getpagesize)
87 #endif
88 
89 #include <sys/types.h>
90 #include <sys/stat.h>
91 #include <fcntl.h>
92 #include <errno.h>
93 #include <unistd.h>
94 #include <string.h>
95 #include <stdio.h>
96 #include <mntent.h>
97 #include <sys/param.h>
98 #include <pthread.h>
99 
100 /* We don't want sys/mman.h to be included after we redefine mmap and
101    dlmunmap.  */
102 #include <sys/mman.h>
103 #define LACKS_SYS_MMAN_H 1
104 
105 #if FFI_MMAP_EXEC_SELINUX
106 #include <sys/statfs.h>
107 #include <stdlib.h>
108 
109 static int selinux_enabled = -1;
110 
111 static int
selinux_enabled_check(void)112 selinux_enabled_check (void)
113 {
114   struct statfs sfs;
115   FILE *f;
116   char *buf = NULL;
117   size_t len = 0;
118 
119   if (statfs ("/selinux", &sfs) >= 0
120       && (unsigned int) sfs.f_type == 0xf97cff8cU)
121     return 1;
122   f = fopen ("/proc/mounts", "r");
123   if (f == NULL)
124     return 0;
125   while (getline (&buf, &len, f) >= 0)
126     {
127       char *p = strchr (buf, ' ');
128       if (p == NULL)
129         break;
130       p = strchr (p + 1, ' ');
131       if (p == NULL)
132         break;
133       if (strncmp (p + 1, "selinuxfs ", 10) != 0)
134         {
135           free (buf);
136           fclose (f);
137           return 1;
138         }
139     }
140   free (buf);
141   fclose (f);
142   return 0;
143 }
144 
145 #define is_selinux_enabled() (selinux_enabled >= 0 ? selinux_enabled \
146 			      : (selinux_enabled = selinux_enabled_check ()))
147 
148 #else
149 
150 #define is_selinux_enabled() 0
151 
152 #endif
153 
154 /* Declare all functions defined in dlmalloc.c as static.  */
155 static void *dlmalloc(size_t);
156 static void dlfree(void*);
157 static void *dlcalloc(size_t, size_t) MAYBE_UNUSED;
158 static void *dlrealloc(void *, size_t) MAYBE_UNUSED;
159 static void *dlmemalign(size_t, size_t) MAYBE_UNUSED;
160 static void *dlvalloc(size_t) MAYBE_UNUSED;
161 static int dlmallopt(int, int) MAYBE_UNUSED;
162 static size_t dlmalloc_footprint(void) MAYBE_UNUSED;
163 static size_t dlmalloc_max_footprint(void) MAYBE_UNUSED;
164 static void** dlindependent_calloc(size_t, size_t, void**) MAYBE_UNUSED;
165 static void** dlindependent_comalloc(size_t, size_t*, void**) MAYBE_UNUSED;
166 static void *dlpvalloc(size_t) MAYBE_UNUSED;
167 static int dlmalloc_trim(size_t) MAYBE_UNUSED;
168 static size_t dlmalloc_usable_size(void*) MAYBE_UNUSED;
169 static void dlmalloc_stats(void) MAYBE_UNUSED;
170 
171 /* Use these for mmap and munmap within dlmalloc.c.  */
172 static void *dlmmap(void *, size_t, int, int, int, off_t);
173 static int dlmunmap(void *, size_t);
174 
175 #define mmap dlmmap
176 #define munmap dlmunmap
177 
178 #include "dlmalloc.c"
179 
180 #undef mmap
181 #undef munmap
182 
183 /* A mutex used to synchronize access to *exec* variables in this file.  */
184 static pthread_mutex_t open_temp_exec_file_mutex = PTHREAD_MUTEX_INITIALIZER;
185 
186 /* A file descriptor of a temporary file from which we'll map
187    executable pages.  */
188 static int execfd = -1;
189 
190 /* The amount of space already allocated from the temporary file.  */
191 static size_t execsize = 0;
192 
193 /* Open a temporary file name, and immediately unlink it.  */
194 static int
open_temp_exec_file_name(char * name)195 open_temp_exec_file_name (char *name)
196 {
197   int fd = mkstemp (name);
198 
199   if (fd != -1)
200     unlink (name);
201 
202   return fd;
203 }
204 
205 /* Open a temporary file in the named directory.  */
206 static int
open_temp_exec_file_dir(const char * dir)207 open_temp_exec_file_dir (const char *dir)
208 {
209   static const char suffix[] = "/ffiXXXXXX";
210   int lendir = strlen (dir);
211   char *tempname = __builtin_alloca (lendir + sizeof (suffix));
212 
213   if (!tempname)
214     return -1;
215 
216   memcpy (tempname, dir, lendir);
217   memcpy (tempname + lendir, suffix, sizeof (suffix));
218 
219   return open_temp_exec_file_name (tempname);
220 }
221 
222 /* Open a temporary file in the directory in the named environment
223    variable.  */
224 static int
open_temp_exec_file_env(const char * envvar)225 open_temp_exec_file_env (const char *envvar)
226 {
227   const char *value = getenv (envvar);
228 
229   if (!value)
230     return -1;
231 
232   return open_temp_exec_file_dir (value);
233 }
234 
235 /* Open a temporary file in an executable and writable mount point
236    listed in the mounts file.  Subsequent calls with the same mounts
237    keep searching for mount points in the same file.  Providing NULL
238    as the mounts file closes the file.  */
239 static int
open_temp_exec_file_mnt(const char * mounts)240 open_temp_exec_file_mnt (const char *mounts)
241 {
242   static const char *last_mounts;
243   static FILE *last_mntent;
244 
245   if (mounts != last_mounts)
246     {
247       if (last_mntent)
248 	endmntent (last_mntent);
249 
250       last_mounts = mounts;
251 
252       if (mounts)
253 	last_mntent = setmntent (mounts, "r");
254       else
255 	last_mntent = NULL;
256     }
257 
258   if (!last_mntent)
259     return -1;
260 
261   for (;;)
262     {
263       int fd;
264       struct mntent mnt;
265       char buf[MAXPATHLEN * 3];
266 
267       if (getmntent_r (last_mntent, &mnt, buf, sizeof (buf)))
268 	return -1;
269 
270       if (hasmntopt (&mnt, "ro")
271 	  || hasmntopt (&mnt, "noexec")
272 	  || access (mnt.mnt_dir, W_OK))
273 	continue;
274 
275       fd = open_temp_exec_file_dir (mnt.mnt_dir);
276 
277       if (fd != -1)
278 	return fd;
279     }
280 }
281 
282 /* Instructions to look for a location to hold a temporary file that
283    can be mapped in for execution.  */
284 static struct
285 {
286   int (*func)(const char *);
287   const char *arg;
288   int repeat;
289 } open_temp_exec_file_opts[] = {
290   { open_temp_exec_file_env, "TMPDIR", 0 },
291   { open_temp_exec_file_dir, "/tmp", 0 },
292   { open_temp_exec_file_dir, "/var/tmp", 0 },
293   { open_temp_exec_file_dir, "/dev/shm", 0 },
294   { open_temp_exec_file_env, "HOME", 0 },
295   { open_temp_exec_file_mnt, "/etc/mtab", 1 },
296   { open_temp_exec_file_mnt, "/proc/mounts", 1 },
297 };
298 
299 /* Current index into open_temp_exec_file_opts.  */
300 static int open_temp_exec_file_opts_idx = 0;
301 
302 /* Reset a current multi-call func, then advances to the next entry.
303    If we're at the last, go back to the first and return nonzero,
304    otherwise return zero.  */
305 static int
open_temp_exec_file_opts_next(void)306 open_temp_exec_file_opts_next (void)
307 {
308   if (open_temp_exec_file_opts[open_temp_exec_file_opts_idx].repeat)
309     open_temp_exec_file_opts[open_temp_exec_file_opts_idx].func (NULL);
310 
311   open_temp_exec_file_opts_idx++;
312   if (open_temp_exec_file_opts_idx
313       == (sizeof (open_temp_exec_file_opts)
314 	  / sizeof (*open_temp_exec_file_opts)))
315     {
316       open_temp_exec_file_opts_idx = 0;
317       return 1;
318     }
319 
320   return 0;
321 }
322 
323 /* Return a file descriptor of a temporary zero-sized file in a
324    writable and exexutable filesystem.  */
325 static int
open_temp_exec_file(void)326 open_temp_exec_file (void)
327 {
328   int fd;
329 
330   do
331     {
332       fd = open_temp_exec_file_opts[open_temp_exec_file_opts_idx].func
333 	(open_temp_exec_file_opts[open_temp_exec_file_opts_idx].arg);
334 
335       if (!open_temp_exec_file_opts[open_temp_exec_file_opts_idx].repeat
336 	  || fd == -1)
337 	{
338 	  if (open_temp_exec_file_opts_next ())
339 	    break;
340 	}
341     }
342   while (fd == -1);
343 
344   return fd;
345 }
346 
347 /* Map in a chunk of memory from the temporary exec file into separate
348    locations in the virtual memory address space, one writable and one
349    executable.  Returns the address of the writable portion, after
350    storing an offset to the corresponding executable portion at the
351    last word of the requested chunk.  */
352 static void *
dlmmap_locked(void * start,size_t length,int prot,int flags,off_t offset)353 dlmmap_locked (void *start, size_t length, int prot, int flags, off_t offset)
354 {
355   void *ptr;
356 
357   if (execfd == -1)
358     {
359       open_temp_exec_file_opts_idx = 0;
360     retry_open:
361       execfd = open_temp_exec_file ();
362       if (execfd == -1)
363 	return MFAIL;
364     }
365 
366   offset = execsize;
367 
368   if (ftruncate (execfd, offset + length))
369     return MFAIL;
370 
371   flags &= ~(MAP_PRIVATE | MAP_ANONYMOUS);
372   flags |= MAP_SHARED;
373 
374   ptr = mmap (NULL, length, (prot & ~PROT_WRITE) | PROT_EXEC,
375 	      flags, execfd, offset);
376   if (ptr == MFAIL)
377     {
378       if (!offset)
379 	{
380 	  close (execfd);
381 	  goto retry_open;
382 	}
383       ftruncate (execfd, offset);
384       return MFAIL;
385     }
386   else if (!offset
387 	   && open_temp_exec_file_opts[open_temp_exec_file_opts_idx].repeat)
388     open_temp_exec_file_opts_next ();
389 
390   start = mmap (start, length, prot, flags, execfd, offset);
391 
392   if (start == MFAIL)
393     {
394       munmap (ptr, length);
395       ftruncate (execfd, offset);
396       return start;
397     }
398 
399   mmap_exec_offset ((char *)start, length) = (char*)ptr - (char*)start;
400 
401   execsize += length;
402 
403   return start;
404 }
405 
406 /* Map in a writable and executable chunk of memory if possible.
407    Failing that, fall back to dlmmap_locked.  */
408 static void *
dlmmap(void * start,size_t length,int prot,int flags,int fd,off_t offset)409 dlmmap (void *start, size_t length, int prot,
410 	int flags, int fd, off_t offset)
411 {
412   void *ptr;
413 
414   assert (start == NULL && length % malloc_getpagesize == 0
415 	  && prot == (PROT_READ | PROT_WRITE)
416 	  && flags == (MAP_PRIVATE | MAP_ANONYMOUS)
417 	  && fd == -1 && offset == 0);
418 
419 #if FFI_CLOSURE_TEST
420   printf ("mapping in %zi\n", length);
421 #endif
422 
423   if (execfd == -1 && !is_selinux_enabled ())
424     {
425       ptr = mmap (start, length, prot | PROT_EXEC, flags, fd, offset);
426 
427       if (ptr != MFAIL || (errno != EPERM && errno != EACCES))
428 	/* Cool, no need to mess with separate segments.  */
429 	return ptr;
430 
431       /* If MREMAP_DUP is ever introduced and implemented, try mmap
432 	 with ((prot & ~PROT_WRITE) | PROT_EXEC) and mremap with
433 	 MREMAP_DUP and prot at this point.  */
434     }
435 
436   if (execsize == 0 || execfd == -1)
437     {
438       pthread_mutex_lock (&open_temp_exec_file_mutex);
439       ptr = dlmmap_locked (start, length, prot, flags, offset);
440       pthread_mutex_unlock (&open_temp_exec_file_mutex);
441 
442       return ptr;
443     }
444 
445   return dlmmap_locked (start, length, prot, flags, offset);
446 }
447 
448 /* Release memory at the given address, as well as the corresponding
449    executable page if it's separate.  */
450 static int
dlmunmap(void * start,size_t length)451 dlmunmap (void *start, size_t length)
452 {
453   /* We don't bother decreasing execsize or truncating the file, since
454      we can't quite tell whether we're unmapping the end of the file.
455      We don't expect frequent deallocation anyway.  If we did, we
456      could locate pages in the file by writing to the pages being
457      deallocated and checking that the file contents change.
458      Yuck.  */
459   msegmentptr seg = segment_holding (gm, start);
460   void *code;
461 
462 #if FFI_CLOSURE_TEST
463   printf ("unmapping %zi\n", length);
464 #endif
465 
466   if (seg && (code = add_segment_exec_offset (start, seg)) != start)
467     {
468       int ret = munmap (code, length);
469       if (ret)
470 	return ret;
471     }
472 
473   return munmap (start, length);
474 }
475 
476 #if FFI_CLOSURE_FREE_CODE
477 /* Return segment holding given code address.  */
478 static msegmentptr
segment_holding_code(mstate m,char * addr)479 segment_holding_code (mstate m, char* addr)
480 {
481   msegmentptr sp = &m->seg;
482   for (;;) {
483     if (addr >= add_segment_exec_offset (sp->base, sp)
484 	&& addr < add_segment_exec_offset (sp->base, sp) + sp->size)
485       return sp;
486     if ((sp = sp->next) == 0)
487       return 0;
488   }
489 }
490 #endif
491 
492 /* Allocate a chunk of memory with the given size.  Returns a pointer
493    to the writable address, and sets *CODE to the executable
494    corresponding virtual address.  */
495 void *
ffi_closure_alloc(size_t size,void ** code)496 ffi_closure_alloc (size_t size, void **code)
497 {
498   void *ptr;
499 
500   if (!code)
501     return NULL;
502 
503   ptr = dlmalloc (size);
504 
505   if (ptr)
506     {
507       msegmentptr seg = segment_holding (gm, ptr);
508 
509       *code = add_segment_exec_offset (ptr, seg);
510     }
511 
512   return ptr;
513 }
514 
515 /* Release a chunk of memory allocated with ffi_closure_alloc.  If
516    FFI_CLOSURE_FREE_CODE is nonzero, the given address can be the
517    writable or the executable address given.  Otherwise, only the
518    writable address can be provided here.  */
519 void
ffi_closure_free(void * ptr)520 ffi_closure_free (void *ptr)
521 {
522 #if FFI_CLOSURE_FREE_CODE
523   msegmentptr seg = segment_holding_code (gm, ptr);
524 
525   if (seg)
526     ptr = sub_segment_exec_offset (ptr, seg);
527 #endif
528 
529   dlfree (ptr);
530 }
531 
532 
533 #if FFI_CLOSURE_TEST
534 /* Do some internal sanity testing to make sure allocation and
535    deallocation of pages are working as intended.  */
main()536 int main ()
537 {
538   void *p[3];
539 #define GET(idx, len) do { p[idx] = dlmalloc (len); printf ("allocated %zi for p[%i]\n", (len), (idx)); } while (0)
540 #define PUT(idx) do { printf ("freeing p[%i]\n", (idx)); dlfree (p[idx]); } while (0)
541   GET (0, malloc_getpagesize / 2);
542   GET (1, 2 * malloc_getpagesize - 64 * sizeof (void*));
543   PUT (1);
544   GET (1, 2 * malloc_getpagesize);
545   GET (2, malloc_getpagesize / 2);
546   PUT (1);
547   PUT (0);
548   PUT (2);
549   return 0;
550 }
551 #endif /* FFI_CLOSURE_TEST */
552 # else /* ! FFI_MMAP_EXEC_WRIT */
553 
554 /* On many systems, memory returned by malloc is writable and
555    executable, so just use it.  */
556 
557 #include <stdlib.h>
558 
559 void *
ffi_closure_alloc(size_t size,void ** code)560 ffi_closure_alloc (size_t size, void **code)
561 {
562   if (!code)
563     return NULL;
564 
565   return *code = malloc (size);
566 }
567 
568 void
ffi_closure_free(void * ptr)569 ffi_closure_free (void *ptr)
570 {
571   free (ptr);
572 }
573 
574 # endif /* ! FFI_MMAP_EXEC_WRIT */
575 #endif /* FFI_CLOSURES */
576