prepare for aalloc.c, which I have just written myself, as an area-based

allocator using malloc and free, with mmap malloc and omalloc in mind,
not counterfeiting its security measures such as guard pages, and having
some of our own, e.g. XOR random cookies, optional mprotect, etc.

zero cost (for we have arc4random())
This commit is contained in:
tg 2008-11-12 04:55:19 +00:00
parent 8d5d720f08
commit 3c1e46ee4d
4 changed files with 479 additions and 5 deletions

470
aalloc.c Normal file
View File

@ -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;
}

View File

@ -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:

6
main.c
View File

@ -13,7 +13,7 @@
#include <locale.h>
#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[] = {

4
sh.h
View File

@ -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