Skip to content

CPI Return<T>::get() does not validate program_id from get_return_data() #4232

@rz1989s

Description

@rz1989s

Bug Description

Return<T>::get() in the CPI codegen calls sol_get_return_data() which returns (program_id, data), but discards the program_id via:

let (_key, data) = anchor_lang::solana_program::program::get_return_data().unwrap();

This means the caller has no way to verify the return data actually came from the expected program.

Impact

Solana's set_return_data() uses a last-writer-wins model. If Program A CPIs into Program B, and B CPIs into a malicious Program C, then C can call set_return_data() to overwrite B's return data. When A reads via Return<T>::get(), it receives C's spoofed data without any validation.

This affects every Anchor program that uses CPI return values — a framework-level vulnerability.

Affected Code

Both codegen paths discard the program_id:

  • lang/syn/src/codegen/program/cpi.rs (standard #[program])
  • lang/attribute/program/src/declare_program/mods/cpi.rs (declare_program!)

Internal Evidence

The same repository already implements the correct pattern in spl/src/token_2022.rs (lines 377-388), where get_return_data() validates key != ctx.program_id:

.and_then(|(key, data)| {
    if key != ctx.program_id {
        Err(ProgramError::IncorrectProgramId)
    } else {
        // ...deserialize
    }
})

Reproduction

A full PoC with 3 programs (caller, callee, malicious) demonstrating the spoofing attack is included in the fix PR: #4231

Suggested Fix

  1. Store the expected program_id (from CpiContext) in the Return<T> struct
  2. Validate in get() that get_return_data() key matches
  3. Add get_unchecked() for backward compatibility

Full write-up: https://gist.github.com/rz1989s/f0d0d217c27a28f89bbec11b3b439cb6

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions