Repository: pwndbg/pwndbg

Issue:
Make symbol.get bulletproof against duplicated symbols
@lebr0nli pointed out an interesting case with `symbol.get` failing when Pwntools+Pwndbg is used: https://github.com/pwndbg/pwndbg/pull/1605#discussion_r1126105118

The bug actually does not require Pwntools at all and was partially caused by our `_add_main_to_symbols` hook. I am going to remove that hook in https://github.com/pwndbg/pwndbg/pull/1609 if tests passes, since it seems reduntant.

However, that does not really fix the problem as there may always be duplicated symbols such that `info symbols main` would show:
```
pwndbg> info symbol main
main in section .text of /pwndbg/bug/vaccine
main in section .text of /pwndbg/bug/vaccine
```

This can probably be mitigated by doing something like:
```py
loc_string, *_ = result.split(...)
```

instead of:
```py
loc_string, _ = result.split(...)
```

In the `symbol.get` implementation.

This would also mean we would effectively always assume the first symbol is the one we want to use, but I also do not see a good other way to resolve the duplicated symbol issue.


Discussion:

Resolving patch:
diff --git a/pwndbg/gdblib/symbol.py b/pwndbg/gdblib/symbol.py
--- a/pwndbg/gdblib/symbol.py
+++ b/pwndbg/gdblib/symbol.py
@@ -84,6 +84,10 @@ def get(address: int, gdb_only=False) -> str:
                 res = pwndbg.ida.Name(address) or pwndbg.ida.GetFuncOffset(address)
                 return res or ""
 
+    # If there are newlines, which means that there are multiple symbols for the address
+    # then use the first one (see also #1610)
+    result = result[: result.index("\n")]
+
     # See https://github.com/bminor/binutils-gdb/blob/d1702fea87aa62dff7de465464097dba63cc8c0f/gdb/printcmd.c#L1594-L1624
     # The most often encountered formats looks like this:
     #   "main in section .text of /bin/bash"


Test patch:
diff --git a/tests/gdb-tests/tests/test_symbol.py b/tests/gdb-tests/tests/test_symbol.py
--- a/tests/gdb-tests/tests/test_symbol.py
+++ b/tests/gdb-tests/tests/test_symbol.py
@@ -28,3 +28,43 @@ def get_next_ptr():
     assert get_next_ptr() == "A::foo(int, int)"
 
     assert get_next_ptr() == "A::call_foo()"
+
+
+def test_symbol_duplicated_symbols_issue_1610():
+    """
+    Test for the bug from https://github.com/pwndbg/pwndbg/issues/1610
+    """
+    gdb.execute(f"file {MANGLING_BINARY}")
+
+    main_addr = int(gdb.parse_and_eval("(unsigned long long)main"))
+
+    # Sanity checks
+    assert gdb.execute("info symbol main", to_string=True) == "main in section .text\n"
+    assert pwndbg.gdblib.symbol.get(main_addr) == "main"
+
+    # This causes some confusion, see below :)
+    # Note: {addr} argument is needed for old GDB (e.g. on Ubuntu 18.04)
+    addr = _get_section_addr(".text")
+    gdb.execute(f"add-symbol-file {MANGLING_BINARY} {addr}")
+
+    # Sanity check - yeah, there are two symbols
+    out = gdb.execute("info symbol main", to_string=True).split("\n")
+
+    assert len(out) == 3
+
+    assert out[0] == out[1]
+    assert out[0].startswith("main in section .text of ")
+
+    assert out[2] == ""
+
+    # Make sure to clear cache!
+    pwndbg.gdblib.symbol.get.clear()
+
+    # Real test assert - this should not crash!
+    assert pwndbg.gdblib.symbol.get(main_addr) == "main"
+
+
+def _get_section_addr(sect):
+    result = gdb.execute("maintenance info sections", to_string=True).split("\n")
+    text_line = next(line for line in result if f": {sect} " in line)
+    return int(text_line.split(" at ")[1].split(":")[0], 16)