From: Rusty Russell Date: Mon, 5 Dec 2011 12:16:46 +0000 (+1030) Subject: ptr_valid: test whether a ptr is valid. X-Git-Url: https://git.ozlabs.org/?p=ccan;a=commitdiff_plain;h=1086d8cef04f90f6ba74a35fdc9a44fe615518a8;ds=sidebyside ptr_valid: test whether a ptr is valid. Very slow, but sometimes you need to know without crashing. --- diff --git a/ccan/ptr_valid/LICENSE b/ccan/ptr_valid/LICENSE new file mode 120000 index 00000000..2354d129 --- /dev/null +++ b/ccan/ptr_valid/LICENSE @@ -0,0 +1 @@ +../../licenses/BSD-MIT \ No newline at end of file diff --git a/ccan/ptr_valid/_info b/ccan/ptr_valid/_info new file mode 100644 index 00000000..b577535f --- /dev/null +++ b/ccan/ptr_valid/_info @@ -0,0 +1,28 @@ +#include +#include "config.h" + +/** + * ptr_valid - test whether a pointer is safe to dereference. + * + * This little helper tells you if an address is mapped; it doesn't tell you + * if it's read-only (or execute only). + * + * License: BSD-MIT + * + * Ccanlint: + * // Our child actually crashes, but that's OK! + * tests_pass_valgrind test/run.c:--child-silent-after-fork=yes + */ +int main(int argc, char *argv[]) +{ + /* Expect exactly one argument */ + if (argc != 2) + return 1; + + if (strcmp(argv[1], "depends") == 0) { + printf("ccan/noerr\n"); + return 0; + } + + return 1; +} diff --git a/ccan/ptr_valid/ptr_valid.c b/ccan/ptr_valid/ptr_valid.c new file mode 100644 index 00000000..ef95d34f --- /dev/null +++ b/ccan/ptr_valid/ptr_valid.c @@ -0,0 +1,339 @@ +// Licensed under BSD-MIT: See LICENSE. +#include "ptr_valid.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if HAVE_PROC_SELF_MAPS +static char *grab(const char *filename) +{ + int ret, fd; + size_t max = 16384, s = 0; + char *buffer; + + fd = open(filename, O_RDONLY); + if (fd < 0) + return NULL; + + buffer = malloc(max+1); + if (!buffer) + goto close; + + while ((ret = read(fd, buffer + s, max - s)) > 0) { + s += ret; + if (s == max) { + buffer = realloc(buffer, max*2+1); + if (!buffer) + goto close; + max *= 2; + } + } + if (ret < 0) + goto free; + + close(fd); + buffer[s] = '\0'; + return buffer; + +free: + free(buffer); +close: + close_noerr(fd); + return NULL; +} + +static char *skip_line(char *p) +{ + char *nl = strchr(p, '\n'); + if (!nl) + return NULL; + return nl + 1; +} + +static struct ptr_valid_map *add_map(struct ptr_valid_map *map, + unsigned int *num, + unsigned int *max, + unsigned long start, unsigned long end, bool is_write) +{ + if (*num == *max) { + *max *= 2; + map = realloc(map, sizeof(*map) * *max); + if (!map) + return NULL; + } + map[*num].start = (void *)start; + map[*num].end = (void *)end; + map[*num].is_write = is_write; + (*num)++; + return map; +} + +static struct ptr_valid_map *get_proc_maps(unsigned int *num) +{ + char *buf, *p; + struct ptr_valid_map *map; + unsigned int max = 16; + + buf = grab("/proc/self/maps"); + if (!buf) { + *num = 0; + return NULL; + } + + map = malloc(sizeof(*map) * max); + if (!map) + goto free_buf; + + *num = 0; + for (p = buf; p && *p; p = skip_line(p)) { + unsigned long start, end; + char *endp; + + /* Expect '- rw... */ + start = strtoul(p, &endp, 16); + if (*endp != '-') + goto malformed; + end = strtoul(endp+1, &endp, 16); + if (*endp != ' ') + goto malformed; + + endp++; + if (endp[0] != 'r' && endp[0] != '-') + goto malformed; + if (endp[1] != 'w' && endp[1] != '-') + goto malformed; + + /* We only add readable mappings. */ + if (endp[0] == 'r') { + map = add_map(map, num, &max, start, end, + endp[1] == 'w'); + if (!map) + goto free_buf; + } + } + + free(buf); + return map; + + +malformed: + free(map); +free_buf: + free(buf); + *num = 0; + return NULL; +} +#else +static struct ptr_valid_map *get_proc_maps(unsigned int *num) +{ + *num = 0; + return NULL; +} +#endif + +static bool check_with_maps(struct ptr_valid_batch *batch, + const char *p, size_t size, bool is_write) +{ + unsigned int i; + + for (i = 0; i < batch->num_maps; i++) { + if (p >= batch->maps[i].start && p < batch->maps[i].end) { + /* Overlap into other maps? Recurse with remainder. */ + if (p + size > batch->maps[i].end) { + size_t len = p + size - batch->maps[i].end; + if (!check_with_maps(batch, batch->maps[i].end, + len, is_write)) + return false; + } + return !is_write || batch->maps[i].is_write; + } + } + return false; +} + +static void finish_child(struct ptr_valid_batch *batch) +{ + close(batch->to_child); + close(batch->from_child); + waitpid(batch->child_pid, NULL, 0); + batch->child_pid = 0; +} + +static bool child_alive(struct ptr_valid_batch *batch) +{ + return batch->child_pid != 0; +} + +static void run_child(int infd, int outfd) +{ + volatile char *p; + + /* This is how we expect to exit. */ + while (read(infd, &p, sizeof(p)) == sizeof(p)) { + size_t i, size; + bool is_write; + char ret = 0; + + /* This is weird. */ + if (read(infd, &size, sizeof(size)) != sizeof(size)) + exit(1); + if (read(infd, &is_write, sizeof(is_write)) != sizeof(is_write)) + exit(2); + + for (i = 0; i < size; i++) { + ret = p[i]; + if (is_write) + p[i] = ret; + } + + /* If we're still here, the answer is "yes". */ + if (write(outfd, &ret, 1) != 1) + exit(3); + } + exit(0); +} + +static bool create_child(struct ptr_valid_batch *batch) +{ + int outpipe[2], inpipe[2]; + + if (pipe(outpipe) != 0) + return false; + if (pipe(inpipe) != 0) + goto close_outpipe; + + fflush(stdout); + batch->child_pid = fork(); + if (batch->child_pid == 0) { + close(outpipe[1]); + close(inpipe[0]); + run_child(outpipe[0], inpipe[1]); + } + + if (batch->child_pid == -1) + goto cleanup_pid; + + close(outpipe[0]); + close(inpipe[1]); + + batch->to_child = outpipe[1]; + batch->from_child = inpipe[0]; + return true; + +cleanup_pid: + batch->child_pid = 0; + close_noerr(inpipe[0]); + close_noerr(inpipe[1]); +close_outpipe: + close_noerr(outpipe[0]); + close_noerr(outpipe[1]); + return false; +} + +static bool check_with_child(struct ptr_valid_batch *batch, + const void *p, size_t size, bool is_write) +{ + char ret; + + if (!child_alive(batch)) { + if (!create_child(batch)) + return false; + } + + write(batch->to_child, &p, sizeof(p)); + write(batch->to_child, &size, sizeof(size)); + write(batch->to_child, &is_write, sizeof(is_write)); + + if (read(batch->from_child, &ret, sizeof(ret)) != sizeof(ret)) { + finish_child(batch); + errno = EFAULT; + return false; + } + return true; +} + +/* msync seems most well-defined test, but page could be mapped with + * no permissions, and can't distiguish readonly from writable. */ +bool ptr_valid_batch(struct ptr_valid_batch *batch, + const void *p, size_t alignment, size_t size, bool write) +{ + char *start, *end; + bool ret; + + if ((intptr_t)p & (alignment - 1)) + return false; + + start = (void *)((intptr_t)p & ~(getpagesize() - 1)); + end = (void *)(((intptr_t)p + size - 1) & ~(getpagesize() - 1)); + + /* We cache single page hits. */ + if (start == end) { + if (batch->last && batch->last == start) + return batch->last_ok; + } + + if (batch->num_maps) + ret = check_with_maps(batch, p, size, write); + else + ret = check_with_child(batch, p, size, write); + + if (start == end) { + batch->last = start; + batch->last_ok = ret; + } + + return ret; +} + +bool ptr_valid_batch_string(struct ptr_valid_batch *batch, const char *p) +{ + while (ptr_valid_batch(batch, p, 1, 1, false)) { + if (*p == '\0') + return true; + p++; + } + return false; +} + +bool ptr_valid(const void *p, size_t alignment, size_t size, bool write) +{ + bool ret; + struct ptr_valid_batch batch; + if (!ptr_valid_batch_start(&batch)) + return false; + ret = ptr_valid_batch(&batch, p, alignment, size, write); + ptr_valid_batch_end(&batch); + return ret; +} + +bool ptr_valid_string(const char *p) +{ + bool ret; + struct ptr_valid_batch batch; + if (!ptr_valid_batch_start(&batch)) + return false; + ret = ptr_valid_batch_string(&batch, p); + ptr_valid_batch_end(&batch); + return ret; +} + +bool ptr_valid_batch_start(struct ptr_valid_batch *batch) +{ + batch->child_pid = 0; + batch->maps = get_proc_maps(&batch->num_maps); + batch->last = NULL; + return true; +} + +void ptr_valid_batch_end(struct ptr_valid_batch *batch) +{ + if (child_alive(batch)) + finish_child(batch); + free(batch->maps); +} diff --git a/ccan/ptr_valid/ptr_valid.h b/ccan/ptr_valid/ptr_valid.h new file mode 100644 index 00000000..3871cad8 --- /dev/null +++ b/ccan/ptr_valid/ptr_valid.h @@ -0,0 +1,229 @@ +// Licensed under BSD-MIT: See LICENSE. +#ifndef CCAN_PTR_VALID_H +#define CCAN_PTR_VALID_H +#include "config.h" +#include +#include + +/** + * ptr_valid_read - can I safely read from a pointer? + * @p: the proposed pointer. + * + * This function verifies that the pointer @p is safe to dereference for + * reading. It is very slow, particularly if the answer is "no". + * + * Sets errno to EFAULT on failure. + * + * See Also: + * ptr_valid_batch_read() + */ +#define ptr_valid_read(p) \ + ptr_valid_r((p), PTR_VALID_ALIGNOF(*(p)), sizeof(*(p))) + +/** + * ptr_valid_write - can I safely write to a pointer? + * @p: the proposed pointer. + * + * This function verifies that the pointer @p is safe to dereference + * for writing (and reading). It is very slow, particularly if the + * answer is "no". + * + * Sets errno to EFAULT on failure. + * + * See Also: + * ptr_valid_batch_write() + */ +#define ptr_valid_write(p) \ + ptr_valid_w((p), PTR_VALID_ALIGNOF(*(p)), sizeof(*(p))) + +/** + * ptr_valid_string - can I safely read a string? + * @p: the proposed string. + * + * This function verifies that the pointer @p is safe to dereference + * up to a nul character. It is very slow, particularly if the answer + * is "no". + * + * Sets errno to EFAULT on failure. + * + * See Also: + * ptr_valid_batch_string() + */ +bool ptr_valid_string(const char *p); + +/** + * ptr_valid - generic pointer check function + * @p: the proposed pointer. + * @align: the alignment requirements of the pointer. + * @size: the size of the region @p should point to + * @write: true if @p should be writable as well as readable. + * + * This function verifies that the pointer @p is safe to dereference. + * It is very slow, particularly if the answer is "no". + * + * Sets errno to EFAULT on failure. + * + * See Also: + * ptr_valid_batch() + */ +bool ptr_valid(const void *p, size_t align, size_t size, bool write); + +/** + * struct ptr_valid_batch - pointer to store state for batch ptr ops + * + * Treat as private. + */ +struct ptr_valid_batch { + unsigned int num_maps; + struct ptr_valid_map *maps; + int child_pid; + int to_child, from_child; + void *last; + bool last_ok; +}; + +/** + * ptr_valid_batch_start - prepare for a batch of ptr_valid checks. + * @batch: an uninitialized ptr_valid_batch structure. + * + * This initializes @batch; this same @batch pointer can be reused + * until the memory map changes (eg. via mmap(), munmap() or even + * malloc() and free()). + * + * This is useful to check many pointers, because otherwise it can be + * extremely slow. + * + * Example: + * struct linked { + * struct linked *next; + * const char *str; + * }; + * + * static bool check_linked_carefully(struct linked *head) + * { + * struct ptr_valid_batch batch; + * struct linked *old = head; + * bool half = true; + * + * // If this fails, we can't check. Assume OK. + * if (!ptr_valid_batch_start(&batch)) + * return true; + * + * while (head) { + * if (!ptr_valid_batch_read(&batch, head)) + * goto fail; + * if (!ptr_valid_batch_string(&batch, head->str)) + * goto fail; + * // Loop detection; move old at half speed of head. + * if (half) + * old = old->next; + * half = !half; + * if (head == old) { + * errno = ELOOP; + * goto fail; + * } + * } + * ptr_valid_batch_end(&batch); + * return true; + * + * fail: + * ptr_valid_batch_end(&batch); + * return false; + * } + * + * See Also: + * ptr_valid_batch_stop() + */ +bool ptr_valid_batch_start(struct ptr_valid_batch *batch); + +/** + * ptr_valid_batch_read - can I safely read from a pointer? + * @batch: the batch initialized by ptr_valid_batch_start(). + * @p: the proposed pointer. + * + * Batched version of ptr_valid_read(). + */ +#define ptr_valid_batch_read(batch, p) \ + ptr_valid_batch_r((batch), \ + (p), PTR_VALID_ALIGNOF(*(p)), sizeof(*(p))) + +/** + * ptr_valid_batch_write - can I safely write to a pointer? + * @batch: the batch initialized by ptr_valid_batch_start(). + * @p: the proposed pointer. + * + * Batched version of ptr_valid_write(). + */ +#define ptr_valid_batch_write(batch, p) \ + ptr_valid_batch_w((batch), \ + (p), PTR_VALID_ALIGNOF(*(p)), sizeof(*(p))) + +/** + * ptr_valid_batch_string - can I safely read a string? + * @batch: the batch initialized by ptr_valid_batch_start(). + * @p: the proposed string. + * + * Batched version of ptr_valid_string(). + */ +bool ptr_valid_batch_string(struct ptr_valid_batch *batch, const char *p); + +/** + * ptr_valid_batch - generic batched pointer check function + * @batch: the batch initialized by ptr_valid_batch_start(). + * @p: the proposed pointer. + * @align: the alignment requirements of the pointer. + * @size: the size of the region @p should point to + * @write: true if @p should be writable as well as readable. + * + * Batched version of ptr_valid(). + */ +bool ptr_valid_batch(struct ptr_valid_batch *batch, + const void *p, size_t alignment, size_t size, bool write); + +/** + * ptr_valid_batch_end - end a batch of ptr_valid checks. + * @batch: a ptr_valid_batch structure. + * + * This is used after all checks are complete. + * + * See Also: + * ptr_valid_batch_start() + */ +void ptr_valid_batch_end(struct ptr_valid_batch *batch); + + +/* These wrappers get constness correct. */ +static inline bool ptr_valid_r(const void *p, size_t align, size_t size) +{ + return ptr_valid(p, align, size, false); +} + +static inline bool ptr_valid_w(void *p, size_t align, size_t size) +{ + return ptr_valid(p, align, size, true); +} + +static inline bool ptr_valid_batch_r(struct ptr_valid_batch *batch, + const void *p, size_t align, size_t size) +{ + return ptr_valid_batch(batch, p, align, size, false); +} + +static inline bool ptr_valid_batch_w(struct ptr_valid_batch *batch, + void *p, size_t align, size_t size) +{ + return ptr_valid_batch(batch, p, align, size, true); +} + +struct ptr_valid_map { + const char *start, *end; + bool is_write; +}; + +#if HAVE_ALIGNOF +#define PTR_VALID_ALIGNOF(var) __alignof__(var) +#else +/* Can't check this... */ +#define PTR_VALID_ALIGNOF(var) 1 +#endif +#endif /* CCAN_PTR_VALID_H */ diff --git a/ccan/ptr_valid/test/run-string.c b/ccan/ptr_valid/test/run-string.c new file mode 100644 index 00000000..59d36a44 --- /dev/null +++ b/ccan/ptr_valid/test/run-string.c @@ -0,0 +1,56 @@ +#include +/* Include the C files directly. */ +#include +#include +#include + +int main(void) +{ + char *page; + struct ptr_valid_batch *batch = malloc(sizeof *batch); + + /* This is how many tests you plan to run */ + plan_tests(14); + + page = mmap(NULL, getpagesize(), PROT_READ|PROT_WRITE, + MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); + strcpy(page, "hello"); + ok1(ptr_valid_read(page)); + ok1(ptr_valid_write(page)); + ok1(ptr_valid_string(page)); + + ok1(ptr_valid_batch_start(batch)); + ok1(ptr_valid_batch_string(batch, page)); + ptr_valid_batch_end(batch); + + /* Check invalid case. */ + munmap(page, getpagesize()); + ok1(!ptr_valid_string(page)); + + ok1(ptr_valid_batch_start(batch)); + ok1(!ptr_valid_batch_string(batch, page)); + ptr_valid_batch_end(batch); + + /* Check for overrun. */ + page = mmap(NULL, getpagesize()*2, PROT_READ|PROT_WRITE, + MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); + munmap(page + getpagesize(), getpagesize()); + + memset(page, 'a', getpagesize()); + ok1(!ptr_valid_string(page)); + ok1(ptr_valid_batch_start(batch)); + ok1(!ptr_valid_batch_string(batch, page)); + ptr_valid_batch_end(batch); + + page[getpagesize()-1] = '\0'; + ok1(ptr_valid_string(page)); + + ok1(ptr_valid_batch_start(batch)); + ok1(ptr_valid_batch_string(batch, page)); + ptr_valid_batch_end(batch); + munmap(page, getpagesize()); + + free(batch); + /* This exits depending on whether all tests passed */ + return exit_status(); +} diff --git a/ccan/ptr_valid/test/run.c b/ccan/ptr_valid/test/run.c new file mode 100644 index 00000000..c624aff5 --- /dev/null +++ b/ccan/ptr_valid/test/run.c @@ -0,0 +1,90 @@ +#include +/* Include the C files directly. */ +#include +#include +#include + +static bool check_batch(char *p, unsigned int num, bool expect) +{ + struct ptr_valid_batch batch; + unsigned int i; + + if (!ptr_valid_batch_start(&batch)) + return false; + for (i = 0; i < num; i++) { + if (ptr_valid_batch(&batch, p + i, 1, 1, false) != expect) + return false; + if (ptr_valid_batch(&batch, p + i, 1, 1, true) != expect) + return false; + } + ptr_valid_batch_end(&batch); + return true; +} + +int main(void) +{ + char *page; + + /* This is how many tests you plan to run */ + plan_tests(30); + + page = mmap(NULL, getpagesize(), PROT_READ|PROT_WRITE, + MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); + ok1(ptr_valid_read(page)); + ok1(ptr_valid_write(page)); + ok1(ptr_valid(page, 1, getpagesize(), false)); + ok1(ptr_valid(page, 1, getpagesize(), true)); + + /* Test alignment constraints. */ + ok1(ptr_valid(page, getpagesize(), getpagesize(), false)); + ok1(ptr_valid(page, getpagesize(), getpagesize(), true)); + ok1(!ptr_valid(page+1, getpagesize(), 1, false)); + ok1(!ptr_valid(page+1, getpagesize(), 1, true)); + + /* Test batch. */ + ok1(check_batch(page, getpagesize(), true)); + + /* Unmap, all should fail. */ + munmap(page, getpagesize()); + ok1(!ptr_valid_read(page)); + ok1(!ptr_valid_write(page)); + ok1(!ptr_valid(page, 1, getpagesize(), false)); + ok1(!ptr_valid(page, 1, getpagesize(), true)); + + /* Test alignment constraints. */ + ok1(!ptr_valid(page, getpagesize(), getpagesize(), false)); + ok1(!ptr_valid(page, getpagesize(), getpagesize(), true)); + ok1(!ptr_valid(page+1, getpagesize(), 1, false)); + ok1(!ptr_valid(page, getpagesize(), 1, true)); + + /* Test batch (slow, since each fails, so reduce count). */ + ok1(check_batch(page, 4, false)); + + /* Check read-only */ + page = mmap(NULL, getpagesize(), PROT_READ, + MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); + ok1(ptr_valid_read(page)); + ok1(!ptr_valid_write(page)); + ok1(ptr_valid(page, 1, getpagesize(), false)); + ok1(!ptr_valid(page, 1, getpagesize(), true)); + + /* Test alignment constraints. */ + ok1(ptr_valid(page, getpagesize(), getpagesize(), false)); + ok1(!ptr_valid(page, getpagesize(), getpagesize(), true)); + ok1(!ptr_valid(page+1, getpagesize(), 1, false)); + ok1(!ptr_valid(page+1, getpagesize(), 1, true)); + munmap(page, getpagesize()); + + /* Check for overrun. */ + page = mmap(NULL, getpagesize()*2, PROT_READ|PROT_WRITE, + MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); + munmap(page + getpagesize(), getpagesize()); + + ok1(ptr_valid(page, 1, getpagesize(), false)); + ok1(ptr_valid(page, 1, getpagesize(), true)); + ok1(!ptr_valid(page, 1, getpagesize()+1, false)); + ok1(!ptr_valid(page, 1, getpagesize()+1, true)); + + /* This exits depending on whether all tests passed */ + return exit_status(); +}