carmtrustzone

ARM TrustZone: Accessing a non-secure buffer from a secure monitor runtime service


My setup consists of a STM32MP157C-DK2 which uses Trusted Firmware-A to load SP-MIN as BL32 and uBoot+Linux as BL33.

I am trying to get a small example working where I create an SMC from the Linux Kernel which passes a reference to non-secure memory. The data at that location should be altered by the runtime service handling the SMC.

The problem I'm facing is that I can't find any information on what steps are required in order to translate the virtual address from the Linux Kernel at NS:EL1 to the translation regime of EL3.

The code of my runtime service looks like this:

static int32_t my_svc_setup(void)
{
    return 0;
}    

static uintptr_t my_svc_smc_handler(uint32_t smc_fid,
  u_register_t x1,
  u_register_t x2,
  u_register_t x3,
  u_register_t x4,
  void *cookie,
  void *handle,
  u_register_t flags)
{
    uint16_t smc_function_number = (uint16_t) smc_fid;
    uint32_t *data;

    switch(smc_function_number){
    case 123:
        data = (uint32_t *) x1;
        // Address Translation Magic ...
        *data = 42;
        SMC_RET1(handle, 1);
    default:
        SMC_RET1(handle, SMC_UNK);
    }
}

DECLARE_RT_SVC(
    my_svc,
    OEN_OEM_START,
    OEN_OEM_END,
    SMC_TYPE_FAST,
    my_svc_setup,
    my_svc_smc_handler
);

The SMC reaches the handler without issues, but as soon as I try to dereference the physical address I passed through x1 the CPU (obviously) crashes. If anyone could help me fill in the remaining required steps in order to get a valid reference, that would be greatly appreciated.


Solution

  • The problem I'm facing is that I can't find any information on what steps are required in order to translate the virtual address from the Linux Kernel at NS:EL1 to the translation regime of EL3.

    The TrustZone protection is based on a physical address. For either NS:EL1 or EL3, you can map using an MMU in various ways, but both must map to the same physical address. For Linux kernel, you need to add a mapping of the shared memory that is backed by the physical address. You can use virt_to_phys() with such a mapping to find the physical address.

    You need to have the same mapping available in the EL3. The simplest is to have a flat virt==phys mapping with sections and super-sections.

    Another portion is that you MUST setup the TZASC to have permissions of the physical portion as world shareable. An example of code manipulating TZASC. This depends on your hardware, often this information is only given under NDA with chip manufacturer.

    The other caveat is that you SHOULD map the memory as non-cacheable or you rely on flushes, which is error prone and could be a security issue, if the system has a VIVT cache. Some ARM CPUs have a VIPT cache and it maybe possible to use cached memory on those systems.

    I would also recommend you do not pass addresses via the SMC API. You know the fixed world shareable buffer size. So, it is better to pass an index that is 0..extent-1 and immediately give an error if the address is outside the range. In this way only your initial Linux code needs to create the mapping and then you can use the virtual address given and only pass the index. Naively this seems more secure. Most attacks against TrustZone will be on the API itself.

    Related: DMA and TrustZone, Accessing TZASC