summaryrefslogtreecommitdiff
path: root/src/lib/libc/stdlib
diff options
context:
space:
mode:
authorotto <>2023-06-04 06:58:33 +0000
committerotto <>2023-06-04 06:58:33 +0000
commit359dbb8e7485c66a6707470ce9d5af0ddbbff9f8 (patch)
tree658d7037f67b98a6598726569c6933ca718927f6 /src/lib/libc/stdlib
parent2cbf6fb83a2b98d0b6ffbb161bcc85c81e5f2b59 (diff)
downloadopenbsd-359dbb8e7485c66a6707470ce9d5af0ddbbff9f8.tar.gz
openbsd-359dbb8e7485c66a6707470ce9d5af0ddbbff9f8.tar.bz2
openbsd-359dbb8e7485c66a6707470ce9d5af0ddbbff9f8.zip
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.
Diffstat (limited to 'src/lib/libc/stdlib')
-rw-r--r--src/lib/libc/stdlib/malloc.320
-rw-r--r--src/lib/libc/stdlib/malloc.c32
2 files changed, 33 insertions, 19 deletions
diff --git a/src/lib/libc/stdlib/malloc.3 b/src/lib/libc/stdlib/malloc.3
index 4957591eef..d893626051 100644
--- a/src/lib/libc/stdlib/malloc.3
+++ b/src/lib/libc/stdlib/malloc.3
@@ -30,9 +30,9 @@
30.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31.\" SUCH DAMAGE. 31.\" SUCH DAMAGE.
32.\" 32.\"
33.\" $OpenBSD: malloc.3,v 1.132 2023/04/17 05:45:06 jmc Exp $ 33.\" $OpenBSD: malloc.3,v 1.133 2023/06/04 06:58:33 otto Exp $
34.\" 34.\"
35.Dd $Mdocdate: April 17 2023 $ 35.Dd $Mdocdate: June 4 2023 $
36.Dt MALLOC 3 36.Dt MALLOC 3
37.Os 37.Os
38.Sh NAME 38.Sh NAME
@@ -314,7 +314,7 @@ Increase the junk level by one if it is smaller than 2.
314Decrease the junk level by one if it is larger than 0. 314Decrease the junk level by one if it is larger than 0.
315Junking writes some junk bytes into the area allocated. 315Junking writes some junk bytes into the area allocated.
316Junk is bytes of 0xdb when allocating; 316Junk is bytes of 0xdb when allocating;
317freed chunks are filled with 0xdf. 317freed allocations are filled with 0xdf.
318By default the junk level is 1: after free, 318By default the junk level is 1: after free,
319small chunks are completely junked; 319small chunks are completely junked;
320for pages the first part is junked. 320for pages the first part is junked.
@@ -628,22 +628,24 @@ An attempt to
628.Fn free 628.Fn free
629or 629or
630reallocate an unallocated pointer was made. 630reallocate an unallocated pointer was made.
631.It Dq chunk is already free 631.It Dq double free
632There was an attempt to free a chunk that had already been freed. 632There was an attempt to free an allocation that had already been freed.
633.It Dq write after free 633.It Dq write after free
634A chunk has been modified after it was freed. 634An allocation has been modified after it was freed.
635.It Dq modified chunk-pointer 635.It Dq modified chunk-pointer
636The pointer passed to 636The pointer passed to
637.Fn free 637.Fn free
638or a reallocation function has been modified. 638or a reallocation function has been modified.
639.It Dq chunk canary corrupted address offset@length 639.It Dq canary corrupted address offset@length
640A byte after the requested size has been overwritten, 640A byte after the requested size has been overwritten,
641indicating a heap overflow. 641indicating a heap overflow.
642The offset at which corruption was detected is printed before the @, 642The offset at which corruption was detected is printed before the @,
643and the requested length of the allocation after the @. 643and the requested length of the allocation after the @.
644.It Dq recorded old size oldsize != size 644.It Dq recorded size oldsize inconsistent with size
645.Fn recallocarray 645.Fn recallocarray
646has detected that the given old size does not equal the recorded size in its 646or
647.Fn freezero
648has detected that the given old size does not match the recorded size in its
647meta data. 649meta data.
648Enabling option 650Enabling option
649.Cm C 651.Cm 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 @@
1/* $OpenBSD: malloc.c,v 1.284 2023/05/27 04:33:00 otto Exp $ */ 1/* $OpenBSD: malloc.c,v 1.285 2023/06/04 06:58:33 otto Exp $ */
2/* 2/*
3 * Copyright (c) 2008, 2010, 2011, 2016, 2023 Otto Moerbeek <otto@drijf.net> 3 * Copyright (c) 2008, 2010, 2011, 2016, 2023 Otto Moerbeek <otto@drijf.net>
4 * Copyright (c) 2012 Matthew Dempsky <matthew@openbsd.org> 4 * Copyright (c) 2012 Matthew Dempsky <matthew@openbsd.org>
@@ -977,6 +977,10 @@ omalloc_make_chunks(struct dir_info *d, u_int bucket, u_int listnum)
977 NULL)) 977 NULL))
978 goto err; 978 goto err;
979 LIST_INSERT_HEAD(&d->chunk_dir[bucket][listnum], bp, entries); 979 LIST_INSERT_HEAD(&d->chunk_dir[bucket][listnum], bp, entries);
980
981 if (bucket > 0 && d->malloc_junk != 0)
982 memset(pp, SOME_FREEJUNK, MALLOC_PAGESIZE);
983
980 return bp; 984 return bp;
981 985
982err: 986err:
@@ -1113,9 +1117,8 @@ found:
1113 1117
1114 p = (char *)bp->page + k; 1118 p = (char *)bp->page + k;
1115 if (bp->bucket > 0) { 1119 if (bp->bucket > 0) {
1116 if (d->malloc_junk == 2) 1120 validate_junk(d, p, B2SIZE(bp->bucket));
1117 memset(p, SOME_JUNK, B2SIZE(bp->bucket)); 1121 if (mopts.chunk_canaries)
1118 else if (mopts.chunk_canaries)
1119 fill_canary(p, size, B2SIZE(bp->bucket)); 1122 fill_canary(p, size, B2SIZE(bp->bucket));
1120 } 1123 }
1121 return p; 1124 return p;
@@ -1134,7 +1137,7 @@ validate_canary(struct dir_info *d, u_char *ptr, size_t sz, size_t allocated)
1134 1137
1135 while (p < q) { 1138 while (p < q) {
1136 if (*p != (u_char)mopts.chunk_canaries && *p != SOME_JUNK) { 1139 if (*p != (u_char)mopts.chunk_canaries && *p != SOME_JUNK) {
1137 wrterror(d, "chunk canary corrupted %p %#tx@%#zx%s", 1140 wrterror(d, "canary corrupted %p %#tx@%#zx%s",
1138 ptr, p - ptr, sz, 1141 ptr, p - ptr, sz,
1139 *p == SOME_FREEJUNK ? " (double free?)" : ""); 1142 *p == SOME_FREEJUNK ? " (double free?)" : "");
1140 } 1143 }
@@ -1157,7 +1160,7 @@ find_chunknum(struct dir_info *d, struct chunk_info *info, void *ptr, int check)
1157 wrterror(d, "modified chunk-pointer %p", ptr); 1160 wrterror(d, "modified chunk-pointer %p", ptr);
1158 if (info->bits[chunknum / MALLOC_BITS] & 1161 if (info->bits[chunknum / MALLOC_BITS] &
1159 (1U << (chunknum % MALLOC_BITS))) 1162 (1U << (chunknum % MALLOC_BITS)))
1160 wrterror(d, "chunk is already free %p", ptr); 1163 wrterror(d, "double free %p", ptr);
1161 if (check && info->bucket > 0) { 1164 if (check && info->bucket > 0) {
1162 validate_canary(d, ptr, info->bits[info->offset + chunknum], 1165 validate_canary(d, ptr, info->bits[info->offset + chunknum],
1163 B2SIZE(info->bucket)); 1166 B2SIZE(info->bucket));
@@ -1924,13 +1927,22 @@ orecallocarray(struct dir_info **argpool, void *p, size_t oldsize,
1924 uint32_t chunknum = find_chunknum(pool, info, p, 0); 1927 uint32_t chunknum = find_chunknum(pool, info, p, 0);
1925 1928
1926 if (info->bits[info->offset + chunknum] != oldsize) 1929 if (info->bits[info->offset + chunknum] != oldsize)
1927 wrterror(pool, "recorded old size %hu != %zu", 1930 wrterror(pool, "recorded size %hu != %zu",
1928 info->bits[info->offset + chunknum], 1931 info->bits[info->offset + chunknum],
1929 oldsize); 1932 oldsize);
1933 } else {
1934 if (sz < oldsize)
1935 wrterror(pool, "chunk size %zu < %zu",
1936 sz, oldsize);
1930 } 1937 }
1931 } else if (oldsize < (sz - mopts.malloc_guard) / 2) 1938 } else {
1932 wrterror(pool, "recorded old size %zu != %zu", 1939 if (sz - mopts.malloc_guard < oldsize)
1933 sz - mopts.malloc_guard, oldsize); 1940 wrterror(pool, "recorded size %zu < %zu",
1941 sz - mopts.malloc_guard, oldsize);
1942 if (oldsize < (sz - mopts.malloc_guard) / 2)
1943 wrterror(pool, "recorded size %zu inconsistent with %zu",
1944 sz - mopts.malloc_guard, oldsize);
1945 }
1934 1946
1935 newptr = omalloc(pool, newsize, 0, f); 1947 newptr = omalloc(pool, newsize, 0, f);
1936 if (newptr == NULL) 1948 if (newptr == NULL)