Skip to content

Commit 5032f81

Browse files
authored
Merge pull request #74 from mwolter805/fix/reader-parser-crash-safety
fix: add umbrella crash protection and length guards to reader/parser dispatch
2 parents 173bba5 + b040656 commit 5032f81

3 files changed

Lines changed: 1109 additions & 860 deletions

File tree

src/meshcore/meshcore_parser.py

Lines changed: 61 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,30 @@ async def parsePacketPayload(self, payload, log_data={}):
4242
Returns :
4343
completed log_data
4444
"""
45+
# Minimum viable payload is 2 bytes (1 header + 1 path_byte) for a
46+
# direct route. Anything shorter is provably broken — for example,
47+
# the LOG_DATA branch in reader.py only requires `len(data) > 3`,
48+
# which means a 4-byte LOG_DATA frame produces a 1-byte payload
49+
# here, and `path_byte = pbuf.read(1)[0]` further down would raise
50+
# IndexError on the empty buffer. Populate sentinel values so the
51+
# caller's downstream `log_data['route_type']` etc. lookups don't
52+
# KeyError, then return early.
53+
if len(payload) < 2:
54+
logger.debug(f"parsePacketPayload: payload too short ({len(payload)} bytes < 2), returning sentinel log_data")
55+
log_data["route_type"] = -1
56+
log_data["route_typename"] = "UNK"
57+
log_data["payload_type"] = -1
58+
log_data["payload_typename"] = "UNK"
59+
log_data["payload_ver"] = 0
60+
log_data["path_len"] = 0
61+
log_data["path_hash_size"] = 1
62+
log_data["path"] = ""
63+
log_data["pkt_payload"] = b""
64+
log_data["pkt_hash"] = 0
65+
return log_data
66+
4567
pbuf = io.BytesIO(payload)
46-
68+
4769
header = pbuf.read(1)[0]
4870
route_type = header & 0x03
4971
payload_type = (header & 0x3c) >> 2
@@ -128,7 +150,7 @@ async def parsePacketPayload(self, payload, log_data={}):
128150
uncrypted = cipher.decrypt(msg)
129151
timestamp = int.from_bytes(uncrypted[0:4], "little", signed=False)
130152
attempt = uncrypted[4] & 3
131-
txt_type = int.from_bytes(uncrypted[4:4], "little", signed=False) >> 2
153+
txt_type = int.from_bytes(uncrypted[4:5], "little", signed=False) >> 2
132154
message = uncrypted[5:].strip(b"\0")
133155
msg_hash = int.from_bytes(SHA256.new(timestamp.to_bytes(4, "little", signed=False) + message).digest()[0:4], "little", signed=False)
134156
log_data["message"] = message.decode("utf-8", "ignore")
@@ -149,39 +171,42 @@ async def parsePacketPayload(self, payload, log_data={}):
149171
del self.channels_log[:25]
150172

151173
elif not payload is None and payload_type == 0x04: # Advert
152-
pk_buf = io.BytesIO(pkt_payload)
153-
adv_key = pk_buf.read(32).hex()
154-
adv_timestamp = int.from_bytes(pk_buf.read(4), "little", signed=False)
155-
signature = pk_buf.read(64).hex()
156-
flags = pk_buf.read(1)[0]
157-
adv_type = flags & 0x0F
158-
adv_lat = None
159-
adv_lon = None
160-
adv_feat1 = None
161-
adv_feat2 = None
162-
if flags & 0x10 > 0: #has location
163-
adv_lat = int.from_bytes(pk_buf.read(4), "little", signed=True)/1000000.0
164-
adv_lon = int.from_bytes(pk_buf.read(4), "little", signed=True)/1000000.0
165-
if flags & 0x20 > 0: #has feature1
166-
adv_feat1 = pk_buf.read(2).hex()
167-
if flags & 0x40 > 0: #has feature2
168-
adv_feat2 = pk_buf.read(2).hex()
169-
if flags & 0x80 > 0: #has name
170-
adv_name = pk_buf.read().decode("utf-8", "ignore").strip("\x00")
171-
log_data["adv_name"] = adv_name
172-
173-
log_data["adv_key"] = adv_key
174-
log_data["adv_timestamp"] = adv_timestamp
175-
log_data["signature"] = signature
176-
log_data["adv_flags"] = flags
177-
log_data["adv_type"] = adv_type
178-
if not adv_lat is None :
179-
log_data["adv_lat"] = adv_lat
180-
if not adv_lon is None :
181-
log_data["adv_lon"] = adv_lon
182-
if not adv_feat1 is None:
183-
log_data["adv_feat1"] = adv_feat1
184-
if not adv_feat2 is None:
185-
log_data["adv_feat2"] = adv_feat2
174+
try:
175+
pk_buf = io.BytesIO(pkt_payload)
176+
adv_key = pk_buf.read(32).hex()
177+
adv_timestamp = int.from_bytes(pk_buf.read(4), "little", signed=False)
178+
signature = pk_buf.read(64).hex()
179+
flags = pk_buf.read(1)[0]
180+
adv_type = flags & 0x0F
181+
adv_lat = None
182+
adv_lon = None
183+
adv_feat1 = None
184+
adv_feat2 = None
185+
if flags & 0x10 > 0: #has location
186+
adv_lat = int.from_bytes(pk_buf.read(4), "little", signed=True)/1000000.0
187+
adv_lon = int.from_bytes(pk_buf.read(4), "little", signed=True)/1000000.0
188+
if flags & 0x20 > 0: #has feature1
189+
adv_feat1 = pk_buf.read(2).hex()
190+
if flags & 0x40 > 0: #has feature2
191+
adv_feat2 = pk_buf.read(2).hex()
192+
if flags & 0x80 > 0: #has name
193+
adv_name = pk_buf.read().decode("utf-8", "ignore").strip("\x00")
194+
log_data["adv_name"] = adv_name
195+
196+
log_data["adv_key"] = adv_key
197+
log_data["adv_timestamp"] = adv_timestamp
198+
log_data["signature"] = signature
199+
log_data["adv_flags"] = flags
200+
log_data["adv_type"] = adv_type
201+
if not adv_lat is None :
202+
log_data["adv_lat"] = adv_lat
203+
if not adv_lon is None :
204+
log_data["adv_lon"] = adv_lon
205+
if not adv_feat1 is None:
206+
log_data["adv_feat1"] = adv_feat1
207+
if not adv_feat2 is None:
208+
log_data["adv_feat2"] = adv_feat2
209+
except (IndexError, ValueError) as e:
210+
logger.debug(f"parsePacketPayload: malformed ADVERT payload ({type(e).__name__}: {e}), len={len(pkt_payload)}")
186211

187212
return log_data

0 commit comments

Comments
 (0)