1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
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<Self> {
        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<u8> {
        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<u8> {
        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<u8> {
        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
        }
    }
}