aboutsummaryrefslogtreecommitdiff
path: root/block
diff options
context:
space:
mode:
authorMax Reitz <mreitz@redhat.com>2019-10-11 17:28:10 +0200
committerMax Reitz <mreitz@redhat.com>2019-10-28 11:54:04 +0100
commit099febf3ac37e8d615e90066e515dd9b1d9bba52 (patch)
tree95f9d3045565458a3d76c629b66accbee2c38e2d /block
parent624143355cb6e4149ec27b9b00088aeb958da31d (diff)
downloadqemu-099febf3ac37e8d615e90066e515dd9b1d9bba52.zip
qemu-099febf3ac37e8d615e90066e515dd9b1d9bba52.tar.gz
qemu-099febf3ac37e8d615e90066e515dd9b1d9bba52.tar.bz2
qcow2: Fix overly long snapshot tables
We currently refuse to open qcow2 images with overly long snapshot tables. This patch makes qemu-img check -r all drop all offending entries past what we deem acceptable. The user cannot choose which snapshots are removed. This is fine because we have chosen the maximum snapshot table size to be so large (64 MB) that it cannot be reasonably reached. If the snapshot table exceeds this size, the image has probably been corrupted in some way; in this case, it is most important to just make the image usable such that the user can copy off at least the active layer. (Also note that the snapshots will be removed only with "-r all", so a plain "check" or "check -r leaks" will not delete any data.) Signed-off-by: Max Reitz <mreitz@redhat.com> Reviewed-by: Eric Blake <eblake@redhat.com> Message-id: 20191011152814.14791-13-mreitz@redhat.com Signed-off-by: Max Reitz <mreitz@redhat.com>
Diffstat (limited to 'block')
-rw-r--r--block/qcow2-snapshot.c88
1 files changed, 78 insertions, 10 deletions
diff --git a/block/qcow2-snapshot.c b/block/qcow2-snapshot.c
index 582eb33..366d9f5 100644
--- a/block/qcow2-snapshot.c
+++ b/block/qcow2-snapshot.c
@@ -29,15 +29,24 @@
#include "qemu/error-report.h"
#include "qemu/cutils.h"
+static void qcow2_free_single_snapshot(BlockDriverState *bs, int i)
+{
+ BDRVQcow2State *s = bs->opaque;
+
+ assert(i >= 0 && i < s->nb_snapshots);
+ g_free(s->snapshots[i].name);
+ g_free(s->snapshots[i].id_str);
+ g_free(s->snapshots[i].unknown_extra_data);
+ memset(&s->snapshots[i], 0, sizeof(s->snapshots[i]));
+}
+
void qcow2_free_snapshots(BlockDriverState *bs)
{
BDRVQcow2State *s = bs->opaque;
int i;
for(i = 0; i < s->nb_snapshots; i++) {
- g_free(s->snapshots[i].name);
- g_free(s->snapshots[i].id_str);
- g_free(s->snapshots[i].unknown_extra_data);
+ qcow2_free_single_snapshot(bs, i);
}
g_free(s->snapshots);
s->snapshots = NULL;
@@ -48,6 +57,14 @@ void qcow2_free_snapshots(BlockDriverState *bs)
* If @repair is true, try to repair a broken snapshot table instead
* of just returning an error:
*
+ * - If the snapshot table was too long, set *nb_clusters_reduced to
+ * the number of snapshots removed off the end.
+ * The caller will update the on-disk nb_snapshots accordingly;
+ * this leaks clusters, but is safe.
+ * (The on-disk information must be updated before
+ * qcow2_check_refcounts(), because that function relies on
+ * s->nb_snapshots to reflect the on-disk value.)
+ *
* - If there were snapshots with too much extra metadata, increment
* *extra_data_dropped for each.
* This requires the caller to eventually rewrite the whole snapshot
@@ -59,6 +76,7 @@ void qcow2_free_snapshots(BlockDriverState *bs)
* extra data.)
*/
static int qcow2_do_read_snapshots(BlockDriverState *bs, bool repair,
+ int *nb_clusters_reduced,
int *extra_data_dropped,
Error **errp)
{
@@ -67,7 +85,7 @@ static int qcow2_do_read_snapshots(BlockDriverState *bs, bool repair,
QCowSnapshotExtraData extra;
QCowSnapshot *sn;
int i, id_str_size, name_size;
- int64_t offset;
+ int64_t offset, pre_sn_offset;
uint64_t table_length = 0;
int ret;
@@ -83,6 +101,7 @@ static int qcow2_do_read_snapshots(BlockDriverState *bs, bool repair,
for(i = 0; i < s->nb_snapshots; i++) {
bool truncate_unknown_extra_data = false;
+ pre_sn_offset = offset;
table_length = ROUND_UP(table_length, 8);
/* Read statically sized part of the snapshot header */
@@ -197,9 +216,31 @@ static int qcow2_do_read_snapshots(BlockDriverState *bs, bool repair,
if (table_length > QCOW_MAX_SNAPSHOTS_SIZE ||
offset - s->snapshots_offset > INT_MAX)
{
- ret = -EFBIG;
- error_setg(errp, "Snapshot table is too big");
- goto fail;
+ if (!repair) {
+ ret = -EFBIG;
+ error_setg(errp, "Snapshot table is too big");
+ error_append_hint(errp, "You can force-remove all %u "
+ "overhanging snapshots with qemu-img check "
+ "-r all\n", s->nb_snapshots - i);
+ goto fail;
+ }
+
+ fprintf(stderr, "Discarding %u overhanging snapshots (snapshot "
+ "table is too big)\n", s->nb_snapshots - i);
+
+ *nb_clusters_reduced += (s->nb_snapshots - i);
+
+ /* Discard current snapshot also */
+ qcow2_free_single_snapshot(bs, i);
+
+ /*
+ * This leaks all the rest of the snapshot table and the
+ * snapshots' clusters, but we run in check -r all mode,
+ * so qcow2_check_refcounts() will take care of it.
+ */
+ s->nb_snapshots = i;
+ offset = pre_sn_offset;
+ break;
}
}
@@ -214,7 +255,7 @@ fail:
int qcow2_read_snapshots(BlockDriverState *bs, Error **errp)
{
- return qcow2_do_read_snapshots(bs, false, NULL, errp);
+ return qcow2_do_read_snapshots(bs, false, NULL, NULL, errp);
}
/* add at the end of the file a new list of snapshots */
@@ -382,6 +423,7 @@ int coroutine_fn qcow2_check_read_snapshot_table(BlockDriverState *bs,
{
BDRVQcow2State *s = bs->opaque;
Error *local_err = NULL;
+ int nb_clusters_reduced = 0;
int extra_data_dropped = 0;
int ret;
struct {
@@ -419,7 +461,8 @@ int coroutine_fn qcow2_check_read_snapshot_table(BlockDriverState *bs,
qemu_co_mutex_unlock(&s->lock);
ret = qcow2_do_read_snapshots(bs, fix & BDRV_FIX_ERRORS,
- &extra_data_dropped, &local_err);
+ &nb_clusters_reduced, &extra_data_dropped,
+ &local_err);
qemu_co_mutex_lock(&s->lock);
if (ret < 0) {
result->check_errors++;
@@ -432,7 +475,32 @@ int coroutine_fn qcow2_check_read_snapshot_table(BlockDriverState *bs,
return ret;
}
- result->corruptions += extra_data_dropped;
+ result->corruptions += nb_clusters_reduced + extra_data_dropped;
+
+ if (nb_clusters_reduced) {
+ /*
+ * Update image header now, because:
+ * (1) qcow2_check_refcounts() relies on s->nb_snapshots to be
+ * the same as what the image header says,
+ * (2) this leaks clusters, but qcow2_check_refcounts() will
+ * fix that.
+ */
+ assert(fix & BDRV_FIX_ERRORS);
+
+ snapshot_table_pointer.nb_snapshots = cpu_to_be32(s->nb_snapshots);
+ ret = bdrv_pwrite_sync(bs->file, offsetof(QCowHeader, nb_snapshots),
+ &snapshot_table_pointer.nb_snapshots,
+ sizeof(snapshot_table_pointer.nb_snapshots));
+ if (ret < 0) {
+ result->check_errors++;
+ fprintf(stderr, "ERROR failed to update the snapshot count in the "
+ "image header: %s\n", strerror(-ret));
+ return ret;
+ }
+
+ result->corruptions_fixed += nb_clusters_reduced;
+ result->corruptions -= nb_clusters_reduced;
+ }
return 0;
}