New Upgrade Interface (#543)

* includes metadata inside partition, introduces the partition helper

* style improvements
This commit is contained in:
kaczmarczyck
2022-09-13 10:06:58 +02:00
committed by GitHub
parent 8288bb0860
commit d6994e3bc3
7 changed files with 335 additions and 244 deletions

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::api::upgrade_storage::helper::{find_slice, is_aligned, ModRange};
use crate::api::upgrade_storage::helper::{find_slice, is_aligned, ModRange, Partition};
use crate::api::upgrade_storage::UpgradeStorage;
use alloc::borrow::Cow;
use alloc::vec::Vec;
@@ -231,16 +231,17 @@ impl Storage for TockStorage {
pub struct TockUpgradeStorage {
page_size: usize,
partition: ModRange,
partition: Partition,
metadata: ModRange,
running_metadata: ModRange,
identifier: u32,
}
impl TockUpgradeStorage {
// Ideally, the kernel should tell us metadata and partitions directly.
// This code only works for one layout, refactor this into the storage driver to support more.
const METADATA_ADDRESS: usize = 0x4000;
const _PARTITION_ADDRESS_A: usize = 0x20000;
const PARTITION_ADDRESS_A: usize = 0x20000;
const PARTITION_ADDRESS_B: usize = 0x60000;
/// Provides access to the other upgrade partition and metadata if available.
@@ -260,13 +261,15 @@ impl TockUpgradeStorage {
pub fn new() -> StorageResult<TockUpgradeStorage> {
let mut locations = TockUpgradeStorage {
page_size: get_info(command_nr::get_info_nr::PAGE_SIZE, 0)?,
partition: ModRange::new_empty(),
partition: Partition::new(),
metadata: ModRange::new_empty(),
running_metadata: ModRange::new_empty(),
identifier: Self::PARTITION_ADDRESS_A as u32,
};
if !locations.page_size.is_power_of_two() {
return Err(StorageError::CustomError);
}
let mut firmware_range = ModRange::new_empty();
for i in 0..memop(memop_nr::STORAGE_CNT, 0)? {
let storage_type = memop(memop_nr::STORAGE_TYPE, i)?;
if !matches!(storage_type, storage_type::PARTITION) {
@@ -286,21 +289,27 @@ impl TockUpgradeStorage {
ModRange::new(range.start() + locations.page_size, locations.page_size);
}
_ => {
locations.partition = locations
.partition
.append(range)
.ok_or(StorageError::NotAligned)?
if !firmware_range.append(&range) {
return Err(StorageError::NotAligned);
}
}
}
}
if locations.partition.is_empty()
if firmware_range.is_empty()
|| locations.metadata.is_empty()
|| locations.running_metadata.is_empty()
{
return Err(StorageError::CustomError);
}
if locations.partition.start() == Self::PARTITION_ADDRESS_B {
if firmware_range.start() == Self::PARTITION_ADDRESS_B {
core::mem::swap(&mut locations.metadata, &mut locations.running_metadata);
locations.identifier = Self::PARTITION_ADDRESS_B as u32;
}
if !locations.partition.append(locations.metadata.clone()) {
return Err(StorageError::NotAligned);
}
if !locations.partition.append(firmware_range) {
return Err(StorageError::NotAligned);
}
Ok(locations)
}
@@ -308,6 +317,38 @@ impl TockUpgradeStorage {
fn is_page_aligned(&self, x: usize) -> bool {
is_aligned(self.page_size, x)
}
/// Returns whether the metadata is contained in this range or not.
///
/// Assumes that metadata is written in one call per range. If the metadata is only partially
/// contained, returns an error.
fn contains_metadata(&self, checked_range: &ModRange) -> StorageResult<bool> {
if checked_range.intersects_range(&self.metadata) {
if checked_range.contains_range(&self.metadata) {
Ok(true)
} else {
Err(StorageError::NotAligned)
}
} else {
Ok(false)
}
}
/// Checks if the metadata's hash matches the partition's content.
fn check_partition_hash(&self, metadata: &[u8]) -> StorageResult<()> {
let start_address = self.metadata.start() + METADATA_SIGN_OFFSET;
let mut hasher = Sha256::new();
for range in self.partition.ranges_from(start_address) {
let partition_slice = unsafe { read_slice(range.start(), range.length()) };
// The hash implementation handles this in chunks, so no memory issues.
hasher.update(partition_slice);
}
let computed_hash = hasher.finalize();
if &computed_hash != parse_metadata_hash(metadata) {
return Err(StorageError::CustomError);
}
Ok(())
}
}
impl UpgradeStorage for TockUpgradeStorage {
@@ -315,11 +356,7 @@ impl UpgradeStorage for TockUpgradeStorage {
if length == 0 {
return Err(StorageError::OutOfBounds);
}
let address = self.partition.start() + offset;
if self
.partition
.contains_range(&ModRange::new(address, length))
{
if let Some(address) = self.partition.find_address(offset, length) {
Ok(unsafe { read_slice(address, length) })
} else {
Err(StorageError::OutOfBounds)
@@ -330,44 +367,38 @@ impl UpgradeStorage for TockUpgradeStorage {
if data.is_empty() {
return Err(StorageError::OutOfBounds);
}
let address = self.partition.start() + offset;
let address = self
.partition
.find_address(offset, data.len())
.ok_or(StorageError::OutOfBounds)?;
let write_range = ModRange::new(address, data.len());
if self.partition.contains_range(&write_range) {
// Erases all pages that have their first byte in the write range.
// Since we expect calls in order, we don't want to erase half-written pages.
for address in write_range.aligned_iter(self.page_size) {
erase_page(address, self.page_size)?;
}
write_slice(address, data)
} else {
Err(StorageError::OutOfBounds)
if self.contains_metadata(&write_range)? {
let new_metadata = &data[self.metadata.start() - address..][..self.metadata.length()];
check_metadata(self, UPGRADE_PUBLIC_KEY, new_metadata)?;
}
// Erases all pages that have their first byte in the write range.
// Since we expect calls in order, we don't want to erase half-written pages.
for address in write_range.aligned_iter(self.page_size) {
erase_page(address, self.page_size)?;
}
write_slice(address, data)?;
// Case: Last slice is written.
if data.len() == self.partition_length() - offset {
let metadata = unsafe { read_slice(self.metadata.start(), self.metadata.length()) };
self.check_partition_hash(&metadata)?;
}
Ok(())
}
fn partition_address(&self) -> usize {
self.partition.start()
fn partition_identifier(&self) -> u32 {
self.identifier
}
fn partition_length(&self) -> usize {
self.partition.length()
}
fn read_metadata(&self) -> StorageResult<&[u8]> {
Ok(unsafe { read_slice(self.metadata.start(), self.metadata.length()) })
}
fn write_metadata(&mut self, data: &[u8]) -> StorageResult<()> {
if data.len() != self.metadata.length() {
return Err(StorageError::CustomError);
}
// Compares the hash inside the metadata to the actual hash.
parse_metadata(self, UPGRADE_PUBLIC_KEY, &data)?;
for address in self.metadata.aligned_iter(self.page_size) {
erase_page(address, self.page_size)?;
}
write_slice(self.metadata.start(), data)
}
fn running_firmware_version(&self) -> u64 {
let running_metadata = unsafe {
read_slice(
@@ -389,8 +420,9 @@ impl UpgradeStorage for TockUpgradeStorage {
/// - 4 B partition address in little endian encoding
/// written at METADATA_SIGN_OFFSET.
///
/// Checks hash and signature correctness, and whether the partition offset matches.
fn parse_metadata(
/// Checks signature correctness against the hash, and whether the partition offset matches.
/// Whether the hash matches the partition content is not tested here!
fn check_metadata(
upgrade_locations: &impl UpgradeStorage,
public_key_bytes: &[u8],
metadata: &[u8],
@@ -406,29 +438,23 @@ fn parse_metadata(
}
let metadata_address = LittleEndian::read_u32(&metadata[METADATA_SIGN_OFFSET + 8..][..4]);
if metadata_address as usize != upgrade_locations.partition_address() {
return Err(StorageError::CustomError);
}
// The hash implementation handles this in chunks, so no memory issues.
let partition_slice =
upgrade_locations.read_partition(0, upgrade_locations.partition_length())?;
let mut hasher = Sha256::new();
hasher.update(&metadata[METADATA_SIGN_OFFSET..]);
hasher.update(partition_slice);
let computed_hash = hasher.finalize();
if &computed_hash != array_ref!(metadata, 0, 32) {
if metadata_address != upgrade_locations.partition_identifier() {
return Err(StorageError::CustomError);
}
verify_signature(
array_ref!(metadata, 32, 64),
public_key_bytes,
&computed_hash,
parse_metadata_hash(metadata),
)?;
Ok(())
}
/// Parses the metadata, returns the hash.
fn parse_metadata_hash(data: &[u8]) -> &[u8; 32] {
array_ref!(data, 0, 32)
}
/// Parses the metadata, returns the firmware version.
fn parse_metadata_version(data: &[u8]) -> u64 {
LittleEndian::read_u64(&data[METADATA_SIGN_OFFSET..][..8])
@@ -459,7 +485,7 @@ mod test {
use crate::env::Env;
#[test]
fn test_parse_metadata() {
fn test_check_metadata() {
let mut env = TestEnv::new();
let private_key = crypto::ecdsa::SecKey::gensk(env.rng());
let upgrade_locations = env.upgrade_storage().unwrap();
@@ -489,42 +515,37 @@ mod test {
public_key.to_bytes_uncompressed(&mut public_key_bytes);
assert_eq!(
parse_metadata(upgrade_locations, &public_key_bytes, &metadata),
check_metadata(upgrade_locations, &public_key_bytes, &metadata),
Ok(())
);
// Manipulating the partition address fails.
metadata[METADATA_SIGN_OFFSET] = 0x88;
metadata[METADATA_SIGN_OFFSET + 8] = 0x88;
assert_eq!(
parse_metadata(upgrade_locations, &public_key_bytes, &metadata),
check_metadata(upgrade_locations, &public_key_bytes, &metadata),
Err(StorageError::CustomError)
);
metadata[METADATA_SIGN_OFFSET] = 0x00;
// Any manipulation of signed data fails.
metadata[METADATA_LEN - 1] = 0x88;
metadata[METADATA_SIGN_OFFSET + 8] = 0x00;
// Wrong metadata length fails.
assert_eq!(
parse_metadata(upgrade_locations, &public_key_bytes, &metadata),
check_metadata(
upgrade_locations,
&public_key_bytes,
&metadata[..METADATA_LEN - 1]
),
Err(StorageError::CustomError)
);
metadata[METADATA_LEN - 1] = 0xFF;
// Manipulating the hash fails.
metadata[0] ^= 0x01;
assert_eq!(
parse_metadata(upgrade_locations, &public_key_bytes, &metadata),
check_metadata(upgrade_locations, &public_key_bytes, &metadata),
Err(StorageError::CustomError)
);
metadata[0] ^= 0x01;
// Manipulating the signature fails.
metadata[32] ^= 0x01;
assert_eq!(
parse_metadata(upgrade_locations, &public_key_bytes, &metadata),
Err(StorageError::CustomError)
);
metadata[32] ^= 0x01;
// Manipulating the partition data fails.
upgrade_locations.write_partition(0, &[0x88; 1]).unwrap();
assert_eq!(
parse_metadata(upgrade_locations, &public_key_bytes, &metadata),
check_metadata(upgrade_locations, &public_key_bytes, &metadata),
Err(StorageError::CustomError)
);
}