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 commands: get_file called with incorrect path #2659

Open
sasha-999 opened this issue Dec 31, 2024 · 3 comments
Open

heap commands: get_file called with incorrect path #2659

sasha-999 opened this issue Dec 31, 2024 · 3 comments
Labels

Comments

@sasha-999
Copy link

Description

╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ /home/sasha/gdb/pwndbg_2025/pwndbg/commands/__init__.py:183 in __call__                          │
│                                                                                                  │
│   180 │                                                                                          │
│   181 │   def __call__(self, *args: Any, **kwargs: Any) -> str | None:                           │
│   182 │   │   try:                                                                               │
│ ❱ 183 │   │   │   return self.function(*args, **kwargs)                                          │
│   184 │   │   except TypeError:                                                                  │
│   185 │   │   │   print(f"{self.function.__name__.strip()!r}: {self.function.__doc__.strip()}"   │
│   186 │   │   │   pwndbg.exception.handle(self.function.__name__)                                │
│                                                                                                  │
│ /home/sasha/gdb/pwndbg_2025/pwndbg/commands/__init__.py:514 in _OnlyWithResolvedHeapSyms         │
│                                                                                                  │
│   511 │   │   │   ):                                                                             │
│   512 │   │   │   │   # In auto mode, if the debug symbols are not enough, we will try to use    │
│   513 │   │   │   │   heuristic_heap = HeuristicHeap()                                           │
│ ❱ 514 │   │   │   │   if heuristic_heap.can_be_resolved():                                       │
│   515 │   │   │   │   │   pwndbg.aglib.heap.current = heuristic_heap                             │
│   516 │   │   │   │   │   w(                                                                     │
│   517 │   │   │   │   │   │   "pwndbg will try to resolve the heap symbols via heuristic now s   │
│                                                                                                  │
│ /home/sasha/gdb/pwndbg_2025/pwndbg/aglib/heap/ptmalloc.py:1734 in can_be_resolved                │
│                                                                                                  │
│   1731 │   │   return self._structs_module                                                       │
│   1732 │                                                                                         │
│   1733 │   def can_be_resolved(self) -> bool:                                                    │
│ ❱ 1734 │   │   return self.struct_module is not None                                             │
│   1735 │                                                                                         │
│   1736 │   @property                                                                             │
│   1737 │   def main_arena(self) -> Arena | None:                                                 │
│                                                                                                  │
│ /home/sasha/gdb/pwndbg_2025/pwndbg/aglib/heap/ptmalloc.py:1724 in struct_module                  │
│                                                                                                  │
│   1721 │                                                                                         │
│   1722 │   @property                                                                             │
│   1723 │   def struct_module(self) -> types.ModuleType | None:                                   │
│ ❱ 1724 │   │   if not self._structs_module and pwndbg.glibc.get_version():                       │
│   1725 │   │   │   try:                                                                          │
│   1726 │   │   │   │   self._structs_module = importlib.reload(                                  │
│   1727 │   │   │   │   │   importlib.import_module("pwndbg.aglib.heap.structs")                  │
│                                                                                                  │
│ /home/sasha/gdb/pwndbg_2025/pwndbg/aglib/proc.py:145 in wrapper                                  │
│                                                                                                  │
│   142 │   │   @functools.wraps(func)                                                             │
│   143 │   │   def wrapper(*a: P.args, **kw: P.kwargs) -> T | None:                               │
│   144 │   │   │   if self.alive:                                                                 │
│ ❱ 145 │   │   │   │   return func(*a, **kw)                                                      │
│   146 │   │   │   return None                                                                    │
│   147 │   │                                                                                      │
│   148 │   │   return wrapper                                                                     │
│                                                                                                  │
│ /home/sasha/gdb/pwndbg_2025/pwndbg/glibc.py:63 in get_version                                    │
│                                                                                                  │
│    60                                                                                            │
│    61 @pwndbg.aglib.proc.OnlyWhenRunning                                                         │
│    62 def get_version() -> Tuple[int, ...] | None:                                               │
│ ❱  63 │   return cast(Union[Tuple[int, ...], None], glibc_version) or _get_version()             │
│    64                                                                                            │
│    65                                                                                            │
│    66 @pwndbg.aglib.proc.OnlyWhenRunning                                                         │
│                                                                                                  │
│ /home/sasha/gdb/pwndbg_2025/pwndbg/aglib/proc.py:145 in wrapper                                  │
│                                                                                                  │
│   142 │   │   @functools.wraps(func)                                                             │
│   143 │   │   def wrapper(*a: P.args, **kw: P.kwargs) -> T | None:                               │
│   144 │   │   │   if self.alive:                                                                 │
│ ❱ 145 │   │   │   │   return func(*a, **kw)                                                      │
│   146 │   │   │   return None                                                                    │
│   147 │   │                                                                                      │
│   148 │   │   return wrapper                                                                     │
│                                                                                                  │
│ /home/sasha/gdb/pwndbg_2025/pwndbg/lib/cache.py:162 in decorator                                 │
│                                                                                                  │
│   159 │   │   │   │   if cached_value is not _NOT_FOUND_IN_CACHE:                                │
│   160 │   │   │   │   │   return cached_value                                                    │
│   161 │   │   │   │                                                                              │
│ ❱ 162 │   │   │   │   value = func(*a, **kw)                                                     │
│   163 │   │   │   │                                                                              │
│   164 │   │   │   │   # Sanity check: its not perfect and won't cover all cases like ([],)       │
│   165 │   │   │   │   # but it should be good enough                                             │
│                                                                                                  │
│ /home/sasha/gdb/pwndbg_2025/pwndbg/glibc.py:81 in _get_version                                   │
│                                                                                                  │
│    78 │   libc_filename = get_libc_filename_from_info_sharedlibrary()                            │
│    79 │   if not libc_filename:                                                                  │
│    80 │   │   return None                                                                        │
│ ❱  81 │   result = pwndbg.aglib.elf.dump_section_by_name(libc_filename, ".rodata", try_local_p   │
│    82 │   if result is None:                                                                     │
│    83 │   │   return None                                                                        │
│    84 │   _, _, data = result                                                                    │
│                                                                                                  │
│ /home/sasha/gdb/pwndbg_2025/pwndbg/aglib/elf.py:214 in dump_section_by_name                      │
│                                                                                                  │
│   211 │   Dump the content of a section from an ELF file, return the start address, size and c   │
│   212 │   """                                                                                    │
│   213 │   # TODO: We should have some cache mechanism or something at `pndbg.aglib.file.get_fi   │
│ ❱ 214 │   local_path = pwndbg.aglib.file.get_file(filepath, try_local_path=try_local_path)       │
│   215 │                                                                                          │
│   216 │   with open(local_path, "rb") as f:                                                      │
│   217 │   │   elffile = ELFFile(f)                                                               │
│                                                                                                  │
│ /home/sasha/gdb/pwndbg_2025/pwndbg/aglib/file.py:85 in get_file                                  │
│                                                                                                  │
│    82 │   Returns:                                                                               │
│    83 │   │   The local path to the file                                                         │
│    84 │   """                                                                                    │
│ ❱  85 │   assert path.startswith(("/", "./", "../")) or path.startswith(                         │
│    86 │   │   "target:"                                                                          │
│    87 │   ), "get_file called with incorrect path"                                               │
│    88                                                                                            │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
AssertionError: get_file called with incorrect path

When using any heap command, such as vis_heap_chunks, when determining the glibc version pwndbg may fail to get the version when the glibc is stripped.

Steps to reproduce

The binary needs to be patched such that the path for the libc is relative and doesn't begin with ./, such as glibc/libc.so.6:

$ readelf -Wd ./hello

Dynamic section at offset 0x2de0 contains 26 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [glibc/libc.so.6]
 0x000000000000000c (INIT)               0x1000
 0x000000000000000d (FINI)               0x1154
 0x0000000000000019 (INIT_ARRAY)         0x3dd0
 0x000000000000001b (INIT_ARRAYSZ)       8 (bytes)
 0x000000000000001a (FINI_ARRAY)         0x3dd8
 0x000000000000001c (FINI_ARRAYSZ)       8 (bytes)
 0x000000006ffffef5 (GNU_HASH)           0x50a8
 0x0000000000000005 (STRTAB)             0x6000
 0x0000000000000006 (SYMTAB)             0x5000
 0x000000000000000a (STRSZ)              157 (bytes)
 0x000000000000000b (SYMENT)             24 (bytes)
 0x0000000000000015 (DEBUG)              0x0
 0x0000000000000003 (PLTGOT)             0x3fe8
 0x0000000000000002 (PLTRELSZ)           24 (bytes)
 0x0000000000000014 (PLTREL)             RELA
 0x0000000000000017 (JMPREL)             0x600
 0x0000000000000007 (RELA)               0x540
 0x0000000000000008 (RELASZ)             192 (bytes)
 0x0000000000000009 (RELAENT)            24 (bytes)
 0x000000006ffffffb (FLAGS_1)            Flags: PIE
 0x000000006ffffffe (VERNEED)            0x510
 0x000000006fffffff (VERNEEDNUM)         1
 0x000000006ffffff0 (VERSYM)             0x4fe
 0x000000006ffffff9 (RELACOUNT)          3
 0x0000000000000000 (NULL)               0x0

This way, info sharedlibrary returns the following:

pwndbg> info sharedlibrary
From                To                  Syms Read   Shared Object Library
0x00007ffff7fc5090  0x00007ffff7fee335  Yes (*)     glibc/ld-2.35.so
0x00007ffff7dbb700  0x00007ffff7f4dabd  Yes (*)     glibc/libc.so.6
(*): Shared library is missing debugging information.

The libc also needs to be stripped, so that the version can't be found using the __libc_version symbol.

$ file glibc/libc.so.6 
glibc/libc.so.6: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=69389d485a9793dbe873f0ea2c93e02efaa9aa3d, for GNU/Linux 3.2.0, stripped

$ strings glibc/libc.so.6 | grep "GNU C Library"
GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3.1) stable release version 2.35.

$ md5sum glibc/libc.so.6
3d7240354d70ebbd11911187f1acd6e8  glibc/libc.so.6

Then when debugging the program, use a heap command like vis_heap_chunks, and it will fail to get the libc.

My setup

Platform: Linux-5.18.0-14parrot1-amd64-x86_64-with-glibc2.36
OS: Parrot Security 6.0 (lorikeet)
OS ABI: #1 SMP PREEMPT_DYNAMIC Debian 5.18.14-1parrot1 (2022-08-07)
Architecture: x86_64
Endian: little
Charset: utf-8
Width: 213
Height: 42
Gdb:      13.1
Python:   3.11.2 (main, Mar 13 2023, 12:18:29) [GCC 12.2.0]
Pwndbg:   2024.08.29 build: ad90ec1a
Capstone: 5.0.1280
Unicorn:  2.1.0
This GDB was configured as follows:
   configure --host=x86_64-linux-gnu --target=x86_64-linux-gnu
	     --with-auto-load-dir=$debugdir:$datadir/auto-load
	     --with-auto-load-safe-path=$debugdir:$datadir/auto-load
	     --with-expat
	     --with-gdb-datadir=/usr/share/gdb (relocatable)
	     --with-jit-reader-dir=/usr/lib/gdb (relocatable)
	     --without-libunwind-ia64
	     --with-lzma
	     --with-babeltrace
	     --with-intel-pt
	     --with-mpfr
	     --with-xxhash
	     --with-python=/usr (relocatable)
	     --with-python-libdir=/usr/lib (relocatable)
	     --with-debuginfod
	     --without-guile
	     --enable-source-highlight
	     --enable-threading
	     --with-separate-debug-dir=/usr/lib/debug (relocatable)
	     --with-system-gdbinit=/etc/gdb/gdbinit
	     --with-system-gdbinit-dir=/etc/gdb/gdbinit.d

("Relocatable" means the directory can be moved with the GDB installation
tree, and GDB will still find it.)
@sasha-999 sasha-999 added the bug label Dec 31, 2024
@disconnect3d
Copy link
Member

disconnect3d commented Jan 6, 2025

...should we just remove the assertion all together?

@patryk4815
Copy link
Member

@sasha-999 please check with current dev branch, it should be "fixed". (PR #2646 )

@sasha-999
Copy link
Author

pwndbg> vis
╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ /home/sasha/gdb/pwndbg_2025/pwndbg/commands/__init__.py:183 in __call__                          │
│                                                                                                  │
│   180 │                                                                                          │
│   181 │   def __call__(self, *args: Any, **kwargs: Any) -> str | None:                           │
│   182 │   │   try:                                                                               │
│ ❱ 183 │   │   │   return self.function(*args, **kwargs)                                          │
│   184 │   │   except TypeError:                                                                  │
│   185 │   │   │   print(f"{self.function.__name__.strip()!r}: {self.function.__doc__.strip()}"   │
│   186 │   │   │   pwndbg.exception.handle(self.function.__name__)                                │
│                                                                                                  │
│ /home/sasha/gdb/pwndbg_2025/pwndbg/commands/__init__.py:514 in _OnlyWithResolvedHeapSyms         │
│                                                                                                  │
│   511 │   │   │   ):                                                                             │
│   512 │   │   │   │   # In auto mode, if the debug symbols are not enough, we will try to use    │
│   513 │   │   │   │   heuristic_heap = HeuristicHeap()                                           │
│ ❱ 514 │   │   │   │   if heuristic_heap.can_be_resolved():                                       │
│   515 │   │   │   │   │   pwndbg.aglib.heap.current = heuristic_heap                             │
│   516 │   │   │   │   │   w(                                                                     │
│   517 │   │   │   │   │   │   "pwndbg will try to resolve the heap symbols via heuristic now s   │
│                                                                                                  │
│ /home/sasha/gdb/pwndbg_2025/pwndbg/aglib/heap/ptmalloc.py:1734 in can_be_resolved                │
│                                                                                                  │
│   1731 │   │   return self._structs_module                                                       │
│   1732 │                                                                                         │
│   1733 │   def can_be_resolved(self) -> bool:                                                    │
│ ❱ 1734 │   │   return self.struct_module is not None                                             │
│   1735 │                                                                                         │
│   1736 │   @property                                                                             │
│   1737 │   def main_arena(self) -> Arena | None:                                                 │
│                                                                                                  │
│ /home/sasha/gdb/pwndbg_2025/pwndbg/aglib/heap/ptmalloc.py:1724 in struct_module                  │
│                                                                                                  │
│   1721 │                                                                                         │
│   1722 │   @property                                                                             │
│   1723 │   def struct_module(self) -> types.ModuleType | None:                                   │
│ ❱ 1724 │   │   if not self._structs_module and pwndbg.glibc.get_version():                       │
│   1725 │   │   │   try:                                                                          │
│   1726 │   │   │   │   self._structs_module = importlib.reload(                                  │
│   1727 │   │   │   │   │   importlib.import_module("pwndbg.aglib.heap.structs")                  │
│                                                                                                  │
│ /home/sasha/gdb/pwndbg_2025/pwndbg/aglib/proc.py:145 in wrapper                                  │
│                                                                                                  │
│   142 │   │   @functools.wraps(func)                                                             │
│   143 │   │   def wrapper(*a: P.args, **kw: P.kwargs) -> T | None:                               │
│   144 │   │   │   if self.alive:                                                                 │
│ ❱ 145 │   │   │   │   return func(*a, **kw)                                                      │
│   146 │   │   │   return None                                                                    │
│   147 │   │                                                                                      │
│   148 │   │   return wrapper                                                                     │
│                                                                                                  │
│ /home/sasha/gdb/pwndbg_2025/pwndbg/glibc.py:63 in get_version                                    │
│                                                                                                  │
│    60                                                                                            │
│    61 @pwndbg.aglib.proc.OnlyWhenRunning                                                         │
│    62 def get_version() -> Tuple[int, ...] | None:                                               │
│ ❱  63 │   return cast(Union[Tuple[int, ...], None], glibc_version) or _get_version()             │
│    64                                                                                            │
│    65                                                                                            │
│    66 @pwndbg.aglib.proc.OnlyWhenRunning                                                         │
│                                                                                                  │
│ /home/sasha/gdb/pwndbg_2025/pwndbg/aglib/proc.py:145 in wrapper                                  │
│                                                                                                  │
│   142 │   │   @functools.wraps(func)                                                             │
│   143 │   │   def wrapper(*a: P.args, **kw: P.kwargs) -> T | None:                               │
│   144 │   │   │   if self.alive:                                                                 │
│ ❱ 145 │   │   │   │   return func(*a, **kw)                                                      │
│   146 │   │   │   return None                                                                    │
│   147 │   │                                                                                      │
│   148 │   │   return wrapper                                                                     │
│                                                                                                  │
│ /home/sasha/gdb/pwndbg_2025/pwndbg/lib/cache.py:162 in decorator                                 │
│                                                                                                  │
│   159 │   │   │   │   if cached_value is not _NOT_FOUND_IN_CACHE:                                │
│   160 │   │   │   │   │   return cached_value                                                    │
│   161 │   │   │   │                                                                              │
│ ❱ 162 │   │   │   │   value = func(*a, **kw)                                                     │
│   163 │   │   │   │                                                                              │
│   164 │   │   │   │   # Sanity check: its not perfect and won't cover all cases like ([],)       │
│   165 │   │   │   │   # but it should be good enough                                             │
│                                                                                                  │
│ /home/sasha/gdb/pwndbg_2025/pwndbg/glibc.py:81 in _get_version                                   │
│                                                                                                  │
│    78 │   libc_filename = get_libc_filename_from_info_sharedlibrary()                            │
│    79 │   if not libc_filename:                                                                  │
│    80 │   │   return None                                                                        │
│ ❱  81 │   result = pwndbg.aglib.elf.dump_section_by_name(libc_filename, ".rodata", try_local_p   │
│    82 │   if result is None:                                                                     │
│    83 │   │   return None                                                                        │
│    84 │   _, _, data = result                                                                    │
│                                                                                                  │
│ /home/sasha/gdb/pwndbg_2025/pwndbg/aglib/elf.py:212 in dump_section_by_name                      │
│                                                                                                  │
│   209 │   Dump the content of a section from an ELF file, return the start address, size and c   │
│   210 │   """                                                                                    │
│   211 │   # TODO: We should have some cache mechanism or something at `pndbg.aglib.file.get_fi   │
│ ❱ 212 │   local_path = pwndbg.aglib.file.get_file(filepath, try_local_path=try_local_path)       │
│   213 │                                                                                          │
│   214 │   with open(local_path, "rb") as f:                                                      │
│   215 │   │   elffile = ELFFile(f)                                                               │
│                                                                                                  │
│ /home/sasha/gdb/pwndbg_2025/pwndbg/aglib/file.py:87 in get_file                                  │
│                                                                                                  │
│    84 │   has_target_prefix = path.startswith("target:")                                         │
│    85 │   has_good_prefix = path.startswith(("/", "./", "../")) or has_target_prefix             │
│    86 │   if not has_good_prefix:                                                                │
│ ❱  87 │   │   raise OSError("get_file called with incorrect path", errno.ENOENT)                 │
│    88 │                                                                                          │
│    89 │   if has_target_prefix:                                                                  │
│    90 │   │   path = path[7:]  # len('target:') == 7                                             │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
OSError: [Errno get_file called with incorrect path] 2

It's just throwing an exception now instead of an assertion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

No branches or pull requests

3 participants