Vendor: ARM Vendor URL: https://os.mbed.com/ Versions affected: Prior to 5.15.2 Systems Affected: ARM Mbed OS Author: Ilya Zhuravlev Risk: High
Summary:
The ARM Mbed operating system contains a USB Mass Storage driver (USBMD), which allows emulation of a mass storage device over USB. This driver contains a three (3) memory safety vulnerabilities, allowing adversaries with physical access to corrupt kernel memory or disclose kernel memory contents.
Location:
Impact:
When the USB mass storage driver is enabled in devices that run the Mbed OS, an adversary with physical access is able to corrupt and disclose kernel memory contents, potentially leading to code execution.
Details:
The code in USBMSD.cpp
is responsible for processing SCSI commands sent over USB and responding to them. The following sections describe three vulnerabilities that arise due to how these commands are handled.
Vulnerability #1
If, at the start of a transfer, the base address (_addr
) is set up to be greater than the total size of the block device (_memory_size
), when the USB mass storage driver attempts to adjust the read or write size, an integer underflow will occur, as shown in the below code snippet taken from the memoryRead
function:
void USBMSD::memoryRead(void) { uint32_t n; n = (_length > MAX_PACKET) ? MAX_PACKET : _length; if ((_addr + n) > _memory_size) { n = _memory_size - _addr; _stage = ERROR; } // we read an entire block if (!(_addr % _block_size)) { disk_read(_page, _addr / _block_size, 1); } // write data which are in RAM _write_next( _page[_addr % _block_size], MAX_PACKET); _addr += n; _length -= n; _csw.DataResidue -= n; if (!_length || (_stage != PROCESS_CBW)) { _csw.Status = (_stage == PROCESS_CBW) ? CSW_PASSED : CSW_FAILED; _stage = (_stage == PROCESS_CBW) ? SEND_CSW : _stage; } }
The code then calls disk_read
, which eventually calls the read method of the underlying BlockDevice
class. Depending on the underlying implementation this may lead to a read of memory past the end of the block device’s buffers.
Furthermore, when disk_read
returns, this function also fails to handle the case where _addr
is greater than _memory_size
. If _addr
is greater than _memory_size
, after the size check fails, an underflow occurs when the value of n
is calculated which is assigned a large value. At the end of the function, a failure flag is set into _csw
which is later sent to the host.
Vulnerability #2:
The WRITE10 and WRITE12 commands are implemented by the memoryWrite
function. As the size of the USB packet, MAX_PACKET
(64), is much less than the storage block size, before the data is flushed to the underlying storage, this function accumulates it in the _page[]
buffer (size determined by memory geometry, but likely at least 512 bytes). The relevant part is reproduced below:
void USBMSD::memoryWrite(uint8_t *buf, uint16_t size) { if ((_addr + size) > _memory_size) { size = _memory_size - _addr; _stage = ERROR; endpoint_stall(_bulk_out); } // we fill an array in RAM of 1 block before writing it in memory for (int i = 0; i < size; i++) { _page[_addr % _block_size + i] = buf[i]; } // if the array is filled, write it in memory if (!((_addr + size) % _block_size)) { if (!(disk_status() WRITE_PROTECT)) { disk_write(_page, _addr / _block_size, 1); } } _addr += size; _length -= size; _csw.DataResidue -= size; if ((!_length) || (_stage != PROCESS_CBW)) { _csw.Status = (_stage == ERROR) ? CSW_FAILED : CSW_PASSED; sendCSW(); } }
If at function entry _addr
is misaligned, e.g. 511, then during the copy from buf
into _page
it could overflow the _page
array. Specifically, with the max USB payload size being 0x40
bytes, the copy would overflow by up to 0x3F
bytes.
The exploitability of the issue would then depend on the exact layout of object variables generated by the compiler.
Vulnerability #3:
The WRITE10 and WRITE12 commands are implemented by the memoryWrite
function, while the VERIFY10 command is implemented by the memoryVerify
function. Both functions deal with data sent in by the host and, in the case of memoryWrite
, this data is written to the underlying storage, while in the case of the memoryVerify
function, the data is compared with the existing contents of the storage.
The data transfer starts with the infoTransfer
function, which parses the Command Block included within the Command Block Wrapper and extracts address and total length of the transfer. Then, as new data comes in over USB, either memoryWrite
or memoryVerify
are executed. Both of these functions have the same code to deal with invalid input, however in both places the input sanitization checks are performed improperly:
if ((_addr + size) > _memory_size) { size = _memory_size - _addr; _stage = ERROR; endpoint_stall(_bulk_out); }
The code above attempts to limit the size
of the incoming data so that the total does not exceed _memory_size
. Both _addr
and size
are controlled by the attacker, with _addr
being an arbitrary value aligned to 512 bytes, and size being an arbitrary value up to 64. In the case where _addr
is greater than _memory_size
, the calculated size
would underflow and, being an unsigned 16-bit variable, can become a value up to 0xFE00
.
Then, in case of memoryWrite
the data is written into a temporary _page
buffer as follows:
// we fill an array in RAM of 1 block before writing it in memory for (int i = 0; i < size; i++) { _page[_addr % _block_size + i] = buf[i]; }
When size is greater than MAX_PACKET
(64), reading from buf[i]
would reference out-of-bounds memory. When size is greater than _block_size
(memory geometry dependent, but likely at least 512), writing to _page[_addr%_block_size+i]
would write out-of-bounds into the object’s memory.
The issue could then be exploited either to leak stack memory contents (by setting the size between 64 and 512 bytes), or to corrupt global kernel memory (by setting the size larger than 512 bytes). As the contents of a stack buffer buf
past index 64 are not directly controlled by the attacker, the exploitation of the memory corruption issue is non-trivial and might be impossible, depending on the exact memory layout.
Recommendation:
Upgrade to the v5.15.2 LTS for MbedOS.
Vendor Communication:
- Feb 17, 2020: Realization that NCC-ZEP-024, NCC-ZEP-025, NCC-ZEP-026 also affect MbedOS.
- Feb 23, 2020: Initial contact with ARM to identify security contact.
- Mar 03, 2020: Full details of the 3 issues disclosed to ARM security team.
- Mar 04, 2020: ARM acknowledged receipt of issues.
- Mar 04, 2020: Meeting with ARM to discuss details and answer questions.
- Mar 24, 2020: Reached out to ARM for updates on remediation.
- Mar 24, 2020: ARM responds with a link to the fixes (master branch).
- Apr 01, 2020: ARM confirms fixes were merged (backported to v5.15).
- Apr 01, 2020: NCC Group asked if there are CVE numbers assigned.
- Apr 23, 2020: NCC Group asked again if there are CVE numbers assigned.
- May 28, 2020: NCC Group advised ARM that we plan to publish an advisory.
- June 9, 2020: ARM confirms that CVE numbers will not be assigned.
- June 11, 2020: Publication date of this advisory.
Thanks to:
Rob Wood, for assisting with the disclosure process.
About NCC Group:
NCC Group is a global expert in cyber security and risk mitigation, working with businesses to protect their brand, value and reputation against the ever-evolving threat landscape.
With our knowledge, experience and global footprint, we are best placed to help businesses identify, assess, mitigate and respond to the risks they face.
We are passionate about making the Internet safer and revolutionizing the way in which organizations think about cybersecurity.