Skip to content

Latest commit

 

History

History
90 lines (65 loc) · 3.19 KB

File metadata and controls

90 lines (65 loc) · 3.19 KB

S010 — Upgrade Risk

  • Category: upgrades
  • Severity: Medium
  • Rule name: upgrade_risk

What it detects

S010 analyzes upgrade, admin, and initialization mechanisms. It walks impl blocks and reports:

  • Governance — an upgrade/admin function (e.g. upgrade, set_admin, anything matching the upgrade/admin heuristic) that mutates state without require_auth.
  • InitPattern — an initialization function with no re-init guard, so it can be called more than once.
  • Timelock — upgrade functions are checked for a delay/timelock reference so you can confirm the delay is actually enforced.

Why it matters

Upgrade and admin paths are the keys to the kingdom. An unauthenticated upgrade lets anyone swap the contract's WASM; an initialize callable twice lets an attacker re-seize ownership; an instant, single-key upgrade with no timelock gives one compromised key total control. These are the single-key takeover paths that turn a small key leak into a full contract compromise.

Vulnerable example

#![no_std]
use soroban_sdk::{contract, contractimpl, contracttype, Address, BytesN, Env};

#[contracttype]
pub enum DataKey {
    Admin,
}

#[contract]
pub struct Upgradeable;

#[contractimpl]
impl Upgradeable {
    // S010: upgrade path mutates state with no require_auth and no timelock.
    pub fn upgrade(env: Env, new_wasm_hash: BytesN<32>) {
        env.deployer().update_current_contract_wasm(new_wasm_hash);
    }
}

Safe example

#![no_std]
use soroban_sdk::{contract, contractimpl, contracttype, Address, BytesN, Env};

#[contracttype]
pub enum DataKey {
    Admin,
}

#[contract]
pub struct Upgradeable;

#[contractimpl]
impl Upgradeable {
    pub fn upgrade(env: Env, new_wasm_hash: BytesN<32>) {
        // Require the admin (ideally a multisig/timelock account) to authorize.
        let admin: Address = env.storage().instance().get(&DataKey::Admin).unwrap();
        admin.require_auth();
        env.deployer().update_current_contract_wasm(new_wasm_hash);
    }
}

CVSS-style risk rating

  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
  • Base score: 9.8
  • Rating: Critical

An unauthenticated upgrade path is a full takeover (Critical). The catalog assigns the category a Medium default because many S010 findings are governance-hardening recommendations (missing timelock, single-key admin) rather than open auth gaps; rate each finding by whether an auth guard is actually missing.

How to fix

  1. Add require_auth (or require_auth_for_args) on every upgrade and admin path, authorizing the admin principal.
  2. Guard initialization with an early return when an init flag already exists in storage.
  3. Use multi-signature governance and a timelock delay before upgrades take effect.
  4. Emit an event on every upgrade/admin change so it can be monitored off-chain.

Related rules

Related rules: S001, S008

References