Skip to content

[Bug]: Root-origin gates are transaction-wide, so any contract a Root call reaches inherits DotNS governance #300

Open

Description

@re-gius

Component

Other

Priority

P1

What happened?

Four contracts gate governance on SystemUtils.originIsRoot(): DotnsRegistrarController.sol:296, DotnsPopController.sol:936, PopRules.sol:90, DotnsNameWhitelist.sol:109.

originIsRoot reads the transaction origin, not the caller (upstream it is self.origin == Origin::Root from polkadot-sdk substrate/frame/revive/src/exec.rs:2431), with no frame comparison. The authority is therefore ambient for the whole call stack, so any code reached during a Root transaction holds full DotNS governance without holding any DotNS key. The trigger need not involve DotNS: any Root Revive.call into any Asset Hub contract is enough.

That grants the whitelist's ten onlyGovernance functions, setShortNamesEnabled, the whole PoP issuance surface, and registerReserved's Root branch, which skips both isGrantedTo and consume and passes 0 as the charge — any available label to any owner for free, including the 3-to-8 band the priced path is closed out of. PoP names are soulbound, so issuance is permanent without a registrar upgrade.

Expected behavior

Governance authority should require the Root dispatch to be aimed at DotNS, not merely that a Root transaction is somewhere on the stack.

DEPLOYMENTS.md:290 states the stronger guarantee today: "No signed account can do any of them, the contract owner included" and "no single key can grant a name; a grant costs a referendum", and neither holds while the gate is transaction-wide.

Reproduction

No path inside DotNS produces the trigger; it needs a Root dispatch reaching code governance does not control.

Additional context

callerIsRoot() was not usable here because of a pallet-revive bug, paritytech/polkadot-sdk#13412. Behind a UUPS proxy it returns false even for a direct Root dispatch, which is why originIsRoot() was chosen. The same bug makes it return true in a contract reached through a delegate call followed by a regular call, so any contract that a Root dispatch reaches can pass a callerIsRoot() check in another contract.

The fix is paritytech/polkadot-sdk#13413. Once it is live on Asset Hub, callerIsRoot() is true only in the contract that Root dispatched to, including behind its proxy, and the four gates can switch from originIsRoot() to callerIsRoot() with an upgrade of each contract.

PR #302 routes Root through DotnsRootGateway, which checks callerIsRoot(). Until #13413 ships, a Root-dispatched contract can pass that check with a delegate call followed by a regular call, so the gateway does not close this issue. Once #13413 ships, the gateway is no longer needed.

Activity

  1. re-gius commented on Sep 25, 2026

    @re-gius
    CollaboratorAuthor

    PR #302 needs further discussion, so this has been moved out of the scope for R1 and will be addressed for R2 - removing from milestone

  2. removed this from the 5 - Release to MainNet milestone on Sep 25, 2026
  3. self-assigned this
    on Oct 6, 2026
  4. re-gius commented on Oct 8, 2026

    @re-gius
    CollaboratorAuthor

    Update: the pallet-revive fix, paritytech/polkadot-sdk#13413, is merged on polkadot-sdk master (not backported), and paritytech/polkadot-sdk#13412 is closed. It reaches Asset Hub with the first polkadot-sdk release that includes it and the Asset Hub runtime upgrade that picks it up. From then on, the four gates can switch from originIsRoot() to callerIsRoot() with an upgrade of each contract, and the DotnsRootGateway from #302 is no longer needed.

  5. valentyna-kozlova commented on Oct 9, 2026

    @valentyna-kozlova

    Decided on 9 Oct meeting that this should be done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

P1Secondary PrioritysecurityRelated to securitytype: bugBug report

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions