pwnfreehard

vkexchange

umdctf

Task: a Vulkan-based exchange service on Mesa lavapipe lets the user quote positions through vkUpdateDescriptorSets, but the chosen array element is attacker-controlled despite a single-descriptor binding. Solution: align the resulting out-of-bounds descriptor write onto the settlement clearing buffer descriptor, retarget it to an attacker-owned account buffer, then settle rounds to copy out the flag and read it back with audit_account.

$ ls tags/ techniques/
descriptor_set_layout_reasoningcross_object_descriptor_corruptionbuffer_descriptor_retargetinggpu_assisted_arbitrary_read

$ cat /etc/rate-limit

Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.

vkexchange — UMDCTF

Description

No original organizer description was preserved in the local workspace files.

English summary: the service exposes a small trading interface backed by Vulkan compute resources. The intended bug is not in the shader itself, but in how the host program updates storage-buffer descriptors for market quotes, which lets us corrupt a neighboring descriptor set and redirect a later GPU copy into memory we control.

Analysis

The important host helper is update_storage_desc(), which wraps vkUpdateDescriptorSets() with:

.dstBinding = binding, .dstArrayElement = array_elem, .descriptorCount = 1,

For quote_position(), array_elem comes directly from the user-supplied price_index. The program also enforces:

  • MIN_PRICE_INDEX = 32768
  • quote descriptor binding 0 has descriptorCount = 1

So every valid quote writes far beyond the single descriptor that should exist in quote_book binding 0. On lavapipe from Mesa 22.3.6 this becomes exploitable because:

  • each descriptor is 32 bytes
  • each descriptor set has an 88-byte header before its descriptor array
  • descriptor sets are individually allocated, but in practice land contiguously enough for deterministic cross-object corruption

That means a very large dstArrayElement can walk out of the quote_book allocation and start overwriting descriptors in a later descriptor set.

Why the first layout does not work

With one market configured as:

  • outcome_slots = 32768
  • memo_bytes = 0

the target settlement_book descriptors are misaligned relative to the oversized quote_book walk. Only partial overlaps at indices 32775 through 32777 are reachable, and those corruptions crash instead of producing a useful retargeted descriptor.

Fixing alignment with memo_bytes = 8

The trick is to create one market with memo_bytes = 8.

That adds a 40-byte shift in lavapipe's descriptor layout:

  • 32 bytes for the inline uniform descriptor
  • 8 bytes for the inline uniform storage itself

...

$ grep --similar

Similar writeups