Review: Memory Pool Allocator Written by an AI Assistant

Your networking team asked an AI coding assistant to replace malloc for packet buffers in the device firmware. It opened this pull request. You are the reviewer, and nothing merges without your approval.

The assistant's description:

Adds a fixed-size block allocator so the network stack never touches the heap. O(1) alloc and free through an intrusive free list, no per-block header, and pool_owns() guards pool_free() against foreign pointers.

Where it runs: STM32H7 (Cortex-M7) running FreeRTOS. The Ethernet receive interrupt calls pool_alloc() for each frame and queues it to the protocol task, which calls pool_free() when done. The pool is created with pool_init(&rx_pool, rx_mem, sizeof rx_mem, sizeof(struct pkt_hdr) + 1522), giving 1538-byte blocks; rx_mem is a static uint8_t array and struct pkt_hdr holds a uint64_t timestamp. The Ethernet DMA needs word-aligned buffers. Several error paths in the stack can free the same packet twice.

Your task

  • Flag every problem you would block the merge for. Click a line number to comment, and Shift-click to cover a range.
  • For each one, choose the issue type and severity, and explain what fails and how to fix it.
  • You are scored on the problems you catch, the right issue type and a clear explanation. Missing a critical problem fails the round.

Aim for 20 minutes, as in a real review round.

Block 1 starts at base + 1538. Is that a valid address for a struct pkt_hdr?

The receive interrupt can fire between any two lines of pool_free(). Pick the worst place.

What does pool_owns() return for base + block_count * block_size? For base + 3?

The same packet is freed twice. What do the next two pool_alloc() calls return?

A traffic burst empties the pool and then someone calls pool_calloc().

The allocator is neat, but it hands out misaligned blocks and its free list is corrupted by the very interrupt it was written for. Either is enough to block the merge.

  1. Blocks are not aligned (lines 25–30, critical). block_size is only raised to the size of a pointer, never rounded up to an alignment, and rx_mem is a byte array with no alignment guarantee. With 1538-byte blocks every other block starts at an address that is 2 mod 8, so the uint64_t timestamp and the free-list pointer are misaligned: undefined behaviour, a fault on LDRD/STRD/LDM, and a buffer the Ethernet DMA cannot use. Round block_size up to _Alignof(max_align_t) or the DMA alignment, and align base (or reject unaligned memory).
  2. Free list is not interrupt-safe (lines 69–72, critical). pool_free() reads free_list, links the block, then writes free_list. If the receive interrupt runs pool_alloc() in between, it takes the head block, and the task then puts it back as b->next: the block is in use and on the free list, so it is handed out twice. in_use++ and in_use-- also lose updates. Mask interrupts around both operations (taskENTER_CRITICAL() in the task, taskENTER_CRITICAL_FROM_ISR() in the ISR). A lock-free stack with compare-and-swap would also need ABA protection.
  3. Double free is not detected (lines 67–68, major). Freeing the same block twice pushes it twice, so the next two allocations return the same buffer and two packets share memory; in_use also wraps below zero. pool_owns() checks range only. Keep a free bitmap or a magic word in debug builds and assert, or at least check that the block is not already on the list.
  4. pool_owns accepts the end and interior pointers (line 45, major). <= accepts the one-past-the-end address, and any pointer inside a block passes too. pool_free() then links memory outside the pool, or the middle of a block, and later allocations overlap other data. Use < and check (c - base) % block_size == 0.
  5. pool_calloc does not handle an empty pool (lines 60–61, major). When the pool is exhausted, pool_alloc() returns NULL and memset(NULL, ...) faults, exactly during the traffic burst that emptied the pool. Return NULL before clearing.
Next Post Previous Post
No Comment
Add Comment
comment url