From cc43721cac7427d5282923e75f937fcae5def619 Mon Sep 17 00:00:00 2001 From: ygd58 Date: Fri, 27 Mar 2026 22:51:01 +0100 Subject: [PATCH 1/2] feat: add owner_program_id field to AccountWithMetadata Programs can now verify that input accounts are owned by themselves, preventing spoofing attacks where malicious programs pass fake accounts with matching data layouts. Changes: - Add optional owner_program_id field to AccountWithMetadata - Add with_owner_program_id() builder method - Backward compatible: serde(default) = None for existing data Usage in programs: if let Some(owner) = account.owner_program_id { assert_eq!(owner, SELF_PROGRAM_ID, 'account not owned by this program'); } Fixes #347 --- nssa/core/src/account.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/nssa/core/src/account.rs b/nssa/core/src/account.rs index 0f9248e3..c1e5b0ad 100644 --- a/nssa/core/src/account.rs +++ b/nssa/core/src/account.rs @@ -126,6 +126,11 @@ pub struct AccountWithMetadata { pub account: Account, pub is_authorized: bool, pub account_id: AccountId, + /// The program that owns this account. Programs can use this to verify + /// that an input account is owned by themselves, preventing spoofing attacks. + /// See: https://github.com/logos-blockchain/logos-execution-zone/issues/347 + #[serde(default)] + pub owner_program_id: Option, } #[cfg(feature = "host")] @@ -135,8 +140,14 @@ impl AccountWithMetadata { account, is_authorized, account_id: account_id.into(), + owner_program_id: None, } } + + pub fn with_owner_program_id(mut self, program_id: crate::program::ProgramId) -> Self { + self.owner_program_id = Some(program_id); + self + } } #[derive( From 9a342732225c3b5c14a4635086f154b2de330f5b Mon Sep 17 00:00:00 2001 From: ygd58 Date: Fri, 27 Mar 2026 22:56:58 +0100 Subject: [PATCH 2/2] feat: populate owner_program_id in public transaction pre_states Now AccountWithMetadata.owner_program_id is set from the account's actual program_owner when building pre_states for execution. Programs can now reliably check account ownership: if let Some(owner) = account.owner_program_id { assert_eq!(owner, SELF_PROGRAM_ID); } Refs #347 --- nssa/src/public_transaction/transaction.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index 8151f8cf..ed370ca0 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -109,11 +109,20 @@ impl PublicTransaction { .account_ids .iter() .map(|account_id| { + let account = state.get_account_by_id(*account_id); + let owner_program_id = account.program_owner; + let is_default_owner = owner_program_id == nssa_core::program::DEFAULT_PROGRAM_ID; AccountWithMetadata::new( - state.get_account_by_id(*account_id), + account, signer_account_ids.contains(account_id), *account_id, ) + .with_owner_program_id(if is_default_owner { + // Uninitialized accounts have no meaningful owner + nssa_core::program::DEFAULT_PROGRAM_ID + } else { + owner_program_id + }) }) .collect();