Yo [email protected]!

Thanks for the reports.

My opinion is the same as Linus Torvald's.  Everyone is using the
same tools and finding the same issues.  So no point in any
embargos.

I created issue 397 to track this:

    https://gitlab.com/gpsd/gpsd/-/work_items/397

> 1. CVE assignment - we request CVE IDs for issues 1-8.

Yeah, no.  We don't play the CVE game.  Feel free to do all that work
if you wish.

> 2. Coordinated disclosure - we propose a 90-day embargo from today

Yeah, no.  Public now as issue 397.  I'm getting triplicate and even
quadrupicate reports of the same bugs.  Going public protects your
primacy.

> 3. Proof-of-concept code

Nice, not required.

> 4. Credit - please credit "Xchg Labs"

Will do.


On Sun, 7 Jun 2026 17:55:27 -0400 (EDT)
"[email protected] [email protected]" <[email protected]>
wrote:

> Hello Gary,
> 
> Xchg Labs has been auditing gpsd. We are writing to
> privately disclose twelve memory-safety issues present in current
> master (commit 43a340603, post-3.27.5). Several are remotely
> reachable and at least one is exploitable for remote code execution.
> 
> We are committed to coordinated/responsible disclosure: nothing here
> is public, and we will hold all details and proof-of-concept code
> until a fix ships and an embargo period elapses. We would like to
> request CVE assignment for the reachable issues and to coordinate
> fixes and credit with you.
> 
> We acknowledge the substantial bounds-checking work already landed in
> master since February - it closed roughly twenty issues we had
> tracked, including the RTCM3/RTCM2 length checks, the TSIP and
> Geostar overflows, and the NMEA PASHR fixes. The items below are what
> remains.
> 
> All line numbers are against master @ 43a340603. Where noted,
> findings were confirmed dynamically against a build of this tree. 
> 
> I would also personally be happy to hop on a quick call for any
> guidance you may need while patching.
> 
> ====================================================================
> SUMMARY
> ====================================================================
> 
>  #   Component                       Type
> Severity         Remote --  ------------------------------
> ----------------------------  ---------------
> ------------------------- 1   Skytraq 0xDD raw measurement    OOB
> write -> RCE              Critical         via network GPS
> source/device 2   NMEA $PGRMT / $INFO             Format string ->
> leak+write   Critical         via NMEA source 3   AIS Type 6 / Type 8
> (6 app-IDs) OOB write                     High             via AIS
> feed 4   AllyStar NAV-SVINFO             OOB read
>  Medium           via device 5   u-blox RXM-RAWX                 OOB
> read                      Medium           via device 6   u-blox
> NAV-SBAS                 OOB read                      Medium
>   via device 7   PASSTHROUGH mode                1-byte over-read
>          Medium           proxy mode 8   Client request parser
>    Stale-buffer disclosure       Medium           port 2947
> 
> Attack vector depends on how the GPS data reaches gpsd: a malicious
> serial/USB receiver, or for deployments configured with a tcp://,
> udp://, or gpsd:// source (common on embedded/IoT/timing systems) - a
> remote network peer. Issue 8 is reachable by any client that can
> connect to the daemon's TCP port (2947).
> 
> 
> ====================================================================
> CRITICAL
> ====================================================================
> 
> 1. Skytraq 0xDD - measurement count not bounded against meas[]
> 
>   File: drivers/driver_skytraq.c:1088, :1094, :1108
> 
>   nmeas = getub(buf, 2) (0-255) is the loop bound. Only packet length
> is validated (len < 3 + nmeas*23); there is NO check against the
> array size. raw.meas[] is meas[MAXCHANNELS] (230, include/gps.h:132 /
> :2732).
> 
>   nmeas of 231-255 writes meas[230..254] - up to ~2.2 KB of attacker-
>   controlled doubles past the array. We have previously demonstrated
> this primitive yields remote code execution.
> 
>   The May change (d0385996e) raised MAXCHANNELS 184->230 and added a
> compile- time assert, but the runtime loop is unchanged.
> 
>   Fix: bound the count before the loop, as sky_msg_DE() already does:
>       if (SKY_CHANNELS < nmeas) return 0;
> 
> 
> 2. NMEA $PGRMT / $INFO - attacker-controlled format string
>   (confirmed dynamically)
> 
>   Files: drivers/driver_nmea0183.c:4267 ($PGRMT -> session->subtype),
>          :2812 ($INFO -> dev.sernum);
>          gpsd/gpsd_json.c:805 (json_stringify), helper :68;
>          gpsd/gpsd.c:1784-1785;
>          sink notify_watchers() :730 (vsnprintf(buf, sizeof(buf),
> sentence, ap)) and :741 (throttled_write(sub, buf, len)).
> 
>   The receiver-supplied subtype/sernum string is emitted through
>   json_stringify(), which escapes quotes/control/non-ASCII but NOT
> '%', then passed as the FORMAT argument to vsnprintf in
> notify_watchers(). Fires on DEVICEID_SET | DRIVER_IS (gpsd.c:1775),
> which a receiver can trigger.
> 
>   Two consequences:
>     - %n yields an arbitrary write.
>     - Stack over-read / ASLR + canary bypass: notify_watchers uses the
>       vsnprintf RETURN value as the throttled_write length, and that
> return is the would-be length, which can exceed sizeof(buf) (BUFSIZ).
> A width specifier such as %99999c makes vsnprintf return 99999;
> throttled_write then sends 99999 bytes from buf[BUFSIZ], reading ~91
> KB of stack past the buffer - including PIE, libc, and stack-canary
> values - to every JSON-watching client.
> 
>   This over-read is the primitive that defeats ASLR; it is NOT the
> separate client-parser leak (issue 8), whose ~10 KB reply-buffer
> channel cannot reach the deep-stack pointers used here.
> 
>   Confirmed dynamically against a build of this tree: planting
> %99999c as the $PGRMT subtype and firing one DEVICEID event returned
> ~300 KB on a single JSON WATCH socket (well past buf[BUFSIZ]=8192),
> with live PIE/libc-class pointers visible beyond offset 8192. The
> over-read is driven entirely by notify_watchers/throttled_write, and
> the leaked bytes arrive as a truncated DEVICE record, not via the
> request parser.
> 
>   Fix: do not pass dynamic content as a format -
> notify_watchers(device, ..., "%s", id2); AND clamp the write length
> to bytes actually written, e.g. if (len >= (int)sizeof(buf)) len =
> (int)sizeof(buf) - 1;  before throttled_write().
> 
> 
> ====================================================================
> HIGH
> ====================================================================
> 
> 3. AIS Type 6 / Type 8 application IDs - unbounded array writes
> 
>   File: drivers/driver_ais.c; Type 6 capped at bitlen 1008 (:226),
>         Type 8 at 1008 (:515).
> 
>     Message              Loop                          Array
> (include/gps.h)  Max idx -------------------
> ----------------------------  ---------------------  ------- T6 FID25
> cargos      :422  100+u*17 <  bitlen      cargos[28]   (:1850)   53
> T6 FID14 tidals      :332  97+93*u  <= bitlen      tidals[3]
> (:1876)   9 T6 FID32 tidals      :462  97+88*u  <= bitlen
> tidals[3]              10 T6 FID28 waypoints   :440  5-bit waycount
>        waypoints[16](:1525)   30 T8 FID17 targets     :597  56+122*u
> <= bitlen      targets[4]   (:2094)   7 T8 FID27 waypoints   :660
> 5-bit waycount          waypoints[16]          30
> 
>   Loop counts derive from the attacker-supplied bit length or a 5-bit
> field and are never clamped to the destination size. The writes land
> within the larger ais_t union in current layouts (no crash observed)
> but corrupt unrelated decoded fields and are latent out-of-structure
> writes. Reachable over AIS network feeds. (The recent AIS commits
> hardened the six-bit string path only, not these loops.)
> 
>   Fix: clamp each loop to its array bound, e.g.
>       for (u = 0; u < N && <bit-check>; u++)
> 
> 
> ====================================================================
> MEDIUM
> ====================================================================
> 
> 4. AllyStar NAV-SVINFO - payload length not validated
> 
>   File: drivers/driver_allystar.c:1298 (nchan > MAXCHANNELS),
>         :1305 (// FIXME: check payload_len), loop :1308.
> 
>   nchan is checked against MAXCHANNELS but not against payload
> length; the loop reads 24 bytes per entry (plus an 8-byte getled64 at
> off+24) past short payloads. The FIXME is unresolved.
> 
>   Fix: if (payload_len < (size_t)nchan * 24 + HDR) return 0;  //
> before loop
> 
> 
> 5. u-blox RXM-RAWX - count not validated against payload
> 
>   File: drivers/driver_ubx.c:4621 (runt check), :4656 (numMeas >
> MAXCHANNELS), loop :4663.
> 
>   numMeas is checked against MAXCHANNELS but not data_len; the loop
> reads 32 bytes per measurement past outbuffer. Handler sets RAW_IS,
> so leaked bytes can reach JSON clients.
> 
>   Fix: if (data_len < (16 + (size_t)numMeas * 32)) return 0;  //
> before loop
> 
> 
> 6. u-blox NAV-SBAS - non-fatal length check
> 
>   File: drivers/driver_ubx.c:3639 (if (data_len < (12 + 12*cnt)) -
> logs only), loop :3644.
> 
>   The length check logs a warning but does NOT return; the loop then
> reads past outbuffer -> SIGSEGV.
> 
>   Fix: make the check fatal - add return 0;
> 
> 
> 7. PASSTHROUGH mode - one-byte over-read sent to clients
> 
>   File: gpsd/gpsd.c:1992 (strlcat(outbuffer, "\r\n", ...)),
>         :1996 (throttled_write(..., outbuflen + 2)).
> 
>   The transmit length outbuflen + 2 is computed independently of the
> size- bounded strlcat; when it exceeds the buffer, one byte past the
> buffer is sent. Reachable in proxy mode (gpsd://) with an upstream
> sending maximum- size packets.
> 
>   Fix: transmit strnlen(outbuffer, sizeof(outbuffer)) bytes.
> 
> 
> 8. Client request parser - stale receive-buffer disclosed across
> clients (confirmed dynamically)
> 
>   Files: gpsd/gpsd.c:1640 (error JSON), :1645 (buf += len);
>          caller loop :2059-2066;
>          recv buffer :3166, handler call :3204 (passes sizeof(buf)).
> 
>   Two combined defects:
>     (a) The receive buffer (char buf[BUFSIZ] at :3166) is not zeroed;
> only buf[buflen] is NUL-terminated, and the handler is passed
> sizeof(buf) rather than buflen.
>     (b) On an unrecognized command, len is the snprintf RETURN value
> (length of the error JSON, ~57 B), and buf += len advances the INPUT
> pointer by it. The caller loop (for (end=buf; *buf!='\0'; buf=end))
> then continues parsing PAST the actual command, into stale buffer
> left by earlier requests, and echoes it in further ERROR responses.
> 
>   Impact: remote, unauthenticated, single unknown command (e.g.
> ?z\n). We confirmed this against a build of this tree: a client
> sending one short malformed command received back up to ~7.9 KB of
> buffer content from a PREVIOUS client's request - a cross-client
> information disclosure. Note: in our testing the disclosed bytes are
> stale request text only; we did not observe pointer or stack-canary
> disclosure (the buffer is written only by recv()), and the path does
> not crash. Severity is confidentiality-only and the data is generally
> low-sensitivity, but the cross-client boundary violation is real and
> unauthenticated.
> 
>   Fix: advance the parse position by the CONSUMED INPUT length (to
> the next terminator), not by the reply length; and pass buflen (not
> sizeof(buf)) to the handler, and/or zero buf before use.
> 
> 
> ====================================================================
> REQUESTS AND DISCLOSURE TERMS
> ====================================================================
> 
> 1. CVE assignment - we request CVE IDs for issues 1-8. We are happy
> for GPSD (or its CNA/MITRE) to assign; we can also assist with
> descriptions and CWE classification if useful.
> 
> 2. Coordinated disclosure - we propose a 90-day embargo from today
>   (2026-06-07), i.e. public disclosure on or around 2026-09-05,
> flexible if you need more time to ship and let downstream
> distributors (Debian, Ubuntu, Fedora, RHEL, Yocto, etc.) rebuild.
> 
> 3. Proof-of-concept code - we have working PoCs and can share them
> privately, along with reproduction instructions and a test harness,
> on request.
> 
> 4. Credit - please credit "Xchg Labs" in the
>   changelog/advisory. We will reciprocate.
> 
> Please let us know your preferred private channel (encrypted email /
> GitLab confidential issue) and we will route everything there. Thank
> you for gpsd and for your continued attention to these CVE-class
> issues.
> 
> Best regards,
> 
> Aaron Sacks
> Xchg Labs
> [email protected]
> 




RGDS
GARY
---------------------------------------------------------------------------
Gary E. Miller Rellim 109 NW Wilmington Ave., Suite E, Bend, OR 97703
        [email protected]  Tel:+1 541 382 8588

            Veritas liberabit vos. -- Quid est veritas?
    "If you can't measure it, you can't improve it." - Lord Kelvin

Attachment: pgp7075dBzWWh.pgp
Description: OpenPGP digital signature

Reply via email to