Skip to content

Commit

Permalink
add test for ed25519 check
Browse files Browse the repository at this point in the history
  • Loading branch information
ebatsell committed Mar 4, 2024
1 parent 20b28e8 commit 76723a3
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ use validator_history_vote_state::VoteStateVersions;
#[derive(Default, Debug, Copy, Clone, Zeroable, Pod, Eq, PartialEq)]
#[repr(C)]
pub struct Ed25519SignatureOffsets {
signature_offset: u16, // offset to ed25519 signature of 64 bytes
signature_instruction_index: u16, // instruction index to find signature
pub public_key_offset: u16, // offset to public key of 32 bytes
public_key_instruction_index: u16, // instruction index to find public key
pub message_data_offset: u16, // offset to start of message data
message_data_size: u16, // size of message data
message_instruction_index: u16, // index of instruction data to get message data
pub signature_offset: u16, // offset to ed25519 signature of 64 bytes
pub signature_instruction_index: u16, // instruction index to find signature
pub public_key_offset: u16, // offset to public key of 32 bytes
pub public_key_instruction_index: u16, // instruction index to find public key
pub message_data_offset: u16, // offset to start of message data
pub message_data_size: u16, // size of message data
pub message_instruction_index: u16, // index of instruction data to get message data
}

pub const PUBKEY_SERIALIZED_SIZE: usize = 32;
Expand Down Expand Up @@ -72,6 +72,19 @@ pub fn handle_copy_gossip_contact_info(ctx: Context<CopyGossipContactInfo>) -> R
[SIGNATURE_OFFSETS_START..SIGNATURE_OFFSETS_START + SIGNATURE_OFFSETS_SERIALIZED_SIZE],
);

// Check offsets and indices are correct so an attacker cannot submit invalid data
if ed25519_offsets.signature_instruction_index != ed25519_offsets.public_key_instruction_index
|| ed25519_offsets.signature_instruction_index != ed25519_offsets.message_instruction_index
|| ed25519_offsets.public_key_offset
!= (SIGNATURE_OFFSETS_START + SIGNATURE_OFFSETS_SERIALIZED_SIZE) as u16
|| ed25519_offsets.signature_offset
!= ed25519_offsets.public_key_offset + PUBKEY_SERIALIZED_SIZE as u16
|| ed25519_offsets.message_data_offset
!= ed25519_offsets.signature_offset + SIGNATURE_SERIALIZED_SIZE as u16
{
return Err(ValidatorHistoryError::GossipDataInvalid.into());
}

let message_signer = Pubkey::try_from(
&verify_instruction.data[ed25519_offsets.public_key_offset as usize
..ed25519_offsets.public_key_offset as usize + PUBKEY_SERIALIZED_SIZE],
Expand Down
124 changes: 122 additions & 2 deletions tests/tests/test_gossip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr};

use anchor_lang::{solana_program::instruction::Instruction, InstructionData, ToAccountMetas};
use bincode::serialize;
use bytemuck::bytes_of;
use ed25519_dalek::Signer as Ed25519Signer;
use rand::thread_rng;
use solana_gossip::{
contact_info::ContactInfo,
Expand All @@ -10,14 +12,18 @@ use solana_gossip::{
};
use solana_program_test::*;
use solana_sdk::{
clock::Clock, ed25519_instruction::new_ed25519_instruction, signer::Signer,
clock::Clock,
ed25519_instruction::{
new_ed25519_instruction, DATA_START, PUBKEY_SERIALIZED_SIZE, SIGNATURE_SERIALIZED_SIZE,
},
signer::Signer,
transaction::Transaction,
};
use solana_version::LegacyVersion2;
use tests::fixtures::TestFixture;
use validator_history::{
crds_value::{CrdsData as ValidatorHistoryCrdsData, LegacyVersion, LegacyVersion1},
ValidatorHistory,
Ed25519SignatureOffsets, ValidatorHistory,
};

fn create_gossip_tx(fixture: &TestFixture, crds_data: &CrdsData) -> Transaction {
Expand Down Expand Up @@ -407,3 +413,117 @@ async fn test_gossip_timestamps() {
.submit_transaction_assert_error(transaction, "GossipDataInFuture")
.await;
}

#[tokio::test]
async fn test_fake_offsets() {
// Put in fake offsets, and make sure we get a GossipDataInvalid error
let fixture = TestFixture::new().await;
fixture.initialize_config().await;
fixture.initialize_validator_history_account().await;
let mut banks_client = {
let ctx = fixture.ctx.borrow_mut();
ctx.banks_client.clone()
};

// Initial valid instruction
let dalek_keypair =
ed25519_dalek::Keypair::from_bytes(&fixture.identity_keypair.to_bytes()).unwrap();

let clock: Clock = banks_client.get_sysvar().await.unwrap();
let wallclock = clock.unix_timestamp as u64 * 1000;
let mut contact_info = ContactInfo::new(fixture.identity_keypair.pubkey(), wallclock, 0);
let ipv4 = Ipv4Addr::new(1, 2, 3, 4);
let ip = IpAddr::V4(ipv4);
contact_info
.set_socket(0, SocketAddr::new(ip, 1234))
.expect("could not set socket");
let crds_data = CrdsData::ContactInfo(contact_info.clone());

let ed25519_ix = new_ed25519_instruction(&dalek_keypair, &serialize(&crds_data).unwrap());

// Invalid instruction
let fake_ipv4 = Ipv4Addr::new(5, 5, 5, 5);
let fake_ip = IpAddr::V4(fake_ipv4);
let mut contact_info = ContactInfo::new(fixture.identity_keypair.pubkey(), wallclock, 0);
contact_info
.set_socket(0, SocketAddr::new(fake_ip, 1234))
.unwrap();
let crds_data = CrdsData::ContactInfo(contact_info.clone());

// Code from new_ed25519_instruction with modified instruction indices
let message = serialize(&crds_data).unwrap();

let signature = dalek_keypair.sign(&message).to_bytes();
let pubkey = dalek_keypair.public.to_bytes();

assert_eq!(pubkey.len(), PUBKEY_SERIALIZED_SIZE);
assert_eq!(signature.len(), SIGNATURE_SERIALIZED_SIZE);

let mut instruction_data = Vec::with_capacity(
DATA_START
.saturating_add(SIGNATURE_SERIALIZED_SIZE)
.saturating_add(PUBKEY_SERIALIZED_SIZE)
.saturating_add(message.len()),
);

let num_signatures: u8 = 1;
let public_key_offset = DATA_START;
let signature_offset = public_key_offset.saturating_add(PUBKEY_SERIALIZED_SIZE);
let message_data_offset = signature_offset.saturating_add(SIGNATURE_SERIALIZED_SIZE);

instruction_data.extend_from_slice(bytes_of(&[num_signatures, 0]));

let offsets = Ed25519SignatureOffsets {
signature_offset: signature_offset as u16,
signature_instruction_index: 0, // Index of real signature
public_key_offset: public_key_offset as u16,
public_key_instruction_index: 0, // Index of real signer
message_data_offset: message_data_offset as u16,
message_data_size: message.len() as u16,
message_instruction_index: 1, // Index of fake data
};

instruction_data.extend_from_slice(bytes_of(&offsets));

debug_assert_eq!(instruction_data.len(), public_key_offset);

instruction_data.extend_from_slice(&pubkey);

debug_assert_eq!(instruction_data.len(), signature_offset);

instruction_data.extend_from_slice(&signature);

debug_assert_eq!(instruction_data.len(), message_data_offset);

instruction_data.extend_from_slice(&message);

let fake_instruction = Instruction {
program_id: solana_sdk::ed25519_program::id(),
accounts: vec![],
data: instruction_data,
};

let copy_gossip_ix = Instruction {
program_id: validator_history::id(),
accounts: validator_history::accounts::CopyGossipContactInfo {
validator_history_account: fixture.validator_history_account,
vote_account: fixture.vote_account,
instructions: anchor_lang::solana_program::sysvar::instructions::id(),
config: fixture.validator_history_config,
oracle_authority: fixture.keypair.pubkey(),
}
.to_account_metas(None),
data: validator_history::instruction::CopyGossipContactInfo {}.data(),
};

let transaction = Transaction::new_signed_with_payer(
&[ed25519_ix, fake_instruction, copy_gossip_ix],
Some(&fixture.keypair.pubkey()),
&[&fixture.keypair],
fixture.ctx.borrow().last_blockhash,
);

fixture
.submit_transaction_assert_error(transaction, "GossipDataInvalid")
.await;
}

0 comments on commit 76723a3

Please sign in to comment.