diff --git a/src/embedded_flash/buffer_upgrade.rs b/src/embedded_flash/buffer_upgrade.rs new file mode 100644 index 0000000..d01e913 --- /dev/null +++ b/src/embedded_flash/buffer_upgrade.rs @@ -0,0 +1,77 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::helper::ModRange; +use super::upgrade_storage::UpgradeStorage; +use alloc::boxed::Box; +use persistent_store::{StorageError, StorageResult}; + +const PARTITION_LENGTH: usize = 0x40000; +const METADATA_LENGTH: usize = 0x1000; + +pub struct BufferUpgradeStorage { + /// Content of the partition storage. + partition: Box<[u8]>, + + /// Content of the metadata storage. + metadata: Box<[u8]>, +} + +impl BufferUpgradeStorage { + pub fn new() -> StorageResult { + Ok(BufferUpgradeStorage { + partition: vec![0xff; PARTITION_LENGTH].into_boxed_slice(), + metadata: vec![0xff; METADATA_LENGTH].into_boxed_slice(), + }) + } +} + +impl UpgradeStorage for BufferUpgradeStorage { + fn read_partition(&self, offset: usize, length: usize) -> StorageResult<&[u8]> { + let partition_range = ModRange::new(0, self.partition.len()); + if partition_range.contains_range(&ModRange::new(offset, length)) { + Ok(&self.partition[offset..][..length]) + } else { + Err(StorageError::OutOfBounds) + } + } + + fn write_partition(&mut self, offset: usize, data: &[u8]) -> StorageResult<()> { + let partition_range = ModRange::new(0, self.partition.len()); + if partition_range.contains_range(&ModRange::new(offset, data.len())) { + self.partition[offset..][..data.len()].copy_from_slice(data); + Ok(()) + } else { + Err(StorageError::OutOfBounds) + } + } + + fn partition_length(&self) -> usize { + PARTITION_LENGTH + } + + fn read_metadata(&self) -> StorageResult<&[u8]> { + Ok(&self.metadata[..]) + } + + fn write_metadata(&mut self, data: &[u8]) -> StorageResult<()> { + if data.len() <= METADATA_LENGTH { + self.metadata.copy_from_slice(&[0xff; METADATA_LENGTH]); + self.metadata[..data.len()].copy_from_slice(data); + Ok(()) + } else { + Err(StorageError::OutOfBounds) + } + } +} diff --git a/src/embedded_flash/helper.rs b/src/embedded_flash/helper.rs new file mode 100644 index 0000000..90168cb --- /dev/null +++ b/src/embedded_flash/helper.rs @@ -0,0 +1,237 @@ +// Copyright 2019-2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// For compiling with std outside of tests. +#![cfg_attr(feature = "std", allow(dead_code))] + +use core::iter::Iterator; +use persistent_store::{StorageError, StorageResult}; + +/// Reads a slice from a list of slices. +/// +/// The returned slice contains the interval `[start, start+length)`. +/// +/// # Errors +/// +/// Returns [`StorageError::OutOfBounds`] if the range is not within exactly one slice. +pub fn find_slice<'a>( + slices: &'a [&'a [u8]], + mut start: usize, + length: usize, +) -> StorageResult<&'a [u8]> { + for slice in slices { + if start >= slice.len() { + start -= slice.len(); + continue; + } + if start + length > slice.len() { + break; + } + return Ok(&slice[start..][..length]); + } + Err(StorageError::OutOfBounds) +} + +/// Checks whether the address is aligned with the block size. +/// +/// Requires `block_size` to be a power of two. +pub fn is_aligned(block_size: usize, address: usize) -> bool { + debug_assert!(block_size.is_power_of_two()); + address & (block_size - 1) == 0 +} + +/// A range implementation using start and length. +/// +/// The range is treated as the interval `[start, start + length)`. +/// All objects with length of 0, regardless of the start value, are considered empty. +pub struct ModRange { + start: usize, + length: usize, +} + +impl ModRange { + /// Returns a new range of given start and length. + pub fn new(start: usize, length: usize) -> ModRange { + ModRange { start, length } + } + + /// Create a new empty range. + pub fn new_empty() -> ModRange { + ModRange::new(0, 0) + } + + /// Returns the start of the range. + pub fn start(&self) -> usize { + self.start + } + + /// Returns the length of the range. + pub fn length(&self) -> usize { + self.length + } + + /// Returns whether this range contains any addresses. + pub fn is_empty(&self) -> bool { + self.length == 0 + } + + /// Returns the disjoint union with the other range, if is consecutive. + /// + /// Appending empty ranges is not possible. + /// Appending to the empty range returns the other range. + pub fn append(&self, other: ModRange) -> Option { + if self.is_empty() { + return Some(other); + } + if other.is_empty() { + return None; + } + if self.start >= other.start { + return None; + } + if self.length != other.start - self.start { + return None; + } + let new_length = self.length.checked_add(other.length); + new_length.map(|l| ModRange::new(self.start, l)) + } + + /// Returns whether the given range is fully contained. + /// + /// Mathematically, we calculate whether: `self ∩ range = range`. + pub fn contains_range(&self, range: &ModRange) -> bool { + range.is_empty() + || (self.start <= range.start + && range.length <= self.length + && range.start - self.start <= self.length - range.length) + } + + /// Returns an iterator for all contained numbers that are divisible by the modulus. + pub fn aligned_iter(&self, modulus: usize) -> impl Iterator { + (self.start..=usize::MAX) + .take(self.length) + // Skip the minimum number of elements to align. + .skip((modulus - self.start % modulus) % modulus) + // Only return aligned elements. + .step_by(modulus) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn find_slice_ok() { + assert_eq!( + find_slice(&[&[1, 2, 3, 4]], 0, 4).ok(), + Some(&[1u8, 2, 3, 4] as &[u8]) + ); + assert_eq!( + find_slice(&[&[1, 2, 3, 4], &[5, 6]], 1, 2).ok(), + Some(&[2u8, 3] as &[u8]) + ); + assert_eq!( + find_slice(&[&[1, 2, 3, 4], &[5, 6]], 4, 2).ok(), + Some(&[5u8, 6] as &[u8]) + ); + assert_eq!( + find_slice(&[&[1, 2, 3, 4], &[5, 6]], 4, 0).ok(), + Some(&[] as &[u8]) + ); + assert!(find_slice(&[], 0, 1).is_err()); + assert!(find_slice(&[&[1, 2, 3, 4], &[5, 6]], 6, 0).is_err()); + assert!(find_slice(&[&[1, 2, 3, 4], &[5, 6]], 3, 2).is_err()); + } + + #[test] + fn alignment() { + for exponent in 0..8 { + let block_size = 1 << exponent; + for i in 0..10 { + assert!(is_aligned(block_size, block_size * i)); + } + for i in 1..block_size { + assert!(!is_aligned(block_size, block_size + i)); + } + } + } + + #[test] + fn mod_range_parameters() { + let range = ModRange::new(200, 100); + assert_eq!(range.start(), 200); + assert_eq!(range.length(), 100); + assert_eq!(ModRange::new_empty().length(), 0); + } + + #[test] + fn mod_range_is_empty() { + assert!(!ModRange::new(200, 100).is_empty()); + assert!(ModRange::new(200, 0).is_empty()); + assert!(ModRange::new_empty().is_empty()); + assert!(!ModRange::new(usize::MAX, 2).is_empty()); + } + + #[test] + fn mod_range_append() { + let range = ModRange::new(200, 100); + let new_range = range.append(ModRange::new(300, 400)).unwrap(); + assert!(new_range.start() == 200); + assert!(new_range.length() == 500); + assert!(range.append(ModRange::new(299, 400)).is_none()); + assert!(range.append(ModRange::new(301, 400)).is_none()); + assert!(range.append(ModRange::new(200, 400)).is_none()); + let empty_append = ModRange::new_empty() + .append(ModRange::new(200, 100)) + .unwrap(); + assert!(empty_append.start() == 200); + assert!(empty_append.length() == 100); + } + + #[test] + fn mod_range_contains_range() { + let range = ModRange::new(200, 100); + assert!(!range.contains_range(&ModRange::new(199, 100))); + assert!(!range.contains_range(&ModRange::new(201, 100))); + assert!(!range.contains_range(&ModRange::new(199, 99))); + assert!(!range.contains_range(&ModRange::new(202, 99))); + assert!(!range.contains_range(&ModRange::new(200, 101))); + assert!(range.contains_range(&ModRange::new(200, 100))); + assert!(range.contains_range(&ModRange::new(200, 99))); + assert!(range.contains_range(&ModRange::new(201, 99))); + assert!(ModRange::new_empty().contains_range(&ModRange::new_empty())); + assert!(ModRange::new(usize::MAX, 1).contains_range(&ModRange::new(usize::MAX, 1))); + assert!(ModRange::new(usize::MAX, 2).contains_range(&ModRange::new(usize::MAX, 2))); + } + + #[test] + fn mod_range_aligned_iter() { + let mut iter = ModRange::new(200, 100).aligned_iter(100); + assert_eq!(iter.next(), Some(200)); + assert_eq!(iter.next(), None); + let mut iter = ModRange::new(200, 101).aligned_iter(100); + assert_eq!(iter.next(), Some(200)); + assert_eq!(iter.next(), Some(300)); + assert_eq!(iter.next(), None); + let mut iter = ModRange::new(199, 100).aligned_iter(100); + assert_eq!(iter.next(), Some(200)); + assert_eq!(iter.next(), None); + let mut iter = ModRange::new(201, 99).aligned_iter(100); + assert_eq!(iter.next(), None); + let mut iter = ModRange::new(usize::MAX - 16, 20).aligned_iter(16); + assert_eq!(iter.next(), Some(0xf_fff_fff_fff_fff_ff0)); + assert_eq!(iter.next(), None); + } +} diff --git a/src/embedded_flash/mod.rs b/src/embedded_flash/mod.rs index e307a55..5744b29 100644 --- a/src/embedded_flash/mod.rs +++ b/src/embedded_flash/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2020 Google LLC +// Copyright 2019-2021 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,27 +12,36 @@ // See the License for the specific language governing permissions and // limitations under the License. +#[cfg(feature = "std")] +mod buffer_upgrade; +mod helper; #[cfg(not(feature = "std"))] mod syscall; +mod upgrade_storage; -#[cfg(not(feature = "std"))] -pub use self::syscall::SyscallStorage; +pub use upgrade_storage::UpgradeStorage; -/// Storage definition for production. +/// Definitions for production. #[cfg(not(feature = "std"))] mod prod { - pub type Storage = super::SyscallStorage; + use super::syscall::{SyscallStorage, SyscallUpgradeStorage}; + + pub type Storage = SyscallStorage; pub fn new_storage(num_pages: usize) -> Storage { Storage::new(num_pages).unwrap() } + + pub type UpgradeLocations = SyscallUpgradeStorage; } #[cfg(not(feature = "std"))] -pub use self::prod::{new_storage, Storage}; +pub use self::prod::{new_storage, Storage, UpgradeLocations}; -/// Storage definition for testing. +/// Definitions for testing. #[cfg(feature = "std")] mod test { + use super::buffer_upgrade::BufferUpgradeStorage; + pub type Storage = persistent_store::BufferStorage; pub fn new_storage(num_pages: usize) -> Storage { @@ -47,6 +56,8 @@ mod test { }; Storage::new(store, options) } + + pub type UpgradeLocations = BufferUpgradeStorage; } #[cfg(feature = "std")] -pub use self::test::{new_storage, Storage}; +pub use self::test::{new_storage, Storage, UpgradeLocations}; diff --git a/src/embedded_flash/syscall.rs b/src/embedded_flash/syscall.rs index 6fae49e..5902ca9 100644 --- a/src/embedded_flash/syscall.rs +++ b/src/embedded_flash/syscall.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2020 Google LLC +// Copyright 2019-2021 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use super::helper::{find_slice, is_aligned, ModRange}; +use super::upgrade_storage::UpgradeStorage; use alloc::vec::Vec; use libtock_core::syscalls; use persistent_store::{Storage, StorageError, StorageIndex, StorageResult}; @@ -43,6 +45,8 @@ mod memop_nr { mod storage_type { pub const STORE: usize = 1; + pub const PARTITION: usize = 2; + pub const METADATA: usize = 3; } fn get_info(nr: usize, arg: usize) -> StorageResult { @@ -59,6 +63,37 @@ fn memop(nr: u32, arg: usize) -> StorageResult { } } +fn write_slice(ptr: usize, value: &[u8]) -> StorageResult<()> { + let code = unsafe { + syscalls::raw::allow( + DRIVER_NUMBER, + allow_nr::WRITE_SLICE, + // We rely on the driver not writing to the slice. This should use read-only allow + // when available. See https://github.com/tock/tock/issues/1274. + value.as_ptr() as *mut u8, + value.len(), + ) + }; + if code < 0 { + return Err(StorageError::CustomError); + } + + let code = syscalls::command(DRIVER_NUMBER, command_nr::WRITE_SLICE, ptr, value.len()); + if code.is_err() { + return Err(StorageError::CustomError); + } + + Ok(()) +} + +fn erase_page(ptr: usize, page_length: usize) -> StorageResult<()> { + let code = syscalls::command(DRIVER_NUMBER, command_nr::ERASE_PAGE, ptr, page_length); + if code.is_err() { + return Err(StorageError::CustomError); + } + Ok(()) +} + pub struct SyscallStorage { word_size: usize, page_size: usize, @@ -118,11 +153,11 @@ impl SyscallStorage { } fn is_word_aligned(&self, x: usize) -> bool { - x & (self.word_size - 1) == 0 + is_aligned(self.word_size, x) } fn is_page_aligned(&self, x: usize) -> bool { - x & (self.page_size - 1) == 0 + is_aligned(self.page_size, x) } } @@ -157,83 +192,136 @@ impl Storage for SyscallStorage { return Err(StorageError::NotAligned); } let ptr = self.read_slice(index, value.len())?.as_ptr() as usize; - - let code = unsafe { - syscalls::raw::allow( - DRIVER_NUMBER, - allow_nr::WRITE_SLICE, - // We rely on the driver not writing to the slice. This should use read-only allow - // when available. See https://github.com/tock/tock/issues/1274. - value.as_ptr() as *mut u8, - value.len(), - ) - }; - if code < 0 { - return Err(StorageError::CustomError); - } - - let code = syscalls::command(DRIVER_NUMBER, command_nr::WRITE_SLICE, ptr, value.len()); - if code.is_err() { - return Err(StorageError::CustomError); - } - - Ok(()) + write_slice(ptr, value) } fn erase_page(&mut self, page: usize) -> StorageResult<()> { let index = StorageIndex { page, byte: 0 }; let length = self.page_size(); let ptr = self.read_slice(index, length)?.as_ptr() as usize; - let code = syscalls::command(DRIVER_NUMBER, command_nr::ERASE_PAGE, ptr, length); - if code.is_err() { + erase_page(ptr, length) + } +} + +pub struct SyscallUpgradeStorage { + page_size: usize, + partition: ModRange, + metadata: ModRange, +} + +impl SyscallUpgradeStorage { + /// Provides access to the other upgrade partition and metadata if available. + /// + /// The implementation assumes that storage locations returned by the kernel through + /// `memop_nr::STORAGE_*` calls are in address space order. + /// + /// # Errors + /// + /// Returns `CustomError` if any of the following conditions do not hold: + /// - The page size is a power of two. + /// - The storage slices are page-aligned. + /// - There are not partition or metadata slices. + /// Returns a `NotAligned` error if partitions or metadata ranges are + /// - not exclusive or, + /// - not consecutive. + pub fn new() -> StorageResult { + let mut locations = SyscallUpgradeStorage { + page_size: get_info(command_nr::get_info_nr::PAGE_SIZE, 0)?, + partition: ModRange::new_empty(), + metadata: ModRange::new_empty(), + }; + if !locations.page_size.is_power_of_two() { return Err(StorageError::CustomError); } - Ok(()) - } -} - -fn find_slice<'a>( - slices: &'a [&'a [u8]], - mut start: usize, - length: usize, -) -> StorageResult<&'a [u8]> { - for slice in slices { - if start >= slice.len() { - start -= slice.len(); - continue; + for i in 0..memop(memop_nr::STORAGE_CNT, 0)? { + let storage_type = memop(memop_nr::STORAGE_TYPE, i)?; + match storage_type { + storage_type::PARTITION | storage_type::METADATA => (), + _ => continue, + }; + let storage_ptr = memop(memop_nr::STORAGE_PTR, i)?; + let storage_len = memop(memop_nr::STORAGE_LEN, i)?; + if !locations.is_page_aligned(storage_ptr) || !locations.is_page_aligned(storage_len) { + return Err(StorageError::CustomError); + } + let range = ModRange::new(storage_ptr, storage_len); + match storage_type { + storage_type::PARTITION => { + locations.partition = locations + .partition + .append(range) + .ok_or(StorageError::NotAligned)? + } + storage_type::METADATA => { + locations.metadata = locations + .metadata + .append(range) + .ok_or(StorageError::NotAligned)? + } + _ => (), + }; } - if start + length > slice.len() { - break; + if locations.partition.is_empty() || locations.metadata.is_empty() { + Err(StorageError::CustomError) + } else { + Ok(locations) } - return Ok(&slice[start..][..length]); } - Err(StorageError::OutOfBounds) -} -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn find_slice_ok() { - assert_eq!( - find_slice(&[&[1, 2, 3, 4]], 0, 4).ok(), - Some(&[1u8, 2, 3, 4] as &[u8]) - ); - assert_eq!( - find_slice(&[&[1, 2, 3, 4], &[5, 6]], 1, 2).ok(), - Some(&[2u8, 3] as &[u8]) - ); - assert_eq!( - find_slice(&[&[1, 2, 3, 4], &[5, 6]], 4, 2).ok(), - Some(&[5u8, 6] as &[u8]) - ); - assert_eq!( - find_slice(&[&[1, 2, 3, 4], &[5, 6]], 4, 0).ok(), - Some(&[] as &[u8]) - ); - assert!(find_slice(&[], 0, 1).is_err()); - assert!(find_slice(&[&[1, 2, 3, 4], &[5, 6]], 6, 0).is_err()); - assert!(find_slice(&[&[1, 2, 3, 4], &[5, 6]], 3, 2).is_err()); + fn is_page_aligned(&self, x: usize) -> bool { + is_aligned(self.page_size, x) + } +} + +impl UpgradeStorage for SyscallUpgradeStorage { + fn read_partition(&self, offset: usize, length: usize) -> StorageResult<&[u8]> { + let address = self.partition.start() + offset; + if self + .partition + .contains_range(&ModRange::new(address, length)) + { + Ok(unsafe { core::slice::from_raw_parts(address as *const u8, length) }) + } else { + Err(StorageError::OutOfBounds) + } + } + + fn write_partition(&mut self, offset: usize, data: &[u8]) -> StorageResult<()> { + let address = self.partition.start() + offset; + if self + .partition + .contains_range(&ModRange::new(address, data.len())) + { + // 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 ModRange::new(address, data.len()).aligned_iter(self.page_size) { + erase_page(address, self.page_size)?; + } + write_slice(address, data) + } else { + Err(StorageError::OutOfBounds) + } + } + + fn partition_length(&self) -> usize { + self.partition.length() + } + + fn read_metadata(&self) -> StorageResult<&[u8]> { + Ok(unsafe { + core::slice::from_raw_parts(self.metadata.start() as *const u8, self.metadata.length()) + }) + } + + fn write_metadata(&mut self, data: &[u8]) -> StorageResult<()> { + // If less data is passed in than is reserved, assume the rest is 0xFF. + if data.len() <= self.metadata.length() { + for address in self.metadata.aligned_iter(self.page_size) { + erase_page(address, self.page_size)?; + } + write_slice(self.metadata.start(), data) + } else { + Err(StorageError::OutOfBounds) + } } } diff --git a/src/embedded_flash/upgrade_storage.rs b/src/embedded_flash/upgrade_storage.rs new file mode 100644 index 0000000..1199a35 --- /dev/null +++ b/src/embedded_flash/upgrade_storage.rs @@ -0,0 +1,51 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use persistent_store::StorageResult; + +/// Accessors to storage locations used for upgrading from a CTAP command. +pub trait UpgradeStorage { + /// Reads a slice of the partition, if within bounds. + /// + /// The offset is relative to the start of the partition. + /// + /// # Errors + /// + /// Returns [`StorageError::OutOfBounds`] if the requested slice is not inside the partition. + fn read_partition(&self, offset: usize, length: usize) -> StorageResult<&[u8]>; + + /// Writes the given data to the given offset address, if within bounds of the partition. + /// + /// The offset is relative to the start of the partition. + /// + /// # Errors + /// + /// Returns [`StorageError::OutOfBounds`] if the data does not fit the partition. + fn write_partition(&mut self, offset: usize, data: &[u8]) -> StorageResult<()>; + + /// Returns the length of the partition. + fn partition_length(&self) -> usize; + + /// Reads the metadata location. + fn read_metadata(&self) -> StorageResult<&[u8]>; + + /// Writes the given data into the metadata location. + /// + /// The passed in data is appended with 0xFF bytes if shorter than the metadata storage. + /// + /// # Errors + /// + /// Returns [`StorageError::OutOfBounds`] if the data is too long to fit the metadata storage. + fn write_metadata(&mut self, data: &[u8]) -> StorageResult<()>; +}