[PATCH] malloc tcache: Debugger now sees the address of the corrupted chunk.
Adder
adder.thief@gmail.com
Sat Dec 12 05:18:05 GMT 2020
On Sat, Dec 12, 2020 at 5:17 AM DJ Delorie <dj@redhat.com> wrote:
> Hmm... OK, I think I get it. It's not the 'e' we know, its the 'e' from
> the previous call to tcache_get().
Yes ! I am so happy !
> So basically, when we remove a chunk from the tcache, we want to
> validate the pointer we're leaving behind?
Correct ! And the result is crash on the first malloc, not the second one.
This has the additional benefit of capturing (in the core file)
the illegally-overwritten-via-p1 contents of the chunk,
without further (legal) write results via p7 (which is equal to p1),
which may occur before the second malloc (which currently crashes).
> static __always_inline void *
> tcache_get (size_t tc_idx)
> {
> tcache_entry *e = tcache->entries[tc_idx];
> if (__glibc_unlikely (!aligned_OK (e)))
> malloc_printerr ("malloc(): unaligned tcache chunk detected");
> tcache->entries[tc_idx] = REVEAL_PTR (e->next);
> + /* Validate the pointer we're leaving behind, while we still know
> + where it came from, in case a use-after-free corrupted it. */
> + if (tcache->entries[tc_idx])
> + * (volatile char **) tcache->entries[tc_idx];
> --(tcache->counts[tc_idx]);
> e->key = NULL;
> return (void *) e;
> }
This looks cleaner and more efficient than my attempt.
Thank you for the "volatile" tip !
But I am getting:
malloc.c:2961:5: error: statement with no effect [-Werror=unused-value]
* (volatile char **) tcache->entries[tc_idx];
Even if I #pragma out the warning, no machine code seems to be generated
(at least for x86_64).
I would like to suggest:
static __always_inline void *
tcache_get (size_t tc_idx)
{
tcache_entry *e = tcache->entries[tc_idx];
if (__glibc_unlikely (!aligned_OK (e)))
malloc_printerr ("malloc(): unaligned tcache chunk detected");
- tcache->entries[tc_idx] = REVEAL_PTR (e->next);
+ tcache_entry *const e_next = REVEAL_PTR (e->next);
+
+ /* Validate the pointer we're leaving behind, while we still know
+ where it came from, in case a write-after-free has corrupted it.
+ The write operation (via pointer-to-volatile) serves two purposes:
+ it prevents the code from being optimized out
+ and it validates the pointer for writing (not just for reading). */
+ if (e_next)
+ ((volatile tcache_entry *) e_next)->next = e_next->next;
tcache->entries[tc_idx] = e_next;
--(tcache->counts[tc_idx]);
e->key = NULL;
return (void *) e;
}
It crashes as desired, and we can see the value of e in the core file.
The extra generated machine code is, again,
much simpler than my original version:
cmp r9, rdi
je @@new_label_1
mov rdi, [rax]
mov [rax], rdi
@@ new_label_1:
...
More information about the Libc-alpha
mailing list