diff mbox series

[v5,05/11] nbd/server: Refactor zero-length option check

Message ID 20171019222637.17890-6-eblake@redhat.com
State New
Headers show
Series nbd minimal structured read | expand

Commit Message

Eric Blake Oct. 19, 2017, 10:26 p.m. UTC
Consolidate the check for a zero-length payload to an option
into a new function, nbd_check_zero_length(); this check will
also be used when introducing support for structured replies.

By sticking a catch-all check at the end of the loop for
processing options, we can simplify several of the intermediate
cases.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 nbd/server.c | 76 +++++++++++++++++++++++++++---------------------------------
 1 file changed, 34 insertions(+), 42 deletions(-)

Comments

Vladimir Sementsov-Ogievskiy Oct. 20, 2017, 8:34 a.m. UTC | #1
20.10.2017 01:26, Eric Blake wrote:
> Consolidate the check for a zero-length payload to an option
> into a new function, nbd_check_zero_length(); this check will
> also be used when introducing support for structured replies.
>
> By sticking a catch-all check at the end of the loop for
> processing options, we can simplify several of the intermediate
> cases.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>

looks like two patches in one, however I'm not against (considering my 
big patches =)).
I've already put an r-b here but suddenly understood a hidden behavior 
change you've made,
which may considered like a bug, see below.

> ---
>   nbd/server.c | 76 +++++++++++++++++++++++++++---------------------------------
>   1 file changed, 34 insertions(+), 42 deletions(-)
>
> diff --git a/nbd/server.c b/nbd/server.c
> index 05ff7470d5..b3f7e0b18e 100644
> --- a/nbd/server.c
> +++ b/nbd/server.c
> @@ -253,21 +253,10 @@ static int nbd_negotiate_send_rep_list(QIOChannel *ioc, NBDExport *exp,
>
>   /* Process the NBD_OPT_LIST command, with a potential series of replies.
>    * Return -errno on error, 0 on success. */
> -static int nbd_negotiate_handle_list(NBDClient *client, uint32_t length,
> -                                     Error **errp)
> +static int nbd_negotiate_handle_list(NBDClient *client, Error **errp)
>   {
>       NBDExport *exp;
>
> -    if (length) {
> -        if (nbd_drop(client->ioc, length, errp) < 0) {
> -            return -EIO;
> -        }
> -        return nbd_negotiate_send_rep_err(client->ioc,
> -                                          NBD_REP_ERR_INVALID, NBD_OPT_LIST,
> -                                          errp,
> -                                          "OPT_LIST should not have length");
> -    }
> -
>       /* For each export, send a NBD_REP_SERVER reply. */
>       QTAILQ_FOREACH(exp, &exports, next) {
>           if (nbd_negotiate_send_rep_list(client->ioc, exp, errp)) {
> @@ -531,7 +520,6 @@ static int nbd_negotiate_handle_info(NBDClient *client, uint32_t length,
>   /* Handle NBD_OPT_STARTTLS. Return NULL to drop connection, or else the
>    * new channel for all further (now-encrypted) communication. */
>   static QIOChannel *nbd_negotiate_handle_starttls(NBDClient *client,
> -                                                 uint32_t length,
>                                                    Error **errp)
>   {
>       QIOChannel *ioc;
> @@ -540,15 +528,6 @@ static QIOChannel *nbd_negotiate_handle_starttls(NBDClient *client,
>
>       trace_nbd_negotiate_handle_starttls();
>       ioc = client->ioc;
> -    if (length) {
> -        if (nbd_drop(ioc, length, errp) < 0) {
> -            return NULL;
> -        }
> -        nbd_negotiate_send_rep_err(ioc, NBD_REP_ERR_INVALID, NBD_OPT_STARTTLS,
> -                                   errp,
> -                                   "OPT_STARTTLS should not have length");
> -        return NULL;
> -    }
>
>       if (nbd_negotiate_send_rep(client->ioc, NBD_REP_ACK,
>                                  NBD_OPT_STARTTLS, errp) < 0) {
> @@ -584,6 +563,25 @@ static QIOChannel *nbd_negotiate_handle_starttls(NBDClient *client,
>       return QIO_CHANNEL(tioc);
>   }
>
> +/* nbd_check_zero_length: Handle any unexpected payload.
> + * Return:
> + * -errno  on error, errp is set
> + * 0       on successful negotiation, errp is not set
> + */
> +static int nbd_check_zero_length(NBDClient *client, uint32_t length,
> +                                 uint32_t option, Error **errp)
> +{
> +    if (!length) {
> +        return 0;
> +    }
> +    if (nbd_drop(client->ioc, length, errp) < 0) {
> +        return -EIO;
> +    }
> +    return nbd_negotiate_send_rep_err(client->ioc, NBD_REP_ERR_INVALID, option,
> +                                      errp, "option %s should have zero length",

may be quotes around %s or your trace-notation %d (%s) would be more 
readable

> +                                      nbd_opt_lookup(option));
> +}
> +
>   /* nbd_negotiate_options
>    * Process all NBD_OPT_* client option commands, during fixed newstyle
>    * negotiation.
> @@ -674,7 +672,11 @@ static int nbd_negotiate_options(NBDClient *client, uint16_t myflags,
>               }
>               switch (option) {
>               case NBD_OPT_STARTTLS:
> -                tioc = nbd_negotiate_handle_starttls(client, length, errp);
> +                ret = nbd_check_zero_length(client, length, option, errp);
> +                if (ret < 0) {
> +                    return ret;
> +                }

no, you should not continue if length>0 (old behavior). 
nbd_negotiate_send_rep_err returns 0 on success
in nbd_check_zero_length().

> +                tioc = nbd_negotiate_handle_starttls(client, errp);
>                   if (!tioc) {
>                       return -EIO;
>                   }
> @@ -698,9 +700,6 @@ static int nbd_negotiate_options(NBDClient *client, uint16_t myflags,
>                                                    "Option 0x%" PRIx32
>                                                    "not permitted before TLS",
>                                                    option);
> -                if (ret < 0) {
> -                    return ret;
> -                }
>                   /* Let the client keep trying, unless they asked to
>                    * quit. In this mode, we've already sent an error, so
>                    * we can't ack the abort.  */
> @@ -712,9 +711,9 @@ static int nbd_negotiate_options(NBDClient *client, uint16_t myflags,
>           } else if (fixedNewstyle) {
>               switch (option) {
>               case NBD_OPT_LIST:
> -                ret = nbd_negotiate_handle_list(client, length, errp);
> -                if (ret < 0) {
> -                    return ret;
> +                ret = nbd_check_zero_length(client, length, option, errp);
> +                if (!ret) {

the same here

> +                    ret = nbd_negotiate_handle_list(client, errp);
>                   }
>                   break;
>
> @@ -738,16 +737,12 @@ static int nbd_negotiate_options(NBDClient *client, uint16_t myflags,
>                       assert(option == NBD_OPT_GO);
>                       return 0;
>                   }
> -                if (ret) {
> -                    return ret;
> -                }
>                   break;
>
>               case NBD_OPT_STARTTLS:
> -                if (nbd_drop(client->ioc, length, errp) < 0) {
> -                    return -EIO;
> -                }
> -                if (client->tlscreds) {
> +                if (length) {
> +                    ret = nbd_check_zero_length(client, length, option, errp);
> +                } else if (client->tlscreds) {
>                       ret = nbd_negotiate_send_rep_err(client->ioc,
>                                                        NBD_REP_ERR_INVALID,
>                                                        option, errp,
> @@ -758,9 +753,6 @@ static int nbd_negotiate_options(NBDClient *client, uint16_t myflags,
>                                                        option, errp,
>                                                        "TLS not configured");
>                   }
> -                if (ret < 0) {
> -                    return ret;
> -                }
>                   break;
>               default:
>                   if (nbd_drop(client->ioc, length, errp) < 0) {
> @@ -772,9 +764,6 @@ static int nbd_negotiate_options(NBDClient *client, uint16_t myflags,
>                                                    "Unsupported option 0x%"
>                                                    PRIx32 " (%s)", option,
>                                                    nbd_opt_lookup(option));
> -                if (ret < 0) {
> -                    return ret;
> -                }
>                   break;
>               }
>           } else {
> @@ -794,6 +783,9 @@ static int nbd_negotiate_options(NBDClient *client, uint16_t myflags,
>                   return -EINVAL;
>               }
>           }
> +        if (ret < 0) {
> +            return ret;
> +        }
>       }
>   }
>
Eric Blake Oct. 20, 2017, 3:07 p.m. UTC | #2
On 10/20/2017 03:34 AM, Vladimir Sementsov-Ogievskiy wrote:
> 20.10.2017 01:26, Eric Blake wrote:
>> Consolidate the check for a zero-length payload to an option
>> into a new function, nbd_check_zero_length(); this check will
>> also be used when introducing support for structured replies.
>>
>> By sticking a catch-all check at the end of the loop for
>> processing options, we can simplify several of the intermediate
>> cases.
>>
>> Signed-off-by: Eric Blake <eblake@redhat.com>
> 
> looks like two patches in one, however I'm not against (considering my
> big patches =)).
> I've already put an r-b here but suddenly understood a hidden behavior
> change you've made,
> which may considered like a bug, see below.
> 

>> +/* nbd_check_zero_length: Handle any unexpected payload.
>> + * Return:
>> + * -errno  on error, errp is set
>> + * 0       on successful negotiation, errp is not set
>> + */
>> +static int nbd_check_zero_length(NBDClient *client, uint32_t length,
>> +                                 uint32_t option, Error **errp)
>> +{
>> +    if (!length) {
>> +        return 0;
>> +    }
>> +    if (nbd_drop(client->ioc, length, errp) < 0) {
>> +        return -EIO;
>> +    }
>> +    return nbd_negotiate_send_rep_err(client->ioc,
>> NBD_REP_ERR_INVALID, option,
>> +                                      errp, "option %s should have
>> zero length",
> 
> may be quotes around %s or your trace-notation %d (%s) would be more
> readable

quotes don't hurt, but since none of the option names contain spaces,
it's not quite as important as when you are quoting a message sent over
the wire.

> 
>> +                                      nbd_opt_lookup(option));
>> +}
>> +
>>   /* nbd_negotiate_options
>>    * Process all NBD_OPT_* client option commands, during fixed newstyle
>>    * negotiation.
>> @@ -674,7 +672,11 @@ static int nbd_negotiate_options(NBDClient
>> *client, uint16_t myflags,
>>               }
>>               switch (option) {
>>               case NBD_OPT_STARTTLS:
>> -                tioc = nbd_negotiate_handle_starttls(client, length,
>> errp);
>> +                ret = nbd_check_zero_length(client, length, option,
>> errp);
>> +                if (ret < 0) {
>> +                    return ret;
>> +                }
> 
> no, you should not continue if length>0 (old behavior).
> nbd_negotiate_send_rep_err returns 0 on success
> in nbd_check_zero_length().

Oh, good catch. But it's subtler than that. In the old code,
nbd_negotiate_handle_starttls() returns NULL on non-zero length (even if
it sent a message to the client), because we really want to kill the
connection if a client can't turn on TLS correctly...

>> @@ -712,9 +711,9 @@ static int nbd_negotiate_options(NBDClient
>> *client, uint16_t myflags,
>>           } else if (fixedNewstyle) {
>>               switch (option) {
>>               case NBD_OPT_LIST:
>> -                ret = nbd_negotiate_handle_list(client, length, errp);
>> -                if (ret < 0) {
>> -                    return ret;
>> +                ret = nbd_check_zero_length(client, length, option,
>> errp);
>> +                if (!ret) {
> 
> the same here
> 

while nbd_negotiate_handle_list() used to return 0 if the client sent
non-zero length (we handled the incorrect message from the client just
fine, and can continue listening for more options).

Maybe I can fix it with a tri-state return: 1 if correct length, 0 if
nonzero length but error message sent successfully, and negative on
transmission failure; although then it's trickier for callers.  I'll
have to think about it...

>>               case NBD_OPT_STARTTLS:
>> -                if (nbd_drop(client->ioc, length, errp) < 0) {
>> -                    return -EIO;
>> -                }
>> -                if (client->tlscreds) {
>> +                if (length) {
>> +                    ret = nbd_check_zero_length(client, length,
>> option, errp);

Maybe explicitly checking for length at each caller is the simplest
approach for getting the decision logic correct, since I really wasn't
able to abstract a clean "failure to communicate" vs. "error message
sent, go on to next message or abort connection as appropriate" vs.
"everything validated, proceed with rest of handing current option".
Vladimir Sementsov-Ogievskiy Oct. 20, 2017, 6:12 p.m. UTC | #3
20.10.2017 18:07, Eric Blake wrote:
> On 10/20/2017 03:34 AM, Vladimir Sementsov-Ogievskiy wrote:

>> 20.10.2017 01:26, Eric Blake wrote:

>>> Consolidate the check for a zero-length payload to an option

>>> into a new function, nbd_check_zero_length(); this check will

>>> also be used when introducing support for structured replies.

>>>

>>> By sticking a catch-all check at the end of the loop for

>>> processing options, we can simplify several of the intermediate

>>> cases.

>>>

>>> Signed-off-by: Eric Blake <eblake@redhat.com>

>> looks like two patches in one, however I'm not against (considering my

>> big patches =)).

>> I've already put an r-b here but suddenly understood a hidden behavior

>> change you've made,

>> which may considered like a bug, see below.

>>

>>> +/* nbd_check_zero_length: Handle any unexpected payload.

>>> + * Return:

>>> + * -errno  on error, errp is set

>>> + * 0       on successful negotiation, errp is not set

>>> + */

>>> +static int nbd_check_zero_length(NBDClient *client, uint32_t length,

>>> +                                 uint32_t option, Error **errp)

>>> +{

>>> +    if (!length) {

>>> +        return 0;

>>> +    }

>>> +    if (nbd_drop(client->ioc, length, errp) < 0) {

>>> +        return -EIO;

>>> +    }

>>> +    return nbd_negotiate_send_rep_err(client->ioc,

>>> NBD_REP_ERR_INVALID, option,

>>> +                                      errp, "option %s should have

>>> zero length",

>> may be quotes around %s or your trace-notation %d (%s) would be more

>> readable

> quotes don't hurt, but since none of the option names contain spaces,


two of them: "export name" and "structured reply"

> it's not quite as important as when you are quoting a message sent over

> the wire.

>

>>> +                                      nbd_opt_lookup(option));

>>> +}

>>> +

>>>    /* nbd_negotiate_options

>>>     * Process all NBD_OPT_* client option commands, during fixed newstyle

>>>     * negotiation.

>>> @@ -674,7 +672,11 @@ static int nbd_negotiate_options(NBDClient

>>> *client, uint16_t myflags,

>>>                }

>>>                switch (option) {

>>>                case NBD_OPT_STARTTLS:

>>> -                tioc = nbd_negotiate_handle_starttls(client, length,

>>> errp);

>>> +                ret = nbd_check_zero_length(client, length, option,

>>> errp);

>>> +                if (ret < 0) {

>>> +                    return ret;

>>> +                }

>> no, you should not continue if length>0 (old behavior).

>> nbd_negotiate_send_rep_err returns 0 on success

>> in nbd_check_zero_length().

> Oh, good catch. But it's subtler than that. In the old code,

> nbd_negotiate_handle_starttls() returns NULL on non-zero length (even if

> it sent a message to the client), because we really want to kill the

> connection if a client can't turn on TLS correctly...

>

>>> @@ -712,9 +711,9 @@ static int nbd_negotiate_options(NBDClient

>>> *client, uint16_t myflags,

>>>            } else if (fixedNewstyle) {

>>>                switch (option) {

>>>                case NBD_OPT_LIST:

>>> -                ret = nbd_negotiate_handle_list(client, length, errp);

>>> -                if (ret < 0) {

>>> -                    return ret;

>>> +                ret = nbd_check_zero_length(client, length, option,

>>> errp);

>>> +                if (!ret) {

>> the same here

>>

> while nbd_negotiate_handle_list() used to return 0 if the client sent

> non-zero length (we handled the incorrect message from the client just

> fine, and can continue listening for more options).

>

> Maybe I can fix it with a tri-state return: 1 if correct length, 0 if

> nonzero length but error message sent successfully, and negative on

> transmission failure; although then it's trickier for callers.  I'll

> have to think about it...

>

>>>                case NBD_OPT_STARTTLS:

>>> -                if (nbd_drop(client->ioc, length, errp) < 0) {

>>> -                    return -EIO;

>>> -                }

>>> -                if (client->tlscreds) {

>>> +                if (length) {

>>> +                    ret = nbd_check_zero_length(client, length,

>>> option, errp);

> Maybe explicitly checking for length at each caller is the simplest

> approach for getting the decision logic correct, since I really wasn't

> able to abstract a clean "failure to communicate" vs. "error message

> sent, go on to next message or abort connection as appropriate" vs.

> "everything validated, proceed with rest of handing current option".

>



-- 
Best regards,
Vladimir
diff mbox series

Patch

diff --git a/nbd/server.c b/nbd/server.c
index 05ff7470d5..b3f7e0b18e 100644
--- a/nbd/server.c
+++ b/nbd/server.c
@@ -253,21 +253,10 @@  static int nbd_negotiate_send_rep_list(QIOChannel *ioc, NBDExport *exp,

 /* Process the NBD_OPT_LIST command, with a potential series of replies.
  * Return -errno on error, 0 on success. */
-static int nbd_negotiate_handle_list(NBDClient *client, uint32_t length,
-                                     Error **errp)
+static int nbd_negotiate_handle_list(NBDClient *client, Error **errp)
 {
     NBDExport *exp;

-    if (length) {
-        if (nbd_drop(client->ioc, length, errp) < 0) {
-            return -EIO;
-        }
-        return nbd_negotiate_send_rep_err(client->ioc,
-                                          NBD_REP_ERR_INVALID, NBD_OPT_LIST,
-                                          errp,
-                                          "OPT_LIST should not have length");
-    }
-
     /* For each export, send a NBD_REP_SERVER reply. */
     QTAILQ_FOREACH(exp, &exports, next) {
         if (nbd_negotiate_send_rep_list(client->ioc, exp, errp)) {
@@ -531,7 +520,6 @@  static int nbd_negotiate_handle_info(NBDClient *client, uint32_t length,
 /* Handle NBD_OPT_STARTTLS. Return NULL to drop connection, or else the
  * new channel for all further (now-encrypted) communication. */
 static QIOChannel *nbd_negotiate_handle_starttls(NBDClient *client,
-                                                 uint32_t length,
                                                  Error **errp)
 {
     QIOChannel *ioc;
@@ -540,15 +528,6 @@  static QIOChannel *nbd_negotiate_handle_starttls(NBDClient *client,

     trace_nbd_negotiate_handle_starttls();
     ioc = client->ioc;
-    if (length) {
-        if (nbd_drop(ioc, length, errp) < 0) {
-            return NULL;
-        }
-        nbd_negotiate_send_rep_err(ioc, NBD_REP_ERR_INVALID, NBD_OPT_STARTTLS,
-                                   errp,
-                                   "OPT_STARTTLS should not have length");
-        return NULL;
-    }

     if (nbd_negotiate_send_rep(client->ioc, NBD_REP_ACK,
                                NBD_OPT_STARTTLS, errp) < 0) {
@@ -584,6 +563,25 @@  static QIOChannel *nbd_negotiate_handle_starttls(NBDClient *client,
     return QIO_CHANNEL(tioc);
 }

+/* nbd_check_zero_length: Handle any unexpected payload.
+ * Return:
+ * -errno  on error, errp is set
+ * 0       on successful negotiation, errp is not set
+ */
+static int nbd_check_zero_length(NBDClient *client, uint32_t length,
+                                 uint32_t option, Error **errp)
+{
+    if (!length) {
+        return 0;
+    }
+    if (nbd_drop(client->ioc, length, errp) < 0) {
+        return -EIO;
+    }
+    return nbd_negotiate_send_rep_err(client->ioc, NBD_REP_ERR_INVALID, option,
+                                      errp, "option %s should have zero length",
+                                      nbd_opt_lookup(option));
+}
+
 /* nbd_negotiate_options
  * Process all NBD_OPT_* client option commands, during fixed newstyle
  * negotiation.
@@ -674,7 +672,11 @@  static int nbd_negotiate_options(NBDClient *client, uint16_t myflags,
             }
             switch (option) {
             case NBD_OPT_STARTTLS:
-                tioc = nbd_negotiate_handle_starttls(client, length, errp);
+                ret = nbd_check_zero_length(client, length, option, errp);
+                if (ret < 0) {
+                    return ret;
+                }
+                tioc = nbd_negotiate_handle_starttls(client, errp);
                 if (!tioc) {
                     return -EIO;
                 }
@@ -698,9 +700,6 @@  static int nbd_negotiate_options(NBDClient *client, uint16_t myflags,
                                                  "Option 0x%" PRIx32
                                                  "not permitted before TLS",
                                                  option);
-                if (ret < 0) {
-                    return ret;
-                }
                 /* Let the client keep trying, unless they asked to
                  * quit. In this mode, we've already sent an error, so
                  * we can't ack the abort.  */
@@ -712,9 +711,9 @@  static int nbd_negotiate_options(NBDClient *client, uint16_t myflags,
         } else if (fixedNewstyle) {
             switch (option) {
             case NBD_OPT_LIST:
-                ret = nbd_negotiate_handle_list(client, length, errp);
-                if (ret < 0) {
-                    return ret;
+                ret = nbd_check_zero_length(client, length, option, errp);
+                if (!ret) {
+                    ret = nbd_negotiate_handle_list(client, errp);
                 }
                 break;

@@ -738,16 +737,12 @@  static int nbd_negotiate_options(NBDClient *client, uint16_t myflags,
                     assert(option == NBD_OPT_GO);
                     return 0;
                 }
-                if (ret) {
-                    return ret;
-                }
                 break;

             case NBD_OPT_STARTTLS:
-                if (nbd_drop(client->ioc, length, errp) < 0) {
-                    return -EIO;
-                }
-                if (client->tlscreds) {
+                if (length) {
+                    ret = nbd_check_zero_length(client, length, option, errp);
+                } else if (client->tlscreds) {
                     ret = nbd_negotiate_send_rep_err(client->ioc,
                                                      NBD_REP_ERR_INVALID,
                                                      option, errp,
@@ -758,9 +753,6 @@  static int nbd_negotiate_options(NBDClient *client, uint16_t myflags,
                                                      option, errp,
                                                      "TLS not configured");
                 }
-                if (ret < 0) {
-                    return ret;
-                }
                 break;
             default:
                 if (nbd_drop(client->ioc, length, errp) < 0) {
@@ -772,9 +764,6 @@  static int nbd_negotiate_options(NBDClient *client, uint16_t myflags,
                                                  "Unsupported option 0x%"
                                                  PRIx32 " (%s)", option,
                                                  nbd_opt_lookup(option));
-                if (ret < 0) {
-                    return ret;
-                }
                 break;
             }
         } else {
@@ -794,6 +783,9 @@  static int nbd_negotiate_options(NBDClient *client, uint16_t myflags,
                 return -EINVAL;
             }
         }
+        if (ret < 0) {
+            return ret;
+        }
     }
 }