Safe wallet MultiSend self-calls display Security Review

Description

The MultiSend and MultiSendCallOnly libraries replace a batch sub-transaction target of address(0) with the contract’s own address. Because these libraries run via delegatecall from the Safe, an inner action encoded with to == address(0) executes against the Safe itself. An action such as enableModule encoded this way therefore mutates the Safe even though its declared target is the zero address.

The Safe wallet displays such an action as a call to the zero address. The wallet identifies a transaction target by an address book name, or by a wallet name that applies when the target matches the wallet address. The zero address matches neither, so the action is shown as a call to the zero address with no name and no warning, whereas the same action targeting the Safe address is shown with the Safe’s name. The wallet applies no special handling to the zero address, and adding a name for it to the address book causes it to render with that name, which confirms the target is resolved purely by address lookup. The decoded method name is still shown, so the operation remains visible while the significance of its target is understated, and the wallet’s automated transaction scanner reports no threat for such a batch.

Impact

A malicious or compromised owner may propose a batch that encodes a Safe-mutating self-call, such as enableModule, as an inner action targeting address(0). The remaining owners reviewing the batch see a call to the zero address rather than an action on their own Safe, with no warning and no threat flagged by the scanner, which may lead them to treat it as a harmless call to an empty address and approve it. If approved, the action enables an attacker-controlled module that grants full control of the Safe.

Demonstration

A single batch contains two identical enableModule actions, one targeting the Safe and one targeting address(0). The first renders with the Safe’s name. The second renders as a zero-address call with no name and no warning.

image image

Status

The issue was communicated to the Safe team and resolved in the following PR:

How does the PR fix the issue?

The zero→Safe rule is a MultiSend execution semantic, so it’s applied at the display layer only — the gateway’s decoded data stays faithful to the calldata (it’s also consumed by the security scanner and other clients). A shared @safe-global/utils helper resolves a zero-address sub-target to the executing Safe; web and mobile both use it.

Notes:

  • The executing Safe = the Safe running the batch (address(this)). For a nested-Safe view that’s the child, not the connected Safe, so it’s derived from the rendered tx’s own safeAddress (with an explicit override for the one path that renders a preview without it).
  • Nothing that signs / proposes / verifies / executes a transaction reads the decoded to, so this is display-only — the safeTxHash and on-chain behaviour are unchanged.