Readonly Misuse
Detects semantic misuse of readonly accounts.
Readonly Misuse
Overview
Remediation Guide: How to Fix Readonly Misuse
The readonly misuse detector identifies Solana program functions that write to account data without first checking the is_writable flag. While the Solana runtime may reject transactions that modify readonly accounts in strict validation modes, relying on runtime enforcement alone is insufficient. Missing writable checks violate the semantic contract, cause unpredictable behavior in cross-program calls, and lead to transaction failures that are difficult to debug.
Sigvex scans for StoreAccountData statements where the target account has no prior IsWritable check.
Why This Is an Issue
- Transaction failures: writing to a readonly account causes the runtime to reject the transaction, wasting compute budget and user fees.
- Cross-program confusion: when accounts are passed through CPI, the writable flag propagates. A missing check in your program can cause downstream programs to fail unexpectedly.
- Semantic violation: even if the write succeeds in some contexts, it violates the intended access pattern and signals a logic error.
CWE mapping: CWE-754 (Improper Check for Unusual Conditions).
How to Resolve
Native Solana
pub fn update(accounts: &[AccountInfo], new_value: u64) -> ProgramResult {
let account = &accounts[0];
// Check writable flag before writing
if !account.is_writable {
return Err(ProgramError::InvalidAccountData);
}
let mut data = account.data.borrow_mut();
data[8..16].copy_from_slice(&new_value.to_le_bytes());
Ok(())
}
Anchor
#[derive(Accounts)]
pub struct Update<'info> {
// #[account(mut)] enforces writable at deserialization
#[account(mut)]
pub state: Account<'info, MyState>,
}
Examples
Vulnerable
let mut data = account.data.borrow_mut();
// No writable check — will fail if account is readonly
data[0..8].copy_from_slice(&value.to_le_bytes());
Fixed
require!(account.is_writable, AccountNotWritable);
let mut data = account.data.borrow_mut();
data[0..8].copy_from_slice(&value.to_le_bytes());
JSON Finding
{
"detector": "readonly-misuse",
"severity": "Medium",
"confidence": 0.65,
"title": "Account Write Without Writable Flag Check",
"description": "Account is written to without checking is_writable flag.",
"cwe": [754]
}
Detection Methodology
The detector tracks IsWritable expression evaluations and StoreAccountData statements. Any write to an account without a prior writable check generates a finding. The analysis is per-function and tracks account variables by ID.
Limitations
- The detector cannot distinguish between accounts that are always writable (by program design) and those that should be checked.
- Anchor programs receive reduced confidence since
#[account(mut)]enforces writability at the framework level. - Writable checks in called functions or prior instructions are not tracked.
Related Detectors
- Readonly Account Mutation - detects writes to accounts explicitly verified as readonly.