Secure Channel Manager
Table of Contents
Overview
Qualcomm chose to name the channel through which the "Normal World" interacts with the "Secure World" via SMC opcodes - SCM (Secure Channel Manager).
The "Secure World" is also called TrustZone.
According to ARM Ltd., TrustZone is:
"…a system-wide approach to security for a wide array of client and server computing platforms, including handsets, tablets, wearable devices and enterprise systems. Applications enabled by the technology are extremely varied but include payment protection technology, digital rights management, BYOD, and a host of secured enterprise solutions."
More about TrustZone here and here.
The documentation provided by Qualcomm in the relevant source files is quite extensive, and is enough to get quite a good grip on the format of SCM commands.
Putting it shortly, SCM commands fall into one of two categories:1
- Regular SCM Call
- Atomic SCM Call
Regular SCM Call
These calls are used when there is information that needs to be passed from the "Normal World" to the "Secure World", which is needed in order to service the SCM call. The kernel populates the following structure:
/** * struct scm_command - one SCM command buffer * @len: total available memory for command and response * @buf_offset: start of command buffer * @resp_hdr_offset: start of response buffer * @id: command to be executed * @buf: buffer returned from scm_get_command_buffer() * * An SCM command is laid out in memory as follows: * * ------------------- <--- struct scm_command * | command header | * ------------------- <--- scm_get_command_buffer() * | command buffer | * ------------------- <--- struct scm_response and * | response header | scm_command_to_response() * ------------------- <--- scm_get_response_buffer() * | response buffer | * ------------------- * * There can be arbitrary padding between the headers and buffers so * you should always use the appropriate scm_get_*_buffer() routines * to access the buffers in a safe manner. */ struct scm_command { u32 len; u32 buf_offset; u32 resp_hdr_offset; u32 id; u32 buf[0]; };
And the TrustZone kernel, after servicing the SCM call, writes the
response back to the scm_response
structure:
/** * struct scm_response - one SCM response buffer * @len: total available memory for response * @buf_offset: start of response data relative to start of scm_response * @is_complete: indicates if the command has finished processing */ struct scm_response { u32 len; u32 buf_offset; u32 is_complete; };
In order to allocate and fill these structures, the kernel may call
the wrapping function scm_call
, which receives pointers to
kernel-space buffers containing the data to be sent, the location to
which the data should be returned, and most importantly, the service
identifier and command identifier.
Each SCM call has a "category", which means which TrustZone kernel subsystem is responsible for handling that call. This is denoted by the service identifier. The command identifier is the code which specifies, within a given service, which command was requested.
After the scm_call
function allocates and populates the
scm_command
and scm_response
buffers, it calls an internal
__scm_call
function which flushes all the caches (inner and outer
caches), and calls the smc
function.
This last function actually executes the SMC opcode, transferring control to the TrustZone kernel, like so:
static u32 smc(u32 cmd_addr) { int context_id; register u32 r0 asm("r0") = 1; register u32 r1 asm("r1") = (u32)&context_id; register u32 r2 asm("r2") = cmd_addr; do { asm volatile( __asmeq("%0", "r0") __asmeq("%1", "r0") __asmeq("%2", "r1") __asmeq("%3", "r2") #ifdef REQUIRES_SEC ".arch_extension sec\n" #endif "smc #0 @ switch to secure world\n" : "=r" (r0) : "r" (r0), "r" (r1), "r" (r2) : "r3"); } while (r0 == SCM_INTERRUPTED); return r0; }
Note that R0 is set to 1, R1 is set to point to a local kernel stack
address, which is used as a "context ID" for that call, and R2 is set
to point to the physical address of the allocated scm_command
structure.
This "magic" value set in R0 indicates that this is a regular SCM
call, using the scm_command
structure. However, for certain commands
where less data is required, it would be rather wasteful to allocate
all these data structures for no reason. In order to address this
issue, another form of SCM calls was introduced.
Atomic SCM Call
For calls in which the number of arguments is quite low (up to four arguments), there exists an alternate way to request an SCM call.
There are four wrapper functions, scm_call_atomic_[1-4]
, which
correspond to the number of arguments requested. These functions can
be called in order to directly issue an SMC for an SCM call with the
given service and command IDs, and the given arguments.
Here's the code for the scm_call_atomic1
function:
/** * scm_call_atomic1() - Send an atomic SCM command with one argument * @svc_id: service identifier * @cmd_id: command identifier * @arg1: first argument * * This shall only be used with commands that are guaranteed to be * uninterruptable, atomic and SMP safe. */ s32 scm_call_atomic1(u32 svc, u32 cmd, u32 arg1) { int context_id; register u32 r0 asm("r0") = SCM_ATOMIC(svc, cmd, 1); register u32 r1 asm("r1") = (u32)&context_id; register u32 r2 asm("r2") = arg1; asm volatile( __asmeq("%0", "r0") __asmeq("%1", "r0") __asmeq("%2", "r1") __asmeq("%3", "r2") #ifdef REQUIRES_SEC ".arch_extension sec\n" #endif "smc #0 @ switch to secure world\n" : "=r" (r0) : "r" (r0), "r" (r1), "r" (r2) : "r3"); return r0; }
Where SCM_ATOMIC
is defined as:
#define SCM_ATOMIC(svc, cmd, n) (((((svc) << 10)|((cmd) & 0x3ff)) << 12) | \ SCM_CLASS_REGISTER | \ SCM_MASK_IRQS | \ (n & 0xf))
Note that both the service ID and the command ID are encoded into R0, along with the number of arguments in the call (in this case, 1). This is instead of the previous "magic" value of 1 used for regular SCM calls.
This different value in R0 indicates to the TrustZone kernel that the following SCM call is an atomic call, which means that the arguments will be passed in using R2-R5 (and not using a structure pointed to by R2).
Analysing SCM calls
Now that we understand how SCM calls work, and we've found the handling function in the TrustZone kernel which is used to handle these SCM calls, we can begin disassembling the SCM calls to try and find a vulnerability in one of them.
I'll skip over most of the analysis of the SCM handling function, since most of it is boilerplate handling of user input, etc. However, After switching the stack over to the TrustZone area and saving the original registers with which the call was performed, the handling function goes on to process the service ID and the command ID in order to see which internal handling function should be called.
In order to easily map between the service and command IDs and the relevant handling function, a static list is compiled into the TrustZone image's data segment, and is referenced by the SCM handling function. Here is a short snipped from the list:
As you can see, the list has the following structure:
- Pointer to the string containing the name of the SCM function
- "Type" of call
- Pointer to the handling function
- Number of arguments
- Size of each argument (one DWORD for each argument)
- The Service ID and Command ID, concatenated into a single DWORD -
For example, the
tz_blow_sw_fuse
function above, has the type 0x2002 which means it belongs to the service ID 0x20 and its command ID is 0x02.