| From 2318681bf850996a3e9c682ca3290aa9f05fd15c Mon Sep 17 00:00:00 2001 |
| From: Kevin Wolf <kwolf@redhat.com> |
| Date: Tue, 25 Mar 2014 14:23:31 +0100 |
| Subject: [PATCH 24/49] qcow2: Validate snapshot table offset/size (CVE-2014-0144) |
| |
| RH-Author: Kevin Wolf <kwolf@redhat.com> |
| Message-id: <1395753835-7591-25-git-send-email-kwolf@redhat.com> |
| Patchwork-id: n/a |
| O-Subject: [virt-devel] [EMBARGOED RHEL-7.0 qemu-kvm PATCH 24/48] qcow2: Validate snapshot table offset/size (CVE-2014-0144) |
| Bugzilla: 1079455 |
| RH-Acked-by: Jeff Cody <jcody@redhat.com> |
| RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com> |
| RH-Acked-by: Paolo Bonzini <pbonzini@redhat.com> |
| |
| Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1079455 |
| Upstream status: Embargoed |
| |
| This avoid unbounded memory allocation and fixes a potential buffer |
| overflow on 32 bit hosts. |
| |
| Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
| |
| block/qcow2-snapshot.c | 29 ++++------------------------- |
| block/qcow2.c | 15 +++++++++++++++ |
| block/qcow2.h | 29 ++++++++++++++++++++++++++++- |
| tests/qemu-iotests/080 | 27 +++++++++++++++++++++++++++ |
| tests/qemu-iotests/080.out | 17 +++++++++++++++++ |
| 5 files changed, 91 insertions(+), 26 deletions(-) |
| |
| diff --git a/block/qcow2-snapshot.c b/block/qcow2-snapshot.c |
| index aa88c51..5d73506 100644 |
| |
| |
| @@ -26,31 +26,6 @@ |
| #include "block/block_int.h" |
| #include "block/qcow2.h" |
| |
| -typedef struct QEMU_PACKED QCowSnapshotHeader { |
| - /* header is 8 byte aligned */ |
| - uint64_t l1_table_offset; |
| - |
| - uint32_t l1_size; |
| - uint16_t id_str_size; |
| - uint16_t name_size; |
| - |
| - uint32_t date_sec; |
| - uint32_t date_nsec; |
| - |
| - uint64_t vm_clock_nsec; |
| - |
| - uint32_t vm_state_size; |
| - uint32_t extra_data_size; /* for extension */ |
| - /* extra data follows */ |
| - /* id_str follows */ |
| - /* name follows */ |
| -} QCowSnapshotHeader; |
| - |
| -typedef struct QEMU_PACKED QCowSnapshotExtraData { |
| - uint64_t vm_state_size_large; |
| - uint64_t disk_size; |
| -} QCowSnapshotExtraData; |
| - |
| void qcow2_free_snapshots(BlockDriverState *bs) |
| { |
| BDRVQcowState *s = bs->opaque; |
| @@ -334,6 +309,10 @@ int qcow2_snapshot_create(BlockDriverState *bs, QEMUSnapshotInfo *sn_info) |
| uint64_t *l1_table = NULL; |
| int64_t l1_table_offset; |
| |
| + if (s->nb_snapshots >= QCOW_MAX_SNAPSHOTS) { |
| + return -EFBIG; |
| + } |
| + |
| memset(sn, 0, sizeof(*sn)); |
| |
| /* Generate an ID if it wasn't passed */ |
| diff --git a/block/qcow2.c b/block/qcow2.c |
| index 5513fac..8c74dea 100644 |
| |
| |
| @@ -622,6 +622,21 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags, |
| goto fail; |
| } |
| |
| + /* Snapshot table offset/length */ |
| + if (header.nb_snapshots > QCOW_MAX_SNAPSHOTS) { |
| + error_setg(errp, "Too many snapshots"); |
| + ret = -EINVAL; |
| + goto fail; |
| + } |
| + |
| + ret = validate_table_offset(bs, header.snapshots_offset, |
| + header.nb_snapshots, |
| + sizeof(QCowSnapshotHeader)); |
| + if (ret < 0) { |
| + error_setg(errp, "Invalid snapshot table offset"); |
| + goto fail; |
| + } |
| + |
| s->snapshots_offset = header.snapshots_offset; |
| s->nb_snapshots = header.nb_snapshots; |
| |
| diff --git a/block/qcow2.h b/block/qcow2.h |
| index 4a653c5..5efc96e 100644 |
| |
| |
| @@ -38,6 +38,7 @@ |
| #define QCOW_CRYPT_AES 1 |
| |
| #define QCOW_MAX_CRYPT_CLUSTERS 32 |
| +#define QCOW_MAX_SNAPSHOTS 65536 |
| |
| /* indicate that the refcount of the referenced cluster is exactly one. */ |
| #define QCOW_OFLAG_COPIED (1LL << 63) |
| @@ -97,6 +98,32 @@ typedef struct QCowHeader { |
| uint32_t header_length; |
| } QCowHeader; |
| |
| +typedef struct QEMU_PACKED QCowSnapshotHeader { |
| + /* header is 8 byte aligned */ |
| + uint64_t l1_table_offset; |
| + |
| + uint32_t l1_size; |
| + uint16_t id_str_size; |
| + uint16_t name_size; |
| + |
| + uint32_t date_sec; |
| + uint32_t date_nsec; |
| + |
| + uint64_t vm_clock_nsec; |
| + |
| + uint32_t vm_state_size; |
| + uint32_t extra_data_size; /* for extension */ |
| + /* extra data follows */ |
| + /* id_str follows */ |
| + /* name follows */ |
| +} QCowSnapshotHeader; |
| + |
| +typedef struct QEMU_PACKED QCowSnapshotExtraData { |
| + uint64_t vm_state_size_large; |
| + uint64_t disk_size; |
| +} QCowSnapshotExtraData; |
| + |
| + |
| typedef struct QCowSnapshot { |
| uint64_t l1_table_offset; |
| uint32_t l1_size; |
| @@ -202,7 +229,7 @@ typedef struct BDRVQcowState { |
| AES_KEY aes_decrypt_key; |
| uint64_t snapshots_offset; |
| int snapshots_size; |
| - int nb_snapshots; |
| + unsigned int nb_snapshots; |
| QCowSnapshot *snapshots; |
| |
| int flags; |
| diff --git a/tests/qemu-iotests/080 b/tests/qemu-iotests/080 |
| index f58ac73..8a8b460 100755 |
| |
| |
| @@ -47,6 +47,8 @@ header_size=104 |
| offset_backing_file_offset=8 |
| offset_refcount_table_offset=48 |
| offset_refcount_table_clusters=56 |
| +offset_nb_snapshots=60 |
| +offset_snapshots_offset=64 |
| offset_header_size=100 |
| offset_ext_magic=$header_size |
| offset_ext_size=$((header_size + 4)) |
| @@ -90,6 +92,31 @@ poke_file "$TEST_IMG" "$offset_refcount_table_offset" "\xff\xff\xff\xff\xff\xff\ |
| poke_file "$TEST_IMG" "$offset_refcount_table_clusters" "\x00\x00\x00\x7f" |
| { $QEMU_IO -c "read 0 512" $TEST_IMG; } 2>&1 | _filter_qemu_io | _filter_testdir |
| |
| +echo |
| +echo "== Invalid snapshot table ==" |
| +_make_test_img 64M |
| +poke_file "$TEST_IMG" "$offset_nb_snapshots" "\xff\xff\xff\xff" |
| +{ $QEMU_IO -c "read 0 512" $TEST_IMG; } 2>&1 | _filter_qemu_io | _filter_testdir |
| +poke_file "$TEST_IMG" "$offset_nb_snapshots" "\x7f\xff\xff\xff" |
| +{ $QEMU_IO -c "read 0 512" $TEST_IMG; } 2>&1 | _filter_qemu_io | _filter_testdir |
| + |
| +poke_file "$TEST_IMG" "$offset_snapshots_offset" "\xff\xff\xff\xff\xff\xff\x00\x00" |
| +poke_file "$TEST_IMG" "$offset_nb_snapshots" "\x00\x00\xff\xff" |
| +{ $QEMU_IO -c "read 0 512" $TEST_IMG; } 2>&1 | _filter_qemu_io | _filter_testdir |
| + |
| +poke_file "$TEST_IMG" "$offset_snapshots_offset" "\x12\x34\x56\x78\x90\xab\xcd\xef" |
| +poke_file "$TEST_IMG" "$offset_nb_snapshots" "\x00\x00\x00\x00" |
| +{ $QEMU_IO -c "read 0 512" $TEST_IMG; } 2>&1 | _filter_qemu_io | _filter_testdir |
| + |
| +echo |
| +echo "== Hitting snapshot table size limit ==" |
| +_make_test_img 64M |
| +# Put the refcount table in a more or less safe place (16 MB) |
| +poke_file "$TEST_IMG" "$offset_snapshots_offset" "\x00\x00\x00\x00\x01\x00\x00\x00" |
| +poke_file "$TEST_IMG" "$offset_nb_snapshots" "\x00\x01\x00\x00" |
| +{ $QEMU_IMG snapshot -c test $TEST_IMG; } 2>&1 | _filter_testdir |
| +{ $QEMU_IO -c "read 0 512" $TEST_IMG; } 2>&1 | _filter_qemu_io | _filter_testdir |
| + |
| # success, all done |
| echo "*** done" |
| rm -f $seq.full |
| diff --git a/tests/qemu-iotests/080.out b/tests/qemu-iotests/080.out |
| index f919b58..b06f47f 100644 |
| |
| |
| @@ -30,4 +30,21 @@ no file open, try 'help open' |
| Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 |
| qemu-io: can't open device TEST_DIR/t.qcow2: Invalid reference count table offset |
| no file open, try 'help open' |
| + |
| +== Invalid snapshot table == |
| +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 |
| +qemu-io: can't open device TEST_DIR/t.qcow2: Too many snapshots |
| +no file open, try 'help open' |
| +qemu-io: can't open device TEST_DIR/t.qcow2: Too many snapshots |
| +no file open, try 'help open' |
| +qemu-io: can't open device TEST_DIR/t.qcow2: Invalid snapshot table offset |
| +no file open, try 'help open' |
| +qemu-io: can't open device TEST_DIR/t.qcow2: Invalid snapshot table offset |
| +no file open, try 'help open' |
| + |
| +== Hitting snapshot table size limit == |
| +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 |
| +qemu-img: Could not create snapshot 'test': -27 (File too large) |
| +read 512/512 bytes at offset 0 |
| +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
| *** done |
| -- |
| 1.7.1 |
| |