malloc: tcache double free check

* malloc/malloc.c (tcache_entry): Add key field.
(tcache_put): Set it.
(tcache_get): Likewise.
(_int_free): Check for double free in tcache.
* malloc/tst-tcfree1.c: New.
* malloc/tst-tcfree2.c: New.
* malloc/Makefile: Run the new tests.
* manual/probes.texi: Document memory_tcache_double_free probe.

* dlfcn/dlerror.c (check_free): Prevent double frees.
This commit is contained in:
DJ Delorie 2018-11-20 13:24:09 -05:00
parent 5770c0ad1e
commit bcdaad21d4
7 changed files with 148 additions and 1 deletions

View file

@ -1,3 +1,16 @@
2018-11-20 DJ Delorie <dj@redhat.com>
* malloc/malloc.c (tcache_entry): Add key field.
(tcache_put): Set it.
(tcache_get): Likewise.
(_int_free): Check for double free in tcache.
* malloc/tst-tcfree1.c: New.
* malloc/tst-tcfree2.c: New.
* malloc/Makefile: Run the new tests.
* manual/probes.texi: Document memory_tcache_double_free probe.
* dlfcn/dlerror.c (check_free): Prevent double frees.
2018-11-20 Wilco Dijkstra <wdijkstr@arm.com>
* sysdeps/aarch64/memset.S (MEMSET): Improve non-zero memset loop.

View file

@ -198,7 +198,10 @@ check_free (struct dl_action_result *rec)
Dl_info info;
if (_dl_addr (check_free, &info, &map, NULL) != 0 && map->l_ns == 0)
#endif
free ((char *) rec->errstring);
{
free ((char *) rec->errstring);
rec->errstring = NULL;
}
}
}

View file

@ -38,6 +38,7 @@ tests := mallocbug tst-malloc tst-valloc tst-calloc tst-obstack \
tst-malloc_info \
tst-malloc-too-large \
tst-malloc-stats-cancellation \
tst-tcfree1 tst-tcfree2 \
tests-static := \
tst-interpose-static-nothread \

View file

@ -2967,6 +2967,8 @@ mremap_chunk (mchunkptr p, size_t new_size)
typedef struct tcache_entry
{
struct tcache_entry *next;
/* This field exists to detect double frees. */
struct tcache_perthread_struct *key;
} tcache_entry;
/* There is one of these for each thread, which contains the
@ -2990,6 +2992,11 @@ tcache_put (mchunkptr chunk, size_t tc_idx)
{
tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
assert (tc_idx < TCACHE_MAX_BINS);
/* Mark this chunk as "in the tcache" so the test in _int_free will
detect a double free. */
e->key = tcache;
e->next = tcache->entries[tc_idx];
tcache->entries[tc_idx] = e;
++(tcache->counts[tc_idx]);
@ -3005,6 +3012,7 @@ tcache_get (size_t tc_idx)
assert (tcache->entries[tc_idx] > 0);
tcache->entries[tc_idx] = e->next;
--(tcache->counts[tc_idx]);
e->key = NULL;
return (void *) e;
}
@ -4218,6 +4226,26 @@ _int_free (mstate av, mchunkptr p, int have_lock)
{
size_t tc_idx = csize2tidx (size);
/* Check to see if it's already in the tcache. */
tcache_entry *e = (tcache_entry *) chunk2mem (p);
/* This test succeeds on double free. However, we don't 100%
trust it (it also matches random payload data at a 1 in
2^<size_t> chance), so verify it's not an unlikely coincidence
before aborting. */
if (__glibc_unlikely (e->key == tcache && tcache))
{
tcache_entry *tmp;
LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx);
for (tmp = tcache->entries[tc_idx];
tmp;
tmp = tmp->next)
if (tmp == e)
malloc_printerr ("free(): double free detected in tcache 2");
/* If we get here, it was a coincidence. We've wasted a few
cycles, but don't abort. */
}
if (tcache
&& tc_idx < mp_.tcache_bins
&& tcache->counts[tc_idx] < mp_.tcache_count)

42
malloc/tst-tcfree1.c Normal file
View file

@ -0,0 +1,42 @@
/* Test that malloc tcache catches double free.
Copyright (C) 2018 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<http://www.gnu.org/licenses/>. */
#include <errno.h>
#include <error.h>
#include <limits.h>
#include <malloc.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/signal.h>
static int
do_test (void)
{
/* Do one allocation of any size that fits in tcache. */
char * volatile x = malloc (32);
free (x); // puts in tcache
free (x); // should abort
printf("FAIL: tcache double free not detected\n");
return 1;
}
#define TEST_FUNCTION do_test
#define EXPECTED_SIGNAL SIGABRT
#include <support/test-driver.c>

48
malloc/tst-tcfree2.c Normal file
View file

@ -0,0 +1,48 @@
/* Test that malloc tcache catches double free.
Copyright (C) 2018 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<http://www.gnu.org/licenses/>. */
#include <errno.h>
#include <error.h>
#include <limits.h>
#include <malloc.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/signal.h>
static int
do_test (void)
{
char * volatile ptrs[20];
int i;
/* Allocate enough small chunks so that when we free them all, the tcache
is full, and the first one we freed is at the end of its linked list. */
#define COUNT 20
for (i=0; i<COUNT; i++)
ptrs[i] = malloc (20);
for (i=0; i<COUNT; i++)
free (ptrs[i]);
free (ptrs[0]);
printf("FAIL: tcache double free\n");
return 1;
}
#define TEST_FUNCTION do_test
#define EXPECTED_SIGNAL SIGABRT
#include <support/test-driver.c>

View file

@ -243,6 +243,18 @@ This probe is triggered when the
value of this tunable.
@end deftp
@deftp Probe memory_tcache_double_free (void *@var{$arg1}, int @var{$arg2})
This probe is triggered when @code{free} determines that the memory
being freed has probably already been freed, and resides in the
per-thread cache. Note that there is an extremely unlikely chance
that this probe will trigger due to random payload data remaining in
the allocated memory matching the key used to detect double frees.
This probe actually indicates that an expensive linear search of the
tcache, looking for a double free, has happened. Argument @var{$arg1}
is the memory location as passed to @code{free}, Argument @var{$arg2}
is the tcache bin it resides in.
@end deftp
@node Mathematical Function Probes
@section Mathematical Function Probes