1

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.

TNTea
  • 53
  • 5
  • 1
    My answer is giving more 'questions'. Are you sure you have configured the physical memory to be world writable? Have you mapped the address in Linux; typically done by a driver. What is your `TZASC` controller and configuration? Does EL3 of Trusted Firmware use an MMU at all and if so, is it a `phys==virt` mapping? How have you communicated this world shareable memory to Linux? You can expect someone to answer all of these questions, but it show a little more research if you can provide the answers. – artless noise Dec 01 '21 at 15:08
  • Thank you for asking these questions. I can't answer all of them right now, but the keywords alone will help me to continue my research. I'm very new to this entire topic and I still don't know a few things that seem to be taken for granted. – TNTea Dec 01 '21 at 15:33

1 Answers1

1

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

artless noise
  • 21,212
  • 6
  • 68
  • 105
  • Thank you for the quick response! Just to make sure that I understand you correctly: My current way of doing things (passing a physical address from Linux via the SMC) would work, if the TZASC was setup correctly for that portion of memory and the MMU had a direct mapping from virt to phys? But a better way of doing it would be to create a known static area in physical memory that is setup as world shareable and immediately allocated by Linux? – TNTea Dec 01 '21 at 15:30
  • Why do you need to use non-cacheable? TrustZone-aware caches include the security state in the cache tag don't they? – solidpixel Dec 01 '21 at 19:13
  • @solidpixel Sometimes, the cache entry applies only the the normal world. Ie, when the secure world accesses, it is either the secure world cache or the physical device and not the normal world cache value. The cache is obviously tagged with NS or the normal world could virtually alias a secure world and try to inspect values. It is best to make it non-cached in my opinion. – artless noise Dec 01 '21 at 21:37
  • 1
    @TNTea You are correct. The problem about allowing normal world access it the TZASC is that it could then just map the entire secure world and overwrite it. So normally, the secure boot will setup a fixed physical buffer and it will be 'boot locked'. Ie, even if someone gains access to the TZASC registers, it can not be changes except for a full system reset. To allow dynamic TZASC is a rabbit hole. In theory what you suggest can work, but I would only suggest to do this for development and not for a real deployment. This is probably a security risk in a real device. – artless noise Dec 01 '21 at 21:48
  • The secure world can directly access non-secure cache lines - the access type (S or NS) is part of the secure world page table entry for the memory (the NS bit in the page table is obviously ignored for normal page tables and forced to non-secure). – solidpixel Dec 02 '21 at 08:41
  • @solidpixel I was under the impression that some ARM CPUs were VIVT; I did some more research and I can not verify this recollection. Indeed, the secure world can access normal world memory, but the caching could be an issue. If the cache is VIPT then your method may work. I think that the caches changed with armv8 CPUs (which support ARM64), so it maybe correct for the OP, that the cache is not an issue. I have updated the answer. – artless noise Dec 02 '21 at 11:12
  • Old Arm designs (Arm9) were VIVT. IIRC Armv7-A onwards require the caches to behave as if physically tagged, and I'm pretty sure Arm1176 with TZ was also PT. – solidpixel Dec 02 '21 at 12:22
  • https://developer.arm.com/documentation/den0013/d/Caches/Cache-architecture/Virtual-and-physical-tags-and-indexes – solidpixel Dec 02 '21 at 12:24