Spoofing the Sky

breaking RTKLIB, the brain behind centimeter-accurate GPS.

RTKLIB turns raw satellite signals into centimeter-level position fixes for ships, drones, aircraft and self-driving machines. We found four memory-corruption bugs in its correction- and observation-stream decoders, reachable from the very data those machines are designed to trust.

What is RTK, and why should you care?

Plain GPS is accurate to a few meters, fine for a phone, useless for landing a drone on a moving boat or keeping a tractor in a 2 cm furrow. Real-Time Kinematic (RTK) GNSS fixes that by comparing your receiver against a known base station and streaming correction data to cancel out atmospheric and orbital error. The result: centimeter accuracy, in real time.

RTKLIB is the de-facto open-source engine for this. Its tools (rtkrcvrtknavirnx2rtkpstr2str) and its decoders are embedded across drones, survey gear, CORS networks, marine and automotive positioning stacks. To deliver centimeter accuracy it must continuously parse correction streams and observation files, and that parser is our attack surface.

Fig 1 — The RTK trust chain. The rover blindly decodes whatever correction bytes arrive. Own the stream, own the decoder.

The languages of precision GNSS

Three formats carry the data RTKLIB ingests. Two of them, RTCM3 and RINEX, are where our bugs live.

RTCM 3.x
binary · real-time · RTCM SC-104

The correction wire format. Compact binary messages (station coordinates, observations/MSM, ephemerides, SSR biases) streamed live to the rover. Framed with a 0xD3 preamble, a 10-bit length, a 12-bit message type, and a 24-bit CRC.

 
NTRIP
transport · HTTP-like · internet

“Networked Transport of RTCM via Internet Protocol.” How RTCM3 reaches a rover that isn’t in radio range of the base: a caster relays mountpoint streams over the internet. Anyone able to MITM or impersonate a caster controls the bytes.

 
RINEX 2/3
text · files · post-processing

“Receiver Independent Exchange.” Human-readable observation/navigation files, distributed by the thousands over FTP/HTTP by public CORS networks and fed into post-processing tools. A single file is enough to reach the parser.

Anatomy of an RTCM3 message and where one field goes rogue

Fig 2 — A length byte the decoder never checks against its destination buffer. Set it to 255 and decode_type1033 writes far past the end.

The attack surface

Every bug sits behind data RTKLIB is built to accept from outside. None require a login; all are reachable from the normal correction/observation paths.

Fig 3 — Untrusted channels → RTKLIB decoders → four memory-safety defects. Red = out-of-bounds write, amber = out-of-bounds read.

Why this matters in the real world

RTK-grade GNSS is load-bearing for machines that move. Here’s what “the decoder crashed” translates to once it’s bolted onto something with mass and momentum.

Boats & autonomous vessels

RTK drives precision docking, dynamic positioning and uncrewed surface vessels. A poisoned NTRIP correction feed that crashes or corrupts the positioning stack mid-maneuver removes the very reference the autopilot is steering by, in a harbor, that’s a grounding or allision risk.

Autonomous vehicles & robotics

Lane-level localization fuses RTK GNSS with other sensors, fed by CORS/NTRIP services. A correction stream that can crash the localizer becomes a remote way to degrade or drop a safety- critical input across an entire fleet subscribed to the same caster.

Drones & aircraft

Survey, mapping, agriculture and delivery UAVs lean on RTK/PPK for tight flight paths and precision landing, with corrections piped over 4G/telemetry. Corrupting the GNSS decoder on the companion computer can stall the position estimate, the difference between a landing and a flyaway.

Surveying & CORS infrastructure

National CORS networks publish RINEX by the terabyte; surveyors batch-process untrusted files in RTKLIB-based tools. One booby-trapped RINEX file in a dataset is enough to crash, or corrupt the memory of, whoever opens it, a quiet foothold in the geospatial supply chain.

From a forged packet to a lost vehicle

The bugs are memory corruption inside the decode step. Here’s how an attacker chains position in the network into impact on the machine.

Fig 4 — The attacker never needs the data to be "valid GNSS", only valid enough to reach the decoder.

The vulnerabilities

Unbounded antenna/receiver descriptors in decode_type1033

An RTCM3 type-1033 message carries five 8-bit length fields for antenna and receiver descriptor strings. The decoder copies that many bytes into fixed 64-byte fields of rtcm->sta with strncpy and never clamps the length against the destination size. A length of 255 writes ~191 bytes past a 64-byte buffer, five times over, corrupting adjacent decoder state.

Reach: any NTRIP/serial correction stream, base-station metadata is broadcast routinely; a valid CRC is trivially computed by the sender.

➕➕
/* n,m,n1,n2,n3 read straight off the wire: 0..255, unchecked */
strncpy(rtcm->sta.antdes, des, n); rtcm->sta.antdes[n] = '\0';  // char[64], n up to 255
strncpy(rtcm->sta.antsno, sno, m);  ... // + four more fields, same flaw

Off-by-one code-bias lookup in decode_ssr3

An RTCM3 type-1033 message carries five 8-bit length fields for antenna and receiver descriptor strings. The decoder copies that many bytes into fixed 64-byte fields of rtcm->sta with strncpy and never clamps the length against the destination size. A length of 255 writes ~191 bytes past a 64-byte buffer, five times over, corrupting adjacent decoder state.

Reach: RTCM3 SSR streams (types 1059/1065/1242/1248/1254/1260) over NTRIP/serial.

➕➕
if (mode <= ncode) {              // should be: mode < ncode
    cbias[codes[mode] - 1] = bias;  // codes[ncode] is out of bounds
}

Oversized epoch satellite count in readrnxobsb

A RINEX observation epoch declares how many satellites follow. decode_obsepoch returns that count unclamped; the record loop then writes data[n] and reads sats[i-1] for every declared satellite and the n < MAXOBS bound is checked only after the write. Declare 70 satellites (cap is 64) and the observation buffer overflows.

Reach: a single RINEX file, local, emailed, or auto-fetched from a CORS FTP/HTTP mirror.

➕➕
data[n].time = time;                 // rinex.c:948 — written before the bound check
data[n].sat  = sats[i-1];           // sats[MAXOBS] overrun
if (decode_obsdata(...) && n < MAXOBS) n++; // too late

Unknown observation code in getcodepri

When a RINEX header lists an observation code RTKLIB doesn’t recognise, code2obs hands back an empty string and index 0getcodepri then computes codepris[i][j-1], a negative [-1] index, and reads one byte past the empty string constant.

Reach: a crafted RINEX header with a bogus obs code.

➕➕
obs = code2obs(code, &j);              // unknown code → "" , j = 0
... strchr(codepris[i][j-1], obs[1]);  // codepris[i][-1] + read past ""

The bottom line

Centimeter-grade GNSS quietly became load-bearing for machines that move themselves, and RTKLIB is the engine most of them lean on. Its decoders are the front door: they parse correction streams and observation files before a single coordinate is ever computed, and that front door was never hardened against hostile input.

We found four memory-corruption bugs, each reachable from a single crafted RTCM3 message or RINEX file, an out-of-bounds write in the antenna-descriptor decoder and three out-of-bounds reads across the SSR, epoch-count and observation-code paths. No authentication, no handshake: an attacker only has to sit upstream of the antenna, a rogue NTRIP caster, a man-in-the-middled stream, or one booby-trapped file. And because RTKLIB is permissively licensed and forked everywhere, the same flaws ride quietly into closed firmware that never says the word “RTKLIB.”

The fix is unglamorous and well understood: clamp every wire-supplied length and count to its buffer before you touch it, and treat correction data as the untrusted input it has always been. Operators should authenticate their casters and sensor-fuse GNSS so a single poisoned stream can’t silently steer the platform. We reported all four findings upstream under coordinated disclosure (RTKLIB #796–799). The GNSS stack deserves the same adversarial scrutiny we’ve spent two decades giving browsers and kernels and at FuzzingLabs, we’re just getting started.

Patrick Ventuzelo / @Pat_Ventuzelo

About Us

Founded in 2021 and headquartered in Paris, FuzzingLabs is a cybersecurity startup specializing in vulnerability research, fuzzing, and blockchain security. We combine cutting-edge research with hands-on expertise to secure some of the most critical components in the blockchain ecosystem.

Contact us for an audit or long term partnership!

Get Your Free Security Quote!

Let’s work together to ensure your peace of mind.

Keep in touch with us !

email

contact@fuzzinglabs.com

X (Twitter)

@FuzzingLabs

Github

FuzzingLabs

LinkedIn

FuzzingLabs

email

contact@fuzzinglabs.com

X (Twitter)

@FuzzingLabs

Github

FuzzingLabs

LinkedIn

FuzzingLabs