Add offset checks for sigverify (#6452)

* Add offset checks for sigverify

* decode_len returning error instead of unwrap
This commit is contained in:
sakridge 2019-10-18 17:52:59 -07:00 committed by GitHub
parent 985f5c7351
commit 32bfced6a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 149 additions and 73 deletions

View File

@ -22,3 +22,23 @@ fn bench_sigverify(bencher: &mut Bencher) {
let _ans = sigverify::ed25519_verify(&batches, &recycler, &recycler_out);
})
}
#[bench]
fn bench_get_offsets(bencher: &mut Bencher) {
let tx = test_tx();
// generate packet vector
let batches = to_packets(&vec![tx; 1024]);
let recycler = Recycler::default();
// verify packets
bencher.iter(|| {
let ans = sigverify::generate_offsets(&batches, &recycler);
assert!(ans.is_ok());
let ans = ans.unwrap();
recycler.recycle(ans.0);
recycler.recycle(ans.1);
recycler.recycle(ans.2);
recycler.recycle(ans.3);
})
}

View File

@ -7,7 +7,6 @@
use crate::cuda_runtime::PinnedVec;
use crate::packet::{Packet, Packets};
use crate::recycler::Recycler;
use crate::result::Result;
use bincode::serialized_size;
use rayon::ThreadPool;
use solana_ledger::perf_libs;
@ -52,23 +51,18 @@ impl PacketOffsets {
}
}
struct UnsanitizedPacketOffsets {
pub correct: bool,
pub packet_offsets: PacketOffsets,
#[derive(Debug, PartialEq)]
pub enum PacketError {
InvalidLen,
InvalidSignatureLen,
InvalidPubkeyLen,
MismatchSignatureLen,
InvalidShortVec,
}
impl UnsanitizedPacketOffsets {
pub fn new(
correct: bool,
sig_len: u32,
sig_start: u32,
msg_start: u32,
pubkey_start: u32,
) -> Self {
Self {
correct,
packet_offsets: PacketOffsets::new(sig_len, sig_start, msg_start, pubkey_start),
}
impl std::convert::From<std::boxed::Box<bincode::ErrorKind>> for PacketError {
fn from(_e: std::boxed::Box<bincode::ErrorKind>) -> PacketError {
PacketError::InvalidShortVec
}
}
@ -125,46 +119,73 @@ fn batch_size(batches: &[Packets]) -> usize {
}
// internal function to be unit-tested; should be used only by get_packet_offsets
fn do_get_packet_offsets(packet: &Packet, current_offset: u32) -> UnsanitizedPacketOffsets {
// This directly reads the length of Transaction.signatures (serialized with short_vec)
let (sig_len_untrusted, sig_size) = decode_len(&packet.data);
fn do_get_packet_offsets(
packet: &Packet,
current_offset: u32,
) -> Result<PacketOffsets, PacketError> {
let message_header_size = serialized_size(&MessageHeader::default()).unwrap() as usize;
// should have at least 1 signature, sig lengths and the message header
if (1 + size_of::<Signature>() + message_header_size) > packet.meta.size {
return Err(PacketError::InvalidLen);
}
// read the length of Transaction.signatures (serialized with short_vec)
let (sig_len_untrusted, sig_size) = decode_len(&packet.data)?;
// This directly reads MessageHeader.num_required_signatures (serialized with u8)
let msg_start_offset = sig_size + sig_len_untrusted * size_of::<Signature>();
// Using msg_start_offset which is based on sig_len_untrusted introduces uncertainty.
// Ultimately, the actual sigverify will determine the uncertainty.
let msg_start_offset = sig_size + sig_len_untrusted * size_of::<Signature>();
// Packet should have data at least for signatures, MessageHeader, 1 byte for Message.account_keys.len
if (msg_start_offset + message_header_size + 1) > packet.meta.size {
return Err(PacketError::InvalidSignatureLen);
}
// read MessageHeader.num_required_signatures (serialized with u8)
let sig_len_maybe_trusted = packet.data[msg_start_offset] as usize;
let msg_header_size = serialized_size(&MessageHeader::default()).unwrap() as usize;
let message_account_keys_len_offset = msg_start_offset + message_header_size;
// This directly reads the length of Message.account_keys (serialized with short_vec)
let (_pubkey_len, pubkey_len_size) =
decode_len(&packet.data[(msg_start_offset + msg_header_size)..]);
// read the length of Message.account_keys (serialized with short_vec)
let (pubkey_len, pubkey_len_size) =
decode_len(&packet.data[message_account_keys_len_offset..])?;
if (message_account_keys_len_offset + pubkey_len * size_of::<Pubkey>() + pubkey_len_size)
> packet.meta.size
{
return Err(PacketError::InvalidPubkeyLen);
}
let sig_start = current_offset as usize + sig_size;
let msg_start = current_offset as usize + msg_start_offset;
let pubkey_start = msg_start + msg_header_size + pubkey_len_size;
let pubkey_start = msg_start + message_header_size + pubkey_len_size;
UnsanitizedPacketOffsets::new(
sig_len_maybe_trusted == sig_len_untrusted,
if sig_len_maybe_trusted != sig_len_untrusted {
return Err(PacketError::MismatchSignatureLen);
}
Ok(PacketOffsets::new(
sig_len_untrusted as u32,
sig_start as u32,
msg_start as u32,
pubkey_start as u32,
)
))
}
fn get_packet_offsets(packet: &Packet, current_offset: u32) -> PacketOffsets {
let unsanitized_packet_offsets = do_get_packet_offsets(packet, current_offset);
if unsanitized_packet_offsets.correct {
unsanitized_packet_offsets.packet_offsets
if let Ok(offsets) = unsanitized_packet_offsets {
offsets
} else {
// force sigverify to fail by returning zeros
PacketOffsets::new(0, 0, 0, 0)
}
}
pub fn generate_offsets(batches: &[Packets], recycler: &Recycler<TxOffset>) -> Result<TxOffsets> {
pub fn generate_offsets(
batches: &[Packets],
recycler: &Recycler<TxOffset>,
) -> Result<TxOffsets, ()> {
debug!("allocating..");
let mut signature_offsets: PinnedVec<_> = recycler.allocate("sig_offsets");
signature_offsets.set_pinnable();
@ -353,17 +374,17 @@ pub fn make_packet_from_transaction(tx: Transaction) -> Packet {
#[cfg(test)]
mod tests {
use super::PacketError;
use crate::packet::{Packet, Packets};
use crate::recycler::Recycler;
use crate::sigverify;
use crate::sigverify::PacketOffsets;
use crate::test_tx::{test_multisig_tx, test_tx};
use bincode::{deserialize, serialize, serialized_size};
use bincode::{deserialize, serialize};
use solana_sdk::hash::Hash;
use solana_sdk::message::{Message, MessageHeader};
use solana_sdk::signature::Signature;
use solana_sdk::transaction::Transaction;
use std::mem::size_of;
const SIG_OFFSET: usize = 1;
@ -415,11 +436,7 @@ mod tests {
assert_eq!(packet_offsets.sig_len, 1);
}
#[test]
fn test_untrustworthy_sigs() {
let required_num_sigs = 14;
let actual_num_sigs = 5;
fn packet_from_num_sigs(required_num_sigs: u8, actual_num_sigs: usize) -> Packet {
let message = Message {
header: MessageHeader {
num_required_signatures: required_num_sigs,
@ -432,14 +449,21 @@ mod tests {
};
let mut tx = Transaction::new_unsigned(message);
tx.signatures = vec![Signature::default(); actual_num_sigs as usize];
let packet = sigverify::make_packet_from_transaction(tx.clone());
sigverify::make_packet_from_transaction(tx)
}
#[test]
fn test_untrustworthy_sigs() {
let required_num_sigs = 14;
let actual_num_sigs = 5;
let packet = packet_from_num_sigs(required_num_sigs, actual_num_sigs);
let unsanitized_packet_offsets = sigverify::do_get_packet_offsets(&packet, 0);
assert_eq!(unsanitized_packet_offsets.correct, false);
assert_eq!(
unsanitized_packet_offsets.packet_offsets.sig_len as usize,
actual_num_sigs
unsanitized_packet_offsets,
Err(PacketError::MismatchSignatureLen)
);
}
@ -449,38 +473,70 @@ mod tests {
let required_num_sigs = 214;
let actual_num_sigs = 5;
let message = Message {
header: MessageHeader {
num_required_signatures: required_num_sigs,
num_credit_only_signed_accounts: 12,
num_credit_only_unsigned_accounts: 11,
},
account_keys: vec![],
recent_blockhash: Hash::default(),
instructions: vec![],
};
let mut tx = Transaction::new_unsigned(message);
// reduce to actual_num_sigs to avoid packet error
tx.signatures = vec![Signature::default(); actual_num_sigs];
let packet = sigverify::make_packet_from_transaction(tx.clone());
let packet = packet_from_num_sigs(required_num_sigs, actual_num_sigs);
let unsanitized_packet_offsets = sigverify::do_get_packet_offsets(&packet, 0);
let expected_sig_size = 1;
let expected_sigs_size = actual_num_sigs * size_of::<Signature>();
let expected_msg_header_size = serialized_size(&MessageHeader::default()).unwrap() as usize;
let expected_pubkey_size = 1;
let expected_pubkey_start = expected_sig_size
+ expected_sigs_size
+ expected_msg_header_size
+ expected_pubkey_size;
assert_eq!(
expected_pubkey_start,
unsanitized_packet_offsets.packet_offsets.pubkey_start as usize
unsanitized_packet_offsets,
Err(PacketError::MismatchSignatureLen)
);
}
#[test]
fn test_small_packet() {
let tx = test_tx();
let mut packet = sigverify::make_packet_from_transaction(tx.clone());
packet.data[0] = 0xff;
packet.data[1] = 0xff;
packet.meta.size = 2;
let res = sigverify::do_get_packet_offsets(&packet, 0);
assert_eq!(res, Err(PacketError::InvalidLen));
}
#[test]
fn test_large_sig_len() {
let tx = test_tx();
let mut packet = sigverify::make_packet_from_transaction(tx.clone());
// Make the signatures len huge
packet.data[0] = 0xff;
let res = sigverify::do_get_packet_offsets(&packet, 0);
assert_eq!(res, Err(PacketError::InvalidSignatureLen));
}
#[test]
fn test_really_large_sig_len() {
let tx = test_tx();
let mut packet = sigverify::make_packet_from_transaction(tx.clone());
// Make the signatures len huge
packet.data[0] = 0xff;
packet.data[1] = 0xff;
packet.data[2] = 0xff;
packet.data[3] = 0xff;
let res = sigverify::do_get_packet_offsets(&packet, 0);
assert_eq!(res, Err(PacketError::InvalidShortVec));
}
#[test]
fn test_invalid_pubkey_len() {
let tx = test_tx();
let mut packet = sigverify::make_packet_from_transaction(tx.clone());
let res = sigverify::do_get_packet_offsets(&packet, 0);
// make pubkey len huge
packet.data[res.unwrap().pubkey_start as usize - 1] = 0xff;
let res = sigverify::do_get_packet_offsets(&packet, 0);
assert_eq!(res, Err(PacketError::InvalidPubkeyLen));
}
#[test]
fn test_system_transaction_data_layout() {
use crate::packet::PACKET_DATA_SIZE;

View File

@ -177,10 +177,10 @@ impl<'de, T: Deserialize<'de>> Deserialize<'de> for ShortVec<T> {
}
/// Return the decoded value and how many bytes it consumed.
pub fn decode_len(bytes: &[u8]) -> (usize, usize) {
let short_len: ShortU16 = bincode::deserialize(bytes).unwrap();
pub fn decode_len(bytes: &[u8]) -> Result<(usize, usize), Box<bincode::ErrorKind>> {
let short_len: ShortU16 = bincode::deserialize(bytes)?;
let num_bytes = bincode::serialized_size(&short_len).unwrap() as usize;
(short_len.0 as usize, num_bytes)
Ok((short_len.0 as usize, num_bytes))
}
#[cfg(test)]
@ -197,7 +197,7 @@ mod tests {
fn assert_len_encoding(len: u16, bytes: &[u8]) {
assert_eq!(encode_len(len), bytes, "unexpected usize encoding");
assert_eq!(
decode_len(bytes),
decode_len(bytes).unwrap(),
(len as usize, bytes.len()),
"unexpected usize decoding"
);
@ -217,7 +217,7 @@ mod tests {
#[test]
#[should_panic]
fn test_short_vec_decode_zero_len() {
decode_len(&[]);
decode_len(&[]).unwrap();
}
#[test]