Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions meshtastic/tests/test_tunnel.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,33 @@ def test_shouldFilterPacket_random(mock_platform_system, caplog, iface_with_node
assert not ignore


@pytest.mark.unit
@patch("platform.system")
@pytest.mark.parametrize(
"packet",
[
b"",
b"\x00" * 19,
"not-a-packet",
*[b"\x00" * 9 + bytes([protocol]) + b"\x00" * 10 for protocol in (1, 6, 17)],
],
)
def test_shouldFilterPacket_rejects_malformed_packets(
mock_platform_system, caplog, iface_with_nodes, packet
):
"""Malformed mesh payloads must not crash the tunnel reader."""
iface = iface_with_nodes
iface.noProto = True
mock_platform_system.return_value = "Linux"

with caplog.at_level(logging.WARNING):
with patch("socket.socket"):
tun = Tunnel(iface)
assert tun._shouldFilterPacket(packet)

assert re.search(r"Ignoring (malformed|truncated)", caplog.text)


@pytest.mark.unitslow
@patch("platform.system")
def test_shouldFilterPacket_in_blacklist(
Expand Down
17 changes: 17 additions & 0 deletions meshtastic/tunnel.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,14 @@ def onReceive(self, packet):

def _shouldFilterPacket(self, p):
"""Given a packet, decode it and return true if it should be ignored"""
# Packets received from the mesh are untrusted. IPv4 requires a
# 20-byte header, and the protocol-specific fields below require the
# first four bytes of the transport header. Without these checks a
# truncated payload raises IndexError in the tunnel reader thread.
if not isinstance(p, (bytes, bytearray)) or len(p) < 20:
logger.warning("Ignoring malformed IP tunnel packet")
return True

protocol = p[8 + 1]
srcaddr = p[12:16]
destAddr = p[16:20]
Expand All @@ -157,6 +165,9 @@ def _shouldFilterPacket(self, p):
self.LOG_TRACE, f"Ignoring blacklisted protocol 0x{protocol:02x}"
)
elif protocol == 0x01: # ICMP
if len(p) < subheader + 4:
logger.warning("Ignoring truncated ICMP tunnel packet")
return True
icmpType = p[20]
icmpCode = p[21]
checksum = p[22:24]
Expand All @@ -168,6 +179,9 @@ def _shouldFilterPacket(self, p):
# pingback = p[:12]+p[16:20]+p[12:16]+p[20:]
# tap.write(pingback)
elif protocol == 0x11: # UDP
if len(p) < subheader + 4:
logger.warning("Ignoring truncated UDP tunnel packet")
return True
srcport = readnet_u16(p, subheader)
destport = readnet_u16(p, subheader + 2)
if destport in self.udpBlacklist:
Expand All @@ -176,6 +190,9 @@ def _shouldFilterPacket(self, p):
else:
logger.debug(f"forwarding udp srcport={srcport}, destport={destport}")
elif protocol == 0x06: # TCP
if len(p) < subheader + 4:
logger.warning("Ignoring truncated TCP tunnel packet")
return True
srcport = readnet_u16(p, subheader)
destport = readnet_u16(p, subheader + 2)
if destport in self.tcpBlacklist:
Expand Down