# Marketplace may call onERC721Received() and create a lien during buyNftFromMarket(), creating divergence

Particle's onERC721Received whitelist checks operator, but Reservoir Router delegates to an unwhitelisted Executor, letting a marketplace buy create a second lien on the same NFT.

- Payout: Contest reward
- Severity: high
- Program: Particle Protocol
- Credited: minhquanym
- Disclosed: 2023-08-07
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2023-05-particle-findings/issues/3
- Library page: https://bugbountylibrary.com/r/c4%3A2023-05-particle%3AH-06
- Tags: domain:web3 impact:integrity kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:chained technique:source-review vuln:access-control vuln:business-logic

**Target:** NFT lending protocol onERC721Received / buyNftFromMarket marketplace integration
**Root cause:** onERC721Received gates push-based supply on registeredMarketplaces[operator], but the actual caller during a Reservoir Router buy is the Executor module, not the whitelisted Router, so the check is bypassed.
**Steps:**
1. Protocol whitelists Reservoir Router in registeredMarketplaces[]
2. buyNftFromMarket calls _execBuyNftFromMarket which low-level calls marketplace with tradeData
3. Router._executeInternal calls module.call(executionInfo.data) on the Executor
4. Executor safeTransferFrom's the NFT to Particle; operator is the Executor, not the Router
5. registeredMarketplaces[operator] is false, so data.length==64 path runs _supplyNft, creating a second lien on the same tokenId
**How found:** Code-reading of onERC721Received's operator-based whitelist against the documented Reservoir Router integration; noticed the Router delegates execution to a separate Executor contract that becomes the actual operator.
**Exploitation:** Attacker crafts a marketplace order whose execution data includes 64 bytes (price, rate) so that when the Executor transfers the NFT during buyNftFromMarket, onERC721Received treats it as a push-supply and mints a new lien, leaving two liens tracking one NFT and enabling divergence in accounting/liquidation.
**Insight:** When whitelisting a marketplace/router address for callback validation, verify who the actual msg.sender/operator is at the NFT transfer — aggregator routers delegate to per-order Executor modules, so the whitelisted address never appears as operator.
