Re: [Libguestfs] [libnbd PATCH 1/2] api: Tighter checking of structured read replies

2022-06-01 Thread Eric Blake
On Tue, May 31, 2022 at 10:32:23PM +0300, Nir Soffer wrote:
> > > But we can have this case:
> > >
> > > 1. ask for 32m
> > > 2. server sends 16m (data_seen increase to 16m)
> > > 3. server sends 16m (data_seen increase to 32m)
> > > 4. server sends 1m (data_seen does not increase)
> >
> > Yes it does. 32m <= cmd->count is true, so we bump data_seen to 33m.
> 
> Right! I missed this.
> 
> > Then, later on when retiring the command, we note that 33m != 32m and
> > fail the read with EIO (if it has not already failed for other
> > reasons).
> >
> > > 5. entire request succeeds
> > >
> > > Shouldn't we fail if server sends unexpected data?
> > >
> > > If we detected that all data was received, and we get
> > > unexpected data, why not fail immediately?
> > >
> > > cmd->data_seen += length
> > > if (cmd->data_seen > cmd->count)
> > > switch to dead state?
> >
> > Switching immediately to a dead state is also possible, but it's nice
> > to try and keep the connection alive as long as we can with a nice
> > diagnosis of a failed CMD_READ but still allow further commands,
> > rather than an abrupt disconnect that takes out all other use of the
> > server.
> 
> I agree, this is better.
> 
> Reviewed-by: Nir Soffer 

I've pushed this one as edd8f5c; the rest of the series will be
tweaked and posted as v2.

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.   +1-919-301-3266
Virtualization:  qemu.org | libvirt.org
___
Libguestfs mailing list
Libguestfs@redhat.com
https://listman.redhat.com/mailman/listinfo/libguestfs



Re: [Libguestfs] [libnbd PATCH 1/2] api: Tighter checking of structured read replies

2022-05-31 Thread Nir Soffer
On Tue, May 31, 2022 at 10:23 PM Eric Blake  wrote:
>
> On Tue, May 31, 2022 at 08:40:57PM +0300, Nir Soffer wrote:
> > > > > @@ -364,6 +363,8 @@ STATE_MACHINE {
> > > > >SET_NEXT_STATE (%.DEAD);
> > > > >return 0;
> > > > >  }
> > > > > +if (cmd->data_seen <= cmd->count)
> > > > > +  cmd->data_seen += length;
> > > >
> > > > This does not feel right. if you received more data, it should be 
> > > > counted,
> > > > and if this causes data_seen to be bigger than cmd->count, isn't this a 
> > > > fatal
> > > > error?
> > >
> > > cmd->count is at most 64M; it represents how much we asked the server
> > > to provide.  length was just validated (in the elided statement
> > > between these two hunks) to be <= cmd->count (otherwise, the server is
> > > known-buggy for replying with more than we asked, and we've already
> > > moved to %.DEAD state).  cmd->data_seen is a cumulative count of all
> > > bytes seen in prior chunks, plus the current chunk.  If we have not
> > > yet passed cmd->count, then this chunk counts towards the cumulative
> > > limit (and there is no overflow, since 64M*2 < UINT32_MAX).  If we
> > > have already passed cmd->count (in a previous chunk), then we don't
> > > further increase cmd->count, but we already know that we will fail the
> > > overall read later.  In other words, we can stop incrementing
> > > cmd->data_seen as soon as we know it exceeds cmd->count, and by
> > > stopping early, we still detect server bugs without overflowing
> > > uint32_t.
> >
> > But we can have this case:
> >
> > 1. ask for 32m
> > 2. server sends 16m (data_seen increase to 16m)
> > 3. server sends 16m (data_seen increase to 32m)
> > 4. server sends 1m (data_seen does not increase)
>
> Yes it does. 32m <= cmd->count is true, so we bump data_seen to 33m.

Right! I missed this.

> Then, later on when retiring the command, we note that 33m != 32m and
> fail the read with EIO (if it has not already failed for other
> reasons).
>
> > 5. entire request succeeds
> >
> > Shouldn't we fail if server sends unexpected data?
> >
> > If we detected that all data was received, and we get
> > unexpected data, why not fail immediately?
> >
> > cmd->data_seen += length
> > if (cmd->data_seen > cmd->count)
> > switch to dead state?
>
> Switching immediately to a dead state is also possible, but it's nice
> to try and keep the connection alive as long as we can with a nice
> diagnosis of a failed CMD_READ but still allow further commands,
> rather than an abrupt disconnect that takes out all other use of the
> server.

I agree, this is better.

Reviewed-by: Nir Soffer 

___
Libguestfs mailing list
Libguestfs@redhat.com
https://listman.redhat.com/mailman/listinfo/libguestfs



Re: [Libguestfs] [libnbd PATCH 1/2] api: Tighter checking of structured read replies

2022-05-31 Thread Eric Blake
On Tue, May 31, 2022 at 08:40:57PM +0300, Nir Soffer wrote:
> > > > @@ -364,6 +363,8 @@ STATE_MACHINE {
> > > >SET_NEXT_STATE (%.DEAD);
> > > >return 0;
> > > >  }
> > > > +if (cmd->data_seen <= cmd->count)
> > > > +  cmd->data_seen += length;
> > >
> > > This does not feel right. if you received more data, it should be counted,
> > > and if this causes data_seen to be bigger than cmd->count, isn't this a 
> > > fatal
> > > error?
> >
> > cmd->count is at most 64M; it represents how much we asked the server
> > to provide.  length was just validated (in the elided statement
> > between these two hunks) to be <= cmd->count (otherwise, the server is
> > known-buggy for replying with more than we asked, and we've already
> > moved to %.DEAD state).  cmd->data_seen is a cumulative count of all
> > bytes seen in prior chunks, plus the current chunk.  If we have not
> > yet passed cmd->count, then this chunk counts towards the cumulative
> > limit (and there is no overflow, since 64M*2 < UINT32_MAX).  If we
> > have already passed cmd->count (in a previous chunk), then we don't
> > further increase cmd->count, but we already know that we will fail the
> > overall read later.  In other words, we can stop incrementing
> > cmd->data_seen as soon as we know it exceeds cmd->count, and by
> > stopping early, we still detect server bugs without overflowing
> > uint32_t.
> 
> But we can have this case:
> 
> 1. ask for 32m
> 2. server sends 16m (data_seen increase to 16m)
> 3. server sends 16m (data_seen increase to 32m)
> 4. server sends 1m (data_seen does not increase)

Yes it does. 32m <= cmd->count is true, so we bump data_seen to 33m.

Then, later on when retiring the command, we note that 33m != 32m and
fail the read with EIO (if it has not already failed for other
reasons).

> 5. entire request succeeds
> 
> Shouldn't we fail if server sends unexpected data?
> 
> If we detected that all data was received, and we get
> unexpected data, why not fail immediately?
> 
> cmd->data_seen += length
> if (cmd->data_seen > cmd->count)
> switch to dead state?

Switching immediately to a dead state is also possible, but it's nice
to try and keep the connection alive as long as we can with a nice
diagnosis of a failed CMD_READ but still allow further commands,
rather than an abrupt disconnect that takes out all other use of the
server.

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.   +1-919-301-3266
Virtualization:  qemu.org | libvirt.org
___
Libguestfs mailing list
Libguestfs@redhat.com
https://listman.redhat.com/mailman/listinfo/libguestfs



Re: [Libguestfs] [libnbd PATCH 1/2] api: Tighter checking of structured read replies

2022-05-31 Thread Nir Soffer
On Tue, May 31, 2022 at 7:13 PM Eric Blake  wrote:
>
> On Tue, May 31, 2022 at 06:59:25PM +0300, Nir Soffer wrote:
> > On Tue, May 31, 2022 at 5:15 PM Eric Blake  wrote:
> > >
> > > Now that we allow clients to bypass buffer pre-initialization, it
> > > becomes more important to detect when a buggy server using structured
> > > replies does not send us enough bytes to cover the requested read
> > > size.  Our check is not perfect (a server that duplicates reply chunks
> > > for byte 0 and omits byte 1 gets past our check), but this is a
> > > tighter sanity check so that we are less likely to report a successful
> > > read containing uninitialized memory on a buggy server.
> >
> > Nice!
> >
> > > Because we have a maximum read buffer size of 64M, and first check
> > > that the server's chunk fits bounds, we don't have to worry about
> > > overflowing a uint32_t, even if a server sends enough duplicate
> > > responses that an actual sum would overflow.
> > > ---
>
> > > +++ b/generator/states-reply-structured.c
> > > @@ -354,7 +354,6 @@ STATE_MACHINE {
> > >  assert (cmd); /* guaranteed by CHECK */
> > >
> > >  assert (cmd->data && cmd->type == NBD_CMD_READ);
> > > -cmd->data_seen = true;
> > >
> > >  /* Length of the data following. */
> > >  length -= 8;
> > > @@ -364,6 +363,8 @@ STATE_MACHINE {
> > >SET_NEXT_STATE (%.DEAD);
> > >return 0;
> > >  }
> > > +if (cmd->data_seen <= cmd->count)
> > > +  cmd->data_seen += length;
> >
> > This does not feel right. if you received more data, it should be counted,
> > and if this causes data_seen to be bigger than cmd->count, isn't this a 
> > fatal
> > error?
>
> cmd->count is at most 64M; it represents how much we asked the server
> to provide.  length was just validated (in the elided statement
> between these two hunks) to be <= cmd->count (otherwise, the server is
> known-buggy for replying with more than we asked, and we've already
> moved to %.DEAD state).  cmd->data_seen is a cumulative count of all
> bytes seen in prior chunks, plus the current chunk.  If we have not
> yet passed cmd->count, then this chunk counts towards the cumulative
> limit (and there is no overflow, since 64M*2 < UINT32_MAX).  If we
> have already passed cmd->count (in a previous chunk), then we don't
> further increase cmd->count, but we already know that we will fail the
> overall read later.  In other words, we can stop incrementing
> cmd->data_seen as soon as we know it exceeds cmd->count, and by
> stopping early, we still detect server bugs without overflowing
> uint32_t.

But we can have this case:

1. ask for 32m
2. server sends 16m (data_seen increase to 16m)
3. server sends 16m (data_seen increase to 32m)
4. server sends 1m (data_seen does not increase)
5. entire request succeeds

Shouldn't we fail if server sends unexpected data?

If we detected that all data was received, and we get
unexpected data, why not fail immediately?

cmd->data_seen += length
if (cmd->data_seen > cmd->count)
switch to dead state?

Nir

___
Libguestfs mailing list
Libguestfs@redhat.com
https://listman.redhat.com/mailman/listinfo/libguestfs



Re: [Libguestfs] [libnbd PATCH 1/2] api: Tighter checking of structured read replies

2022-05-31 Thread Eric Blake
On Tue, May 31, 2022 at 06:59:25PM +0300, Nir Soffer wrote:
> On Tue, May 31, 2022 at 5:15 PM Eric Blake  wrote:
> >
> > Now that we allow clients to bypass buffer pre-initialization, it
> > becomes more important to detect when a buggy server using structured
> > replies does not send us enough bytes to cover the requested read
> > size.  Our check is not perfect (a server that duplicates reply chunks
> > for byte 0 and omits byte 1 gets past our check), but this is a
> > tighter sanity check so that we are less likely to report a successful
> > read containing uninitialized memory on a buggy server.
> 
> Nice!
> 
> > Because we have a maximum read buffer size of 64M, and first check
> > that the server's chunk fits bounds, we don't have to worry about
> > overflowing a uint32_t, even if a server sends enough duplicate
> > responses that an actual sum would overflow.
> > ---

> > +++ b/generator/states-reply-structured.c
> > @@ -354,7 +354,6 @@ STATE_MACHINE {
> >  assert (cmd); /* guaranteed by CHECK */
> >
> >  assert (cmd->data && cmd->type == NBD_CMD_READ);
> > -cmd->data_seen = true;
> >
> >  /* Length of the data following. */
> >  length -= 8;
> > @@ -364,6 +363,8 @@ STATE_MACHINE {
> >SET_NEXT_STATE (%.DEAD);
> >return 0;
> >  }
> > +if (cmd->data_seen <= cmd->count)
> > +  cmd->data_seen += length;
> 
> This does not feel right. if you received more data, it should be counted,
> and if this causes data_seen to be bigger than cmd->count, isn't this a fatal
> error?

cmd->count is at most 64M; it represents how much we asked the server
to provide.  length was just validated (in the elided statement
between these two hunks) to be <= cmd->count (otherwise, the server is
known-buggy for replying with more than we asked, and we've already
moved to %.DEAD state).  cmd->data_seen is a cumulative count of all
bytes seen in prior chunks, plus the current chunk.  If we have not
yet passed cmd->count, then this chunk counts towards the cumulative
limit (and there is no overflow, since 64M*2 < UINT32_MAX).  If we
have already passed cmd->count (in a previous chunk), then we don't
further increase cmd->count, but we already know that we will fail the
overall read later.  In other words, we can stop incrementing
cmd->data_seen as soon as we know it exceeds cmd->count, and by
stopping early, we still detect server bugs without overflowing
uint32_t.

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.   +1-919-301-3266
Virtualization:  qemu.org | libvirt.org
___
Libguestfs mailing list
Libguestfs@redhat.com
https://listman.redhat.com/mailman/listinfo/libguestfs



Re: [Libguestfs] [libnbd PATCH 1/2] api: Tighter checking of structured read replies

2022-05-31 Thread Nir Soffer
On Tue, May 31, 2022 at 5:15 PM Eric Blake  wrote:
>
> Now that we allow clients to bypass buffer pre-initialization, it
> becomes more important to detect when a buggy server using structured
> replies does not send us enough bytes to cover the requested read
> size.  Our check is not perfect (a server that duplicates reply chunks
> for byte 0 and omits byte 1 gets past our check), but this is a
> tighter sanity check so that we are less likely to report a successful
> read containing uninitialized memory on a buggy server.

Nice!

> Because we have a maximum read buffer size of 64M, and first check
> that the server's chunk fits bounds, we don't have to worry about
> overflowing a uint32_t, even if a server sends enough duplicate
> responses that an actual sum would overflow.
> ---
>  lib/internal.h  | 2 +-
>  generator/states-reply-simple.c | 4 ++--
>  generator/states-reply-structured.c | 6 --
>  lib/aio.c   | 7 +--
>  4 files changed, 12 insertions(+), 7 deletions(-)
>
> diff --git a/lib/internal.h b/lib/internal.h
> index 885cee1..4121a5c 100644
> --- a/lib/internal.h
> +++ b/lib/internal.h
> @@ -352,8 +352,8 @@ struct command {
>void *data; /* Buffer for read/write */
>struct command_cb cb;
>enum state state; /* State to resume with on next POLLIN */
> -  bool data_seen; /* For read, true if at least one data chunk seen */
>bool initialized; /* For read, true if getting a hole may skip memset */
> +  uint32_t data_seen; /* For read, cumulative size of data chunks seen */
>uint32_t error; /* Local errno value */
>  };
>
> diff --git a/generator/states-reply-simple.c b/generator/states-reply-simple.c
> index 7dc26fd..2a7b9a9 100644
> --- a/generator/states-reply-simple.c
> +++ b/generator/states-reply-simple.c
> @@ -1,5 +1,5 @@
>  /* nbd client library in userspace: state machine
> - * Copyright (C) 2013-2019 Red Hat Inc.
> + * Copyright (C) 2013-2022 Red Hat Inc.
>   *
>   * This library is free software; you can redistribute it and/or
>   * modify it under the terms of the GNU Lesser General Public
> @@ -40,7 +40,7 @@ STATE_MACHINE {
>if (cmd->error == 0 && cmd->type == NBD_CMD_READ) {
>  h->rbuf = cmd->data;
>  h->rlen = cmd->count;
> -cmd->data_seen = true;
> +cmd->data_seen = cmd->count;
>  SET_NEXT_STATE (%RECV_READ_PAYLOAD);
>}
>else {
> diff --git a/generator/states-reply-structured.c 
> b/generator/states-reply-structured.c
> index 12c24f5..cabd543 100644
> --- a/generator/states-reply-structured.c
> +++ b/generator/states-reply-structured.c
> @@ -354,7 +354,6 @@ STATE_MACHINE {
>  assert (cmd); /* guaranteed by CHECK */
>
>  assert (cmd->data && cmd->type == NBD_CMD_READ);
> -cmd->data_seen = true;
>
>  /* Length of the data following. */
>  length -= 8;
> @@ -364,6 +363,8 @@ STATE_MACHINE {
>SET_NEXT_STATE (%.DEAD);
>return 0;
>  }
> +if (cmd->data_seen <= cmd->count)
> +  cmd->data_seen += length;

This does not feel right. if you received more data, it should be counted,
and if this causes data_seen to be bigger than cmd->count, isn't this a fatal
error?

>  /* Now this is the byte offset in the read buffer. */
>  offset -= cmd->offset;
>
> @@ -422,13 +423,14 @@ STATE_MACHINE {
>  assert (cmd); /* guaranteed by CHECK */
>
>  assert (cmd->data && cmd->type == NBD_CMD_READ);
> -cmd->data_seen = true;
>
>  /* Is the data within bounds? */
>  if (! structured_reply_in_bounds (offset, length, cmd)) {
>SET_NEXT_STATE (%.DEAD);
>return 0;
>  }
> +if (cmd->data_seen <= cmd->count)
> +  cmd->data_seen += length;

Same here

>  /* Now this is the byte offset in the read buffer. */
>  offset -= cmd->offset;
>
> diff --git a/lib/aio.c b/lib/aio.c
> index 9744840..dc01f90 100644
> --- a/lib/aio.c
> +++ b/lib/aio.c
> @@ -1,5 +1,5 @@
>  /* NBD client library in userspace
> - * Copyright (C) 2013-2019 Red Hat Inc.
> + * Copyright (C) 2013-2022 Red Hat Inc.
>   *
>   * This library is free software; you can redistribute it and/or
>   * modify it under the terms of the GNU Lesser General Public
> @@ -91,8 +91,11 @@ nbd_unlocked_aio_command_completed (struct nbd_handle *h,
>assert (cmd->type != NBD_CMD_DISC);
>/* The spec states that a 0-length read request is unspecified; but
> * it is easy enough to treat it as successful as an extension.
> +   * Conversely, make sure a server sending structured replies sent
> +   * enough data chunks to cover the overall count (although we do not
> +   * detect if it duplicated some bytes while omitting others).
> */
> -  if (type == NBD_CMD_READ && !cmd->data_seen && cmd->count && !error)
> +  if (type == NBD_CMD_READ && cmd->data_seen != cmd->count && !error)
>  error = EIO;
>
>/* Retire it from the list and free it. */
> --
> 2.36.1
>
> ___
> Libguestfs mailing list
> 

[Libguestfs] [libnbd PATCH 1/2] api: Tighter checking of structured read replies

2022-05-31 Thread Eric Blake
Now that we allow clients to bypass buffer pre-initialization, it
becomes more important to detect when a buggy server using structured
replies does not send us enough bytes to cover the requested read
size.  Our check is not perfect (a server that duplicates reply chunks
for byte 0 and omits byte 1 gets past our check), but this is a
tighter sanity check so that we are less likely to report a successful
read containing uninitialized memory on a buggy server.

Because we have a maximum read buffer size of 64M, and first check
that the server's chunk fits bounds, we don't have to worry about
overflowing a uint32_t, even if a server sends enough duplicate
responses that an actual sum would overflow.
---
 lib/internal.h  | 2 +-
 generator/states-reply-simple.c | 4 ++--
 generator/states-reply-structured.c | 6 --
 lib/aio.c   | 7 +--
 4 files changed, 12 insertions(+), 7 deletions(-)

diff --git a/lib/internal.h b/lib/internal.h
index 885cee1..4121a5c 100644
--- a/lib/internal.h
+++ b/lib/internal.h
@@ -352,8 +352,8 @@ struct command {
   void *data; /* Buffer for read/write */
   struct command_cb cb;
   enum state state; /* State to resume with on next POLLIN */
-  bool data_seen; /* For read, true if at least one data chunk seen */
   bool initialized; /* For read, true if getting a hole may skip memset */
+  uint32_t data_seen; /* For read, cumulative size of data chunks seen */
   uint32_t error; /* Local errno value */
 };

diff --git a/generator/states-reply-simple.c b/generator/states-reply-simple.c
index 7dc26fd..2a7b9a9 100644
--- a/generator/states-reply-simple.c
+++ b/generator/states-reply-simple.c
@@ -1,5 +1,5 @@
 /* nbd client library in userspace: state machine
- * Copyright (C) 2013-2019 Red Hat Inc.
+ * Copyright (C) 2013-2022 Red Hat Inc.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -40,7 +40,7 @@ STATE_MACHINE {
   if (cmd->error == 0 && cmd->type == NBD_CMD_READ) {
 h->rbuf = cmd->data;
 h->rlen = cmd->count;
-cmd->data_seen = true;
+cmd->data_seen = cmd->count;
 SET_NEXT_STATE (%RECV_READ_PAYLOAD);
   }
   else {
diff --git a/generator/states-reply-structured.c 
b/generator/states-reply-structured.c
index 12c24f5..cabd543 100644
--- a/generator/states-reply-structured.c
+++ b/generator/states-reply-structured.c
@@ -354,7 +354,6 @@ STATE_MACHINE {
 assert (cmd); /* guaranteed by CHECK */

 assert (cmd->data && cmd->type == NBD_CMD_READ);
-cmd->data_seen = true;

 /* Length of the data following. */
 length -= 8;
@@ -364,6 +363,8 @@ STATE_MACHINE {
   SET_NEXT_STATE (%.DEAD);
   return 0;
 }
+if (cmd->data_seen <= cmd->count)
+  cmd->data_seen += length;
 /* Now this is the byte offset in the read buffer. */
 offset -= cmd->offset;

@@ -422,13 +423,14 @@ STATE_MACHINE {
 assert (cmd); /* guaranteed by CHECK */

 assert (cmd->data && cmd->type == NBD_CMD_READ);
-cmd->data_seen = true;

 /* Is the data within bounds? */
 if (! structured_reply_in_bounds (offset, length, cmd)) {
   SET_NEXT_STATE (%.DEAD);
   return 0;
 }
+if (cmd->data_seen <= cmd->count)
+  cmd->data_seen += length;
 /* Now this is the byte offset in the read buffer. */
 offset -= cmd->offset;

diff --git a/lib/aio.c b/lib/aio.c
index 9744840..dc01f90 100644
--- a/lib/aio.c
+++ b/lib/aio.c
@@ -1,5 +1,5 @@
 /* NBD client library in userspace
- * Copyright (C) 2013-2019 Red Hat Inc.
+ * Copyright (C) 2013-2022 Red Hat Inc.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -91,8 +91,11 @@ nbd_unlocked_aio_command_completed (struct nbd_handle *h,
   assert (cmd->type != NBD_CMD_DISC);
   /* The spec states that a 0-length read request is unspecified; but
* it is easy enough to treat it as successful as an extension.
+   * Conversely, make sure a server sending structured replies sent
+   * enough data chunks to cover the overall count (although we do not
+   * detect if it duplicated some bytes while omitting others).
*/
-  if (type == NBD_CMD_READ && !cmd->data_seen && cmd->count && !error)
+  if (type == NBD_CMD_READ && cmd->data_seen != cmd->count && !error)
 error = EIO;

   /* Retire it from the list and free it. */
-- 
2.36.1

___
Libguestfs mailing list
Libguestfs@redhat.com
https://listman.redhat.com/mailman/listinfo/libguestfs