Sanitizes tiered storage footer after reading from disk (#34200)
This commit is contained in:
parent
fa0930f254
commit
4f65f7dc8d
|
@ -1,4 +1,4 @@
|
||||||
use {std::path::PathBuf, thiserror::Error};
|
use {super::footer::SanitizeFooterError, std::path::PathBuf, thiserror::Error};
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum TieredStorageError {
|
pub enum TieredStorageError {
|
||||||
|
@ -16,4 +16,13 @@ pub enum TieredStorageError {
|
||||||
|
|
||||||
#[error("Unsupported: the feature is not yet supported")]
|
#[error("Unsupported: the feature is not yet supported")]
|
||||||
Unsupported(),
|
Unsupported(),
|
||||||
|
|
||||||
|
#[error("invalid footer size: {0}, expected: {1}")]
|
||||||
|
InvalidFooterSize(u64, u64),
|
||||||
|
|
||||||
|
#[error("invalid footer version: {0}")]
|
||||||
|
InvalidFooterVersion(u64),
|
||||||
|
|
||||||
|
#[error("footer is unsanitary: {0}")]
|
||||||
|
SanitizeFooter(#[from] SanitizeFooterError),
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,12 @@ use {
|
||||||
error::TieredStorageError, file::TieredStorageFile, index::IndexBlockFormat,
|
error::TieredStorageError, file::TieredStorageFile, index::IndexBlockFormat,
|
||||||
mmap_utils::get_type, TieredStorageResult,
|
mmap_utils::get_type, TieredStorageResult,
|
||||||
},
|
},
|
||||||
|
bytemuck::{Pod, Zeroable},
|
||||||
memmap2::Mmap,
|
memmap2::Mmap,
|
||||||
|
num_enum::TryFromPrimitiveError,
|
||||||
solana_sdk::{hash::Hash, pubkey::Pubkey},
|
solana_sdk::{hash::Hash, pubkey::Pubkey},
|
||||||
std::{mem, path::Path},
|
std::{mem, path::Path},
|
||||||
|
thiserror::Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const FOOTER_FORMAT_VERSION: u64 = 1;
|
pub const FOOTER_FORMAT_VERSION: u64 = 1;
|
||||||
|
@ -22,7 +25,7 @@ pub const FOOTER_TAIL_SIZE: usize = 24;
|
||||||
/// The ending 8 bytes of a valid tiered account storage file.
|
/// The ending 8 bytes of a valid tiered account storage file.
|
||||||
pub const FOOTER_MAGIC_NUMBER: u64 = 0x502A2AB5; // SOLALABS -> SOLANA LABS
|
pub const FOOTER_MAGIC_NUMBER: u64 = 0x502A2AB5; // SOLALABS -> SOLANA LABS
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq, Clone, Copy, Pod, Zeroable)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct TieredStorageMagicNumber(pub u64);
|
pub struct TieredStorageMagicNumber(pub u64);
|
||||||
|
|
||||||
|
@ -86,7 +89,7 @@ pub enum OwnersBlockFormat {
|
||||||
LocalIndex = 0,
|
LocalIndex = 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct TieredStorageFooter {
|
pub struct TieredStorageFooter {
|
||||||
// formats
|
// formats
|
||||||
|
@ -144,6 +147,30 @@ pub struct TieredStorageFooter {
|
||||||
// pub magic_number: u64,
|
// pub magic_number: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// It is undefined behavior to read/write uninitialized bytes.
|
||||||
|
// The `Pod` marker trait indicates there are no uninitialized bytes.
|
||||||
|
// In order to safely guarantee a type is POD, it cannot have any padding.
|
||||||
|
const _: () = assert!(
|
||||||
|
std::mem::size_of::<TieredStorageFooter>()
|
||||||
|
== std::mem::size_of::<AccountMetaFormat>()
|
||||||
|
+ std::mem::size_of::<OwnersBlockFormat>()
|
||||||
|
+ std::mem::size_of::<IndexBlockFormat>()
|
||||||
|
+ std::mem::size_of::<AccountBlockFormat>()
|
||||||
|
+ std::mem::size_of::<u32>() // account_entry_count
|
||||||
|
+ std::mem::size_of::<u32>() // account_meta_entry_size
|
||||||
|
+ std::mem::size_of::<u64>() // account_block_size
|
||||||
|
+ std::mem::size_of::<u32>() // owner_count
|
||||||
|
+ std::mem::size_of::<u32>() // owner_entry_size
|
||||||
|
+ std::mem::size_of::<u64>() // index_block_offset
|
||||||
|
+ std::mem::size_of::<u64>() // owners_block_offset
|
||||||
|
+ std::mem::size_of::<Pubkey>() // min_account_address
|
||||||
|
+ std::mem::size_of::<Pubkey>() // max_account_address
|
||||||
|
+ std::mem::size_of::<Hash>() // hash
|
||||||
|
+ std::mem::size_of::<u64>() // footer_size
|
||||||
|
+ std::mem::size_of::<u64>(), // format_version
|
||||||
|
"TieredStorageFooter cannot have any padding"
|
||||||
|
);
|
||||||
|
|
||||||
impl Default for TieredStorageFooter {
|
impl Default for TieredStorageFooter {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -182,14 +209,23 @@ impl TieredStorageFooter {
|
||||||
|
|
||||||
pub fn new_from_footer_block(file: &TieredStorageFile) -> TieredStorageResult<Self> {
|
pub fn new_from_footer_block(file: &TieredStorageFile) -> TieredStorageResult<Self> {
|
||||||
let mut footer_size: u64 = 0;
|
let mut footer_size: u64 = 0;
|
||||||
let mut footer_version: u64 = 0;
|
|
||||||
let mut magic_number = TieredStorageMagicNumber(0);
|
|
||||||
|
|
||||||
file.seek_from_end(-(FOOTER_TAIL_SIZE as i64))?;
|
file.seek_from_end(-(FOOTER_TAIL_SIZE as i64))?;
|
||||||
file.read_type(&mut footer_size)?;
|
file.read_type(&mut footer_size)?;
|
||||||
file.read_type(&mut footer_version)?;
|
if footer_size != FOOTER_SIZE as u64 {
|
||||||
file.read_type(&mut magic_number)?;
|
return Err(TieredStorageError::InvalidFooterSize(
|
||||||
|
footer_size,
|
||||||
|
FOOTER_SIZE as u64,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut footer_version: u64 = 0;
|
||||||
|
file.read_type(&mut footer_version)?;
|
||||||
|
if footer_version != FOOTER_FORMAT_VERSION {
|
||||||
|
return Err(TieredStorageError::InvalidFooterVersion(footer_version));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut magic_number = TieredStorageMagicNumber::zeroed();
|
||||||
|
file.read_type(&mut magic_number)?;
|
||||||
if magic_number != TieredStorageMagicNumber::default() {
|
if magic_number != TieredStorageMagicNumber::default() {
|
||||||
return Err(TieredStorageError::MagicNumberMismatch(
|
return Err(TieredStorageError::MagicNumberMismatch(
|
||||||
TieredStorageMagicNumber::default().0,
|
TieredStorageMagicNumber::default().0,
|
||||||
|
@ -200,16 +236,27 @@ impl TieredStorageFooter {
|
||||||
let mut footer = Self::default();
|
let mut footer = Self::default();
|
||||||
file.seek_from_end(-(footer_size as i64))?;
|
file.seek_from_end(-(footer_size as i64))?;
|
||||||
file.read_type(&mut footer)?;
|
file.read_type(&mut footer)?;
|
||||||
|
Self::sanitize(&footer)?;
|
||||||
|
|
||||||
Ok(footer)
|
Ok(footer)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_from_mmap(mmap: &Mmap) -> TieredStorageResult<&TieredStorageFooter> {
|
pub fn new_from_mmap(mmap: &Mmap) -> TieredStorageResult<&TieredStorageFooter> {
|
||||||
let offset = mmap.len().saturating_sub(FOOTER_TAIL_SIZE);
|
let offset = mmap.len().saturating_sub(FOOTER_TAIL_SIZE);
|
||||||
let (footer_size, offset) = get_type::<u64>(mmap, offset)?;
|
let (&footer_size, offset) = get_type::<u64>(mmap, offset)?;
|
||||||
let (_footer_version, offset) = get_type::<u64>(mmap, offset)?;
|
if footer_size != FOOTER_SIZE as u64 {
|
||||||
let (magic_number, _offset) = get_type::<TieredStorageMagicNumber>(mmap, offset)?;
|
return Err(TieredStorageError::InvalidFooterSize(
|
||||||
|
footer_size,
|
||||||
|
FOOTER_SIZE as u64,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (footer_version, offset) = get_type::<u64>(mmap, offset)?;
|
||||||
|
if *footer_version != FOOTER_FORMAT_VERSION {
|
||||||
|
return Err(TieredStorageError::InvalidFooterVersion(*footer_version));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (magic_number, _offset) = get_type::<TieredStorageMagicNumber>(mmap, offset)?;
|
||||||
if *magic_number != TieredStorageMagicNumber::default() {
|
if *magic_number != TieredStorageMagicNumber::default() {
|
||||||
return Err(TieredStorageError::MagicNumberMismatch(
|
return Err(TieredStorageError::MagicNumberMismatch(
|
||||||
TieredStorageMagicNumber::default().0,
|
TieredStorageMagicNumber::default().0,
|
||||||
|
@ -217,13 +264,64 @@ impl TieredStorageFooter {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let (footer, _offset) = get_type::<TieredStorageFooter>(
|
let footer_offset = mmap.len().saturating_sub(footer_size as usize);
|
||||||
mmap,
|
let (footer, _offset) = get_type::<TieredStorageFooter>(mmap, footer_offset)?;
|
||||||
mmap.len().saturating_sub(*footer_size as usize),
|
Self::sanitize(footer)?;
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(footer)
|
Ok(footer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sanitizes the footer
|
||||||
|
///
|
||||||
|
/// Since the various formats only have specific valid values, they must be sanitized
|
||||||
|
/// prior to use. This ensures the formats are valid to interpret as (rust) enums.
|
||||||
|
fn sanitize(footer: &Self) -> Result<(), SanitizeFooterError> {
|
||||||
|
let account_meta_format_u16 =
|
||||||
|
unsafe { &*(&footer.account_meta_format as *const _ as *const u16) };
|
||||||
|
let owners_block_format_u16 =
|
||||||
|
unsafe { &*(&footer.owners_block_format as *const _ as *const u16) };
|
||||||
|
let index_block_format_u16 =
|
||||||
|
unsafe { &*(&footer.index_block_format as *const _ as *const u16) };
|
||||||
|
let account_block_format_u16 =
|
||||||
|
unsafe { &*(&footer.account_block_format as *const _ as *const u16) };
|
||||||
|
|
||||||
|
_ = AccountMetaFormat::try_from(*account_meta_format_u16)
|
||||||
|
.map_err(SanitizeFooterError::InvalidAccountMetaFormat)?;
|
||||||
|
_ = OwnersBlockFormat::try_from(*owners_block_format_u16)
|
||||||
|
.map_err(SanitizeFooterError::InvalidOwnersBlockFormat)?;
|
||||||
|
_ = IndexBlockFormat::try_from(*index_block_format_u16)
|
||||||
|
.map_err(SanitizeFooterError::InvalidIndexBlockFormat)?;
|
||||||
|
_ = AccountBlockFormat::try_from(*account_block_format_u16)
|
||||||
|
.map_err(SanitizeFooterError::InvalidAccountBlockFormat)?;
|
||||||
|
|
||||||
|
// Since we just sanitized the formats within the footer,
|
||||||
|
// it is now safe to read them as (rust) enums.
|
||||||
|
//
|
||||||
|
// from https://doc.rust-lang.org/reference/items/enumerations.html#casting:
|
||||||
|
// > If an enumeration is unit-only (with no tuple and struct variants),
|
||||||
|
// > then its discriminant can be directly accessed with a numeric cast;
|
||||||
|
//
|
||||||
|
// from https://doc.rust-lang.org/reference/items/enumerations.html#pointer-casting:
|
||||||
|
// > If the enumeration specifies a primitive representation,
|
||||||
|
// > then the discriminant may be reliably accessed via unsafe pointer casting
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Errors that can happen while sanitizing the footer
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum SanitizeFooterError {
|
||||||
|
#[error("invalid account meta format: {0}")]
|
||||||
|
InvalidAccountMetaFormat(#[from] TryFromPrimitiveError<AccountMetaFormat>),
|
||||||
|
|
||||||
|
#[error("invalid owners block format: {0}")]
|
||||||
|
InvalidOwnersBlockFormat(#[from] TryFromPrimitiveError<OwnersBlockFormat>),
|
||||||
|
|
||||||
|
#[error("invalid index block format: {0}")]
|
||||||
|
InvalidIndexBlockFormat(#[from] TryFromPrimitiveError<IndexBlockFormat>),
|
||||||
|
|
||||||
|
#[error("invalid account block format: {0}")]
|
||||||
|
InvalidAccountBlockFormat(#[from] TryFromPrimitiveError<AccountBlockFormat>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -295,4 +393,75 @@ mod tests {
|
||||||
assert_eq!(offset_of!(TieredStorageFooter, footer_size), 0x90);
|
assert_eq!(offset_of!(TieredStorageFooter, footer_size), 0x90);
|
||||||
assert_eq!(offset_of!(TieredStorageFooter, format_version), 0x98);
|
assert_eq!(offset_of!(TieredStorageFooter, format_version), 0x98);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sanitize() {
|
||||||
|
// test: all good
|
||||||
|
{
|
||||||
|
let footer = TieredStorageFooter::default();
|
||||||
|
let result = TieredStorageFooter::sanitize(&footer);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
// test: bad account meta format
|
||||||
|
{
|
||||||
|
let mut footer = TieredStorageFooter::default();
|
||||||
|
unsafe {
|
||||||
|
std::ptr::write(
|
||||||
|
&mut footer.account_meta_format as *mut _ as *mut u16,
|
||||||
|
0xBAD0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let result = TieredStorageFooter::sanitize(&footer);
|
||||||
|
assert!(matches!(
|
||||||
|
result,
|
||||||
|
Err(SanitizeFooterError::InvalidAccountMetaFormat(_))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// test: bad owners block format
|
||||||
|
{
|
||||||
|
let mut footer = TieredStorageFooter::default();
|
||||||
|
unsafe {
|
||||||
|
std::ptr::write(
|
||||||
|
&mut footer.owners_block_format as *mut _ as *mut u16,
|
||||||
|
0xBAD0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let result = TieredStorageFooter::sanitize(&footer);
|
||||||
|
assert!(matches!(
|
||||||
|
result,
|
||||||
|
Err(SanitizeFooterError::InvalidOwnersBlockFormat(_))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// test: bad index block format
|
||||||
|
{
|
||||||
|
let mut footer = TieredStorageFooter::default();
|
||||||
|
unsafe {
|
||||||
|
std::ptr::write(&mut footer.index_block_format as *mut _ as *mut u16, 0xBAD0);
|
||||||
|
}
|
||||||
|
let result = TieredStorageFooter::sanitize(&footer);
|
||||||
|
assert!(matches!(
|
||||||
|
result,
|
||||||
|
Err(SanitizeFooterError::InvalidIndexBlockFormat(_))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// test: bad account block format
|
||||||
|
{
|
||||||
|
let mut footer = TieredStorageFooter::default();
|
||||||
|
unsafe {
|
||||||
|
std::ptr::write(
|
||||||
|
&mut footer.account_block_format as *mut _ as *mut u16,
|
||||||
|
0xBAD0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let result = TieredStorageFooter::sanitize(&footer);
|
||||||
|
assert!(matches!(
|
||||||
|
result,
|
||||||
|
Err(SanitizeFooterError::InvalidAccountBlockFormat(_))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -207,11 +207,11 @@ impl HotStorageReader {
|
||||||
pub fn new_from_path(path: impl AsRef<Path>) -> TieredStorageResult<Self> {
|
pub fn new_from_path(path: impl AsRef<Path>) -> TieredStorageResult<Self> {
|
||||||
let file = OpenOptions::new().read(true).open(path)?;
|
let file = OpenOptions::new().read(true).open(path)?;
|
||||||
let mmap = unsafe { MmapOptions::new().map(&file)? };
|
let mmap = unsafe { MmapOptions::new().map(&file)? };
|
||||||
// Here we are cloning the footer as accessing any data in a
|
// Here we are copying the footer, as accessing any data in a
|
||||||
// TieredStorage instance requires accessing its Footer.
|
// TieredStorage instance requires accessing its Footer.
|
||||||
// This can help improve cache locality and reduce the overhead
|
// This can help improve cache locality and reduce the overhead
|
||||||
// of indirection associated with memory-mapped accesses.
|
// of indirection associated with memory-mapped accesses.
|
||||||
let footer = TieredStorageFooter::new_from_mmap(&mmap)?.clone();
|
let footer = *TieredStorageFooter::new_from_mmap(&mmap)?;
|
||||||
|
|
||||||
Ok(Self { mmap, footer })
|
Ok(Self { mmap, footer })
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue