None
An information disclosure vulnerability exists in the Security Monitor SMSyscallPeripheralAcquire functionality of Microsoft Azure Sphere 21.01. A specially crafted syscall can lead to leaking heap data. An attacker can use an SMC syscall to trigger this vulnerability.
The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.
Microsoft Azure Sphere 21.01
Azure Sphere - https://azure.microsoft.com/en-us/services/azure-sphere/
4.4 - CVSS:3.0/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N
CWE-457 - Use of Uninitialized Variable
Microsoft’s Azure Sphere is a platform for the development of internet-of-things applications. It features a custom SoC that consists of a set of cores that run both high-level and real-time applications, enforces security and manages encryption (among other functions). The high-level applications execute on a custom Linux-based OS, with several modifications to make it smaller and more secure, specifically for IoT applications.
Processes with AZURE_SPHERE_CAP_*
are allowed to interact with Pluton and Security Monitor, but only via the syscalls that they are allowed to access. For instance, when a user holds the AZURE_SPHERE_CAP_PERIPHERAL_PIN_MAPPING
capability, they are allowed to use the following Secmon syscalls:
{.number = SMSyscallStartRtCoreByComponentId, .caps = AZURE_SPHERE_CAP_PERIPHERAL_PIN_MAPPING, .linux_caps = 0},
{.number = SMSyscallStopRtCore, .caps = AZURE_SPHERE_CAP_PERIPHERAL_PIN_MAPPING, .linux_caps = 0},
{.number = SMSyscallSetRtCoreCommunicationBuffer, .caps = AZURE_SPHERE_CAP_PERIPHERAL_PIN_MAPPING, .linux_caps = 0},
{.number = SMSyscallPeripheralAcquire, .caps = AZURE_SPHERE_CAP_PERIPHERAL_PIN_MAPPING, .linux_caps = 0},
{.number = SMSyscallPeripheralRelease, .caps = AZURE_SPHERE_CAP_PERIPHERAL_PIN_MAPPING, .linux_caps = 0},
{.number = SMSyscallPeripheralGetAvailableDomains, .caps = AZURE_SPHERE_CAP_PERIPHERAL_PIN_MAPPING, .linux_caps = 0},
{.number = SMSyscallPeripheralLockConfig, .caps = AZURE_SPHERE_CAP_PERIPHERAL_PIN_MAPPING, .linux_caps = 0},
For our present advisory we deal with the SMSyscallPeripheralAcquire
syscall, but it’s important to stress that an attacker would already needed to have elevated privileges or gained AZURE_SPHERE_CAP_PERIPHERAL_PIN_MAPPING
.
To start, let us examine the parameters that SMSyscallPeripheralAcquire
requires:
struct azure_sphere_syscall syscall = {};
syscall.number = SMSyscallPeripheralAcquire;
syscall.flags = 0x4945;
syscall.args[0] = inpbuf; // inpbuf [0]
syscall.args[1] = 0x400; // insize [1]
syscall.args[2] = outbuf; // outbuf [2]
syscall.args[3] = 0x400; // outsize [3]
The input buffer [0] must point to valid userspace memory, and the input buffer size [1] must be greater than or equal to 0x8. The output buffer [2] and the output buffer size [3] follow the same requirements as the input buffer and input size. It’s also worth noting that insize + outsize + sizeof(azure_sphere_syscall)
must be less than 0x1060, but that’s more a generic Security Monitor syscall aspect. Let us now examine our best approximation of the inpbuf
and outbuf
structures, starting with inpbuf
:
struct PeriphAcquireInput{
uint32_t owner_id; // [0]
uint32_t desired_pin_mode; // [1]
uint32_t periph_pin_id[(insize-8)/4]; // [2]
}
The owner_id
member is just an arbitrary identifier that aids in locking peripherals from being changed, while the much more important desired_pin_mode
[1] lets Security Monitor know which mux mode to change a given pin to. Finally, the array of periph_pin_id
lays out which pins exactly need to be configured. So, to summarize, if one wanted to change pin GPIO4 and GPIO5 from GPIO to PWM, one could send as an inpbuf
: 0x01010101 0x00000001 0x00010004 0x00010005
. The 0x00000001
says “pwm mode”, and the 0x00010004 0x00010005
says “GPIO pin 4 and GPIO pin 5”. Also worth noting, the individual pin modes differ for each pin. Now onto the outbuf
structure:
struct PeriphAcquireOutput{
uint32_t input_entries_processed; // [0]
uint32_t processed_entries_in_outbuf; // [1]
struct PeriphAcquireOutputEntry[output_entries_in_outbuf]; // size 0x10 each
}
struct PeriphAcquireOutputEntry{
uint32_t periph_pin_id; // [2]
uint16_t holds_0x4; // [3]
uint16_t holds_???; // [4]
uint32_t zeroed_0;
uint32_t zeroed_1;
}
At [0], the amount of input entries processed is listed, and at [1] we see the amount of processed entries that could fit in the output buffer. Following that, we see a list of output entries. We’ve only observed the first 8 bytes of each of these entries ever being filled, the first dword [2] being the given input periph_pin_id
that was processed. The next hword [3] always seems to contain 0x0004, and the last eight bytes always seem to be zeroed out, but the hword at [4] is of most interest to us. To demonstrate, a sample input buffer:
[T_T] Before sorting:
0x00000000: 0x00000001 0xabcd0002 0x00010000 0x0000ff83 | ....
0x00000010: 0x0000ff06 0x0000fe89 0x0000fe0c 0x0000fd8f | ....
0x00000020: 0x0000fd12 0x0000fc95 0x0000fc18 0x0000fb9b | ....
0x00000030: 0x0000fb1e 0x0000faa1 0x0000fa24 0x0000f9a7 | ..$.
0x00000040: 0x0000f92a 0x0000f8ad 0x0000f830 0x00000001 | *.0.
With its corresponding output buffer:
[^_^] PeriphAcquire=> i:0x51 t:0x4c
0x00000000: 0x00000011 0x00000011 0x00000001 0x803e0004 | ....
0x00000010: 0x00000000 0x00000000 0x0000f830 0x803e0004 | ..0.
0x00000020: 0x00000000 0x00000000 0x0000f8ad 0x803e0004 | ....
0x00000030: 0x00000000 0x00000000 0x0000f92a 0x803e0004 | ..*.
0x00000040: 0x00000000 0x00000000 0x0000f9a7 0x00000004 | ....
0x00000050: 0x00000000 0x00000000 0x0000fa24 0x251c0004 | ..$.
0x00000060: 0x00000000 0x00000000 0x0000faa1 0x7eb30004 | ....
0x00000070: 0x00000000 0x00000000 0x0000fb1e 0x00000004 | ....
0x00000080: 0x00000000 0x00000000 0x0000fb9b 0x00000004 | ....
0x00000090: 0x00000000 0x00000000 0x0000fc18 0x96e20004 | ....
0x000000a0: 0x00000000 0x00000000 0x0000fc95 0x00000004 | ....
0x000000b0: 0x00000000 0x00000000 0x0000fd12 0x425a0004 | ....
0x000000c0: 0x00000000 0x00000000 0x0000fd8f 0x00000004 | ....
0x000000d0: 0x00000000 0x00000000 0x0000fe0c 0x251c0004 | ....
0x000000e0: 0x00000000 0x00000000 0x0000fe89 0x471b0004 | ....
0x000000f0: 0x00000000 0x00000000 0x0000ff06 0x00000004 | ....
0x00000100: 0x00000000 0x00000000 0x0000ff83 0x951e0004 | ....
The last column corresponds to the holds_0x4
and holds_???
members of the PeriphAcquireOutputEntry
structure, and as shown, the holds_???
member does indeed hold an unknown value. It’s worth noting that Security Monitor lies at 0x803d0000-0x803ec4a4 in memory, which lines up with the 0x803e....
entries we see within the list. Also, based on the fact that these two bytes can change in between syscalls and reboots, we believe this to be an information leak from Security Monitor’s heap. Furthermore, we can see a lack of initialization in the allocated structure from which this info leak comes from:
alloc_and_init_0x18_LL_obj:
803d0b60 f8b5 push {r3, r4, r5, r6, r7, lr} {var_18} {var_4} {__saved_r7} {__saved_r6} {__saved_r5} {__saved_r4}
803d0b62 0446 mov r4, r0
803d0b64 1820 movs r0, #0x18
803d0b66 0d46 mov r5, r1
803d0b68 1746 mov r7, r2
803d0b6a 1e46 mov r6, r3
803d0b6c 07f0d9fd bl #allocate_bytes(size) // [0]
803d0b70 069b ldr r3, [sp, #0x18] {arg5}
803d0b72 b5f800c0 ldrh r12, [r5]
803d0b76 1b68 ldr r3, [r3]
803d0b78 6d88 ldrh r5, [r5, #2]
803d0b7a 3988 ldrh r1, [r7]
803d0b7c 3268 ldr r2, [r6]
803d0b7e 4361 str r3, [r0, #0x14] {periph_0x18_thing::another_periph_upper.d}
803d0b80 6368 ldr r3, [r4, #4]
803d0b82 a0f808c0 strh r12, [r0, #8] {periph_0x18_thing::periph_lower}
803d0b86 4360 str r3, [r0, #4] {periph_0x18_thing::ll_prev}
803d0b88 6368 ldr r3, [r4, #4]
803d0b8a 4581 strh r5, [r0, #0xa] {periph_0x18_thing::periph_upper}
803d0b8c 0261 str r2, [r0, #0x10] {periph_0x18_thing::periph_mode_copy}
803d0b8e 8181 strh r1, [r0, #0xc] {periph_0x18_thing::some_hword} // [1]
803d0b90 0460 str r4, [r0] {periph_0x18_thing::muh_ll}
803d0b92 1860 str r0, [r3]
803d0b94 a368 ldr r3, [r4, #8]
803d0b96 6060 str r0, [r4, #4]
803d0b98 0133 adds r3, #1
803d0b9a a360 str r3, [r4, #8]
803d0b9c f8bd pop {r3, r4, r5, r6, r7, pc} {var_18} {__saved_r4} {__saved_r5} {__saved_r6} {__saved_r7} {var_4}
In this function we only care about [0], where a size 0x18 object is allocated, and [1], where we see a strh
occur on object+0xc
that occurs without any corresponding strh
on object+0xe
. We can then clearly see when pieces of this object are copied into the syscall’s output buffer that this data gets thrown into PeriphAcquireOutputEntry->holds_???
at 0x803e36ea
.
803e36d8 b442 cmp r4, r6 // while not end_of_LL, copy output entries
803e36da 00f08b82 beq #0x803e3bf4
803e36de 53f808cb ldr r12, [r3], #8 {periph_0x18_thing::muh_ll}
803e36e2 06f10807 add r7, r6, #8
803e36e6 1036 adds r6, #0x10
803e36e8 0fcb ldm r3, {r0, r1, r2, r3}
803e36ea 87e80f00 stm r7, {r0, r1, r2, r3} {periph_acquire_outbuf_entry::pin_periph_id} {periph_acquire_outbuf_entry::hword_leak} {periph_acquire_outbuf_entry::zeroed_0x8} {periph_acquire_outbuf_entry::zeroed_0xC}
803e36ee 6346 mov r3, r12
803e36f0 f2e7 b #0x803e36d8
Note that while we believe this vulnerability is also within 21.04, we have only tested it on 21.01.
2021-06-08 - Vendor Disclosure
2021-09-14 - Public Release
Discovered by Claudio Bozzato and Lilith >_> of Cisco Talos.