[Bug] Bare qSupported reaches an unchecked null pointer in GdbServerConnection::query()
I maintainer di solito rispondono entro 2 giorni
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 2/5
- Tempo stimato
- 1-3 ore
- Idoneità per principianti
- 62/100
Direzione di ricerca
Inizia da src/GdbServerConnection.cc, in GdbServerConnection::query(), dove args = strchr(payload, ':') può lasciare args a null e il ramo Supported lo passa a strstr. Verifica il percorso con una richiesta maintenance packet qSupported senza argomenti su una sessione di replay, idealmente con un backtrace del server. È completato quando la richiesta senza argomenti restituisce la normale risposta sulle capacità senza toccare un puntatore nullo, qSupported: viene gestito, e una richiesta senza argomenti dopo una lista popolata cancella i flag multiprocess, hwbreak e swbreak precedenti.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
[Bug] Bare qSupported reaches an unchecked null pointer in GdbServerConnection::query()
1. Problem description
This is a protocol-handling bug: the GDB remote protocol allows a qSupported request without a client feature list, but rr's parser leaves the argument pointer null and subsequently passes it to strstr.
I discovered this while investigating an rr.soft replay-server crash triggered by loading bata24/gef. GEF sends bare qSupported during target detection, exposing this unsafe server-side path.
Scope: the runtime crash was observed with rr.soft. Source inspection confirmed the same defect in upstream rr at commit 858a9fbcc5ac71cc578c2e1966f573d7c4ff8257. I have not reproduced the crash using an upstream rr binary.
Expected behavior
Treat an omitted client feature list as empty and return the server's capability response safely.
Actual behavior
The inspected implementation reaches strstr(nullptr, ...), which is undefined behavior and can terminate the server. In the affected rr.soft setup, loading GEF causes the replay server to terminate with Segmentation fault, and GDB reports Remote connection closed.
The exact crashing instruction in that runtime failure still needs confirmation with a server backtrace or an isolated bare-packet test.
Environment where the crash was observed
| Component | Details |
|---|---|
| Architecture | AArch64 |
| OS and kernel | Linux; [fill in distribution and uname -a] |
| rr implementation | sidkshatriya/rr.soft |
| Nix package name | rr-soft-post5.9.0+builtwithclang |
| Installed rr.soft source revision | [fill in, if known] |
| Replay mode | Software counters, -W |
| GDB | [fill in version] |
| GEF | bata24/gef; [fill in installed revision or SHA256] |
| Target | how2heap overlapping_chunks |
Control observations:
rr replay -W -a <TRACE_DIR>completes successfully.- Plain GDB can connect and debug the recording.
- GDB's
info proc mappingssucceeds. - Loading GEF after connecting triggers the server crash.
- The failure persists with the system libc and dynamic linker; a target modified with patchelf is not required.
2. Reproduction steps
Observed failing workflow — rr.soft
Start the replay server using an affected recording:
rr replay -W <TRACE_DIR> -s 1234
Start GDB without initialization files or automatic script loading:
GDB_UNDER_RR=1 gdb -q -nx \
-iex 'set auto-load off' \
'<RECORDED_ELF>'
<RECORDED_ELF> means the executable associated with the recording, such as mmap_clone_4_overlapping_chunks, rather than the trace directory.
Connect and load GEF:
set sysroot /
set pagination off
set remotelogfile /tmp/rr-gef-failure.log
target extended-remote 127.0.0.1:1234
info proc mappings
source <PATH_TO_GEF_PY>
The mapping command succeeds. Loading GEF triggers the failure.
Server output:
Segmentation fault
GDB/GEF output:
[!] Remote connection closed
A BFD warning about a system-supplied DSO is also printed during connection. Plain GDB remains usable after that warning, so its causal relationship to the crash has not been established.
Proposed isolated reproduction — not yet verified
In a fresh replay session, connect with clean GDB and run the following instead of loading GEF:
maintenance packet qSupported
For upstream testing, use an upstream rr build and a recording supported by that build:
rr replay <UPSTREAM_TRACE_DIR> -s 1234
Then connect using clean GDB as above and send the bare packet. The rr.soft -W recording should not be assumed compatible with upstream rr.
Current verification status: the GEF-triggered rr.soft crash is observed; the unsafe code path is confirmed by inspection. The bare-packet-only test, upstream runtime reproduction, and server backtrace are still pending.
3. Failure mechanism and relevant code
GEF sends qSupported, without a feature list
In the inspected bata24/gef source, is_pin() executes:
gdb.execute("maintenance packet qSupported", to_string=True, from_tty=False)
The request contains no colon and no client feature list.
This probe can be reached during module import: the MmapMemoryCommand class definition evaluates get_pagesize() as an argument default. With a connected inferior, this can lead to target detection before post-import overrides are installed.
The linked GEF revision is an inspected source revision, not a confirmed identifier for the installed GEF file.
rr processes Supported with a null argument pointer
The source-level path is:
-
The packet dispatcher separates the
qrequest type from the remaining payload. -
GdbServerConnection::query()receivesSupportedand searches for a colon:args = strchr(payload, ':'); -
There is no colon, so
argsbecomesnullptr. -
The
Supportedhandler passes this pointer tostrstrwhile checkingmultiprocess+,hwbreak+, andswbreak+. Its debug log also receives the nullable pointer.
This reaches strstr(nullptr, ...), producing undefined behavior.
The same unsafe handling is present in rr.soft at the inspected revision. That revision has not yet been matched to the installed Nix binary.
Why this is a server bug
The GDB remote protocol specification permits omitting the client feature list. The server therefore misses a valid input case: it processes the request as though a non-null argument string were always present.
GEF provides a trigger for this path. A client-side workaround can avoid the request, but the server still needs to handle it safely.
4. Proposed fix
Inside the Supported branch, normalize the optional argument to an empty string before logging or searching it. Replace the existing log statement and capability assignments with:
const char* client_features = args ? args : "";
LOG(debug) << "debugger supports " << client_features;
multiprocess_supported_ =
strstr(client_features, "multiprocess+") != nullptr;
hwbreak_supported_ =
strstr(client_features, "hwbreak+") != nullptr;
swbreak_supported_ =
strstr(client_features, "swbreak+") != nullptr;
Continue generating the normal server capability response. Keep this normalization local to the Supported branch because other queries have different argument requirements.
The flags must still be recalculated on every request: the protocol specifies that each qSupported request replaces previously supplied client capabilities. An omitted list should clear the corresponding flags rather than preserve previous values.
Suggested regression coverage:
- Bare
qSupported: normal response, no null-pointer access. qSupported:: safely handle an explicitly empty list.- A populated list followed by bare
qSupported: verify that previously enabled client capability flags are cleared. - Run these cases with debug logging enabled as well.
This is a proposed fix. Patch application has been checked against the inspected rr.soft source, but build results, the rr test suite, and replay results with the server patch are not yet available.
Separately, GEF's use of qSupported as a detection probe can change negotiated capabilities. Fixing the null-pointer bug does not by itself establish that the probe has no other compatibility effects.
5. Verified workaround
The affected rr.soft recording can be debugged with GEF using an rr-only bootstrap loaded before establishing the remote connection.
The bootstrap:
- Imports GEF while no inferior is connected.
- Before
Gef.main(), bypasses unrelated target probes, including Intel Pin detection. - Updates stored function references in
GDB_MODE_CHECKERSand explicitly selects rr mode for this session. - Obtains memory mappings through GDB's
info proc mappings. - Initially disables automatic context rendering.
The successful workflow is:
rr replay -W <TRACE_DIR> -s 1234
GDB_UNDER_RR=1 gdb -q -nx \
-iex 'set auto-load off' \
-ex 'source /home/gosh/rr-gef-bootstrap.py' \
'<RECORDED_ELF>'
set sysroot /
set pagination off
set remotelogfile /tmp/rr-gef-remote.log
target remote 127.0.0.1:1234
si
rsi
info proc mappings
vmmap
gef config context.layout="regs stack code"
gef config context.enable true
si
rsi
Forward stepping, reverse stepping, mapping queries, and stepping after enabling context succeed with this workaround.
The mapping override addresses a separate GEF replay compatibility issue involving access to the recorded PID's local /proc path.
This is a client-side mitigation, not a repair of the server parser. It is intended only for an explicitly selected rr session. Since the bootstrap changes several behaviors simultaneously, its success alone does not isolate the observed crash to the qSupported request.
rr-gef-bootstrap.py used for the workaround
import hashlib
import importlib.util
import os
from pathlib import Path
import sys
import gdb
def _rr_gef_stage(message):
gdb.write("[rr-gef] " + message + "\n")
gdb.flush()
def _rr_gef_bootstrap():
if any(inf.pid or getattr(inf, "connection", None)
for inf in gdb.inferiors()):
raise gdb.GdbError(
"Start a fresh gdb -nx and load this script BEFORE target remote."
)
if "gef" in sys.modules:
raise gdb.GdbError("GEF was already imported; restart gdb -nx.")
path = Path(os.environ.get("GEF_RR_FILE", "~/.gef/gef.py")).expanduser().resolve()
if not path.is_file():
raise gdb.GdbError("GEF file does not exist: " + str(path))
_rr_gef_stage("GEF file: " + str(path))
_rr_gef_stage("GEF SHA256: " + hashlib.sha256(path.read_bytes()).hexdigest())
# Prevent old saved aliases/settings from changing this diagnostic session.
os.environ["GEF_RC"] = "/dev/null"
_rr_gef_stage("1/4 importing GEF with no connected inferior")
spec = importlib.util.spec_from_file_location("gef", path)
if spec is None or spec.loader is None:
raise gdb.GdbError("Could not create the GEF module loader")
gef_module = importlib.util.module_from_spec(spec)
sys.modules["gef"] = gef_module
spec.loader.exec_module(gef_module)
_rr_gef_stage("2/4 import complete; installing rr-only adaptations")
required = ("Gef", "ProcessMap", "GDB_MODE_CHECKERS", "is_alive")
missing = [name for name in required if not hasattr(gef_module, name)]
if missing:
raise gdb.GdbError("Unsupported GEF API; missing: " + ", ".join(missing))
if not hasattr(gef_module.ProcessMap, "get_process_maps_from_info_proc"):
raise gdb.GdbError("GEF lacks get_process_maps_from_info_proc")
def not_this_target():
return False
# This is an explicitly selected rr session, so unrelated target probes
# are unnecessary. Keep is_remote_debug unchanged: the transport is remote.
mode_functions = {
"pin": "is_pin",
"qemu-user": "is_qemu_user",
"qemu-system": "is_qemu_system",
"qiling": "is_qiling",
"vmware": "is_vmware",
"kgdb": "is_kgdb",
"kdb": "is_kdb",
"wine": "is_wine",
}
for mode, function_name in mode_functions.items():
setattr(gef_module, function_name, not_this_target)
gef_module.GDB_MODE_CHECKERS[mode] = not_this_target
gef_module.is_qemu = not_this_target
def this_is_rr():
return bool(gef_module.is_alive())
gef_module.is_rr = this_is_rr
gef_module.GDB_MODE_CHECKERS["rr"] = this_is_rr
def replay_maps(outer=False):
if not gef_module.is_alive():
return []
# Deliberately no additional cache: query the current replay position.
return gef_module.ProcessMap.get_process_maps_from_info_proc()
gef_module.ProcessMap.get_process_maps = staticmethod(replay_maps)
_rr_gef_stage("3/4 initializing GEF commands and event handlers")
gef_module.Gef.main()
gdb.execute("gef config context.enable false")
gdb.execute("gef config context.clear_screen false")
_rr_gef_stage("4/4 ready; connect to rr now. Automatic context is OFF.")
_rr_gef_bootstrap()
- Lingua principale
- C++
- Stelle
- 10.7k
- Fork
- 665
- Merge medio
- 3g 1h
- PR unite (30g)
- 19
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Nessun modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di rr-debugger/rr
-
Difficoltà 5/5 Più di una settimana Idoneità per principianti 12/100
rr-debugger/rr#4124 · 4 commenti ·
I maintainer di solito rispondono entro 2 giorni
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 35/100
rr-debugger/rr#4060 · 5 commenti ·
I maintainer di solito rispondono entro 2 giorni
-
Difficoltà 3/5 1-2 giorni Idoneità per principianti 68/100
rr-debugger/rr#4059 · 1 commento ·
I maintainer di solito rispondono entro 2 giorni
-
Difficoltà 5/5 Più di una settimana Idoneità per principianti 25/100
rr-debugger/rr#4047 · 4 commenti ·
I maintainer di solito rispondono entro 2 giorni
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 45/100
rr-debugger/rr#4042 · 4 commenti ·
I maintainer di solito rispondono entro 2 giorni
Tutte le issue di rr-debugger/rr
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 74/100
I maintainer di solito rispondono entro 1 giorno
-
enhancement question
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
DarkStarSystems/pcons#224 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 77/100
LizardByte/Sunshine#5953 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 1 giorno
-
(S1 - Need confirmation)
Difficoltà 2/5 1-3 ore Idoneità per principianti 63/100
CleverRaven/Cataclysm-DDA#89133 ·
I maintainer di solito rispondono entro 1 giorno