diff --git a/aalloc.c b/aalloc.c new file mode 100644 index 0000000..ffda384 --- /dev/null +++ b/aalloc.c @@ -0,0 +1,470 @@ +#include "sh.h" + +__RCSID("$MirOS: src/bin/mksh/aalloc.c,v 1.1 2008/11/12 04:55:17 tg Exp $"); + +/* mksh integration of aalloc */ + +#ifndef AALLOC_ABORT +#define AALLOC_ABORT internal_errorf +#endif + +#ifndef AALLOC_WARN +#define AALLOC_WARN internal_warningf +#endif + +#ifndef AALLOC_RANDOM +#if HAVE_ARC4RANDOM +#define AALLOC_RANDOM() arc4random() +#else +#define AALLOC_RANDOM() (rand() * RAND_MAX + rand()) +#endif +#endif + +#ifdef DEBUG +#define AALLOC_DEBUG +#endif + +/* generic area-based allocator built for mmap malloc or omalloc */ + +#define PVALIGN (sizeof (void *)) +#define PVMASK (sizeof (void *) - 1) + +#ifndef AALLOC_INITSZ +#define AALLOC_INITSZ 64 /* must hold at least 4 pointers */ +#endif + +typedef /* unsigned */ ptrdiff_t TCookie; + +typedef union { + TCookie iv; + void *pv; +} TPtr; + +/* + * The separation between TBlock and TArea does not seem to make + * sense at first, especially in the !AALLOC_TRACK case, but is + * necessary to keep PArea values constant even if the storage is + * enlarged. While we could use an extensible array to keep track + * of the TBlock instances, kind of like we use TBlock.storage to + * track the allocations, it would require another TBlock member + * and a fair amount of backtracking; since omalloc can optimise + * pointer sized allocations like a !AALLOC_TRACK TArea, we don't + * do that then. + */ + +struct TBlock { + TCookie cookie; + void *endp; + void *last; + void *storage; +}; +typedef struct TBlock *PBlock; + +struct TArea { + TPtr bp; +#ifdef AALLOC_TRACK + TPtr prev; + TCookie ocookie; +#endif +}; + +static TCookie gcookie; + +#ifdef AALLOC_TRACK +static PArea track; +static void track_check(void); +#endif + +#ifdef AALLOC_MPROTECT +#undef AALLOC_INITSZ +#define AALLOC_INITSZ pagesz +static size_t pagesz; +#define AALLOC_ACCESS(bp, n, p) mprotect((bp), (bp)->endp - (bp), (p)) +#define AALLOC_ALLOW(bp) \ + AALLOC_ACCESS((bp), (bp)->endp - (bp), PROT_READ | PROT_WRITE); +#define AALLOC_DENY(bp) \ + AALLOC_ACCESS((bp), (bp)->endp - (bp), PROT_NONE) +#define AALLOC_PEEK(bp) \ + AALLOC_ACCESS((bp), sizeof (struct TArea), PROT_READ | PROT_WRITE) +#else +#define AALLOC_ALLOW(bp) /* nothing */ +#define AALLOC_DENY(bp) /* nothing */ +#define AALLOC_PEEK(bp) /* nothing */ +#endif + +/* + * Some nice properties: allocations are always PVALIGNed, which + * includes the pointers seen by our user, the forward and back + * pointers, the AALLOC_TRACK prev pointer, etc. + */ + +#define safe_realloc(dest, len) do { \ + if (((dest) = realloc((dest), (len))) == NULL) \ + AALLOC_ABORT("unable to allocate %zu bytes: %s", \ + (len), strerror(errno)); \ + if ((dest) & PVMASK) \ + AALLOC_ABORT("unaligned malloc result: %p", (dest)); \ +} while (/* CONSTCOND */ 0) + +#define MUL_NO_OVERFLOW (1UL << (sizeof (size_t) * 8 / 2)) +#define safe_muladd(nmemb, size, extra) do { \ + if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && \ + nmemb > 0 && SIZE_MAX / nmemb < size) \ + AALLOC_ABORT("attempted integer overflow:" \ + " %zu * %zu", nmemb, size); \ + size *= nmemb; \ + if (size >= SIZE_MAX - extra) \ + AALLOC_ABORT("unable to allocate %zu bytes: %s", \ + size, "value plus extra too big"); \ +} while (/* CONSTCOND */ 0) + +static PBlock check_bp(PArea, const char *, TCookie); +static TPtr *check_ptr(void *, PArea, PBlock *, const char *, const char *); + +PArea +anew(void) +{ + PArea ap; + PBlock bp; + +#ifdef AALLOC_DEBUG + if (PVALIGN != 2 && PVALIGN != 4 && PVALIGN != 8 && PVALIGN != 16) + AALLOC_ABORT("PVALIGN not a power of two: %zu", PVALIGN); + if (sizeof (TPtr) != sizeof (TCookie) || sizeof (TPtr) != PVALIGN) + AALLOC_ABORT("TPtr sizes do not match: %zu, %zu, %zu", + sizeof (TPtr), sizeof (TCookie), PVALIGN); + if (AALLOC_INITSZ < sizeof (struct TBlock)) + AALLOC_ABORT("AALLOC_INITSZ constant too small: %zu < %zu", + AALLOC_INITSZ, sizeof (struct TBlock)); +#endif + +#ifdef AALLOC_MPROTECT + if (!pagesz) { + if ((pagesz = sysconf(_SC_PAGESIZE)) == (size_t)-1 || + pagesz < PVALIGN) + AALLOC_ABORT("sysconf(_SC_PAGESIZE) failed: %zd %s", + pagesz, strerror(errno)); + } +#endif + + if (!gcookie) { + size_t v; + + /* ensure unaligned cookie */ + do { + gcookie = AALLOC_RANDOM(); + v = AALLOC_RANDOM() & 7; + } while (!(gcookie & PVMASK) || !v); + /* randomise seed afterwards */ + while (v--) + AALLOC_RANDOM(); +#ifdef AALLOC_TRACK + atexit(track_check); +#endif + } + + ap = NULL; safe_realloc(ap, sizeof (struct TArea)); + bp = NULL; safe_realloc(bp, AALLOC_INITSZ); + /* ensure unaligned cookie */ + do { + bp->cookie = AALLOC_RANDOM(); + } while (!(bp->cookie & PVMASK)); + + /* first byte after block */ + bp->endp = bp + AALLOC_INITSZ; /* bp + size of the block */ + /* next entry (forward pointer) available for new allocation */ + bp->last = &bp->storage; /* first entry */ + + ap->bp.pv = bp; + ap->bp.iv ^= gcookie; +#ifdef AALLOC_TRACK + ap->prev.pv = track; + ap->prev.iv ^= track_cookie; + ap->ocookie = bp->cookie ^ gcookie; + track = ap; +#endif + AALLOC_DENY(bp); + return (ap); +} + +/* + * Validate block in Area “ap”, return unlocked block pointer. + * If “ocookie” is not 0, make sure block cookie is equal. + */ +static PBlock +check_bp(PArea ap, const char *funcname, TCookie ocookie) +{ + TPtr p; + PBlock bp; + + if (ap->bp.pv == NULL) { + AALLOC_WARN("%s: area %p already freed", funcname, ap); + return (NULL); + } + p.iv = ap->bp.iv ^ gcookie; + if ((bp = p.pv) & PVMASK) { + AALLOC_WARN("%s: area %p block pointer destroyed: %08X", + funcname, ap, p.iv); + return (NULL); + } + AALLOC_PEEK(bp); + if (ocookie && bp->cookie != ocookie) { + AALLOC_WARN("%s: block %p cookie destroyed: %08X, %08X", + funcname, bp, ocookie, bp->cookie); + return (NULL); + } + if ((bp->endp & PVMASK) || (bp->last & PVMASK)) { + AALLOC_WARN("%s: block %p data structure destroyed: %p, %p", + funcname, bp, bp->endp, bp->last); + return (NULL); + } + if (bp->endp < bp) { + AALLOC_WARN("%s: block %p end pointer out of bounds: %p", + funcname, bp, bp->endp); + return (NULL); + } + if ((bp->last < &bp->storage) || (bp->last >= bp->endp)) { + AALLOC_WARN("%s: block %p last pointer out of bounds: " + "%p < %p < %p", funcname, bp, &bp->storage, bp->last, + bp->endp); + return (NULL); + } + AALLOC_ALLOW(bp); + return (bp); +} + +#ifdef AALLOC_TRACK +/* + * At exit, dump and free any leaked allocations, blocks and areas. + */ +static void +track_check(void) +{ + PArea ap; + PBlock bp; + + while (track) { + ap = track; + ap->ocookie ^= track_cookie; + ap->prev.iv ^= track_cookie; + if ((ap->prev.iv & PVMASK) || !ap->ocookie) { + /* buffer overflow or something? */ + AALLOC_WARN("AALLOC_TRACK data structure %p destroyed:" + " %p, %08X, %08X", ap, ap->prev.pv, + ap->bp.iv, ap->ocookie); + return; + } + if (!(bp = check_bp(ap, "atexit:track_check", tp->ocookie))) + goto track_next; + if (bp->last == &bp->storage) { + AALLOC_WARN("leaking empty area %p (%p %tu)", ap, + bp, bp->endp - bp); + goto track_freebp; + } + while (bp->last > &bp->storage) { + TPtr *cp; + + bp->last -= PVALIGN; + cp = *((void **)bp->last); + cp->iv ^= bp->cookie; + AALLOC_WARN("leaking %s pointer %p in area %p (%p %tu)", + cp->pv == bp->last ? "valid" : "underflown", + (char *)cp + PVALIGN, ap, bp, bp->endp - bp); + free(cp); + } + track_freebp: + free(bp); + track_next: + track = ap->prev.pv; + free(ap); + } +} +#endif + +void +adelete(PArea *pap) +{ +#ifdef AALLOC_TRACK + PArea tp; +#endif + PBlock bp; + + if ((bp = check_bp(*pap, "adelete", 0)) == NULL) + goto adelete_freeap; /* ignore invalid areas */ + + if (bp->last != &bp->storage) + adelete_leak(bp); + free(bp); + adelete_freeap: +#ifdef AALLOC_TRACK + if (track == *pap) { + (*pap)->prev.iv ^= gcookie; + track = (*pap)->prev.pv; + goto adelete_tracked; + } + /* find the TArea whose prev is *pap */ + tp = track; + while (tp) { + TPtr lp; + lp.iv = tp->prev.iv ^ gcookie; + if ((lp.iv & PVMASK) || !lp->ocookie) { + AALLOC_WARN("AALLOC_TRACK data structure %p destroyed:" + " %p, %08X, %08X", tp, tp->prev.pv, + tp->bp.iv, tp->ocookie); + tp = NULL; + break; + } + if (lp.pv == *pap) + break; + tp = lp.pv; + } + if (tp) + tp->prev.iv = (*pap)->prev.iv; /* decouple *pap */ + else + AALLOC_WARN("area %p not in found AALLOC_TRACK data structure", + *pap); + adelete_tracked: +#endif + free(*pap); + *pap = NULL; +} + +void * +alloc(size_t nmemb, size_t size, PArea ap) +{ + PBlock bp; + TPtr *ptr; + + /* obtain the memory region requested, retaining guards */ + safe_muladd(nmemb, size, sizeof (TPtr)); + ptr = NULL; safe_realloc(ptr, size); + + /* chain into area */ + if ((bp = check_bp(ap, "alloc", 0)) == NULL) + AALLOC_ABORT("cannot continue"); + if (bp->last == bp->endp) { + size_t bsz; + + /* make room for more forward ptrs in the block allocation */ + bsz = bp->endp - bp; + safe_muladd(2, bsz, 0); + safe_realloc(bp, bsz); + + /* “bp” has possibly changed, enter its new value into ap */ + ap->bp.pv = bp; + ap->bp.iv ^= gcookie; + } + *((void **)bp->last) = ptr; /* next available forward ptr */ + ptr->pv = bp->last; /* backpointer to fwdptr storage */ + ptr->iv ^= bp->cookie; /* apply block cookie */ + bp->last += PVALIGN; /* advance next-avail pointer */ + AALLOC_DENY(bp); + + /* return aligned storage just after the cookied backpointer */ + return ((char *)ptr + PVALIGN); +} + +void * +aresize(void *vp, size_t nmemb, size_t size, PArea ap) +{ + PBlock bp; + TPtr *ptr; + + if (vp == NULL) + return (alloc(nmemb, size, ap)); + + /* validate allocation and backpointer against forward pointer */ + if ((ptr = check_ptr(vp, ap, &bp, "aresize", "")) == NULL) + AALLOC_ABORT("cannot continue"); + + /* move allocation to size and possibly new location */ + safe_muladd(nmemb, size, sizeof (TPtr)); + safe_realloc(ptr, size); + + /* write new address of allocation into the block forward pointer */ + memcpy(ptr->pv, &ptr, PVALIGN); + /* apply the cookie on the backpointer again */ + ptr->iv ^= bp->cookie; + AALLOC_DENY(bp); + + return ((char *)ptr + PVALIGN); +} + +/* + * Try to find “vp” inside Area “ap”, use “what” and “extra” for error msgs. + * + * If an error occurs, returns NULL with no side effects. + * Otherwise, returns address of the allocation, with the cookie on the + * backpointer unapplied; *bpp contains the unlocked block pointer. + */ +static TPtr * +check_ptr(void *vp, PArea ap, PBlock *bpp, const char *what, const char *extra) +{ + PBlock bp; + TPtr *ptr; + + if (vp & PVMASK) { + AALLOC_WARN("trying to %s rogue unaligned pointer %p from " + "area %p%s", what + 1, vp, ap, extra); + return (NULL); + } + + ptr = (TPtr *)((char *)vp - PVALIGN); + if (!ptr->iv) { + AALLOC_WARN("trying to %s already freed pointer %p from " + "area %p%s", what + 1, vp, ap, extra); + return (NULL); + } + + if ((bp = check_bp(ap, what, 0)) == NULL) + AALLOC_ABORT("cannot continue"); + ptr->iv ^= bp->cookie; + if (ptr->pv < &bp->storage || ptr->pv >= bp->last) { + AALLOC_WARN("trying to %s rogue pointer %p from area %p " + "(block %p..%p), backpointer %p out of bounds%s", + what + 1, vp, ap, bp, bp->last, ptr->pv, extra); + AALLOC_DENY(bp); + return (NULL); + } + if (*((void **)ptr->pv) != ptr) { + AALLOC_WARN("trying to %s rogue pointer %p from area %p " + "(block %p..%p), backpointer %p, forward pointer to " + "%p instead%s", what + 1, vp, ap, bp, bp->last, + ptr->pv, *((void **)ptr->pv), extra); + AALLOC_DENY(bp); + return (NULL); + } + *bpp = bp; + return (ptr); +} + +void +afree(void *vp, PArea ap) +{ + PBlock bp; + TPtr *ptr; + + if (vp == NULL) + return; + + /* validate allocation and backpointer, ignore rogues */ + if ((ptr = check_ptr(vp, ap, &bp, "afree", ", ignoring")) == NULL) + return; + + /* note: the block allocation does not ever shrink */ + bp->last -= PVALIGN; /* mark the last forward pointer as free */ + /* if our forward pointer was not the last one, relocate the latter */ + if (ptr->pv < bp->last) { + TPtr *tmp = bp->last; /* former last forward pointer */ + + tmp->pv = ptr->pv; /* its backpointer to former our … */ + tmp->iv ^= bp->cookie; /* … forward pointer, and cookie it */ + + memcpy(ptr->pv, bp->last, PVALIGN); /* relocate fwd ptr */ + } + ptr->iv = 0; /* our backpointer, just in case, for double frees */ + free(ptr); + + AALLOC_DENY(bp); + return; +} diff --git a/check.t b/check.t index 8d4a257..e69ebfd 100644 --- a/check.t +++ b/check.t @@ -1,4 +1,4 @@ -# $MirOS: src/bin/mksh/check.t,v 1.242 2008/11/10 19:33:07 tg Exp $ +# $MirOS: src/bin/mksh/check.t,v 1.243 2008/11/12 04:55:17 tg Exp $ # $OpenBSD: bksl-nl.t,v 1.2 2001/01/28 23:04:56 niklas Exp $ # $OpenBSD: history.t,v 1.5 2001/01/28 23:04:56 niklas Exp $ # $OpenBSD: read.t,v 1.3 2003/03/10 03:48:16 david Exp $ @@ -7,7 +7,7 @@ # http://www.research.att.com/~gsf/public/ifs.sh expected-stdout: - @(#)MIRBSD KSH R36 2008/11/10 + @(#)MIRBSD KSH R36 2008/11/11 description: Check version of shell. stdin: diff --git a/main.c b/main.c index b93f1d6..f5a25c5 100644 --- a/main.c +++ b/main.c @@ -13,7 +13,7 @@ #include #endif -__RCSID("$MirOS: src/bin/mksh/main.c,v 1.109 2008/11/12 00:54:49 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/main.c,v 1.110 2008/11/12 04:55:18 tg Exp $"); extern char **environ; @@ -76,6 +76,10 @@ main(int argc, const char *argv[]) char *cp; #endif +#if !HAVE_ARC4RANDOM + change_random((unsigned long)time(NULL) * getpid()); +#endif + /* make sure argv[] is sane */ if (!*argv) { static const char *empty_argv[] = { diff --git a/sh.h b/sh.h index a60d7ff..1e71432 100644 --- a/sh.h +++ b/sh.h @@ -103,9 +103,9 @@ #define __SCCSID(x) __IDSTRING(sccsid,x) #ifdef EXTERN -__RCSID("$MirOS: src/bin/mksh/sh.h,v 1.256 2008/11/12 00:55:32 tg Exp $"); +__RCSID("$MirOS: src/bin/mksh/sh.h,v 1.257 2008/11/12 04:55:19 tg Exp $"); #endif -#define MKSH_VERSION "R36 2008/11/10" +#define MKSH_VERSION "R36 2008/11/11" #ifndef MKSH_INCLUDES_ONLY