Skip to content

kora-lib: Unrecognized Instruction Types Create Empty Stubs That Bypass Fee Payer Policy

Moderate severity GitHub Reviewed Published Mar 11, 2026 in solana-foundation/kora

Package

cargo kora-lib (Rust)

Affected versions

< 2.0.5

Patched versions

2.0.5

Description

Summary

When inner CPI instructions use instruction types not recognized by Kora's parser (including Token-2022 extension instructions like ConfidentialTransfer, TransferFeeExtension::WithdrawWithheldTokens, etc.), they are reconstructed as stub instructions with empty accounts and empty data. These stubs fail deserialization during fee payer policy validation and are silently skipped, meaning any fee payer usage within those instructions goes completely unchecked.

Severity

Medium

Affected Component

  • File: crates/lib/src/transaction/instruction_util.rs
  • Functions: reconstruct_system_instruction(), reconstruct_spl_token_instruction()
  • Lines: 750–753, 1187–1189

Root Cause

The instruction reconstruction functions have a catch-all _ => arm for unrecognized instruction types that creates a stub CompiledInstruction with the correct program_id_index but empty accounts and empty data. When this stub reaches the fee payer policy parsing (parse_system_instructions / parse_token_instructions), deserialization of empty data fails. The parsing functions also have a catch-all _ => {} that silently skips the failed instruction. The result: the instruction exists in all_instructions (so program allowlist checks pass), but fee payer policy is never enforced on it.

Vulnerable Code

Stub Creation

// crates/lib/src/transaction/instruction_util.rs:750-753
// System program — unrecognized instruction type:
_ => {
    log::error!("Unsupported system instruction type: {}", instruction_type);
    Ok(Self::build_default_compiled_instruction(program_id_index))
}

// crates/lib/src/transaction/instruction_util.rs:1187-1189
// SPL Token program — unrecognized instruction type:
_ => {
    log::error!("Unsupported token instruction type: {}", instruction_type);
    Ok(Self::build_default_compiled_instruction(program_id_index))
}

The stub builder:

pub fn build_default_compiled_instruction(program_id_index: u8) -> CompiledInstruction {
    CompiledInstruction {
        program_id_index,
        accounts: vec![],  // <-- No accounts
        data: vec![],       // <-- No data
    }
}

Silent Skip During Policy Parsing

// In parse_system_instructions:
if let Ok(system_instruction) = bincode::deserialize::<SystemInstruction>(&instruction.data) {
    match system_instruction {
        // ... known types handled ...
        _ => {}  // <-- Unrecognized: silently skipped
    }
}
// If deserialize fails (empty data), the entire `if let Ok` block is skipped.
// The instruction is not added to any policy check map.

// In parse_token_instructions:
if let Ok(token_instruction) = TokenInstruction::unpack(&instruction.data) {
    match token_instruction {
        // ... known types handled ...
        _ => {}  // <-- Unrecognized: silently skipped
    }
}
// Same: empty data causes unpack to fail, instruction completely invisible to policy.

Proof of Concept

Affected Token-2022 Extension Instructions

The following Token-2022 extension instruction types are NOT handled by Kora's parser and would produce empty stubs:

Extension Instruction Risk if Fee Payer is Authority
TransferFeeExtension WithdrawWithheldTokensFromMint Fee payer as withdraw authority can drain withheld fees
TransferFeeExtension WithdrawWithheldTokensFromAccounts Same
TransferFeeExtension HarvestWithheldTokensToMint Fee collection manipulation
ConfidentialTransfer Transfer Hidden transfer amounts bypass fee tracking
ConfidentialTransfer Withdraw Hidden withdrawals
InterestBearingMint UpdateRate Fee payer as rate authority can manipulate interest
TransferHook Execute Arbitrary hook execution
GroupMemberPointer Update Metadata manipulation
MetadataPointer Update Metadata manipulation
PermanentDelegate Transfer (via delegate) Delegate-based unauthorized transfers

Code Path Trace

1. Transaction contains an inner CPI instruction:
   Program: Token-2022
   Type: "withdrawWithheldTokensFromMint" (TransferFeeExtension)
   Accounts: [fee_payer (as withdraw_withheld_authority), mint, destination]

2. RPC returns this as a Parsed inner instruction

3. reconstruct_spl_token_instruction() is called:
   - instruction_type = "withdrawWithheldTokensFromMint"
   - No match in the known types (transfer, transferChecked, burn, etc.)
   - Falls through to _ => arm
   - Returns: CompiledInstruction { program_id_index, accounts: [], data: [] }

4. Stub is added to all_instructions
   → validate_programs() sees Token-2022 program ID → PASS (allowed)
   → validate_disallowed_accounts() sees no accounts in the stub → PASS

5. parse_token_instructions() processes the stub:
   - TokenInstruction::unpack(&[]) → Err (empty data)
   - if let Ok(...) block skipped entirely
   - Instruction not added to any ParsedSPLInstructionType map

6. validate_fee_payer_usage() iterates parsed SPL instructions:
   - No entry for "withdrawWithheldTokensFromMint"
   - Fee payer's usage as withdraw_withheld_authority is NEVER checked

7. Transaction is signed by Kora

8. On-chain: fee_payer (as withdraw authority) withdraws withheld
   transfer fees from the mint to attacker's account

Verifiable Test

#[test]
fn test_unrecognized_instruction_produces_empty_stub() {
    // Simulate what happens for an unrecognized Token-2022 instruction
    let program_id_index: u8 = 3; // Token-2022 at index 3

    // This is what the catch-all arm produces:
    let stub = IxUtils::build_default_compiled_instruction(program_id_index);

    assert_eq!(stub.accounts.len(), 0);  // No accounts
    assert_eq!(stub.data.len(), 0);      // No data

    // Attempt to parse it:
    let result = TokenInstruction::unpack(&stub.data);
    assert!(result.is_err());  // Cannot parse empty data

    // Therefore: fee payer policy is never applied to this instruction
    // The fee payer could be the withdraw_withheld_authority in the
    // REAL instruction, but the stub has zero accounts — invisible.
}

Impact

  • Fee Payer Policy Bypass: Token-2022 extension instructions that use the fee payer as an authority are invisible to policy enforcement.
  • Forward-Looking Risk: As Solana and SPL Token-2022 add new instruction types, they will automatically bypass all fee payer policy checks in Kora.
  • Precondition: Requires the fee payer to hold some authority role (e.g., withdraw_withheld_authority, permanent_delegate) for Token-2022 accounts. This is unlikely in typical deployments but possible in misconfigured setups.

Recommendation

Reject transactions containing inner instructions with unrecognized types (fail-secure):

// In reconstruct_system_instruction:
_ => {
    return Err(KoraError::InvalidTransaction(format!(
        "Unrecognized system instruction type '{}' in CPI — \
         cannot validate fee payer policy. Transaction rejected.",
        instruction_type
    )));
}

// In reconstruct_spl_token_instruction:
_ => {
    return Err(KoraError::InvalidTransaction(format!(
        "Unrecognized SPL Token instruction type '{}' in CPI — \
         cannot validate fee payer policy. Transaction rejected.",
        instruction_type
    )));
}

Alternatively, maintain a list of known-safe instruction types that don't involve authority checks, and only reject truly unknown types.

References

  • crates/lib/src/transaction/instruction_util.rs:750-753 — system instruction catch-all
  • crates/lib/src/transaction/instruction_util.rs:1187-1189 — SPL token instruction catch-all
  • crates/lib/src/transaction/instruction_util.rs:316-319build_default_compiled_instruction
  • SPL Token-2022 instruction types — full list of extension instructions

References

@dev-jodee dev-jodee published to solana-foundation/kora Mar 11, 2026
Published to the GitHub Advisory Database Mar 12, 2026
Reviewed Mar 12, 2026

Severity

Moderate

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v4 base metrics

Exploitability Metrics
Attack Vector Network
Attack Complexity Low
Attack Requirements None
Privileges Required None
User interaction None
Vulnerable System Impact Metrics
Confidentiality None
Integrity Low
Availability None
Subsequent System Impact Metrics
Confidentiality None
Integrity None
Availability None

CVSS v4 base metrics

Exploitability Metrics
Attack Vector: This metric reflects the context by which vulnerability exploitation is possible. This metric value (and consequently the resulting severity) will be larger the more remote (logically, and physically) an attacker can be in order to exploit the vulnerable system. The assumption is that the number of potential attackers for a vulnerability that could be exploited from across a network is larger than the number of potential attackers that could exploit a vulnerability requiring physical access to a device, and therefore warrants a greater severity.
Attack Complexity: This metric captures measurable actions that must be taken by the attacker to actively evade or circumvent existing built-in security-enhancing conditions in order to obtain a working exploit. These are conditions whose primary purpose is to increase security and/or increase exploit engineering complexity. A vulnerability exploitable without a target-specific variable has a lower complexity than a vulnerability that would require non-trivial customization. This metric is meant to capture security mechanisms utilized by the vulnerable system.
Attack Requirements: This metric captures the prerequisite deployment and execution conditions or variables of the vulnerable system that enable the attack. These differ from security-enhancing techniques/technologies (ref Attack Complexity) as the primary purpose of these conditions is not to explicitly mitigate attacks, but rather, emerge naturally as a consequence of the deployment and execution of the vulnerable system.
Privileges Required: This metric describes the level of privileges an attacker must possess prior to successfully exploiting the vulnerability. The method by which the attacker obtains privileged credentials prior to the attack (e.g., free trial accounts), is outside the scope of this metric. Generally, self-service provisioned accounts do not constitute a privilege requirement if the attacker can grant themselves privileges as part of the attack.
User interaction: This metric captures the requirement for a human user, other than the attacker, to participate in the successful compromise of the vulnerable system. This metric determines whether the vulnerability can be exploited solely at the will of the attacker, or whether a separate user (or user-initiated process) must participate in some manner.
Vulnerable System Impact Metrics
Confidentiality: This metric measures the impact to the confidentiality of the information managed by the VULNERABLE SYSTEM due to a successfully exploited vulnerability. Confidentiality refers to limiting information access and disclosure to only authorized users, as well as preventing access by, or disclosure to, unauthorized ones.
Integrity: This metric measures the impact to integrity of a successfully exploited vulnerability. Integrity refers to the trustworthiness and veracity of information. Integrity of the VULNERABLE SYSTEM is impacted when an attacker makes unauthorized modification of system data. Integrity is also impacted when a system user can repudiate critical actions taken in the context of the system (e.g. due to insufficient logging).
Availability: This metric measures the impact to the availability of the VULNERABLE SYSTEM resulting from a successfully exploited vulnerability. While the Confidentiality and Integrity impact metrics apply to the loss of confidentiality or integrity of data (e.g., information, files) used by the system, this metric refers to the loss of availability of the impacted system itself, such as a networked service (e.g., web, database, email). Since availability refers to the accessibility of information resources, attacks that consume network bandwidth, processor cycles, or disk space all impact the availability of a system.
Subsequent System Impact Metrics
Confidentiality: This metric measures the impact to the confidentiality of the information managed by the SUBSEQUENT SYSTEM due to a successfully exploited vulnerability. Confidentiality refers to limiting information access and disclosure to only authorized users, as well as preventing access by, or disclosure to, unauthorized ones.
Integrity: This metric measures the impact to integrity of a successfully exploited vulnerability. Integrity refers to the trustworthiness and veracity of information. Integrity of the SUBSEQUENT SYSTEM is impacted when an attacker makes unauthorized modification of system data. Integrity is also impacted when a system user can repudiate critical actions taken in the context of the system (e.g. due to insufficient logging).
Availability: This metric measures the impact to the availability of the SUBSEQUENT SYSTEM resulting from a successfully exploited vulnerability. While the Confidentiality and Integrity impact metrics apply to the loss of confidentiality or integrity of data (e.g., information, files) used by the system, this metric refers to the loss of availability of the impacted system itself, such as a networked service (e.g., web, database, email). Since availability refers to the accessibility of information resources, attacks that consume network bandwidth, processor cycles, or disk space all impact the availability of a system.
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N

EPSS score

Weaknesses

Protection Mechanism Failure

The product does not use or incorrectly uses a protection mechanism that provides sufficient defense against directed attacks against the product. Learn more on MITRE.

CVE ID

No known CVE

GHSA ID

GHSA-x442-m7cc-hr92

Credits

Loading Checking history
See something to contribute? Suggest improvements for this vulnerability.