summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorotto <>2023-03-25 15:22:06 +0000
committerotto <>2023-03-25 15:22:06 +0000
commitaa9c36ea6771e5920cc990455a1c5a238404271a (patch)
treed2c5e22c1b7782c67519711149e923495a1a3acb /src
parentfaae8138ba863e01a95f59b65ee87dac2b5cf5bd (diff)
downloadopenbsd-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.c244
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
256static inline void 262static 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
885static void 898static void
886init_chunk_info(struct dir_info *d, struct chunk_info *p, int bits) 899init_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
909static struct chunk_info * 914static struct chunk_info *
910alloc_chunk_info(struct dir_info *d, int bits) 915alloc_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 */
951static struct chunk_info * 963static struct chunk_info *
952omalloc_make_chunks(struct dir_info *d, int bits, int listnum) 964omalloc_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
977err: 989err:
@@ -979,23 +991,46 @@ err:
979 return NULL; 991 return NULL;
980} 992}
981 993
982static int 994static inline unsigned int
983find_chunksize(size_t size) 995lb(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 */
1003static inline unsigned int
1004bin_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
1023static inline u_short
1024find_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
1001static void 1036static void
@@ -1014,8 +1049,7 @@ fill_canary(char *ptr, size_t sz, size_t allocated)
1014static void * 1049static void *
1015malloc_bytes(struct dir_info *d, size_t size, void *f) 1050malloc_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
2252dump_chunk(int fd, struct chunk_info *p, void *f, int fromfreelist) 2288dump_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