Skip to content

Commit b29f7ae

Browse files
committed
Reject malformed tunnel packets
1 parent ae71047 commit b29f7ae

2 files changed

Lines changed: 44 additions & 0 deletions

File tree

meshtastic/tests/test_tunnel.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,33 @@ def test_shouldFilterPacket_random(mock_platform_system, caplog, iface_with_node
123123
assert not ignore
124124

125125

126+
@pytest.mark.unit
127+
@patch("platform.system")
128+
@pytest.mark.parametrize(
129+
"packet",
130+
[
131+
b"",
132+
b"\x00" * 19,
133+
"not-a-packet",
134+
*[b"\x00" * 9 + bytes([protocol]) + b"\x00" * 10 for protocol in (1, 6, 17)],
135+
],
136+
)
137+
def test_shouldFilterPacket_rejects_malformed_packets(
138+
mock_platform_system, caplog, iface_with_nodes, packet
139+
):
140+
"""Malformed mesh payloads must not crash the tunnel reader."""
141+
iface = iface_with_nodes
142+
iface.noProto = True
143+
mock_platform_system.return_value = "Linux"
144+
145+
with caplog.at_level(logging.WARNING):
146+
with patch("socket.socket"):
147+
tun = Tunnel(iface)
148+
assert tun._shouldFilterPacket(packet)
149+
150+
assert re.search(r"Ignoring (malformed|truncated)", caplog.text)
151+
152+
126153
@pytest.mark.unitslow
127154
@patch("platform.system")
128155
def test_shouldFilterPacket_in_blacklist(

meshtastic/tunnel.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,14 @@ def onReceive(self, packet):
146146

147147
def _shouldFilterPacket(self, p):
148148
"""Given a packet, decode it and return true if it should be ignored"""
149+
# Packets received from the mesh are untrusted. IPv4 requires a
150+
# 20-byte header, and the protocol-specific fields below require the
151+
# first four bytes of the transport header. Without these checks a
152+
# truncated payload raises IndexError in the tunnel reader thread.
153+
if not isinstance(p, (bytes, bytearray)) or len(p) < 20:
154+
logger.warning("Ignoring malformed IP tunnel packet")
155+
return True
156+
149157
protocol = p[8 + 1]
150158
srcaddr = p[12:16]
151159
destAddr = p[16:20]
@@ -157,6 +165,9 @@ def _shouldFilterPacket(self, p):
157165
self.LOG_TRACE, f"Ignoring blacklisted protocol 0x{protocol:02x}"
158166
)
159167
elif protocol == 0x01: # ICMP
168+
if len(p) < subheader + 4:
169+
logger.warning("Ignoring truncated ICMP tunnel packet")
170+
return True
160171
icmpType = p[20]
161172
icmpCode = p[21]
162173
checksum = p[22:24]
@@ -168,6 +179,9 @@ def _shouldFilterPacket(self, p):
168179
# pingback = p[:12]+p[16:20]+p[12:16]+p[20:]
169180
# tap.write(pingback)
170181
elif protocol == 0x11: # UDP
182+
if len(p) < subheader + 4:
183+
logger.warning("Ignoring truncated UDP tunnel packet")
184+
return True
171185
srcport = readnet_u16(p, subheader)
172186
destport = readnet_u16(p, subheader + 2)
173187
if destport in self.udpBlacklist:
@@ -176,6 +190,9 @@ def _shouldFilterPacket(self, p):
176190
else:
177191
logger.debug(f"forwarding udp srcport={srcport}, destport={destport}")
178192
elif protocol == 0x06: # TCP
193+
if len(p) < subheader + 4:
194+
logger.warning("Ignoring truncated TCP tunnel packet")
195+
return True
179196
srcport = readnet_u16(p, subheader)
180197
destport = readnet_u16(p, subheader + 2)
181198
if destport in self.tcpBlacklist:

0 commit comments

Comments
 (0)