Skip to contentSkip to conversation

cutils: Assert in-range base for string-to-integer conversions

PATCH Applied Eric Blake 4 participants 6 messages Dec 6, 2018 – Dec 7, 2018

lore
Share & tools
lore

Eric Blake

author
#0 lore raw

PATCH 1/1 cutils: Assert in-range base for string-to-integer conversions

POSIX states that the value of endptr is unspecified if strtol() fails with EINVAL due to an invalid base argument. Since none of the callers to check_strtox_error() initialized endptr, we could end up propagating uninitialized data back to a caller on error. However, passing an out-of-range base is already a sign of poor programming, so let's just assert that base is in range, at which point check_strtox_error() can be tightened to assert that it is receiving an initialized ep that points somewhere within the caller's original string, regardless of whether strto*() succeeded or failed with ERANGE.

---

Also tested that this does not negatively impact David's pending additions of qemu_strtod{,_finite}(). Thus:
Based-on: <[email protected]>

 util/cutils.c | 8 ++++++++
 1 file changed, 8 insertions(+)
util/cutils.c +8 -0
index 91930d1bbeb..e098debdc0c 100644
@@ -278,6 +278,7 @@ int qemu_strtosz_metric(const char *nptr, const char **end, uint64_t *result)
 static int check_strtox_error(const char *nptr, char *ep,
                               const char **endptr, int libc_errno)
 {
+    assert(ep >= nptr);
     if (endptr) {
         *endptr = ep;
     }
@@ -325,6 +326,7 @@ int qemu_strtoi(const char *nptr, const char **endptr, int base,
     char *ep;
     long long lresult;
 
+    assert((unsigned) base <= 36 && base != 1);
     if (!nptr) {
         if (endptr) {
             *endptr = nptr;
@@ -377,6 +379,7 @@ int qemu_strtoui(const char *nptr, const char **endptr, int base,
     char *ep;
     long long lresult;
 
+    assert((unsigned) base <= 36 && base != 1);
     if (!nptr) {
         if (endptr) {
             *endptr = nptr;
@@ -433,6 +436,7 @@ int qemu_strtol(const char *nptr, const char **endptr, int base,
 {
     char *ep;
 
+    assert((unsigned) base <= 36 && base != 1);
     if (!nptr) {
         if (endptr) {
             *endptr = nptr;
@@ -475,6 +479,7 @@ int qemu_strtoul(const char *nptr, const char **endptr, int base,
 {
     char *ep;
 
+    assert((unsigned) base <= 36 && base != 1);
     if (!nptr) {
         if (endptr) {
             *endptr = nptr;
@@ -502,6 +507,7 @@ int qemu_strtoi64(const char *nptr, const char **endptr, int base,
 {
     char *ep;
 
+    assert((unsigned) base <= 36 && base != 1);
     if (!nptr) {
         if (endptr) {
             *endptr = nptr;
@@ -525,6 +531,7 @@ int qemu_strtou64(const char *nptr, const char **endptr, int base,
 {
     char *ep;
 
+    assert((unsigned) base <= 36 && base != 1);
     if (!nptr) {
         if (endptr) {
             *endptr = nptr;
@@ -657,6 +664,7 @@ int parse_uint(const char *s, unsigned long long *value, char **endptr,
     char *endp = (char *)s;
     unsigned long long val = 0;
 
+    assert((unsigned) base <= 36 && base != 1);
     if (!s) {
         r = -EINVAL;
         goto out;
Signature · 1 line
2.17.2
#1 lore raw

Le 06/12/2018 à 16:18, Eric Blake a écrit :

Quoted 35 lines

POSIX states that the value of endptr is unspecified if strtol() fails with EINVAL due to an invalid base argument. Since none of the callers to check_strtox_error() initialized endptr, we could end up propagating uninitialized data back to a caller on error. However, passing an out-of-range base is already a sign of poor programming, so let's just assert that base is in range, at which point check_strtox_error() can be tightened to assert that it is receiving an initialized ep that points somewhere within the caller's original string, regardless of whether strto*() succeeded or failed with ERANGE.

---

Also tested that this does not negatively impact David's pending additions of qemu_strtod{,_finite}(). Thus:
Based-on: <[email protected]>

 util/cutils.c | 8 ++++++++
 1 file changed, 8 insertions(+)
util/cutils.c +1 -0
index 91930d1bbeb..e098debdc0c 100644
@@ -278,6 +278,7 @@ int qemu_strtosz_metric(const char *nptr, const char **end, uint64_t *result)
 static int check_strtox_error(const char *nptr, char *ep,
                               const char **endptr, int libc_errno)
 {
+    assert(ep >= nptr);
     if (endptr) {
         *endptr = ep;
     }

util/cutils.c int qemu_strtoi(const char *nptr, const char **endptr, int base, · line 329

Quoted 4 lines
util/cutils.c +1 -0
@@ -325,6 +326,7 @@ int qemu_strtoi(const char *nptr, const char **endptr, int base,
     char *ep;
     long long lresult;
 
+    assert((unsigned) base <= 36 && base != 1);

If you want to play with type, I think you can do:

assert((unsigned)(base - 2) <= 34)

Thanks,
Laurent

#2 lore raw

Le 06/12/2018 à 17:40, Laurent Vivier a écrit :

Quoted 39 lines

Le 06/12/2018 à 16:18, Eric Blake a écrit :

Quoted 38 lines

POSIX states that the value of endptr is unspecified if strtol() fails with EINVAL due to an invalid base argument. Since none of the callers to check_strtox_error() initialized endptr, we could end up propagating uninitialized data back to a caller on error. However, passing an out-of-range base is already a sign of poor programming, so let's just assert that base is in range, at which point check_strtox_error() can be tightened to assert that it is receiving an initialized ep that points somewhere within the caller's original string, regardless of whether strto*() succeeded or failed with ERANGE.

---

Also tested that this does not negatively impact David's pending additions of qemu_strtod{,_finite}(). Thus:
Based-on: <[email protected]>

 util/cutils.c | 8 ++++++++
 1 file changed, 8 insertions(+)
util/cutils.c +1 -0
index 91930d1bbeb..e098debdc0c 100644
@@ -278,6 +278,7 @@ int qemu_strtosz_metric(const char *nptr, const char **end, uint64_t *result)
 static int check_strtox_error(const char *nptr, char *ep,
                               const char **endptr, int libc_errno)
 {
+    assert(ep >= nptr);
     if (endptr) {
         *endptr = ep;
     }
@@ -325,6 +326,7 @@ int qemu_strtoi(const char *nptr, const char **endptr, int base,
     char *ep;
     long long lresult;
 
Quoted 5 lines

util/cutils.c int qemu_strtoi(const char *nptr, const char **endptr, int base, · line 329

Quoted 1 line
util/cutils.c +1 -0
+    assert((unsigned) base <= 36 && base != 1);

If you want to play with type, I think you can do:

   assert((unsigned)(base - 2) <= 34)

oops, no, '0' is a valid case. Forgive this...

Laurent

Reviewed-by on v1 1/1
#3 lore raw
In reply to Eric Blake
Quoted 11 lines · Eric Blake

POSIX states that the value of endptr is unspecified if strtol() fails with EINVAL due to an invalid base argument. Since none of the callers to check_strtox_error() initialized endptr, we could end up propagating uninitialized data back to a caller on error. However, passing an out-of-range base is already a sign of poor programming, so let's just assert that base is in range, at which point check_strtox_error() can be tightened to assert that it is receiving an initialized ep that points somewhere within the caller's original string, regardless of whether strto*() succeeded or failed with ERANGE.

Quoted 2 lines
Reviewed-by on v1 1/1
#4 lore raw
In reply to Eric Blake
Quoted 84 lines · Eric Blake

POSIX states that the value of endptr is unspecified if strtol() fails with EINVAL due to an invalid base argument. Since none of the callers to check_strtox_error() initialized endptr, we could end up propagating uninitialized data back to a caller on error. However, passing an out-of-range base is already a sign of poor programming, so let's just assert that base is in range, at which point check_strtox_error() can be tightened to assert that it is receiving an initialized ep that points somewhere within the caller's original string, regardless of whether strto*() succeeded or failed with ERANGE.

---

Also tested that this does not negatively impact David's pending additions of qemu_strtod{,_finite}(). Thus:
Based-on: <[email protected]>

 util/cutils.c | 8 ++++++++
 1 file changed, 8 insertions(+)
util/cutils.c +7 -0
index 91930d1bbeb..e098debdc0c 100644
@@ -278,6 +278,7 @@ int qemu_strtosz_metric(const char *nptr, const char **end, uint64_t *result)
 static int check_strtox_error(const char *nptr, char *ep,
                               const char **endptr, int libc_errno)
 {
+    assert(ep >= nptr);
     if (endptr) {
         *endptr = ep;
     }
@@ -325,6 +326,7 @@ int qemu_strtoi(const char *nptr, const char **endptr, int base,
     char *ep;
     long long lresult;
 
+    assert((unsigned) base <= 36 && base != 1);
     if (!nptr) {
         if (endptr) {
             *endptr = nptr;
@@ -377,6 +379,7 @@ int qemu_strtoui(const char *nptr, const char **endptr, int base,
     char *ep;
     long long lresult;
 
+    assert((unsigned) base <= 36 && base != 1);
     if (!nptr) {
         if (endptr) {
             *endptr = nptr;
@@ -433,6 +436,7 @@ int qemu_strtol(const char *nptr, const char **endptr, int base,
 {
     char *ep;
 
+    assert((unsigned) base <= 36 && base != 1);
     if (!nptr) {
         if (endptr) {
             *endptr = nptr;
@@ -475,6 +479,7 @@ int qemu_strtoul(const char *nptr, const char **endptr, int base,
 {
     char *ep;
 
+    assert((unsigned) base <= 36 && base != 1);
     if (!nptr) {
         if (endptr) {
             *endptr = nptr;
@@ -502,6 +507,7 @@ int qemu_strtoi64(const char *nptr, const char **endptr, int base,
 {
     char *ep;
 
+    assert((unsigned) base <= 36 && base != 1);
     if (!nptr) {
         if (endptr) {
             *endptr = nptr;
@@ -525,6 +531,7 @@ int qemu_strtou64(const char *nptr, const char **endptr, int base,
 {
     char *ep;
 
+    assert((unsigned) base <= 36 && base != 1);
     if (!nptr) {
         if (endptr) {
             *endptr = nptr;

util/cutils.c int parse_uint(const char *s, unsigned long long *value, char **endptr, · line 670

Quoted 7 lines
util/cutils.c +1 -0
@@ -657,6 +664,7 @@ int parse_uint(const char *s, unsigned long long *value, char **endptr,
     char *endp = (char *)s;
     unsigned long long val = 0;
 
+    assert((unsigned) base <= 36 && base != 1);
     if (!s) {
         r = -EINVAL;
         goto out;
#5 lore raw
In reply to Eric Blake

Le 06/12/2018 à 16:18, Eric Blake a écrit :

Quoted 13 lines

POSIX states that the value of endptr is unspecified if strtol() fails with EINVAL due to an invalid base argument. Since none of the callers to check_strtox_error() initialized endptr, we could end up propagating uninitialized data back to a caller on error. However, passing an out-of-range base is already a sign of poor programming, so let's just assert that base is in range, at which point check_strtox_error() can be tightened to assert that it is receiving an initialized ep that points somewhere within the caller's original string, regardless of whether strto*() succeeded or failed with ERANGE.

Quoted 9 lines

---

Also tested that this does not negatively impact David's pending additions of qemu_strtod{,_finite}(). Thus:
Based-on: <[email protected]>

 util/cutils.c | 8 ++++++++
 1 file changed, 8 insertions(+)

Applied to my trivial-patches branch.

Thanks,
Laurent