2 * Tiny C Memory and bounds checker
4 * Copyright (c) 2002 Fabrice Bellard
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 /* define so that bound array is static (faster, but use memory if
24 bound checking not used) */
25 //#define BOUND_STATIC
27 #define BOUND_T1_BITS 13
28 #define BOUND_T2_BITS 11
29 #define BOUND_T3_BITS (32 - BOUND_T1_BITS - BOUND_T2_BITS)
31 #define BOUND_T1_SIZE (1 << BOUND_T1_BITS)
32 #define BOUND_T2_SIZE (1 << BOUND_T2_BITS)
33 #define BOUND_T3_SIZE (1 << BOUND_T3_BITS)
34 #define BOUND_E_BITS 4
36 #define BOUND_T23_BITS (BOUND_T2_BITS + BOUND_T3_BITS)
37 #define BOUND_T23_SIZE (1 << BOUND_T23_BITS)
40 /* this pointer is generated when bound check is incorrect */
41 #define INVALID_POINTER ((void *)(-2))
42 /* size of an empty region */
43 #define EMPTY_SIZE 0xffffffff
44 /* size of an invalid region */
45 #define INVALID_SIZE 0
47 typedef struct BoundEntry
{
50 struct BoundEntry
*next
;
51 unsigned long is_invalid
; /* true if pointers outside region are invalid */
54 /* external interface */
55 void __bound_init(void);
56 void __bound_new_region(void *p
, unsigned long size
);
57 void __bound_delete_region(void *p
);
59 /* currently, tcc cannot compile that because we use unsupported GNUC
61 #if !defined(__TINYC__)
62 void *__bound_ptr_add(void *p
, int offset
) __attribute__((regparm(2)));
63 void *__bound_ptr_indir1(void *p
, int offset
) __attribute__((regparm(2)));
64 void *__bound_ptr_indir2(void *p
, int offset
) __attribute__((regparm(2)));
65 void *__bound_ptr_indir4(void *p
, int offset
) __attribute__((regparm(2)));
66 void *__bound_ptr_indir8(void *p
, int offset
) __attribute__((regparm(2)));
67 void *__bound_ptr_indir12(void *p
, int offset
) __attribute__((regparm(2)));
68 void *__bound_ptr_indir16(void *p
, int offset
) __attribute__((regparm(2)));
69 void __bound_local_new(void *p
) __attribute__((regparm(1)));
70 void __bound_local_delete(void *p
) __attribute__((regparm(1)));
73 void *__bound_malloc(size_t size
, const void *caller
);
74 void *__bound_memalign(size_t size
, size_t align
, const void *caller
);
75 void __bound_free(void *ptr
, const void *caller
);
76 void *__bound_realloc(void *ptr
, size_t size
, const void *caller
);
77 static void *libc_malloc(size_t size
);
78 static void libc_free(void *ptr
);
79 static void install_malloc_hooks(void);
80 static void restore_malloc_hooks(void);
82 static void *saved_malloc_hook
;
83 static void *saved_free_hook
;
84 static void *saved_realloc_hook
;
85 static void *saved_memalign_hook
;
90 static BoundEntry
*__bound_t1
[BOUND_T1_SIZE
]; /* page table */
92 static BoundEntry
**__bound_t1
; /* page table */
94 static BoundEntry
*__bound_empty_t2
; /* empty page, for unused pages */
95 static BoundEntry
*__bound_invalid_t2
; /* invalid page, for invalid pointers */
97 static BoundEntry
*__bound_find_region(BoundEntry
*e1
, void *p
)
99 unsigned long addr
, tmp
;
104 addr
= (unsigned long)p
;
106 if (addr
<= e
->size
) {
107 /* put region at the head */
109 e1
->start
= e
->start
;
118 /* no entry found: return empty entry or invalid entry */
120 return __bound_invalid_t2
;
122 return __bound_empty_t2
;
125 static void bound_error(const char *fmt
, ...)
129 fprintf(stderr
, "bounds check: ");
130 vfprintf(stderr
, fmt
, ap
);
131 fprintf(stderr
, "\n");
136 static void bound_alloc_error(void)
138 bound_error("not enough memory for bound checking code\n");
141 /* currently, tcc cannot compile that because we use GNUC extensions */
142 #if !defined(__TINYC__)
144 /* return '(p + offset)' for pointer arithmetic (a pointer can reach
145 the end of a region in this case */
146 void *__bound_ptr_add(void *p
, int offset
)
148 unsigned long addr
= (unsigned long)p
;
150 #if defined(BOUND_DEBUG)
151 printf("add: 0x%x %d\n", (int)p
, offset
);
154 e
= __bound_t1
[addr
>> (BOUND_T2_BITS
+ BOUND_T3_BITS
)];
155 e
= (BoundEntry
*)((char *)e
+
156 ((addr
>> (BOUND_T3_BITS
- BOUND_E_BITS
)) &
157 ((BOUND_T2_SIZE
- 1) << BOUND_E_BITS
)));
159 if (addr
> e
->size
) {
160 e
= __bound_find_region(e
, p
);
161 addr
= (unsigned long)p
- e
->start
;
165 return INVALID_POINTER
; /* return an invalid pointer */
169 /* return '(p + offset)' for pointer indirection (the resulting must
170 be strictly inside the region */
171 #define BOUND_PTR_INDIR(dsize) \
172 void *__bound_ptr_indir ## dsize (void *p, int offset) \
174 unsigned long addr = (unsigned long)p; \
177 e = __bound_t1[addr >> (BOUND_T2_BITS + BOUND_T3_BITS)]; \
178 e = (BoundEntry *)((char *)e + \
179 ((addr >> (BOUND_T3_BITS - BOUND_E_BITS)) & \
180 ((BOUND_T2_SIZE - 1) << BOUND_E_BITS))); \
182 if (addr > e->size) { \
183 e = __bound_find_region(e, p); \
184 addr = (unsigned long)p - e->start; \
186 addr += offset + dsize; \
187 if (addr > e->size) \
188 return INVALID_POINTER; /* return an invalid pointer */ \
193 #define GET_CALLER_FP(fp)\
196 __asm__ __volatile__ ("movl %%ebp,%0" :"=g" (fp1));\
200 #error put code to extract the calling frame pointer
203 /* called when entering a function to add all the local regions */
204 void __bound_local_new(void *p1
)
206 unsigned long addr
, size
, fp
, *p
= p1
;
215 __bound_new_region((void *)addr
, size
);
219 /* called when leaving a function to delete all the local regions */
220 void __bound_local_delete(void *p1
)
222 unsigned long addr
, fp
, *p
= p1
;
230 __bound_delete_region((void *)addr
);
236 void __bound_local_new(void *p
)
239 void __bound_local_delete(void *p
)
243 void *__bound_ptr_add(void *p
, int offset
)
248 #define BOUND_PTR_INDIR(dsize) \
249 void *__bound_ptr_indir ## dsize (void *p, int offset) \
263 static BoundEntry
*__bound_new_page(void)
268 page
= libc_malloc(sizeof(BoundEntry
) * BOUND_T2_SIZE
);
271 for(i
=0;i
<BOUND_T2_SIZE
;i
++) {
272 /* put empty entries */
274 page
[i
].size
= EMPTY_SIZE
;
276 page
[i
].is_invalid
= 0;
281 /* currently we use malloc(). Should use bound_new_page() */
282 static BoundEntry
*bound_new_entry(void)
285 e
= libc_malloc(sizeof(BoundEntry
));
289 static void bound_free_entry(BoundEntry
*e
)
294 static inline BoundEntry
*get_page(int index
)
297 page
= __bound_t1
[index
];
298 if (page
== __bound_empty_t2
|| page
== __bound_invalid_t2
) {
299 /* create a new page if necessary */
300 page
= __bound_new_page();
301 __bound_t1
[index
] = page
;
306 /* mark a region as being invalid (can only be used during init) */
307 static void mark_invalid(unsigned long addr
, unsigned long size
)
309 unsigned long start
, end
;
311 int t1_start
, t1_end
, i
, j
, t2_start
, t2_end
;
316 t2_start
= (start
+ BOUND_T3_SIZE
- 1) >> BOUND_T3_BITS
;
318 t2_end
= end
>> BOUND_T3_BITS
;
320 t2_end
= 1 << (BOUND_T1_BITS
+ BOUND_T2_BITS
);
323 printf("mark_invalid: start = %x %x\n", t2_start
, t2_end
);
326 /* first we handle full pages */
327 t1_start
= (t2_start
+ BOUND_T2_SIZE
- 1) >> BOUND_T2_BITS
;
328 t1_end
= t2_end
>> BOUND_T2_BITS
;
330 i
= t2_start
& (BOUND_T2_SIZE
- 1);
331 j
= t2_end
& (BOUND_T2_SIZE
- 1);
333 if (t1_start
== t1_end
) {
334 page
= get_page(t2_start
>> BOUND_T2_BITS
);
336 page
[i
].size
= INVALID_SIZE
;
337 page
[i
].is_invalid
= 1;
341 page
= get_page(t2_start
>> BOUND_T2_BITS
);
342 for(; i
< BOUND_T2_SIZE
; i
++) {
343 page
[i
].size
= INVALID_SIZE
;
344 page
[i
].is_invalid
= 1;
347 for(i
= t1_start
; i
< t1_end
; i
++) {
348 __bound_t1
[i
] = __bound_invalid_t2
;
351 page
= get_page(t1_end
);
352 for(i
= 0; i
< j
; i
++) {
353 page
[i
].size
= INVALID_SIZE
;
354 page
[i
].is_invalid
= 1;
360 void __bound_init(void)
364 unsigned long start
, size
;
366 /* save malloc hooks and install bound check hooks */
367 install_malloc_hooks();
370 __bound_t1
= libc_malloc(BOUND_T1_SIZE
* sizeof(BoundEntry
*));
374 __bound_empty_t2
= __bound_new_page();
375 for(i
=0;i
<BOUND_T1_SIZE
;i
++) {
376 __bound_t1
[i
] = __bound_empty_t2
;
379 page
= __bound_new_page();
380 for(i
=0;i
<BOUND_T2_SIZE
;i
++) {
381 /* put invalid entries */
383 page
[i
].size
= INVALID_SIZE
;
385 page
[i
].is_invalid
= 1;
387 __bound_invalid_t2
= page
;
389 /* invalid pointer zone */
390 start
= (unsigned long)INVALID_POINTER
& ~(BOUND_T23_SIZE
- 1);
391 size
= BOUND_T23_SIZE
;
392 mark_invalid(start
, size
);
394 #if !defined(__TINYC__)
395 /* malloc zone is also marked invalid */
396 start
= (unsigned long)&_end
;
397 size
= 128 * 0x100000;
398 mark_invalid(start
, size
);
402 static inline void add_region(BoundEntry
*e
,
403 unsigned long start
, unsigned long size
)
407 /* no region : add it */
411 /* already regions in the list: add it at the head */
412 e1
= bound_new_entry();
413 e1
->start
= e
->start
;
422 /* create a new region. It should not already exist in the region list */
423 void __bound_new_region(void *p
, unsigned long size
)
425 unsigned long start
, end
;
426 BoundEntry
*page
, *e
, *e2
;
427 int t1_start
, t1_end
, i
, t2_start
, t2_end
;
429 start
= (unsigned long)p
;
431 t1_start
= start
>> (BOUND_T2_BITS
+ BOUND_T3_BITS
);
432 t1_end
= end
>> (BOUND_T2_BITS
+ BOUND_T3_BITS
);
435 page
= get_page(t1_start
);
436 t2_start
= (start
>> (BOUND_T3_BITS
- BOUND_E_BITS
)) &
437 ((BOUND_T2_SIZE
- 1) << BOUND_E_BITS
);
438 t2_end
= (end
>> (BOUND_T3_BITS
- BOUND_E_BITS
)) &
439 ((BOUND_T2_SIZE
- 1) << BOUND_E_BITS
);
441 printf("new %lx %lx %x %x %x %x\n",
442 start
, end
, t1_start
, t1_end
, t2_start
, t2_end
);
445 e
= (BoundEntry
*)((char *)page
+ t2_start
);
446 add_region(e
, start
, size
);
448 if (t1_end
== t1_start
) {
449 /* same ending page */
450 e2
= (BoundEntry
*)((char *)page
+ t2_end
);
457 add_region(e
, start
, size
);
460 /* mark until end of page */
461 e2
= page
+ BOUND_T2_SIZE
;
467 /* mark intermediate pages, if any */
468 for(i
=t1_start
+1;i
<t1_end
;i
++) {
470 e2
= page
+ BOUND_T2_SIZE
;
471 for(e
=page
;e
<e2
;e
++) {
477 page
= get_page(t2_end
);
478 e2
= (BoundEntry
*)((char *)page
+ t2_end
);
479 for(e
=page
;e
<e2
;e
++) {
483 add_region(e
, start
, size
);
487 /* delete a region */
488 static inline void delete_region(BoundEntry
*e
,
489 void *p
, unsigned long empty_size
)
494 addr
= (unsigned long)p
;
496 if (addr
<= e
->size
) {
497 /* region found is first one */
500 /* no more region: mark it empty */
502 e
->size
= empty_size
;
504 /* copy next region in head */
505 e
->start
= e1
->start
;
508 bound_free_entry(e1
);
511 /* find the matching region */
515 /* region not found: do nothing */
518 addr
= (unsigned long)p
- e
->start
;
519 if (addr
<= e
->size
) {
520 /* found: remove entry */
529 /* WARNING: 'p' must be the starting point of the region. */
530 void __bound_delete_region(void *p
)
532 unsigned long start
, end
, addr
, size
, empty_size
;
533 BoundEntry
*page
, *e
, *e2
;
534 int t1_start
, t1_end
, t2_start
, t2_end
, i
;
536 start
= (unsigned long)p
;
537 t1_start
= start
>> (BOUND_T2_BITS
+ BOUND_T3_BITS
);
538 t2_start
= (start
>> (BOUND_T3_BITS
- BOUND_E_BITS
)) &
539 ((BOUND_T2_SIZE
- 1) << BOUND_E_BITS
);
541 /* find region size */
542 page
= __bound_t1
[t1_start
];
543 e
= (BoundEntry
*)((char *)page
+ t2_start
);
544 addr
= start
- e
->start
;
546 e
= __bound_find_region(e
, p
);
547 /* test if invalid region */
548 if (e
->size
== EMPTY_SIZE
|| (unsigned long)p
!= e
->start
)
549 bound_error("freeing invalid region");
550 /* compute the size we put in invalid regions */
552 empty_size
= INVALID_SIZE
;
554 empty_size
= EMPTY_SIZE
;
558 /* now we can free each entry */
559 t1_end
= end
>> (BOUND_T2_BITS
+ BOUND_T3_BITS
);
560 t2_end
= (end
>> (BOUND_T3_BITS
- BOUND_E_BITS
)) &
561 ((BOUND_T2_SIZE
- 1) << BOUND_E_BITS
);
563 delete_region(e
, p
, empty_size
);
564 if (t1_end
== t1_start
) {
565 /* same ending page */
566 e2
= (BoundEntry
*)((char *)page
+ t2_end
);
571 e
->size
= empty_size
;
573 delete_region(e
, p
, empty_size
);
576 /* mark until end of page */
577 e2
= page
+ BOUND_T2_SIZE
;
581 e
->size
= empty_size
;
583 /* mark intermediate pages, if any */
584 /* XXX: should free them */
585 for(i
=t1_start
+1;i
<t1_end
;i
++) {
587 e2
= page
+ BOUND_T2_SIZE
;
588 for(e
=page
;e
<e2
;e
++) {
590 e
->size
= empty_size
;
594 page
= get_page(t2_end
);
595 e2
= (BoundEntry
*)((char *)page
+ t2_end
);
596 for(e
=page
;e
<e2
;e
++) {
598 e
->size
= empty_size
;
600 delete_region(e
, p
, empty_size
);
604 /* return the size of the region starting at p, or EMPTY_SIZE if non
606 static unsigned long get_region_size(void *p
)
608 unsigned long addr
= (unsigned long)p
;
611 e
= __bound_t1
[addr
>> (BOUND_T2_BITS
+ BOUND_T3_BITS
)];
612 e
= (BoundEntry
*)((char *)e
+
613 ((addr
>> (BOUND_T3_BITS
- BOUND_E_BITS
)) &
614 ((BOUND_T2_SIZE
- 1) << BOUND_E_BITS
)));
617 e
= __bound_find_region(e
, p
);
618 if (e
->start
!= (unsigned long)p
)
623 /* patched memory functions */
625 static void install_malloc_hooks(void)
627 saved_malloc_hook
= __malloc_hook
;
628 saved_free_hook
= __free_hook
;
629 saved_realloc_hook
= __realloc_hook
;
630 saved_memalign_hook
= __memalign_hook
;
631 __malloc_hook
= __bound_malloc
;
632 __free_hook
= __bound_free
;
633 __realloc_hook
= __bound_realloc
;
634 __memalign_hook
= __bound_memalign
;
637 static void restore_malloc_hooks(void)
639 __malloc_hook
= saved_malloc_hook
;
640 __free_hook
= saved_free_hook
;
641 __realloc_hook
= saved_realloc_hook
;
642 __memalign_hook
= saved_memalign_hook
;
645 static void *libc_malloc(size_t size
)
648 restore_malloc_hooks();
650 install_malloc_hooks();
654 static void libc_free(void *ptr
)
656 restore_malloc_hooks();
658 install_malloc_hooks();
661 /* XXX: we should use a malloc which ensure that it is unlikely that
662 two malloc'ed data have the same address if 'free' are made in
664 void *__bound_malloc(size_t size
, const void *caller
)
668 /* we allocate one more byte to ensure the regions will be
669 separated by at least one byte. With the glibc malloc, it may
670 be in fact not necessary */
671 ptr
= libc_malloc(size
+ 1);
675 __bound_new_region(ptr
, size
);
679 void *__bound_memalign(size_t size
, size_t align
, const void *caller
)
683 restore_malloc_hooks();
685 /* we allocate one more byte to ensure the regions will be
686 separated by at least one byte. With the glibc malloc, it may
687 be in fact not necessary */
688 ptr
= memalign(size
+ 1, align
);
690 install_malloc_hooks();
694 __bound_new_region(ptr
, size
);
698 void __bound_free(void *ptr
, const void *caller
)
702 __bound_delete_region(ptr
);
707 void *__bound_realloc(void *ptr
, size_t size
, const void *caller
)
713 __bound_free(ptr
, caller
);
716 ptr1
= __bound_malloc(size
, caller
);
717 if (ptr
== NULL
|| ptr1
== NULL
)
719 old_size
= get_region_size(ptr
);
720 if (old_size
== EMPTY_SIZE
)
721 bound_error("realloc'ing invalid pointer");
722 memcpy(ptr1
, ptr
, old_size
);
723 __bound_free(ptr
, caller
);
729 static void bound_dump(void)
731 BoundEntry
*page
, *e
;
734 printf("region dump:\n");
735 for(i
=0;i
<BOUND_T1_SIZE
;i
++) {
736 page
= __bound_t1
[i
];
737 for(j
=0;j
<BOUND_T2_SIZE
;j
++) {
739 /* do not print invalid or empty entries */
740 if (e
->size
!= EMPTY_SIZE
&& e
->start
!= 0) {
742 (i
<< (BOUND_T2_BITS
+ BOUND_T3_BITS
)) +
743 (j
<< BOUND_T3_BITS
));
745 printf(" %08lx:%08lx", e
->start
, e
->start
+ e
->size
);
756 /* resolve check check syms */
757 typedef struct BCSyms
{
762 static BCSyms bcheck_syms
[] = {
767 static void *bound_resolve_sym(const char *sym
)
772 while (p
->str
!= NULL
) {
773 if (!strcmp(p
->str
, sym
))