On 26/10/15 04:19 PM, Daniel Micay wrote:
> This is an improved revision of my earlier patch.
> 
> It now validates the junk data in the delayed_chunks array in an atexit 
> handler
> too, rather than just when allocations are swapped out.
> 
> It will now catch this simple UAF 100% of the time:
> 
> #include <stdlib.h>
> #include <stdio.h>
> 
> int main(void) {
>   size_t i;
>   char *p;
>   for (i = 0; i < 32; i++) {
>     p = malloc(16);
>     if (!p) return 1;
>   }
> 
>   p = malloc(16);
>   if (!p) return 1;
>   free(p);
>   *p = 5;
> 
>   for (i = 0; i < 4; i++) {
>     p = malloc(16);
>     if (!p) return 1;
>     free(p);
>   }
>   return 0;
> }
> 
> In general, it depends on the allocation still being in the delayed chunks
> array when the use-after-free happens. This means a larger delayed chunks
> array would improve the detection rate.

That revision was missing a NULL check as it got lost when refactoring
and the test cases weren't short enough to trigger it. Properly working
implementation:

diff --git a/stdlib/malloc.c b/stdlib/malloc.c
index 424dd77..c408594 100644
--- a/stdlib/malloc.c
+++ b/stdlib/malloc.c
@@ -182,6 +182,7 @@ struct malloc_readonly {
        int     malloc_freeunmap;       /* mprotect free pages PROT_NONE? */
        int     malloc_hint;            /* call madvice on free pages?  */
        int     malloc_junk;            /* junk fill? */
+       int     malloc_validate;        /* validate junk */
        int     malloc_move;            /* move allocations to end of page? */
        int     malloc_realloc;         /* always realloc? */
        int     malloc_xmalloc;         /* xmalloc behaviour? */
@@ -218,6 +219,8 @@ static void malloc_exit(void);
 #define CALLER NULL
 #endif

+static void validate_delayed_chunks(void);
+
 /* low bits of r->p determine size: 0 means >= page size and p->size
holding
  *  real size, otherwise r->size is a shift count, or 1 for malloc(0)
  */
@@ -560,6 +563,12 @@ omalloc_init(struct dir_info **dp)
                        case 'J':
                                mopts.malloc_junk = 2;
                                break;
+                       case 'v':
+                               mopts.malloc_validate = 0;
+                               break;
+                       case 'V':
+                               mopts.malloc_validate = 1;
+                               break;
                        case 'n':
                        case 'N':
                                break;
@@ -608,6 +617,9 @@ omalloc_init(struct dir_info **dp)
                }
        }

+       if (!mopts.malloc_junk)
+               mopts.malloc_validate = 0;
+
 #ifdef MALLOC_STATS
        if (mopts.malloc_stats && (atexit(malloc_exit) == -1)) {
                static const char q[] = "malloc() warning: atexit(2) failed."
@@ -616,6 +628,12 @@ omalloc_init(struct dir_info **dp)
        }
 #endif /* MALLOC_STATS */

+       if (mopts.malloc_validate && (atexit(validate_delayed_chunks) == -1)) {
+               static const char q[] = "malloc() warning: atexit(2) failed."
+                   " Will not be able to check for use after free\n";
+               write(STDERR_FILENO, q, sizeof(q) - 1);
+       }
+
        while ((mopts.malloc_canary = arc4random()) == 0)
                ;

@@ -1190,6 +1208,35 @@ malloc(size_t size)
 /*DEF_STRONG(malloc);*/

 static void
+validate_junk(void *p) {
+       struct region_info *r;
+       struct dir_info *pool = getpool();
+       size_t byte, sz;
+       if (p == NULL)
+               return;
+       r = find(pool, p);
+       if (r == NULL)
+               wrterror("bogus pointer in validate_junk", p);
+       REALSIZE(sz, r);
+       for (byte = 0; byte < sz; byte++) {
+               if (((char *)p)[byte] != SOME_FREEJUNK) {
+                       wrterror("use after free", p);
+                       return;
+               }
+       }
+}
+
+static void
+validate_delayed_chunks(void) {
+       struct dir_info *pool = getpool();
+       int i;
+       if (pool == NULL)
+               return;
+       for (i = 0; i < MALLOC_DELAYED_CHUNK_MASK + 1; i++)
+               validate_junk(pool->delayed_chunks[i]);
+}
+
+static void
 ofree(void *p)
 {
        struct dir_info *pool = getpool();
@@ -1253,6 +1300,8 @@ ofree(void *p)
                                wrterror("double free", p);
                                return;
                        }
+                       if (mopts.malloc_validate)
+                               validate_junk(p);
                        pool->delayed_chunks[i] = tmp;
                }
                if (p != NULL) {

Reply via email to