pvmfw: Add support for appended configuration data

Implement a standardized way for pvmfw to receive data at load time,
which can be used by the platform to pass device-specific secrets or
influence the boot process. Unlike data received from the VMM, it is
(and must be) trusted.

Previously, the payload appended to pvmfw was the BCC (now incorporated
into the config data). To avoid breaking devices that do not yet support
this config format, if the appended data doesn't contain a valid config
header, fall back to assuming that it is a raw BCC when the 'legacy'
feature is enabled (default).

Bug: 238050226
Bug: 256827715
Test: atest MicrodroidTestApp
Change-Id: I2614e5df34df19052d7e12d24280d581dfaf06f7
diff --git a/pvmfw/src/config.rs b/pvmfw/src/config.rs
new file mode 100644
index 0000000..0f2a39c
--- /dev/null
+++ b/pvmfw/src/config.rs
@@ -0,0 +1,200 @@
+// Copyright 2022, The Android Open Source Project
+//
+// 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.
+
+//! Support for the pvmfw configuration data format.
+
+use crate::helpers;
+use core::fmt;
+use core::mem;
+use core::num::NonZeroUsize;
+use core::ops;
+use core::result;
+
+#[repr(C, packed)]
+#[derive(Clone, Copy, Debug)]
+struct Header {
+    magic: u32,
+    version: u32,
+    total_size: u32,
+    flags: u32,
+    entries: [HeaderEntry; Entry::COUNT],
+}
+
+#[derive(Debug)]
+pub enum Error {
+    /// Reserved region can't fit configuration header.
+    BufferTooSmall,
+    /// Header doesn't contain the expect magic value.
+    InvalidMagic,
+    /// Version of the header isn't supported.
+    UnsupportedVersion(u16, u16),
+    /// Header sets flags incorrectly or uses reserved flags.
+    InvalidFlags(u32),
+    /// Header describes configuration data that doesn't fit in the expected buffer.
+    InvalidSize(usize),
+    /// Header entry is invalid.
+    InvalidEntry(Entry),
+}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Self::BufferTooSmall => write!(f, "Reserved region is smaller than config header"),
+            Self::InvalidMagic => write!(f, "Wrong magic number"),
+            Self::UnsupportedVersion(x, y) => write!(f, "Version {x}.{y} not supported"),
+            Self::InvalidFlags(v) => write!(f, "Flags value {v:#x} is incorrect or reserved"),
+            Self::InvalidSize(sz) => write!(f, "Total size ({sz:#x}) overflows reserved region"),
+            Self::InvalidEntry(e) => write!(f, "Entry {e:?} is invalid"),
+        }
+    }
+}
+
+pub type Result<T> = result::Result<T, Error>;
+
+impl Header {
+    const MAGIC: u32 = u32::from_ne_bytes(*b"pvmf");
+    const PADDED_SIZE: usize =
+        helpers::unchecked_align_up(mem::size_of::<Self>(), mem::size_of::<u64>());
+
+    pub const fn version(major: u16, minor: u16) -> u32 {
+        ((major as u32) << 16) | (minor as u32)
+    }
+
+    pub const fn version_tuple(&self) -> (u16, u16) {
+        ((self.version >> 16) as u16, self.version as u16)
+    }
+
+    pub fn total_size(&self) -> usize {
+        self.total_size as usize
+    }
+
+    pub fn body_size(&self) -> usize {
+        self.total_size() - Self::PADDED_SIZE
+    }
+
+    fn get(&self, entry: Entry) -> HeaderEntry {
+        self.entries[entry as usize]
+    }
+}
+
+#[derive(Clone, Copy, Debug)]
+pub enum Entry {
+    Bcc = 0,
+    DebugPolicy = 1,
+}
+
+impl Entry {
+    const COUNT: usize = 2;
+}
+
+#[repr(packed)]
+#[derive(Clone, Copy, Debug)]
+struct HeaderEntry {
+    offset: u32,
+    size: u32,
+}
+
+impl HeaderEntry {
+    pub fn is_empty(&self) -> bool {
+        self.offset() == 0 && self.size() == 0
+    }
+
+    pub fn fits_in(&self, max_size: usize) -> bool {
+        (Header::PADDED_SIZE..max_size).contains(&self.offset())
+            && NonZeroUsize::new(self.size())
+                .and_then(|s| s.checked_add(self.offset()))
+                .filter(|&x| x.get() <= max_size)
+                .is_some()
+    }
+
+    pub fn as_body_range(&self) -> ops::Range<usize> {
+        let start = self.offset() - Header::PADDED_SIZE;
+
+        start..(start + self.size())
+    }
+
+    pub fn offset(&self) -> usize {
+        self.offset as usize
+    }
+
+    pub fn size(&self) -> usize {
+        self.size as usize
+    }
+}
+
+#[derive(Debug)]
+pub struct Config<'a> {
+    header: &'a Header,
+    body: &'a mut [u8],
+}
+
+impl<'a> Config<'a> {
+    /// Take ownership of a pvmfw configuration consisting of its header and following entries.
+    ///
+    /// SAFETY - 'data' should respect the alignment of Header.
+    pub unsafe fn new(data: &'a mut [u8]) -> Result<Self> {
+        let header = data.get(..Header::PADDED_SIZE).ok_or(Error::BufferTooSmall)?;
+
+        let header = &*(header.as_ptr() as *const Header);
+
+        if header.magic != Header::MAGIC {
+            return Err(Error::InvalidMagic);
+        }
+
+        if header.version != Header::version(1, 0) {
+            let (major, minor) = header.version_tuple();
+            return Err(Error::UnsupportedVersion(major, minor));
+        }
+
+        if header.flags != 0 {
+            return Err(Error::InvalidFlags(header.flags));
+        }
+
+        let total_size = header.total_size();
+
+        // BCC is a mandatory entry of the configuration data.
+        if !header.get(Entry::Bcc).fits_in(total_size) {
+            return Err(Error::InvalidEntry(Entry::Bcc));
+        }
+
+        // Debug policy is optional.
+        let dp = header.get(Entry::DebugPolicy);
+        if !dp.is_empty() && !dp.fits_in(total_size) {
+            return Err(Error::InvalidEntry(Entry::DebugPolicy));
+        }
+
+        let body = data
+            .get_mut(Header::PADDED_SIZE..)
+            .ok_or(Error::BufferTooSmall)?
+            .get_mut(..header.body_size())
+            .ok_or(Error::InvalidSize(total_size))?;
+
+        Ok(Self { header, body })
+    }
+
+    /// Get slice containing the platform BCC.
+    pub fn get_bcc_mut(&mut self) -> &mut [u8] {
+        &mut self.body[self.header.get(Entry::Bcc).as_body_range()]
+    }
+
+    /// Get slice containing the platform debug policy.
+    pub fn get_debug_policy(&mut self) -> Option<&mut [u8]> {
+        let entry = self.header.get(Entry::DebugPolicy);
+        if entry.is_empty() {
+            None
+        } else {
+            Some(&mut self.body[entry.as_body_range()])
+        }
+    }
+}
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index 18ff1c1..b840488 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -14,6 +14,7 @@
 
 //! Low-level entry and exit points of pvmfw.
 
+use crate::config;
 use crate::fdt;
 use crate::heap;
 use crate::helpers;
@@ -26,6 +27,7 @@
 use log::debug;
 use log::error;
 use log::info;
+use log::warn;
 use log::LevelFilter;
 use vmbase::{console, layout, logger, main, power::reboot};
 
@@ -33,6 +35,8 @@
 enum RebootReason {
     /// A malformed BCC was received.
     InvalidBcc,
+    /// An invalid configuration was appended to pvmfw.
+    InvalidConfig,
     /// An unexpected internal error happened.
     InternalError,
     /// The provided FDT was invalid.
@@ -197,14 +201,14 @@
         RebootReason::InternalError
     })?;
 
-    let appended_range = appended_data.as_ptr_range();
-    let appended_range = (appended_range.start as usize)..(appended_range.end as usize);
-    page_table.map_rodata(&appended_range).map_err(|e| {
-        error!("Failed to remap the appended payload as a dynamic page table entry: {e}");
-        RebootReason::InternalError
+    // SAFETY - We only get the appended payload from here, once. It is statically mapped and the
+    // linker script prevents it from overlapping with other objects.
+    let mut appended = unsafe { AppendedPayload::new(appended_data) }.ok_or_else(|| {
+        error!("No valid configuration found");
+        RebootReason::InvalidConfig
     })?;
 
-    let bcc = as_bcc(appended_data).ok_or_else(|| {
+    let bcc = appended.get_bcc_mut().ok_or_else(|| {
         error!("Invalid BCC");
         RebootReason::InvalidBcc
     })?;
@@ -296,13 +300,50 @@
     slice::from_raw_parts_mut(base as *mut u8, size)
 }
 
-fn as_bcc(data: &mut [u8]) -> Option<&mut [u8]> {
-    const BCC_SIZE: usize = helpers::SIZE_4KB;
+enum AppendedPayload<'a> {
+    /// Configuration data.
+    Config(config::Config<'a>),
+    /// Deprecated raw BCC, as used in Android T.
+    LegacyBcc(&'a mut [u8]),
+}
 
-    if cfg!(feature = "legacy") {
+impl<'a> AppendedPayload<'a> {
+    /// SAFETY - 'data' should respect the alignment of config::Header.
+    unsafe fn new(data: &'a mut [u8]) -> Option<Self> {
+        if Self::is_valid_config(data) {
+            Some(Self::Config(config::Config::new(data).unwrap()))
+        } else if cfg!(feature = "legacy") {
+            const BCC_SIZE: usize = helpers::SIZE_4KB;
+            warn!("Assuming the appended data at {:?} to be a raw BCC", data.as_ptr());
+            Some(Self::LegacyBcc(&mut data[..BCC_SIZE]))
+        } else {
+            None
+        }
+    }
+
+    unsafe fn is_valid_config(data: &mut [u8]) -> bool {
+        // This function is necessary to prevent the borrow checker from getting confused
+        // about the ownership of data in new(); see https://users.rust-lang.org/t/78467.
+        let addr = data.as_ptr();
+        config::Config::new(data)
+            .map_err(|e| warn!("Invalid configuration data at {addr:?}: {e}"))
+            .is_ok()
+    }
+
+    #[allow(dead_code)] // TODO(b/232900974)
+    fn get_debug_policy(&mut self) -> Option<&mut [u8]> {
+        match self {
+            Self::Config(ref mut cfg) => cfg.get_debug_policy(),
+            Self::LegacyBcc(_) => None,
+        }
+    }
+
+    fn get_bcc_mut(&mut self) -> Option<&mut [u8]> {
+        let bcc = match self {
+            Self::LegacyBcc(ref mut bcc) => bcc,
+            Self::Config(ref mut cfg) => cfg.get_bcc_mut(),
+        };
         // TODO(b/256148034): return None if BccHandoverParse(bcc) != kDiceResultOk.
-        Some(&mut data[..BCC_SIZE])
-    } else {
-        None
+        Some(bcc)
     }
 }
diff --git a/pvmfw/src/helpers.rs b/pvmfw/src/helpers.rs
index 9062957..ead8bb4 100644
--- a/pvmfw/src/helpers.rs
+++ b/pvmfw/src/helpers.rs
@@ -26,6 +26,13 @@
     addr & !(alignment - 1)
 }
 
+/// Computes the smallest multiple of the provided alignment larger or equal to the address.
+///
+/// Note: the result is undefined if alignment isn't a power of two and may wrap to 0.
+pub const fn unchecked_align_up(addr: usize, alignment: usize) -> usize {
+    unchecked_align_down(addr + alignment - 1, alignment)
+}
+
 /// Safe wrapper around unchecked_align_up() that validates its assumptions and doesn't wrap.
 pub const fn align_up(addr: usize, alignment: usize) -> Option<usize> {
     if !alignment.is_power_of_two() {
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index 97a79ec..6810fda 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -17,8 +17,10 @@
 #![no_main]
 #![no_std]
 #![feature(default_alloc_error_handler)]
+#![feature(ptr_const_cast)] // Stabilized in 1.65.0
 
 mod avb;
+mod config;
 mod entry;
 mod exceptions;
 mod fdt;
diff --git a/pvmfw/src/mmu.rs b/pvmfw/src/mmu.rs
index d68e389..fa94e85 100644
--- a/pvmfw/src/mmu.rs
+++ b/pvmfw/src/mmu.rs
@@ -14,6 +14,7 @@
 
 //! Memory management.
 
+use crate::helpers;
 use aarch64_paging::idmap::IdMap;
 use aarch64_paging::paging::Attributes;
 use aarch64_paging::paging::MemoryRegion;
@@ -35,6 +36,14 @@
     idmap: IdMap,
 }
 
+fn appended_payload_range() -> Range<usize> {
+    let start = helpers::align_up(layout::binary_end(), helpers::SIZE_4KB).unwrap();
+    // pvmfw is contained in a 2MiB region so the payload can't be larger than the 2MiB alignment.
+    let end = helpers::align_up(start, helpers::SIZE_2MB).unwrap();
+
+    start..end
+}
+
 impl PageTable {
     const ASID: usize = 1;
     const ROOT_LEVEL: usize = 1;
@@ -46,6 +55,7 @@
         page_table.map_code(&layout::text_range())?;
         page_table.map_data(&layout::writable_region())?;
         page_table.map_rodata(&layout::rodata_range())?;
+        page_table.map_data(&appended_payload_range())?;
 
         Ok(page_table)
     }