Skip to content

op_id child derivation is XOR-malleable and profile OpSeen is squattable (payout DoS) #68

Description

@0xdevcollins

Severity: HIGH · Contract · boundless-events + boundless-profile

Surfaced by the Scout + Almanax scans of e1f17937. Closes Almanax XOR-derived child op_ids allow crafted collisions, Global op_id allows cross-flow replay DoS, and Global op_id namespace enables id squatting DoS; also reframes the three Scout incorrect_exponentiation CRITICAL flags (the ^= is intentional XOR, not a botched power operator — the real defect is malleability).

Files

  • contracts/events/src/idempotency.rs:55-69derive_child, derive_child_indexed
  • contracts/profile/src/bootstrap.rs:36-48bootstrap_self
  • contracts/profile/src/storage.rs:189-200 — global OpSeen namespace

Attack path (verified against code)

  1. Events derives child op_ids for its cross-contract profile calls by XORing a 1-byte tag (and sub-index) into parent[0]/parent[1]. XOR is reversible.
  2. bootstrap_self lets any caller mark an arbitrary op_id seen in a global namespace keyed only by op_id (not caller/kind): user.require_auth() passes for the attacker's own address, then mark_seen(op_id).
  3. Attacker observes a pending select_winners/grant tx carrying the parent op_id, recomputes the deterministic child ids, and front-runs bootstrap_self(attacker, child_id).
  4. The events tx's profile call hits require_unseen → reverts OpAlreadySeen. Winner payout / reputation / earnings mutation is blocked. OpSeen is temporary() so it expires, but the attack is cheap and repeatable → sustained griefing of payouts.

Fix

  • Replace XOR derivation with a collision-resistant, non-malleable construction: env.crypto().sha256(parent ‖ [op_tag] ‖ [sub_idx] ‖ callee_contract_id) reduced to BytesN<32>, using the callee contract id as a domain separator.
  • Namespace the profile OpSeen set so an unprivileged bootstrap_self cannot collide with events-originated op_ids: key by (caller_domain, op_id), or require that privileged mutation op_ids are only settable by the configured events contract.
  • Deleting the XOR path also clears the Scout incorrect_exponentiation flags.

Tests

  • Two distinct parents that XOR-collide must now derive distinct children.
  • Attacker calls bootstrap_self with a crafted child id; a subsequent legitimate events-orchestrated select_winners still succeeds.
  • True replay of the same op_id stays rejected.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions