Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

heap chunks does not work with (all) x86 executables #689

Closed
4 tasks done
theguy147 opened this issue Jul 28, 2021 · 5 comments · Fixed by #697
Closed
4 tasks done

heap chunks does not work with (all) x86 executables #689

theguy147 opened this issue Jul 28, 2021 · 5 comments · Fixed by #697

Comments

@theguy147
Copy link
Collaborator

theguy147 commented Jul 28, 2021

  • Did you use the latest version of GEF from dev branch?
  • Is your bug specific to GEF (not GDB)? - Try to reproduce it running gdb -nx
  • Did you read the documentation first?
  • Did you check issues (including
    the closed ones) - and the PR?

Step 1: Describe your environment

  • OS: Ubuntu 20.04 focal
  • Kernel: x86_64 Linux 5.4.0-80-generic
  • GEF version:
GEF: rev:77889d8d2307ffb1b6077a2dd2dbcf21a75df821 (Git - clean)
SHA1(/gef/rules/gef.py): 4b26a1175abcd8314d4816f97fdf908b3837c779
GDB: 9.2
GDB-Python: 3.8

Step 2: Describe your problem

This issue was identified in issue #646. It seems like the heap chunks command does not work with x86 executables (or at least not with all of them). The issue is that the first chunks address is miscalculated.

Steps to reproduce / Minimalist test case

// filename: heap.c
// compile with gcc -m32 -oheap32 heap.c
#include<stdlib.h>
#include<stdio.h>

int main() {
	int* ptr = malloc(0x100);
	printf("%p\n", ptr);
	return 0;
}
# filename: gdbscript
gef config context.enable False
start
break *__libc_malloc
continue
del break
up
break
continue
heap chunks
quit
gdb -q -x ./gdbscript ./heap32

Observed Results

No heap chunks in output (but also no error message)

image

Expected results

I expected heap chunks :)
This is the same source compiled for 64-bit with some nice heap chunks:

image

The Problem

self.address is not calculated the correct way here (L803):

gef/gef.py

Lines 799 to 810 in 48a9fd7

def __init__(self, addr, from_base=False):
self.ptrsize = current_arch.ptrsize
if from_base:
self.chunk_base_address = addr
self.address = addr + 2 * self.ptrsize
else:
self.chunk_base_address = int(addr - 2 * self.ptrsize)
self.address = addr
self.size_addr = int(self.address - self.ptrsize)
self.prev_size_addr = self.chunk_base_address
return

With this testcase the real heap chunk address is not at this address but at this address + 8. I'm not sure as to why this is - maybe because of some memory alignment or just simply the formula is not correct for x86 but only x86_64 and we need to make this calculation aware of the current architecture and adjust it accordingly.

@hugsy
Copy link
Owner

hugsy commented Aug 1, 2021

It seems it's slightly more complicated than just adding a if is_x86() in the code 😐 It's the libc itself that has changed:

  • on 16.04 -> works as expected with your c file (2.23-0ubuntu11.2)
    image

  • on 18.04 (libc-2.27-3ubuntu1.4) and 20.04 (2.31-0ubuntu9.2) I get the same result as you.

@hugsy
Copy link
Owner

hugsy commented Aug 1, 2021

Confirmed: with this ugly patch

diff --git a/gef.py b/gef.py
index f0aef0f..7134a13 100644
--- a/gef.py
+++ b/gef.py
@@ -799,14 +799,20 @@ class GlibcChunk:
     def __init__(self, addr, from_base=False):
         self.ptrsize = current_arch.ptrsize
         if from_base:
-            self.chunk_base_address = addr
-            self.address = addr + 2 * self.ptrsize
+            if get_libc_version() >= (2, 26) and is_x86_32(): # post-tcache on x86-32
+                self.chunk_base_address = addr + 8
+                self.address = addr + 8 + 2 * self.ptrsize
+            else:
+                self.chunk_base_address = addr
+                self.address = addr + 2 * self.ptrsize
+
         else:
             self.chunk_base_address = int(addr - 2 * self.ptrsize)
             self.address = addr

It works everywhere... I'd rather avoid this kind of ultra-specific cases so I'll look into a better solution, if you find any, please update.

@theguy147
Copy link
Collaborator Author

theguy147 commented Aug 3, 2021

I think you're right and the reason for this is glibc's MALLOC_ALIGNMENT constant that was changed for i386 in glibc version 2.26.

Before version 2.26 every supported linux architecture calculated it as follows:

/* The corresponding word size.  */
#define SIZE_SZ (sizeof (INTERNAL_SIZE_T))

/* MALLOC_ALIGNMENT is the minimum alignment for malloc'ed chunks.  It
   must be a power of two at least 2 * SIZE_SZ, even on machines for
   which smaller alignments would suffice. It may be defined as larger
   than this though. Note however that code and data structures are
   optimized for the case of 8-byte alignment.  */
#ifndef MALLOC_ALIGNMENT
# define MALLOC_ALIGNMENT (2 * SIZE_SZ < __alignof__ (long double) \
                           ? __alignof__ (long double) : 2 * SIZE_SZ)
#endif

(From: https://elixir.bootlin.com/glibc/glibc-2.25/source/malloc/malloc-internal.h#L67)

But since version 2.26 this snippet was moved to sysdeps/generic/malloc-alignment.h and a different version was introduced only for i386 in sysdeps/i386/malloc-alignment.h (without any comments to explain this change whatsoever!):

#define MALLOC_ALIGNMENT 16

(From: https://elixir.bootlin.com/glibc/glibc-2.26/source/sysdeps/i386/malloc-alignment.h#L22)

So unless this memory alignment constant is later written to some header somewhere in memory the only way to correctly handle this IMO is to do the check in your "ugly patch".

So because the main_arena has no heap_info and malloc_state structs in the heap memory (heap_info is a global var for main_arena) the chunks start right at the beginning of the page but because there is no previous chunk the first MALLOC_ALIGNMENT bytes are left empty (of course apart from the first chunk's header at the end of it) and the data for the first chunk then starts at offset MALLOC_ALIGNMENT of the page.

EDIT:
@hugsy This "kind of ultra-specific" case should also not happen in glibc without a good reason that should be commented IMO. But because they do it, it probably has to be done in GEF as well...

EDIT:
To clarify __alignof__ (long double) is 4 (not 16) on i386 (and sizeof(long double) is BTW 12 and not 16)

@hugsy
Copy link
Owner

hugsy commented Aug 3, 2021

@hugsy This "kind of ultra-specific" case should also not happen in glibc without a good reason that should be commented IMO. > But because they do it, it probably has to be done in GEF as well...

Fair point, I'll apply the patch and a comment to this issue since you did a good job investigating the root cause.

Cheers,

@theguy147
Copy link
Collaborator Author

otherwise i would have PR coming up that mimics Glibc's behavior to keep it in sync easier (and it's even commented ;) )

@hugsy hugsy linked a pull request Aug 4, 2021 that will close this issue
5 tasks
@theguy147 theguy147 added this to Needs triage in Functional Review of GEF (#693) via automation Sep 18, 2021
@theguy147 theguy147 moved this from Needs triage to Closed in Functional Review of GEF (#693) Sep 18, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
No open projects
Development

Successfully merging a pull request may close this issue.

2 participants