Skip to content

Latest commit

 

History

History
94 lines (68 loc) · 2.89 KB

File metadata and controls

94 lines (68 loc) · 2.89 KB

S009 — Unhandled Result

  • Category: logic
  • Severity: Medium
  • Rule name: unhandled_result

What it detects

S009 flags expressions that produce a Result but never consume it — a Result-returning call used as a statement and discarded. The rule walks contract functions and reports calls whose Result value is dropped instead of being handled with ?, match, if let, or an explicit .unwrap() / .expect().

Why it matters

A discarded Result means an error path is silently ignored: the call may have failed, but execution continues as if it succeeded. In a contract, that turns a failed sub-operation into a success the caller trusts — the classic "silent failure masquerading as success," which can leave balances, allowances, or flags in an inconsistent state with no signal that anything went wrong.

Vulnerable example

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

fn record_payment(env: &Env, from: &Address, amount: i128) -> Result<(), Error> {
    // ... may fail ...
    Ok(())
}

pub enum Error {
    Failed,
}

#[contract]
pub struct Ledger;

#[contractimpl]
impl Ledger {
    pub fn pay(env: Env, from: Address, amount: i128) {
        from.require_auth();
        // S009: the Result is discarded — a failure is silently ignored.
        record_payment(&env, &from, amount);
    }
}

Safe example

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

fn record_payment(env: &Env, from: &Address, amount: i128) -> Result<(), Error> {
    Ok(())
}

#[derive(Clone)]
pub enum Error {
    Failed,
}

#[contract]
pub struct Ledger;

#[contractimpl]
impl Ledger {
    pub fn pay(env: Env, from: Address, amount: i128) -> Result<(), Error> {
        from.require_auth();
        // Propagate the error with `?` instead of dropping it.
        record_payment(&env, &from, amount)?;
        Ok(())
    }
}

CVSS-style risk rating

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

Exploitation depends on forcing the ignored sub-operation to fail (higher complexity), but the consequence — committed state that assumes a failed step succeeded — is a high integrity impact.

How to fix

  1. Handle every Result with ? to propagate, or match / if let to branch on the error.
  2. If a failure is genuinely irrelevant, make the intent explicit (e.g. let _ = ...; with a comment) rather than silently dropping it.
  3. For cross-contract calls, use try_invoke_contract and inspect the returned Result.

Related rules

Related rules: S002, S001

References