From 359dbb8e7485c66a6707470ce9d5af0ddbbff9f8 Mon Sep 17 00:00:00 2001 From: otto <> Date: Sun, 4 Jun 2023 06:58:33 +0000 Subject: More thorough write-afetr-free checks. On free, chunks (the pieces of a pages used for smaller allocations) are junked and then validated after they leave the delayed free list. So after free, a chunk always contains junk bytes. This means that if we start with the right contents for a new page of chunks, we can *validate* instead of *write* junk bytes when (re)-using a chunk. With this, we can detect write-after-free when a chunk is recycled, not justy when a chunk is in the delayed free list. We do a little bit more work on initial allocation of a page of chunks and when re-using (as we validate now even on junk level 1). Also: some extra consistency checks for recallocaray(3) and fixes in error messages to make them more consistent, with man page bits. Plus regress additions. --- src/lib/libc/stdlib/malloc.c | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) (limited to 'src/lib/libc/stdlib/malloc.c') diff --git a/src/lib/libc/stdlib/malloc.c b/src/lib/libc/stdlib/malloc.c index 316ae4f484..c4196c74ed 100644 --- a/src/lib/libc/stdlib/malloc.c +++ b/src/lib/libc/stdlib/malloc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: malloc.c,v 1.284 2023/05/27 04:33:00 otto Exp $ */ +/* $OpenBSD: malloc.c,v 1.285 2023/06/04 06:58:33 otto Exp $ */ /* * Copyright (c) 2008, 2010, 2011, 2016, 2023 Otto Moerbeek * Copyright (c) 2012 Matthew Dempsky @@ -977,6 +977,10 @@ omalloc_make_chunks(struct dir_info *d, u_int bucket, u_int listnum) NULL)) goto err; LIST_INSERT_HEAD(&d->chunk_dir[bucket][listnum], bp, entries); + + if (bucket > 0 && d->malloc_junk != 0) + memset(pp, SOME_FREEJUNK, MALLOC_PAGESIZE); + return bp; err: @@ -1113,9 +1117,8 @@ found: p = (char *)bp->page + k; if (bp->bucket > 0) { - if (d->malloc_junk == 2) - memset(p, SOME_JUNK, B2SIZE(bp->bucket)); - else if (mopts.chunk_canaries) + validate_junk(d, p, B2SIZE(bp->bucket)); + if (mopts.chunk_canaries) fill_canary(p, size, B2SIZE(bp->bucket)); } return p; @@ -1134,7 +1137,7 @@ validate_canary(struct dir_info *d, u_char *ptr, size_t sz, size_t allocated) while (p < q) { if (*p != (u_char)mopts.chunk_canaries && *p != SOME_JUNK) { - wrterror(d, "chunk canary corrupted %p %#tx@%#zx%s", + wrterror(d, "canary corrupted %p %#tx@%#zx%s", ptr, p - ptr, sz, *p == SOME_FREEJUNK ? " (double free?)" : ""); } @@ -1157,7 +1160,7 @@ find_chunknum(struct dir_info *d, struct chunk_info *info, void *ptr, int check) wrterror(d, "modified chunk-pointer %p", ptr); if (info->bits[chunknum / MALLOC_BITS] & (1U << (chunknum % MALLOC_BITS))) - wrterror(d, "chunk is already free %p", ptr); + wrterror(d, "double free %p", ptr); if (check && info->bucket > 0) { validate_canary(d, ptr, info->bits[info->offset + chunknum], B2SIZE(info->bucket)); @@ -1924,13 +1927,22 @@ orecallocarray(struct dir_info **argpool, void *p, size_t oldsize, uint32_t chunknum = find_chunknum(pool, info, p, 0); if (info->bits[info->offset + chunknum] != oldsize) - wrterror(pool, "recorded old size %hu != %zu", + wrterror(pool, "recorded size %hu != %zu", info->bits[info->offset + chunknum], oldsize); + } else { + if (sz < oldsize) + wrterror(pool, "chunk size %zu < %zu", + sz, oldsize); } - } else if (oldsize < (sz - mopts.malloc_guard) / 2) - wrterror(pool, "recorded old size %zu != %zu", - sz - mopts.malloc_guard, oldsize); + } else { + if (sz - mopts.malloc_guard < oldsize) + wrterror(pool, "recorded size %zu < %zu", + sz - mopts.malloc_guard, oldsize); + if (oldsize < (sz - mopts.malloc_guard) / 2) + wrterror(pool, "recorded size %zu inconsistent with %zu", + sz - mopts.malloc_guard, oldsize); + } newptr = omalloc(pool, newsize, 0, f); if (newptr == NULL) -- cgit v1.2.3-55-g6feb