diff options
author | otto <> | 2023-03-25 15:22:06 +0000 |
---|---|---|
committer | otto <> | 2023-03-25 15:22:06 +0000 |
commit | aa9c36ea6771e5920cc990455a1c5a238404271a (patch) | |
tree | d2c5e22c1b7782c67519711149e923495a1a3acb /src | |
parent | faae8138ba863e01a95f59b65ee87dac2b5cf5bd (diff) | |
download | openbsd-aa9c36ea6771e5920cc990455a1c5a238404271a.tar.gz openbsd-aa9c36ea6771e5920cc990455a1c5a238404271a.tar.bz2 openbsd-aa9c36ea6771e5920cc990455a1c5a238404271a.zip |
Change malloc chunk sizes to be fine grained.
The basic idea is simple: one of the reasons the recent sshd bug
is potentially exploitable is that a (erroneously) freed malloc
chunk gets re-used in a different role. malloc has power of two
chunk sizes and so one page of chunks holds many different types
of allocations. Userland malloc has no knowledge of types, we only
know about sizes. So I changed that to use finer-grained chunk
sizes.
This has some performance impact as we need to allocate chunk pages
in more cases. Gain it back by allocation chunk_info pages in a
bundle, and use less buckets is !malloc option S. The chunk sizes
used are 16, 32, 48, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320,
384, 448, 512, 640, 768, 896, 1024, 1280, 1536, 1792, 2048 (and a
few more for sparc64 with its 8k sized pages and loongson with its
16k pages).
If malloc option S (or rather cache size 0) is used we use strict
multiple of 16 sized chunks, to get as many buckets as possible.
ssh(d) enabled malloc option S, in general security sensitive
programs should.
See the find_bucket() and bin_of() functions. Thanks to Tony Finch
for pointing me to code to compute nice bucket sizes.
ok tb@
Diffstat (limited to 'src')
-rw-r--r-- | src/lib/libc/stdlib/malloc.c | 244 |
1 files changed, 142 insertions, 102 deletions
diff --git a/src/lib/libc/stdlib/malloc.c b/src/lib/libc/stdlib/malloc.c index 6167145669..c049b2da54 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.277 2023/02/27 06:47:54 otto Exp $ */ | 1 | /* $OpenBSD: malloc.c,v 1.278 2023/03/25 15:22:06 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> |
@@ -67,6 +67,11 @@ | |||
67 | #define MALLOC_CHUNK_LISTS 4 | 67 | #define MALLOC_CHUNK_LISTS 4 |
68 | #define CHUNK_CHECK_LENGTH 32 | 68 | #define CHUNK_CHECK_LENGTH 32 |
69 | 69 | ||
70 | #define B2SIZE(b) ((b) * MALLOC_MINSIZE) | ||
71 | #define B2ALLOC(b) ((b) == 0 ? MALLOC_MINSIZE : \ | ||
72 | (b) * MALLOC_MINSIZE) | ||
73 | #define BUCKETS (MALLOC_MAXCHUNK / MALLOC_MINSIZE) | ||
74 | |||
70 | /* | 75 | /* |
71 | * We move allocations between half a page and a whole page towards the end, | 76 | * We move allocations between half a page and a whole page towards the end, |
72 | * subject to alignment constraints. This is the extra headroom we allow. | 77 | * subject to alignment constraints. This is the extra headroom we allow. |
@@ -144,9 +149,9 @@ struct dir_info { | |||
144 | int mutex; | 149 | int mutex; |
145 | int malloc_mt; /* multi-threaded mode? */ | 150 | int malloc_mt; /* multi-threaded mode? */ |
146 | /* lists of free chunk info structs */ | 151 | /* lists of free chunk info structs */ |
147 | struct chunk_head chunk_info_list[MALLOC_MAXSHIFT + 1]; | 152 | struct chunk_head chunk_info_list[BUCKETS + 1]; |
148 | /* lists of chunks with free slots */ | 153 | /* lists of chunks with free slots */ |
149 | struct chunk_head chunk_dir[MALLOC_MAXSHIFT + 1][MALLOC_CHUNK_LISTS]; | 154 | struct chunk_head chunk_dir[BUCKETS + 1][MALLOC_CHUNK_LISTS]; |
150 | /* delayed free chunk slots */ | 155 | /* delayed free chunk slots */ |
151 | void *delayed_chunks[MALLOC_DELAYED_CHUNK_MASK + 1]; | 156 | void *delayed_chunks[MALLOC_DELAYED_CHUNK_MASK + 1]; |
152 | u_char rbytes[32]; /* random bytes */ | 157 | u_char rbytes[32]; /* random bytes */ |
@@ -155,6 +160,8 @@ struct dir_info { | |||
155 | size_t bigcache_used; | 160 | size_t bigcache_used; |
156 | size_t bigcache_size; | 161 | size_t bigcache_size; |
157 | struct bigcache *bigcache; | 162 | struct bigcache *bigcache; |
163 | void *chunk_pages; | ||
164 | size_t chunk_pages_used; | ||
158 | #ifdef MALLOC_STATS | 165 | #ifdef MALLOC_STATS |
159 | size_t inserts; | 166 | size_t inserts; |
160 | size_t insert_collisions; | 167 | size_t insert_collisions; |
@@ -195,8 +202,7 @@ struct chunk_info { | |||
195 | LIST_ENTRY(chunk_info) entries; | 202 | LIST_ENTRY(chunk_info) entries; |
196 | void *page; /* pointer to the page */ | 203 | void *page; /* pointer to the page */ |
197 | u_short canary; | 204 | u_short canary; |
198 | u_short size; /* size of this page's chunks */ | 205 | u_short bucket; |
199 | u_short shift; /* how far to shift for this size */ | ||
200 | u_short free; /* how many free chunks */ | 206 | u_short free; /* how many free chunks */ |
201 | u_short total; /* how many chunks */ | 207 | u_short total; /* how many chunks */ |
202 | u_short offset; /* requested size table offset */ | 208 | u_short offset; /* requested size table offset */ |
@@ -247,11 +253,11 @@ static void malloc_exit(void); | |||
247 | #endif | 253 | #endif |
248 | 254 | ||
249 | /* low bits of r->p determine size: 0 means >= page size and r->size holding | 255 | /* low bits of r->p determine size: 0 means >= page size and r->size holding |
250 | * real size, otherwise low bits are a shift count, or 1 for malloc(0) | 256 | * real size, otherwise low bits is the bucket + 1 |
251 | */ | 257 | */ |
252 | #define REALSIZE(sz, r) \ | 258 | #define REALSIZE(sz, r) \ |
253 | (sz) = (uintptr_t)(r)->p & MALLOC_PAGEMASK, \ | 259 | (sz) = (uintptr_t)(r)->p & MALLOC_PAGEMASK, \ |
254 | (sz) = ((sz) == 0 ? (r)->size : ((sz) == 1 ? 0 : (1 << ((sz)-1)))) | 260 | (sz) = ((sz) == 0 ? (r)->size : B2SIZE((sz) - 1)) |
255 | 261 | ||
256 | static inline void | 262 | static inline void |
257 | _MALLOC_LEAVE(struct dir_info *d) | 263 | _MALLOC_LEAVE(struct dir_info *d) |
@@ -502,7 +508,7 @@ omalloc_poolinit(struct dir_info *d, int mmap_flag) | |||
502 | d->r = NULL; | 508 | d->r = NULL; |
503 | d->rbytesused = sizeof(d->rbytes); | 509 | d->rbytesused = sizeof(d->rbytes); |
504 | d->regions_free = d->regions_total = 0; | 510 | d->regions_free = d->regions_total = 0; |
505 | for (i = 0; i <= MALLOC_MAXSHIFT; i++) { | 511 | for (i = 0; i <= BUCKETS; i++) { |
506 | LIST_INIT(&d->chunk_info_list[i]); | 512 | LIST_INIT(&d->chunk_info_list[i]); |
507 | for (j = 0; j < MALLOC_CHUNK_LISTS; j++) | 513 | for (j = 0; j < MALLOC_CHUNK_LISTS; j++) |
508 | LIST_INIT(&d->chunk_dir[i][j]); | 514 | LIST_INIT(&d->chunk_dir[i][j]); |
@@ -720,7 +726,7 @@ unmap(struct dir_info *d, void *p, size_t sz, size_t clear) | |||
720 | 726 | ||
721 | /* don't look through all slots */ | 727 | /* don't look through all slots */ |
722 | for (j = 0; j < d->bigcache_size / 4; j++) { | 728 | for (j = 0; j < d->bigcache_size / 4; j++) { |
723 | i = (base + j) % d->bigcache_size; | 729 | i = (base + j) & (d->bigcache_size - 1); |
724 | if (d->bigcache_used < | 730 | if (d->bigcache_used < |
725 | BIGCACHE_FILL(d->bigcache_size)) { | 731 | BIGCACHE_FILL(d->bigcache_size)) { |
726 | if (d->bigcache[i].psize == 0) | 732 | if (d->bigcache[i].psize == 0) |
@@ -764,10 +770,13 @@ unmap(struct dir_info *d, void *p, size_t sz, size_t clear) | |||
764 | } | 770 | } |
765 | cache = &d->smallcache[psz - 1]; | 771 | cache = &d->smallcache[psz - 1]; |
766 | if (cache->length == cache->max) { | 772 | if (cache->length == cache->max) { |
773 | int fresh; | ||
767 | /* use a random slot */ | 774 | /* use a random slot */ |
768 | i = getrbyte(d) % cache->max; | 775 | i = getrbyte(d) & (cache->max - 1); |
769 | r = cache->pages[i]; | 776 | r = cache->pages[i]; |
770 | if (!mopts.malloc_freeunmap) | 777 | fresh = (uintptr_t)r & 1; |
778 | *(uintptr_t*)&r &= ~1ULL; | ||
779 | if (!fresh && !mopts.malloc_freeunmap) | ||
771 | validate_junk(d, r, sz); | 780 | validate_junk(d, r, sz); |
772 | if (munmap(r, sz)) | 781 | if (munmap(r, sz)) |
773 | wrterror(d, "munmap %p", r); | 782 | wrterror(d, "munmap %p", r); |
@@ -809,7 +818,7 @@ map(struct dir_info *d, size_t sz, int zero_fill) | |||
809 | ushort j; | 818 | ushort j; |
810 | 819 | ||
811 | for (j = 0; j < d->bigcache_size && cached >= psz; j++) { | 820 | for (j = 0; j < d->bigcache_size && cached >= psz; j++) { |
812 | i = (j + base) % d->bigcache_size; | 821 | i = (j + base) & (d->bigcache_size - 1); |
813 | if (d->bigcache[i].psize == psz) { | 822 | if (d->bigcache[i].psize == psz) { |
814 | p = d->bigcache[i].page; | 823 | p = d->bigcache[i].page; |
815 | d->bigcache_used -= psz; | 824 | d->bigcache_used -= psz; |
@@ -832,6 +841,7 @@ map(struct dir_info *d, size_t sz, int zero_fill) | |||
832 | if (psz <= MAX_SMALLCACHEABLE_SIZE && d->smallcache[psz - 1].max > 0) { | 841 | if (psz <= MAX_SMALLCACHEABLE_SIZE && d->smallcache[psz - 1].max > 0) { |
833 | cache = &d->smallcache[psz - 1]; | 842 | cache = &d->smallcache[psz - 1]; |
834 | if (cache->length > 0) { | 843 | if (cache->length > 0) { |
844 | int fresh; | ||
835 | if (cache->length == 1) | 845 | if (cache->length == 1) |
836 | p = cache->pages[--cache->length]; | 846 | p = cache->pages[--cache->length]; |
837 | else { | 847 | else { |
@@ -839,7 +849,11 @@ map(struct dir_info *d, size_t sz, int zero_fill) | |||
839 | p = cache->pages[i]; | 849 | p = cache->pages[i]; |
840 | cache->pages[i] = cache->pages[--cache->length]; | 850 | cache->pages[i] = cache->pages[--cache->length]; |
841 | } | 851 | } |
842 | if (!mopts.malloc_freeunmap) | 852 | /* check if page was not junked, i.e. "fresh |
853 | we use the lsb of the pointer for that */ | ||
854 | fresh = (uintptr_t)p & 1UL; | ||
855 | *(uintptr_t*)&p &= ~1UL; | ||
856 | if (!fresh && !mopts.malloc_freeunmap) | ||
843 | validate_junk(d, p, sz); | 857 | validate_junk(d, p, sz); |
844 | if (mopts.malloc_freeunmap) | 858 | if (mopts.malloc_freeunmap) |
845 | mprotect(p, sz, PROT_READ | PROT_WRITE); | 859 | mprotect(p, sz, PROT_READ | PROT_WRITE); |
@@ -859,15 +873,14 @@ map(struct dir_info *d, size_t sz, int zero_fill) | |||
859 | for (i = 0; i < cache->max - 1; i++) { | 873 | for (i = 0; i < cache->max - 1; i++) { |
860 | void *q = (char*)p + i * sz; | 874 | void *q = (char*)p + i * sz; |
861 | cache->pages[i] = q; | 875 | cache->pages[i] = q; |
862 | if (!mopts.malloc_freeunmap) | 876 | /* mark pointer in slot as not junked */ |
863 | junk_free(d->malloc_junk, q, | 877 | *(uintptr_t*)&cache->pages[i] |= 1UL; |
864 | sz); | ||
865 | } | 878 | } |
866 | if (mopts.malloc_freeunmap) | 879 | if (mopts.malloc_freeunmap) |
867 | mprotect(p, (cache->max - 1) * sz, | 880 | mprotect(p, (cache->max - 1) * sz, |
868 | PROT_NONE); | 881 | PROT_NONE); |
869 | p = (char*)p + (cache->max - 1) * sz; | 882 | p = (char*)p + (cache->max - 1) * sz; |
870 | /* zero fill not needed */ | 883 | /* zero fill not needed, freshly mmapped */ |
871 | return p; | 884 | return p; |
872 | } | 885 | } |
873 | } | 886 | } |
@@ -883,21 +896,13 @@ map(struct dir_info *d, size_t sz, int zero_fill) | |||
883 | } | 896 | } |
884 | 897 | ||
885 | static void | 898 | static void |
886 | init_chunk_info(struct dir_info *d, struct chunk_info *p, int bits) | 899 | init_chunk_info(struct dir_info *d, struct chunk_info *p, u_int bucket) |
887 | { | 900 | { |
888 | int i; | 901 | u_int i; |
889 | 902 | ||
890 | if (bits == 0) { | 903 | p->bucket = bucket; |
891 | p->shift = MALLOC_MINSHIFT; | 904 | p->total = p->free = MALLOC_PAGESIZE / B2ALLOC(bucket); |
892 | p->total = p->free = MALLOC_PAGESIZE >> p->shift; | 905 | p->offset = bucket == 0 ? 0xdead : howmany(p->total, MALLOC_BITS); |
893 | p->size = 0; | ||
894 | p->offset = 0xdead; | ||
895 | } else { | ||
896 | p->shift = bits; | ||
897 | p->total = p->free = MALLOC_PAGESIZE >> p->shift; | ||
898 | p->size = 1U << bits; | ||
899 | p->offset = howmany(p->total, MALLOC_BITS); | ||
900 | } | ||
901 | p->canary = (u_short)d->canary1; | 906 | p->canary = (u_short)d->canary1; |
902 | 907 | ||
903 | /* set all valid bits in the bitmap */ | 908 | /* set all valid bits in the bitmap */ |
@@ -907,41 +912,48 @@ init_chunk_info(struct dir_info *d, struct chunk_info *p, int bits) | |||
907 | } | 912 | } |
908 | 913 | ||
909 | static struct chunk_info * | 914 | static struct chunk_info * |
910 | alloc_chunk_info(struct dir_info *d, int bits) | 915 | alloc_chunk_info(struct dir_info *d, u_int bucket) |
911 | { | 916 | { |
912 | struct chunk_info *p; | 917 | struct chunk_info *p; |
913 | 918 | ||
914 | if (LIST_EMPTY(&d->chunk_info_list[bits])) { | 919 | if (LIST_EMPTY(&d->chunk_info_list[bucket])) { |
920 | const size_t chunk_pages = 64; | ||
915 | size_t size, count, i; | 921 | size_t size, count, i; |
916 | char *q; | 922 | char *q; |
917 | 923 | ||
918 | if (bits == 0) | 924 | count = MALLOC_PAGESIZE / B2ALLOC(bucket); |
919 | count = MALLOC_PAGESIZE / MALLOC_MINSIZE; | ||
920 | else | ||
921 | count = MALLOC_PAGESIZE >> bits; | ||
922 | 925 | ||
923 | size = howmany(count, MALLOC_BITS); | 926 | size = howmany(count, MALLOC_BITS); |
924 | size = sizeof(struct chunk_info) + (size - 1) * sizeof(u_short); | 927 | size = sizeof(struct chunk_info) + (size - 1) * sizeof(u_short); |
925 | if (mopts.chunk_canaries) | 928 | if (mopts.chunk_canaries) |
926 | size += count * sizeof(u_short); | 929 | size += count * sizeof(u_short); |
927 | size = _ALIGN(size); | 930 | size = _ALIGN(size); |
931 | count = MALLOC_PAGESIZE / size; | ||
928 | 932 | ||
929 | /* Don't use cache here, we don't want user uaf touch this */ | 933 | /* Don't use cache here, we don't want user uaf touch this */ |
930 | q = MMAP(MALLOC_PAGESIZE, d->mmap_flag); | 934 | if (d->chunk_pages_used == chunk_pages || |
931 | if (q == MAP_FAILED) | 935 | d->chunk_pages == NULL) { |
932 | return NULL; | 936 | q = MMAP(MALLOC_PAGESIZE * chunk_pages, d->mmap_flag); |
933 | STATS_ADD(d->malloc_used, MALLOC_PAGESIZE); | 937 | if (q == MAP_FAILED) |
934 | count = MALLOC_PAGESIZE / size; | 938 | return NULL; |
939 | d->chunk_pages = q; | ||
940 | d->chunk_pages_used = 0; | ||
941 | STATS_ADD(d->malloc_used, MALLOC_PAGESIZE * | ||
942 | chunk_pages); | ||
943 | } | ||
944 | q = (char *)d->chunk_pages + d->chunk_pages_used * | ||
945 | MALLOC_PAGESIZE; | ||
946 | d->chunk_pages_used++; | ||
935 | 947 | ||
936 | for (i = 0; i < count; i++, q += size) { | 948 | for (i = 0; i < count; i++, q += size) { |
937 | p = (struct chunk_info *)q; | 949 | p = (struct chunk_info *)q; |
938 | LIST_INSERT_HEAD(&d->chunk_info_list[bits], p, entries); | 950 | LIST_INSERT_HEAD(&d->chunk_info_list[bucket], p, entries); |
939 | } | 951 | } |
940 | } | 952 | } |
941 | p = LIST_FIRST(&d->chunk_info_list[bits]); | 953 | p = LIST_FIRST(&d->chunk_info_list[bucket]); |
942 | LIST_REMOVE(p, entries); | 954 | LIST_REMOVE(p, entries); |
943 | if (p->shift == 0) | 955 | if (p->total == 0) |
944 | init_chunk_info(d, p, bits); | 956 | init_chunk_info(d, p, bucket); |
945 | return p; | 957 | return p; |
946 | } | 958 | } |
947 | 959 | ||
@@ -949,7 +961,7 @@ alloc_chunk_info(struct dir_info *d, int bits) | |||
949 | * Allocate a page of chunks | 961 | * Allocate a page of chunks |
950 | */ | 962 | */ |
951 | static struct chunk_info * | 963 | static struct chunk_info * |
952 | omalloc_make_chunks(struct dir_info *d, int bits, int listnum) | 964 | omalloc_make_chunks(struct dir_info *d, u_int bucket, u_int listnum) |
953 | { | 965 | { |
954 | struct chunk_info *bp; | 966 | struct chunk_info *bp; |
955 | void *pp; | 967 | void *pp; |
@@ -960,18 +972,18 @@ omalloc_make_chunks(struct dir_info *d, int bits, int listnum) | |||
960 | return NULL; | 972 | return NULL; |
961 | 973 | ||
962 | /* memory protect the page allocated in the malloc(0) case */ | 974 | /* memory protect the page allocated in the malloc(0) case */ |
963 | if (bits == 0 && mprotect(pp, MALLOC_PAGESIZE, PROT_NONE) == -1) | 975 | if (bucket == 0 && mprotect(pp, MALLOC_PAGESIZE, PROT_NONE) == -1) |
964 | goto err; | 976 | goto err; |
965 | 977 | ||
966 | bp = alloc_chunk_info(d, bits); | 978 | bp = alloc_chunk_info(d, bucket); |
967 | if (bp == NULL) | 979 | if (bp == NULL) |
968 | goto err; | 980 | goto err; |
969 | bp->page = pp; | 981 | bp->page = pp; |
970 | 982 | ||
971 | if (insert(d, (void *)((uintptr_t)pp | (bits + 1)), (uintptr_t)bp, | 983 | if (insert(d, (void *)((uintptr_t)pp | (bucket + 1)), (uintptr_t)bp, |
972 | NULL)) | 984 | NULL)) |
973 | goto err; | 985 | goto err; |
974 | LIST_INSERT_HEAD(&d->chunk_dir[bits][listnum], bp, entries); | 986 | LIST_INSERT_HEAD(&d->chunk_dir[bucket][listnum], bp, entries); |
975 | return bp; | 987 | return bp; |
976 | 988 | ||
977 | err: | 989 | err: |
@@ -979,23 +991,46 @@ err: | |||
979 | return NULL; | 991 | return NULL; |
980 | } | 992 | } |
981 | 993 | ||
982 | static int | 994 | static inline unsigned int |
983 | find_chunksize(size_t size) | 995 | lb(u_int x) |
996 | { | ||
997 | /* I need an extension just for integer-length (: */ | ||
998 | return (sizeof(int) * CHAR_BIT - 1) - __builtin_clz(x); | ||
999 | } | ||
1000 | |||
1001 | /* https://pvk.ca/Blog/2015/06/27/linear-log-bucketing-fast-versatile-simple/ | ||
1002 | via Tony Finch */ | ||
1003 | static inline unsigned int | ||
1004 | bin_of(unsigned int size) | ||
984 | { | 1005 | { |
985 | int r; | 1006 | const unsigned int linear = 6; |
1007 | const unsigned int subbin = 2; | ||
986 | 1008 | ||
1009 | unsigned int mask, range, rounded, sub_index, rounded_size; | ||
1010 | unsigned int n_bits, shift; | ||
1011 | |||
1012 | n_bits = lb(size | (1U << linear)); | ||
1013 | shift = n_bits - subbin; | ||
1014 | mask = (1ULL << shift) - 1; | ||
1015 | rounded = size + mask; /* XXX: overflow. */ | ||
1016 | sub_index = rounded >> shift; | ||
1017 | range = n_bits - linear; | ||
1018 | |||
1019 | rounded_size = rounded & ~mask; | ||
1020 | return rounded_size; | ||
1021 | } | ||
1022 | |||
1023 | static inline u_short | ||
1024 | find_bucket(u_short size) | ||
1025 | { | ||
987 | /* malloc(0) is special */ | 1026 | /* malloc(0) is special */ |
988 | if (size == 0) | 1027 | if (size == 0) |
989 | return 0; | 1028 | return 0; |
990 | |||
991 | if (size < MALLOC_MINSIZE) | 1029 | if (size < MALLOC_MINSIZE) |
992 | size = MALLOC_MINSIZE; | 1030 | size = MALLOC_MINSIZE; |
993 | size--; | 1031 | if (mopts.def_maxcache != 0) |
994 | 1032 | size = bin_of(size); | |
995 | r = MALLOC_MINSHIFT; | 1033 | return howmany(size, MALLOC_MINSIZE); |
996 | while (size >> r) | ||
997 | r++; | ||
998 | return r; | ||
999 | } | 1034 | } |
1000 | 1035 | ||
1001 | static void | 1036 | static void |
@@ -1014,8 +1049,7 @@ fill_canary(char *ptr, size_t sz, size_t allocated) | |||
1014 | static void * | 1049 | static void * |
1015 | malloc_bytes(struct dir_info *d, size_t size, void *f) | 1050 | malloc_bytes(struct dir_info *d, size_t size, void *f) |
1016 | { | 1051 | { |
1017 | u_int i, r; | 1052 | u_int i, r, bucket, listnum; |
1018 | int j, listnum; | ||
1019 | size_t k; | 1053 | size_t k; |
1020 | u_short *lp; | 1054 | u_short *lp; |
1021 | struct chunk_info *bp; | 1055 | struct chunk_info *bp; |
@@ -1025,13 +1059,14 @@ malloc_bytes(struct dir_info *d, size_t size, void *f) | |||
1025 | d->canary1 != ~d->canary2) | 1059 | d->canary1 != ~d->canary2) |
1026 | wrterror(d, "internal struct corrupt"); | 1060 | wrterror(d, "internal struct corrupt"); |
1027 | 1061 | ||
1028 | j = find_chunksize(size); | 1062 | bucket = find_bucket(size); |
1029 | 1063 | ||
1030 | r = ((u_int)getrbyte(d) << 8) | getrbyte(d); | 1064 | r = ((u_int)getrbyte(d) << 8) | getrbyte(d); |
1031 | listnum = r % MALLOC_CHUNK_LISTS; | 1065 | listnum = r % MALLOC_CHUNK_LISTS; |
1066 | |||
1032 | /* If it's empty, make a page more of that size chunks */ | 1067 | /* If it's empty, make a page more of that size chunks */ |
1033 | if ((bp = LIST_FIRST(&d->chunk_dir[j][listnum])) == NULL) { | 1068 | if ((bp = LIST_FIRST(&d->chunk_dir[bucket][listnum])) == NULL) { |
1034 | bp = omalloc_make_chunks(d, j, listnum); | 1069 | bp = omalloc_make_chunks(d, bucket, listnum); |
1035 | if (bp == NULL) | 1070 | if (bp == NULL) |
1036 | return NULL; | 1071 | return NULL; |
1037 | } | 1072 | } |
@@ -1039,12 +1074,13 @@ malloc_bytes(struct dir_info *d, size_t size, void *f) | |||
1039 | if (bp->canary != (u_short)d->canary1) | 1074 | if (bp->canary != (u_short)d->canary1) |
1040 | wrterror(d, "chunk info corrupted"); | 1075 | wrterror(d, "chunk info corrupted"); |
1041 | 1076 | ||
1042 | i = (r / MALLOC_CHUNK_LISTS) & (bp->total - 1); | 1077 | /* bias, as bp->total is not a power of 2 */ |
1078 | i = (r / MALLOC_CHUNK_LISTS) % bp->total; | ||
1043 | 1079 | ||
1044 | /* start somewhere in a short */ | 1080 | /* potentially start somewhere in a short */ |
1045 | lp = &bp->bits[i / MALLOC_BITS]; | 1081 | lp = &bp->bits[i / MALLOC_BITS]; |
1046 | if (*lp) { | 1082 | if (*lp) { |
1047 | j = i % MALLOC_BITS; | 1083 | int j = i % MALLOC_BITS; /* j must be signed */ |
1048 | k = ffs(*lp >> j); | 1084 | k = ffs(*lp >> j); |
1049 | if (k != 0) { | 1085 | if (k != 0) { |
1050 | k += j - 1; | 1086 | k += j - 1; |
@@ -1054,7 +1090,7 @@ malloc_bytes(struct dir_info *d, size_t size, void *f) | |||
1054 | /* no bit halfway, go to next full short */ | 1090 | /* no bit halfway, go to next full short */ |
1055 | i /= MALLOC_BITS; | 1091 | i /= MALLOC_BITS; |
1056 | for (;;) { | 1092 | for (;;) { |
1057 | if (++i >= bp->total / MALLOC_BITS) | 1093 | if (++i >= howmany(bp->total, MALLOC_BITS)) |
1058 | i = 0; | 1094 | i = 0; |
1059 | lp = &bp->bits[i]; | 1095 | lp = &bp->bits[i]; |
1060 | if (*lp) { | 1096 | if (*lp) { |
@@ -1082,14 +1118,14 @@ found: | |||
1082 | if (mopts.chunk_canaries && size > 0) | 1118 | if (mopts.chunk_canaries && size > 0) |
1083 | bp->bits[bp->offset + k] = size; | 1119 | bp->bits[bp->offset + k] = size; |
1084 | 1120 | ||
1085 | k <<= bp->shift; | 1121 | k *= B2ALLOC(bp->bucket); |
1086 | 1122 | ||
1087 | p = (char *)bp->page + k; | 1123 | p = (char *)bp->page + k; |
1088 | if (bp->size > 0) { | 1124 | if (bp->bucket > 0) { |
1089 | if (d->malloc_junk == 2) | 1125 | if (d->malloc_junk == 2) |
1090 | memset(p, SOME_JUNK, bp->size); | 1126 | memset(p, SOME_JUNK, B2SIZE(bp->bucket)); |
1091 | else if (mopts.chunk_canaries) | 1127 | else if (mopts.chunk_canaries) |
1092 | fill_canary(p, size, bp->size); | 1128 | fill_canary(p, size, B2SIZE(bp->bucket)); |
1093 | } | 1129 | } |
1094 | return p; | 1130 | return p; |
1095 | } | 1131 | } |
@@ -1124,16 +1160,16 @@ find_chunknum(struct dir_info *d, struct chunk_info *info, void *ptr, int check) | |||
1124 | wrterror(d, "chunk info corrupted"); | 1160 | wrterror(d, "chunk info corrupted"); |
1125 | 1161 | ||
1126 | /* Find the chunk number on the page */ | 1162 | /* Find the chunk number on the page */ |
1127 | chunknum = ((uintptr_t)ptr & MALLOC_PAGEMASK) >> info->shift; | 1163 | chunknum = ((uintptr_t)ptr & MALLOC_PAGEMASK) / B2ALLOC(info->bucket); |
1128 | 1164 | ||
1129 | if ((uintptr_t)ptr & ((1U << (info->shift)) - 1)) | 1165 | if ((uintptr_t)ptr & (MALLOC_MINSIZE - 1)) |
1130 | wrterror(d, "modified chunk-pointer %p", ptr); | 1166 | wrterror(d, "modified chunk-pointer %p", ptr); |
1131 | if (info->bits[chunknum / MALLOC_BITS] & | 1167 | if (info->bits[chunknum / MALLOC_BITS] & |
1132 | (1U << (chunknum % MALLOC_BITS))) | 1168 | (1U << (chunknum % MALLOC_BITS))) |
1133 | wrterror(d, "chunk is already free %p", ptr); | 1169 | wrterror(d, "chunk is already free %p", ptr); |
1134 | if (check && info->size > 0) { | 1170 | if (check && info->bucket > 0) { |
1135 | validate_canary(d, ptr, info->bits[info->offset + chunknum], | 1171 | validate_canary(d, ptr, info->bits[info->offset + chunknum], |
1136 | info->size); | 1172 | B2SIZE(info->bucket)); |
1137 | } | 1173 | } |
1138 | return chunknum; | 1174 | return chunknum; |
1139 | } | 1175 | } |
@@ -1147,7 +1183,7 @@ free_bytes(struct dir_info *d, struct region_info *r, void *ptr) | |||
1147 | struct chunk_head *mp; | 1183 | struct chunk_head *mp; |
1148 | struct chunk_info *info; | 1184 | struct chunk_info *info; |
1149 | uint32_t chunknum; | 1185 | uint32_t chunknum; |
1150 | int listnum; | 1186 | uint32_t listnum; |
1151 | 1187 | ||
1152 | info = (struct chunk_info *)r->size; | 1188 | info = (struct chunk_info *)r->size; |
1153 | chunknum = find_chunknum(d, info, ptr, 0); | 1189 | chunknum = find_chunknum(d, info, ptr, 0); |
@@ -1158,11 +1194,7 @@ free_bytes(struct dir_info *d, struct region_info *r, void *ptr) | |||
1158 | if (info->free == 1) { | 1194 | if (info->free == 1) { |
1159 | /* Page became non-full */ | 1195 | /* Page became non-full */ |
1160 | listnum = getrbyte(d) % MALLOC_CHUNK_LISTS; | 1196 | listnum = getrbyte(d) % MALLOC_CHUNK_LISTS; |
1161 | if (info->size != 0) | 1197 | mp = &d->chunk_dir[info->bucket][listnum]; |
1162 | mp = &d->chunk_dir[info->shift][listnum]; | ||
1163 | else | ||
1164 | mp = &d->chunk_dir[0][listnum]; | ||
1165 | |||
1166 | LIST_INSERT_HEAD(mp, info, entries); | 1198 | LIST_INSERT_HEAD(mp, info, entries); |
1167 | return; | 1199 | return; |
1168 | } | 1200 | } |
@@ -1172,15 +1204,12 @@ free_bytes(struct dir_info *d, struct region_info *r, void *ptr) | |||
1172 | 1204 | ||
1173 | LIST_REMOVE(info, entries); | 1205 | LIST_REMOVE(info, entries); |
1174 | 1206 | ||
1175 | if (info->size == 0 && !mopts.malloc_freeunmap) | 1207 | if (info->bucket == 0 && !mopts.malloc_freeunmap) |
1176 | mprotect(info->page, MALLOC_PAGESIZE, PROT_READ | PROT_WRITE); | 1208 | mprotect(info->page, MALLOC_PAGESIZE, PROT_READ | PROT_WRITE); |
1177 | unmap(d, info->page, MALLOC_PAGESIZE, 0); | 1209 | unmap(d, info->page, MALLOC_PAGESIZE, 0); |
1178 | 1210 | ||
1179 | delete(d, r); | 1211 | delete(d, r); |
1180 | if (info->size != 0) | 1212 | mp = &d->chunk_info_list[info->bucket]; |
1181 | mp = &d->chunk_info_list[info->shift]; | ||
1182 | else | ||
1183 | mp = &d->chunk_info_list[0]; | ||
1184 | LIST_INSERT_HEAD(mp, info, entries); | 1213 | LIST_INSERT_HEAD(mp, info, entries); |
1185 | } | 1214 | } |
1186 | 1215 | ||
@@ -1520,7 +1549,7 @@ ofree(struct dir_info **argpool, void *p, int clear, int check, size_t argsz) | |||
1520 | 1549 | ||
1521 | /* Validate and optionally canary check */ | 1550 | /* Validate and optionally canary check */ |
1522 | struct chunk_info *info = (struct chunk_info *)r->size; | 1551 | struct chunk_info *info = (struct chunk_info *)r->size; |
1523 | if (info->size != sz) | 1552 | if (B2SIZE(info->bucket) != sz) |
1524 | wrterror(pool, "internal struct corrupt"); | 1553 | wrterror(pool, "internal struct corrupt"); |
1525 | find_chunknum(pool, info, p, mopts.chunk_canaries); | 1554 | find_chunknum(pool, info, p, mopts.chunk_canaries); |
1526 | 1555 | ||
@@ -1750,13 +1779,13 @@ orealloc(struct dir_info **argpool, void *p, size_t newsz, void *f) | |||
1750 | } | 1779 | } |
1751 | if (oldsz <= MALLOC_MAXCHUNK && oldsz > 0 && | 1780 | if (oldsz <= MALLOC_MAXCHUNK && oldsz > 0 && |
1752 | newsz <= MALLOC_MAXCHUNK && newsz > 0 && | 1781 | newsz <= MALLOC_MAXCHUNK && newsz > 0 && |
1753 | 1 << find_chunksize(newsz) == oldsz && !forced) { | 1782 | !forced && find_bucket(newsz) == find_bucket(oldsz)) { |
1754 | /* do not reallocate if new size fits good in existing chunk */ | 1783 | /* do not reallocate if new size fits good in existing chunk */ |
1755 | if (pool->malloc_junk == 2) | 1784 | if (pool->malloc_junk == 2) |
1756 | memset((char *)p + newsz, SOME_JUNK, oldsz - newsz); | 1785 | memset((char *)p + newsz, SOME_JUNK, oldsz - newsz); |
1757 | if (mopts.chunk_canaries) { | 1786 | if (mopts.chunk_canaries) { |
1758 | info->bits[info->offset + chunknum] = newsz; | 1787 | info->bits[info->offset + chunknum] = newsz; |
1759 | fill_canary(p, newsz, info->size); | 1788 | fill_canary(p, newsz, B2SIZE(info->bucket)); |
1760 | } | 1789 | } |
1761 | STATS_SETF(r, f); | 1790 | STATS_SETF(r, f); |
1762 | ret = p; | 1791 | ret = p; |
@@ -2048,14 +2077,21 @@ omemalign(struct dir_info *pool, size_t alignment, size_t sz, int zero_fill, | |||
2048 | if (sz > MALLOC_MAXCHUNK && sz < MALLOC_PAGESIZE) | 2077 | if (sz > MALLOC_MAXCHUNK && sz < MALLOC_PAGESIZE) |
2049 | sz = MALLOC_PAGESIZE; | 2078 | sz = MALLOC_PAGESIZE; |
2050 | if (alignment <= MALLOC_PAGESIZE) { | 2079 | if (alignment <= MALLOC_PAGESIZE) { |
2080 | size_t pof2; | ||
2051 | /* | 2081 | /* |
2052 | * max(size, alignment) is enough to assure the requested | 2082 | * max(size, alignment) rounded up to power of 2 is enough |
2053 | * alignment, since the allocator always allocates | 2083 | * to assure the requested alignment. Large regions are |
2054 | * power-of-two blocks. | 2084 | * always page aligned. |
2055 | */ | 2085 | */ |
2056 | if (sz < alignment) | 2086 | if (sz < alignment) |
2057 | sz = alignment; | 2087 | sz = alignment; |
2058 | return omalloc(pool, sz, zero_fill, f); | 2088 | if (sz < MALLOC_PAGESIZE) { |
2089 | pof2 = MALLOC_MINSIZE; | ||
2090 | while (pof2 < sz) | ||
2091 | pof2 <<= 1; | ||
2092 | } else | ||
2093 | pof2 = sz; | ||
2094 | return omalloc(pool, pof2, zero_fill, f); | ||
2059 | } | 2095 | } |
2060 | 2096 | ||
2061 | if (sz >= SIZE_MAX - mopts.malloc_guard - MALLOC_PAGESIZE) { | 2097 | if (sz >= SIZE_MAX - mopts.malloc_guard - MALLOC_PAGESIZE) { |
@@ -2252,15 +2288,16 @@ static void | |||
2252 | dump_chunk(int fd, struct chunk_info *p, void *f, int fromfreelist) | 2288 | dump_chunk(int fd, struct chunk_info *p, void *f, int fromfreelist) |
2253 | { | 2289 | { |
2254 | while (p != NULL) { | 2290 | while (p != NULL) { |
2255 | dprintf(fd, "chunk %18p %18p %4d %d/%d\n", | 2291 | dprintf(fd, "chunk %18p %18p %4zu %d/%d\n", |
2256 | p->page, ((p->bits[0] & 1) ? NULL : f), | 2292 | p->page, ((p->bits[0] & 1) ? NULL : f), |
2257 | p->size, p->free, p->total); | 2293 | B2SIZE(p->bucket), p->free, p->total); |
2258 | if (!fromfreelist) { | 2294 | if (!fromfreelist) { |
2295 | size_t sz = B2SIZE(p->bucket); | ||
2259 | if (p->bits[0] & 1) | 2296 | if (p->bits[0] & 1) |
2260 | putleakinfo(NULL, p->size, p->total - p->free); | 2297 | putleakinfo(NULL, sz, p->total - p->free); |
2261 | else { | 2298 | else { |
2262 | putleakinfo(f, p->size, 1); | 2299 | putleakinfo(f, sz, 1); |
2263 | putleakinfo(NULL, p->size, | 2300 | putleakinfo(NULL, sz, |
2264 | p->total - p->free - 1); | 2301 | p->total - p->free - 1); |
2265 | } | 2302 | } |
2266 | break; | 2303 | break; |
@@ -2278,7 +2315,7 @@ dump_free_chunk_info(int fd, struct dir_info *d) | |||
2278 | struct chunk_info *p; | 2315 | struct chunk_info *p; |
2279 | 2316 | ||
2280 | dprintf(fd, "Free chunk structs:\n"); | 2317 | dprintf(fd, "Free chunk structs:\n"); |
2281 | for (i = 0; i <= MALLOC_MAXSHIFT; i++) { | 2318 | for (i = 0; i <= BUCKETS; i++) { |
2282 | count = 0; | 2319 | count = 0; |
2283 | LIST_FOREACH(p, &d->chunk_info_list[i], entries) | 2320 | LIST_FOREACH(p, &d->chunk_info_list[i], entries) |
2284 | count++; | 2321 | count++; |
@@ -2286,7 +2323,10 @@ dump_free_chunk_info(int fd, struct dir_info *d) | |||
2286 | p = LIST_FIRST(&d->chunk_dir[i][j]); | 2323 | p = LIST_FIRST(&d->chunk_dir[i][j]); |
2287 | if (p == NULL && count == 0) | 2324 | if (p == NULL && count == 0) |
2288 | continue; | 2325 | continue; |
2289 | dprintf(fd, "%2d) %3d ", i, count); | 2326 | if (j == 0) |
2327 | dprintf(fd, "%3d) %3d ", i, count); | ||
2328 | else | ||
2329 | dprintf(fd, " "); | ||
2290 | if (p != NULL) | 2330 | if (p != NULL) |
2291 | dump_chunk(fd, p, NULL, 1); | 2331 | dump_chunk(fd, p, NULL, 1); |
2292 | else | 2332 | else |