Role-Based Access Control
Detects role-based access control vulnerabilities including missing role checks, escalation risks, and hardcoded permissions.
Role-Based Access Control
Overview
Remediation Guide: How to Fix Role-Based Access Control Issues
The RBAC detector identifies vulnerabilities in role-based access control implementations on Solana. It flags privileged operations that execute without checking the caller’s role, hardcoded role permissions that cannot be updated after deployment, role grant operations without proper authorization (escalation risk), and missing role revocation mechanisms. These issues allow unauthorized users to perform restricted operations or escalate their privileges.
Why This Is an Issue
Role-based access control separates privileges into distinct roles (admin, operator, user) so that each account only has the minimum necessary permissions. Flawed RBAC leads to:
- Missing role checks (CWE-284): Privileged operations execute without verifying the caller holds the required role. Any user can perform admin actions.
- Hardcoded roles (CWE-798): Role definitions embedded in bytecode cannot be updated without redeploying the program. This prevents responding to compromised keys.
- Unvalidated role grants (CWE-269): If role grants are not restricted to admins, any user can grant themselves elevated privileges.
- Missing revocation: Without the ability to revoke roles, compromised accounts retain their privileges indefinitely.
How to Resolve
Native Solana
use solana_program::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey};
#[repr(u8)]
pub enum Role { Admin = 0, Operator = 1, User = 2 }
pub fn process_admin_action(accounts: &[AccountInfo]) -> ProgramResult {
let caller = &accounts[0];
let role_account = &accounts[1];
if !caller.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
// Load role from on-chain account
let role_data = role_account.try_borrow_data()?;
let stored_key = Pubkey::try_from(&role_data[0..32])
.map_err(|_| ProgramError::InvalidAccountData)?;
let role = role_data[32];
// Verify caller matches the role account
if *caller.key != stored_key {
return Err(ProgramError::InvalidArgument);
}
// Verify caller has admin role
if role != Role::Admin as u8 {
return Err(ProgramError::InvalidArgument);
}
// Safe to proceed
Ok(())
}
Anchor
#[derive(Accounts)]
pub struct AdminAction<'info> {
pub caller: Signer<'info>,
#[account(
constraint = role_account.authority == caller.key() @ ErrorCode::Unauthorized,
constraint = role_account.role == Role::Admin @ ErrorCode::InsufficientRole
)]
pub role_account: Account<'info, RoleAssignment>,
}
#[account]
pub struct RoleAssignment {
pub authority: Pubkey,
pub role: Role,
}
Examples
Vulnerable Code
pub fn update_config(accounts: &[AccountInfo], new_fee: u64) -> ProgramResult {
let caller = &accounts[0];
let config = &accounts[1];
// VULNERABLE: no role check -- any account can update config
let mut data = config.try_borrow_mut_data()?;
data[0..8].copy_from_slice(&new_fee.to_le_bytes());
Ok(())
}
Fixed Code
pub fn update_config(accounts: &[AccountInfo], new_fee: u64) -> ProgramResult {
let caller = &accounts[0];
let config = &accounts[1];
let role_account = &accounts[2];
if !caller.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
// Verify caller has operator or admin role
let role_data = role_account.try_borrow_data()?;
let role_key = Pubkey::try_from(&role_data[0..32])
.map_err(|_| ProgramError::InvalidAccountData)?;
if *caller.key != role_key || role_data[32] > 1 {
return Err(ProgramError::InvalidArgument);
}
drop(role_data);
let mut data = config.try_borrow_mut_data()?;
data[0..8].copy_from_slice(&new_fee.to_le_bytes());
Ok(())
}
Sample Sigvex Output
{
"detector_id": "rbac",
"severity": "high",
"confidence": 0.78,
"title": "Missing Role Check Before Privileged Operation",
"description": "Privileged operation (v5) executed without checking caller's role. Unauthorized users could perform restricted operations.",
"location": { "function": "update_config", "block": 0, "statement": 1 },
"cwe": 284
}
Detection Methodology
The detector analyzes role-based access patterns in the function’s intermediate representation:
- Role check identification: Tracks
CheckKeyandCheckOwnerstatements that indicate the caller’s role or authority is being validated. - Privileged operation scanning: Identifies
StoreAccountDataandTransferLamportsas operations requiring role-based authorization. - Hardcoded role detection: Flags constant assignments to role-like variables in the parameter range, indicating roles embedded in bytecode.
- Role grant analysis: Detects role assignment operations and checks whether the caller’s authority is verified before granting roles (prevents escalation).
- Revocation check: If role grants exist but no role revocation pattern is found, a missing-revocation finding is generated.
- Context adjustment: Confidence is reduced for Anchor programs and read-only functions.
Limitations
False positives:
- Anchor programs where account constraints enforce role checks before the handler.
- Programs that use PDA derivation to implicitly encode roles (the PDA seeds contain role information).
False negatives:
- Custom role systems with non-standard data layouts that heuristics do not recognize.
- Role validation performed through CPI to a separate access control program.
Related Detectors
- Admin Key Management — admin key handling without role separation
- Signer Authority Role — signer present but no role validation
- Instruction Sender Validation — missing sender authority