Skip to contentSkip to conversation

qemu-io: check the size of the I/O requests

PATCH Applied Alberto Garcia 3 participants 18 messages Jan 31, 2017 – Feb 3, 2017

lore
Share & tools
lore
#0 lore raw

cover qemu-io: check the size of the I/O requests

Hi,

qemu-io allows arbitrary values (up to SIZE_MAX) for the size of its I/O requests, but QEMU cannot handle anything larger than INT_MAX.

   $ qemu-io -c 'aio_write 0 2G' hd.qcow2
   block/block-backend.c:1035: blk_aio_write_entry:
   Assertion `!rwco->qiov || rwco->qiov->size == acb->bytes' failed.
   $ qemu-io -c 'aio_read 0 1G 1G' hd.qcow2
   block/block-backend.c:1024:
   blk_aio_read_entry: Assertion `rwco->qiov->size == acb->bytes' failed.

This series checks that those values are within range and also adds assertions to qemu_iovec_add() and qemu_iovec_init_external() to detect these cases earlier.

Regards,

Berto

Alberto Garcia (2):
qemu-io: don't allow I/O operations larger than INT_MAX
iov: assert that qiov->size doesn't exceed INT_MAX

 qemu-io-cmds.c | 21 ++++++++++++---------
 util/iov.c     |  7 ++++++-
 2 files changed, 18 insertions(+), 10 deletions(-)
Signature · 1 line
2.11.0
#1 lore raw

PATCH 1/2 qemu-io: don't allow I/O operations larger than INT_MAX

Passing a request size larger than INT_MAX to any of the I/O commands results in an error. While 'read' and 'write' handle the error correctly, 'aio_read' and 'aio_write' hit an assertion:

blk_aio_read_entry: Assertion `rwco->qiov->size == acb->bytes' failed.

The reason is that the QEMU I/O code cannot handle request sizes larger than INT_MAX, so this patch makes qemu-io check that all values are within range.

---
 qemu-io-cmds.c | 21 ++++++++++++---------
 1 file changed, 12 insertions(+), 9 deletions(-)
qemu-io-cmds.c +12 -9
index 95bcde1d88..d806a83076 100644
@@ -388,9 +388,14 @@ create_iovec(BlockBackend *blk, QEMUIOVector *qiov, char **argv, int nr_iov,
             goto fail;
         }
 
-        if (len > SIZE_MAX) {
-            printf("Argument '%s' exceeds maximum size %llu\n", arg,
-                   (unsigned long long)SIZE_MAX);
+        if (len > INT_MAX) {
+            printf("Argument '%s' exceeds maximum size %d\n", arg, INT_MAX);
+            goto fail;
+        }
+
+        if (count > INT_MAX - len) {
+            printf("The total number of bytes exceed the maximum size %d\n",
+                   INT_MAX);
             goto fail;
         }
 
@@ -682,9 +687,8 @@ static int read_f(BlockBackend *blk, int argc, char **argv)
     if (count < 0) {
         print_cvtnum_err(count, argv[optind]);
         return 0;
-    } else if (count > SIZE_MAX) {
-        printf("length cannot exceed %" PRIu64 ", given %s\n",
-               (uint64_t) SIZE_MAX, argv[optind]);
+    } else if (count > INT_MAX) {
+        printf("length cannot exceed %d, given %s\n", INT_MAX, argv[optind]);
         return 0;
     }
 
@@ -1004,9 +1008,8 @@ static int write_f(BlockBackend *blk, int argc, char **argv)
     if (count < 0) {
         print_cvtnum_err(count, argv[optind]);
         return 0;
-    } else if (count > SIZE_MAX) {
-        printf("length cannot exceed %" PRIu64 ", given %s\n",
-               (uint64_t) SIZE_MAX, argv[optind]);
+    } else if (count > INT_MAX) {
+        printf("length cannot exceed %d, given %s\n", INT_MAX, argv[optind]);
         return 0;
     }
 
Signature · 1 line
2.11.0
Reviewed-by on v1 1/2
#2 lore raw

Re: [Qemu-devel] [PATCH 1/2] qemu-io: don't allow I/O operations larger than INT_MAX

Quoted 9 lines · Alberto Garcia

Passing a request size larger than INT_MAX to any of the I/O commands results in an error. While 'read' and 'write' handle the error correctly, 'aio_read' and 'aio_write' hit an assertion:

blk_aio_read_entry: Assertion `rwco->qiov->size == acb->bytes' failed.

The reason is that the QEMU I/O code cannot handle request sizes larger than INT_MAX, so this patch makes qemu-io check that all values are within range.

Ideally, it would be nice to fix the block layer to allow larger requests (since we already have code to auto-fragment down to device limits, we should be able to rely on that code instead of having to duplicate artificial constraints everywhere else in the tree). But that's a bigger task, and this is a good patch in the interim.

Quoted 5 lines
---
 qemu-io-cmds.c | 21 ++++++++++++---------
 1 file changed, 12 insertions(+), 9 deletions(-)
Signature · 2 lines
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org
#3 lore raw

Re: [Qemu-devel] [PATCH 1/2] qemu-io: don't allow I/O operations larger than INT_MAX

Quoted 5 lines · Eric Blake

Ideally, it would be nice to fix the block layer to allow larger requests (since we already have code to auto-fragment down to device limits, we should be able to rely on that code instead of having to duplicate artificial constraints everywhere else in the tree). But that's a bigger task, and this is a good patch in the interim.

Related question: what's the largest request than a guest can theoretically submit?

Berto

#4 lore raw

Re: [Qemu-devel] [PATCH 1/2] qemu-io: don't allow I/O operations larger than INT_MAX

Quoted 10 lines · Alberto Garcia
Quoted 5 lines · Eric Blake

Ideally, it would be nice to fix the block layer to allow larger requests (since we already have code to auto-fragment down to device limits, we should be able to rely on that code instead of having to duplicate artificial constraints everywhere else in the tree). But that's a bigger task, and this is a good patch in the interim.

Related question: what's the largest request than a guest can theoretically submit?

off_t supports up to 2^63 (not 2^64, because it is a signed type). Ideally, we should be constrained only by the disk size (as no one actually has 2^63 bytes of storage available), by using uint64_t offset AND length in all our APIs; but right now, we still have a lot of 32-bit length issues, and often signed length limiting us to 2^31 depending on the API.

Signature · 2 lines
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org
#5 lore raw

Re: [Qemu-devel] [PATCH 1/2] qemu-io: don't allow I/O operations larger than INT_MAX

Quoted 9 lines · Eric Blake
Quoted 8 lines
Quoted 5 lines

Ideally, it would be nice to fix the block layer to allow larger requests (since we already have code to auto-fragment down to device limits, we should be able to rely on that code instead of having to duplicate artificial constraints everywhere else in the tree). But that's a bigger task, and this is a good patch in the interim.

Related question: what's the largest request than a guest can theoretically submit?

Quoted 6 lines

off_t supports up to 2^63 (not 2^64, because it is a signed type). Ideally, we should be constrained only by the disk size (as no one actually has 2^63 bytes of storage available), by using uint64_t offset AND length in all our APIs; but right now, we still have a lot of 32-bit length issues, and often signed length limiting us to 2^31 depending on the API.

My question was more like: what happens if the guest submits a request with the maximum possible size? Does QEMU handle that correctly?

Berto

#6 lore raw

Re: [Qemu-devel] [PATCH 1/2] qemu-io: don't allow I/O operations larger than INT_MAX

Quoted 18 lines · Alberto Garcia
Quoted 9 lines · Eric Blake
Quoted 8 lines
Quoted 5 lines

Ideally, it would be nice to fix the block layer to allow larger requests (since we already have code to auto-fragment down to device limits, we should be able to rely on that code instead of having to duplicate artificial constraints everywhere else in the tree). But that's a bigger task, and this is a good patch in the interim.

Related question: what's the largest request than a guest can theoretically submit?

Quoted 6 lines

off_t supports up to 2^63 (not 2^64, because it is a signed type). Ideally, we should be constrained only by the disk size (as no one actually has 2^63 bytes of storage available), by using uint64_t offset AND length in all our APIs; but right now, we still have a lot of 32-bit length issues, and often signed length limiting us to 2^31 depending on the API.

Quoted 2 lines

My question was more like: what happens if the guest submits a request with the maximum possible size? Does QEMU handle that correctly?

As far as I'm aware we rely on the device emulation code to split the request.

Max

#7 lore raw

Re: [Qemu-devel] [PATCH 1/2] qemu-io: don't allow I/O operations larger than INT_MAX

In reply to Alberto Garcia
Quoted 22 lines · Alberto Garcia

Passing a request size larger than INT_MAX to any of the I/O commands results in an error. While 'read' and 'write' handle the error correctly, 'aio_read' and 'aio_write' hit an assertion:

blk_aio_read_entry: Assertion `rwco->qiov->size == acb->bytes' failed.

The reason is that the QEMU I/O code cannot handle request sizes larger than INT_MAX, so this patch makes qemu-io check that all values are within range.

---
 qemu-io-cmds.c | 21 ++++++++++++---------
 1 file changed, 12 insertions(+), 9 deletions(-)
qemu-io-cmds.c +0 -0
index 95bcde1d88..d806a83076 100644
@@ -388,9 +388,14 @@ create_iovec(BlockBackend *blk, QEMUIOVector *qiov, char **argv, int nr_iov,
             goto fail;
         }

qemu-io-cmds.c create_iovec(BlockBackend *blk, QEMUIOVector *qiov, char **argv, int nr_iov, · line 396

Quoted 10 lines
qemu-io-cmds.c +6 -3
 
-        if (len > SIZE_MAX) {
-            printf("Argument '%s' exceeds maximum size %llu\n", arg,
-                   (unsigned long long)SIZE_MAX);
+        if (len > INT_MAX) {
+            printf("Argument '%s' exceeds maximum size %d\n", arg, INT_MAX);
+            goto fail;
+        }
+
+        if (count > INT_MAX - len) {

How about using BDRV_REQUEST_MAX_BYTES instead?

(not yet in master, just in my block branch)

Max

Quoted 30 lines
qemu-io-cmds.c +6 -6
+            printf("The total number of bytes exceed the maximum size %d\n",
+                   INT_MAX);
             goto fail;
         }
 
@@ -682,9 +687,8 @@ static int read_f(BlockBackend *blk, int argc, char **argv)
     if (count < 0) {
         print_cvtnum_err(count, argv[optind]);
         return 0;
-    } else if (count > SIZE_MAX) {
-        printf("length cannot exceed %" PRIu64 ", given %s\n",
-               (uint64_t) SIZE_MAX, argv[optind]);
+    } else if (count > INT_MAX) {
+        printf("length cannot exceed %d, given %s\n", INT_MAX, argv[optind]);
         return 0;
     }
 
@@ -1004,9 +1008,8 @@ static int write_f(BlockBackend *blk, int argc, char **argv)
     if (count < 0) {
         print_cvtnum_err(count, argv[optind]);
         return 0;
-    } else if (count > SIZE_MAX) {
-        printf("length cannot exceed %" PRIu64 ", given %s\n",
-               (uint64_t) SIZE_MAX, argv[optind]);
+    } else if (count > INT_MAX) {
+        printf("length cannot exceed %d, given %s\n", INT_MAX, argv[optind]);
         return 0;
     }
 
#8 lore raw

Re: [Qemu-devel] [PATCH 1/2] qemu-io: don't allow I/O operations larger than INT_MAX

Quoted 5 lines · Max Reitz
Quoted 1 line

+ if (count > INT_MAX - len) {

How about using BDRV_REQUEST_MAX_BYTES instead?

(not yet in master, just in my block branch)

Sounds good to me, feel free to edit my patch directly.

Berto

Applied
#9 lore raw

Re: [Qemu-devel] [PATCH 1/2] qemu-io: don't allow I/O operations larger than INT_MAX

In reply to Alberto Garcia
Quoted 6 lines · Alberto Garcia

Passing a request size larger than INT_MAX to any of the I/O commands results in an error. While 'read' and 'write' handle the error correctly, 'aio_read' and 'aio_write' hit an assertion:

blk_aio_read_entry: Assertion `rwco->qiov->size == acb->bytes' failed.

Quoted 8 lines

The reason is that the QEMU I/O code cannot handle request sizes larger than INT_MAX, so this patch makes qemu-io check that all values are within range.

---
 qemu-io-cmds.c | 21 ++++++++++++---------
 1 file changed, 12 insertions(+), 9 deletions(-)

Thanks, applied to my block tree, with
%s/INT_MAX/BDRV_REQUEST_MAX_BYTES/g:

https://github.com/XanClic/qemu/commits/block

(Maybe you should take a quick glance on whether all of the changes are really OK for you, the commit currently resides under
https://github.com/XanClic/qemu/commit/beaf099368f91eb44ebd49e863c57bc0ff80659f
)

Max

#10 lore raw

Re: [Qemu-devel] [PATCH 1/2] qemu-io: don't allow I/O operations larger than INT_MAX

Quoted 2 lines · Max Reitz

Thanks, applied to my block tree, with
%s/INT_MAX/BDRV_REQUEST_MAX_BYTES/g:

I think you can use %d to print BDRV_REQUEST_MAX_BYTES, after all the definition guarantees that it won't be larger than MIN(SIZE_MAX,INT_MAX)

But it's fine with me if you prefer to cast it to uint64_t, that way it'll remain valid if BDRV_REQUEST_MAX_BYTES is increased in the future.

Berto

#11 lore raw

Re: [Qemu-devel] [PATCH 1/2] qemu-io: don't allow I/O operations larger than INT_MAX

Quoted 7 lines · Alberto Garcia
Quoted 2 lines · Max Reitz

Thanks, applied to my block tree, with
%s/INT_MAX/BDRV_REQUEST_MAX_BYTES/g:

I think you can use %d to print BDRV_REQUEST_MAX_BYTES, after all the definition guarantees that it won't be larger than MIN(SIZE_MAX,INT_MAX)

I'm not sure what C makes of that expression. I'd think it becomes a size_t, probably, regardless of whether it would fit into an int.

Max

#12 lore raw

PATCH 2/2 iov: assert that qiov->size doesn't exceed INT_MAX

In reply to Alberto Garcia

The type of qiov->size is size_t but the QEMU I/O code uses int all over the place to measure request sizes. Overflows are likely going to be detected by any of the many assert(qiov->size == nbytes), where nbytes is an int, but let's add proper checks in the QEMUIOVector initialization, which is where it belongs.

---
 util/iov.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)
util/iov.c +6 -1
index 74e6ca8ed7..6b5cc9203c 100644
@@ -283,13 +283,18 @@ void qemu_iovec_init_external(QEMUIOVector *qiov, struct iovec *iov, int niov)
     qiov->niov = niov;
     qiov->nalloc = -1;
     qiov->size = 0;
-    for (i = 0; i < niov; i++)
+    for (i = 0; i < niov; i++) {
+        assert(iov[i].iov_len <= INT_MAX);
+        assert(qiov->size <= INT_MAX - iov[i].iov_len);
         qiov->size += iov[i].iov_len;
+    }
 }
 
 void qemu_iovec_add(QEMUIOVector *qiov, void *base, size_t len)
 {
     assert(qiov->nalloc != -1);
+    assert(len <= INT_MAX);
+    assert(qiov->size <= INT_MAX - len);
 
     if (qiov->niov == qiov->nalloc) {
         qiov->nalloc = 2 * qiov->nalloc + 1;
Signature · 1 line
2.11.0
Reviewed-by on v1 2/2
#13 lore raw

Re: [Qemu-devel] [PATCH 2/2] iov: assert that qiov->size doesn't exceed INT_MAX

Quoted 10 lines · Alberto Garcia

The type of qiov->size is size_t but the QEMU I/O code uses int all over the place to measure request sizes. Overflows are likely going to be detected by any of the many assert(qiov->size == nbytes), where nbytes is an int, but let's add proper checks in the QEMUIOVector initialization, which is where it belongs.

---
 util/iov.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)
Signature · 2 lines
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org
#14 lore raw

Re: [Qemu-devel] [PATCH 2/2] iov: assert that qiov->size doesn't exceed INT_MAX

In reply to Alberto Garcia
Quoted 5 lines · Alberto Garcia

The type of qiov->size is size_t but the QEMU I/O code uses int all over the place to measure request sizes. Overflows are likely going to be detected by any of the many assert(qiov->size == nbytes), where nbytes is an int, but let's add proper checks in the QEMUIOVector initialization, which is where it belongs.

I'm not entirely convinced that's where it belongs. In my opinion, the BlockBackend functions should all get uint64_t size parameters and let blk_check_byte_request() do the rest.

I wouldn't mind too much if blk_check_byte_requests() did assertions instead of returning -EIO.

But the thing with the I/O vector is that it's not exclusively used for the block layer. I can see at least hw/9pfs/9p.c and hw/usb/core.c using it internally.

The assertion probably makes sense even for them, considering that size_t does not have a constant size. But I'm not entirely sold that the I/O vector creation is actually the place where the assertions belong.

Max

Quoted 31 lines
---
 util/iov.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)
util/iov.c +6 -1
index 74e6ca8ed7..6b5cc9203c 100644
@@ -283,13 +283,18 @@ void qemu_iovec_init_external(QEMUIOVector *qiov, struct iovec *iov, int niov)
     qiov->niov = niov;
     qiov->nalloc = -1;
     qiov->size = 0;
-    for (i = 0; i < niov; i++)
+    for (i = 0; i < niov; i++) {
+        assert(iov[i].iov_len <= INT_MAX);
+        assert(qiov->size <= INT_MAX - iov[i].iov_len);
         qiov->size += iov[i].iov_len;
+    }
 }
 
 void qemu_iovec_add(QEMUIOVector *qiov, void *base, size_t len)
 {
     assert(qiov->nalloc != -1);
+    assert(len <= INT_MAX);
+    assert(qiov->size <= INT_MAX - len);
 
     if (qiov->niov == qiov->nalloc) {
         qiov->nalloc = 2 * qiov->nalloc + 1;
#15 lore raw

Re: [Qemu-devel] [PATCH 2/2] iov: assert that qiov->size doesn't exceed INT_MAX

Quoted 4 lines · Max Reitz

The assertion probably makes sense even for them, considering that size_t does not have a constant size. But I'm not entirely sold that the I/O vector creation is actually the place where the assertions belong.

I was actually just reading virtio_blk_handle_request() and I see that the I/O vector is initialized before the check for the maximum request size that returns VIRTIO_BLK_S_IOERR.

So you're probably right and it's safer not to add that assertion.

Berto

#16 lore raw

Re: [Qemu-devel] [PATCH 2/2] iov: assert that qiov->size doesn't exceed INT_MAX

Quoted 7 lines · Alberto Garcia
Quoted 4 lines · Max Reitz

The assertion probably makes sense even for them, considering that size_t does not have a constant size. But I'm not entirely sold that the I/O vector creation is actually the place where the assertions belong.

Quoted 5 lines

I was actually just reading virtio_blk_handle_request() and I see that the I/O vector is initialized before the check for the maximum request size that returns VIRTIO_BLK_S_IOERR.

So you're probably right and it's safer not to add that assertion.

So... Apply patch 1 and hope we do everything right in the future? :-)

Max

#17 lore raw

Re: [Qemu-devel] [PATCH 2/2] iov: assert that qiov->size doesn't exceed INT_MAX

Quoted 7 lines · Max Reitz
Quoted 5 lines

I was actually just reading virtio_blk_handle_request() and I see that the I/O vector is initialized before the check for the maximum request size that returns VIRTIO_BLK_S_IOERR.

So you're probably right and it's safer not to add that assertion.

So... Apply patch 1 and hope we do everything right in the future? :-)

I think patch 1 is definitely necessary and enough to solve the crash that I reported.

Tomorrow I'll think about what to do in place of the second one :-)

Berto