Skip to content

Commit 5519def

Browse files
committed
Merge branch 'fix/binary_log_invalid_data' into 'master'
fix(binary_log): Fix monitor being stuck on decoding invalid binary log data Closes IDFGH-16338 See merge request espressif/esp-idf-monitor!95
2 parents c651422 + 3b0e86c commit 5519def

File tree

3 files changed

+73
-16
lines changed

3 files changed

+73
-16
lines changed

esp_idf_monitor/base/binlog.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -222,25 +222,35 @@ def find_frames(self, data: bytes) -> Tuple[List[bytes], bytes]:
222222
frames: List[bytes] = []
223223
i = 0
224224
idx_of_last_found_pkg = 0
225+
225226
while i < len(data):
226227
if len(data[i:]) < 15: # Minimal frame len
227228
break
228229
if self.detected(int(data[i])):
229230
start_idx = i
230-
control = Control(data[start_idx + 1:])
231-
if control.pkg_len > len(data[i:]):
232-
break
233-
frame = data[start_idx:start_idx + control.pkg_len]
234-
if control.pkg_len != 0 and self.crc8(frame) == 0:
235-
frames.append(frame)
236-
idx_of_last_found_pkg = start_idx + control.pkg_len
237-
i += control.pkg_len - 1
231+
try:
232+
control = Control(data[start_idx + 1:])
233+
if control.pkg_len > len(data[i:]):
234+
break
235+
frame = data[start_idx:start_idx + control.pkg_len]
236+
if control.pkg_len != 0 and self.crc8(frame) == 0:
237+
frames.append(frame)
238+
idx_of_last_found_pkg = start_idx + control.pkg_len
239+
i += control.pkg_len - 1
240+
else:
241+
raise ValueError('Invalid binary log; invalid CRC')
242+
except (struct.error, IndexError):
243+
# Invalid control structure
244+
raise ValueError('Invalid binary log; invalid control structure')
245+
else:
246+
raise ValueError('Invalid binary log; invalid start of frame')
247+
238248
i += 1
239249
# Return recognized frames and any remaining unprocessed data
240250
return frames, data[idx_of_last_found_pkg:]
241251

242252
def convert_to_text(self, data: bytes) -> Tuple[List[bytes], bytes]:
243-
messages: List[bytes] = [b'']
253+
messages: List[bytes] = []
244254
frames, incomplete_fragment = self.find_frames(data)
245255
for pkg_msg in frames:
246256
elf_path = self.source_of_message(pkg_msg[0])
@@ -257,7 +267,7 @@ def format_message(self, message: Message) -> bytes:
257267
return f'{level_name} ({message.timestamp}) {message.tag}: {text_msg}\n'.encode('ascii')
258268

259269
def format_buffer_message(self, message) -> List[bytes]:
260-
text_msg: List[bytes] = [b'']
270+
text_msg: List[bytes] = []
261271
BYTES_PER_LINE = 16
262272
buff_len = message.args[0]
263273
buffer = message.args[1]

esp_idf_monitor/base/serial_handler.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -164,11 +164,21 @@ def handle_serial_input(self, data, console_parser, coredump, gdb_helper, line_m
164164

165165
sp = self.splitdata(data)
166166
if self.binary_log_detected:
167-
text_lines, self._last_line_part = self.binlog.convert_to_text(sp[0])
168-
for line in text_lines:
169-
self.print_colored(line)
170-
self.logger.handle_possible_pc_address_in_line(line)
171-
return
167+
try:
168+
text_lines, self._last_line_part = self.binlog.convert_to_text(sp[0])
169+
for line in text_lines:
170+
self.print_colored(line)
171+
self.logger.handle_possible_pc_address_in_line(line)
172+
return
173+
except ValueError:
174+
# If no valid binary log frames were found, or if we have too much accumulated data
175+
# without valid frames, exit binary log mode
176+
self.binary_log_detected = False
177+
# Process the accumulated data as regular text instead
178+
accumulated_data = sp[0] if sp else b''
179+
if accumulated_data:
180+
self._last_line_part = accumulated_data
181+
sp = self.splitdata(b'') # Re-split the data normally
172182

173183
for line in sp:
174184
line_strip = line.strip()

test/host_test/test_monitor.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import errno
77
import filecmp
88
import os
9+
import random
910
import re
1011
import socket
1112
import subprocess
@@ -267,7 +268,7 @@ def test_auto_color_advanced(self):
267268
@pytest.mark.skipif(os.name == 'nt', reason='Linux/MacOS only')
268269
def test_rfc2217(self, rfc2217: str):
269270
"""Run monitor with RFC2217 port"""
270-
# run with no reset because it is not supported for socker ports
271+
# run with no reset because it is not supported for socket ports
271272
input_file = 'in1.txt'
272273
out, err = self.run_monitor(['--no-reset'], input_file, custom_port=rfc2217)
273274
with open(err, 'r') as f:
@@ -375,6 +376,42 @@ def test_binary_logging(self):
375376
log_clean
376377
), "Expected address, 'app_main', and 'main.c:<line>' in the output"
377378

379+
@pytest.fixture
380+
def invalid_binary_log(self):
381+
with NamedTemporaryFile(delete=False) as f:
382+
f.write(b'I (1) main: Starting\r\n')
383+
# Binary log detection trigger
384+
f.write(b'\x01')
385+
# Corrupted/invalid binary log data that would cause stuck behavior
386+
# The max length of the binary log frame is 1023 bytes so just to be sure we write more
387+
f.write(b'\x01' + random.randbytes(1024))
388+
# Text after binary log (should be processed normally)
389+
f.write(b'I (1000) main: Application started\r\n')
390+
# Add some valid binary log data frame from inputs/binlog
391+
# Should be decoded as "I (259) example: >>> String Formatting Tests <<<"
392+
f.write(b'\x02\x0c\x10\x00\x00\x85\x9c\x3f\x40\x09\x9c\x00\x00\x01\x03\xd6')
393+
394+
yield f.name
395+
os.unlink(f.name)
396+
397+
def test_binary_log_invalid_data(self, invalid_binary_log: str):
398+
"""Test the binary log with invalid data to make sure it is processed normally and not stuck"""
399+
args = [os.path.join(IN_DIR, 'log.elf')]
400+
out, err = self.run_monitor(args, invalid_binary_log, timeout=15)
401+
print('Using binary log file: ', invalid_binary_log)
402+
with open(err, 'r') as f_err:
403+
stderr = f_err.read()
404+
assert 'Stopping condition has been received' in stderr
405+
406+
# Verify that monitor didn't get stuck and processed all data; ignore errors because we are using random data
407+
with open(out, 'r', errors='ignore') as f_out:
408+
output = f_out.read()
409+
# Should contain messages from both before and after invalid binary log processing
410+
assert 'I (1) main: Starting' in output
411+
assert 'I (1000) main: Application started' in output
412+
# Valid binary log data frame should be decoded
413+
assert 'I (259) example: >>> String Formatting Tests <<<' in output
414+
378415

379416
@pytest.mark.skipif(os.name == 'nt', reason='Linux/MacOS only')
380417
class TestConfig(TestBaseClass):

0 commit comments

Comments
 (0)