Skip to content

Management note scripts load and hash full note storage before validating num_storage_items, enabling avoidable resource exhaustion #3562

Description

@onurinanc

Root Cause: owner_action and faucet_policy_action call active_note::get_storage before rejecting invalid storage lengths, forcing O(n) hashing/memory writes on oversized notes.

Toy example:

  • An attacker publishes a public OwnerAction note with the correct script root but num_storage_items = 1024.
  • A network executor attempts to execute the note.
  • The script calls active_note::get_storage, which pipes and hashes all 1024 items.
  • The script later rejects due to an unexpected storage length, after paying the full storage-processing cost.

Location: crates/miden-standards/asm/standards/notes/owner_action.masm#L61-L97

The OwnerAction and FaucetPolicyAction note scripts are intended to dispatch management actions based on a selector stored in note storage (see owner_action::main and faucet_policy_action::main). At the protocol layer, note storage can contain up to MAX_NOTE_STORAGE_ITEMS = 1024.

Both scripts unconditionally call active_note::get_storage at the start of main, which triggers write_storage_to_memory. This helper loads the full storage preimage from the advice map (adv.push_mapvaln.8), writes all elements to memory, and recomputes the storage commitment via mem::pipe_elements_preimage_to_memory. Only after this work do the scripts read the selector and enforce the expected storage lengths (e.g., execute_transfer_ownership and load_policy_root_window).

As a result, a public note with a valid management script root but an oversized storage length will always force bounded but avoidable O(num_storage_items) hashing and memory writes before failing. Repeated submission of such notes can degrade the throughput of any executor/prover that automatically attempts execution of public management notes without prefiltering by storage length.

Consider introducing and using a lightweight active_note helper that returns (NOTE_STORAGE_COMMITMENT, num_storage_items) without piping the full storage to memory (modeled after input_note::get_storage_info), and rejecting invalid lengths before calling active_note::get_storage. For OwnerAction, an upfront check that num_storage_items is in {1, 3} can reject all oversized notes without reading the selector; for FaucetPolicyAction, enforcing num_storage_items == 5 upfront avoids processing arbitrarily long storages.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions