1/*  $OpenBSD: malloc.c,v 1.297 2024/09/20 02:00:46 jsg Exp $    */
2/*
3 * Copyright (c) 2008, 2010, 2011, 2016, 2023 Otto Moerbeek <otto@drijf.net>
4 * Copyright (c) 2012 Matthew Dempsky <matthew@openbsd.org>
5 * Copyright (c) 2008 Damien Miller <djm@openbsd.org>
6 * Copyright (c) 2000 Poul-Henning Kamp <phk@FreeBSD.org>
7 *
8 * Permission to use, copy, modify, and distribute this software for any
9 * purpose with or without fee is hereby granted, provided that the above
10 * copyright notice and this permission notice appear in all copies.
11 *
12 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 */
20
21/*
22 * If we meet some day, and you think this stuff is worth it, you
23 * can buy me a beer in return. Poul-Henning Kamp
24 */
25
26#ifndef MALLOC_SMALL
27#define MALLOC_STATS
28#endif
29
30#include <sys/types.h>
31#include <sys/queue.h>
32#include <sys/mman.h>
33#include <sys/sysctl.h>
34#include <uvm/uvmexp.h>
35#include <errno.h>
36#include <stdarg.h>
37#include <stdint.h>
38#include <stdio.h>
39#include <stdlib.h>
40#include <string.h>
41#include <unistd.h>
42
43#ifdef MALLOC_STATS
44#include <sys/tree.h>
45#include <sys/ktrace.h>
46#include <dlfcn.h>
47#endif
48
49#include "thread_private.h"
50#include <tib.h>
51
52#define MALLOC_PAGESHIFT    _MAX_PAGE_SHIFT
53
54#define MALLOC_MINSHIFT     4
55#define MALLOC_MAXSHIFT     (MALLOC_PAGESHIFT - 1)
56#define MALLOC_PAGESIZE     (1UL << MALLOC_PAGESHIFT)
57#define MALLOC_MINSIZE      (1UL << MALLOC_MINSHIFT)
58#define MALLOC_PAGEMASK     (MALLOC_PAGESIZE - 1)
59#define MASK_POINTER(p)     ((void *)(((uintptr_t)(p)) & ~MALLOC_PAGEMASK))
60
61#define MALLOC_MAXCHUNK     (1 << MALLOC_MAXSHIFT)
62#define MALLOC_MAXCACHE     256
63#define MALLOC_DELAYED_CHUNK_MASK   15
64#ifdef MALLOC_STATS
65#define MALLOC_INITIAL_REGIONS  512
66#else
67#define MALLOC_INITIAL_REGIONS  (MALLOC_PAGESIZE / sizeof(struct region_info))
68#endif
69#define MALLOC_DEFAULT_CACHE    64
70#define MALLOC_CHUNK_LISTS  4
71#define CHUNK_CHECK_LENGTH  32
72
73#define B2SIZE(b)       ((b) * MALLOC_MINSIZE)
74#define B2ALLOC(b)      ((b) == 0 ? MALLOC_MINSIZE : \
75                    (b) * MALLOC_MINSIZE)
76#define BUCKETS         (MALLOC_MAXCHUNK / MALLOC_MINSIZE)
77
78/*
79 * We move allocations between half a page and a whole page towards the end,
80 * subject to alignment constraints. This is the extra headroom we allow.
81 * Set to zero to be the most strict.
82 */
83#define MALLOC_LEEWAY       0
84#define MALLOC_MOVE_COND(sz)    ((sz) - mopts.malloc_guard <        \
85                    MALLOC_PAGESIZE - MALLOC_LEEWAY)
86#define MALLOC_MOVE(p, sz)      (((char *)(p)) +            \
87                    ((MALLOC_PAGESIZE - MALLOC_LEEWAY - \
88                        ((sz) - mopts.malloc_guard)) &  \
89                    ~(MALLOC_MINSIZE - 1)))
90
91#define PAGEROUND(x)  (((x) + (MALLOC_PAGEMASK)) & ~MALLOC_PAGEMASK)
92
93/*
94 * What to use for Junk.  This is the byte value we use to fill with
95 * when the 'J' option is enabled. Use SOME_JUNK right after alloc,
96 * and SOME_FREEJUNK right before free.
97 */
98#define SOME_JUNK       0xdb    /* deadbeef */
99#define SOME_FREEJUNK       0xdf    /* dead, free */
100#define SOME_FREEJUNK_ULL   0xdfdfdfdfdfdfdfdfULL
101
102#define MMAP(sz,f)  mmap(NULL, (sz), PROT_READ | PROT_WRITE, \
103    MAP_ANON | MAP_PRIVATE | (f), -1, 0)
104
105#define MMAPNONE(sz,f)  mmap(NULL, (sz), PROT_NONE, \
106    MAP_ANON | MAP_PRIVATE | (f), -1, 0)
107
108#define MMAPA(a,sz,f)   mmap((a), (sz), PROT_READ | PROT_WRITE, \
109    MAP_ANON | MAP_PRIVATE | (f), -1, 0)
110
111struct region_info {
112    void *p;        /* page; low bits used to mark chunks */
113    uintptr_t size;     /* size for pages, or chunk_info pointer */
114#ifdef MALLOC_STATS
115    void **f;       /* where allocated from */
116#endif
117};
118
119LIST_HEAD(chunk_head, chunk_info);
120
121/*
122 * Two caches, one for "small" regions, one for "big".
123 * Small cache is an array per size, big cache is one array with different
124 * sized regions
125 */
126#define MAX_SMALLCACHEABLE_SIZE 32
127#define MAX_BIGCACHEABLE_SIZE   512
128/* If the total # of pages is larger than this, evict before inserting */
129#define BIGCACHE_FILL(sz)   (MAX_BIGCACHEABLE_SIZE * (sz) / 4)
130
131struct smallcache {
132    void **pages;
133    ushort length;
134    ushort max;
135};
136
137struct bigcache {
138    void *page;
139    size_t psize;
140};
141
142#ifdef MALLOC_STATS
143#define NUM_FRAMES      4
144struct btnode {
145    RBT_ENTRY(btnode) entry;
146    void *caller[NUM_FRAMES];
147};
148RBT_HEAD(btshead, btnode);
149RBT_PROTOTYPE(btshead, btnode, entry, btcmp);
150#endif /* MALLOC_STATS */
151
152struct dir_info {
153    u_int32_t canary1;
154    int active;         /* status of malloc */
155    struct region_info *r;      /* region slots */
156    size_t regions_total;       /* number of region slots */
157    size_t regions_free;        /* number of free slots */
158    size_t rbytesused;      /* random bytes used */
159    const char *func;       /* current function */
160    int malloc_junk;        /* junk fill? */
161    int mmap_flag;          /* extra flag for mmap */
162    int mutex;
163    int malloc_mt;          /* multi-threaded mode? */
164                    /* lists of free chunk info structs */
165    struct chunk_head chunk_info_list[BUCKETS + 1];
166                    /* lists of chunks with free slots */
167    struct chunk_head chunk_dir[BUCKETS + 1][MALLOC_CHUNK_LISTS];
168                    /* delayed free chunk slots */
169    void *delayed_chunks[MALLOC_DELAYED_CHUNK_MASK + 1];
170    u_char rbytes[32];      /* random bytes */
171                    /* free pages cache */
172    struct smallcache smallcache[MAX_SMALLCACHEABLE_SIZE];
173    size_t bigcache_used;
174    size_t bigcache_size;
175    struct bigcache *bigcache;
176    void *chunk_pages;
177    size_t chunk_pages_used;
178#ifdef MALLOC_STATS
179    void *caller;
180    size_t inserts;
181    size_t insert_collisions;
182    size_t deletes;
183    size_t delete_moves;
184    size_t cheap_realloc_tries;
185    size_t cheap_reallocs;
186    size_t malloc_used;     /* bytes allocated */
187    size_t malloc_guarded;      /* bytes used for guards */
188    struct btshead btraces;     /* backtraces seen */
189    struct btnode *btnodes;     /* store of backtrace nodes */
190    size_t btnodesused;
191#define STATS_ADD(x,y)  ((x) += (y))
192#define STATS_SUB(x,y)  ((x) -= (y))
193#define STATS_INC(x)    ((x)++)
194#define STATS_ZERO(x)   ((x) = 0)
195#define STATS_SETF(x,y) ((x)->f = (y))
196#define STATS_SETFN(x,k,y)  ((x)->f[k] = (y))
197#define SET_CALLER(x,y) if (DO_STATS) ((x)->caller = (y))
198#else
199#define STATS_ADD(x,y)  /* nothing */
200#define STATS_SUB(x,y)  /* nothing */
201#define STATS_INC(x)    /* nothing */
202#define STATS_ZERO(x)   /* nothing */
203#define STATS_SETF(x,y) /* nothing */
204#define STATS_SETFN(x,k,y)  /* nothing */
205#define SET_CALLER(x,y) /* nothing */
206#endif /* MALLOC_STATS */
207    u_int32_t canary2;
208};
209
210static void unmap(struct dir_info *d, void *p, size_t sz, size_t clear);
211
212/*
213 * This structure describes a page worth of chunks.
214 *
215 * How many bits per u_short in the bitmap
216 */
217#define MALLOC_BITS     (NBBY * sizeof(u_short))
218struct chunk_info {
219    LIST_ENTRY(chunk_info) entries;
220    void *page;         /* pointer to the page */
221    /* number of shorts should add up to 8, check alloc_chunk_info() */
222    u_short canary;
223    u_short bucket;
224    u_short free;           /* how many free chunks */
225    u_short total;          /* how many chunks */
226    u_short offset;         /* requested size table offset */
227#define CHUNK_INFO_TAIL         3
228    u_short bits[CHUNK_INFO_TAIL];  /* which chunks are free */
229};
230
231#define CHUNK_FREE(i, n) ((i)->bits[(n) / MALLOC_BITS] & \
232    (1U << ((n) % MALLOC_BITS)))
233
234struct malloc_readonly {
235                    /* Main bookkeeping information */
236    struct dir_info *malloc_pool[_MALLOC_MUTEXES];
237    u_int   malloc_mutexes;     /* how much in actual use? */
238    int malloc_freecheck;   /* Extensive double free check */
239    int malloc_freeunmap;   /* mprotect free pages PROT_NONE? */
240    int def_malloc_junk;    /* junk fill? */
241    int malloc_realloc;     /* always realloc? */
242    int malloc_xmalloc;     /* xmalloc behaviour? */
243    u_int   chunk_canaries;     /* use canaries after chunks? */
244    int internal_funcs;     /* use better recallocarray/freezero? */
245    u_int   def_maxcache;       /* free pages we cache */
246    u_int   junk_loc;       /* variation in location of junk */
247    size_t  malloc_guard;       /* use guard pages after allocations? */
248#ifdef MALLOC_STATS
249    int malloc_stats;       /* save callers, dump leak report */
250    int malloc_verbose;     /* dump verbose statistics at end */
251#define DO_STATS    mopts.malloc_stats
252#else
253#define DO_STATS    0
254#endif
255    u_int32_t malloc_canary;    /* Matched against ones in pool */
256};
257
258
259/* This object is mapped PROT_READ after initialisation to prevent tampering */
260static union {
261    struct malloc_readonly mopts;
262    u_char _pad[MALLOC_PAGESIZE];
263} malloc_readonly __attribute__((aligned(MALLOC_PAGESIZE)))
264        __attribute__((section(".openbsd.mutable")));
265#define mopts   malloc_readonly.mopts
266
267char        *malloc_options;    /* compile-time options */
268
269static __dead void wrterror(struct dir_info *d, char *msg, ...)
270    __attribute__((__format__ (printf, 2, 3)));
271
272#ifdef MALLOC_STATS
273void malloc_dump(void);
274PROTO_NORMAL(malloc_dump);
275static void malloc_exit(void);
276static void print_chunk_details(struct dir_info *, void *, size_t, size_t);
277static void* store_caller(struct dir_info *, struct btnode *);
278
279/* below are the arches for which deeper caller info has been tested */
280#if defined(__aarch64__) || \
281    defined(__amd64__) || \
282    defined(__arm__) || \
283    defined(__i386__) || \
284    defined(__powerpc__)
285__attribute__((always_inline))
286static inline void*
287caller(struct dir_info *d)
288{
289    struct btnode p;
290    int level = DO_STATS;
291
292    if (level == 0)
293        return NULL;
294
295    memset(&p.caller, 0, sizeof(p.caller));
296    if (level >= 1)
297        p.caller[0] = __builtin_extract_return_addr(
298            __builtin_return_address(0));
299    if (p.caller[0] != NULL && level >= 2)
300        p.caller[1] = __builtin_extract_return_addr(
301            __builtin_return_address(1));
302    if (p.caller[1] != NULL && level >= 3)
303        p.caller[2] = __builtin_extract_return_addr(
304            __builtin_return_address(2));
305    if (p.caller[2] != NULL && level >= 4)
306        p.caller[3] = __builtin_extract_return_addr(
307            __builtin_return_address(3));
308    return store_caller(d, &p);
309}
310#else
311__attribute__((always_inline))
312static inline void* caller(struct dir_info *d)
313{
314    struct btnode p;
315
316    if (DO_STATS == 0)
317        return NULL;
318    memset(&p.caller, 0, sizeof(p.caller));
319    p.caller[0] = __builtin_extract_return_addr(__builtin_return_address(0));
320    return store_caller(d, &p);
321}
322#endif
323#endif /* MALLOC_STATS */
324
325/* low bits of r->p determine size: 0 means >= page size and r->size holding
326 * real size, otherwise low bits is the bucket + 1
327 */
328#define REALSIZE(sz, r)                     \
329    (sz) = (uintptr_t)(r)->p & MALLOC_PAGEMASK,     \
330    (sz) = ((sz) == 0 ? (r)->size : B2SIZE((sz) - 1))
331
332static inline size_t
333hash(void *p)
334{
335    size_t sum;
336    uintptr_t u;
337
338    u = (uintptr_t)p >> MALLOC_PAGESHIFT;
339    sum = u;
340    sum = (sum << 7) - sum + (u >> 16);
341#ifdef __LP64__
342    sum = (sum << 7) - sum + (u >> 32);
343    sum = (sum << 7) - sum + (u >> 48);
344#endif
345    return sum;
346}
347
348static inline struct dir_info *
349getpool(void)
350{
351    if (mopts.malloc_pool[1] == NULL || !mopts.malloc_pool[1]->malloc_mt)
352        return mopts.malloc_pool[1];
353    else    /* first one reserved for special pool */
354        return mopts.malloc_pool[1 + TIB_GET()->tib_tid %
355            (mopts.malloc_mutexes - 1)];
356}
357
358static __dead void
359wrterror(struct dir_info *d, char *msg, ...)
360{
361    int     saved_errno = errno;
362    va_list     ap;
363
364    dprintf(STDERR_FILENO, "%s(%d) in %s(): ", __progname,
365        getpid(), (d != NULL && d->func) ? d->func : "unknown");
366    va_start(ap, msg);
367    vdprintf(STDERR_FILENO, msg, ap);
368    va_end(ap);
369    dprintf(STDERR_FILENO, "\n");
370
371#ifdef MALLOC_STATS
372    if (DO_STATS && mopts.malloc_verbose)
373        malloc_dump();
374#endif
375
376    errno = saved_errno;
377
378    abort();
379}
380
381static void
382rbytes_init(struct dir_info *d)
383{
384    arc4random_buf(d->rbytes, sizeof(d->rbytes));
385    /* add 1 to account for using d->rbytes[0] */
386    d->rbytesused = 1 + d->rbytes[0] % (sizeof(d->rbytes) / 2);
387}
388
389static inline u_char
390getrbyte(struct dir_info *d)
391{
392    u_char x;
393
394    if (d->rbytesused >= sizeof(d->rbytes))
395        rbytes_init(d);
396    x = d->rbytes[d->rbytesused++];
397    return x;
398}
399
400static void
401omalloc_parseopt(char opt)
402{
403    switch (opt) {
404    case '+':
405        mopts.malloc_mutexes <<= 1;
406        if (mopts.malloc_mutexes > _MALLOC_MUTEXES)
407            mopts.malloc_mutexes = _MALLOC_MUTEXES;
408        break;
409    case '-':
410        mopts.malloc_mutexes >>= 1;
411        if (mopts.malloc_mutexes < 2)
412            mopts.malloc_mutexes = 2;
413        break;
414    case '>':
415        mopts.def_maxcache <<= 1;
416        if (mopts.def_maxcache > MALLOC_MAXCACHE)
417            mopts.def_maxcache = MALLOC_MAXCACHE;
418        break;
419    case '<':
420        mopts.def_maxcache >>= 1;
421        break;
422    case 'c':
423        mopts.chunk_canaries = 0;
424        break;
425    case 'C':
426        mopts.chunk_canaries = 1;
427        break;
428#ifdef MALLOC_STATS
429    case 'd':
430        mopts.malloc_stats = 0;
431        break;
432    case 'D':
433    case '1':
434        mopts.malloc_stats = 1;
435        break;
436    case '2':
437        mopts.malloc_stats = 2;
438        break;
439    case '3':
440        mopts.malloc_stats = 3;
441        break;
442    case '4':
443        mopts.malloc_stats = 4;
444        break;
445#endif /* MALLOC_STATS */
446    case 'f':
447        mopts.malloc_freecheck = 0;
448        mopts.malloc_freeunmap = 0;
449        break;
450    case 'F':
451        mopts.malloc_freecheck = 1;
452        mopts.malloc_freeunmap = 1;
453        break;
454    case 'g':
455        mopts.malloc_guard = 0;
456        break;
457    case 'G':
458        mopts.malloc_guard = MALLOC_PAGESIZE;
459        break;
460    case 'j':
461        if (mopts.def_malloc_junk > 0)
462            mopts.def_malloc_junk--;
463        break;
464    case 'J':
465        if (mopts.def_malloc_junk < 2)
466            mopts.def_malloc_junk++;
467        break;
468    case 'r':
469        mopts.malloc_realloc = 0;
470        break;
471    case 'R':
472        mopts.malloc_realloc = 1;
473        break;
474    case 'u':
475        mopts.malloc_freeunmap = 0;
476        break;
477    case 'U':
478        mopts.malloc_freeunmap = 1;
479        break;
480#ifdef MALLOC_STATS
481    case 'v':
482        mopts.malloc_verbose = 0;
483        break;
484    case 'V':
485        mopts.malloc_verbose = 1;
486        break;
487#endif /* MALLOC_STATS */
488    case 'x':
489        mopts.malloc_xmalloc = 0;
490        break;
491    case 'X':
492        mopts.malloc_xmalloc = 1;
493        break;
494    default:
495        dprintf(STDERR_FILENO, "malloc() warning: "
496                    "unknown char in MALLOC_OPTIONS\n");
497        break;
498    }
499}
500
501static void
502omalloc_init(void)
503{
504    char *p, *q, b[16];
505    int i, j;
506    const int mib[2] = { CTL_VM, VM_MALLOC_CONF };
507    size_t sb;
508
509    /*
510     * Default options
511     */
512    mopts.malloc_mutexes = 8;
513    mopts.def_malloc_junk = 1;
514    mopts.def_maxcache = MALLOC_DEFAULT_CACHE;
515
516    for (i = 0; i < 3; i++) {
517        switch (i) {
518        case 0:
519            sb = sizeof(b);
520            j = sysctl(mib, 2, b, &sb, NULL, 0);
521            if (j != 0)
522                continue;
523            p = b;
524            break;
525        case 1:
526            if (issetugid() == 0)
527                p = getenv("MALLOC_OPTIONS");
528            else
529                continue;
530            break;
531        case 2:
532            p = malloc_options;
533            break;
534        default:
535            p = NULL;
536        }
537
538        for (; p != NULL && *p != '\0'; p++) {
539            switch (*p) {
540            case 'S':
541                for (q = "CFGJ"; *q != '\0'; q++)
542                    omalloc_parseopt(*q);
543                mopts.def_maxcache = 0;
544                break;
545            case 's':
546                for (q = "cfgj"; *q != '\0'; q++)
547                    omalloc_parseopt(*q);
548                mopts.def_maxcache = MALLOC_DEFAULT_CACHE;
549                break;
550            default:
551                omalloc_parseopt(*p);
552                break;
553            }
554        }
555    }
556
557#ifdef MALLOC_STATS
558    if (DO_STATS && (atexit(malloc_exit) == -1)) {
559        dprintf(STDERR_FILENO, "malloc() warning: atexit(3) failed."
560            " Will not be able to dump stats on exit\n");
561    }
562#endif
563
564    while ((mopts.malloc_canary = arc4random()) == 0)
565        ;
566    mopts.junk_loc = arc4random();
567    if (mopts.chunk_canaries)
568        do {
569            mopts.chunk_canaries = arc4random();
570        } while ((u_char)mopts.chunk_canaries == 0 ||
571            (u_char)mopts.chunk_canaries == SOME_FREEJUNK);
572}
573
574static void
575omalloc_poolinit(struct dir_info *d, int mmap_flag)
576{
577    u_int i, j;
578
579    d->r = NULL;
580    d->rbytesused = sizeof(d->rbytes);
581    d->regions_free = d->regions_total = 0;
582    for (i = 0; i <= BUCKETS; i++) {
583        LIST_INIT(&d->chunk_info_list[i]);
584        for (j = 0; j < MALLOC_CHUNK_LISTS; j++)
585            LIST_INIT(&d->chunk_dir[i][j]);
586    }
587    d->mmap_flag = mmap_flag;
588    d->malloc_junk = mopts.def_malloc_junk;
589#ifdef MALLOC_STATS
590    RBT_INIT(btshead, &d->btraces);
591#endif
592    d->canary1 = mopts.malloc_canary ^ (u_int32_t)(uintptr_t)d;
593    d->canary2 = ~d->canary1;
594}
595
596static int
597omalloc_grow(struct dir_info *d)
598{
599    size_t newtotal;
600    size_t newsize;
601    size_t mask;
602    size_t i, oldpsz;
603    struct region_info *p;
604
605    if (d->regions_total > SIZE_MAX / sizeof(struct region_info) / 2)
606        return 1;
607
608    newtotal = d->regions_total == 0 ? MALLOC_INITIAL_REGIONS :
609        d->regions_total * 2;
610    newsize = PAGEROUND(newtotal * sizeof(struct region_info));
611    mask = newtotal - 1;
612
613    /* Don't use cache here, we don't want user uaf touch this */
614    p = MMAP(newsize, d->mmap_flag);
615    if (p == MAP_FAILED)
616        return 1;
617
618    STATS_ADD(d->malloc_used, newsize);
619    STATS_ZERO(d->inserts);
620    STATS_ZERO(d->insert_collisions);
621    for (i = 0; i < d->regions_total; i++) {
622        void *q = d->r[i].p;
623        if (q != NULL) {
624            size_t index = hash(q) & mask;
625            STATS_INC(d->inserts);
626            while (p[index].p != NULL) {
627                index = (index - 1) & mask;
628                STATS_INC(d->insert_collisions);
629            }
630            p[index] = d->r[i];
631        }
632    }
633
634    if (d->regions_total > 0) {
635        oldpsz = PAGEROUND(d->regions_total *
636            sizeof(struct region_info));
637        /* clear to avoid meta info ending up in the cache */
638        unmap(d, d->r, oldpsz, oldpsz);
639    }
640    d->regions_free += newtotal - d->regions_total;
641    d->regions_total = newtotal;
642    d->r = p;
643    return 0;
644}
645
646/*
647 * The hashtable uses the assumption that p is never NULL. This holds since
648 * non-MAP_FIXED mappings with hint 0 start at BRKSIZ.
649 */
650static int
651insert(struct dir_info *d, void *p, size_t sz, void *f)
652{
653    size_t index;
654    size_t mask;
655    void *q;
656
657    if (d->regions_free * 4 < d->regions_total || d->regions_total == 0) {
658        if (omalloc_grow(d))
659            return 1;
660    }
661    mask = d->regions_total - 1;
662    index = hash(p) & mask;
663    q = d->r[index].p;
664    STATS_INC(d->inserts);
665    while (q != NULL) {
666        index = (index - 1) & mask;
667        q = d->r[index].p;
668        STATS_INC(d->insert_collisions);
669    }
670    d->r[index].p = p;
671    d->r[index].size = sz;
672    STATS_SETF(&d->r[index], f);
673    d->regions_free--;
674    return 0;
675}
676
677static struct region_info *
678find(struct dir_info *d, void *p)
679{
680    size_t index;
681    size_t mask = d->regions_total - 1;
682    void *q, *r;
683
684    if (mopts.malloc_canary != (d->canary1 ^ (u_int32_t)(uintptr_t)d) ||
685        d->canary1 != ~d->canary2)
686        wrterror(d, "internal struct corrupt");
687    if (d->r == NULL)
688        return NULL;
689    p = MASK_POINTER(p);
690    index = hash(p) & mask;
691    r = d->r[index].p;
692    q = MASK_POINTER(r);
693    while (q != p && r != NULL) {
694        index = (index - 1) & mask;
695        r = d->r[index].p;
696        q = MASK_POINTER(r);
697    }
698    return (q == p && r != NULL) ? &d->r[index] : NULL;
699}
700
701static void
702delete(struct dir_info *d, struct region_info *ri)
703{
704    /* algorithm R, Knuth Vol III section 6.4 */
705    size_t mask = d->regions_total - 1;
706    size_t i, j, r;
707
708    if (d->regions_total & (d->regions_total - 1))
709        wrterror(d, "regions_total not 2^x");
710    d->regions_free++;
711    STATS_INC(d->deletes);
712
713    i = ri - d->r;
714    for (;;) {
715        d->r[i].p = NULL;
716        d->r[i].size = 0;
717        j = i;
718        for (;;) {
719            i = (i - 1) & mask;
720            if (d->r[i].p == NULL)
721                return;
722            r = hash(d->r[i].p) & mask;
723            if ((i <= r && r < j) || (r < j && j < i) ||
724                (j < i && i <= r))
725                continue;
726            d->r[j] = d->r[i];
727            STATS_INC(d->delete_moves);
728            break;
729        }
730
731    }
732}
733
734static inline void
735junk_free(int junk, void *p, size_t sz)
736{
737    size_t i, step = 1;
738    uint64_t *lp = p;
739
740    if (junk == 0 || sz == 0)
741        return;
742    sz /= sizeof(uint64_t);
743    if (junk == 1) {
744        if (sz > MALLOC_PAGESIZE / sizeof(uint64_t))
745            sz = MALLOC_PAGESIZE / sizeof(uint64_t);
746        step = sz / 4;
747        if (step == 0)
748            step = 1;
749    }
750    /* Do not always put the free junk bytes in the same spot.
751       There is modulo bias here, but we ignore that. */
752    for (i = mopts.junk_loc % step; i < sz; i += step)
753        lp[i] = SOME_FREEJUNK_ULL;
754}
755
756static inline void
757validate_junk(struct dir_info *pool, void *p, size_t argsz)
758{
759    size_t i, sz, step = 1;
760    uint64_t *lp = p;
761
762    if (pool->malloc_junk == 0 || argsz == 0)
763        return;
764    sz = argsz / sizeof(uint64_t);
765    if (pool->malloc_junk == 1) {
766        if (sz > MALLOC_PAGESIZE / sizeof(uint64_t))
767            sz = MALLOC_PAGESIZE / sizeof(uint64_t);
768        step = sz / 4;
769        if (step == 0)
770            step = 1;
771    }
772    /* see junk_free */
773    for (i = mopts.junk_loc % step; i < sz; i += step) {
774        if (lp[i] != SOME_FREEJUNK_ULL) {
775#ifdef MALLOC_STATS
776            if (DO_STATS && argsz <= MALLOC_MAXCHUNK)
777                print_chunk_details(pool, lp, argsz, i);
778            else
779#endif
780                wrterror(pool,
781                    "write to free mem %p[%zu..%zu]@%zu",
782                    lp, i * sizeof(uint64_t),
783                    (i + 1) * sizeof(uint64_t) - 1, argsz);
784        }
785    }
786}
787
788
789/*
790 * Cache maintenance.
791 * Opposed to the regular region data structure, the sizes in the
792 * cache are in MALLOC_PAGESIZE units.
793 */
794static void
795unmap(struct dir_info *d, void *p, size_t sz, size_t clear)
796{
797    size_t psz = sz >> MALLOC_PAGESHIFT;
798    void *r;
799    u_short i;
800    struct smallcache *cache;
801
802    if (sz != PAGEROUND(sz) || psz == 0)
803        wrterror(d, "munmap round");
804
805    if (d->bigcache_size > 0 && psz > MAX_SMALLCACHEABLE_SIZE &&
806        psz <= MAX_BIGCACHEABLE_SIZE) {
807        u_short base = getrbyte(d);
808        u_short j;
809
810        /* don't look through all slots */
811        for (j = 0; j < d->bigcache_size / 4; j++) {
812            i = (base + j) & (d->bigcache_size - 1);
813            if (d->bigcache_used <
814                BIGCACHE_FILL(d->bigcache_size))  {
815                if (d->bigcache[i].psize == 0)
816                    break;
817            } else {
818                if (d->bigcache[i].psize != 0)
819                    break;
820            }
821        }
822        /* if we didn't find a preferred slot, use random one */
823        if (d->bigcache[i].psize != 0) {
824            size_t tmp;
825
826            r = d->bigcache[i].page;
827            d->bigcache_used -= d->bigcache[i].psize;
828            tmp = d->bigcache[i].psize << MALLOC_PAGESHIFT;
829            if (!mopts.malloc_freeunmap)
830                validate_junk(d, r, tmp);
831            if (munmap(r, tmp))
832                 wrterror(d, "munmap %p", r);
833            STATS_SUB(d->malloc_used, tmp);
834        }
835
836        if (clear > 0)
837            explicit_bzero(p, clear);
838        if (mopts.malloc_freeunmap) {
839            if (mprotect(p, sz, PROT_NONE))
840                wrterror(d, "mprotect %p", r);
841        } else
842            junk_free(d->malloc_junk, p, sz);
843        d->bigcache[i].page = p;
844        d->bigcache[i].psize = psz;
845        d->bigcache_used += psz;
846        return;
847    }
848    if (psz > MAX_SMALLCACHEABLE_SIZE || d->smallcache[psz - 1].max == 0) {
849        if (munmap(p, sz))
850            wrterror(d, "munmap %p", p);
851        STATS_SUB(d->malloc_used, sz);
852        return;
853    }
854    cache = &d->smallcache[psz - 1];
855    if (cache->length == cache->max) {
856        int fresh;
857        /* use a random slot */
858        i = getrbyte(d) & (cache->max - 1);
859        r = cache->pages[i];
860        fresh = (uintptr_t)r & 1;
861        *(uintptr_t*)&r &= ~1UL;
862        if (!fresh && !mopts.malloc_freeunmap)
863            validate_junk(d, r, sz);
864        if (munmap(r, sz))
865            wrterror(d, "munmap %p", r);
866        STATS_SUB(d->malloc_used, sz);
867        cache->length--;
868    } else
869        i = cache->length;
870
871    /* fill slot */
872    if (clear > 0)
873        explicit_bzero(p, clear);
874    if (mopts.malloc_freeunmap)
875        mprotect(p, sz, PROT_NONE);
876    else
877        junk_free(d->malloc_junk, p, sz);
878    cache->pages[i] = p;
879    cache->length++;
880}
881
882static void *
883map(struct dir_info *d, size_t sz, int zero_fill)
884{
885    size_t psz = sz >> MALLOC_PAGESHIFT;
886    u_short i;
887    void *p;
888    struct smallcache *cache;
889
890    if (mopts.malloc_canary != (d->canary1 ^ (u_int32_t)(uintptr_t)d) ||
891        d->canary1 != ~d->canary2)
892        wrterror(d, "internal struct corrupt");
893    if (sz != PAGEROUND(sz) || psz == 0)
894        wrterror(d, "map round");
895
896
897    if (d->bigcache_size > 0 && psz > MAX_SMALLCACHEABLE_SIZE &&
898        psz <= MAX_BIGCACHEABLE_SIZE) {
899        size_t base = getrbyte(d);
900        size_t cached = d->bigcache_used;
901        ushort j;
902
903        for (j = 0; j < d->bigcache_size && cached >= psz; j++) {
904            i = (j + base) & (d->bigcache_size - 1);
905            if (d->bigcache[i].psize == psz) {
906                p = d->bigcache[i].page;
907                d->bigcache_used -= psz;
908                d->bigcache[i].page = NULL;
909                d->bigcache[i].psize = 0;
910
911                if (!mopts.malloc_freeunmap)
912                    validate_junk(d, p, sz);
913                if (mopts.malloc_freeunmap)
914                    mprotect(p, sz, PROT_READ | PROT_WRITE);
915                if (zero_fill)
916                    memset(p, 0, sz);
917                else if (mopts.malloc_freeunmap)
918                    junk_free(d->malloc_junk, p, sz);
919                return p;
920            }
921            cached -= d->bigcache[i].psize;
922        }
923    }
924    if (psz <= MAX_SMALLCACHEABLE_SIZE && d->smallcache[psz - 1].max > 0) {
925        cache = &d->smallcache[psz - 1];
926        if (cache->length > 0) {
927            int fresh;
928            if (cache->length == 1)
929                p = cache->pages[--cache->length];
930            else {
931                i = getrbyte(d) % cache->length;
932                p = cache->pages[i];
933                cache->pages[i] = cache->pages[--cache->length];
934            }
935            /* check if page was not junked, i.e. "fresh
936               we use the lsb of the pointer for that */
937            fresh = (uintptr_t)p & 1UL;
938            *(uintptr_t*)&p &= ~1UL;
939            if (!fresh && !mopts.malloc_freeunmap)
940                validate_junk(d, p, sz);
941            if (mopts.malloc_freeunmap)
942                mprotect(p, sz, PROT_READ | PROT_WRITE);
943            if (zero_fill)
944                memset(p, 0, sz);
945            else if (mopts.malloc_freeunmap)
946                junk_free(d->malloc_junk, p, sz);
947            return p;
948        }
949        if (psz <= 1) {
950            p = MMAP(cache->max * sz, d->mmap_flag);
951            if (p != MAP_FAILED) {
952                STATS_ADD(d->malloc_used, cache->max * sz);
953                cache->length = cache->max - 1;
954                for (i = 0; i < cache->max - 1; i++) {
955                    void *q = (char*)p + i * sz;
956                    cache->pages[i] = q;
957                    /* mark pointer in slot as not junked */
958                    *(uintptr_t*)&cache->pages[i] |= 1UL;
959                }
960                if (mopts.malloc_freeunmap)
961                    mprotect(p, (cache->max - 1) * sz,
962                        PROT_NONE);
963                p = (char*)p + (cache->max - 1) * sz;
964                /* zero fill not needed, freshly mmapped */
965                return p;
966            }
967        }
968
969    }
970    p = MMAP(sz, d->mmap_flag);
971    if (p != MAP_FAILED)
972        STATS_ADD(d->malloc_used, sz);
973    /* zero fill not needed */
974    return p;
975}
976
977static void
978init_chunk_info(struct dir_info *d, struct chunk_info *p, u_int bucket)
979{
980    u_int i;
981
982    p->bucket = bucket;
983    p->total = p->free = MALLOC_PAGESIZE / B2ALLOC(bucket);
984    p->offset = howmany(p->total, MALLOC_BITS);
985    p->canary = (u_short)d->canary1;
986
987    /* set all valid bits in the bitmap */
988    i = p->total - 1;
989    memset(p->bits, 0xff, sizeof(p->bits[0]) * (i / MALLOC_BITS));
990    p->bits[i / MALLOC_BITS] = (2U << (i % MALLOC_BITS)) - 1;
991}
992
993static struct chunk_info *
994alloc_chunk_info(struct dir_info *d, u_int bucket)
995{
996    struct chunk_info *p;
997
998    if (LIST_EMPTY(&d->chunk_info_list[bucket])) {
999        const size_t chunk_pages = 64;
1000        size_t size, count, i;
1001        char *q;
1002
1003        count = MALLOC_PAGESIZE / B2ALLOC(bucket);
1004
1005        size = howmany(count, MALLOC_BITS);
1006        /* see declaration of struct chunk_info */
1007        if (size <= CHUNK_INFO_TAIL)
1008            size = 0;
1009        else
1010            size -= CHUNK_INFO_TAIL;
1011        size = sizeof(struct chunk_info) + size * sizeof(u_short);
1012        if (mopts.chunk_canaries && bucket > 0)
1013            size += count * sizeof(u_short);
1014        size = _ALIGN(size);
1015        count = MALLOC_PAGESIZE / size;
1016
1017        /* Don't use cache here, we don't want user uaf touch this */
1018        if (d->chunk_pages_used == chunk_pages ||
1019             d->chunk_pages == NULL) {
1020            q = MMAP(MALLOC_PAGESIZE * chunk_pages, d->mmap_flag);
1021            if (q == MAP_FAILED)
1022                return NULL;
1023            d->chunk_pages = q;
1024            d->chunk_pages_used = 0;
1025            STATS_ADD(d->malloc_used, MALLOC_PAGESIZE *
1026                chunk_pages);
1027        }
1028        q = (char *)d->chunk_pages + d->chunk_pages_used *
1029            MALLOC_PAGESIZE;
1030        d->chunk_pages_used++;
1031
1032        for (i = 0; i < count; i++, q += size) {
1033            p = (struct chunk_info *)q;
1034            LIST_INSERT_HEAD(&d->chunk_info_list[bucket], p,
1035                entries);
1036        }
1037    }
1038    p = LIST_FIRST(&d->chunk_info_list[bucket]);
1039    LIST_REMOVE(p, entries);
1040    if (p->total == 0)
1041        init_chunk_info(d, p, bucket);
1042    return p;
1043}
1044
1045/*
1046 * Allocate a page of chunks
1047 */
1048static struct chunk_info *
1049omalloc_make_chunks(struct dir_info *d, u_int bucket, u_int listnum)
1050{
1051    struct chunk_info *bp;
1052    void *pp;
1053    void *ff = NULL;
1054
1055    /* Allocate a new bucket */
1056    pp = map(d, MALLOC_PAGESIZE, 0);
1057    if (pp == MAP_FAILED)
1058        return NULL;
1059    if (DO_STATS) {
1060        ff = map(d, MALLOC_PAGESIZE, 0);
1061        if (ff == MAP_FAILED)
1062            goto err;
1063        memset(ff, 0, sizeof(void *) * MALLOC_PAGESIZE /
1064            B2ALLOC(bucket));
1065    }
1066
1067    /* memory protect the page allocated in the malloc(0) case */
1068    if (bucket == 0 && mprotect(pp, MALLOC_PAGESIZE, PROT_NONE) == -1)
1069        goto err;
1070
1071    bp = alloc_chunk_info(d, bucket);
1072    if (bp == NULL)
1073        goto err;
1074    bp->page = pp;
1075
1076    if (insert(d, (void *)((uintptr_t)pp | (bucket + 1)), (uintptr_t)bp,
1077        ff))
1078        goto err;
1079    LIST_INSERT_HEAD(&d->chunk_dir[bucket][listnum], bp, entries);
1080
1081    if (bucket > 0 && d->malloc_junk != 0)
1082        memset(pp, SOME_FREEJUNK, MALLOC_PAGESIZE);
1083
1084    return bp;
1085
1086err:
1087    unmap(d, pp, MALLOC_PAGESIZE, 0);
1088    if (ff != NULL && ff != MAP_FAILED)
1089        unmap(d, ff, MALLOC_PAGESIZE, 0);
1090    return NULL;
1091}
1092
1093#if defined(__GNUC__) && __GNUC__ < 4
1094static inline unsigned int
1095lb(u_int x)
1096{
1097#if defined(__m88k__)
1098    __asm__ __volatile__ ("ff1 %0, %0" : "=r" (x) : "0" (x));
1099    return x;
1100#else
1101    /* portable version */
1102    unsigned int count = 0;
1103    while ((x & (1U << (sizeof(int) * CHAR_BIT - 1))) == 0) {
1104        count++;
1105        x <<= 1;
1106    }
1107    return (sizeof(int) * CHAR_BIT - 1) - count;
1108#endif
1109}
1110#else
1111/* using built-in function version */
1112static inline unsigned int
1113lb(u_int x)
1114{
1115    /* I need an extension just for integer-length (: */
1116    return (sizeof(int) * CHAR_BIT - 1) - __builtin_clz(x);
1117}
1118#endif
1119
1120/* https://pvk.ca/Blog/2015/06/27/linear-log-bucketing-fast-versatile-simple/
1121   via Tony Finch */
1122static inline unsigned int
1123bin_of(unsigned int size)
1124{
1125    const unsigned int linear = 6;
1126    const unsigned int subbin = 2;
1127
1128    unsigned int mask, rounded, rounded_size;
1129    unsigned int n_bits, shift;
1130
1131    n_bits = lb(size | (1U << linear));
1132    shift = n_bits - subbin;
1133    mask = (1ULL << shift) - 1;
1134    rounded = size + mask; /* XXX: overflow. */
1135
1136    rounded_size = rounded & ~mask;
1137    return rounded_size;
1138}
1139
1140static inline u_short
1141find_bucket(u_short size)
1142{
1143    /* malloc(0) is special */
1144    if (size == 0)
1145        return 0;
1146    if (size < MALLOC_MINSIZE)
1147        size = MALLOC_MINSIZE;
1148    if (mopts.def_maxcache != 0)
1149        size = bin_of(size);
1150    return howmany(size, MALLOC_MINSIZE);
1151}
1152
1153static void
1154fill_canary(char *ptr, size_t sz, size_t allocated)
1155{
1156    size_t check_sz = allocated - sz;
1157
1158    if (check_sz > CHUNK_CHECK_LENGTH)
1159        check_sz = CHUNK_CHECK_LENGTH;
1160    memset(ptr + sz, mopts.chunk_canaries, check_sz);
1161}
1162
1163/*
1164 * Allocate a chunk
1165 */
1166static void *
1167malloc_bytes(struct dir_info *d, size_t size)
1168{
1169    u_int i, j, k, r, bucket, listnum;
1170    u_short *lp;
1171    struct chunk_info *bp;
1172    void *p;
1173
1174    if (mopts.malloc_canary != (d->canary1 ^ (u_int32_t)(uintptr_t)d) ||
1175        d->canary1 != ~d->canary2)
1176        wrterror(d, "internal struct corrupt");
1177
1178    bucket = find_bucket(size);
1179
1180    r = getrbyte(d);
1181    listnum = r % MALLOC_CHUNK_LISTS;
1182
1183    /* If it's empty, make a page more of that size chunks */
1184    if ((bp = LIST_FIRST(&d->chunk_dir[bucket][listnum])) == NULL) {
1185        bp = omalloc_make_chunks(d, bucket, listnum);
1186        if (bp == NULL)
1187            return NULL;
1188    }
1189
1190    if (bp->canary != (u_short)d->canary1 || bucket != bp->bucket)
1191        wrterror(d, "chunk info corrupted");
1192
1193    r /= MALLOC_CHUNK_LISTS;
1194    /* do we need more random bits? */
1195    if (bp->total > 256 / MALLOC_CHUNK_LISTS)
1196        r = r << 8 | getrbyte(d);
1197    /* bias, as bp->total is not a power of 2 */
1198    i = r % bp->total;
1199
1200    j = i % MALLOC_BITS;
1201    i /= MALLOC_BITS;
1202    lp = &bp->bits[i];
1203    /* potentially start somewhere in a short */
1204    if (j > 0 && *lp >> j)
1205        k = ffs(*lp >> j) + j;
1206    else {
1207        /* no bit halfway, go to next full short */
1208        for (;;) {
1209            if (*lp) {
1210                k = ffs(*lp);
1211                break;
1212            }
1213            if (++i >= bp->offset)
1214                i = 0;
1215            lp = &bp->bits[i];
1216        }
1217    }
1218    *lp ^= 1 << --k;
1219
1220    /* If there are no more free, remove from free-list */
1221    if (--bp->free == 0)
1222        LIST_REMOVE(bp, entries);
1223
1224    /* Adjust to the real offset of that chunk */
1225    k += i * MALLOC_BITS;
1226
1227    if (mopts.chunk_canaries && size > 0)
1228        bp->bits[bp->offset + k] = size;
1229
1230    if (DO_STATS) {
1231        struct region_info *r = find(d, bp->page);
1232        STATS_SETFN(r, k, d->caller);
1233    }
1234
1235    p = (char *)bp->page + k * B2ALLOC(bucket);
1236    if (bucket > 0) {
1237        validate_junk(d, p, B2SIZE(bucket));
1238        if (mopts.chunk_canaries)
1239            fill_canary(p, size, B2SIZE(bucket));
1240    }
1241    return p;
1242}
1243
1244static void
1245validate_canary(struct dir_info *d, u_char *ptr, size_t sz, size_t allocated)
1246{
1247    size_t check_sz = allocated - sz;
1248    u_char *p, *q;
1249
1250    if (check_sz > CHUNK_CHECK_LENGTH)
1251        check_sz = CHUNK_CHECK_LENGTH;
1252    p = ptr + sz;
1253    q = p + check_sz;
1254
1255    while (p < q) {
1256        if (*p != (u_char)mopts.chunk_canaries && *p != SOME_JUNK) {
1257            wrterror(d, "canary corrupted %p[%tu]@%zu/%zu%s",
1258                ptr, p - ptr, sz, allocated,
1259                *p == SOME_FREEJUNK ? " (double free?)" : "");
1260        }
1261        p++;
1262    }
1263}
1264
1265static inline uint32_t
1266find_chunknum(struct dir_info *d, struct chunk_info *info, void *ptr, int check)
1267{
1268    uint32_t chunknum;
1269
1270    if (info->canary != (u_short)d->canary1)
1271        wrterror(d, "chunk info corrupted");
1272
1273    /* Find the chunk number on the page */
1274    chunknum = ((uintptr_t)ptr & MALLOC_PAGEMASK) / B2ALLOC(info->bucket);
1275
1276    if ((uintptr_t)ptr & (MALLOC_MINSIZE - 1))
1277        wrterror(d, "modified chunk-pointer %p", ptr);
1278    if (CHUNK_FREE(info, chunknum))
1279        wrterror(d, "double free %p", ptr);
1280    if (check && info->bucket > 0) {
1281        validate_canary(d, ptr, info->bits[info->offset + chunknum],
1282            B2SIZE(info->bucket));
1283    }
1284    return chunknum;
1285}
1286
1287/*
1288 * Free a chunk, and possibly the page it's on, if the page becomes empty.
1289 */
1290static void
1291free_bytes(struct dir_info *d, struct region_info *r, void *ptr)
1292{
1293    struct chunk_head *mp;
1294    struct chunk_info *info;
1295    uint32_t chunknum;
1296    uint32_t listnum;
1297
1298    info = (struct chunk_info *)r->size;
1299    chunknum = find_chunknum(d, info, ptr, 0);
1300
1301    info->bits[chunknum / MALLOC_BITS] |= 1U << (chunknum % MALLOC_BITS);
1302    info->free++;
1303
1304    if (info->free == 1) {
1305        /* Page became non-full */
1306        listnum = getrbyte(d) % MALLOC_CHUNK_LISTS;
1307        mp = &d->chunk_dir[info->bucket][listnum];
1308        LIST_INSERT_HEAD(mp, info, entries);
1309        return;
1310    }
1311
1312    if (info->free != info->total)
1313        return;
1314
1315    LIST_REMOVE(info, entries);
1316
1317    if (info->bucket == 0 && !mopts.malloc_freeunmap)
1318        mprotect(info->page, MALLOC_PAGESIZE, PROT_READ | PROT_WRITE);
1319    unmap(d, info->page, MALLOC_PAGESIZE, 0);
1320#ifdef MALLOC_STATS
1321    if (r->f != NULL) {
1322        unmap(d, r->f, MALLOC_PAGESIZE, MALLOC_PAGESIZE);
1323        r->f = NULL;
1324    }
1325#endif
1326
1327    delete(d, r);
1328    mp = &d->chunk_info_list[info->bucket];
1329    LIST_INSERT_HEAD(mp, info, entries);
1330}
1331
1332static void *
1333omalloc(struct dir_info *pool, size_t sz, int zero_fill)
1334{
1335    void *p, *caller = NULL;
1336    size_t psz;
1337
1338    if (sz > MALLOC_MAXCHUNK) {
1339        if (sz >= SIZE_MAX - mopts.malloc_guard - MALLOC_PAGESIZE) {
1340            errno = ENOMEM;
1341            return NULL;
1342        }
1343        sz += mopts.malloc_guard;
1344        psz = PAGEROUND(sz);
1345        p = map(pool, psz, zero_fill);
1346        if (p == MAP_FAILED) {
1347            errno = ENOMEM;
1348            return NULL;
1349        }
1350#ifdef MALLOC_STATS
1351        if (DO_STATS)
1352            caller = pool->caller;
1353#endif
1354        if (insert(pool, p, sz, caller)) {
1355            unmap(pool, p, psz, 0);
1356            errno = ENOMEM;
1357            return NULL;
1358        }
1359        if (mopts.malloc_guard) {
1360            if (mprotect((char *)p + psz - mopts.malloc_guard,
1361                mopts.malloc_guard, PROT_NONE))
1362                wrterror(pool, "mprotect");
1363            STATS_ADD(pool->malloc_guarded, mopts.malloc_guard);
1364        }
1365
1366        if (MALLOC_MOVE_COND(sz)) {
1367            /* fill whole allocation */
1368            if (pool->malloc_junk == 2)
1369                memset(p, SOME_JUNK, psz - mopts.malloc_guard);
1370            /* shift towards the end */
1371            p = MALLOC_MOVE(p, sz);
1372            /* fill zeros if needed and overwritten above */
1373            if (zero_fill && pool->malloc_junk == 2)
1374                memset(p, 0, sz - mopts.malloc_guard);
1375        } else {
1376            if (pool->malloc_junk == 2) {
1377                if (zero_fill)
1378                    memset((char *)p + sz -
1379                        mopts.malloc_guard, SOME_JUNK,
1380                        psz - sz);
1381                else
1382                    memset(p, SOME_JUNK,
1383                        psz - mopts.malloc_guard);
1384            } else if (mopts.chunk_canaries)
1385                fill_canary(p, sz - mopts.malloc_guard,
1386                    psz - mopts.malloc_guard);
1387        }
1388
1389    } else {
1390        /* takes care of SOME_JUNK */
1391        p = malloc_bytes(pool, sz);
1392        if (zero_fill && p != NULL && sz > 0)
1393            memset(p, 0, sz);
1394    }
1395
1396    return p;
1397}
1398
1399/*
1400 * Common function for handling recursion.  Only
1401 * print the error message once, to avoid making the problem
1402 * potentially worse.
1403 */
1404static void
1405malloc_recurse(struct dir_info *d)
1406{
1407    static int noprint;
1408
1409    if (noprint == 0) {
1410        noprint = 1;
1411        wrterror(d, "recursive call");
1412    }
1413    d->active--;
1414    _MALLOC_UNLOCK(d->mutex);
1415    errno = EDEADLK;
1416}
1417
1418void
1419_malloc_init(int from_rthreads)
1420{
1421    u_int i, j, nmutexes;
1422    struct dir_info *d;
1423
1424    _MALLOC_LOCK(1);
1425    if (!from_rthreads && mopts.malloc_pool[1]) {
1426        _MALLOC_UNLOCK(1);
1427        return;
1428    }
1429    if (!mopts.malloc_canary) {
1430        char *p;
1431        size_t sz, roundup_sz, d_avail;
1432
1433        omalloc_init();
1434        /*
1435         * Allocate dir_infos with a guard page on either side. Also
1436         * randomise offset inside the page at which the dir_infos
1437         * lay (subject to alignment by 1 << MALLOC_MINSHIFT)
1438         */
1439        sz = mopts.malloc_mutexes * sizeof(*d);
1440        roundup_sz = (sz + MALLOC_PAGEMASK) & ~MALLOC_PAGEMASK;
1441        if ((p = MMAPNONE(roundup_sz + 2 * MALLOC_PAGESIZE, 0)) ==
1442            MAP_FAILED)
1443            wrterror(NULL, "malloc_init mmap1 failed");
1444        if (mprotect(p + MALLOC_PAGESIZE, roundup_sz,
1445            PROT_READ | PROT_WRITE))
1446            wrterror(NULL, "malloc_init mprotect1 failed");
1447        if (mimmutable(p, roundup_sz + 2 * MALLOC_PAGESIZE))
1448            wrterror(NULL, "malloc_init mimmutable1 failed");
1449        d_avail = (roundup_sz - sz) >> MALLOC_MINSHIFT;
1450        d = (struct dir_info *)(p + MALLOC_PAGESIZE +
1451            (arc4random_uniform(d_avail) << MALLOC_MINSHIFT));
1452        STATS_ADD(d[1].malloc_used, roundup_sz + 2 * MALLOC_PAGESIZE);
1453        for (i = 0; i < mopts.malloc_mutexes; i++)
1454            mopts.malloc_pool[i] = &d[i];
1455        mopts.internal_funcs = 1;
1456        if (((uintptr_t)&malloc_readonly & MALLOC_PAGEMASK) == 0) {
1457            if (mprotect(&malloc_readonly, sizeof(malloc_readonly),
1458                PROT_READ))
1459                wrterror(NULL,
1460                    "malloc_init mprotect r/o failed");
1461            if (mimmutable(&malloc_readonly,
1462                sizeof(malloc_readonly)))
1463                wrterror(NULL,
1464                    "malloc_init mimmutable r/o failed");
1465        }
1466    }
1467
1468    nmutexes = from_rthreads ? mopts.malloc_mutexes : 2;
1469    for (i = 0; i < nmutexes; i++) {
1470        d = mopts.malloc_pool[i];
1471        d->malloc_mt = from_rthreads;
1472        if (d->canary1 == ~d->canary2)
1473            continue;
1474        if (i == 0) {
1475            omalloc_poolinit(d, MAP_CONCEAL);
1476            d->malloc_junk = 2;
1477            d->bigcache_size = 0;
1478            for (j = 0; j < MAX_SMALLCACHEABLE_SIZE; j++)
1479                d->smallcache[j].max = 0;
1480        } else {
1481            size_t sz = 0;
1482
1483            omalloc_poolinit(d, 0);
1484            d->malloc_junk = mopts.def_malloc_junk;
1485            d->bigcache_size = mopts.def_maxcache;
1486            for (j = 0; j < MAX_SMALLCACHEABLE_SIZE; j++) {
1487                d->smallcache[j].max =
1488                    mopts.def_maxcache >> (j / 8);
1489                sz += d->smallcache[j].max * sizeof(void *);
1490            }
1491            sz += d->bigcache_size * sizeof(struct bigcache);
1492            if (sz > 0) {
1493                void *p = MMAP(sz, 0);
1494                if (p == MAP_FAILED)
1495                    wrterror(NULL,
1496                        "malloc_init mmap2 failed");
1497                if (mimmutable(p, sz))
1498                    wrterror(NULL,
1499                        "malloc_init mimmutable2 failed");
1500                for (j = 0; j < MAX_SMALLCACHEABLE_SIZE; j++) {
1501                    d->smallcache[j].pages = p;
1502                    p = (char *)p + d->smallcache[j].max *
1503                        sizeof(void *);
1504                }
1505                d->bigcache = p;
1506            }
1507        }
1508        d->mutex = i;
1509    }
1510
1511    _MALLOC_UNLOCK(1);
1512}
1513DEF_STRONG(_malloc_init);
1514
1515#define PROLOGUE(p, fn)         \
1516    d = (p);            \
1517    if (d == NULL) {        \
1518        _malloc_init(0);    \
1519        d = (p);        \
1520    }               \
1521    _MALLOC_LOCK(d->mutex);     \
1522    d->func = fn;           \
1523    if (d->active++) {      \
1524        malloc_recurse(d);  \
1525        return NULL;        \
1526    }               \
1527
1528#define EPILOGUE()              \
1529    d->active--;                \
1530    _MALLOC_UNLOCK(d->mutex);       \
1531    if (r == NULL && mopts.malloc_xmalloc)  \
1532        wrterror(d, "out of memory");   \
1533    if (r != NULL)              \
1534        errno = saved_errno;        \
1535
1536void *
1537malloc(size_t size)
1538{
1539    void *r;
1540    struct dir_info *d;
1541    int saved_errno = errno;
1542
1543    PROLOGUE(getpool(), "malloc")
1544    SET_CALLER(d, caller(d));
1545    r = omalloc(d, size, 0);
1546    EPILOGUE()
1547    return r;
1548}
1549DEF_STRONG(malloc);
1550
1551void *
1552malloc_conceal(size_t size)
1553{
1554    void *r;
1555    struct dir_info *d;
1556    int saved_errno = errno;
1557
1558    PROLOGUE(mopts.malloc_pool[0], "malloc_conceal")
1559    SET_CALLER(d, caller(d));
1560    r = omalloc(d, size, 0);
1561    EPILOGUE()
1562    return r;
1563}
1564DEF_WEAK(malloc_conceal);
1565
1566static struct region_info *
1567findpool(void *p, struct dir_info *argpool, struct dir_info **foundpool,
1568    const char ** saved_function)
1569{
1570    struct dir_info *pool = argpool;
1571    struct region_info *r = find(pool, p);
1572
1573    if (r == NULL) {
1574        u_int i, nmutexes;
1575
1576        nmutexes = mopts.malloc_pool[1]->malloc_mt ?
1577            mopts.malloc_mutexes : 2;
1578        for (i = 1; i < nmutexes; i++) {
1579            u_int j = (argpool->mutex + i) & (nmutexes - 1);
1580
1581            pool->active--;
1582            _MALLOC_UNLOCK(pool->mutex);
1583            pool = mopts.malloc_pool[j];
1584            _MALLOC_LOCK(pool->mutex);
1585            pool->active++;
1586            r = find(pool, p);
1587            if (r != NULL) {
1588                *saved_function = pool->func;
1589                pool->func = argpool->func;
1590                break;
1591            }
1592        }
1593        if (r == NULL)
1594            wrterror(argpool, "bogus pointer (double free?) %p", p);
1595    }
1596    *foundpool = pool;
1597    return r;
1598}
1599
1600static void
1601ofree(struct dir_info **argpool, void *p, int clear, int check, size_t argsz)
1602{
1603    struct region_info *r;
1604    struct dir_info *pool;
1605    const char *saved_function;
1606    size_t sz;
1607
1608    r = findpool(p, *argpool, &pool, &saved_function);
1609
1610    REALSIZE(sz, r);
1611    if (pool->mmap_flag) {
1612        clear = 1;
1613        if (!check) {
1614            argsz = sz;
1615            if (sz > MALLOC_MAXCHUNK)
1616                argsz -= mopts.malloc_guard;
1617        }
1618    }
1619    if (check) {
1620        if (sz <= MALLOC_MAXCHUNK) {
1621            if (mopts.chunk_canaries && sz > 0) {
1622                struct chunk_info *info =
1623                    (struct chunk_info *)r->size;
1624                uint32_t chunknum =
1625                    find_chunknum(pool, info, p, 0);
1626
1627                if (info->bits[info->offset + chunknum] < argsz)
1628                    wrterror(pool, "recorded size %hu"
1629                        " < %zu",
1630                        info->bits[info->offset + chunknum],
1631                        argsz);
1632            } else {
1633                if (sz < argsz)
1634                    wrterror(pool, "chunk size %zu < %zu",
1635                        sz, argsz);
1636            }
1637        } else if (sz - mopts.malloc_guard < argsz) {
1638            wrterror(pool, "recorded size %zu < %zu",
1639                sz - mopts.malloc_guard, argsz);
1640        }
1641    }
1642    if (sz > MALLOC_MAXCHUNK) {
1643        if (!MALLOC_MOVE_COND(sz)) {
1644            if (r->p != p)
1645                wrterror(pool, "bogus pointer %p", p);
1646            if (mopts.chunk_canaries)
1647                validate_canary(pool, p,
1648                    sz - mopts.malloc_guard,
1649                    PAGEROUND(sz - mopts.malloc_guard));
1650        } else {
1651            /* shifted towards the end */
1652            if (p != MALLOC_MOVE(r->p, sz))
1653                wrterror(pool, "bogus moved pointer %p", p);
1654            p = r->p;
1655        }
1656        if (mopts.malloc_guard) {
1657            if (sz < mopts.malloc_guard)
1658                wrterror(pool, "guard size");
1659            if (!mopts.malloc_freeunmap) {
1660                if (mprotect((char *)p + PAGEROUND(sz) -
1661                    mopts.malloc_guard, mopts.malloc_guard,
1662                    PROT_READ | PROT_WRITE))
1663                    wrterror(pool, "mprotect");
1664            }
1665            STATS_SUB(pool->malloc_guarded, mopts.malloc_guard);
1666        }
1667        unmap(pool, p, PAGEROUND(sz), clear ? argsz : 0);
1668        delete(pool, r);
1669    } else {
1670        void *tmp;
1671        u_int i;
1672
1673        /* Validate and optionally canary check */
1674        struct chunk_info *info = (struct chunk_info *)r->size;
1675        if (B2SIZE(info->bucket) != sz)
1676            wrterror(pool, "internal struct corrupt");
1677        find_chunknum(pool, info, p, mopts.chunk_canaries);
1678
1679        if (mopts.malloc_freecheck) {
1680            for (i = 0; i <= MALLOC_DELAYED_CHUNK_MASK; i++) {
1681                tmp = pool->delayed_chunks[i];
1682                if (tmp == p)
1683                    wrterror(pool,
1684                        "double free %p", p);
1685                if (tmp != NULL) {
1686                    size_t tmpsz;
1687
1688                    r = find(pool, tmp);
1689                    if (r == NULL)
1690                        wrterror(pool,
1691                            "bogus pointer ("
1692                            "double free?) %p", tmp);
1693                    REALSIZE(tmpsz, r);
1694                    validate_junk(pool, tmp, tmpsz);
1695                }
1696            }
1697        }
1698
1699        if (clear && argsz > 0)
1700            explicit_bzero(p, argsz);
1701        junk_free(pool->malloc_junk, p, sz);
1702
1703        i = getrbyte(pool) & MALLOC_DELAYED_CHUNK_MASK;
1704        tmp = p;
1705        p = pool->delayed_chunks[i];
1706        if (tmp == p)
1707            wrterror(pool, "double free %p", p);
1708        pool->delayed_chunks[i] = tmp;
1709        if (p != NULL) {
1710            r = find(pool, p);
1711            if (r == NULL)
1712                wrterror(pool,
1713                    "bogus pointer (double free?) %p", p);
1714            if (!mopts.malloc_freecheck) {
1715                REALSIZE(sz, r);
1716                validate_junk(pool, p, sz);
1717            }
1718            free_bytes(pool, r, p);
1719        }
1720    }
1721
1722    if (*argpool != pool) {
1723        pool->func = saved_function;
1724        *argpool = pool;
1725    }
1726}
1727
1728void
1729free(void *ptr)
1730{
1731    struct dir_info *d;
1732    int saved_errno = errno;
1733
1734    /* This is legal. */
1735    if (ptr == NULL)
1736        return;
1737
1738    d = getpool();
1739    if (d == NULL)
1740        wrterror(d, "free() called before allocation");
1741    _MALLOC_LOCK(d->mutex);
1742    d->func = "free";
1743    if (d->active++) {
1744        malloc_recurse(d);
1745        return;
1746    }
1747    ofree(&d, ptr, 0, 0, 0);
1748    d->active--;
1749    _MALLOC_UNLOCK(d->mutex);
1750    errno = saved_errno;
1751}
1752DEF_STRONG(free);
1753
1754static void
1755freezero_p(void *ptr, size_t sz)
1756{
1757    explicit_bzero(ptr, sz);
1758    free(ptr);
1759}
1760
1761void
1762freezero(void *ptr, size_t sz)
1763{
1764    struct dir_info *d;
1765    int saved_errno = errno;
1766
1767    /* This is legal. */
1768    if (ptr == NULL)
1769        return;
1770
1771    if (!mopts.internal_funcs) {
1772        freezero_p(ptr, sz);
1773        return;
1774    }
1775
1776    d = getpool();
1777    if (d == NULL)
1778        wrterror(d, "freezero() called before allocation");
1779    _MALLOC_LOCK(d->mutex);
1780    d->func = "freezero";
1781    if (d->active++) {
1782        malloc_recurse(d);
1783        return;
1784    }
1785    ofree(&d, ptr, 1, 1, sz);
1786    d->active--;
1787    _MALLOC_UNLOCK(d->mutex);
1788    errno = saved_errno;
1789}
1790DEF_WEAK(freezero);
1791
1792static void *
1793orealloc(struct dir_info **argpool, void *p, size_t newsz)
1794{
1795    struct region_info *r;
1796    struct dir_info *pool;
1797    const char *saved_function;
1798    struct chunk_info *info;
1799    size_t oldsz, goldsz, gnewsz;
1800    void *q, *ret;
1801    uint32_t chunknum;
1802    int forced;
1803
1804    if (p == NULL)
1805        return omalloc(*argpool, newsz, 0);
1806
1807    if (newsz >= SIZE_MAX - mopts.malloc_guard - MALLOC_PAGESIZE) {
1808        errno = ENOMEM;
1809        return  NULL;
1810    }
1811
1812    r = findpool(p, *argpool, &pool, &saved_function);
1813
1814    REALSIZE(oldsz, r);
1815    if (oldsz <= MALLOC_MAXCHUNK) {
1816        if (DO_STATS || mopts.chunk_canaries) {
1817            info = (struct chunk_info *)r->size;
1818            chunknum = find_chunknum(pool, info, p, 0);
1819        }
1820    }
1821
1822    goldsz = oldsz;
1823    if (oldsz > MALLOC_MAXCHUNK) {
1824        if (oldsz < mopts.malloc_guard)
1825            wrterror(pool, "guard size");
1826        oldsz -= mopts.malloc_guard;
1827    }
1828
1829    gnewsz = newsz;
1830    if (gnewsz > MALLOC_MAXCHUNK)
1831        gnewsz += mopts.malloc_guard;
1832
1833    forced = mopts.malloc_realloc || pool->mmap_flag;
1834    if (newsz > MALLOC_MAXCHUNK && oldsz > MALLOC_MAXCHUNK && !forced) {
1835        /* First case: from n pages sized allocation to m pages sized
1836           allocation, m > n */
1837        size_t roldsz = PAGEROUND(goldsz);
1838        size_t rnewsz = PAGEROUND(gnewsz);
1839
1840        if (rnewsz < roldsz && rnewsz > roldsz / 2 &&
1841            roldsz - rnewsz < mopts.def_maxcache * MALLOC_PAGESIZE &&
1842            !mopts.malloc_guard) {
1843
1844            ret = p;
1845            goto done;
1846        }
1847
1848        if (rnewsz > roldsz) {
1849            /* try to extend existing region */
1850            if (!mopts.malloc_guard) {
1851                void *hint = (char *)r->p + roldsz;
1852                size_t needed = rnewsz - roldsz;
1853
1854                STATS_INC(pool->cheap_realloc_tries);
1855                q = MMAPA(hint, needed, MAP_FIXED |
1856                    __MAP_NOREPLACE | pool->mmap_flag);
1857                if (q == hint) {
1858                    STATS_ADD(pool->malloc_used, needed);
1859                    if (pool->malloc_junk == 2)
1860                        memset(q, SOME_JUNK, needed);
1861                    r->size = gnewsz;
1862                    if (r->p != p) {
1863                        /* old pointer is moved */
1864                        memmove(r->p, p, oldsz);
1865                        p = r->p;
1866                    }
1867                    if (mopts.chunk_canaries)
1868                        fill_canary(p, newsz,
1869                            PAGEROUND(newsz));
1870                    STATS_SETF(r, (*argpool)->caller);
1871                    STATS_INC(pool->cheap_reallocs);
1872                    ret = p;
1873                    goto done;
1874                }
1875            }
1876        } else if (rnewsz < roldsz) {
1877            /* shrink number of pages */
1878            if (mopts.malloc_guard) {
1879                if (mprotect((char *)r->p + rnewsz -
1880                    mopts.malloc_guard, mopts.malloc_guard,
1881                    PROT_NONE))
1882                    wrterror(pool, "mprotect");
1883            }
1884            if (munmap((char *)r->p + rnewsz, roldsz - rnewsz))
1885                wrterror(pool, "munmap %p", (char *)r->p +
1886                    rnewsz);
1887            STATS_SUB(pool->malloc_used, roldsz - rnewsz);
1888            r->size = gnewsz;
1889            if (MALLOC_MOVE_COND(gnewsz)) {
1890                void *pp = MALLOC_MOVE(r->p, gnewsz);
1891                memmove(pp, p, newsz);
1892                p = pp;
1893            } else if (mopts.chunk_canaries)
1894                fill_canary(p, newsz, PAGEROUND(newsz));
1895            STATS_SETF(r, (*argpool)->caller);
1896            ret = p;
1897            goto done;
1898        } else {
1899            /* number of pages remains the same */
1900            void *pp = r->p;
1901
1902            r->size = gnewsz;
1903            if (MALLOC_MOVE_COND(gnewsz))
1904                pp = MALLOC_MOVE(r->p, gnewsz);
1905            if (p != pp) {
1906                memmove(pp, p, oldsz < newsz ? oldsz : newsz);
1907                p = pp;
1908            }
1909            if (p == r->p) {
1910                if (newsz > oldsz && pool->malloc_junk == 2)
1911                    memset((char *)p + newsz, SOME_JUNK,
1912                        rnewsz - mopts.malloc_guard -
1913                        newsz);
1914                if (mopts.chunk_canaries)
1915                    fill_canary(p, newsz, PAGEROUND(newsz));
1916            }
1917            STATS_SETF(r, (*argpool)->caller);
1918            ret = p;
1919            goto done;
1920        }
1921    }
1922    if (oldsz <= MALLOC_MAXCHUNK && oldsz > 0 &&
1923        newsz <= MALLOC_MAXCHUNK && newsz > 0 &&
1924        !forced && find_bucket(newsz) == find_bucket(oldsz)) {
1925        /* do not reallocate if new size fits good in existing chunk */
1926        if (pool->malloc_junk == 2)
1927            memset((char *)p + newsz, SOME_JUNK, oldsz - newsz);
1928        if (mopts.chunk_canaries) {
1929            info->bits[info->offset + chunknum] = newsz;
1930            fill_canary(p, newsz, B2SIZE(info->bucket));
1931        }
1932        if (DO_STATS)
1933            STATS_SETFN(r, chunknum, (*argpool)->caller);
1934        ret = p;
1935    } else if (newsz != oldsz || forced) {
1936        /* create new allocation */
1937        q = omalloc(pool, newsz, 0);
1938        if (q == NULL) {
1939            ret = NULL;
1940            goto done;
1941        }
1942        if (newsz != 0 && oldsz != 0)
1943            memcpy(q, p, oldsz < newsz ? oldsz : newsz);
1944        ofree(&pool, p, 0, 0, 0);
1945        ret = q;
1946    } else {
1947        /* oldsz == newsz */
1948        if (newsz != 0)
1949            wrterror(pool, "realloc internal inconsistency");
1950        if (DO_STATS)
1951            STATS_SETFN(r, chunknum, (*argpool)->caller);
1952        ret = p;
1953    }
1954done:
1955    if (*argpool != pool) {
1956        pool->func = saved_function;
1957        *argpool = pool;
1958    }
1959    return ret;
1960}
1961
1962void *
1963realloc(void *ptr, size_t size)
1964{
1965    struct dir_info *d;
1966    void *r;
1967    int saved_errno = errno;
1968
1969    PROLOGUE(getpool(), "realloc")
1970    SET_CALLER(d, caller(d));
1971    r = orealloc(&d, ptr, size);
1972    EPILOGUE()
1973    return r;
1974}
1975DEF_STRONG(realloc);
1976
1977/*
1978 * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX
1979 * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW
1980 */
1981#define MUL_NO_OVERFLOW (1UL << (sizeof(size_t) * 4))
1982
1983void *
1984calloc(size_t nmemb, size_t size)
1985{
1986    struct dir_info *d;
1987    void *r;
1988    int saved_errno = errno;
1989
1990    PROLOGUE(getpool(), "calloc")
1991    SET_CALLER(d, caller(d));
1992    if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
1993        nmemb > 0 && SIZE_MAX / nmemb < size) {
1994        d->active--;
1995        _MALLOC_UNLOCK(d->mutex);
1996        if (mopts.malloc_xmalloc)
1997            wrterror(d, "out of memory");
1998        errno = ENOMEM;
1999        return NULL;
2000    }
2001
2002    size *= nmemb;
2003    r = omalloc(d, size, 1);
2004    EPILOGUE()
2005    return r;
2006}
2007DEF_STRONG(calloc);
2008
2009void *
2010calloc_conceal(size_t nmemb, size_t size)
2011{
2012    struct dir_info *d;
2013    void *r;
2014    int saved_errno = errno;
2015
2016    PROLOGUE(mopts.malloc_pool[0], "calloc_conceal")
2017    SET_CALLER(d, caller(d));
2018    if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
2019        nmemb > 0 && SIZE_MAX / nmemb < size) {
2020        d->active--;
2021        _MALLOC_UNLOCK(d->mutex);
2022        if (mopts.malloc_xmalloc)
2023            wrterror(d, "out of memory");
2024        errno = ENOMEM;
2025        return NULL;
2026    }
2027
2028    size *= nmemb;
2029    r = omalloc(d, size, 1);
2030    EPILOGUE()
2031    return r;
2032}
2033DEF_WEAK(calloc_conceal);
2034
2035static void *
2036orecallocarray(struct dir_info **argpool, void *p, size_t oldsize,
2037    size_t newsize)
2038{
2039    struct region_info *r;
2040    struct dir_info *pool;
2041    const char *saved_function;
2042    void *newptr;
2043    size_t sz;
2044
2045    if (p == NULL)
2046        return omalloc(*argpool, newsize, 1);
2047
2048    if (oldsize == newsize)
2049        return p;
2050
2051    r = findpool(p, *argpool, &pool, &saved_function);
2052
2053    REALSIZE(sz, r);
2054    if (sz <= MALLOC_MAXCHUNK) {
2055        if (mopts.chunk_canaries && sz > 0) {
2056            struct chunk_info *info = (struct chunk_info *)r->size;
2057            uint32_t chunknum = find_chunknum(pool, info, p, 0);
2058
2059            if (info->bits[info->offset + chunknum] != oldsize)
2060                wrterror(pool, "recorded size %hu != %zu",
2061                    info->bits[info->offset + chunknum],
2062                    oldsize);
2063        } else {
2064            if (sz < oldsize)
2065                wrterror(pool, "chunk size %zu < %zu",
2066                    sz, oldsize);
2067        }
2068    } else {
2069        if (sz - mopts.malloc_guard < oldsize)
2070            wrterror(pool, "recorded size %zu < %zu",
2071                sz - mopts.malloc_guard, oldsize);
2072        if (oldsize < (sz - mopts.malloc_guard) / 2)
2073            wrterror(pool,
2074                "recorded size %zu inconsistent with %zu",
2075                sz - mopts.malloc_guard, oldsize);
2076    }
2077
2078    newptr = omalloc(pool, newsize, 0);
2079    if (newptr == NULL)
2080        goto done;
2081
2082    if (newsize > oldsize) {
2083        memcpy(newptr, p, oldsize);
2084        memset((char *)newptr + oldsize, 0, newsize - oldsize);
2085    } else
2086        memcpy(newptr, p, newsize);
2087
2088    ofree(&pool, p, 1, 0, oldsize);
2089
2090done:
2091    if (*argpool != pool) {
2092        pool->func = saved_function;
2093        *argpool = pool;
2094    }
2095
2096    return newptr;
2097}
2098
2099static void *
2100recallocarray_p(void *ptr, size_t oldnmemb, size_t newnmemb, size_t size)
2101{
2102    size_t oldsize, newsize;
2103    void *newptr;
2104
2105    if (ptr == NULL)
2106        return calloc(newnmemb, size);
2107
2108    if ((newnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
2109        newnmemb > 0 && SIZE_MAX / newnmemb < size) {
2110        errno = ENOMEM;
2111        return NULL;
2112    }
2113    newsize = newnmemb * size;
2114
2115    if ((oldnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
2116        oldnmemb > 0 && SIZE_MAX / oldnmemb < size) {
2117        errno = EINVAL;
2118        return NULL;
2119    }
2120    oldsize = oldnmemb * size;
2121
2122    /*
2123     * Don't bother too much if we're shrinking just a bit,
2124     * we do not shrink for series of small steps, oh well.
2125     */
2126    if (newsize <= oldsize) {
2127        size_t d = oldsize - newsize;
2128
2129        if (d < oldsize / 2 && d < MALLOC_PAGESIZE) {
2130            memset((char *)ptr + newsize, 0, d);
2131            return ptr;
2132        }
2133    }
2134
2135    newptr = malloc(newsize);
2136    if (newptr == NULL)
2137        return NULL;
2138
2139    if (newsize > oldsize) {
2140        memcpy(newptr, ptr, oldsize);
2141        memset((char *)newptr + oldsize, 0, newsize - oldsize);
2142    } else
2143        memcpy(newptr, ptr, newsize);
2144
2145    explicit_bzero(ptr, oldsize);
2146    free(ptr);
2147
2148    return newptr;
2149}
2150
2151void *
2152recallocarray(void *ptr, size_t oldnmemb, size_t newnmemb, size_t size)
2153{
2154    struct dir_info *d;
2155    size_t oldsize = 0, newsize;
2156    void *r;
2157    int saved_errno = errno;
2158
2159    if (!mopts.internal_funcs)
2160        return recallocarray_p(ptr, oldnmemb, newnmemb, size);
2161
2162    PROLOGUE(getpool(), "recallocarray")
2163    SET_CALLER(d, caller(d));
2164
2165    if ((newnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
2166        newnmemb > 0 && SIZE_MAX / newnmemb < size) {
2167        d->active--;
2168        _MALLOC_UNLOCK(d->mutex);
2169        if (mopts.malloc_xmalloc)
2170            wrterror(d, "out of memory");
2171        errno = ENOMEM;
2172        return NULL;
2173    }
2174    newsize = newnmemb * size;
2175
2176    if (ptr != NULL) {
2177        if ((oldnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
2178            oldnmemb > 0 && SIZE_MAX / oldnmemb < size) {
2179            d->active--;
2180            _MALLOC_UNLOCK(d->mutex);
2181            errno = EINVAL;
2182            return NULL;
2183        }
2184        oldsize = oldnmemb * size;
2185    }
2186
2187    r = orecallocarray(&d, ptr, oldsize, newsize);
2188    EPILOGUE()
2189    return r;
2190}
2191DEF_WEAK(recallocarray);
2192
2193static void *
2194mapalign(struct dir_info *d, size_t alignment, size_t sz, int zero_fill)
2195{
2196    char *p, *q;
2197
2198    if (alignment < MALLOC_PAGESIZE || ((alignment - 1) & alignment) != 0)
2199        wrterror(d, "mapalign bad alignment");
2200    if (sz != PAGEROUND(sz))
2201        wrterror(d, "mapalign round");
2202
2203    /* Allocate sz + alignment bytes of memory, which must include a
2204     * subrange of size bytes that is properly aligned.  Unmap the
2205     * other bytes, and then return that subrange.
2206     */
2207
2208    /* We need sz + alignment to fit into a size_t. */
2209    if (alignment > SIZE_MAX - sz)
2210        return MAP_FAILED;
2211
2212    p = map(d, sz + alignment, zero_fill);
2213    if (p == MAP_FAILED)
2214        return MAP_FAILED;
2215    q = (char *)(((uintptr_t)p + alignment - 1) & ~(alignment - 1));
2216    if (q != p) {
2217        if (munmap(p, q - p))
2218            wrterror(d, "munmap %p", p);
2219    }
2220    if (munmap(q + sz, alignment - (q - p)))
2221        wrterror(d, "munmap %p", q + sz);
2222    STATS_SUB(d->malloc_used, alignment);
2223
2224    return q;
2225}
2226
2227static void *
2228omemalign(struct dir_info *pool, size_t alignment, size_t sz, int zero_fill)
2229{
2230    size_t psz;
2231    void *p, *caller = NULL;
2232
2233    /* If between half a page and a page, avoid MALLOC_MOVE. */
2234    if (sz > MALLOC_MAXCHUNK && sz < MALLOC_PAGESIZE)
2235        sz = MALLOC_PAGESIZE;
2236    if (alignment <= MALLOC_PAGESIZE) {
2237        size_t pof2;
2238        /*
2239         * max(size, alignment) rounded up to power of 2 is enough
2240         * to assure the requested alignment. Large regions are
2241         * always page aligned.
2242         */
2243        if (sz < alignment)
2244            sz = alignment;
2245        if (sz < MALLOC_PAGESIZE) {
2246            pof2 = MALLOC_MINSIZE;
2247            while (pof2 < sz)
2248                pof2 <<= 1;
2249        } else
2250            pof2 = sz;
2251        return omalloc(pool, pof2, zero_fill);
2252    }
2253
2254    if (sz >= SIZE_MAX - mopts.malloc_guard - MALLOC_PAGESIZE) {
2255        errno = ENOMEM;
2256        return NULL;
2257    }
2258
2259    if (sz < MALLOC_PAGESIZE)
2260        sz = MALLOC_PAGESIZE;
2261    sz += mopts.malloc_guard;
2262    psz = PAGEROUND(sz);
2263
2264    p = mapalign(pool, alignment, psz, zero_fill);
2265    if (p == MAP_FAILED) {
2266        errno = ENOMEM;
2267        return NULL;
2268    }
2269
2270#ifdef MALLOC_STATS
2271    if (DO_STATS)
2272        caller = pool->caller;
2273#endif
2274    if (insert(pool, p, sz, caller)) {
2275        unmap(pool, p, psz, 0);
2276        errno = ENOMEM;
2277        return NULL;
2278    }
2279
2280    if (mopts.malloc_guard) {
2281        if (mprotect((char *)p + psz - mopts.malloc_guard,
2282            mopts.malloc_guard, PROT_NONE))
2283            wrterror(pool, "mprotect");
2284        STATS_ADD(pool->malloc_guarded, mopts.malloc_guard);
2285    }
2286
2287    if (pool->malloc_junk == 2) {
2288        if (zero_fill)
2289            memset((char *)p + sz - mopts.malloc_guard,
2290                SOME_JUNK, psz - sz);
2291        else
2292            memset(p, SOME_JUNK, psz - mopts.malloc_guard);
2293    } else if (mopts.chunk_canaries)
2294        fill_canary(p, sz - mopts.malloc_guard,
2295            psz - mopts.malloc_guard);
2296
2297    return p;
2298}
2299
2300int
2301posix_memalign(void **memptr, size_t alignment, size_t size)
2302{
2303    struct dir_info *d;
2304    int res, saved_errno = errno;
2305    void *r;
2306
2307    /* Make sure that alignment is a large enough power of 2. */
2308    if (((alignment - 1) & alignment) != 0 || alignment < sizeof(void *))
2309        return EINVAL;
2310
2311    d = getpool();
2312    if (d == NULL) {
2313        _malloc_init(0);
2314        d = getpool();
2315    }
2316    _MALLOC_LOCK(d->mutex);
2317    d->func = "posix_memalign";
2318    if (d->active++) {
2319        malloc_recurse(d);
2320        goto err;
2321    }
2322    SET_CALLER(d, caller(d));
2323    r = omemalign(d, alignment, size, 0);
2324    d->active--;
2325    _MALLOC_UNLOCK(d->mutex);
2326    if (r == NULL) {
2327        if (mopts.malloc_xmalloc)
2328            wrterror(d, "out of memory");
2329        goto err;
2330    }
2331    errno = saved_errno;
2332    *memptr = r;
2333    return 0;
2334
2335err:
2336    res = errno;
2337    errno = saved_errno;
2338    return res;
2339}
2340DEF_STRONG(posix_memalign);
2341
2342void *
2343aligned_alloc(size_t alignment, size_t size)
2344{
2345    struct dir_info *d;
2346    int saved_errno = errno;
2347    void *r;
2348
2349    /* Make sure that alignment is a positive power of 2. */
2350    if (((alignment - 1) & alignment) != 0 || alignment == 0) {
2351        errno = EINVAL;
2352        return NULL;
2353    }
2354    /* Per spec, size should be a multiple of alignment */
2355    if ((size & (alignment - 1)) != 0) {
2356        errno = EINVAL;
2357        return NULL;
2358    }
2359
2360    PROLOGUE(getpool(), "aligned_alloc")
2361    SET_CALLER(d, caller(d));
2362    r = omemalign(d, alignment, size, 0);
2363    EPILOGUE()
2364    return r;
2365}
2366DEF_STRONG(aligned_alloc);
2367
2368#ifdef MALLOC_STATS
2369
2370static int
2371btcmp(const struct btnode *e1, const struct btnode *e2)
2372{
2373    return memcmp(e1->caller, e2->caller, sizeof(e1->caller));
2374}
2375
2376RBT_GENERATE(btshead, btnode, entry, btcmp);
2377
2378static void*
2379store_caller(struct dir_info *d, struct btnode *f)
2380{
2381    struct btnode *p;
2382
2383    if (DO_STATS == 0 || d->btnodes == MAP_FAILED)
2384        return NULL;
2385
2386    p = RBT_FIND(btshead, &d->btraces, f);
2387    if (p != NULL)
2388        return p;
2389    if (d->btnodes == NULL ||
2390        d->btnodesused >= MALLOC_PAGESIZE / sizeof(struct btnode)) {
2391        d->btnodes = map(d, MALLOC_PAGESIZE, 0);
2392        if (d->btnodes == MAP_FAILED)
2393            return NULL;
2394        d->btnodesused = 0;
2395    }
2396    p = &d->btnodes[d->btnodesused++];
2397    memcpy(p->caller, f->caller, sizeof(p->caller[0]) * DO_STATS);
2398    RBT_INSERT(btshead, &d->btraces, p);
2399    return p;
2400}
2401
2402static void fabstorel(const void *, char *, size_t);
2403
2404static void
2405print_chunk_details(struct dir_info *pool, void *p, size_t sz, size_t index)
2406{
2407    struct region_info *r;
2408    struct chunk_info *chunkinfo;
2409    struct btnode* btnode;
2410    uint32_t chunknum;
2411    int frame;
2412    char buf1[128];
2413    char buf2[128];
2414    const char *msg = "";
2415
2416    r = find(pool, p);
2417    chunkinfo = (struct chunk_info *)r->size;
2418    chunknum = find_chunknum(pool, chunkinfo, p, 0);
2419    btnode = (struct btnode *)r->f[chunknum];
2420    frame = DO_STATS - 1;
2421    if (btnode != NULL)
2422        fabstorel(btnode->caller[frame], buf1, sizeof(buf1));
2423    strlcpy(buf2, ". 0x0", sizeof(buf2));
2424    if (chunknum > 0) {
2425        chunknum--;
2426        btnode = (struct btnode *)r->f[chunknum];
2427        if (btnode != NULL)
2428            fabstorel(btnode->caller[frame], buf2, sizeof(buf2));
2429        if (CHUNK_FREE(chunkinfo, chunknum))
2430            msg = " (now free)";
2431    }
2432
2433    wrterror(pool,
2434        "write to free chunk %p[%zu..%zu]@%zu allocated at %s "
2435        "(preceding chunk %p allocated at %s%s)",
2436        p, index * sizeof(uint64_t), (index + 1) * sizeof(uint64_t) - 1,
2437        sz, buf1, p - sz, buf2, msg);
2438}
2439
2440static void
2441ulog(const char *format, ...)
2442{
2443    va_list ap;
2444    static char* buf;
2445    static size_t filled;
2446    int len;
2447
2448    if (buf == NULL)
2449        buf = MMAP(KTR_USER_MAXLEN, 0);
2450    if (buf == MAP_FAILED)
2451        return;
2452
2453    va_start(ap, format);
2454    len = vsnprintf(buf + filled, KTR_USER_MAXLEN - filled, format, ap);
2455    va_end(ap);
2456    if (len < 0)
2457        return;
2458    if ((size_t)len > KTR_USER_MAXLEN - filled)
2459        len = KTR_USER_MAXLEN - filled;
2460    filled += len;
2461    if (filled > 0) {
2462        if (filled == KTR_USER_MAXLEN || buf[filled - 1] == '\n') {
2463            utrace("malloc", buf, filled);
2464            filled = 0;
2465        }
2466    }
2467}
2468
2469struct malloc_leak {
2470    void *f;
2471    size_t total_size;
2472    int count;
2473};
2474
2475struct leaknode {
2476    RBT_ENTRY(leaknode) entry;
2477    struct malloc_leak d;
2478};
2479
2480static inline int
2481leakcmp(const struct leaknode *e1, const struct leaknode *e2)
2482{
2483    return e1->d.f < e2->d.f ? -1 : e1->d.f > e2->d.f;
2484}
2485
2486RBT_HEAD(leaktree, leaknode);
2487RBT_PROTOTYPE(leaktree, leaknode, entry, leakcmp);
2488RBT_GENERATE(leaktree, leaknode, entry, leakcmp);
2489
2490static void
2491wrtwarning(const char *func, char *msg, ...)
2492{
2493    int     saved_errno = errno;
2494    va_list     ap;
2495
2496    dprintf(STDERR_FILENO, "%s(%d) in %s(): ", __progname,
2497        getpid(), func != NULL ? func : "unknown");
2498    va_start(ap, msg);
2499    vdprintf(STDERR_FILENO, msg, ap);
2500    va_end(ap);
2501    dprintf(STDERR_FILENO, "\n");
2502
2503    errno = saved_errno;
2504}
2505
2506static void
2507putleakinfo(struct leaktree *leaks, void *f, size_t sz, int cnt)
2508{
2509    struct leaknode key, *p;
2510    static struct leaknode *page;
2511    static unsigned int used;
2512
2513    if (cnt == 0 || page == MAP_FAILED)
2514        return;
2515
2516    key.d.f = f;
2517    p = RBT_FIND(leaktree, leaks, &key);
2518    if (p == NULL) {
2519        if (page == NULL ||
2520            used >= MALLOC_PAGESIZE / sizeof(struct leaknode)) {
2521            page = MMAP(MALLOC_PAGESIZE, 0);
2522            if (page == MAP_FAILED) {
2523                wrtwarning(__func__, strerror(errno));
2524                return;
2525            }
2526            used = 0;
2527        }
2528        p = &page[used++];
2529        p->d.f = f;
2530        p->d.total_size = sz * cnt;
2531        p->d.count = cnt;
2532        RBT_INSERT(leaktree, leaks, p);
2533    } else {
2534        p->d.total_size += sz * cnt;
2535        p->d.count += cnt;
2536    }
2537}
2538
2539static void
2540fabstorel(const void *f, char *buf, size_t size)
2541{
2542    Dl_info info;
2543    const char *object = ".";
2544    const char *caller;
2545
2546    caller = f;
2547    if (caller != NULL && dladdr(f, &info) != 0) {
2548        caller -= (uintptr_t)info.dli_fbase;
2549        object = info.dli_fname;
2550    }
2551    snprintf(buf, size, "%s %p", object, caller);
2552}
2553
2554static void
2555dump_leak(struct leaknode *p)
2556{
2557    int i;
2558    char buf[128];
2559
2560    if (p->d.f == NULL) {
2561        fabstorel(NULL, buf, sizeof(buf));
2562        ulog("%18p %7zu %6u %6zu addr2line -e %s\n",
2563            p->d.f, p->d.total_size, p->d.count,
2564            p->d.total_size / p->d.count, buf);
2565        return;
2566    }
2567
2568    for (i = 0; i < DO_STATS; i++) {
2569        const char *abscaller;
2570
2571        abscaller = ((struct btnode*)p->d.f)->caller[i];
2572        if (abscaller == NULL)
2573            break;
2574        fabstorel(abscaller, buf, sizeof(buf));
2575        if (i == 0)
2576            ulog("%18p %7zu %6u %6zu addr2line -e %s\n",
2577                abscaller, p->d.total_size, p->d.count,
2578                p->d.total_size / p->d.count, buf);
2579        else
2580            ulog("%*p %*s %6s %6s addr2line -e %s\n",
2581                i + 18, abscaller, 7 - i, "-", "-", "-", buf);
2582    }
2583}
2584
2585static void
2586dump_leaks(struct leaktree *leaks)
2587{
2588    struct leaknode *p;
2589
2590    ulog("Leak report:\n");
2591    ulog("                 f     sum      #    avg\n");
2592
2593    RBT_FOREACH(p, leaktree, leaks)
2594        dump_leak(p);
2595}
2596
2597static void
2598dump_chunk(struct leaktree* leaks, struct chunk_info *p, void **f,
2599    int fromfreelist)
2600{
2601    while (p != NULL) {
2602        if (mopts.malloc_verbose)
2603            ulog("chunk %18p %18p %4zu %d/%d\n",
2604                p->page, NULL,
2605                B2SIZE(p->bucket), p->free, p->total);
2606        if (!fromfreelist) {
2607            size_t i, sz =  B2SIZE(p->bucket);
2608            for (i = 0; i < p->total; i++) {
2609                if (!CHUNK_FREE(p, i))
2610                    putleakinfo(leaks, f[i], sz, 1);
2611            }
2612            break;
2613        }
2614        p = LIST_NEXT(p, entries);
2615        if (mopts.malloc_verbose && p != NULL)
2616            ulog("       ->");
2617    }
2618}
2619
2620static void
2621dump_free_chunk_info(struct dir_info *d, struct leaktree *leaks)
2622{
2623    u_int i, j, count;
2624    struct chunk_info *p;
2625
2626    ulog("Free chunk structs:\n");
2627    ulog("Bkt) #CI                     page"
2628        "                  f size free/n\n");
2629    for (i = 0; i <= BUCKETS; i++) {
2630        count = 0;
2631        LIST_FOREACH(p, &d->chunk_info_list[i], entries)
2632            count++;
2633        for (j = 0; j < MALLOC_CHUNK_LISTS; j++) {
2634            p = LIST_FIRST(&d->chunk_dir[i][j]);
2635            if (p == NULL && count == 0)
2636                continue;
2637            if (j == 0)
2638                ulog("%3d) %3d ", i, count);
2639            else
2640                ulog("         ");
2641            if (p != NULL)
2642                dump_chunk(leaks, p, NULL, 1);
2643            else
2644                ulog(".\n");
2645        }
2646    }
2647
2648}
2649
2650static void
2651dump_free_page_info(struct dir_info *d)
2652{
2653    struct smallcache *cache;
2654    size_t i, total = 0;
2655
2656    ulog("Cached in small cache:\n");
2657    for (i = 0; i < MAX_SMALLCACHEABLE_SIZE; i++) {
2658        cache = &d->smallcache[i];
2659        if (cache->length != 0)
2660            ulog("%zu(%u): %u = %zu\n", i + 1, cache->max,
2661                cache->length, cache->length * (i + 1));
2662        total += cache->length * (i + 1);
2663    }
2664
2665    ulog("Cached in big cache: %zu/%zu\n", d->bigcache_used,
2666        d->bigcache_size);
2667    for (i = 0; i < d->bigcache_size; i++) {
2668        if (d->bigcache[i].psize != 0)
2669            ulog("%zu: %zu\n", i, d->bigcache[i].psize);
2670        total += d->bigcache[i].psize;
2671    }
2672    ulog("Free pages cached: %zu\n", total);
2673}
2674
2675static void
2676malloc_dump1(int poolno, struct dir_info *d, struct leaktree *leaks)
2677{
2678    size_t i, realsize;
2679
2680    if (mopts.malloc_verbose) {
2681        ulog("Malloc dir of %s pool %d at %p\n", __progname, poolno, d);
2682        ulog("MT=%d J=%d Fl=%#x\n", d->malloc_mt, d->malloc_junk,
2683            d->mmap_flag);
2684        ulog("Region slots free %zu/%zu\n",
2685            d->regions_free, d->regions_total);
2686        ulog("Inserts %zu/%zu\n", d->inserts, d->insert_collisions);
2687        ulog("Deletes %zu/%zu\n", d->deletes, d->delete_moves);
2688        ulog("Cheap reallocs %zu/%zu\n",
2689            d->cheap_reallocs, d->cheap_realloc_tries);
2690        ulog("In use %zu\n", d->malloc_used);
2691        ulog("Guarded %zu\n", d->malloc_guarded);
2692        dump_free_chunk_info(d, leaks);
2693        dump_free_page_info(d);
2694        ulog("Hash table:\n");
2695        ulog("slot)  hash d  type               page                  "
2696            "f size [free/n]\n");
2697    }
2698    for (i = 0; i < d->regions_total; i++) {
2699        if (d->r[i].p != NULL) {
2700            size_t h = hash(d->r[i].p) &
2701                (d->regions_total - 1);
2702            if (mopts.malloc_verbose)
2703                ulog("%4zx) #%4zx %zd ",
2704                    i, h, h - i);
2705            REALSIZE(realsize, &d->r[i]);
2706            if (realsize > MALLOC_MAXCHUNK) {
2707                putleakinfo(leaks, d->r[i].f, realsize, 1);
2708                if (mopts.malloc_verbose)
2709                    ulog("pages %18p %18p %zu\n", d->r[i].p,
2710                        d->r[i].f, realsize);
2711            } else
2712                dump_chunk(leaks,
2713                    (struct chunk_info *)d->r[i].size,
2714                    d->r[i].f, 0);
2715        }
2716    }
2717    if (mopts.malloc_verbose)
2718        ulog("\n");
2719}
2720
2721static void
2722malloc_dump0(int poolno, struct dir_info *pool, struct leaktree *leaks)
2723{
2724    int i;
2725    void *p;
2726    struct region_info *r;
2727
2728    if (pool == NULL || pool->r == NULL)
2729        return;
2730    for (i = 0; i < MALLOC_DELAYED_CHUNK_MASK + 1; i++) {
2731        p = pool->delayed_chunks[i];
2732        if (p == NULL)
2733            continue;
2734        r = find(pool, p);
2735        if (r == NULL)
2736            wrterror(pool, "bogus pointer in malloc_dump %p", p);
2737        free_bytes(pool, r, p);
2738        pool->delayed_chunks[i] = NULL;
2739    }
2740    malloc_dump1(poolno, pool, leaks);
2741}
2742
2743void
2744malloc_dump(void)
2745{
2746    u_int i;
2747    int saved_errno = errno;
2748
2749    /* XXX leak when run multiple times */
2750    struct leaktree leaks = RBT_INITIALIZER(&leaks);
2751
2752    for (i = 0; i < mopts.malloc_mutexes; i++)
2753        malloc_dump0(i, mopts.malloc_pool[i], &leaks);
2754
2755    dump_leaks(&leaks);
2756    ulog("\n");
2757    errno = saved_errno;
2758}
2759DEF_WEAK(malloc_dump);
2760
2761static void
2762malloc_exit(void)
2763{
2764    int save_errno = errno;
2765
2766    ulog("******** Start dump %s *******\n", __progname);
2767    ulog("M=%u I=%d F=%d U=%d J=%d R=%d X=%d C=%#x cache=%u "
2768        "G=%zu\n",
2769        mopts.malloc_mutexes,
2770        mopts.internal_funcs, mopts.malloc_freecheck,
2771        mopts.malloc_freeunmap, mopts.def_malloc_junk,
2772        mopts.malloc_realloc, mopts.malloc_xmalloc,
2773        mopts.chunk_canaries, mopts.def_maxcache,
2774        mopts.malloc_guard);
2775
2776    malloc_dump();
2777    ulog("******** End dump %s *******\n", __progname);
2778    errno = save_errno;
2779}
2780
2781#endif /* MALLOC_STATS */
2782