Talos Vulnerability Report

TALOS-2021-1310

Microsoft Azure Sphere Security Monitor SMSyscallWriteBlockToStageImage information disclosure vulnerability

August 10, 2021
CVE Number

CVE-2021-26428

Summary

An information disclosure vulnerability exists in the Security Monitor SMSyscallWriteBlockToStageImage functionality of Microsoft Azure Sphere 21.01. A specially crafted set of syscalls can lead to a disclosure of sensitive information. An attacker can use SMCs or ioctls to trigger this vulnerability.

Tested Versions

Microsoft Azure Sphere 21.01

Product URLs

https://azure.microsoft.com/en-us/services/azure-sphere/

CVSSv3 Score

4.4 - CVSS:3.0/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N

CWE

CWE-20 - Improper Input Validation

Details

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.

The process of flashing Azure Sphere applications and firmware is a particularly complex procedure. First one must call SMSyscallOpenImageForStaging in order to get a handle that references a firmware image being staged, and then one must actually write to this image via multiple calls to SMSyscallWriteBlockToStageImage. Once the image has been fully written to the staging area, SMSyscallCommitImageStaging and SMSyscallInstallStagedImages are then called, to verify and transfer the image from the staging partition to the production partition.
Also worth noting that most images require an ImageManifest to be staged as well, but for what concerns this advisory, it’s enough to discuss SMSyscallOpenImageForStaging slightly and SMSyscallWriteBlockToStageImage in depth. So let’s start with the arguments for SMSyscallOpenImageForStaging:

syscall.number = SMSyscallOpenImageForStaging;
syscall.flags = 0x444a;
syscall.args[0] = buffer_size;
syscall.args[1] = 0xf0cf0cf0;            // doesn't matter, clobbered
syscall.args[2] = &handle;               // output pointer for handle
syscall.args[3] = 0x8;

Of these arguments, syscall.args[0] is the size of the staging buffer that we wish to allocate, with the maximum total size for all images being ~0x6ec000. syscall.args[1] is clobbered, and syscall.args[2] is the output uint64_t for the resultant image handle, assuming there’s enough space and handles available. Once we’ve allocated a staging buffer and we have the handle, we can now write to this area via SMSyscallWriteBlockToStageImage:

syscall.number = SMSyscallWriteBlockToStageImage;
syscall.flags = 0x454446;
syscall.args[0] = &handle;
syscall.args[1] = 0x8;
syscall.args[2] = dst_offset;
syscall.args[3] = src_offset;
syscall.args[4] = &src_buffer;
syscall.args[5] = src_block_size;

With syscall.args[0], we pass in the same handle as from the previous syscall, syscall.args[1] the size thereof. The syscall.args[2] is the offset into the staging buffer that we wish to start writing, which cannot be greater than the buffer_size input passed in when opening the staging image. syscall.args[3] is the offset into our source buffer, which we pass in at syscall.args[4], along with the source block size at syscall.args[5]. To summarize and simplify the result of this syscall: memcpy(stage_buffer + dst_offset, src_buffer + src_offset, src_block_size);, the end result being a set of images are now stored in one of two different places in flash that are not normally reachable except from these staging syscalls.

An interesting thing to note about the src_offset parameter in particular: there’s only one instance of this argument ever being used.

803dc1c4  struct partition_table* r4_3 = staging_partitions->nested_parttable
803dc1d6  int64_t end_of_src_of_write
803dc1d6  end_of_src_of_write.d = srcptr_arg0x3 + srcptr_arg0x4             // [1]
803dc1d8  r3_8:r2_7 = flash_dst_len
803dc1e0  r3_9:r2_8 = flash_dst_addr
803dc1e4  int32_t r0_12 = TableCommWrite(clobbered: r4_3, 
                                         some_struct: r4_3->nested_parttable), 
                                         dst_high: r2_8, dst_low: r3_9, 
                                         write_len: r3_8:r2_7, 
                                         src_of_write: end_of_src_of_write) // [2]

No other cross references to our argument occur except for the above at [1], whereby the source of the write in TableCommWrite is arg[3] + arg[4], i.e. the source buffer + the buffer offset.
This results in an attacker being able to start the source of the write from anywhere in Security-Monitor’s memory space. After this write has occurred, it’s possible to simply utilize SMSyscallReadFlash to read back the buffer from flash, resulting in an information leak of Security-Monitor’s entire memory space.

Timeline

2021-06-08 - Vendor Disclosure
2021-08-10 - Public Release

Credit

Discovered by Claudio Bozzato and Lilith >_> of Cisco Talos.