Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions contracts/iln_governance/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ pub enum GovernanceError {
/// Issue #531: the cross-contract execution call failed. The proposal
/// remains in `Passed` status so `execute_proposal` can be retried.
ExecutionFailed = 20,
/// Issue #603: set_execution_delay called before initialize -- admin
/// is not yet set, so there is no legitimate caller to authorize against.
NotInitialized = 21,
}

// ================================================================
Expand Down Expand Up @@ -806,16 +809,14 @@ impl GovContract {
) -> Result<(), GovernanceError> {
admin.require_auth();

if let Some(stored_admin) = env
let stored_admin: Address = env
.storage()
.instance()
.get::<StorageKey, Address>(&StorageKey::Admin)
{
if admin != stored_admin {
return Err(GovernanceError::Unauthorized);
}
} else {
env.storage().instance().set(&StorageKey::Admin, &admin);
.ok_or(GovernanceError::NotInitialized)?;

if admin != stored_admin {
return Err(GovernanceError::Unauthorized);
}

let old_value: u32 = env
Expand Down
35 changes: 35 additions & 0 deletions contracts/iln_governance/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1999,3 +1999,38 @@ fn test_execute_proposal_success_still_marks_executed() {
ProposalStatus::Executed
);
}

// ── Issue #603: set_execution_delay privilege escalation ──────────────────

#[test]
#[should_panic]
fn test_set_execution_delay_before_initialize_rejected() {
let env = Env::default();
env.mock_all_auths();
let contract_id = env.register_contract(None, GovContract);
let contract = GovContractClient::new(&env, &contract_id);
let attacker = Address::generate(&env);

// No initialize() call was made -- StorageKey::Admin was never set.
// The old vulnerable code would silently make `attacker` the admin here.
// The fixed code must reject this with NotInitialized instead.
contract.set_execution_delay(&attacker, &100_u32);
}

#[test]
#[should_panic]
fn test_set_execution_delay_wrong_admin_rejected() {
let t = setup();
let attacker = Address::generate(&t.env);

// Contract IS initialized (via setup()), but `attacker` is not the
// configured admin -- must be rejected with Unauthorized.
t.contract.set_execution_delay(&attacker, &100_u32);
}

#[test]
fn test_set_execution_delay_correct_admin_succeeds() {
let t = setup();
t.contract.set_execution_delay(&t.admin, &100_u32);
assert_eq!(t.contract.get_execution_delay(), 100_u32);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
{
"generators": {
"address": 2,
"nonce": 0
},
"auth": [
[]
],
"ledger": {
"protocol_version": 21,
"sequence_number": 0,
"timestamp": 0,
"network_id": "0000000000000000000000000000000000000000000000000000000000000000",
"base_reserve": 0,
"min_persistent_entry_ttl": 4096,
"min_temp_entry_ttl": 16,
"max_entry_ttl": 6312000,
"ledger_entries": [
[
{
"contract_data": {
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
"key": "ledger_key_contract_instance",
"durability": "persistent"
}
},
[
{
"last_modified_ledger_seq": 0,
"data": {
"contract_data": {
"ext": "v0",
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
"key": "ledger_key_contract_instance",
"durability": "persistent",
"val": {
"contract_instance": {
"executable": {
"wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"storage": null
}
}
}
},
"ext": "v0"
},
4095
]
],
[
{
"contract_code": {
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}
},
[
{
"last_modified_ledger_seq": 0,
"data": {
"contract_code": {
"ext": "v0",
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"code": ""
}
},
"ext": "v0"
},
4095
]
]
]
},
"events": [
{
"event": {
"ext": "v0",
"contract_id": null,
"type_": "diagnostic",
"body": {
"v0": {
"topics": [
{
"symbol": "fn_call"
},
{
"bytes": "0000000000000000000000000000000000000000000000000000000000000001"
},
{
"symbol": "set_execution_delay"
}
],
"data": {
"vec": [
{
"address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
},
{
"u32": 100
}
]
}
}
}
},
"failed_call": false
},
{
"event": {
"ext": "v0",
"contract_id": "0000000000000000000000000000000000000000000000000000000000000001",
"type_": "diagnostic",
"body": {
"v0": {
"topics": [
{
"symbol": "fn_return"
},
{
"symbol": "set_execution_delay"
}
],
"data": {
"error": {
"contract": 21
}
}
}
}
},
"failed_call": true
},
{
"event": {
"ext": "v0",
"contract_id": "0000000000000000000000000000000000000000000000000000000000000001",
"type_": "diagnostic",
"body": {
"v0": {
"topics": [
{
"symbol": "error"
},
{
"error": {
"contract": 21
}
}
],
"data": {
"string": "escalating Ok(ScErrorType::Contract) frame-exit to Err"
}
}
}
},
"failed_call": true
},
{
"event": {
"ext": "v0",
"contract_id": null,
"type_": "diagnostic",
"body": {
"v0": {
"topics": [
{
"symbol": "error"
},
{
"error": {
"contract": 21
}
}
],
"data": {
"vec": [
{
"string": "contract call failed"
},
{
"symbol": "set_execution_delay"
},
{
"vec": [
{
"address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
},
{
"u32": 100
}
]
}
]
}
}
}
},
"failed_call": false
},
{
"event": {
"ext": "v0",
"contract_id": null,
"type_": "diagnostic",
"body": {
"v0": {
"topics": [
{
"symbol": "error"
},
{
"error": {
"contract": 21
}
}
],
"data": {
"string": "escalating error to panic"
}
}
}
},
"failed_call": false
}
]
}
Loading
Loading