May 13, 2026·6 min read·6 visits
Anchor versions prior to 1.0.0 skip structural discriminator checks for the `InterfaceAccount` type, allowing attackers to supply improperly typed accounts that bypass framework-level validation.
A critical vulnerability in the Anchor framework's `anchor-lang` crate allows account substitution attacks. The `InterfaceAccount` type fails to validate the 8-byte account discriminator during deserialization, permitting an attacker to supply a mismatched account type and subvert program logic.
The Anchor framework provides the foundational infrastructure for developing smart contracts on the Solana blockchain. Within this ecosystem, the anchor-lang crate manages core account parsing and validation routines. The InterfaceAccount type facilitates polymorphic account handling, allowing developers to accept accounts owned by multiple defined programs, such as distinguishing between SPL Token and Token-2022 programs.
A critical vulnerability exists in Anchor versions prior to 1.0.0 where the InterfaceAccount type fails to perform standard structural validation. Specifically, the framework omits the 8-byte discriminator check during the deserialization of InterfaceAccount structures. This check serves as the primary mechanism in Anchor to verify that a provided account matches the expected struct definition.
Without this discriminator validation, the framework blindly maps the provided account data to the expected structural layout. Attackers exploit this by passing structurally dissimilar accounts that share the same program owner. The framework processes the anomalous account, leading to account substitution and the execution of unauthorized state transitions.
The core issue stems from the improper delegation of deserialization routines within the InterfaceAccount::try_from method. Anchor employs an 8-byte discriminator, derived from the SHA256 hash of the account type name, to uniquely identify program state types. Standard Anchor accounts invoke T::try_deserialize, which explicitly reads this discriminator and validates it against the expected type definition.
In vulnerable versions, InterfaceAccount overrides this behavior by invoking the try_from_unchecked method. This function delegates processing to T::try_deserialize_unchecked, which deliberately skips the discriminator verification step. The framework relies entirely on the outer program ownership check, assuming that any account owned by the specified program is valid for the operation.
This assumption breaks the core security model of Anchor programs. Solana programs frequently manage multiple distinct account types under a single program ID. By bypassing the discriminator verification, the deserializer interprets the raw byte array of an arbitrary account as the expected struct type. This results in type confusion, where malicious or unintended data structures are treated as trusted state components.
A review of the anchor-lang source code illustrates the exact mechanism of the bypass. Prior to the fix, the try_from method simply wrapped the unchecked variant, returning Self::try_from_unchecked(info). This unchecked variant inherently lacked the necessary data validation logic to ensure structural integrity.
The patch introduced in commit 26ef36968a62e28a1f028e7adae4806af30c747d fundamentally restructures this method. The updated code introduces explicit initialization checks, verifying that the account is not an uninitialized system program account with zero lamports. Most importantly, it replaces the unchecked deserialization call with strict, discriminator-aware logic.
The revised implementation requires T::try_deserialize(&mut data) to process the account bytes. This exact function enforces the 8-byte discriminator check prior to reading the state variables. The code diff below highlights the transition from the insecure bypass to the secure, validated deserialization flow.
// Vulnerable Implementation (lang/src/accounts/interface_account.rs)
- pub fn try_from(info: &'a AccountInfo<'a>) -> Result<Self> {
- Self::try_from_unchecked(info)
- }
// Patched Implementation (lang/src/accounts/interface_account.rs)
+ pub fn try_from(info: &'a AccountInfo<'a>) -> Result<Self> {
+ if info.owner == &system_program::ID && info.lamports() == 0 {
+ return Err(ErrorCode::AccountNotInitialized.into());
+ }
+ T::check_owner(info.owner)?;
+ let mut data: &[u8] = &info.try_borrow_data()?;
+ Ok(Self::new(info, T::try_deserialize(&mut data)?))
+ }Exploiting this vulnerability requires the attacker to identify a target instruction that utilizes the InterfaceAccount type. The attacker then analyzes the target program to locate secondary account types managed by the same program owner. The goal is to find an unexpected account type that contains a compatible byte layout or exploitable data values when parsed as the expected type.
The attacker constructs a malicious transaction, substituting the legitimate expected account with the identified alternative account. When the transaction reaches the Solana validator, the vulnerable anchor-lang deserializer skips the discriminator check. The framework parses the bytes of the alternative account according to the layout of the expected struct, mapping fields sequentially based on their defined sizes.
This byte mapping creates an artificial state where the application logic operates on attacker-controlled or misaligned data. The proof-of-concept provided in the patch commit demonstrates this exact behavior. An RPC call passing anotherAccount to an instruction expecting expectedAccount completes successfully in vulnerable versions, rather than throwing the standard AccountDiscriminatorMismatch error.
it("Doesn't allow unexpected accounts owned by the expected programs", async () => {
try {
await program.methods
.test()
.accounts({ expectedAccount: anotherAccount.publicKey })
.rpc();
assert.fail("Allowed unexpected account substitution!");
} catch (e) {
assert(e instanceof anchor.AnchorError);
assert.strictEqual(
e.error.errorCode.number,
anchor.LangErrorCode.AccountDiscriminatorMismatch
);
}
});The consequences of this account substitution vulnerability vary depending on the specific program logic. At a minimum, it enables type confusion across any application relying on InterfaceAccount for state management. Attackers leverage this confusion to bypass access controls, manipulate financial logic, or steal locked assets.
In decentralized finance protocols, passing a forged or alternative token account structure into a swap or withdrawal instruction leads to direct financial loss. If an attacker substitutes an administrative configuration account with a standard user account, they gain unauthorized execution capabilities over the entire protocol. The framework-level nature of this flaw amplifies the risk, as it inherently affects any downstream application utilizing the vulnerable component.
This vulnerability highlights the critical importance of cryptographic discriminators in Solana account models. Relying purely on program ownership checks leaves applications fundamentally exposed to internal state manipulation. Programs that utilize complex, multi-type data architectures are at the highest risk of exploitation.
The official remediation requires an immediate upgrade to the Anchor framework version 1.0.0 or later. This release incorporates the patch from pull request #4139, restoring the discriminator validation to the InterfaceAccount type. Development teams must update their Cargo.toml dependencies and recompile their smart contracts using the updated tooling.
After updating the framework, security teams must audit all existing implementations of InterfaceAccount. It is necessary to confirm that the underlying types defined within the application possess proper discriminators. Applications failing to define strong structural boundaries remain at risk even with the patched framework.
Developers must completely avoid the manual usage of try_from_unchecked or InterfaceAccount::try_from_unchecked methods. These functions bypass the safety mechanisms restored by the 1.0.0 patch and reintroduce the vulnerability. Standard deserialization practices must be strictly enforced throughout the codebase.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
anchor-lang Solana Foundation | < 1.0.0 | 1.0.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-20 |
| Attack Vector | Network |
| Vulnerability Class | Account Substitution / Type Confusion |
| Exploit Status | Proof-of-Concept Available |
| CVSS v3.1 Score | 9.1 |
| Patch Version | 1.0.0 |
Improper Input Validation leading to Account Substitution.