Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

[Bug] Bare qSupported reaches an unchecked null pointer in GdbServerConnection::query()

Chiusa Adatta ai principianti
#4,111 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

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
Tipo di issue
Bug
Chiarezza
Abbastanza chiara
Stato di attività
Attiva
Stack tecnologico
cpp, python
Ambito
devtools

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 mappings succeeds.
  • 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:

  1. The packet dispatcher separates the q request type from the remaining payload.

  2. GdbServerConnection::query() receives Supported and searches for a colon:

    args = strchr(payload, ':');
    
  3. There is no colon, so args becomes nullptr.

  4. The Supported handler passes this pointer to strstr while checking multiprocess+, hwbreak+, and swbreak+. 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:

  1. Imports GEF while no inferior is connected.
  2. Before Gef.main(), bypasses unrelated target probes, including Intel Pin detection.
  3. Updates stored function references in GDB_MODE_CHECKERS and explicitly selects rr mode for this session.
  4. Obtains memory mappings through GDB's info proc mappings.
  5. 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

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di rr-debugger/rr

Tutte le issue di rr-debugger/rr

Issue simili

Altre issue su C++

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.