Last active
December 1, 2017 20:44
-
-
Save philpennock/7227767 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// this is C99 | |
// compile: gcc -std=c99 -ggdb -O3 -Wall guarded_memory_alloc_test.c | |
#include <errno.h> | |
#include <fcntl.h> | |
#include <stdarg.h> | |
#include <stdint.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <unistd.h> | |
#include <sys/mman.h> | |
#ifdef __GNUC__ | |
#define PRINTF_FUNCTION(A,B) __attribute__((format(printf,A,B))) | |
#else | |
#define PRINTF_FUNCTION(A,B) /**/ | |
#endif | |
static void PRINTF_FUNCTION(2, 3) | |
trace_realwithloc(const char *loc, const char *fmt, ...) | |
{ | |
va_list ap; | |
fflush(NULL); | |
fprintf(stderr, "%s(): ", loc); | |
va_start(ap, fmt); | |
vfprintf(stderr, fmt, ap); | |
va_end(ap); | |
fputc('\n', stderr); | |
fflush(stderr); | |
} | |
#define trace(format, ...) do { \ | |
int saved_err = errno; \ | |
trace_realwithloc(__func__, format, ## __VA_ARGS__); \ | |
errno = saved_err; } while(0) | |
static void * | |
create_bounded_range(const size_t want) | |
{ | |
size_t buffered_want, request_sz; | |
int page_sz; | |
void *allocated, *using; | |
if (want < 1) { | |
errno = EINVAL; | |
return NULL; | |
} | |
page_sz = getpagesize(); | |
buffered_want = ((want + page_sz - 1) / page_sz) * page_sz; | |
request_sz = buffered_want + 2 * page_sz; | |
trace("want %#zx octets, page size %#x, padding to %#zx, requesting %#zx", | |
want, page_sz, buffered_want, request_sz); | |
allocated = mmap(NULL, request_sz, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0); | |
if (allocated == MAP_FAILED) { | |
trace("initial mmap(%zu) failed: %s", request_sz, strerror(errno)); | |
return NULL; | |
} | |
trace("allocated at %p", allocated); | |
using = allocated + page_sz; | |
// I originally used munmap() here but four years later, | |
// realized that this is buggy: if the memory is not allocated, then | |
// the mmap call is free to allocate it later. It might not _remain_ | |
// empty as a guard. | |
if (mprotect(allocated, page_sz, PROT_NONE) < 0) { | |
int se = errno; | |
trace("mprotect(first_page, PROT_NONE) failed: %s", strerror(errno)); | |
if (munmap(allocated, request_sz) < 0) { | |
trace("additionally, munmap(allocation) failed: %s", strerror(errno)); | |
} else { | |
trace("however, munmap(allocation) succeeded (is mprotect() not available?)"); | |
} | |
errno = se; | |
return NULL; | |
} | |
if (mprotect(using + buffered_want, page_sz, PROT_NONE) < 0) { | |
int se = errno; | |
trace("mprotect(last_page, PROT_NONE) failed: %s", strerror(se)); | |
if (munmap(allocated, request_sz) < 0) { | |
trace("additionally, munmap(allocation) failed: %s", strerror(errno)); | |
} | |
errno = se; | |
return NULL; | |
} | |
trace("have marked PROT_NONE first and last pages of %#x octets each", page_sz); | |
trace("returning %p", using); | |
return using; | |
} | |
int | |
main(int argc, char **argv) | |
{ | |
char *end; | |
int errcount = 0; | |
int start_tests = 1; | |
int last_allocated_index = -1; | |
size_t last_allocated_sz = 0; | |
int crash = 0; | |
void *got = NULL; // complaint might be used uninitialised is false | |
if (argc < 2) { | |
fprintf(stderr, "%s: need at least one size parameter\n", argv[0]); | |
return 1; | |
} | |
if (strcasecmp(argv[1], "crash") == 0) { | |
start_tests = 2; | |
crash = 1; | |
} | |
for (int i = start_tests; i < argc; ++i) { | |
if (*argv[i] == '\0') { | |
trace("skipping empty parameter #%d", i); | |
errcount++; | |
continue; | |
} | |
unsigned long ul; | |
ul = strtoul(argv[i], &end, 0); | |
if (*end == '\0') { | |
got = create_bounded_range((size_t)ul); | |
if (got == NULL) { | |
errcount++; | |
printf("%d: Allocation of %ld octets failed\n", i, ul); | |
} else { | |
printf("%d: Allocated space for %ld octets at %p\n", i, ul, got); | |
last_allocated_index = i; | |
last_allocated_sz = (size_t)ul; | |
} | |
} else { | |
errcount++; | |
trace("skipping parameter #%d because not numeric", i); | |
} | |
} | |
if (crash) { | |
if (last_allocated_index < 0) { | |
trace("no successful allocations, not forcing a crash"); | |
return 1; | |
} | |
int page_sz = getpagesize(); | |
size_t allocated = ((last_allocated_sz + page_sz - 1) / page_sz) * page_sz; | |
trace("will attempt to crash %#zx octet buffer allocated for size %#zx request", | |
allocated, last_allocated_sz); | |
trace("writing at [start] %p", got); | |
*(uint8_t*)got = 1; | |
trace("writing at [last] %p (%p + %#zx)", got+allocated-1, got, allocated-1); | |
*((uint8_t*)got+allocated-1) = 2; | |
trace("writing one past end (should crash) at %p (%p + %#zx)", got+allocated, got, allocated); | |
*((uint8_t*)got+allocated) = 3; | |
trace("oops, not crashed"); | |
return 2; | |
} | |
return (errcount == 0) ? 0 : 1; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment