# Multicall caller identity spoofing

Batch helpers that forward or synthesize caller identity, letting inner calls authorize the wrong sender

_Area: Smart contract access control and control flow. Seen in 3 disclosed reports; latest 2025-08-20. Updated 2026-10-23._

## How it works
A batch or multicall helper is meant to be the immediate caller of each inner call. The broken assumption is that the identity an inner contract authorizes against equals the real external caller. When the helper forwards the original caller, or derives identity from attacker-controlled calldata (ERC-2771 trailing bytes, tx.origin, a hardcoded `address(this)`), the inner check compares against a value the attacker chose. Any check of the form `caller == user-supplied field` or `allowance[caller][...]` then passes for a victim or privileged address.

## Where it shows up
- Multicall/aggregate/extMulticall that delegatecalls user data to itself or forwards arbitrary target+calldata.
- `_msgSender()` overrides returning tx.origin, the original caller, or the last 20 bytes of calldata (ERC-2771, meta-tx, GSN).
- Recursive or re-entrant paths that synthesize a caller (e.g. hardcoding `address(this)`) instead of threading the real sender.
- Downstream checks like `owner == srcChainSender` or `allowance[msg.sender]` where one side is caller-derived.

## How to review
Two properties must both hold. First, for every inner call the callee authorizes against the helper's own identity (the immediate caller), never the original external caller or calldata-derived bytes. Second, the helper itself must enforce the real user's approval or ownership for each referenced asset before forwarding. Check each batch entry point for per-call authorization. Confirm `_msgSender()` returns the immediate caller. For ERC-2771, verify the forwarder is trusted and that calldata length checks reject appended trailing bytes. Trace every place caller identity is compared to a user-supplied field.

## Variants seen
- ERC-2771 forwarder + Multicall: calldata trailing bytes spoof `_msgSender()` as the TIME/WETH pair, enabling a burn that inflates price [E2](https://bugbountylibrary.com/r/dhl%3A20231206-time.md).
- Multicall aggregate3 spoofs a victim as msg.sender; the token's `_msgSender()` returns the original caller instead of the immediate caller, draining approvals [E3](https://bugbountylibrary.com/r/dhl%3A20250820-multicallwithxera.md).
- Recursive `_lzCompose` hardcodes `address(this)` as srcChainSender, so `owner == srcChainSender` passes the allowance check [E1](https://bugbountylibrary.com/r/sherlock%3A2024-02-tapioca%3AH-11.md).

## Impact and severity
High to critical: full fee theft [E1](https://bugbountylibrary.com/r/sherlock%3A2024-02-tapioca%3AH-11.md), ~84.59 ETH drained from a pair [E2](https://bugbountylibrary.com/r/dhl%3A20231206-time.md), ~$17k approval drain [E3](https://bugbountylibrary.com/r/dhl%3A20250820-multicallwithxera.md). Worse when the helper is reachable by anyone and the spoofed identity is a victim with standing approvals or a privileged contract.

## Evidence
- E1: https://bugbountylibrary.com/r/sherlock%3A2024-02-tapioca%3AH-11.md
- E2: https://bugbountylibrary.com/r/dhl%3A20231206-time.md
- E3: https://bugbountylibrary.com/r/dhl%3A20250820-multicallwithxera.md
