I have an application written for an STM32-L552ZE Cortex M-33 based MCU which makes use of TrustZone-M.
Both worlds have a main.h
, main.c
and both wolds' header files declare a variable foo
(i.e. both the secure main.h
and nonsecure main.h
declare foo
).
To my mind, this should be fine because the way I learned about TrustZone (admittedly, I am coming more from a TrustZone-A, SGX world) makes me believe that the two worlds are independent from each other and only interact through a welldefined interface (secure-/nonsecure function calls through the NSC region).
Unfortunately, when debugging my code, I noticed that the nonsecure world's writes to foo
were ignored. Through checking the address of foo
, which was at 0x3...
, I noticed that it was allocated in the secure world's SRAM. After having uncommented the secure world's foo
declaration and leaving the rest unchanged, the memory address of foo
seen by the nonsecure world changed to 0x2...
, which is in the nonsecure world.
Question:
How is a variable in the secure world able to shadow a nonsecure variable? I am aware that TrustZone-M implements separation through memory addresses, though I would have assumed the linker to be smart enough to differentiate between the two instances of foo
.
Clarification:
To make the situation clearer, following is an excerpt from the code:
secure main.h:
#ifndef __MAIN_H
#define __MAIN_H
/* Includes */
#include "stm32l5xx_hal.h"
#include "secure_nsc.h" // Header file declaring the functions into secure world
/* Private includes*/
/* USER CODE BEGIN Includes */
#include "inttypes.h"
#include "string.h"
/* USER CODE END Includes */
...
typedef struct ringbuf{
volatile int16_t prod_ind;
volatile int16_t cons_ind;
volatile int16_t isr_ctr;
volatile bool_t empty;
byte_t buf[RBUF_LEN];
}ringbuf_t;
ringbuf_t foo;
...
#include "helper.h"
#endif /* __MAIN_H */
nonsecure main.h:
#ifndef __MAIN_H
#define __MAIN_H
/* Includes */
#include "stm32l5xx_hal.h"
#include "secure_nsc.h" /* For export Non-secure callable APIs */
/* Private includes */
/* USER CODE BEGIN Includes */
#include "inttypes.h"
#include "string.h"
/* USER CODE END Includes */
...
typedef struct ringbuf{
volatile int16_t prod_ind;
volatile int16_t cons_ind;
volatile int16_t isr_ctr;
volatile bool_t empty;
byte_t buf[RBUF_LEN];
}ringbuf_t;
ringbuf_t foo;
...
#include "helper.h"
#endif /* __MAIN_H */
Both helper.h, i.e. the secure and nonsecure one, have the following declaration (the definition exists in their respective helper.c files):
bool_t rb_produce_one(ringbuf_t* rb, byte_t ibyte);
This function rb_produce_one
is the one used by the nonsecure world to access its ringbuffer foo
.
Secure main.c
doesn't use its instance of foo
(yet, will use it later).
Nonsecure main.c
accesses foo
through a function which is declared in helper.h
and defined in helper.c
(again, helper.h
, helper.c
exist twice, once in the secure and once in the nonsecure world).