Skip to content

packet_filter_ipv4 in firewall.cc allows a packet with an invalid IHL to bypass the firewall filter. #96

Description

@nding0405

Summary

The packet_filter_ipv4() filter is vulnerable in the IP header length sanity check, it is not exploitable, but better to fix it to avoid the "Swiss Cheese".

Root cause

packet_filter_ipv4() uses sizeof(ipv4Header) where ipv4Header is a pointer

Affected component

lib/firewall/firewall.cc, IPv4 TCP/UDP filtering path in packet_filter_ipv4().

Impact

Malformed IPv4 packets with invalid IHL may pass the header-length check. Any client can build a malformed packet and make the firewall parse fake TCP/UDP fields from inside the IPv4 header to pass this filter. But later checks will intercept the bad-IHL packet, so this bug is not exploitable.

PoC

I added a local test that calls the packet_filter_ipv4() with a crafted invalid IPv4 packet:

myTest.cc
void __cheri_compartment("bad_ihl_test") run_test()
{
	Debug::log("Calling firewall bad-IHL test");

	bool triggered = firewall_test_bad_ihl_filter();

	if (triggered)
	{
		Debug::log("FINAL: BUG TRIGGERED");
	}
	else
	{
		Debug::log("FINAL: bug not triggered");
	}

	while (true) {}
}
firewall.cc (temporary test)
bool __cheri_compartment("Firewall") firewall_test_bad_ihl_filter()
{
	Debug::log("Running packet_filter_ipv4 test");

	EndpointsTable<uint32_t>::instance().clear(IPProtocolNumber::TCP);
	currentClientCount = 0;

	uint16_t serverPort = htons(80);
	EndpointsTable<uint32_t>::instance().add_server_port(serverPort);

	uint8_t p[64] = {};

	/**
	 * Invalid IPv4 header:
	 *   Version = 4
	 *   IHL     = 4
	 *   offset  = 4 * 4 = 16
	 *
	 * Valid IPv4 header must be at least 20 bytes. (IHL >= 5)
	 */
	p[0] = 0x44; // Version = 4 ; IHL = 4

	// Total length = 64.
	p[2] = 0x00;
	p[3] = 64;

	p[8] = 64; // TTL
	p[9] = static_cast<uint8_t>(IPProtocolNumber::TCP); // protocol = TCP

	// Source IP.
	p[12] = 8;
	p[13] = 8;
	p[14] = 8;
	p[15] = 8;

	/**
	 * Since offset=16, buggy filter reads fake TCP ports here.
	 *
	 * fake source port = 0x0035 = 48 + 5 = 53
	 * fake dest port   = 0x0050 = 80
	 */
	p[16] = 0x00;
	p[17] = 0x35;
	p[18] = 0x00;
	p[19] = 0x50;

	/**
	 * fake TCP flags at offset + 12 = 28.
	 * 0x5002 = SYN, no ACK.
	 */
	p[28] = 0x50;
	p[29] = 0x02;

	auto *ip = reinterpret_cast<const IPv4Header *>(p);

	Debug::log("bad packet: body_offset={}, IPv4Header={}, pointerSize={}",
	           ip->body_offset(),
	           sizeof(IPv4Header),
	           sizeof(ip));

	auto result = packet_filter_ipv4(
	  p,
	  sizeof(p),
	  &IPv4Header::sourceAddress,
	  &TCPUDPCommonPrefix::destinationPort,
	  &TCPUDPCommonPrefix::sourcePort,
	  false);

	Debug::log("packet_filter_ipv4 returned {}", static_cast<int>(result));

	if ((result & ForwardFlags::ForwardNetworkStack) != 0)
	{
		Debug::log("BUG TRIGGERED:  packet_filter_ipv4 accepted bad IHL packet");
		return true;
	}

	Debug::log("Bad IHL packet was dropped");
	return false;
}

Here is the output:

Image

On the buggy code, the filter accepts the invalid packet.

Suggested fix

Replace:

if (ipv4Header->body_offset() < sizeof(ipv4Header))

with

if (ipv4Header->body_offset() < sizeof(*ipv4Header))

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions