Fix CVE-2020-13253

By using invalidated address, guest can do out-of-bounds accesses.
 These patches fix the issue by only allowing SD card image sizes
 power of 2, and not switching to SEND_DATA state when the address
 is invalid (out of range).
 
 This issue was found using QEMU fuzzing mode (using --enable-fuzzing,
 see docs/devel/fuzzing.txt) and reported by Alexander Bulekov.
 
 Reproducer:
   https://bugs.launchpad.net/qemu/+bug/1880822/comments/1
 
 CI jobs results:
 . https://cirrus-ci.com/build/5157142548185088
 . https://gitlab.com/philmd/qemu/-/pipelines/166381731
 . https://travis-ci.org/github/philmd/qemu/builds/707956535
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEE+qvnXhKRciHc/Wuy4+MsLN6twN4FAl8NuSQACgkQ4+MsLN6t
 wN7MEg/+PER/n+CpmrC2lggQ3WJwNjvY09A4yfPfhKldjOi+25/amf/bQ2Zjmj7m
 HoiiPFu7vz+FugOfGv5YFlTS2+VNmN1UZqGqZRwY/YJJKg9am6TJ8zA4UBf4iegi
 OqNBJOPW/EYsAYdH3jUFmW15zAsRHEM6g2vZ1Z4WwVZqfYHsMB/y2khp9Fr+jGU0
 6wDeG0cdap5QVsamIll4/BoxgBa5UdtBYjzo7QBENs+abvOf56jjUqZx0+AL/Ua/
 IOpZ01mmPZJ4wJxPNT87gfEnHv0MRA7bSpJ7TAC80xVoQjeoK+V2Ohvy+rvYPaqm
 5mR0l4M+GGhglCg44wV3uwNonmltCxvTgGqZrQPsa3WnXMFoXqwGZgwl6XrYdLzV
 hVODJAu/Ivegk9AAbVrZGXg/shQtkB4gyoOaE3Qoraf1az9/XudECIo+zwocP4Ip
 Z0ny8bwQKq2QGYrCU3NWlgWi30sj6PeW5e6Jgq/2b1sUeKuUgNuuBPcRmXQ6kaz5
 vMX7qYsXAxvO7o1QlbASzdvSvOXGx+0J0CJctPnY4jAJ7qjvJTKOb0j+jwMNJy+D
 XFAgB+D0go+UvnaPJn6teIHzaD4NqWE37MaamxsMY6RWjAnoy1+OOvZIZTnq+LnH
 iLbgk2EsxlFyBd3aZ/51ukeTUxpNgu9J6iRcXB3yVNBS4vqlBDw=
 =VZsF
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/philmd-gitlab/tags/sdcard-CVE-2020-13253-pull-request' into staging

Fix CVE-2020-13253

By using invalidated address, guest can do out-of-bounds accesses.
These patches fix the issue by only allowing SD card image sizes
power of 2, and not switching to SEND_DATA state when the address
is invalid (out of range).

This issue was found using QEMU fuzzing mode (using --enable-fuzzing,
see docs/devel/fuzzing.txt) and reported by Alexander Bulekov.

Reproducer:
  https://bugs.launchpad.net/qemu/+bug/1880822/comments/1

CI jobs results:
. https://cirrus-ci.com/build/5157142548185088
. https://gitlab.com/philmd/qemu/-/pipelines/166381731
. https://travis-ci.org/github/philmd/qemu/builds/707956535

# gpg: Signature made Tue 14 Jul 2020 14:54:44 BST
# gpg:                using RSA key FAABE75E12917221DCFD6BB2E3E32C2CDEADC0DE
# gpg: Good signature from "Philippe Mathieu-Daudé (F4BUG) <f4bug@amsat.org>" [full]
# Primary key fingerprint: FAAB E75E 1291 7221 DCFD  6BB2 E3E3 2C2C DEAD C0DE

* remotes/philmd-gitlab/tags/sdcard-CVE-2020-13253-pull-request:
  hw/sd/sdcard: Do not switch to ReceivingData if address is invalid
  hw/sd/sdcard: Update coding style to make checkpatch.pl happy
  hw/sd/sdcard: Do not allow invalid SD card sizes
  hw/sd/sdcard: Simplify realize() a bit
  hw/sd/sdcard: Restrict Class 6 commands to SCSD cards
  tests/acceptance/boot_linux: Expand SD card image to power of 2
  tests/acceptance/boot_linux: Tag tests using a SD card with 'device:sd'
  docs/orangepi: Add instructions for resizing SD image to power of two
  MAINTAINERS: Cc qemu-block mailing list

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2020-07-15 09:06:55 +01:00
commit 3a9163af4e
4 changed files with 106 additions and 31 deletions

View File

@ -1674,6 +1674,7 @@ F: hw/ssi/xilinx_*
SD (Secure Card) SD (Secure Card)
M: Philippe Mathieu-Daudé <f4bug@amsat.org> M: Philippe Mathieu-Daudé <f4bug@amsat.org>
L: qemu-block@nongnu.org
S: Odd Fixes S: Odd Fixes
F: include/hw/sd/sd* F: include/hw/sd/sd*
F: hw/sd/core.c F: hw/sd/core.c

View File

@ -127,6 +127,16 @@ can be downloaded from:
Alternatively, you can also choose to build you own image with buildroot Alternatively, you can also choose to build you own image with buildroot
using the orangepi_pc_defconfig. Also see https://buildroot.org for more information. using the orangepi_pc_defconfig. Also see https://buildroot.org for more information.
When using an image as an SD card, it must be resized to a power of two. This can be
done with the qemu-img command. It is recommended to only increase the image size
instead of shrinking it to a power of two, to avoid loss of data. For example,
to prepare a downloaded Armbian image, first extract it and then increase
its size to one gigabyte as follows:
.. code-block:: bash
$ qemu-img resize Armbian_19.11.3_Orangepipc_bionic_current_5.3.9.img 1G
You can choose to attach the selected image either as an SD card or as USB mass storage. You can choose to attach the selected image either as an SD card or as USB mass storage.
For example, to boot using the Orange Pi PC Debian image on SD card, simply add the -sd For example, to boot using the Orange Pi PC Debian image on SD card, simply add the -sd
argument and provide the proper root= kernel parameter: argument and provide the proper root= kernel parameter:
@ -213,12 +223,12 @@ Next, unzip the NetBSD image and write the U-Boot binary including SPL using:
$ dd if=/path/to/u-boot-sunxi-with-spl.bin of=armv7.img bs=1024 seek=8 conv=notrunc $ dd if=/path/to/u-boot-sunxi-with-spl.bin of=armv7.img bs=1024 seek=8 conv=notrunc
Finally, before starting the machine the SD image must be extended such Finally, before starting the machine the SD image must be extended such
that the NetBSD kernel will not conclude the NetBSD partition is larger than that the size of the SD image is a power of two and that the NetBSD kernel
the emulated SD card: will not conclude the NetBSD partition is larger than the emulated SD card:
.. code-block:: bash .. code-block:: bash
$ dd if=/dev/zero bs=1M count=64 >> armv7.img $ qemu-img resize armv7.img 2G
Start the machine using the following command: Start the machine using the following command:

View File

@ -32,6 +32,7 @@
#include "qemu/osdep.h" #include "qemu/osdep.h"
#include "qemu/units.h" #include "qemu/units.h"
#include "qemu/cutils.h"
#include "hw/irq.h" #include "hw/irq.h"
#include "hw/registerfields.h" #include "hw/registerfields.h"
#include "sysemu/block-backend.h" #include "sysemu/block-backend.h"
@ -920,6 +921,11 @@ static sd_rsp_type_t sd_normal_command(SDState *sd, SDRequest req)
sd->multi_blk_cnt = 0; sd->multi_blk_cnt = 0;
} }
if (sd_cmd_class[req.cmd] == 6 && FIELD_EX32(sd->ocr, OCR, CARD_CAPACITY)) {
/* Only Standard Capacity cards support class 6 commands */
return sd_illegal;
}
switch (req.cmd) { switch (req.cmd) {
/* Basic commands (Class 0 and Class 1) */ /* Basic commands (Class 0 and Class 1) */
case 0: /* CMD0: GO_IDLE_STATE */ case 0: /* CMD0: GO_IDLE_STATE */
@ -1165,12 +1171,15 @@ static sd_rsp_type_t sd_normal_command(SDState *sd, SDRequest req)
case 17: /* CMD17: READ_SINGLE_BLOCK */ case 17: /* CMD17: READ_SINGLE_BLOCK */
switch (sd->state) { switch (sd->state) {
case sd_transfer_state: case sd_transfer_state:
if (addr + sd->blk_len > sd->size) {
sd->card_status |= ADDRESS_ERROR;
return sd_r1;
}
sd->state = sd_sendingdata_state; sd->state = sd_sendingdata_state;
sd->data_start = addr; sd->data_start = addr;
sd->data_offset = 0; sd->data_offset = 0;
if (sd->data_start + sd->blk_len > sd->size)
sd->card_status |= ADDRESS_ERROR;
return sd_r1; return sd_r1;
default: default:
@ -1181,12 +1190,15 @@ static sd_rsp_type_t sd_normal_command(SDState *sd, SDRequest req)
case 18: /* CMD18: READ_MULTIPLE_BLOCK */ case 18: /* CMD18: READ_MULTIPLE_BLOCK */
switch (sd->state) { switch (sd->state) {
case sd_transfer_state: case sd_transfer_state:
if (addr + sd->blk_len > sd->size) {
sd->card_status |= ADDRESS_ERROR;
return sd_r1;
}
sd->state = sd_sendingdata_state; sd->state = sd_sendingdata_state;
sd->data_start = addr; sd->data_start = addr;
sd->data_offset = 0; sd->data_offset = 0;
if (sd->data_start + sd->blk_len > sd->size)
sd->card_status |= ADDRESS_ERROR;
return sd_r1; return sd_r1;
default: default:
@ -1226,17 +1238,23 @@ static sd_rsp_type_t sd_normal_command(SDState *sd, SDRequest req)
/* Writing in SPI mode not implemented. */ /* Writing in SPI mode not implemented. */
if (sd->spi) if (sd->spi)
break; break;
if (addr + sd->blk_len > sd->size) {
sd->card_status |= ADDRESS_ERROR;
return sd_r1;
}
sd->state = sd_receivingdata_state; sd->state = sd_receivingdata_state;
sd->data_start = addr; sd->data_start = addr;
sd->data_offset = 0; sd->data_offset = 0;
sd->blk_written = 0; sd->blk_written = 0;
if (sd->data_start + sd->blk_len > sd->size) if (sd_wp_addr(sd, sd->data_start)) {
sd->card_status |= ADDRESS_ERROR;
if (sd_wp_addr(sd, sd->data_start))
sd->card_status |= WP_VIOLATION; sd->card_status |= WP_VIOLATION;
if (sd->csd[14] & 0x30) }
if (sd->csd[14] & 0x30) {
sd->card_status |= WP_VIOLATION; sd->card_status |= WP_VIOLATION;
}
return sd_r1; return sd_r1;
default: default:
@ -1250,17 +1268,23 @@ static sd_rsp_type_t sd_normal_command(SDState *sd, SDRequest req)
/* Writing in SPI mode not implemented. */ /* Writing in SPI mode not implemented. */
if (sd->spi) if (sd->spi)
break; break;
if (addr + sd->blk_len > sd->size) {
sd->card_status |= ADDRESS_ERROR;
return sd_r1;
}
sd->state = sd_receivingdata_state; sd->state = sd_receivingdata_state;
sd->data_start = addr; sd->data_start = addr;
sd->data_offset = 0; sd->data_offset = 0;
sd->blk_written = 0; sd->blk_written = 0;
if (sd->data_start + sd->blk_len > sd->size) if (sd_wp_addr(sd, sd->data_start)) {
sd->card_status |= ADDRESS_ERROR;
if (sd_wp_addr(sd, sd->data_start))
sd->card_status |= WP_VIOLATION; sd->card_status |= WP_VIOLATION;
if (sd->csd[14] & 0x30) }
if (sd->csd[14] & 0x30) {
sd->card_status |= WP_VIOLATION; sd->card_status |= WP_VIOLATION;
}
return sd_r1; return sd_r1;
default: default:
@ -2100,12 +2124,36 @@ static void sd_realize(DeviceState *dev, Error **errp)
return; return;
} }
if (sd->blk && blk_is_read_only(sd->blk)) { if (sd->blk) {
int64_t blk_size;
if (blk_is_read_only(sd->blk)) {
error_setg(errp, "Cannot use read-only drive as SD card"); error_setg(errp, "Cannot use read-only drive as SD card");
return; return;
} }
if (sd->blk) { blk_size = blk_getlength(sd->blk);
if (blk_size > 0 && !is_power_of_2(blk_size)) {
int64_t blk_size_aligned = pow2ceil(blk_size);
char *blk_size_str;
blk_size_str = size_to_str(blk_size);
error_setg(errp, "Invalid SD card size: %s", blk_size_str);
g_free(blk_size_str);
blk_size_str = size_to_str(blk_size_aligned);
error_append_hint(errp,
"SD card size has to be a power of 2, e.g. %s.\n"
"You can resize disk images with"
" 'qemu-img resize <imagefile> <new-size>'\n"
"(note that this will lose data if you make the"
" image smaller than it currently is).\n",
blk_size_str);
g_free(blk_size_str);
return;
}
ret = blk_set_perm(sd->blk, BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE, ret = blk_set_perm(sd->blk, BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE,
BLK_PERM_ALL, errp); BLK_PERM_ALL, errp);
if (ret < 0) { if (ret < 0) {

View File

@ -28,6 +28,22 @@ try:
except CmdNotFoundError: except CmdNotFoundError:
P7ZIP_AVAILABLE = False P7ZIP_AVAILABLE = False
"""
Round up to next power of 2
"""
def pow2ceil(x):
return 1 if x == 0 else 2**(x - 1).bit_length()
"""
Expand file size to next power of 2
"""
def image_pow2ceil_expand(path):
size = os.path.getsize(path)
size_aligned = pow2ceil(size)
if size != size_aligned:
with open(path, 'ab+') as fd:
fd.truncate(size_aligned)
class LinuxKernelTest(Test): class LinuxKernelTest(Test):
KERNEL_COMMON_COMMAND_LINE = 'printk.time=0 ' KERNEL_COMMON_COMMAND_LINE = 'printk.time=0 '
@ -620,6 +636,7 @@ class BootLinuxConsole(LinuxKernelTest):
""" """
:avocado: tags=arch:arm :avocado: tags=arch:arm
:avocado: tags=machine:orangepi-pc :avocado: tags=machine:orangepi-pc
:avocado: tags=device:sd
""" """
deb_url = ('https://apt.armbian.com/pool/main/l/' deb_url = ('https://apt.armbian.com/pool/main/l/'
'linux-4.20.7-sunxi/linux-image-dev-sunxi_5.75_armhf.deb') 'linux-4.20.7-sunxi/linux-image-dev-sunxi_5.75_armhf.deb')
@ -635,6 +652,7 @@ class BootLinuxConsole(LinuxKernelTest):
rootfs_path_xz = self.fetch_asset(rootfs_url, asset_hash=rootfs_hash) rootfs_path_xz = self.fetch_asset(rootfs_url, asset_hash=rootfs_hash)
rootfs_path = os.path.join(self.workdir, 'rootfs.cpio') rootfs_path = os.path.join(self.workdir, 'rootfs.cpio')
archive.lzma_uncompress(rootfs_path_xz, rootfs_path) archive.lzma_uncompress(rootfs_path_xz, rootfs_path)
image_pow2ceil_expand(rootfs_path)
self.vm.set_console() self.vm.set_console()
kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE +
@ -669,9 +687,10 @@ class BootLinuxConsole(LinuxKernelTest):
""" """
:avocado: tags=arch:arm :avocado: tags=arch:arm
:avocado: tags=machine:orangepi-pc :avocado: tags=machine:orangepi-pc
:avocado: tags=device:sd
""" """
# This test download a 196MB compressed image and expand it to 932MB... # This test download a 196MB compressed image and expand it to 1GB
image_url = ('https://dl.armbian.com/orangepipc/archive/' image_url = ('https://dl.armbian.com/orangepipc/archive/'
'Armbian_19.11.3_Orangepipc_bionic_current_5.3.9.7z') 'Armbian_19.11.3_Orangepipc_bionic_current_5.3.9.7z')
image_hash = '196a8ffb72b0123d92cea4a070894813d305c71e' image_hash = '196a8ffb72b0123d92cea4a070894813d305c71e'
@ -679,6 +698,7 @@ class BootLinuxConsole(LinuxKernelTest):
image_name = 'Armbian_19.11.3_Orangepipc_bionic_current_5.3.9.img' image_name = 'Armbian_19.11.3_Orangepipc_bionic_current_5.3.9.img'
image_path = os.path.join(self.workdir, image_name) image_path = os.path.join(self.workdir, image_name)
process.run("7z e -o%s %s" % (self.workdir, image_path_7z)) process.run("7z e -o%s %s" % (self.workdir, image_path_7z))
image_pow2ceil_expand(image_path)
self.vm.set_console() self.vm.set_console()
self.vm.add_args('-drive', 'file=' + image_path + ',if=sd,format=raw', self.vm.add_args('-drive', 'file=' + image_path + ',if=sd,format=raw',
@ -710,8 +730,9 @@ class BootLinuxConsole(LinuxKernelTest):
""" """
:avocado: tags=arch:arm :avocado: tags=arch:arm
:avocado: tags=machine:orangepi-pc :avocado: tags=machine:orangepi-pc
:avocado: tags=device:sd
""" """
# This test download a 304MB compressed image and expand it to 1.3GB... # This test download a 304MB compressed image and expand it to 2GB
deb_url = ('http://snapshot.debian.org/archive/debian/' deb_url = ('http://snapshot.debian.org/archive/debian/'
'20200108T145233Z/pool/main/u/u-boot/' '20200108T145233Z/pool/main/u/u-boot/'
'u-boot-sunxi_2020.01%2Bdfsg-1_armhf.deb') 'u-boot-sunxi_2020.01%2Bdfsg-1_armhf.deb')
@ -728,8 +749,9 @@ class BootLinuxConsole(LinuxKernelTest):
image_hash = '2babb29d36d8360adcb39c09e31060945259917a' image_hash = '2babb29d36d8360adcb39c09e31060945259917a'
image_path_gz = self.fetch_asset(image_url, asset_hash=image_hash) image_path_gz = self.fetch_asset(image_url, asset_hash=image_hash)
image_path = os.path.join(self.workdir, 'armv7.img') image_path = os.path.join(self.workdir, 'armv7.img')
image_drive_args = 'if=sd,format=raw,snapshot=on,file=' + image_path
archive.gzip_uncompress(image_path_gz, image_path) archive.gzip_uncompress(image_path_gz, image_path)
image_pow2ceil_expand(image_path)
image_drive_args = 'if=sd,format=raw,snapshot=on,file=' + image_path
# dd if=u-boot-sunxi-with-spl.bin of=armv7.img bs=1K seek=8 conv=notrunc # dd if=u-boot-sunxi-with-spl.bin of=armv7.img bs=1K seek=8 conv=notrunc
with open(uboot_path, 'rb') as f_in: with open(uboot_path, 'rb') as f_in:
@ -737,12 +759,6 @@ class BootLinuxConsole(LinuxKernelTest):
f_out.seek(8 * 1024) f_out.seek(8 * 1024)
shutil.copyfileobj(f_in, f_out) shutil.copyfileobj(f_in, f_out)
# Extend image, to avoid that NetBSD thinks the partition
# inside the image is larger than device size itself
f_out.seek(0, 2)
f_out.seek(64 * 1024 * 1024, 1)
f_out.write(bytearray([0x00]))
self.vm.set_console() self.vm.set_console()
self.vm.add_args('-nic', 'user', self.vm.add_args('-nic', 'user',
'-drive', image_drive_args, '-drive', image_drive_args,