Correct largebin size lookups on i386 (#1623)

* Add largebin reverse lookup tables

* Don't use None value for bin 95 size on i386

* Clarify "bin 95" comment

* Add comment to tables

* Immutable tables

* Make tables class attributes
This commit is contained in:
CptGibbon 2023-03-15 12:29:55 -07:00 committed by GitHub
parent ed73d38f83
commit 64f4d6b6da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 218 additions and 46 deletions

View File

@ -645,6 +645,209 @@ class Arena:
class GlibcMemoryAllocator(pwndbg.heap.heap.MemoryAllocator):
# Largebin reverse lookup tables.
# These help determine the range of chunk sizes that each largebin can hold.
# They were generated by running every chunk size between the minimum & maximum large chunk
# sizes through largebin_index().
# Largebin 31 (bin 95) isn't used on i386 when MALLOC_ALIGNMENT is 16, so its value must be added manually.
largebin_reverse_lookup_32 = (
0x200,
0x240,
0x280,
0x2C0,
0x300,
0x340,
0x380,
0x3C0,
0x400,
0x440,
0x480,
0x4C0,
0x500,
0x540,
0x580,
0x5C0,
0x600,
0x640,
0x680,
0x6C0,
0x700,
0x740,
0x780,
0x7C0,
0x800,
0x840,
0x880,
0x8C0,
0x900,
0x940,
0x980,
0x9C0,
0xA00,
0xC00,
0xE00,
0x1000,
0x1200,
0x1400,
0x1600,
0x1800,
0x1A00,
0x1C00,
0x1E00,
0x2000,
0x2200,
0x2400,
0x2600,
0x2800,
0x2A00,
0x3000,
0x4000,
0x5000,
0x6000,
0x7000,
0x8000,
0x9000,
0xA000,
0x10000,
0x18000,
0x20000,
0x28000,
0x40000,
0x80000,
)
largebin_reverse_lookup_32_big = (
0x3F0,
0x400,
0x440,
0x480,
0x4C0,
0x500,
0x540,
0x580,
0x5C0,
0x600,
0x640,
0x680,
0x6C0,
0x700,
0x740,
0x780,
0x7C0,
0x800,
0x840,
0x880,
0x8C0,
0x900,
0x940,
0x980,
0x9C0,
0xA00,
0xA40,
0xA80,
0xAC0,
0xB00,
0xB40,
0xB80, # Largebin 31 (bin 95) is unused, but its size is used to calculate the previous bin's maximum chunk size.
0xB80,
0xC00,
0xE00,
0x1000,
0x1200,
0x1400,
0x1600,
0x1800,
0x1A00,
0x1C00,
0x1E00,
0x2000,
0x2200,
0x2400,
0x2600,
0x2800,
0x2A00,
0x3000,
0x4000,
0x5000,
0x6000,
0x7000,
0x8000,
0x9000,
0xA000,
0x10000,
0x18000,
0x20000,
0x28000,
0x40000,
0x80000,
)
largebin_reverse_lookup_64 = (
0x400,
0x440,
0x480,
0x4C0,
0x500,
0x540,
0x580,
0x5C0,
0x600,
0x640,
0x680,
0x6C0,
0x700,
0x740,
0x780,
0x7C0,
0x800,
0x840,
0x880,
0x8C0,
0x900,
0x940,
0x980,
0x9C0,
0xA00,
0xA40,
0xA80,
0xAC0,
0xB00,
0xB40,
0xB80,
0xBC0,
0xC00,
0xC40,
0xE00,
0x1000,
0x1200,
0x1400,
0x1600,
0x1800,
0x1A00,
0x1C00,
0x1E00,
0x2000,
0x2200,
0x2400,
0x2600,
0x2800,
0x2A00,
0x3000,
0x4000,
0x5000,
0x6000,
0x7000,
0x8000,
0x9000,
0xA000,
0x10000,
0x18000,
0x20000,
0x28000,
0x40000,
0x80000,
)
def __init__(self) -> None:
# Global ptmalloc objects
self._global_max_fast_addr: int = None
@ -658,17 +861,21 @@ class GlibcMemoryAllocator(pwndbg.heap.heap.MemoryAllocator):
# ptmalloc cache for current thread
self._thread_cache: gdb.Value = None
def largebin_size_range_from_index(self, index):
index += NSMALLBINS
spaces_table = self._spaces_table()
largest_largebin = self.largebin_index(pwndbg.gdblib.arch.ptrmask)
start_size = (NSMALLBINS * self.malloc_alignment) - self.malloc_alignment
def largebin_reverse_lookup(self, index):
"""Pick the appropriate largebin_reverse_lookup_ function for this architecture."""
if pwndbg.gdblib.arch.ptrsize == 8:
return self.largebin_reverse_lookup_64[index]
elif self.malloc_alignment == 16:
return self.largebin_reverse_lookup_32_big[index]
else:
return self.largebin_reverse_lookup_32[index]
for i in range(NSMALLBINS, index + 1):
start_size += spaces_table[i]
def largebin_size_range_from_index(self, index):
largest_largebin = self.largebin_index(pwndbg.gdblib.arch.ptrmask) - 64
start_size = self.largebin_reverse_lookup(index)
if index != largest_largebin:
end_size = start_size + spaces_table[index + 1] - self.malloc_alignment
end_size = self.largebin_reverse_lookup(index + 1) - self.malloc_alignment
else:
end_size = pwndbg.gdblib.arch.ptrmask
@ -806,33 +1013,6 @@ class GlibcMemoryAllocator(pwndbg.heap.heap.MemoryAllocator):
return self.minsize
return (req + self.size_sz + self.malloc_align_mask) & ~self.malloc_align_mask
def _spaces_table(self):
spaces_table = (
[self.malloc_alignment] * 64
+ [pow(2, 6)] * 32
+ [pow(2, 9)] * 16
+ [pow(2, 12)] * 8
+ [pow(2, 15)] * 4
+ [pow(2, 18)] * 2
+ [pow(2, 21)] * 1
)
# There is no index 0
spaces_table = [None] + spaces_table
# Fix up the slop in bin spacing (part of libc - they made
# the trade off of some slop for speed)
# https://bazaar.launchpad.net/~ubuntu-branches/ubuntu/trusty/eglibc/trusty-security/view/head:/malloc/malloc.c#L1356
if pwndbg.gdblib.arch.ptrsize == 8:
spaces_table[97] = 64
spaces_table[98] = 448
spaces_table[113] = 1536
spaces_table[121] = 24576
spaces_table[125] = 98304
return spaces_table
def chunk_flags(self, size):
return (
size & ptmalloc.PREV_INUSE,
@ -1025,12 +1205,9 @@ class GlibcMemoryAllocator(pwndbg.heap.heap.MemoryAllocator):
return result
def smallbins(self, arena_addr=None):
size = self.min_chunk_size - self.malloc_alignment
spaces_table = self._spaces_table()
size = self.min_chunk_size
result = Bins(BinType.SMALL)
for index in range(2, 64):
size += spaces_table[index]
chain = self.bin_at(index, arena_addr=arena_addr)
if chain is None:
@ -1038,24 +1215,19 @@ class GlibcMemoryAllocator(pwndbg.heap.heap.MemoryAllocator):
fd_chain, bk_chain, is_corrupted = chain
result.bins[size] = Bin(fd_chain, bk_chain, is_corrupted=is_corrupted)
size += self.malloc_alignment
return result
def largebins(self, arena_addr=None):
size = (ptmalloc.NSMALLBINS * self.malloc_alignment) - self.malloc_alignment
spaces_table = self._spaces_table()
result = Bins(BinType.LARGE)
for index in range(64, 127):
size += spaces_table[index]
chain = self.bin_at(index, arena_addr=arena_addr)
if chain is None:
return
fd_chain, bk_chain, is_corrupted = chain
result.bins[self.largebin_index(size) - NSMALLBINS] = Bin(
fd_chain, bk_chain, is_corrupted=is_corrupted
)
result.bins[index - NSMALLBINS] = Bin(fd_chain, bk_chain, is_corrupted=is_corrupted)
return result