diff mbox

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

Message ID 79f66648c685929a144396bda24d13a207131dcf.1485878688.git.berto@igalia.com (mailing list archive)
State New, archived
Headers show

Commit Message

Alberto Garcia Jan. 31, 2017, 4:09 p.m. UTC
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.

Signed-off-by: Alberto Garcia <berto@igalia.com>
---
 qemu-io-cmds.c | 21 ++++++++++++---------
 1 file changed, 12 insertions(+), 9 deletions(-)

Comments

Eric Blake Jan. 31, 2017, 4:31 p.m. UTC | #1
On 01/31/2017 10:09 AM, Alberto Garcia wrote:
> 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.

> 
> Signed-off-by: Alberto Garcia <berto@igalia.com>
> ---
>  qemu-io-cmds.c | 21 ++++++++++++---------
>  1 file changed, 12 insertions(+), 9 deletions(-)

Reviewed-by: Eric Blake <eblake@redhat.com>
Alberto Garcia Jan. 31, 2017, 4:36 p.m. UTC | #2
On Tue 31 Jan 2017 05:31:32 PM CET, Eric Blake wrote:

> 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
Eric Blake Jan. 31, 2017, 4:41 p.m. UTC | #3
On 01/31/2017 10:36 AM, Alberto Garcia wrote:
> On Tue 31 Jan 2017 05:31:32 PM CET, Eric Blake wrote:
> 
>> 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.
Alberto Garcia Jan. 31, 2017, 6:11 p.m. UTC | #4
On Tue 31 Jan 2017 05:41:23 PM CET, Eric Blake <eblake@redhat.com> wrote:

>>> 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.

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

Berto
Max Reitz Jan. 31, 2017, 10:33 p.m. UTC | #5
On 31.01.2017 19:11, Alberto Garcia wrote:
> On Tue 31 Jan 2017 05:41:23 PM CET, Eric Blake <eblake@redhat.com> wrote:
> 
>>>> 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.
> 
> 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
Max Reitz Feb. 1, 2017, 9:36 p.m. UTC | #6
On 31.01.2017 17:09, Alberto Garcia wrote:
> 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.
> 
> Signed-off-by: Alberto Garcia <berto@igalia.com>
> ---
>  qemu-io-cmds.c | 21 ++++++++++++---------
>  1 file changed, 12 insertions(+), 9 deletions(-)
> 
> diff --git a/qemu-io-cmds.c b/qemu-io-cmds.c
> index 95bcde1d88..d806a83076 100644
> --- a/qemu-io-cmds.c
> +++ b/qemu-io-cmds.c
> @@ -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) {

How about using BDRV_REQUEST_MAX_BYTES instead?

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

Max

> +            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;
>      }
>  
>
Alberto Garcia Feb. 1, 2017, 9:49 p.m. UTC | #7
On Wed 01 Feb 2017 10:36:20 PM CET, Max Reitz <mreitz@redhat.com> wrote:
>> +        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
Max Reitz Feb. 1, 2017, 10:16 p.m. UTC | #8
On 31.01.2017 17:09, Alberto Garcia wrote:
> 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.
> 
> Signed-off-by: Alberto Garcia <berto@igalia.com>
> ---
>  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
Alberto Garcia Feb. 2, 2017, 8:52 a.m. UTC | #9
On Wed 01 Feb 2017 11:16:38 PM CET, Max Reitz <mreitz@redhat.com> wrote:

> 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
Max Reitz Feb. 3, 2017, 7 p.m. UTC | #10
On 02.02.2017 09:52, Alberto Garcia wrote:
> On Wed 01 Feb 2017 11:16:38 PM CET, Max Reitz <mreitz@redhat.com> wrote:
> 
>> 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
diff mbox

Patch

diff --git a/qemu-io-cmds.c b/qemu-io-cmds.c
index 95bcde1d88..d806a83076 100644
--- a/qemu-io-cmds.c
+++ b/qemu-io-cmds.c
@@ -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;
     }