From 2bbfd8c976fed21e24865618b0a9975d9ab542c4 Mon Sep 17 00:00:00 2001 From: Hugues de Valon Date: Tue, 14 Jan 2020 11:38:34 +0000 Subject: [PATCH 1/2] Initial Rust CMSE support Armv8-M and Armv8.1-M architecture profiles have an optional Security Extension which provides a set of Security features. This patch adds initial support of the Cortex-M Security Extensions but providing support for the TT intrinsics and helper functions on top of it in the newly added cmse module of this crate. The code is a Rust idiomatic implementation of the C requirements described in this document: https://developer.arm.com/docs/ecm0359818/latest Signed-off-by: Hugues de Valon --- Cargo.toml | 1 + asm-v8.s | 27 ++++ assemble.sh | 9 +- bin/thumbv8m.base-none-eabi.a | Bin 2960 -> 4056 bytes bin/thumbv8m.main-none-eabi.a | Bin 5370 -> 6466 bytes bin/thumbv8m.main-none-eabihf.a | Bin 5370 -> 6466 bytes src/asm.rs | 141 +++++++++++++++++++ src/cmse.rs | 240 ++++++++++++++++++++++++++++++++ src/lib.rs | 2 + 9 files changed, 417 insertions(+), 3 deletions(-) create mode 100644 asm-v8.s create mode 100644 src/cmse.rs diff --git a/Cargo.toml b/Cargo.toml index 5137dce5..64b1162a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ links = "cortex-m" # prevent multiple versions of this crate to be linked toget [dependencies] bare-metal = { version = "0.2.0", features = ["const-fn"] } volatile-register = "0.2.0" +bitfield = "0.13.2" [features] cm7-r0p1 = [] diff --git a/asm-v8.s b/asm-v8.s new file mode 100644 index 00000000..b667bf0f --- /dev/null +++ b/asm-v8.s @@ -0,0 +1,27 @@ + .section .text.__tt + .global __tt + .thumb_func +__tt: + tt r0, r0 + bx lr + + .section .text.__ttt + .global __ttt + .thumb_func +__ttt: + ttt r0, r0 + bx lr + + .section .text.__tta + .global __tta + .thumb_func +__tta: + tta r0, r0 + bx lr + + .section .text.__ttat + .global __ttat + .thumb_func +__ttat: + ttat r0, r0 + bx lr diff --git a/assemble.sh b/assemble.sh index 497925f5..f63e8379 100755 --- a/assemble.sh +++ b/assemble.sh @@ -23,15 +23,18 @@ ar crs bin/thumbv7em-none-eabi.a bin/$crate.o bin/$crate-v7.o bin/$crate-cm7-r0p ar crs bin/thumbv7em-none-eabihf.a bin/$crate.o bin/$crate-v7.o bin/$crate-cm7-r0p1.o arm-none-eabi-as -march=armv8-m.base asm.s -o bin/$crate.o -ar crs bin/thumbv8m.base-none-eabi.a bin/$crate.o +arm-none-eabi-as -march=armv8-m.base asm-v8.s -o bin/$crate-v8.o +ar crs bin/thumbv8m.base-none-eabi.a bin/$crate.o bin/$crate-v8.o arm-none-eabi-as -march=armv8-m.main asm.s -o bin/$crate.o arm-none-eabi-as -march=armv8-m.main asm-v7.s -o bin/$crate-v7.o +arm-none-eabi-as -march=armv8-m.main asm-v8.s -o bin/$crate-v8.o arm-none-eabi-as -march=armv8-m.main asm-v8-main.s -o bin/$crate-v8-main.o -ar crs bin/thumbv8m.main-none-eabi.a bin/$crate.o bin/$crate-v7.o bin/$crate-v8-main.o -ar crs bin/thumbv8m.main-none-eabihf.a bin/$crate.o bin/$crate-v7.o bin/$crate-v8-main.o +ar crs bin/thumbv8m.main-none-eabi.a bin/$crate.o bin/$crate-v7.o bin/$crate-v8.o bin/$crate-v8-main.o +ar crs bin/thumbv8m.main-none-eabihf.a bin/$crate.o bin/$crate-v7.o bin/$crate-v8.o bin/$crate-v8-main.o rm bin/$crate.o rm bin/$crate-v7.o rm bin/$crate-cm7-r0p1.o +rm bin/$crate-v8.o rm bin/$crate-v8-main.o diff --git a/bin/thumbv8m.base-none-eabi.a b/bin/thumbv8m.base-none-eabi.a index 78ae15cf805934648803c193d7070986c5043308..44ee9afc94b2d4e2e4102bd77846c4f684631455 100644 GIT binary patch delta 643 zcmbOrenWnOM7^P@g^`7YxtW2vf`Wkp2q2RR3Pz@8U{M8y1TF>!1~CQ(Mi=T}?i~ya z+?*f+j3-X$c8`xQDPaH+5HbeFg?0u7GQ1o^(ZJ1mVBj;Eky~9i2gnux8UacUyg&@{y8sY_{0vkL2Q@%7GLu8O Ql_h2XSs-070J0wh06#HoQ2+n{ delta 200 zcmca1KS6wgM7@Cm0zlZvq=JHxp&3*`0v7`VgAfA)qYO20e0)-NK?%de!1~~=>CL!uzo;C&s z9!(Gd#R9h&7?82ZF9rq#p15JZNJ$Ape0&L%Oa!wNC+7(1PoBk?AcyP|Ljwb#H{hh1 znaO4erZ6r-YI=CoSSH8uaA{fSVFI^dO;wU?(DpCzeb;$aj;`eDZ95bw=mO+#>3X{*zw= h*)fx&MUok1Chr$fW}N|aR?g)40?LGf>Vbe13jli+bZY;l diff --git a/bin/thumbv8m.main-none-eabihf.a b/bin/thumbv8m.main-none-eabihf.a index e7512328a97083106e6d2fc0ae1a680896cb1ab6..6590c92e41968b82aea81d515c385cc31b0578b9 100644 GIT binary patch delta 579 zcmeyRdB|vjM7^P@g^`7YxtW2vf`Wkp2q2RR3dR;jU{M8y1TF>!1~~=>CL!uzo;C&s z9!(Gd#R9h&7?82ZF9rq#p15JZNJ$Ape0&L%Oa!wNC+7(1PoBk?AcyP|Ljwb#H{hh1 znaO4erZ6r-YI=CoSSH8uaA{fSVFI^dO;wU?(DpCzeb;$aj;`eDZ95bw=mO+#>3X{*zw= h*)fx&MUok1Chr$fW}N|aR?g)40?LGf>Vbe13jli+bZY;l diff --git a/src/asm.rs b/src/asm.rs index 5a35fa32..7b1a9ce7 100644 --- a/src/asm.rs +++ b/src/asm.rs @@ -222,3 +222,144 @@ pub fn dmb() { () => unimplemented!(), } } + +/// Test Target +/// +/// Queries the Security state and access permissions of a memory location. +/// Returns a Test Target Response Payload (cf section D1.2.215 of +/// Armv8-M Architecture Reference Manual). +#[inline] +#[cfg(armv8m)] +// The __tt function does not dereference the pointer received. +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn tt(addr: *mut u32) -> u32 { + match () { + #[cfg(all(cortex_m, feature = "inline-asm"))] + () => { + let tt_resp: u32; + unsafe { + asm!("tt $0, $1" : "=r"(tt_resp) : "r"(addr) :: "volatile"); + } + tt_resp + } + + #[cfg(all(cortex_m, not(feature = "inline-asm")))] + () => unsafe { + extern "C" { + fn __tt(_: *mut u32) -> u32; + } + + __tt(addr) + }, + + #[cfg(not(cortex_m))] + () => unimplemented!(), + } +} + +/// Test Target Unprivileged +/// +/// Queries the Security state and access permissions of a memory location for an unprivileged +/// access to that location. +/// Returns a Test Target Response Payload (cf section D1.2.215 of +/// Armv8-M Architecture Reference Manual). +#[inline] +#[cfg(armv8m)] +// The __ttt function does not dereference the pointer received. +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn ttt(addr: *mut u32) -> u32 { + match () { + #[cfg(all(cortex_m, feature = "inline-asm"))] + () => { + let tt_resp: u32; + unsafe { + asm!("ttt $0, $1" : "=r"(tt_resp) : "r"(addr) :: "volatile"); + } + tt_resp + } + + #[cfg(all(cortex_m, not(feature = "inline-asm")))] + () => unsafe { + extern "C" { + fn __ttt(_: *mut u32) -> u32; + } + + __ttt(addr) + }, + + #[cfg(not(cortex_m))] + () => unimplemented!(), + } +} + +/// Test Target Alternate Domain +/// +/// Queries the Security state and access permissions of a memory location for a Non-Secure access +/// to that location. This instruction is only valid when executing in Secure state and is +/// undefined if used from Non-Secure state. +/// Returns a Test Target Response Payload (cf section D1.2.215 of +/// Armv8-M Architecture Reference Manual). +#[inline] +#[cfg(armv8m)] +// The __tta function does not dereference the pointer received. +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn tta(addr: *mut u32) -> u32 { + match () { + #[cfg(all(cortex_m, feature = "inline-asm"))] + () => { + let tt_resp: u32; + unsafe { + asm!("tta $0, $1" : "=r"(tt_resp) : "r"(addr) :: "volatile"); + } + tt_resp + } + + #[cfg(all(cortex_m, not(feature = "inline-asm")))] + () => unsafe { + extern "C" { + fn __tta(_: *mut u32) -> u32; + } + + __tta(addr) + }, + + #[cfg(not(cortex_m))] + () => unimplemented!(), + } +} + +/// Test Target Alternate Domain Unprivileged +/// +/// Queries the Security state and access permissions of a memory location for a Non-Secure and +/// unprivileged access to that location. This instruction is only valid when executing in Secure +/// state and is undefined if used from Non-Secure state. +/// Returns a Test Target Response Payload (cf section D1.2.215 of +/// Armv8-M Architecture Reference Manual). +#[inline] +#[cfg(armv8m)] +// The __ttat function does not dereference the pointer received. +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub fn ttat(addr: *mut u32) -> u32 { + match () { + #[cfg(all(cortex_m, feature = "inline-asm"))] + () => { + let tt_resp: u32; + unsafe { + asm!("ttat $0, $1" : "=r"(tt_resp) : "r"(addr) :: "volatile"); + } + tt_resp + } + + #[cfg(all(cortex_m, not(feature = "inline-asm")))] + () => unsafe { + extern "C" { + fn __ttat(_: *mut u32) -> u32; + } + + __ttat(addr) + }, + + #[cfg(not(cortex_m))] + () => unimplemented!(), + } +} diff --git a/src/cmse.rs b/src/cmse.rs new file mode 100644 index 00000000..393e4638 --- /dev/null +++ b/src/cmse.rs @@ -0,0 +1,240 @@ +//! Cortex-M Security Extensions +//! +//! This module provides several helper functions to support Armv8-M and Armv8.1-M Security +//! Extensions. +//! Most of this implementation is directly inspired by the "Armv8-M Security Extensions: +//! Requirements on Development Tools" document available here: +//! https://developer.arm.com/docs/ecm0359818/latest +//! +//! Please note that the TT instructions support as described part 4 of the document linked above is +//! not part of CMSE but is still present in this module. The TT instructions return the +//! configuration of the Memory Protection Unit at an address. +//! +//! # Notes +//! +//! * Non-Secure Unprivileged code will always read zeroes from TestTarget and should not use it. +//! * Non-Secure Privileged code can check current (AccessType::Current) and Non-Secure Unprivileged +//! accesses (AccessType::Unprivileged). +//! * Secure Unprivileged code can check Non-Secure Unprivileged accesses (AccessType::NonSecure). +//! * Secure Privileged code can check all access types. +//! +//! # Example +//! +//! ``` +//! use cortex_m::cmse::{TestTarget, AccessType}; +//! +//! // suspect_address was given by Non-Secure to a Secure function to write at it. +//! // But is it allowed to? +//! let suspect_address_test = TestTarget::check(0xDEADBEEF as *mut u32, +//! AccessType::NonSecureUnprivileged); +//! if suspect_address_test.ns_read_and_writable() { +//! // Non-Secure can not read or write this address! +//! } +//! ``` + +use crate::asm::{tt, tta, ttat, ttt}; +use bitfield::bitfield; + +/// Memory access behaviour: determine which privilege execution mode is used and which Memory +/// Protection Unit (MPU) is used. +#[allow(clippy::missing_inline_in_public_items)] +#[derive(PartialEq, Copy, Clone, Debug)] +pub enum AccessType { + /// Access using current privilege level and reading from current security state MPU. + /// Uses the TT instruction. + Current, + /// Unprivileged access reading from current security state MPU. Uses the TTT instruction. + Unprivileged, + /// Access using current privilege level reading from Non-Secure MPU. Uses the TTA instruction. + /// Undefined if used from Non-Secure state. + NonSecure, + /// Unprivilege access reading from Non-Secure MPU. Uses the TTAT instruction. + /// Undefined if used from Non-Secure state. + NonSecureUnprivileged, +} + +/// Abstraction of TT instructions and helper functions to determine the security and privilege +/// attribute of a target address, accessed in different ways. +#[allow(clippy::missing_inline_in_public_items)] +#[derive(PartialEq, Copy, Clone, Debug)] +pub struct TestTarget { + tt_resp: TtResp, + access_type: AccessType, +} + +bitfield! { + /// Test Target Response Payload + /// + /// Provides the response payload from a TT, TTA, TTT or TTAT instruction. + #[derive(PartialEq, Copy, Clone)] + struct TtResp(u32); + impl Debug; + mregion, _: 7, 0; + sregion, _: 15, 8; + mrvalid, _: 16; + srvalid, _: 17; + r, _: 18; + rw, _: 19; + nsr, _: 20; + nsrw, _: 21; + s, _: 22; + irvalid, _: 23; + iregion, _: 31, 24; +} + +impl TestTarget { + /// Creates a Test Target Response Payload by testing addr using access_type. + #[inline] + pub fn check(addr: *mut u32, access_type: AccessType) -> Self { + let tt_resp = match access_type { + AccessType::Current => TtResp(tt(addr)), + AccessType::Unprivileged => TtResp(ttt(addr)), + AccessType::NonSecure => TtResp(tta(addr)), + AccessType::NonSecureUnprivileged => TtResp(ttat(addr)), + }; + + TestTarget { + tt_resp, + access_type, + } + } + + /// Creates a Test Target Response Payload by testing the zone from addr to addr + size - 1 + /// using access_type. + /// Returns None if: + /// * the address zone overlaps SAU, IDAU or MPU region boundaries + /// * size is 0 + /// * addr + size - 1 overflows + #[inline] + pub fn check_range(addr: *mut u32, size: usize, access_type: AccessType) -> Option { + let begin: usize = addr as usize; + // Last address of the range (addr + size - 1). This also checks if size is 0. + let end: usize = begin.checked_add(size.checked_sub(1)?)?; + + // Regions are aligned at 32-byte boundaries. If the address range fits in one 32-byte + // address line, a single TT instruction suffices. This is the case when the following + // constraint holds. + let single_check: bool = (begin % 32).checked_add(size)? <= 32usize; + + let test_start = TestTarget::check(addr, access_type); + + if single_check { + Some(test_start) + } else { + let test_end = TestTarget::check(end as *mut u32, access_type); + // Check that the range does not cross SAU, IDAU or MPU region boundaries. + if test_start != test_end { + None + } else { + Some(test_start) + } + } + } + + /// Access type that was used for this test target. + #[inline] + pub fn access_type(self) -> AccessType { + self.access_type + } + + /// Get the raw u32 value returned by the TT instruction used. + #[inline] + pub fn as_u32(self) -> u32 { + self.tt_resp.0 + } + + /// Read accessibility of the target address. Only returns the MPU settings without checking + /// the Security state of the target. + /// For Unprivileged and NonSecureUnprivileged access types, returns the permissions for + /// unprivileged access, regardless of whether the current mode is privileged or unprivileged. + /// Returns false if the TT instruction was executed from an unprivileged mode + /// and the NonSecure access type was not specified. + /// Returns false if the address matches multiple MPU regions. + #[inline] + pub fn readable(self) -> bool { + self.tt_resp.r() + } + + /// Read and write accessibility of the target address. Only returns the MPU settings without + /// checking the Security state of the target. + /// For Unprivileged and NonSecureUnprivileged access types, returns the permissions for + /// unprivileged access, regardless of whether the current mode is privileged or unprivileged. + /// Returns false if the TT instruction was executed from an unprivileged mode + /// and the NonSecure access type was not specified. + /// Returns false if the address matches multiple MPU regions. + #[inline] + pub fn read_and_writable(self) -> bool { + self.tt_resp.rw() + } + + /// Indicate the MPU region number containing the target address. + /// Returns None if the value is not valid: + /// * the MPU is not implemented or MPU_CTRL.ENABLE is set to zero + /// * the register argument specified by the MREGION field does not match any enabled MPU regions + /// * the address matched multiple MPU regions + /// * the address specified by the SREGION field is exempt from the secure memory attribution + /// * the TT instruction was executed from an unprivileged mode and the A flag was not specified. + #[inline] + pub fn mpu_region(self) -> Option { + if self.tt_resp.srvalid() { + // Cast is safe as SREGION field is defined on 8 bits. + Some(self.tt_resp.sregion() as u8) + } else { + None + } + } + + /// Indicates the Security attribute of the target address. Independent of AccessType. + /// Always zero when the test target is done in the Non-Secure state. + #[inline] + pub fn secure(self) -> bool { + self.tt_resp.s() + } + + /// Non-Secure Read accessibility of the target address. + /// Same as readable() && !secure() + #[inline] + pub fn ns_readable(self) -> bool { + self.tt_resp.nsr() + } + + /// Non-Secure Read and Write accessibility of the target address. + /// Same as read_and_writable() && !secure() + #[inline] + pub fn ns_read_and_writable(self) -> bool { + self.tt_resp.nsrw() + } + + /// Indicate the IDAU region number containing the target address. Independent of AccessType. + /// Returns None if the value is not valid: + /// * the IDAU cannot provide a region number + /// * the address is exempt from security attribution + /// * the test target is done from Non-Secure state + #[inline] + pub fn idau_region(self) -> Option { + if self.tt_resp.irvalid() { + // Cast is safe as IREGION field is defined on 8 bits. + Some(self.tt_resp.iregion() as u8) + } else { + None + } + } + + /// Indicate the SAU region number containing the target address. Independent of AccessType. + /// Returns None if the value is not valid: + /// * SAU_CTRL.ENABLE is set to zero + /// * the register argument specified in the SREGION field does not match any enabled SAU regions + /// * the address specified matches multiple enabled SAU regions + /// * the address specified by the SREGION field is exempt from the secure memory attribution + /// * the TT instruction was executed from the Non-secure state or the Security Extension is not + /// implemented + #[inline] + pub fn sau_region(self) -> Option { + if self.tt_resp.srvalid() { + // Cast is safe as SREGION field is defined on 8 bits. + Some(self.tt_resp.sregion() as u8) + } else { + None + } + } +} diff --git a/src/lib.rs b/src/lib.rs index f8b56060..beaecd22 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,6 +57,8 @@ extern crate volatile_register; mod macros; pub mod asm; +#[cfg(armv8m)] +pub mod cmse; pub mod interrupt; #[cfg(not(armv6m))] pub mod itm; From 2433d85190dad9e4cbaa4086eba796ab59bf0adb Mon Sep 17 00:00:00 2001 From: Hugues de Valon Date: Sat, 14 Mar 2020 10:57:52 +0000 Subject: [PATCH 2/2] Allow clippy::match_single_binding Clippy complains that the match expressions used for cfg gating could be rewritten as a let statement, this is a false positive. Also adds inline on two functions. Signed-off-by: Hugues de Valon --- src/lib.rs | 2 ++ src/peripheral/scb.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index beaecd22..276551c3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,6 +34,8 @@ #![no_std] #![allow(clippy::identity_op)] #![allow(clippy::missing_safety_doc)] +// Prevent clippy from complaining about empty match expression that are used for cfg gating. +#![allow(clippy::match_single_binding)] // This makes clippy warn about public functions which are not #[inline]. // diff --git a/src/peripheral/scb.rs b/src/peripheral/scb.rs index dc82dc76..dfcc729a 100644 --- a/src/peripheral/scb.rs +++ b/src/peripheral/scb.rs @@ -713,6 +713,7 @@ impl SCB { /// /// Cleaning the cache causes whatever data is present in the cache to be immediately written /// to main memory, overwriting whatever was in main memory. + #[inline] pub fn clean_dcache_by_ref(&mut self, obj: &T) { self.clean_dcache_by_address(obj as *const T as usize, core::mem::size_of::()); } @@ -729,6 +730,7 @@ impl SCB { /// /// Cleaning the cache causes whatever data is present in the cache to be immediately written /// to main memory, overwriting whatever was in main memory. + #[inline] pub fn clean_dcache_by_slice(&mut self, slice: &[T]) { self.clean_dcache_by_address(slice.as_ptr() as usize, slice.len() * core::mem::size_of::());