backport #479 to v2

This commit is contained in:
Jack May 2020-09-21 13:08:17 -07:00
parent 51dc1ae267
commit f68ff75ba0
3 changed files with 51 additions and 28 deletions

View File

@ -247,11 +247,6 @@ Account's owner via the `Revoke` instruction.
### Multisignatures ### Multisignatures
Warning: SPL Token v2's multisignature validation is compromised and should not
be used. Validation will erroneously pass a single valid signer signs m times.
For more information:
https://github.com/solana-labs/solana-program-library/issues/477
M of N multisignatures are supported and can be used in place of Mint M of N multisignatures are supported and can be used in place of Mint
authorities or Account owners or delegates. Multisignature authorities must be authorities or Account owners or delegates. Multisignature authorities must be
initialized with the `InitializeMultisig` instruction. Initialization specifies initialized with the `InitializeMultisig` instruction. Initialization specifies

View File

@ -56,14 +56,11 @@ pub enum TokenInstruction {
InitializeAccount, InitializeAccount,
/// Initializes a multisignature account with N provided signers. /// Initializes a multisignature account with N provided signers.
/// ///
/// Warning, this instruction is compromised:
/// https://github.com/solana-labs/solana-program-library/issues/477
///
/// Multisignature accounts can used in place of any single owner/delegate accounts in any /// Multisignature accounts can used in place of any single owner/delegate accounts in any
/// token instruction that require an owner/delegate to be present. The variant field represents the /// token instruction that require an owner/delegate to be present. The variant field represents the
/// number of signers (M) required to validate this multisignature account. /// number of signers (M) required to validate this multisignature account.
/// ///
/// The `DangerInitializeMultisig` instruction requires no signers and MUST be included within /// The `InitializeMultisig` instruction requires no signers and MUST be included within
/// the same Transaction as the system program's `CreateInstruction` that creates the account /// the same Transaction as the system program's `CreateInstruction` that creates the account
/// being initialized. Otherwise another party can acquire ownership of the uninitialized account. /// being initialized. Otherwise another party can acquire ownership of the uninitialized account.
/// ///
@ -72,7 +69,7 @@ pub enum TokenInstruction {
/// 0. `[writable]` The multisignature account to initialize. /// 0. `[writable]` The multisignature account to initialize.
/// 1. `[]` Rent sysvar /// 1. `[]` Rent sysvar
/// 2. ..2+N. `[]` The signer accounts, must equal to N where 1 <= N <= 11. /// 2. ..2+N. `[]` The signer accounts, must equal to N where 1 <= N <= 11.
DangerInitializeMultisig { InitializeMultisig {
/// The number of signers (M) required to validate this multisignature account. /// The number of signers (M) required to validate this multisignature account.
m: u8, m: u8,
}, },
@ -353,7 +350,7 @@ impl TokenInstruction {
1 => Self::InitializeAccount, 1 => Self::InitializeAccount,
2 => { 2 => {
let &m = rest.get(0).ok_or(InvalidInstruction)?; let &m = rest.get(0).ok_or(InvalidInstruction)?;
Self::DangerInitializeMultisig { m } Self::InitializeMultisig { m }
} }
3 | 4 | 7 | 8 => { 3 | 4 | 7 | 8 => {
let amount = rest let amount = rest
@ -449,7 +446,7 @@ impl TokenInstruction {
Self::pack_pubkey_option(freeze_authority, &mut buf); Self::pack_pubkey_option(freeze_authority, &mut buf);
} }
Self::InitializeAccount => buf.push(1), Self::InitializeAccount => buf.push(1),
&Self::DangerInitializeMultisig { m } => { &Self::InitializeMultisig { m } => {
buf.push(2); buf.push(2);
buf.push(m); buf.push(m);
} }
@ -624,8 +621,8 @@ pub fn initialize_account(
}) })
} }
/// Creates a `DangerInitializeMultisig` instruction. /// Creates a `InitializeMultisig` instruction.
pub fn danger_initialize_multisig( pub fn initialize_multisig(
token_program_id: &Pubkey, token_program_id: &Pubkey,
multisig_pubkey: &Pubkey, multisig_pubkey: &Pubkey,
signer_pubkeys: &[&Pubkey], signer_pubkeys: &[&Pubkey],
@ -637,7 +634,7 @@ pub fn danger_initialize_multisig(
{ {
return Err(ProgramError::MissingRequiredSignature); return Err(ProgramError::MissingRequiredSignature);
} }
let data = TokenInstruction::DangerInitializeMultisig { m }.pack(); let data = TokenInstruction::InitializeMultisig { m }.pack();
let mut accounts = Vec::with_capacity(1 + 1 + signer_pubkeys.len()); let mut accounts = Vec::with_capacity(1 + 1 + signer_pubkeys.len());
accounts.push(AccountMeta::new(*multisig_pubkey, false)); accounts.push(AccountMeta::new(*multisig_pubkey, false));
@ -1083,7 +1080,7 @@ mod test {
let unpacked = TokenInstruction::unpack(&expect).unwrap(); let unpacked = TokenInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check); assert_eq!(unpacked, check);
let check = TokenInstruction::DangerInitializeMultisig { m: 1 }; let check = TokenInstruction::InitializeMultisig { m: 1 };
let packed = check.pack(); let packed = check.pack();
let expect = Vec::from([2u8, 1]); let expect = Vec::from([2u8, 1]);
assert_eq!(packed, expect); assert_eq!(packed, expect);

View File

@ -4,7 +4,7 @@
use crate::{ use crate::{
error::TokenError, error::TokenError,
instruction::{is_valid_signer_index, AuthorityType, TokenInstruction}, instruction::{is_valid_signer_index, AuthorityType, TokenInstruction, MAX_SIGNERS},
option::COption, option::COption,
pack::{IsInitialized, Pack}, pack::{IsInitialized, Pack},
state::{Account, AccountState, Mint, Multisig}, state::{Account, AccountState, Mint, Multisig},
@ -99,8 +99,8 @@ impl Processor {
Ok(()) Ok(())
} }
/// Processes a [DangerInitializeMultisig](enum.TokenInstruction.html) instruction. /// Processes a [InitializeMultisig](enum.TokenInstruction.html) instruction.
pub fn process_danger_initialize_multisig(accounts: &[AccountInfo], m: u8) -> ProgramResult { pub fn process_initialize_multisig(accounts: &[AccountInfo], m: u8) -> ProgramResult {
let account_info_iter = &mut accounts.iter(); let account_info_iter = &mut accounts.iter();
let multisig_info = next_account_info(account_info_iter)?; let multisig_info = next_account_info(account_info_iter)?;
let multisig_info_data_len = multisig_info.data_len(); let multisig_info_data_len = multisig_info.data_len();
@ -637,9 +637,9 @@ impl Processor {
info!("Instruction: InitializeAccount"); info!("Instruction: InitializeAccount");
Self::process_initialize_account(accounts) Self::process_initialize_account(accounts)
} }
TokenInstruction::DangerInitializeMultisig { m } => { TokenInstruction::InitializeMultisig { m } => {
info!("Instruction: DangerInitializeMultisig"); info!("Instruction: InitializeMultisig");
Self::process_danger_initialize_multisig(accounts, m) Self::process_initialize_multisig(accounts, m)
} }
TokenInstruction::Transfer { amount } => { TokenInstruction::Transfer { amount } => {
info!("Instruction: Transfer"); info!("Instruction: Transfer");
@ -714,14 +714,18 @@ impl Processor {
{ {
let multisig = Multisig::unpack(&owner_account_info.data.borrow())?; let multisig = Multisig::unpack(&owner_account_info.data.borrow())?;
let mut num_signers = 0; let mut num_signers = 0;
let mut matched = [false; MAX_SIGNERS];
for signer in signers.iter() { for signer in signers.iter() {
if multisig.signers[0..multisig.n as usize].contains(signer.key) { for (position, key) in multisig.signers[0..multisig.n as usize].iter().enumerate() {
if key == signer.key && !matched[position] {
if !signer.is_signer { if !signer.is_signer {
return Err(ProgramError::MissingRequiredSignature); return Err(ProgramError::MissingRequiredSignature);
} }
matched[position] = true;
num_signers += 1; num_signers += 1;
} }
} }
}
if num_signers < multisig.m { if num_signers < multisig.m {
return Err(ProgramError::MissingRequiredSignature); return Err(ProgramError::MissingRequiredSignature);
} }
@ -3600,8 +3604,7 @@ mod tests {
assert_eq!( assert_eq!(
Err(TokenError::NotRentExempt.into()), Err(TokenError::NotRentExempt.into()),
do_process_instruction( do_process_instruction(
danger_initialize_multisig(&program_id, &multisig_key, &[&signer_keys[0]], 1) initialize_multisig(&program_id, &multisig_key, &[&signer_keys[0]], 1).unwrap(),
.unwrap(),
vec![ vec![
&mut multisig_account, &mut multisig_account,
&mut rent_sysvar, &mut rent_sysvar,
@ -3615,7 +3618,7 @@ mod tests {
// single signer // single signer
let account_info_iter = &mut signer_accounts.iter_mut(); let account_info_iter = &mut signer_accounts.iter_mut();
do_process_instruction( do_process_instruction(
danger_initialize_multisig(&program_id, &multisig_key, &[&signer_keys[0]], 1).unwrap(), initialize_multisig(&program_id, &multisig_key, &[&signer_keys[0]], 1).unwrap(),
vec![ vec![
&mut multisig_account, &mut multisig_account,
&mut rent_sysvar, &mut rent_sysvar,
@ -3627,7 +3630,7 @@ mod tests {
// multiple signer // multiple signer
let account_info_iter = &mut signer_accounts.iter_mut(); let account_info_iter = &mut signer_accounts.iter_mut();
do_process_instruction( do_process_instruction(
danger_initialize_multisig( initialize_multisig(
&program_id, &program_id,
&multisig_delegate_key, &multisig_delegate_key,
&signer_key_refs, &signer_key_refs,
@ -4087,7 +4090,7 @@ mod tests {
{ {
let mut multisig = let mut multisig =
Multisig::unpack_unchecked(&owner_account_info.data.borrow()).unwrap(); Multisig::unpack_unchecked(&owner_account_info.data.borrow()).unwrap();
multisig.m = 2; // TODO 11? multisig.m = 11;
multisig.n = 11; multisig.n = 11;
Multisig::pack(multisig, &mut owner_account_info.data.borrow_mut()).unwrap(); Multisig::pack(multisig, &mut owner_account_info.data.borrow_mut()).unwrap();
} }
@ -4097,6 +4100,34 @@ mod tests {
Processor::validate_owner(&program_id, &owner_key, &owner_account_info, &signers) Processor::validate_owner(&program_id, &owner_key, &owner_account_info, &signers)
); );
signers[5].is_signer = true; signers[5].is_signer = true;
// 11:11, single signer signs multiple times
{
let mut signer_lamports = 0;
let mut signer_data = vec![];
let signers = vec![
AccountInfo::new(
&signer_keys[5],
true,
false,
&mut signer_lamports,
&mut signer_data,
&program_id,
false,
Epoch::default(),
);
MAX_SIGNERS + 1
];
let mut multisig =
Multisig::unpack_unchecked(&owner_account_info.data.borrow()).unwrap();
multisig.m = 11;
multisig.n = 11;
Multisig::pack(multisig, &mut owner_account_info.data.borrow_mut()).unwrap();
assert_eq!(
Err(ProgramError::MissingRequiredSignature),
Processor::validate_owner(&program_id, &owner_key, &owner_account_info, &signers)
);
}
} }
#[test] #[test]