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
pgp7075dBzWWh.pgp
Description: OpenPGP digital signature
