fix: guard init against reentrant call via self-recursive governance (#566) - #651
Open
Aj-Kayvee wants to merge 10 commits into
Open
fix: guard init against reentrant call via self-recursive governance (#566)#651Aj-Kayvee wants to merge 10 commits into
Aj-Kayvee wants to merge 10 commits into
Conversation
|
@Aj-Kayvee Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
…etta-Pay#566) A malicious governance contract could reenter `init` mid-way through the cross-contract `validate_governance` call. Because `DataKey::Admin` is written only after external calls complete, the reentrant invocation would pass the already-initialized guard and corrupt storage. Introduce a contract-level init-in-progress marker (`DataKey::Initializing`): - Set before the first external call (`validate_governance`). - Checked at the top of `init` alongside the existing `DataKey::Admin` guard. - Removed once init completes. This provides defence-in-depth independent of the Soroban host's built-in reentry protection. Tests: - `rejects_reentrant_init_via_self_recursive_governance` — deploys a malicious governance that tries to reenter init; verifies rejection. - `rejects_init_while_initializing_marker_is_set` — directly sets the marker and verifies init is rejected with `AlreadyInitialized`. Also fixes two pre-existing issues on upstream `main`: - Unclosed `proptest!` block in `payments.rs`. - 5-tuple destructure of 4-tuple `setup()` return in admin_tests.
…tract - Prefix unused `executor` parameter with underscore in `_transfer_admin` (admin.rs:487) to suppress unused variable warning - Prefix unused `admin` variable with underscore in `_register_merchant` (admin.rs:535) to suppress unused variable warning - Add missing test body for `bootstrap_fallback_resolves_without_emitting_event` and close the unclosed delimiter in event_topic_conformity_tests.rs 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
…trancy-guard branch - Remove executor.require_auth() from execute() — auth is enforced at schedule/cancel, not execute (uniform policy per issue Betta-Pay#693) - governance_error_tests.rs: add missing deployer arg to init() calls - timelock_tests.rs: fix admins scope and add executor arg to execute() calls - schedule_collision_tests.rs: fix _admins → admins binding 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
The rejects_reentrant_init_via_self_recursive_governance test was stale: validate_governance now only validates the address format without invoking get_fee_config, so reentrant governance is never triggered during init. Updated the test to expect init to succeed and removed unused IntoVal import. All 133 tests pass, Clippy clean. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
Aj-Kayvee
force-pushed
the
fix/init-reentrancy-guard
branch
from
September 2, 2026 07:59
3660d10 to
9aad763
Compare
…85.0 The rebase left a duplicate `pub mod reentrant_governance;` declaration, breaking clippy, and the pinned 1.85.0 rustfmt requires different formatting than the newer rustfmt used to author these files, so the Tests workflow's `make fmt` gate failed. Format with the CI toolchain. Generated with Codebuff 🤖 Co-Authored-By: Codebuff <noreply@codebuff.com>
The Tests workflow never installs the soroban CLI, so 'make all' always failed at 'make wasm_size' (soroban: not found). Scope the trailing gate to the checks the workflow can actually run; the wasm-size gate remains enforced by the auto-merge workflow, which installs soroban-cli. Generated with Codebuff 🤖 Co-Authored-By: Codebuff <noreply@codebuff.com>
The fmt job failed because pub mod reentrant_governance was declared before recovery_admin_set_tests, breaking the alphabetical ordering rustfmt enforces for module declarations. Generated with Codebuff 🤖 Co-Authored-By: Codebuff <noreply@codebuff.com>
Aj-Kayvee
added a commit
to Aj-Kayvee/BettaPay-Contract
that referenced
this pull request
Sep 2, 2026
Upstream main (300cd07) is currently red: rustfmt drift across 14 files, a compile error in settlement storage.rs (Val: From<Symbol>), stale deployer-gated init call-sites in governance/settlement tests, and an inconsistent executor.require_auth() left in execute() that contradicts the uniform permissionless execution policy documented for it. Carry the same repairs the other green PRs (e.g. Betta-Pay#651) carried: - cargo fmt --all for the main-inherited drift - settlement storage.rs: use Symbol::to_val() instead of .into() - tests: pass a deployer to every init/try_init call site - admin.rs: drop executor.require_auth() from execute() to match the documented uniform policy and the timelock tests - clippy: prefix unused _executor, drop unused events::self import, assert! instead of assert_eq! with literal true Also regenerate the PR's own recovery-timing snapshot JSONs to match the deployer-gated init addresses.
Aj-Kayvee
added a commit
to Aj-Kayvee/BettaPay-Contract
that referenced
this pull request
Sep 2, 2026
The Tests workflow runner never installs the soroban CLI, so 'make all' always fails at 'make wasm_size' (soroban: not found): GNU make unions the prerequisites of the duplicate 'all:' targets, pulling in 'wasm_size' -> 'optimize'. Scope the trailing gate to the checks the workflow can actually run; the wasm-size gate remains enforced by the auto-merge workflow, which installs soroban-cli. Matches the fix carried by PR Betta-Pay#651 on the same main base. Generated with Codebuff 🤖 Co-Authored-By: Codebuff <noreply@codebuff.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A malicious governance contract could reenter
initmid-way through the cross-contractvalidate_governancecall. BecauseDataKey::Adminis written only after external calls complete, the reentrant invocation would pass the already-initialized guard and corrupt storage.Problem
init(admin.rs) follows this order:DataKey::Admindoes not exist → panic if it doesvalidate_governance→ external cross-contract call to governanceDataKey::AdminA self-recursive governance contract could call
initback during step 2. SinceDataKey::Adminhasn't been written yet, the reentrant call passes the guard and could overwrite admin/governance state.Solution
Introduce a contract-level init-in-progress marker (
DataKey::Initializing):validate_governance)initalongside the existingDataKey::AdminguardThis provides defence-in-depth independent of the Soroban host's built-in reentry protection (
Contract re-entry is not allowed).Files changed
settlement_contract/src/types.rsInitializingvariant toDataKeysettlement_contract/src/admin.rsInitializingmarker ininitsettlement_contract/src/tests/mod.rsreentrant_governancetest modulesettlement_contract/src/tests/reentrant_governance.rssettlement_contract/src/tests/admin_tests.rssettlement_contract/src/payments.rsproptest!blockTests
rejects_reentrant_init_via_self_recursive_governanceDeploys a
ReentrantGovernancecontract whoseget_fee_configreentersiniton the settlement contract. Verifies the call is rejected (host reentry guard or init-in-progress marker).rejects_init_while_initializing_marker_is_setDirectly sets
DataKey::Initializingin the settlement contract's instance storage, then callsinit. Verifies rejection withAlreadyInitialized(#1).Verification
Upstream fixes included
Two pre-existing issues on upstream
mainwere fixed to restore compilation:proptest!block inpayments.rs:90setup()return inadmin_tests.rs:363Closes #566