1 /*
2  *    Stack-less Just-In-Time compiler
3  *
4  *    Copyright Zoltan Herczeg (hzmester@freemail.hu). All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without modification, are
7  * permitted provided that the following conditions are met:
8  *
9  *   1. Redistributions of source code must retain the above copyright notice, this list of
10  *      conditions and the following disclaimer.
11  *
12  *   2. Redistributions in binary form must reproduce the above copyright notice, this list
13  *      of conditions and the following disclaimer in the documentation and/or other materials
14  *      provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND CONTRIBUTORS ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
19  * SHALL THE COPYRIGHT HOLDER(S) OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
20  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
21  * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
22  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
24  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26 
27 /*
28    This file contains a simple executable memory allocator
29 
30    It is assumed, that executable code blocks are usually medium (or sometimes
31    large) memory blocks, and the allocator is not too frequently called (less
32    optimized than other allocators). Thus, using it as a generic allocator is
33    not suggested.
34 
35    How does it work:
36      Memory is allocated in continuous memory areas called chunks by alloc_chunk()
37      Chunk format:
38      [ block ][ block ] ... [ block ][ block terminator ]
39 
40    All blocks and the block terminator is started with block_header. The block
41    header contains the size of the previous and the next block. These sizes
42    can also contain special values.
43      Block size:
44        0 - The block is a free_block, with a different size member.
45        1 - The block is a block terminator.
46        n - The block is used at the moment, and the value contains its size.
47      Previous block size:
48        0 - This is the first block of the memory chunk.
49        n - The size of the previous block.
50 
51    Using these size values we can go forward or backward on the block chain.
52    The unused blocks are stored in a chain list pointed by free_blocks. This
53    list is useful if we need to find a suitable memory area when the allocator
54    is called.
55 
56    When a block is freed, the new free block is connected to its adjacent free
57    blocks if possible.
58 
59      [ free block ][ used block ][ free block ]
60    and "used block" is freed, the three blocks are connected together:
61      [           one big free block           ]
62 */
63 
64 /* --------------------------------------------------------------------- */
65 /*  System (OS) functions                                                */
66 /* --------------------------------------------------------------------- */
67 
68 /* 64 KByte. */
69 #define CHUNK_SIZE	0x10000
70 
71 struct chunk_header {
72 	void *executable;
73 };
74 
75 /*
76    alloc_chunk / free_chunk :
77      * allocate executable system memory chunks
78      * the size is always divisible by CHUNK_SIZE
79    allocator_grab_lock / allocator_release_lock :
80      * make the allocator thread safe
81      * can be empty if the OS (or the application) does not support threading
82      * only the allocator requires this lock, sljit is fully thread safe
83        as it only uses local variables
84 */
85 
86 #include <fcntl.h>
87 
88 #ifndef O_NOATIME
89 #define O_NOATIME 0
90 #endif
91 
92 #ifdef __O_TMPFILE
93 #ifndef O_TMPFILE
94 #define O_TMPFILE	(__O_TMPFILE | O_DIRECTORY)
95 #endif
96 #endif
97 
98 #if !(defined(__NetBSD__) && defined(MAP_REMAPDUP))
99 int mkostemp(char *template, int flags);
100 
101 #ifdef __NetBSD__
102 /*
103  * this is a workaround for NetBSD < 8 that lacks a system provided
104  * secure_getenv function.
105  * ideally this should never be used, as the standard allocator is
106  * a preferred option for those systems and should be used instead.
107  */
108 #define secure_getenv(name) issetugid() ?  NULL : getenv(name)
109 #else
110 char *secure_getenv(const char *name);
111 #endif
112 
create_tempfile(void)113 static SLJIT_INLINE int create_tempfile(void)
114 {
115 	int fd;
116 
117 	char tmp_name[256];
118 	size_t tmp_name_len;
119 	char *dir;
120 	size_t len;
121 
122 #ifdef HAVE_MEMFD_CREATE
123 	/* this is a GNU extension, make sure to use -D_GNU_SOURCE */
124 	fd = memfd_create("sljit", MFD_CLOEXEC);
125 	if (fd != -1)
126 		return fd;
127 #endif
128 
129 #ifdef P_tmpdir
130 	len = (P_tmpdir != NULL) ? strlen(P_tmpdir) : 0;
131 
132 	if (len > 0 && len < sizeof(tmp_name)) {
133 		strcpy(tmp_name, P_tmpdir);
134 		tmp_name_len = len;
135 	}
136 	else {
137 		strcpy(tmp_name, "/tmp");
138 		tmp_name_len = 4;
139 	}
140 #else
141 	strcpy(tmp_name, "/tmp");
142 	tmp_name_len = 4;
143 #endif
144 
145 	dir = secure_getenv("TMPDIR");
146 
147 	if (dir) {
148 		len = strlen(dir);
149 		if (len > 0 && len < sizeof(tmp_name)) {
150 			strcpy(tmp_name, dir);
151 			tmp_name_len = len;
152 		}
153 	}
154 
155 	SLJIT_ASSERT(tmp_name_len > 0 && tmp_name_len < sizeof(tmp_name));
156 
157 	while (tmp_name_len > 0 && tmp_name[tmp_name_len - 1] == '/') {
158 		tmp_name_len--;
159 		tmp_name[tmp_name_len] = '\0';
160 	}
161 
162 #ifdef O_TMPFILE
163 	fd = open(tmp_name, O_TMPFILE | O_EXCL | O_RDWR | O_NOATIME | O_CLOEXEC, S_IRUSR | S_IWUSR);
164 	if (fd != -1)
165 		return fd;
166 #endif
167 
168 	if (tmp_name_len + 7 >= sizeof(tmp_name))
169 	{
170 		return -1;
171 	}
172 
173 	strcpy(tmp_name + tmp_name_len, "/XXXXXX");
174 	fd = mkostemp(tmp_name, O_CLOEXEC | O_NOATIME);
175 
176 	if (fd == -1)
177 		return fd;
178 
179 	if (unlink(tmp_name)) {
180 		close(fd);
181 		return -1;
182 	}
183 
184 	return fd;
185 }
186 
alloc_chunk(sljit_uw size)187 static SLJIT_INLINE struct chunk_header* alloc_chunk(sljit_uw size)
188 {
189 	struct chunk_header *retval;
190 	int fd;
191 
192 	fd = create_tempfile();
193 	if (fd == -1)
194 		return NULL;
195 
196 	if (ftruncate(fd, size)) {
197 		close(fd);
198 		return NULL;
199 	}
200 
201 	retval = (struct chunk_header *)mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
202 
203 	if (retval == MAP_FAILED) {
204 		close(fd);
205 		return NULL;
206 	}
207 
208 	retval->executable = mmap(NULL, size, PROT_READ | PROT_EXEC, MAP_SHARED, fd, 0);
209 
210 	if (retval->executable == MAP_FAILED) {
211 		munmap((void *)retval, size);
212 		close(fd);
213 		return NULL;
214 	}
215 
216 	close(fd);
217 	return retval;
218 }
219 #else
alloc_chunk(sljit_uw size)220 static SLJIT_INLINE struct chunk_header* alloc_chunk(sljit_uw size)
221 {
222 	struct chunk_header *retval;
223 	void *maprx;
224 
225 	retval = (struct chunk_header *)mmap(NULL, size,
226 			PROT_MPROTECT(PROT_EXEC|PROT_WRITE|PROT_READ),
227 			MAP_ANON, -1, 0);
228 
229 	if (retval == MAP_FAILED)
230 		return NULL;
231 
232 	maprx = mremap(retval, size, NULL, size, MAP_REMAPDUP);
233 	if (maprx == MAP_FAILED) {
234 		munmap((void *)retval, size);
235 		return NULL;
236 	}
237 
238 	if (mprotect(retval, size, PROT_READ | PROT_WRITE) == -1 ||
239 		mprotect(maprx, size, PROT_READ | PROT_EXEC) == -1) {
240 		munmap(maprx, size);
241 		munmap((void *)retval, size);
242 		return NULL;
243 	}
244 	retval->executable = maprx;
245 	return retval;
246 }
247 #endif /* NetBSD >= 8 */
248 
free_chunk(void * chunk,sljit_uw size)249 static SLJIT_INLINE void free_chunk(void *chunk, sljit_uw size)
250 {
251 	struct chunk_header *header = ((struct chunk_header *)chunk) - 1;
252 
253 	munmap(header->executable, size);
254 	munmap((void *)header, size);
255 }
256 
257 /* --------------------------------------------------------------------- */
258 /*  Common functions                                                     */
259 /* --------------------------------------------------------------------- */
260 
261 #define CHUNK_MASK	(~(CHUNK_SIZE - 1))
262 
263 struct block_header {
264 	sljit_uw size;
265 	sljit_uw prev_size;
266 	sljit_sw executable_offset;
267 };
268 
269 struct free_block {
270 	struct block_header header;
271 	struct free_block *next;
272 	struct free_block *prev;
273 	sljit_uw size;
274 };
275 
276 #define AS_BLOCK_HEADER(base, offset) \
277 	((struct block_header*)(((sljit_u8*)base) + offset))
278 #define AS_FREE_BLOCK(base, offset) \
279 	((struct free_block*)(((sljit_u8*)base) + offset))
280 #define MEM_START(base)		((void*)((base) + 1))
281 #define ALIGN_SIZE(size)	(((size) + sizeof(struct block_header) + 7) & ~7)
282 
283 static struct free_block* free_blocks;
284 static sljit_uw allocated_size;
285 static sljit_uw total_size;
286 
sljit_insert_free_block(struct free_block * free_block,sljit_uw size)287 static SLJIT_INLINE void sljit_insert_free_block(struct free_block *free_block, sljit_uw size)
288 {
289 	free_block->header.size = 0;
290 	free_block->size = size;
291 
292 	free_block->next = free_blocks;
293 	free_block->prev = NULL;
294 	if (free_blocks)
295 		free_blocks->prev = free_block;
296 	free_blocks = free_block;
297 }
298 
sljit_remove_free_block(struct free_block * free_block)299 static SLJIT_INLINE void sljit_remove_free_block(struct free_block *free_block)
300 {
301 	if (free_block->next)
302 		free_block->next->prev = free_block->prev;
303 
304 	if (free_block->prev)
305 		free_block->prev->next = free_block->next;
306 	else {
307 		SLJIT_ASSERT(free_blocks == free_block);
308 		free_blocks = free_block->next;
309 	}
310 }
311 
sljit_malloc_exec(sljit_uw size)312 SLJIT_API_FUNC_ATTRIBUTE void* sljit_malloc_exec(sljit_uw size)
313 {
314 	struct chunk_header *chunk_header;
315 	struct block_header *header;
316 	struct block_header *next_header;
317 	struct free_block *free_block;
318 	sljit_uw chunk_size;
319 	sljit_sw executable_offset;
320 
321 	allocator_grab_lock();
322 	if (size < (64 - sizeof(struct block_header)))
323 		size = (64 - sizeof(struct block_header));
324 	size = ALIGN_SIZE(size);
325 
326 	free_block = free_blocks;
327 	while (free_block) {
328 		if (free_block->size >= size) {
329 			chunk_size = free_block->size;
330 			if (chunk_size > size + 64) {
331 				/* We just cut a block from the end of the free block. */
332 				chunk_size -= size;
333 				free_block->size = chunk_size;
334 				header = AS_BLOCK_HEADER(free_block, chunk_size);
335 				header->prev_size = chunk_size;
336 				header->executable_offset = free_block->header.executable_offset;
337 				AS_BLOCK_HEADER(header, size)->prev_size = size;
338 			}
339 			else {
340 				sljit_remove_free_block(free_block);
341 				header = (struct block_header*)free_block;
342 				size = chunk_size;
343 			}
344 			allocated_size += size;
345 			header->size = size;
346 			allocator_release_lock();
347 			return MEM_START(header);
348 		}
349 		free_block = free_block->next;
350 	}
351 
352 	chunk_size = sizeof(struct chunk_header) + sizeof(struct block_header);
353 	chunk_size = (chunk_size + size + CHUNK_SIZE - 1) & CHUNK_MASK;
354 
355 	chunk_header = alloc_chunk(chunk_size);
356 	if (!chunk_header) {
357 		allocator_release_lock();
358 		return NULL;
359 	}
360 
361 	executable_offset = (sljit_sw)((sljit_u8*)chunk_header->executable - (sljit_u8*)chunk_header);
362 
363 	chunk_size -= sizeof(struct chunk_header) + sizeof(struct block_header);
364 	total_size += chunk_size;
365 
366 	header = (struct block_header *)(chunk_header + 1);
367 
368 	header->prev_size = 0;
369 	header->executable_offset = executable_offset;
370 	if (chunk_size > size + 64) {
371 		/* Cut the allocated space into a free and a used block. */
372 		allocated_size += size;
373 		header->size = size;
374 		chunk_size -= size;
375 
376 		free_block = AS_FREE_BLOCK(header, size);
377 		free_block->header.prev_size = size;
378 		free_block->header.executable_offset = executable_offset;
379 		sljit_insert_free_block(free_block, chunk_size);
380 		next_header = AS_BLOCK_HEADER(free_block, chunk_size);
381 	}
382 	else {
383 		/* All space belongs to this allocation. */
384 		allocated_size += chunk_size;
385 		header->size = chunk_size;
386 		next_header = AS_BLOCK_HEADER(header, chunk_size);
387 	}
388 	next_header->size = 1;
389 	next_header->prev_size = chunk_size;
390 	next_header->executable_offset = executable_offset;
391 	allocator_release_lock();
392 	return MEM_START(header);
393 }
394 
sljit_free_exec(void * ptr)395 SLJIT_API_FUNC_ATTRIBUTE void sljit_free_exec(void* ptr)
396 {
397 	struct block_header *header;
398 	struct free_block* free_block;
399 
400 	allocator_grab_lock();
401 	header = AS_BLOCK_HEADER(ptr, -(sljit_sw)sizeof(struct block_header));
402 	header = AS_BLOCK_HEADER(header, -header->executable_offset);
403 	allocated_size -= header->size;
404 
405 	/* Connecting free blocks together if possible. */
406 
407 	/* If header->prev_size == 0, free_block will equal to header.
408 	   In this case, free_block->header.size will be > 0. */
409 	free_block = AS_FREE_BLOCK(header, -(sljit_sw)header->prev_size);
410 	if (SLJIT_UNLIKELY(!free_block->header.size)) {
411 		free_block->size += header->size;
412 		header = AS_BLOCK_HEADER(free_block, free_block->size);
413 		header->prev_size = free_block->size;
414 	}
415 	else {
416 		free_block = (struct free_block*)header;
417 		sljit_insert_free_block(free_block, header->size);
418 	}
419 
420 	header = AS_BLOCK_HEADER(free_block, free_block->size);
421 	if (SLJIT_UNLIKELY(!header->size)) {
422 		free_block->size += ((struct free_block*)header)->size;
423 		sljit_remove_free_block((struct free_block*)header);
424 		header = AS_BLOCK_HEADER(free_block, free_block->size);
425 		header->prev_size = free_block->size;
426 	}
427 
428 	/* The whole chunk is free. */
429 	if (SLJIT_UNLIKELY(!free_block->header.prev_size && header->size == 1)) {
430 		/* If this block is freed, we still have (allocated_size / 2) free space. */
431 		if (total_size - free_block->size > (allocated_size * 3 / 2)) {
432 			total_size -= free_block->size;
433 			sljit_remove_free_block(free_block);
434 			free_chunk(free_block, free_block->size +
435 				sizeof(struct chunk_header) +
436 				sizeof(struct block_header));
437 		}
438 	}
439 
440 	allocator_release_lock();
441 }
442 
sljit_free_unused_memory_exec(void)443 SLJIT_API_FUNC_ATTRIBUTE void sljit_free_unused_memory_exec(void)
444 {
445 	struct free_block* free_block;
446 	struct free_block* next_free_block;
447 
448 	allocator_grab_lock();
449 
450 	free_block = free_blocks;
451 	while (free_block) {
452 		next_free_block = free_block->next;
453 		if (!free_block->header.prev_size &&
454 				AS_BLOCK_HEADER(free_block, free_block->size)->size == 1) {
455 			total_size -= free_block->size;
456 			sljit_remove_free_block(free_block);
457 			free_chunk(free_block, free_block->size +
458 				sizeof(struct chunk_header) +
459 				sizeof(struct block_header));
460 		}
461 		free_block = next_free_block;
462 	}
463 
464 	SLJIT_ASSERT((total_size && free_blocks) || (!total_size && !free_blocks));
465 	allocator_release_lock();
466 }
467 
sljit_exec_offset(void * ptr)468 SLJIT_API_FUNC_ATTRIBUTE sljit_sw sljit_exec_offset(void* ptr)
469 {
470 	return ((struct block_header *)(ptr))[-1].executable_offset;
471 }
472