Hi,
Canaries and chunk validation on delayed free,
OK?
-Otto
Index: malloc.c
===================================================================
RCS file: /cvs/src/libexec/ld.so/malloc.c,v
retrieving revision 1.12
diff -u -p -r1.12 malloc.c
--- malloc.c 18 Sep 2016 13:54:58 -0000 1.12
+++ malloc.c 8 Oct 2016 16:37:18 -0000
@@ -50,7 +50,8 @@
#define MALLOC_DELAYED_CHUNK_MASK 15
#define MALLOC_INITIAL_REGIONS 512
#define MALLOC_DEFAULT_CACHE 64
-#define MALLOC_CHUNK_LISTS 4
+#define MALLOC_CHUNK_LISTS 4
+#define CHUNK_CHECK_LENGTH 32
/*
* When the P option is active, we move allocations between half a page
@@ -120,6 +121,7 @@ struct chunk_info {
u_short shift; /* how far to shift for this size */
u_short free; /* how many free chunks */
u_short total; /* how many chunk */
+ u_short offset; /* requested size table offset */
/* which chunks are free */
u_short bits[1];
};
@@ -130,6 +132,7 @@ struct malloc_readonly {
int malloc_freeunmap; /* mprotect free pages PROT_NONE? */
int malloc_junk; /* junk fill? */
int malloc_move; /* move allocations to end of page? */
+ int chunk_canaries; /* use canaries after chunks? */
size_t malloc_guard; /* use guard pages after allocations? */
u_int malloc_cache; /* free pages we cache */
u_int32_t malloc_canary; /* Matched against ones in g_pool */
@@ -354,6 +357,7 @@ omalloc_init(struct dir_info **dp)
*/
mopts.malloc_junk = 1;
mopts.malloc_move = 1;
+ mopts.chunk_canaries = 1;
mopts.malloc_cache = MALLOC_DEFAULT_CACHE;
mopts.malloc_guard = MALLOC_PAGESIZE;
@@ -459,6 +463,8 @@ alloc_chunk_info(struct dir_info *d, int
size = howmany(count, MALLOC_BITS);
size = sizeof(struct chunk_info) + (size - 1) * sizeof(u_short);
+ if (mopts.chunk_canaries)
+ size += count * sizeof(u_short);
size = ALIGN(size);
if (LIST_EMPTY(&d->chunk_info_list[bits])) {
@@ -604,6 +610,7 @@ omalloc_make_chunks(struct dir_info *d,
bp->size = 1U << bits;
bp->shift = bits;
bp->total = bp->free = MALLOC_PAGESIZE >> bits;
+ bp->offset = howmany(bp->total, MALLOC_BITS);
bp->page = pp;
}
@@ -633,16 +640,19 @@ omalloc_make_chunks(struct dir_info *d,
* Allocate a chunk
*/
static void *
-malloc_bytes(struct dir_info *d, size_t size)
+malloc_bytes(struct dir_info *d, size_t argsize)
{
int i, j, listnum;
- size_t k;
+ size_t k, size;
u_short u, *lp;
struct chunk_info *bp;
if (mopts.malloc_canary != (d->canary1 ^ (u_int32_t)(uintptr_t)d) ||
d->canary1 != ~d->canary2)
wrterror("internal struct corrupt");
+
+ size = argsize;
+
/* Don't bother with anything less than this */
/* unless we have a malloc(0) requests */
if (size != 0 && size < MALLOC_MINSIZE)
@@ -701,15 +711,32 @@ malloc_bytes(struct dir_info *d, size_t
/* Adjust to the real offset of that chunk */
k += (lp - bp->bits) * MALLOC_BITS;
+
+ if (mopts.chunk_canaries)
+ bp->bits[bp->offset + k] = argsize;
+
k <<= bp->shift;
+ if (bp->size > 0) {
+ if (mopts.malloc_junk == 2)
+ _dl_memset((char *)bp->page + k, SOME_JUNK, bp->size);
+ else if (mopts.chunk_canaries) {
+ size_t sz = bp->size - argsize;
+
+ if (sz > CHUNK_CHECK_LENGTH)
+ sz = CHUNK_CHECK_LENGTH;
+ _dl_memset((char *)bp->page + k + argsize, SOME_JUNK,
+ sz);
+ }
+ }
+
if (mopts.malloc_junk == 2 && bp->size > 0)
_dl_memset((char *)bp->page + k, SOME_JUNK, bp->size);
return ((char *)bp->page + k);
}
static uint32_t
-find_chunknum(struct dir_info *d, struct region_info *r, void *ptr)
+find_chunknum(struct dir_info *d, struct region_info *r, void *ptr, int check)
{
struct chunk_info *info;
uint32_t chunknum;
@@ -720,16 +747,28 @@ find_chunknum(struct dir_info *d, struct
/* Find the chunk number on the page */
chunknum = ((uintptr_t)ptr & MALLOC_PAGEMASK) >> info->shift;
+ if (check && mopts.chunk_canaries && info->size > 0) {
+ size_t sz = info->bits[info->offset + chunknum];
+ size_t check_sz = info->size - sz;
+ u_char *p, *q;
+
+ if (check_sz > CHUNK_CHECK_LENGTH)
+ check_sz = CHUNK_CHECK_LENGTH;
+ p = (u_char *)ptr + sz;
+ q = p + check_sz;
+
+ while (p < q)
+ if (*p++ != SOME_JUNK)
+ wrterror("chunk canary corrupted");
+ }
if ((uintptr_t)ptr & ((1U << (info->shift)) - 1)) {
wrterror("modified chunk-pointer");
return -1;
}
if (info->bits[chunknum / MALLOC_BITS] &
- (1U << (chunknum % MALLOC_BITS))) {
+ (1U << (chunknum % MALLOC_BITS)))
wrterror("chunk is already free");
- return -1;
- }
return chunknum;
}
@@ -745,8 +784,7 @@ free_bytes(struct dir_info *d, struct re
int listnum;
info = (struct chunk_info *)r->size;
- if ((chunknum = find_chunknum(d, r, ptr)) == -1)
- return;
+ chunknum = find_chunknum(d, r, ptr, 0);
info->bits[chunknum / MALLOC_BITS] |= 1U << (chunknum % MALLOC_BITS);
info->free++;
@@ -879,6 +917,26 @@ ret:
}
static void
+validate_junk(struct dir_info *pool, void *p)
+{
+ struct region_info *r;
+ size_t byte, sz;
+
+ if (p == NULL)
+ return;
+ r = find(pool, p);
+ if (r == NULL)
+ wrterror("bogus pointer in validate_junk");
+ REALSIZE(sz, r);
+ if (sz > CHUNK_CHECK_LENGTH)
+ sz = CHUNK_CHECK_LENGTH;
+ for (byte = 0; byte < sz; byte++) {
+ if (((unsigned char *)p)[byte] != SOME_FREEJUNK)
+ wrterror("use after free");
+ }
+}
+
+static void
ofree(void *p)
{
struct region_info *r;
@@ -919,17 +977,21 @@ ofree(void *p)
void *tmp;
int i;
- if (mopts.malloc_junk && sz > 0)
- _dl_memset(p, SOME_FREEJUNK, sz);
if (!mopts.malloc_freenow) {
- if (find_chunknum(g_pool, r, p) == -1)
- return;
+ find_chunknum(g_pool, r, p, 1);
+ if (mopts.malloc_junk && sz > 0)
+ _dl_memset(p, SOME_FREEJUNK, sz);
i = getrbyte(g_pool) & MALLOC_DELAYED_CHUNK_MASK;
tmp = p;
p = g_pool->delayed_chunks[i];
if (tmp == p)
wrterror("double free");
+ if (mopts.malloc_junk)
+ validate_junk(g_pool, p);
g_pool->delayed_chunks[i] = tmp;
+ } else {
+ if (mopts.malloc_junk && sz > 0)
+ _dl_memset(p, SOME_FREEJUNK, sz);
}
if (p != NULL) {
r = find(g_pool, p);