From: otto Date: Sun, 4 Jun 2023 06:58:33 +0000 (+0000) Subject: More thorough write-afetr-free checks. X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=e78208e22ef5fe1ff45739e90c952a0a77911687;p=openbsd 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. --- diff --git a/lib/libc/stdlib/malloc.3 b/lib/libc/stdlib/malloc.3 index 4957591eefd..d8936260510 100644 --- a/lib/libc/stdlib/malloc.3 +++ b/lib/libc/stdlib/malloc.3 @@ -30,9 +30,9 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $OpenBSD: malloc.3,v 1.132 2023/04/17 05:45:06 jmc Exp $ +.\" $OpenBSD: malloc.3,v 1.133 2023/06/04 06:58:33 otto Exp $ .\" -.Dd $Mdocdate: April 17 2023 $ +.Dd $Mdocdate: June 4 2023 $ .Dt MALLOC 3 .Os .Sh NAME @@ -314,7 +314,7 @@ Increase the junk level by one if it is smaller than 2. Decrease the junk level by one if it is larger than 0. Junking writes some junk bytes into the area allocated. Junk is bytes of 0xdb when allocating; -freed chunks are filled with 0xdf. +freed allocations are filled with 0xdf. By default the junk level is 1: after free, small chunks are completely junked; for pages the first part is junked. @@ -628,22 +628,24 @@ An attempt to .Fn free or reallocate an unallocated pointer was made. -.It Dq chunk is already free -There was an attempt to free a chunk that had already been freed. +.It Dq double free +There was an attempt to free an allocation that had already been freed. .It Dq write after free -A chunk has been modified after it was freed. +An allocation has been modified after it was freed. .It Dq modified chunk-pointer The pointer passed to .Fn free or a reallocation function has been modified. -.It Dq chunk canary corrupted address offset@length +.It Dq canary corrupted address offset@length A byte after the requested size has been overwritten, indicating a heap overflow. The offset at which corruption was detected is printed before the @, and the requested length of the allocation after the @. -.It Dq recorded old size oldsize != size +.It Dq recorded size oldsize inconsistent with size .Fn recallocarray -has detected that the given old size does not equal the recorded size in its +or +.Fn freezero +has detected that the given old size does not match the recorded size in its meta data. Enabling option .Cm C diff --git a/lib/libc/stdlib/malloc.c b/lib/libc/stdlib/malloc.c index 316ae4f484d..c4196c74ed0 100644 --- a/lib/libc/stdlib/malloc.c +++ b/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) diff --git a/regress/lib/libc/malloc/malloc_errs/malloc_errs.c b/regress/lib/libc/malloc/malloc_errs/malloc_errs.c index e0efb6ebf36..6040590a65d 100644 --- a/regress/lib/libc/malloc/malloc_errs/malloc_errs.c +++ b/regress/lib/libc/malloc/malloc_errs/malloc_errs.c @@ -1,4 +1,4 @@ -/* $OpenBSD: malloc_errs.c,v 1.2 2023/05/09 19:07:37 otto Exp $ */ +/* $OpenBSD: malloc_errs.c,v 1.3 2023/06/04 06:58:33 otto Exp $ */ /* * Copyright (c) 2023 Otto Moerbeek * @@ -138,9 +138,7 @@ t8(void) void t9(void) { - char *p; - - p = malloc(100); + char *p = malloc(100); p[100] = 0; free(p); } @@ -191,7 +189,6 @@ t15(void) void t16(void) { - abort(); /* not yet */ char *p = recallocarray(NULL, 0, 16, 1); char *q = recallocarray(p, 2, 3, 16); } @@ -208,11 +205,27 @@ t17(void) void t18(void) { - abort(); /* not yet */ char *p = recallocarray(NULL, 0, 1, getpagesize()); char *q = recallocarray(p, 2, 3, getpagesize()); } +/* recallocarray with wrong size, pages */ +void +t19(void) +{ + char *p = recallocarray(NULL, 0, 1, 10 * getpagesize()); + char *q = recallocarray(p, 1, 2, 4 * getpagesize()); +} + +/* canary check pages */ +void +t20(void) +{ + char *p = malloc(2*getpagesize() - 100); + p[2*getpagesize() - 100] = 0; + free(p); +} + struct test { void (*test)(void); const char *flags; @@ -238,6 +251,8 @@ struct test tests[] = { { t16, "" }, { t17, "C" }, { t18, "" }, + { t19, "" }, + { t20, "C" }, }; int main(int argc, char *argv[])