inet_listen_opts: add error checking
diff mbox

Message ID 1386763230-9202-1-git-send-email-kraxel@redhat.com
State New
Headers show

Commit Message

Gerd Hoffmann Dec. 11, 2013, noon UTC
Don't use atoi() function which doesn't detect errors, switch to
strtol and error out on failures.  Also add a range check while
being at it.

Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
 util/qemu-sockets.c | 16 ++++++++++++++--
 1 file changed, 14 insertions(+), 2 deletions(-)

Comments

Eric Blake Dec. 11, 2013, 11:03 p.m. UTC | #1
On 12/11/2013 05:00 AM, Gerd Hoffmann wrote:
> Don't use atoi() function which doesn't detect errors, switch to
> strtol and error out on failures.  Also add a range check while
> being at it.
> 
> Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
> ---

>      /* lookup */
> -    if (port_offset)
> -        snprintf(port, sizeof(port), "%d", atoi(port) + port_offset);
> +    if (port_offset) {
> +        int baseport;
> +        errno = 0;
> +        baseport = strtol(port, NULL, 10);

Fail #1: Silent truncation on 64-bit platforms.  If the user passed
0x100000000 you will see baseport==0 with no error indication (and
doesn't make it any nicer that a 32-bit platform will do what you wanted
- platform-dependent bugs are nasty).  :(

You need to assign the results of strtol() into a long, then do range
checking before truncating to int.

> +        if (errno != 0) {
> +            error_setg(errp, "can't convert to a number: %s", port);
> +            return -1;
> +        }

Fail #2: You forgot to do validation that a number was actually parsed.
 We hate atoi because atoi("a") is happily 0, but your use of strtol()
still has that bug.  POSIX states that implementations are allowed to
fail with EINVAL when parsing "a", but this failure mode is not required
to give an errno diagnostic.  Your code would reject "a" on glibc, but
accept it on other platforms (system-dependent bugs are nasty).  The
only portable way to ensure that a number was actually parsed and that
there is no garbage in the suffix is to pass in the address of a char*
in the second argument, then validate it against your string to ensure
that enough information was parsed and any suffix is expected.

The rest of this email is generic, and not specifically directed at you
or your patch:

<rant>
WHY is strtol() such a PAINFUL interface to use correctly?  And WHY
can't qemu copy libvirt's lead of writing a SANE wrapper function, and
then mandating that the rest of the code base use the sane wrapper
instead of strtol()?
</rant>
Gerd Hoffmann Dec. 12, 2013, 12:27 p.m. UTC | #2
Hi,

> > +    if (port_offset) {
> > +        int baseport;
> > +        errno = 0;
> > +        baseport = strtol(port, NULL, 10);

> <rant>
> WHY is strtol() such a PAINFUL interface to use correctly?

Crossed my mind too after reading the manpage, which sayed you should
clear errno to reliable detect errors as checking the return value
doesn't cut it.

Your points obviously underline that.

>   And WHY
> can't qemu copy libvirt's lead of writing a SANE wrapper function, and
> then mandating that the rest of the code base use the sane wrapper
> instead of strtol()?
> </rant>

Care to share a pointer to the code?

thanks,
  Gerd
Eric Blake Dec. 12, 2013, 3:50 p.m. UTC | #3
On 12/12/2013 05:27 AM, Gerd Hoffmann wrote:
>   Hi,
> 
>>> +    if (port_offset) {
>>> +        int baseport;
>>> +        errno = 0;
>>> +        baseport = strtol(port, NULL, 10);
> 
>> <rant>
>> WHY is strtol() such a PAINFUL interface to use correctly?
> 
> Crossed my mind too after reading the manpage, which sayed you should
> clear errno to reliable detect errors as checking the return value
> doesn't cut it.
> 
> Your points obviously underline that.
> 
>>   And WHY
>> can't qemu copy libvirt's lead of writing a SANE wrapper function, and
>> then mandating that the rest of the code base use the sane wrapper
>> instead of strtol()?
>> </rant>
> 
> Care to share a pointer to the code?

/* Like strtol, but produce an "int" result, and check more carefully.
   Return 0 upon success;  return -1 to indicate failure.
   When END_PTR is NULL, the byte after the final valid digit must be NUL.
   Otherwise, it's like strtol and lets the caller check any suffix for
   validity.  This function is careful to return -1 when the string S
   represents a number that is not representable as an "int". */
int
virStrToLong_i(char const *s, char **end_ptr, int base, int *result)
{
    long int val;
    char *p;
    int err;

    errno = 0;
    val = strtol(s, &p, base); /* exempt from syntax-check */
    err = (errno || (!end_ptr && *p) || p == s || (int) val != val);
    if (end_ptr)
        *end_ptr = p;
    if (err)
        return -1;
    *result = val;
    return 0;
}

and other variants of virStrToLong_* for parsing into unsigned int,
long, etc.

Libvirt then couples that with a syntax check that gets run during 'make
syntax-check' (or we could even migrate it into 'make check') that
forbids all use of strtol() not on a line with the magic exemption
comment.  Therefore, the number of actual uses of strtol() in the source
code base is limited to just these wrapper functions, and everyone else
gets sane semantics.
Gerd Hoffmann Dec. 13, 2013, 9:57 a.m. UTC | #4
Hi,

> <rant>
> WHY is strtol() such a PAINFUL interface to use correctly?  And WHY
> can't qemu copy libvirt's lead of writing a SANE wrapper function, and
> then mandating that the rest of the code base use the sane wrapper
> instead of strtol()?
> </rant>

Turns out there already is one, just /me didn't know.
So, for the record and the mailing list archives:

util/cutil.c provides parse_uint() and parse_uint_full().

The first returns a pointer to the remaining bits to parse, so you can
inspect the suffix (if any).  The second errors out in case there is
trailing garbage, simliar to the libvirt wrapper in case you pass in
NULL as end_ptr.

cheers,
  Gerd

Patch
diff mbox

diff --git a/util/qemu-sockets.c b/util/qemu-sockets.c
index 6b97dc1..5636510 100644
--- a/util/qemu-sockets.c
+++ b/util/qemu-sockets.c
@@ -133,8 +133,20 @@  int inet_listen_opts(QemuOpts *opts, int port_offset, Error **errp)
         ai.ai_family = PF_INET6;
 
     /* lookup */
-    if (port_offset)
-        snprintf(port, sizeof(port), "%d", atoi(port) + port_offset);
+    if (port_offset) {
+        int baseport;
+        errno = 0;
+        baseport = strtol(port, NULL, 10);
+        if (errno != 0) {
+            error_setg(errp, "can't convert to a number: %s", port);
+            return -1;
+        }
+        if (baseport < 0 || baseport + port_offset > 65535) {
+            error_setg(errp, "port %s out of range", port);
+            return -1;
+        }
+        snprintf(port, sizeof(port), "%d", baseport + port_offset);
+    }
     rc = getaddrinfo(strlen(addr) ? addr : NULL, port, &ai, &res);
     if (rc != 0) {
         error_setg(errp, "address resolution failed for %s:%s: %s", addr, port,