Summary
SmallTable::insert() does not update the table’s buffer member after resizing. When the ninth element is inserted, the table keeps a dangling capability to the freed allocation, which can crash the Firewall.
Root cause
resize_if_needed() returns a new allocation (and does not update the member buffer), but SmallTable::insert() only stores it in the local variable currentBase. So the returned capability is never assigned back to the buffer member.
Affected component
lib/firewall/firewall.cc, the internal SmallTable implementation is used by the firewall endpoint tables (e.g. firewall_add_udpipv4_endpoint uses the endpoint table).
Impact
SmallTable initially has a capacity of 8. The ninth insertion triggers a resize: the old allocation is freed and a larger array is allocated, but buffer is left pointing to the freed allocation. The old allocation is marked in the revocation bitmap, and the stale capability’s tag becomes 0.
Functions such as firewall_add_udpipv4_endpoint() may grow the endpoint table. Later access to the table through the stale buffer capability can trigger a hardware exception and kill this compartment.
PoC
void test_small_table_growth()
{
Debug::log("Testing SmallTable growth");
SmallTable<int> table;
for (int i = 0; i < 8; ++i)
{
table.insert(i);
}
Debug::log(
"Before ninth insertion: size {}, capacity {}",
table.size(),
table.capacity());
table.insert(8);
Debug::log(
"After ninth insertion: size {}, capacity {}",
table.size(),
table.capacity());
Debug::log("About to dereference dangling table");
int first = *table.begin();
Debug::log("Dereference survived, value {}", first);
}
Output:
The ninth insertion leaves the table with size 9 and capacity 8 (capacity > size). And later access triggers the fault.
Suggested fix
Copy the returned capability back to the table member before updating the size:
T *currentBase = static_cast<T *>(
resize_if_needed(base(), currentSize, capacity(), sizeof(T)));
buffer = currentBase;
SmallTableBase::insert(
currentBase,
currentSize * sizeof(T),
&element,
sizeof(T));
set_size(currentSize + 1);
Summary
SmallTable::insert()does not update the table’sbuffermember after resizing. When the ninth element is inserted, the table keeps a dangling capability to the freed allocation, which can crash the Firewall.Root cause
resize_if_needed()returns a new allocation (and does not update the memberbuffer), butSmallTable::insert()only stores it in the local variablecurrentBase. So the returned capability is never assigned back to thebuffermember.Affected component
lib/firewall/firewall.cc, the internalSmallTableimplementation is used by the firewall endpoint tables (e.g.firewall_add_udpipv4_endpointuses the endpoint table).Impact
SmallTableinitially has a capacity of 8. The ninth insertion triggers a resize: the old allocation is freed and a larger array is allocated, butbufferis left pointing to the freed allocation. The old allocation is marked in the revocation bitmap, and the stale capability’s tag becomes 0.Functions such as
firewall_add_udpipv4_endpoint()may grow the endpoint table. Later access to the table through the stalebuffercapability can trigger a hardware exception and kill this compartment.PoC
Output:
The ninth insertion leaves the table with size 9 and capacity 8 (capacity > size). And later access triggers the fault.
Suggested fix
Copy the returned capability back to the table member before updating the size: