# Wrong items length assertion in basic order

Off-by-one in Seaport's basic order consideration length assertion lets a fulfiller skip the last consideration item by crafting calldata with empty additionalRecipients.

- Payout: Contest reward
- Severity: medium
- Program: OpenSea
- Credited: 0xsanson
- Disclosed: 2022-08-30
- Write-up quality: 8/10
- Original: https://github.com/code-423n4/2022-05-opensea-seaport-findings/issues/129
- Library page: https://bugbountylibrary.com/r/c4%3A2022-05-opensea-seaport%3AM-02
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:medium tech:evm tech:solidity technique:incomplete-fix technique:source-review vuln:accounting-error vuln:business-logic

**Target:** BasicOrderFulfiller.sol _prepareBasicFulfillmentFromCalldata
**Root cause:** The assertion passes additionalRecipients.length + 1 instead of additionalRecipients.length, so a length one smaller than totalOriginalAdditionalRecipients is accepted, and the inline-assembly loop reads consideration items out-of-bounds.
**Steps:**
1. Craft a basic order with two considerationItems; note consideration[1] amount X and recipient Y.
2. Set calldata additionalRecipients length to 0 and shift signature head pointer to 0x260.
3. Set signature length field to X and first 32 bytes of signature body to Y, padding with zeroes.
4. Call validate with the correct signature beforehand so signature isn't re-read during fulfillment.
5. Call fulfillBasicOrder with crafted calldata; assertion passes, orderHash matches, only offer and consideration[0] transfer.
**How found:** Code review of the length assertion in _prepareBasicFulfillmentFromCalldata spotted the erroneous +1, then traced how the inline-assembly loop at L506 reads consideration items out-of-bounds when additionalRecipients.length < totalOriginalAdditionalRecipients.
**Exploitation:** Attacker pre-validates the order with a valid signature, then submits crafted calldata where additionalRecipients is empty and the signature length equals the skipped item's amount X (with Y as first 32 bytes). The orderHash is computed correctly from calldata pointers, signature is skipped since already validated, and transfers only cover offer plus consideration[0], leaving the last recipient unpaid.
**Insight:** When an assertion guards an assembly loop that reads calldata by pointer, check the exact off-by-one in the bound; a +1 slack lets the loop read attacker-controlled calldata as consideration items, and pre-validation can bypass signature re-checks so the crafted signature bytes are never verified.
