diff options
author | otto <> | 2023-06-04 06:58:33 +0000 |
---|---|---|
committer | otto <> | 2023-06-04 06:58:33 +0000 |
commit | 359dbb8e7485c66a6707470ce9d5af0ddbbff9f8 (patch) | |
tree | 658d7037f67b98a6598726569c6933ca718927f6 /src/lib/libc/stdlib | |
parent | 2cbf6fb83a2b98d0b6ffbb161bcc85c81e5f2b59 (diff) | |
download | openbsd-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.3 | 20 | ||||
-rw-r--r-- | src/lib/libc/stdlib/malloc.c | 32 |
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. | |||
314 | Decrease the junk level by one if it is larger than 0. | 314 | Decrease the junk level by one if it is larger than 0. |
315 | Junking writes some junk bytes into the area allocated. | 315 | Junking writes some junk bytes into the area allocated. |
316 | Junk is bytes of 0xdb when allocating; | 316 | Junk is bytes of 0xdb when allocating; |
317 | freed chunks are filled with 0xdf. | 317 | freed allocations are filled with 0xdf. |
318 | By default the junk level is 1: after free, | 318 | By default the junk level is 1: after free, |
319 | small chunks are completely junked; | 319 | small chunks are completely junked; |
320 | for pages the first part is junked. | 320 | for pages the first part is junked. |
@@ -628,22 +628,24 @@ An attempt to | |||
628 | .Fn free | 628 | .Fn free |
629 | or | 629 | or |
630 | reallocate an unallocated pointer was made. | 630 | reallocate an unallocated pointer was made. |
631 | .It Dq chunk is already free | 631 | .It Dq double free |
632 | There was an attempt to free a chunk that had already been freed. | 632 | There was an attempt to free an allocation that had already been freed. |
633 | .It Dq write after free | 633 | .It Dq write after free |
634 | A chunk has been modified after it was freed. | 634 | An allocation has been modified after it was freed. |
635 | .It Dq modified chunk-pointer | 635 | .It Dq modified chunk-pointer |
636 | The pointer passed to | 636 | The pointer passed to |
637 | .Fn free | 637 | .Fn free |
638 | or a reallocation function has been modified. | 638 | or a reallocation function has been modified. |
639 | .It Dq chunk canary corrupted address offset@length | 639 | .It Dq canary corrupted address offset@length |
640 | A byte after the requested size has been overwritten, | 640 | A byte after the requested size has been overwritten, |
641 | indicating a heap overflow. | 641 | indicating a heap overflow. |
642 | The offset at which corruption was detected is printed before the @, | 642 | The offset at which corruption was detected is printed before the @, |
643 | and the requested length of the allocation after the @. | 643 | and 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 |
646 | has detected that the given old size does not equal the recorded size in its | 646 | or |
647 | .Fn freezero | ||
648 | has detected that the given old size does not match the recorded size in its | ||
647 | meta data. | 649 | meta data. |
648 | Enabling option | 650 | Enabling 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 | ||
982 | err: | 986 | err: |
@@ -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) |