diff options
author | otto <> | 2021-02-25 15:20:18 +0000 |
---|---|---|
committer | otto <> | 2021-02-25 15:20:18 +0000 |
commit | 827c99d1b55bae4268d905d4c4817f7add395c94 (patch) | |
tree | 1e53fb8f01e90fb2daabd5c89841f55313e700eb | |
parent | 851054bdc5a74d09ef9d5133842a93fed3fc408e (diff) | |
download | openbsd-827c99d1b55bae4268d905d4c4817f7add395c94.tar.gz openbsd-827c99d1b55bae4268d905d4c4817f7add395c94.tar.bz2 openbsd-827c99d1b55bae4268d905d4c4817f7add395c94.zip |
- Make use of the fact that we know how the chunks are aligned, and
write 8 bytes at the time by using a uint64_t pointer. For an
allocation a max of 4 such uint64_t's are written spread over the
allocation. For pages sized and larger, the first page is junked in
such a way.
- Delayed free of a small chunk checks the corresponiding way.
- Pages ending up in the cache are validated upon unmapping or re-use.
In snaps for a while
-rw-r--r-- | src/lib/libc/stdlib/malloc.3 | 6 | ||||
-rw-r--r-- | src/lib/libc/stdlib/malloc.c | 126 |
2 files changed, 83 insertions, 49 deletions
diff --git a/src/lib/libc/stdlib/malloc.3 b/src/lib/libc/stdlib/malloc.3 index 0c7574034b..c27f965d0a 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.126 2019/09/14 13:16:50 otto Exp $ | 33 | .\" $OpenBSD: malloc.3,v 1.127 2021/02/25 15:20:18 otto Exp $ |
34 | .\" | 34 | .\" |
35 | .Dd $Mdocdate: September 14 2019 $ | 35 | .Dd $Mdocdate: February 25 2021 $ |
36 | .Dt MALLOC 3 | 36 | .Dt MALLOC 3 |
37 | .Os | 37 | .Os |
38 | .Sh NAME | 38 | .Sh NAME |
@@ -619,7 +619,7 @@ or | |||
619 | reallocate an unallocated pointer was made. | 619 | reallocate an unallocated pointer was made. |
620 | .It Dq chunk is already free | 620 | .It Dq chunk is already free |
621 | There was an attempt to free a chunk that had already been freed. | 621 | There was an attempt to free a chunk that had already been freed. |
622 | .It Dq use after free | 622 | .It Dq write after free |
623 | A chunk has been modified after it was freed. | 623 | A chunk has been modified after it was freed. |
624 | .It Dq modified chunk-pointer | 624 | .It Dq modified chunk-pointer |
625 | The pointer passed to | 625 | The pointer passed to |
diff --git a/src/lib/libc/stdlib/malloc.c b/src/lib/libc/stdlib/malloc.c index 7004a0aa36..46b07ff77d 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.267 2020/11/23 15:42:11 otto Exp $ */ | 1 | /* $OpenBSD: malloc.c,v 1.268 2021/02/25 15:20:18 otto Exp $ */ |
2 | /* | 2 | /* |
3 | * Copyright (c) 2008, 2010, 2011, 2016 Otto Moerbeek <otto@drijf.net> | 3 | * Copyright (c) 2008, 2010, 2011, 2016 Otto Moerbeek <otto@drijf.net> |
4 | * Copyright (c) 2012 Matthew Dempsky <matthew@openbsd.org> | 4 | * Copyright (c) 2012 Matthew Dempsky <matthew@openbsd.org> |
@@ -89,6 +89,7 @@ | |||
89 | */ | 89 | */ |
90 | #define SOME_JUNK 0xdb /* deadbeef */ | 90 | #define SOME_JUNK 0xdb /* deadbeef */ |
91 | #define SOME_FREEJUNK 0xdf /* dead, free */ | 91 | #define SOME_FREEJUNK 0xdf /* dead, free */ |
92 | #define SOME_FREEJUNK_ULL 0xdfdfdfdfdfdfdfdfULL | ||
92 | 93 | ||
93 | #define MMAP(sz,f) mmap(NULL, (sz), PROT_READ | PROT_WRITE, \ | 94 | #define MMAP(sz,f) mmap(NULL, (sz), PROT_READ | PROT_WRITE, \ |
94 | MAP_ANON | MAP_PRIVATE | (f), -1, 0) | 95 | MAP_ANON | MAP_PRIVATE | (f), -1, 0) |
@@ -655,6 +656,49 @@ delete(struct dir_info *d, struct region_info *ri) | |||
655 | } | 656 | } |
656 | } | 657 | } |
657 | 658 | ||
659 | static inline void | ||
660 | junk_free(int junk, void *p, size_t sz) | ||
661 | { | ||
662 | size_t i, step = 1; | ||
663 | uint64_t *lp = p; | ||
664 | |||
665 | if (junk == 0 || sz == 0) | ||
666 | return; | ||
667 | sz /= sizeof(uint64_t); | ||
668 | if (junk == 1) { | ||
669 | if (sz > MALLOC_PAGESIZE / sizeof(uint64_t)) | ||
670 | sz = MALLOC_PAGESIZE / sizeof(uint64_t); | ||
671 | step = sz / 4; | ||
672 | if (step == 0) | ||
673 | step = 1; | ||
674 | } | ||
675 | for (i = 0; i < sz; i += step) | ||
676 | lp[i] = SOME_FREEJUNK_ULL; | ||
677 | } | ||
678 | |||
679 | static inline void | ||
680 | validate_junk(struct dir_info *pool, void *p, size_t sz) | ||
681 | { | ||
682 | size_t i, step = 1; | ||
683 | uint64_t *lp = p; | ||
684 | |||
685 | if (pool->malloc_junk == 0 || sz == 0) | ||
686 | return; | ||
687 | sz /= sizeof(uint64_t); | ||
688 | if (pool->malloc_junk == 1) { | ||
689 | if (sz > MALLOC_PAGESIZE / sizeof(uint64_t)) | ||
690 | sz = MALLOC_PAGESIZE / sizeof(uint64_t); | ||
691 | step = sz / 4; | ||
692 | if (step == 0) | ||
693 | step = 1; | ||
694 | } | ||
695 | for (i = 0; i < sz; i += step) { | ||
696 | if (lp[i] != SOME_FREEJUNK_ULL) | ||
697 | wrterror(pool, "write after free %p", p); | ||
698 | } | ||
699 | } | ||
700 | |||
701 | |||
658 | /* | 702 | /* |
659 | * Cache maintenance. We keep at most malloc_cache pages cached. | 703 | * Cache maintenance. We keep at most malloc_cache pages cached. |
660 | * If the cache is becoming full, unmap pages in the cache for real, | 704 | * If the cache is becoming full, unmap pages in the cache for real, |
@@ -663,7 +707,7 @@ delete(struct dir_info *d, struct region_info *ri) | |||
663 | * cache are in MALLOC_PAGESIZE units. | 707 | * cache are in MALLOC_PAGESIZE units. |
664 | */ | 708 | */ |
665 | static void | 709 | static void |
666 | unmap(struct dir_info *d, void *p, size_t sz, size_t clear, int junk) | 710 | unmap(struct dir_info *d, void *p, size_t sz, size_t clear) |
667 | { | 711 | { |
668 | size_t psz = sz >> MALLOC_PAGESHIFT; | 712 | size_t psz = sz >> MALLOC_PAGESHIFT; |
669 | size_t rsz; | 713 | size_t rsz; |
@@ -695,6 +739,8 @@ unmap(struct dir_info *d, void *p, size_t sz, size_t clear, int junk) | |||
695 | r = &d->free_regions[(i + offset) & mask]; | 739 | r = &d->free_regions[(i + offset) & mask]; |
696 | if (r->p != NULL) { | 740 | if (r->p != NULL) { |
697 | rsz = r->size << MALLOC_PAGESHIFT; | 741 | rsz = r->size << MALLOC_PAGESHIFT; |
742 | if (!mopts.malloc_freeunmap) | ||
743 | validate_junk(d, r->p, rsz); | ||
698 | if (munmap(r->p, rsz)) | 744 | if (munmap(r->p, rsz)) |
699 | wrterror(d, "munmap %p", r->p); | 745 | wrterror(d, "munmap %p", r->p); |
700 | r->p = NULL; | 746 | r->p = NULL; |
@@ -716,12 +762,11 @@ unmap(struct dir_info *d, void *p, size_t sz, size_t clear, int junk) | |||
716 | if (r->p == NULL) { | 762 | if (r->p == NULL) { |
717 | if (clear > 0) | 763 | if (clear > 0) |
718 | memset(p, 0, clear); | 764 | memset(p, 0, clear); |
719 | if (junk && !mopts.malloc_freeunmap) { | ||
720 | size_t amt = junk == 1 ? MALLOC_MAXCHUNK : sz; | ||
721 | memset(p, SOME_FREEJUNK, amt); | ||
722 | } | ||
723 | if (mopts.malloc_freeunmap) | 765 | if (mopts.malloc_freeunmap) |
724 | mprotect(p, sz, PROT_NONE); | 766 | mprotect(p, sz, PROT_NONE); |
767 | else | ||
768 | junk_free(d->malloc_junk, p, | ||
769 | psz << MALLOC_PAGESHIFT); | ||
725 | r->p = p; | 770 | r->p = p; |
726 | r->size = psz; | 771 | r->size = psz; |
727 | d->free_regions_size += psz; | 772 | d->free_regions_size += psz; |
@@ -760,15 +805,17 @@ map(struct dir_info *d, size_t sz, int zero_fill) | |||
760 | if (r->p != NULL) { | 805 | if (r->p != NULL) { |
761 | if (r->size == psz) { | 806 | if (r->size == psz) { |
762 | p = r->p; | 807 | p = r->p; |
808 | if (!mopts.malloc_freeunmap) | ||
809 | validate_junk(d, p, | ||
810 | psz << MALLOC_PAGESHIFT); | ||
763 | r->p = NULL; | 811 | r->p = NULL; |
764 | d->free_regions_size -= psz; | 812 | d->free_regions_size -= psz; |
765 | if (mopts.malloc_freeunmap) | 813 | if (mopts.malloc_freeunmap) |
766 | mprotect(p, sz, PROT_READ | PROT_WRITE); | 814 | mprotect(p, sz, PROT_READ | PROT_WRITE); |
767 | if (zero_fill) | 815 | if (zero_fill) |
768 | memset(p, 0, sz); | 816 | memset(p, 0, sz); |
769 | else if (d->malloc_junk == 2 && | 817 | else if (mopts.malloc_freeunmap) |
770 | mopts.malloc_freeunmap) | 818 | junk_free(d->malloc_junk, p, sz); |
771 | memset(p, SOME_FREEJUNK, sz); | ||
772 | d->rotor += i + 1; | 819 | d->rotor += i + 1; |
773 | return p; | 820 | return p; |
774 | } else if (r->size > psz) | 821 | } else if (r->size > psz) |
@@ -778,15 +825,20 @@ map(struct dir_info *d, size_t sz, int zero_fill) | |||
778 | if (big != NULL) { | 825 | if (big != NULL) { |
779 | r = big; | 826 | r = big; |
780 | p = r->p; | 827 | p = r->p; |
828 | if (!mopts.malloc_freeunmap) | ||
829 | validate_junk(d, p, r->size << MALLOC_PAGESHIFT); | ||
781 | r->p = (char *)r->p + (psz << MALLOC_PAGESHIFT); | 830 | r->p = (char *)r->p + (psz << MALLOC_PAGESHIFT); |
782 | if (mopts.malloc_freeunmap) | ||
783 | mprotect(p, sz, PROT_READ | PROT_WRITE); | ||
784 | r->size -= psz; | 831 | r->size -= psz; |
785 | d->free_regions_size -= psz; | 832 | d->free_regions_size -= psz; |
833 | if (mopts.malloc_freeunmap) | ||
834 | mprotect(p, sz, PROT_READ | PROT_WRITE); | ||
835 | else | ||
836 | junk_free(d->malloc_junk, r->p, | ||
837 | r->size << MALLOC_PAGESHIFT); | ||
786 | if (zero_fill) | 838 | if (zero_fill) |
787 | memset(p, 0, sz); | 839 | memset(p, 0, sz); |
788 | else if (d->malloc_junk == 2 && mopts.malloc_freeunmap) | 840 | else if (mopts.malloc_freeunmap) |
789 | memset(p, SOME_FREEJUNK, sz); | 841 | junk_free(d->malloc_junk, p, sz); |
790 | return p; | 842 | return p; |
791 | } | 843 | } |
792 | if (d->free_regions_size > d->malloc_cache) | 844 | if (d->free_regions_size > d->malloc_cache) |
@@ -892,7 +944,7 @@ omalloc_make_chunks(struct dir_info *d, int bits, int listnum) | |||
892 | return bp; | 944 | return bp; |
893 | 945 | ||
894 | err: | 946 | err: |
895 | unmap(d, pp, MALLOC_PAGESIZE, 0, d->malloc_junk); | 947 | unmap(d, pp, MALLOC_PAGESIZE, 0); |
896 | return NULL; | 948 | return NULL; |
897 | } | 949 | } |
898 | 950 | ||
@@ -1091,7 +1143,7 @@ free_bytes(struct dir_info *d, struct region_info *r, void *ptr) | |||
1091 | 1143 | ||
1092 | if (info->size == 0 && !mopts.malloc_freeunmap) | 1144 | if (info->size == 0 && !mopts.malloc_freeunmap) |
1093 | mprotect(info->page, MALLOC_PAGESIZE, PROT_READ | PROT_WRITE); | 1145 | mprotect(info->page, MALLOC_PAGESIZE, PROT_READ | PROT_WRITE); |
1094 | unmap(d, info->page, MALLOC_PAGESIZE, 0, 0); | 1146 | unmap(d, info->page, MALLOC_PAGESIZE, 0); |
1095 | 1147 | ||
1096 | delete(d, r); | 1148 | delete(d, r); |
1097 | if (info->size != 0) | 1149 | if (info->size != 0) |
@@ -1122,7 +1174,7 @@ omalloc(struct dir_info *pool, size_t sz, int zero_fill, void *f) | |||
1122 | return NULL; | 1174 | return NULL; |
1123 | } | 1175 | } |
1124 | if (insert(pool, p, sz, f)) { | 1176 | if (insert(pool, p, sz, f)) { |
1125 | unmap(pool, p, psz, 0, 0); | 1177 | unmap(pool, p, psz, 0); |
1126 | errno = ENOMEM; | 1178 | errno = ENOMEM; |
1127 | return NULL; | 1179 | return NULL; |
1128 | } | 1180 | } |
@@ -1282,27 +1334,6 @@ malloc_conceal(size_t size) | |||
1282 | } | 1334 | } |
1283 | DEF_WEAK(malloc_conceal); | 1335 | DEF_WEAK(malloc_conceal); |
1284 | 1336 | ||
1285 | static void | ||
1286 | validate_junk(struct dir_info *pool, void *p) | ||
1287 | { | ||
1288 | struct region_info *r; | ||
1289 | size_t byte, sz; | ||
1290 | |||
1291 | if (p == NULL) | ||
1292 | return; | ||
1293 | r = find(pool, p); | ||
1294 | if (r == NULL) | ||
1295 | wrterror(pool, "bogus pointer in validate_junk %p", p); | ||
1296 | REALSIZE(sz, r); | ||
1297 | if (sz > CHUNK_CHECK_LENGTH) | ||
1298 | sz = CHUNK_CHECK_LENGTH; | ||
1299 | for (byte = 0; byte < sz; byte++) { | ||
1300 | if (((unsigned char *)p)[byte] != SOME_FREEJUNK) | ||
1301 | wrterror(pool, "use after free %p", p); | ||
1302 | } | ||
1303 | } | ||
1304 | |||
1305 | |||
1306 | static struct region_info * | 1337 | static struct region_info * |
1307 | findpool(void *p, struct dir_info *argpool, struct dir_info **foundpool, | 1338 | findpool(void *p, struct dir_info *argpool, struct dir_info **foundpool, |
1308 | char **saved_function) | 1339 | char **saved_function) |
@@ -1402,8 +1433,7 @@ ofree(struct dir_info **argpool, void *p, int clear, int check, size_t argsz) | |||
1402 | } | 1433 | } |
1403 | STATS_SUB(pool->malloc_guarded, mopts.malloc_guard); | 1434 | STATS_SUB(pool->malloc_guarded, mopts.malloc_guard); |
1404 | } | 1435 | } |
1405 | unmap(pool, p, PAGEROUND(sz), clear ? argsz : 0, | 1436 | unmap(pool, p, PAGEROUND(sz), clear ? argsz : 0); |
1406 | pool->malloc_junk); | ||
1407 | delete(pool, r); | 1437 | delete(pool, r); |
1408 | } else { | 1438 | } else { |
1409 | /* Validate and optionally canary check */ | 1439 | /* Validate and optionally canary check */ |
@@ -1419,20 +1449,24 @@ ofree(struct dir_info **argpool, void *p, int clear, int check, size_t argsz) | |||
1419 | wrterror(pool, | 1449 | wrterror(pool, |
1420 | "double free %p", p); | 1450 | "double free %p", p); |
1421 | } | 1451 | } |
1422 | if (pool->malloc_junk && sz > 0) | 1452 | junk_free(pool->malloc_junk, p, sz); |
1423 | memset(p, SOME_FREEJUNK, sz); | ||
1424 | i = getrbyte(pool) & MALLOC_DELAYED_CHUNK_MASK; | 1453 | i = getrbyte(pool) & MALLOC_DELAYED_CHUNK_MASK; |
1425 | tmp = p; | 1454 | tmp = p; |
1426 | p = pool->delayed_chunks[i]; | 1455 | p = pool->delayed_chunks[i]; |
1427 | if (tmp == p) | 1456 | if (tmp == p) |
1428 | wrterror(pool, "double free %p", tmp); | 1457 | wrterror(pool, "double free %p", tmp); |
1429 | pool->delayed_chunks[i] = tmp; | 1458 | pool->delayed_chunks[i] = tmp; |
1430 | if (pool->malloc_junk) | 1459 | if (p != NULL) { |
1431 | validate_junk(pool, p); | 1460 | r = find(pool, p); |
1432 | } else if (argsz > 0) | 1461 | REALSIZE(sz, r); |
1462 | if (r != NULL) | ||
1463 | validate_junk(pool, p, sz); | ||
1464 | } | ||
1465 | } else if (argsz > 0) { | ||
1466 | r = find(pool, p); | ||
1433 | memset(p, 0, argsz); | 1467 | memset(p, 0, argsz); |
1468 | } | ||
1434 | if (p != NULL) { | 1469 | if (p != NULL) { |
1435 | r = find(pool, p); | ||
1436 | if (r == NULL) | 1470 | if (r == NULL) |
1437 | wrterror(pool, | 1471 | wrterror(pool, |
1438 | "bogus pointer (double free?) %p", p); | 1472 | "bogus pointer (double free?) %p", p); |
@@ -1969,7 +2003,7 @@ omemalign(struct dir_info *pool, size_t alignment, size_t sz, int zero_fill, | |||
1969 | } | 2003 | } |
1970 | 2004 | ||
1971 | if (insert(pool, p, sz, f)) { | 2005 | if (insert(pool, p, sz, f)) { |
1972 | unmap(pool, p, psz, 0, 0); | 2006 | unmap(pool, p, psz, 0); |
1973 | errno = ENOMEM; | 2007 | errno = ENOMEM; |
1974 | return NULL; | 2008 | return NULL; |
1975 | } | 2009 | } |