Skip to content

SmallTable::insert() keeps a dangling capability to the freed buffer after resizing #97

Description

@nding0405

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:

Image

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);

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