vmbase: Configure stack guard from Rust
Move the thorough validation of the availability of TRNG that was
performed in assembly to Rust and call rand::init() from the entry code
of vmbase to unify the implementations.
Use TRNG from rust_entry() to configure the stack guard (see the comment
about rust_entry() ever returning). As a result, failing to configure
it will now result in a logged error message as, previously,
vmbase-based code would silently reboot, making it impossible to find
out which check had failed (see b/267262026#comment89).
Furthermore, failing to read the entropy for the u64 stack guard due to
NO_ENTROPY will now result in vmbase retrying the HVC where it
previously would abort the VM's boot.
This implementation now only accepts versions of SMCCC between 1.1 and
2.0 (excl.) and TRNG between 1.0 and 2.0 (excl.) instead of resp. 1.1
and above and 1.0 and above.
Bug: 274561905
Test: atest DebugPolicyHostTests#testNoAdbInDebugPolicy_withDebugLevelNone_boots
Test: atest rialto_test vmbase_example.integration_test
Change-Id: I5b95e77732e10ddfbc4476b6d7c698c5dc5f3b6e
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index c81b57c..2d1c418 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -36,7 +36,6 @@
main,
memory::{min_dcache_line_size, MemoryTracker, MEMORY, SIZE_128KB, SIZE_4KB},
power::reboot,
- rand,
};
use zeroize::Zeroize;
@@ -221,11 +220,6 @@
let slices = MemorySlices::new(fdt, payload, payload_size)?;
- rand::init().map_err(|e| {
- error!("Failed to initialize rand: {e}");
- RebootReason::InternalError
- })?;
-
// This wrapper allows main() to be blissfully ignorant of platform details.
let next_bcc = crate::main(slices.fdt, slices.kernel, slices.ramdisk, bcc_slice, debug_policy)?;
diff --git a/vmbase/entry.S b/vmbase/entry.S
index 60930db..9177a4a 100644
--- a/vmbase/entry.S
+++ b/vmbase/entry.S
@@ -63,64 +63,6 @@
.set .Lsctlrval, .L_SCTLR_ELx_M | .L_SCTLR_ELx_C | .L_SCTLR_ELx_SA | .L_SCTLR_EL1_ITD | .L_SCTLR_EL1_SED
.set .Lsctlrval, .Lsctlrval | .L_SCTLR_ELx_I | .L_SCTLR_EL1_SPAN | .L_SCTLR_EL1_RES1 | .L_SCTLR_EL1_WXN
-/* SMC function IDs */
-.set .L_SMCCC_VERSION_ID, 0x80000000
-.set .L_SMCCC_TRNG_VERSION_ID, 0x84000050
-.set .L_SMCCC_TRNG_FEATURES_ID, 0x84000051
-.set .L_SMCCC_TRNG_RND64_ID, 0xc4000053
-
-/* SMC function versions */
-.set .L_SMCCC_VERSION_1_1, 0x0101
-.set .L_SMCCC_TRNG_VERSION_1_0, 0x0100
-
-/**
- * This macro stores a random value into a register.
- * If a TRNG backed is not present or if an error occurs, the value remains unchanged.
- */
-.macro rnd_reg reg:req
- mov x20, x0
- mov x21, x1
- mov x22, x2
- mov x23, x3
-
- /* Verify SMCCC version >=1.1 */
- hvc_call .L_SMCCC_VERSION_ID
- cmp w0, 0
- b.lt 100f
- cmp w0, .L_SMCCC_VERSION_1_1
- b.lt 100f
-
- /* Verify TRNG ABI version 1.x */
- hvc_call .L_SMCCC_TRNG_VERSION_ID
- cmp w0, 0
- b.lt 100f
- cmp w0, .L_SMCCC_TRNG_VERSION_1_0
- b.lt 100f
-
- /* Call TRNG_FEATURES, ensure TRNG_RND is implemented */
- mov_i x1, .L_SMCCC_TRNG_RND64_ID
- hvc_call .L_SMCCC_TRNG_FEATURES_ID
- cmp w0, 0
- b.lt 100f
-
- /* Call TRNG_RND, request 64 bits of entropy */
- mov x1, #64
- hvc_call .L_SMCCC_TRNG_RND64_ID
- cmp x0, 0
- b.lt 100f
-
- mov \reg, x3
- b 101f
-
-100:
- reset_or_hang
-101:
- mov x0, x20
- mov x1, x21
- mov x2, x22
- mov x3, x23
-.endm
-
/**
* This is a generic entry point for an image. It carries out the operations required to prepare the
* loaded image to be run. Specifically, it zeroes the bss section using registers x25 and above,
@@ -225,14 +167,6 @@
adr_l x30, __bionic_tls
msr tpidr_el0, x30
- /* Randomize stack protector. */
- rnd_reg x29
- adr_l x30, __stack_chk_guard
- str x29, [x30]
-
- /* Write a null byte to the top of the stack guard to act as a string terminator. */
- strb wzr, [x30]
-
/* Call into Rust code. */
bl rust_entry
diff --git a/vmbase/example/src/main.rs b/vmbase/example/src/main.rs
index 8aa9f04..8086885 100644
--- a/vmbase/example/src/main.rs
+++ b/vmbase/example/src/main.rs
@@ -109,6 +109,8 @@
// SAFETY: No concurrency issue should occur when running these tests.
let stack_guard = unsafe { bionic::TLS.stack_guard };
assert_ne!(stack_guard, 0);
+ // Check that a NULL-terminating value is added for C functions consuming strings from stack.
+ assert_eq!(stack_guard.to_ne_bytes().last(), Some(&0));
// Check that the TLS and guard are properly accessible from the dedicated register.
assert_eq!(stack_guard, bionic::__get_tls().stack_guard);
// Check that the LLVM __stack_chk_guard alias is also properly set up.
diff --git a/vmbase/src/entry.rs b/vmbase/src/entry.rs
index 3e826a2..24b5035 100644
--- a/vmbase/src/entry.rs
+++ b/vmbase/src/entry.rs
@@ -15,9 +15,11 @@
//! Rust entry point.
use crate::{
- console, heap, logger,
+ bionic, console, heap, logger,
power::{reboot, shutdown},
+ rand,
};
+use core::mem::size_of;
use hyp::{self, get_mmio_guard};
fn try_console_init() -> Result<(), hyp::Error> {
@@ -45,6 +47,18 @@
logger::init().expect("Failed to initialize the logger");
// We initialize the logger to Off (like the log crate) and clients should log::set_max_level.
+ const SIZE_OF_STACK_GUARD: usize = size_of::<u64>();
+ let mut stack_guard = [0u8; SIZE_OF_STACK_GUARD];
+ // We keep a null byte at the top of the stack guard to act as a string terminator.
+ let random_guard = &mut stack_guard[..(SIZE_OF_STACK_GUARD - 1)];
+
+ rand::init().expect("Failed to initialize a source of entropy");
+ rand::fill_with_entropy(random_guard).expect("Failed to get stack canary entropy");
+ bionic::__get_tls().stack_guard = u64::from_ne_bytes(stack_guard);
+
+ // Note: If rust_entry ever returned (which it shouldn't by being -> !), the compiler-injected
+ // stack guard comparison would detect a mismatch and call __stack_chk_fail.
+
// SAFETY: `main` is provided by the application using the `main!` macro, and we make sure it
// has the right type.
unsafe {
diff --git a/vmbase/src/hvc.rs b/vmbase/src/hvc.rs
index 9a5e716..4d489d5 100644
--- a/vmbase/src/hvc.rs
+++ b/vmbase/src/hvc.rs
@@ -22,13 +22,12 @@
};
const ARM_SMCCC_TRNG_VERSION: u32 = 0x8400_0050;
-#[allow(dead_code)]
const ARM_SMCCC_TRNG_FEATURES: u32 = 0x8400_0051;
#[allow(dead_code)]
const ARM_SMCCC_TRNG_GET_UUID: u32 = 0x8400_0052;
#[allow(dead_code)]
const ARM_SMCCC_TRNG_RND32: u32 = 0x8400_0053;
-const ARM_SMCCC_TRNG_RND64: u32 = 0xc400_0053;
+pub const ARM_SMCCC_TRNG_RND64: u32 = 0xc400_0053;
/// Returns the (major, minor) version tuple, as defined by the SMCCC TRNG.
pub fn trng_version() -> trng::Result<(u16, u16)> {
@@ -49,3 +48,10 @@
Ok((regs[1], regs[2], regs[3]))
}
+
+pub fn trng_features(fid: u32) -> trng::Result<u64> {
+ let mut args = [0u64; 17];
+ args[0] = fid as u64;
+
+ positive_or_error_64::<Error>(hvc64(ARM_SMCCC_TRNG_FEATURES, args)[0])
+}
diff --git a/vmbase/src/hvc/trng.rs b/vmbase/src/hvc/trng.rs
index 6331d66..1a27d64 100644
--- a/vmbase/src/hvc/trng.rs
+++ b/vmbase/src/hvc/trng.rs
@@ -16,7 +16,7 @@
use core::result;
/// Standard SMCCC TRNG error values as described in DEN 0098 1.0 REL0.
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq)]
pub enum Error {
/// The call is not supported by the implementation.
NotSupported,
diff --git a/vmbase/src/rand.rs b/vmbase/src/rand.rs
index 26fb51a..a5a5f5f 100644
--- a/vmbase/src/rand.rs
+++ b/vmbase/src/rand.rs
@@ -17,15 +17,28 @@
use crate::hvc;
use core::fmt;
use core::mem::size_of;
+use smccc::{self, Hvc};
/// Error type for rand operations.
pub enum Error {
+ /// No source of entropy found.
+ NoEntropySource,
+ /// Error during architectural SMCCC call.
+ Smccc(smccc::arch::Error),
/// Error during SMCCC TRNG call.
Trng(hvc::trng::Error),
+ /// Unsupported SMCCC version.
+ UnsupportedSmcccVersion(smccc::arch::Version),
/// Unsupported SMCCC TRNG version.
UnsupportedVersion((u16, u16)),
}
+impl From<smccc::arch::Error> for Error {
+ fn from(e: smccc::arch::Error) -> Self {
+ Self::Smccc(e)
+ }
+}
+
impl From<hvc::trng::Error> for Error {
fn from(e: hvc::trng::Error) -> Self {
Self::Trng(e)
@@ -38,7 +51,10 @@
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
+ Self::NoEntropySource => write!(f, "No source of entropy available"),
+ Self::Smccc(e) => write!(f, "Architectural SMCCC error: {e}"),
Self::Trng(e) => write!(f, "SMCCC TRNG error: {e}"),
+ Self::UnsupportedSmcccVersion(v) => write!(f, "Unsupported SMCCC version {v}"),
Self::UnsupportedVersion((x, y)) => {
write!(f, "Unsupported SMCCC TRNG version v{x}.{y}")
}
@@ -53,14 +69,34 @@
}
/// Configure the source of entropy.
-pub fn init() -> Result<()> {
- match hvc::trng_version()? {
- (1, _) => Ok(()),
- version => Err(Error::UnsupportedVersion(version)),
+pub(crate) fn init() -> Result<()> {
+ // SMCCC TRNG requires SMCCC v1.1.
+ match smccc::arch::version::<Hvc>()? {
+ smccc::arch::Version { major: 1, minor } if minor >= 1 => (),
+ version => return Err(Error::UnsupportedSmcccVersion(version)),
}
+
+ // TRNG_RND requires SMCCC TRNG v1.0.
+ match hvc::trng_version()? {
+ (1, _) => (),
+ version => return Err(Error::UnsupportedVersion(version)),
+ }
+
+ // TRNG_RND64 doesn't define any special capabilities so ignore the successful result.
+ let _ = hvc::trng_features(hvc::ARM_SMCCC_TRNG_RND64).map_err(|e| {
+ if e == hvc::trng::Error::NotSupported {
+ // SMCCC TRNG is currently our only source of entropy.
+ Error::NoEntropySource
+ } else {
+ e.into()
+ }
+ })?;
+
+ Ok(())
}
-fn fill_with_entropy(s: &mut [u8]) -> Result<()> {
+/// Fills a slice of bytes with true entropy.
+pub fn fill_with_entropy(s: &mut [u8]) -> Result<()> {
const MAX_BYTES_PER_CALL: usize = size_of::<hvc::TrngRng64Entropy>();
let (aligned, remainder) = s.split_at_mut(s.len() - s.len() % MAX_BYTES_PER_CALL);