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 void *__bound_ptr_add(void *p
, int offset
) __attribute__((regparm(2)));
60 char __bound_ptr_indir_b(void *p
, int offset
) __attribute__((regparm(2)));
61 unsigned char __bound_ptr_indir_ub(void *p
, int offset
) __attribute__((regparm(2)));
62 short __bound_ptr_indir_w(void *p
, int offset
) __attribute__((regparm(2)));
63 unsigned short __bound_ptr_indir_uw(void *p
, int offset
) __attribute__((regparm(2)));
64 int __bound_ptr_indir_i(void *p
, int offset
) __attribute((regparm(2)));
66 void *__bound_calloc(size_t nmemb
, size_t size
);
67 void *__bound_malloc(size_t size
);
68 void __bound_free(void *ptr
);
69 void *__bound_realloc(void *ptr
, size_t size
);
74 static BoundEntry
*__bound_t1
[BOUND_T1_SIZE
]; /* page table */
76 static BoundEntry
**__bound_t1
; /* page table */
78 static BoundEntry
*__bound_empty_t2
; /* empty page, for unused pages */
79 static BoundEntry
*__bound_invalid_t2
; /* invalid page, for invalid pointers */
81 static BoundEntry
*__bound_find_region(BoundEntry
*e1
, void *p
)
83 unsigned long addr
, tmp
;
88 addr
= (unsigned long)p
;
90 if (addr
<= e
->size
) {
91 /* put region at the head */
102 /* no entry found: return empty entry or invalid entry */
104 return __bound_invalid_t2
;
106 return __bound_empty_t2
;
109 static void bound_error(const char *fmt
, ...)
113 fprintf(stderr
, "bounds check: ");
114 vfprintf(stderr
, fmt
, ap
);
115 fprintf(stderr
, "\n");
120 static void bound_alloc_error(void)
122 bound_error("not enough memory for bound checking code\n");
125 /* do: return (p + offset); */
126 void *__bound_ptr_add(void *p
, int offset
)
128 unsigned long addr
= (unsigned long)p
;
131 printf("add: 0x%x %d\n", (int)p
, offset
);
134 e
= __bound_t1
[addr
>> (BOUND_T2_BITS
+ BOUND_T3_BITS
)];
135 e
= (BoundEntry
*)((char *)e
+
136 ((addr
>> (BOUND_T3_BITS
- BOUND_E_BITS
)) &
137 ((BOUND_T2_SIZE
- 1) << BOUND_E_BITS
)));
139 if (addr
> e
->size
) {
140 e
= __bound_find_region(e
, p
);
141 addr
= (unsigned long)p
- e
->start
;
145 return INVALID_POINTER
; /* return an invalid pointer */
149 /* do: return *(type *)(p + offset); */
150 #define BOUND_PTR_INDIR(suffix, type) \
151 type __bound_ptr_indir_ ## suffix (void *p, int offset) \
153 unsigned long addr = (unsigned long)p; \
156 e = __bound_t1[addr >> (BOUND_T2_BITS + BOUND_T3_BITS)]; \
157 e = (BoundEntry *)((char *)e + \
158 ((addr >> (BOUND_T3_BITS - BOUND_E_BITS)) & \
159 ((BOUND_T2_SIZE - 1) << BOUND_E_BITS))); \
161 if (addr > e->size) { \
162 e = __bound_find_region(e, p); \
163 addr = (unsigned long)p - e->start; \
165 addr += offset + sizeof(type); \
166 if (addr > e->size) \
167 bound_error("dereferencing invalid pointer"); \
168 return *(type *)(p + offset); \
171 BOUND_PTR_INDIR(b
, char)
172 BOUND_PTR_INDIR(ub
, unsigned char)
173 BOUND_PTR_INDIR(w
, short)
174 BOUND_PTR_INDIR(uw
, unsigned short)
175 BOUND_PTR_INDIR(i
, int)
177 static BoundEntry
*__bound_new_page(void)
182 page
= malloc(sizeof(BoundEntry
) * BOUND_T2_SIZE
);
185 for(i
=0;i
<BOUND_T2_SIZE
;i
++) {
186 /* put empty entries */
188 page
[i
].size
= EMPTY_SIZE
;
190 page
[i
].is_invalid
= 0;
195 /* currently we use malloc(). Should use bound_new_page() */
196 static BoundEntry
*bound_new_entry(void)
199 e
= malloc(sizeof(BoundEntry
));
203 static void bound_free_entry(BoundEntry
*e
)
208 static inline BoundEntry
*get_page(int index
)
211 page
= __bound_t1
[index
];
212 if (page
== __bound_empty_t2
|| page
== __bound_invalid_t2
) {
213 /* create a new page if necessary */
214 page
= __bound_new_page();
215 __bound_t1
[index
] = page
;
220 /* mark a region as being invalid (can only be used during init) */
221 static void mark_invalid(unsigned long addr
, unsigned long size
)
223 unsigned long start
, end
;
225 int t1_start
, t1_end
, i
, j
, t2_start
, t2_end
;
230 t2_start
= (start
+ BOUND_T3_SIZE
- 1) >> BOUND_T3_BITS
;
232 t2_end
= end
>> BOUND_T3_BITS
;
234 t2_end
= 1 << (BOUND_T1_BITS
+ BOUND_T2_BITS
);
236 printf("mark_invalid: start = %x %x\n", t2_start
, t2_end
);
238 /* first we handle full pages */
239 t1_start
= (t2_start
+ BOUND_T2_SIZE
- 1) >> BOUND_T2_BITS
;
240 t1_end
= t2_end
>> BOUND_T2_BITS
;
242 i
= t2_start
& (BOUND_T2_SIZE
- 1);
243 j
= t2_end
& (BOUND_T2_SIZE
- 1);
245 if (t1_start
== t1_end
) {
246 page
= get_page(t2_start
>> BOUND_T2_BITS
);
248 page
[i
].size
= INVALID_SIZE
;
249 page
[i
].is_invalid
= 1;
253 page
= get_page(t2_start
>> BOUND_T2_BITS
);
254 for(; i
< BOUND_T2_SIZE
; i
++) {
255 page
[i
].size
= INVALID_SIZE
;
256 page
[i
].is_invalid
= 1;
259 for(i
= t1_start
; i
< t1_end
; i
++) {
260 __bound_t1
[i
] = __bound_invalid_t2
;
263 page
= get_page(t1_end
);
264 for(i
= 0; i
< j
; i
++) {
265 page
[i
].size
= INVALID_SIZE
;
266 page
[i
].is_invalid
= 1;
272 void __bound_init(void)
276 unsigned long start
, size
;
279 __bound_t1
= malloc(BOUND_T1_SIZE
* sizeof(BoundEntry
*));
283 __bound_empty_t2
= __bound_new_page();
284 for(i
=0;i
<BOUND_T1_SIZE
;i
++) {
285 __bound_t1
[i
] = __bound_empty_t2
;
288 page
= __bound_new_page();
289 for(i
=0;i
<BOUND_T2_SIZE
;i
++) {
290 /* put invalid entries */
292 page
[i
].size
= INVALID_SIZE
;
294 page
[i
].is_invalid
= 1;
296 __bound_invalid_t2
= page
;
298 /* invalid pointer zone */
299 start
= (unsigned long)INVALID_POINTER
& ~(BOUND_T23_SIZE
- 1);
300 size
= BOUND_T23_SIZE
;
301 mark_invalid(start
, size
);
303 /* malloc zone is also marked invalid */
304 start
= (unsigned long)&_end
;
305 size
= 128 * 0x100000;
306 mark_invalid(start
, size
);
309 static inline void add_region(BoundEntry
*e
,
310 unsigned long start
, unsigned long size
)
314 /* no region : add it */
318 /* already regions in the list: add it at the head */
319 e1
= bound_new_entry();
320 e1
->start
= e
->start
;
329 /* create a new region. It should not already exist in the region list */
330 void __bound_new_region(void *p
, unsigned long size
)
332 unsigned long start
, end
;
333 BoundEntry
*page
, *e
, *e2
;
334 int t1_start
, t1_end
, i
, t2_start
, t2_end
;
336 start
= (unsigned long)p
;
338 t1_start
= start
>> (BOUND_T2_BITS
+ BOUND_T3_BITS
);
339 t1_end
= end
>> (BOUND_T2_BITS
+ BOUND_T3_BITS
);
342 page
= get_page(t1_start
);
343 t2_start
= (start
>> (BOUND_T3_BITS
- BOUND_E_BITS
)) &
344 ((BOUND_T2_SIZE
- 1) << BOUND_E_BITS
);
345 t2_end
= (end
>> (BOUND_T3_BITS
- BOUND_E_BITS
)) &
346 ((BOUND_T2_SIZE
- 1) << BOUND_E_BITS
);
348 printf("new %lx %lx %x %x %x %x\n",
349 start
, end
, t1_start
, t1_end
, t2_start
, t2_end
);
352 e
= (BoundEntry
*)((char *)page
+ t2_start
);
353 add_region(e
, start
, size
);
355 if (t1_end
== t1_start
) {
356 /* same ending page */
357 e2
= (BoundEntry
*)((char *)page
+ t2_end
);
364 add_region(e
, start
, size
);
367 /* mark until end of page */
368 e2
= page
+ BOUND_T2_SIZE
;
374 /* mark intermediate pages, if any */
375 for(i
=t1_start
+1;i
<t1_end
;i
++) {
377 e2
= page
+ BOUND_T2_SIZE
;
378 for(e
=page
;e
<e2
;e
++) {
384 page
= get_page(t2_end
);
385 e2
= (BoundEntry
*)((char *)page
+ t2_end
);
386 for(e
=page
;e
<e2
;e
++) {
390 add_region(e
, start
, size
);
394 /* delete a region */
395 static inline void delete_region(BoundEntry
*e
,
396 void *p
, unsigned long empty_size
)
401 addr
= (unsigned long)p
;
403 if (addr
<= e
->size
) {
404 /* region found is first one */
407 /* no more region: mark it empty */
409 e
->size
= empty_size
;
411 /* copy next region in head */
412 e
->start
= e1
->start
;
415 bound_free_entry(e1
);
418 /* find the matching region */
422 /* region not found: do nothing */
425 addr
= (unsigned long)p
- e
->start
;
426 if (addr
<= e
->size
) {
427 /* found: remove entry */
436 /* WARNING: 'p' must be the starting point of the region. */
437 void __bound_delete_region(void *p
)
439 unsigned long start
, end
, addr
, size
, empty_size
;
440 BoundEntry
*page
, *e
, *e2
;
441 int t1_start
, t1_end
, t2_start
, t2_end
, i
;
443 start
= (unsigned long)p
;
444 t1_start
= start
>> (BOUND_T2_BITS
+ BOUND_T3_BITS
);
445 t2_start
= (start
>> (BOUND_T3_BITS
- BOUND_E_BITS
)) &
446 ((BOUND_T2_SIZE
- 1) << BOUND_E_BITS
);
448 /* find region size */
449 page
= __bound_t1
[t1_start
];
450 e
= (BoundEntry
*)((char *)page
+ t2_start
);
451 addr
= start
- e
->start
;
453 e
= __bound_find_region(e
, p
);
454 /* test if invalid region */
455 if (e
->size
== EMPTY_SIZE
|| (unsigned long)p
!= e
->start
)
456 bound_error("deleting invalid region");
457 /* compute the size we put in invalid regions */
459 empty_size
= INVALID_SIZE
;
461 empty_size
= EMPTY_SIZE
;
465 /* now we can free each entry */
466 t1_end
= end
>> (BOUND_T2_BITS
+ BOUND_T3_BITS
);
467 t2_end
= (end
>> (BOUND_T3_BITS
- BOUND_E_BITS
)) &
468 ((BOUND_T2_SIZE
- 1) << BOUND_E_BITS
);
470 delete_region(e
, p
, empty_size
);
471 if (t1_end
== t1_start
) {
472 /* same ending page */
473 e2
= (BoundEntry
*)((char *)page
+ t2_end
);
478 e
->size
= empty_size
;
480 delete_region(e
, p
, empty_size
);
483 /* mark until end of page */
484 e2
= page
+ BOUND_T2_SIZE
;
488 e
->size
= empty_size
;
490 /* mark intermediate pages, if any */
491 /* XXX: should free them */
492 for(i
=t1_start
+1;i
<t1_end
;i
++) {
494 e2
= page
+ BOUND_T2_SIZE
;
495 for(e
=page
;e
<e2
;e
++) {
497 e
->size
= empty_size
;
501 page
= get_page(t2_end
);
502 e2
= (BoundEntry
*)((char *)page
+ t2_end
);
503 for(e
=page
;e
<e2
;e
++) {
505 e
->size
= empty_size
;
507 delete_region(e
, p
, empty_size
);
511 /* return the size of the region starting at p, or EMPTY_SIZE if non
513 static unsigned long get_region_size(void *p
)
515 unsigned long addr
= (unsigned long)p
;
518 e
= __bound_t1
[addr
>> (BOUND_T2_BITS
+ BOUND_T3_BITS
)];
519 e
= (BoundEntry
*)((char *)e
+
520 ((addr
>> (BOUND_T3_BITS
- BOUND_E_BITS
)) &
521 ((BOUND_T2_SIZE
- 1) << BOUND_E_BITS
)));
524 e
= __bound_find_region(e
, p
);
525 if (e
->start
!= (unsigned long)p
)
530 /* patched memory functions */
532 /* XXX: we should use a malloc which ensure that it is unlikely that
533 two malloc'ed data have the same address if 'free' are made in
535 void *__bound_malloc(size_t size
)
538 /* we allocate one more byte to ensure the regions will be
539 separated by at least one byte. With the glibc malloc, it may
540 be in fact not necessary */
541 ptr
= malloc(size
+ 1);
544 __bound_new_region(ptr
, size
);
548 void __bound_free(void *ptr
)
552 __bound_delete_region(ptr
);
556 void *__bound_realloc(void *ptr
, size_t size
)
565 ptr1
= __bound_malloc(size
);
566 if (ptr
== NULL
|| ptr1
== NULL
)
568 old_size
= get_region_size(ptr
);
569 if (old_size
== EMPTY_SIZE
)
570 bound_error("realloc'ing invalid pointer");
571 memcpy(ptr1
, ptr
, old_size
);
577 void *__bound_calloc(size_t nmemb
, size_t size
)
581 ptr
= __bound_malloc(size
);
584 memset(ptr
, 0, size
);
589 static void bound_dump(void)
591 BoundEntry
*page
, *e
;
594 printf("region dump:\n");
595 for(i
=0;i
<BOUND_T1_SIZE
;i
++) {
596 page
= __bound_t1
[i
];
597 for(j
=0;j
<BOUND_T2_SIZE
;j
++) {
599 /* do not print invalid or empty entries */
600 if (e
->size
!= EMPTY_SIZE
&& e
->start
!= 0) {
602 (i
<< (BOUND_T2_BITS
+ BOUND_T3_BITS
)) +
603 (j
<< BOUND_T3_BITS
));
605 printf(" %08lx:%08lx", e
->start
, e
->start
+ e
->size
);