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 /* use malloc hooks. Currently the code cannot be reliable if no hooks */
28 #define CONFIG_TCC_MALLOC_HOOKS
30 #define BOUND_T1_BITS 13
31 #define BOUND_T2_BITS 11
32 #define BOUND_T3_BITS (32 - BOUND_T1_BITS - BOUND_T2_BITS)
34 #define BOUND_T1_SIZE (1 << BOUND_T1_BITS)
35 #define BOUND_T2_SIZE (1 << BOUND_T2_BITS)
36 #define BOUND_T3_SIZE (1 << BOUND_T3_BITS)
37 #define BOUND_E_BITS 4
39 #define BOUND_T23_BITS (BOUND_T2_BITS + BOUND_T3_BITS)
40 #define BOUND_T23_SIZE (1 << BOUND_T23_BITS)
43 /* this pointer is generated when bound check is incorrect */
44 #define INVALID_POINTER ((void *)(-2))
45 /* size of an empty region */
46 #define EMPTY_SIZE 0xffffffff
47 /* size of an invalid region */
48 #define INVALID_SIZE 0
50 typedef struct BoundEntry
{
53 struct BoundEntry
*next
;
54 unsigned long is_invalid
; /* true if pointers outside region are invalid */
57 /* external interface */
58 void __bound_init(void);
59 void __bound_new_region(void *p
, unsigned long size
);
60 int __bound_delete_region(void *p
);
62 /* currently, tcc cannot compile that because we use unsupported GNU C
64 #if !defined(__TINYC__)
65 void *__bound_ptr_add(void *p
, int offset
) __attribute__((regparm(2)));
66 void *__bound_ptr_indir1(void *p
, int offset
) __attribute__((regparm(2)));
67 void *__bound_ptr_indir2(void *p
, int offset
) __attribute__((regparm(2)));
68 void *__bound_ptr_indir4(void *p
, int offset
) __attribute__((regparm(2)));
69 void *__bound_ptr_indir8(void *p
, int offset
) __attribute__((regparm(2)));
70 void *__bound_ptr_indir12(void *p
, int offset
) __attribute__((regparm(2)));
71 void *__bound_ptr_indir16(void *p
, int offset
) __attribute__((regparm(2)));
72 void __bound_local_new(void *p
) __attribute__((regparm(1)));
73 void __bound_local_delete(void *p
) __attribute__((regparm(1)));
75 static void *get_caller_pc(int n
);
77 void *__bound_malloc(size_t size
, const void *caller
);
78 void *__bound_memalign(size_t size
, size_t align
, const void *caller
);
79 void __bound_free(void *ptr
, const void *caller
);
80 void *__bound_realloc(void *ptr
, size_t size
, const void *caller
);
81 static void *libc_malloc(size_t size
);
82 static void libc_free(void *ptr
);
83 static void install_malloc_hooks(void);
84 static void restore_malloc_hooks(void);
86 #ifdef CONFIG_TCC_MALLOC_HOOKS
87 static void *saved_malloc_hook
;
88 static void *saved_free_hook
;
89 static void *saved_realloc_hook
;
90 static void *saved_memalign_hook
;
96 static BoundEntry
*__bound_t1
[BOUND_T1_SIZE
]; /* page table */
98 static BoundEntry
**__bound_t1
; /* page table */
100 static BoundEntry
*__bound_empty_t2
; /* empty page, for unused pages */
101 static BoundEntry
*__bound_invalid_t2
; /* invalid page, for invalid pointers */
103 static BoundEntry
*__bound_find_region(BoundEntry
*e1
, void *p
)
105 unsigned long addr
, tmp
;
110 addr
= (unsigned long)p
;
112 if (addr
<= e
->size
) {
113 /* put region at the head */
115 e1
->start
= e
->start
;
124 /* no entry found: return empty entry or invalid entry */
126 return __bound_invalid_t2
;
128 return __bound_empty_t2
;
131 /* print a bound error message */
132 static void bound_error(const void *caller
, const char *fmt
, ...)
134 rt_error((unsigned long)caller
, "%s", fmt
);
137 static void bound_alloc_error(void)
139 bound_error(NULL
, "not enough memory for bound checking code");
142 /* currently, tcc cannot compile that because we use GNUC extensions */
143 #if !defined(__TINYC__)
145 /* return '(p + offset)' for pointer arithmetic (a pointer can reach
146 the end of a region in this case */
147 void *__bound_ptr_add(void *p
, int offset
)
149 unsigned long addr
= (unsigned long)p
;
151 #if defined(BOUND_DEBUG)
152 printf("add: 0x%x %d\n", (int)p
, offset
);
155 e
= __bound_t1
[addr
>> (BOUND_T2_BITS
+ BOUND_T3_BITS
)];
156 e
= (BoundEntry
*)((char *)e
+
157 ((addr
>> (BOUND_T3_BITS
- BOUND_E_BITS
)) &
158 ((BOUND_T2_SIZE
- 1) << BOUND_E_BITS
)));
160 if (addr
> e
->size
) {
161 e
= __bound_find_region(e
, p
);
162 addr
= (unsigned long)p
- e
->start
;
166 return INVALID_POINTER
; /* return an invalid pointer */
170 /* return '(p + offset)' for pointer indirection (the resulting must
171 be strictly inside the region */
172 #define BOUND_PTR_INDIR(dsize) \
173 void *__bound_ptr_indir ## dsize (void *p, int offset) \
175 unsigned long addr = (unsigned long)p; \
178 e = __bound_t1[addr >> (BOUND_T2_BITS + BOUND_T3_BITS)]; \
179 e = (BoundEntry *)((char *)e + \
180 ((addr >> (BOUND_T3_BITS - BOUND_E_BITS)) & \
181 ((BOUND_T2_SIZE - 1) << BOUND_E_BITS))); \
183 if (addr > e->size) { \
184 e = __bound_find_region(e, p); \
185 addr = (unsigned long)p - e->start; \
187 addr += offset + dsize; \
188 if (addr > e->size) \
189 return INVALID_POINTER; /* return an invalid pointer */ \
195 /* return the PC of the N'th caller (N=1: first caller) */
196 static void *get_caller_pc(int n
)
201 __asm__
__volatile__ ("movl %%ebp,%0" :"=g" (fp
));
203 fp
= ((unsigned long *)fp
)[0];
204 return ((void **)fp
)[1];
207 /* return the frame pointer of the caller */
208 #define GET_CALLER_FP(fp)\
211 __asm__ __volatile__ ("movl %%ebp,%0" :"=g" (fp1));\
215 #error put code to extract the calling frame pointer
218 /* called when entering a function to add all the local regions */
219 void __bound_local_new(void *p1
)
221 unsigned long addr
, size
, fp
, *p
= p1
;
230 __bound_new_region((void *)addr
, size
);
234 /* called when leaving a function to delete all the local regions */
235 void __bound_local_delete(void *p1
)
237 unsigned long addr
, fp
, *p
= p1
;
245 __bound_delete_region((void *)addr
);
251 void __bound_local_new(void *p
)
254 void __bound_local_delete(void *p
)
258 void *__bound_ptr_add(void *p
, int offset
)
263 #define BOUND_PTR_INDIR(dsize) \
264 void *__bound_ptr_indir ## dsize (void *p, int offset) \
269 static void *get_caller_pc(int n
)
282 static BoundEntry
*__bound_new_page(void)
287 page
= libc_malloc(sizeof(BoundEntry
) * BOUND_T2_SIZE
);
290 for(i
=0;i
<BOUND_T2_SIZE
;i
++) {
291 /* put empty entries */
293 page
[i
].size
= EMPTY_SIZE
;
295 page
[i
].is_invalid
= 0;
300 /* currently we use malloc(). Should use bound_new_page() */
301 static BoundEntry
*bound_new_entry(void)
304 e
= libc_malloc(sizeof(BoundEntry
));
308 static void bound_free_entry(BoundEntry
*e
)
313 static inline BoundEntry
*get_page(int index
)
316 page
= __bound_t1
[index
];
317 if (page
== __bound_empty_t2
|| page
== __bound_invalid_t2
) {
318 /* create a new page if necessary */
319 page
= __bound_new_page();
320 __bound_t1
[index
] = page
;
325 /* mark a region as being invalid (can only be used during init) */
326 static void mark_invalid(unsigned long addr
, unsigned long size
)
328 unsigned long start
, end
;
330 int t1_start
, t1_end
, i
, j
, t2_start
, t2_end
;
335 t2_start
= (start
+ BOUND_T3_SIZE
- 1) >> BOUND_T3_BITS
;
337 t2_end
= end
>> BOUND_T3_BITS
;
339 t2_end
= 1 << (BOUND_T1_BITS
+ BOUND_T2_BITS
);
342 printf("mark_invalid: start = %x %x\n", t2_start
, t2_end
);
345 /* first we handle full pages */
346 t1_start
= (t2_start
+ BOUND_T2_SIZE
- 1) >> BOUND_T2_BITS
;
347 t1_end
= t2_end
>> BOUND_T2_BITS
;
349 i
= t2_start
& (BOUND_T2_SIZE
- 1);
350 j
= t2_end
& (BOUND_T2_SIZE
- 1);
352 if (t1_start
== t1_end
) {
353 page
= get_page(t2_start
>> BOUND_T2_BITS
);
355 page
[i
].size
= INVALID_SIZE
;
356 page
[i
].is_invalid
= 1;
360 page
= get_page(t2_start
>> BOUND_T2_BITS
);
361 for(; i
< BOUND_T2_SIZE
; i
++) {
362 page
[i
].size
= INVALID_SIZE
;
363 page
[i
].is_invalid
= 1;
366 for(i
= t1_start
; i
< t1_end
; i
++) {
367 __bound_t1
[i
] = __bound_invalid_t2
;
370 page
= get_page(t1_end
);
371 for(i
= 0; i
< j
; i
++) {
372 page
[i
].size
= INVALID_SIZE
;
373 page
[i
].is_invalid
= 1;
379 void __bound_init(void)
383 unsigned long start
, size
;
385 /* save malloc hooks and install bound check hooks */
386 install_malloc_hooks();
389 __bound_t1
= libc_malloc(BOUND_T1_SIZE
* sizeof(BoundEntry
*));
393 __bound_empty_t2
= __bound_new_page();
394 for(i
=0;i
<BOUND_T1_SIZE
;i
++) {
395 __bound_t1
[i
] = __bound_empty_t2
;
398 page
= __bound_new_page();
399 for(i
=0;i
<BOUND_T2_SIZE
;i
++) {
400 /* put invalid entries */
402 page
[i
].size
= INVALID_SIZE
;
404 page
[i
].is_invalid
= 1;
406 __bound_invalid_t2
= page
;
408 /* invalid pointer zone */
409 start
= (unsigned long)INVALID_POINTER
& ~(BOUND_T23_SIZE
- 1);
410 size
= BOUND_T23_SIZE
;
411 mark_invalid(start
, size
);
413 #if !defined(__TINYC__) && defined(CONFIG_TCC_MALLOC_HOOKS)
414 /* malloc zone is also marked invalid. can only use that with
415 hooks because all libs should use the same malloc. The solution
416 would be to build a new malloc for tcc. */
417 start
= (unsigned long)&_end
;
418 size
= 128 * 0x100000;
419 mark_invalid(start
, size
);
423 static inline void add_region(BoundEntry
*e
,
424 unsigned long start
, unsigned long size
)
428 /* no region : add it */
432 /* already regions in the list: add it at the head */
433 e1
= bound_new_entry();
434 e1
->start
= e
->start
;
443 /* create a new region. It should not already exist in the region list */
444 void __bound_new_region(void *p
, unsigned long size
)
446 unsigned long start
, end
;
447 BoundEntry
*page
, *e
, *e2
;
448 int t1_start
, t1_end
, i
, t2_start
, t2_end
;
450 start
= (unsigned long)p
;
452 t1_start
= start
>> (BOUND_T2_BITS
+ BOUND_T3_BITS
);
453 t1_end
= end
>> (BOUND_T2_BITS
+ BOUND_T3_BITS
);
456 page
= get_page(t1_start
);
457 t2_start
= (start
>> (BOUND_T3_BITS
- BOUND_E_BITS
)) &
458 ((BOUND_T2_SIZE
- 1) << BOUND_E_BITS
);
459 t2_end
= (end
>> (BOUND_T3_BITS
- BOUND_E_BITS
)) &
460 ((BOUND_T2_SIZE
- 1) << BOUND_E_BITS
);
462 printf("new %lx %lx %x %x %x %x\n",
463 start
, end
, t1_start
, t1_end
, t2_start
, t2_end
);
466 e
= (BoundEntry
*)((char *)page
+ t2_start
);
467 add_region(e
, start
, size
);
469 if (t1_end
== t1_start
) {
470 /* same ending page */
471 e2
= (BoundEntry
*)((char *)page
+ t2_end
);
478 add_region(e
, start
, size
);
481 /* mark until end of page */
482 e2
= page
+ BOUND_T2_SIZE
;
488 /* mark intermediate pages, if any */
489 for(i
=t1_start
+1;i
<t1_end
;i
++) {
491 e2
= page
+ BOUND_T2_SIZE
;
492 for(e
=page
;e
<e2
;e
++) {
498 page
= get_page(t1_end
);
499 e2
= (BoundEntry
*)((char *)page
+ t2_end
);
500 for(e
=page
;e
<e2
;e
++) {
504 add_region(e
, start
, size
);
508 /* delete a region */
509 static inline void delete_region(BoundEntry
*e
,
510 void *p
, unsigned long empty_size
)
515 addr
= (unsigned long)p
;
517 if (addr
<= e
->size
) {
518 /* region found is first one */
521 /* no more region: mark it empty */
523 e
->size
= empty_size
;
525 /* copy next region in head */
526 e
->start
= e1
->start
;
529 bound_free_entry(e1
);
532 /* find the matching region */
536 /* region not found: do nothing */
539 addr
= (unsigned long)p
- e
->start
;
540 if (addr
<= e
->size
) {
541 /* found: remove entry */
550 /* WARNING: 'p' must be the starting point of the region. */
551 /* return non zero if error */
552 int __bound_delete_region(void *p
)
554 unsigned long start
, end
, addr
, size
, empty_size
;
555 BoundEntry
*page
, *e
, *e2
;
556 int t1_start
, t1_end
, t2_start
, t2_end
, i
;
558 start
= (unsigned long)p
;
559 t1_start
= start
>> (BOUND_T2_BITS
+ BOUND_T3_BITS
);
560 t2_start
= (start
>> (BOUND_T3_BITS
- BOUND_E_BITS
)) &
561 ((BOUND_T2_SIZE
- 1) << BOUND_E_BITS
);
563 /* find region size */
564 page
= __bound_t1
[t1_start
];
565 e
= (BoundEntry
*)((char *)page
+ t2_start
);
566 addr
= start
- e
->start
;
568 e
= __bound_find_region(e
, p
);
569 /* test if invalid region */
570 if (e
->size
== EMPTY_SIZE
|| (unsigned long)p
!= e
->start
)
572 /* compute the size we put in invalid regions */
574 empty_size
= INVALID_SIZE
;
576 empty_size
= EMPTY_SIZE
;
580 /* now we can free each entry */
581 t1_end
= end
>> (BOUND_T2_BITS
+ BOUND_T3_BITS
);
582 t2_end
= (end
>> (BOUND_T3_BITS
- BOUND_E_BITS
)) &
583 ((BOUND_T2_SIZE
- 1) << BOUND_E_BITS
);
585 delete_region(e
, p
, empty_size
);
586 if (t1_end
== t1_start
) {
587 /* same ending page */
588 e2
= (BoundEntry
*)((char *)page
+ t2_end
);
593 e
->size
= empty_size
;
595 delete_region(e
, p
, empty_size
);
598 /* mark until end of page */
599 e2
= page
+ BOUND_T2_SIZE
;
603 e
->size
= empty_size
;
605 /* mark intermediate pages, if any */
606 /* XXX: should free them */
607 for(i
=t1_start
+1;i
<t1_end
;i
++) {
609 e2
= page
+ BOUND_T2_SIZE
;
610 for(e
=page
;e
<e2
;e
++) {
612 e
->size
= empty_size
;
616 page
= get_page(t2_end
);
617 e2
= (BoundEntry
*)((char *)page
+ t2_end
);
618 for(e
=page
;e
<e2
;e
++) {
620 e
->size
= empty_size
;
622 delete_region(e
, p
, empty_size
);
627 /* return the size of the region starting at p, or EMPTY_SIZE if non
629 static unsigned long get_region_size(void *p
)
631 unsigned long addr
= (unsigned long)p
;
634 e
= __bound_t1
[addr
>> (BOUND_T2_BITS
+ BOUND_T3_BITS
)];
635 e
= (BoundEntry
*)((char *)e
+
636 ((addr
>> (BOUND_T3_BITS
- BOUND_E_BITS
)) &
637 ((BOUND_T2_SIZE
- 1) << BOUND_E_BITS
)));
640 e
= __bound_find_region(e
, p
);
641 if (e
->start
!= (unsigned long)p
)
646 /* patched memory functions */
648 static void install_malloc_hooks(void)
650 #ifdef CONFIG_TCC_MALLOC_HOOKS
651 saved_malloc_hook
= __malloc_hook
;
652 saved_free_hook
= __free_hook
;
653 saved_realloc_hook
= __realloc_hook
;
654 saved_memalign_hook
= __memalign_hook
;
655 __malloc_hook
= __bound_malloc
;
656 __free_hook
= __bound_free
;
657 __realloc_hook
= __bound_realloc
;
658 __memalign_hook
= __bound_memalign
;
662 static void restore_malloc_hooks(void)
664 #ifdef CONFIG_TCC_MALLOC_HOOKS
665 __malloc_hook
= saved_malloc_hook
;
666 __free_hook
= saved_free_hook
;
667 __realloc_hook
= saved_realloc_hook
;
668 __memalign_hook
= saved_memalign_hook
;
672 static void *libc_malloc(size_t size
)
675 restore_malloc_hooks();
677 install_malloc_hooks();
681 static void libc_free(void *ptr
)
683 restore_malloc_hooks();
685 install_malloc_hooks();
688 /* XXX: we should use a malloc which ensure that it is unlikely that
689 two malloc'ed data have the same address if 'free' are made in
691 void *__bound_malloc(size_t size
, const void *caller
)
695 /* we allocate one more byte to ensure the regions will be
696 separated by at least one byte. With the glibc malloc, it may
697 be in fact not necessary */
698 ptr
= libc_malloc(size
+ 1);
702 __bound_new_region(ptr
, size
);
706 void *__bound_memalign(size_t size
, size_t align
, const void *caller
)
710 restore_malloc_hooks();
712 /* we allocate one more byte to ensure the regions will be
713 separated by at least one byte. With the glibc malloc, it may
714 be in fact not necessary */
715 ptr
= memalign(size
+ 1, align
);
717 install_malloc_hooks();
721 __bound_new_region(ptr
, size
);
725 void __bound_free(void *ptr
, const void *caller
)
729 if (__bound_delete_region(ptr
) != 0)
730 bound_error(caller
, "freeing invalid region");
735 void *__bound_realloc(void *ptr
, size_t size
, const void *caller
)
741 __bound_free(ptr
, caller
);
744 ptr1
= __bound_malloc(size
, caller
);
745 if (ptr
== NULL
|| ptr1
== NULL
)
747 old_size
= get_region_size(ptr
);
748 if (old_size
== EMPTY_SIZE
)
749 bound_error(caller
, "realloc'ing invalid pointer");
750 memcpy(ptr1
, ptr
, old_size
);
751 __bound_free(ptr
, caller
);
756 #ifndef CONFIG_TCC_MALLOC_HOOKS
757 void *__bound_calloc(size_t nmemb
, size_t size
)
761 ptr
= __bound_malloc(size
);
764 memset(ptr
, 0, size
);
770 static void bound_dump(void)
772 BoundEntry
*page
, *e
;
775 printf("region dump:\n");
776 for(i
=0;i
<BOUND_T1_SIZE
;i
++) {
777 page
= __bound_t1
[i
];
778 for(j
=0;j
<BOUND_T2_SIZE
;j
++) {
780 /* do not print invalid or empty entries */
781 if (e
->size
!= EMPTY_SIZE
&& e
->start
!= 0) {
783 (i
<< (BOUND_T2_BITS
+ BOUND_T3_BITS
)) +
784 (j
<< BOUND_T3_BITS
));
786 printf(" %08lx:%08lx", e
->start
, e
->start
+ e
->size
);
796 /* some useful checked functions */
798 /* check that (p ... p + size - 1) lies inside 'p' region, if any */
799 static void __bound_check(const void *p
, size_t size
)
803 p
= __bound_ptr_add((void *)p
, size
);
804 if (p
== INVALID_POINTER
)
805 bound_error(get_caller_pc(2), "invalid pointer");
808 void *__bound_memcpy(void *dst
, const void *src
, size_t size
)
810 __bound_check(dst
, size
);
811 __bound_check(src
, size
);
812 /* check also region overlap */
813 if (src
>= dst
&& src
< dst
+ size
)
814 bound_error(get_caller_pc(1), "overlapping regions in memcpy()");
815 return memcpy(dst
, src
, size
);
818 void *__bound_memmove(void *dst
, const void *src
, size_t size
)
820 __bound_check(dst
, size
);
821 __bound_check(src
, size
);
822 return memmove(dst
, src
, size
);
825 void *__bound_memset(void *dst
, int c
, size_t size
)
827 __bound_check(dst
, size
);
828 return memset(dst
, c
, size
);
831 /* XXX: could be optimized */
832 int __bound_strlen(const char *s
)
839 p
= __bound_ptr_indir1((char *)s
, len
);
840 if (p
== INVALID_POINTER
)
841 bound_error(get_caller_pc(1), "bad pointer in strlen()");
849 char *__bound_strcpy(char *dst
, const char *src
)
852 len
= __bound_strlen(src
);
853 return __bound_memcpy(dst
, src
, len
+ 1);
856 /* resolve bound check syms */
857 typedef struct BCSyms
{
862 static BCSyms bcheck_syms
[] = {
863 #ifndef CONFIG_TCC_MALLOC_HOOKS
864 { "malloc", __bound_malloc
},
865 { "free", __bound_free
},
866 { "realloc", __bound_realloc
},
867 { "memalign", __bound_memalign
},
868 { "calloc", __bound_calloc
},
870 { "memcpy", __bound_memcpy
},
871 { "memmove", __bound_memmove
},
872 { "memset", __bound_memset
},
873 { "strlen", __bound_strlen
},
874 { "strcpy", __bound_strcpy
},
878 static void *bound_resolve_sym(const char *sym
)
882 while (p
->str
!= NULL
) {
883 if (!strcmp(p
->str
, sym
))