All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 00/19] Fix qemu_strtosz() read-out-of-bounds
@ 2023-05-12  2:10 Eric Blake
  2023-05-12  2:10 ` [PATCH v2 01/19] test-cutils: Avoid g_assert in unit tests Eric Blake
                   ` (19 more replies)
  0 siblings, 20 replies; 48+ messages in thread
From: Eric Blake @ 2023-05-12  2:10 UTC (permalink / raw)
  To: qemu-devel; +Cc: hreitz, armbru, richard.henderson

v1 was here:
https://lists.gnu.org/archive/html/qemu-devel/2023-05/msg01988.html

since then:
- make parse_uint easier to use, then use it in qemu_strtosz
- add even more unit tests
- fix a bug in qemu_strtoui
- avoid dereferencing randome memory during unit tests [Hanna]
- other cleanups as I found them
- compress the strtosz unit tests (the major cause of the large
  interdiff statistics)

backport-diff looks like:

001/19:[----] [--] 'test-cutils: Avoid g_assert in unit tests'
002/19:[----] [--] 'test-cutils: Use g_assert_cmpuint where appropriate'
003/19:[----] [--] 'test-cutils: Test integral qemu_strto* value on failures'
004/19:[down] 'test-cutils: Test more integer corner cases'
005/19:[down] 'cutils: Fix wraparound parsing in qemu_strtoui'
006/19:[down] 'cutils: Document differences between parse_uint and qemu_strtou64'
007/19:[down] 'cutils: Adjust signature of parse_uint[_full]'
008/19:[down] 'cutils: Allow NULL endptr in parse_uint()'
009/19:[0147] [FC] 'test-cutils: Add coverage of qemu_strtod'
010/19:[----] [--] 'test-cutils: Prepare for upcoming semantic change in qemu_strtosz'
011/19:[down] 'test-cutils: Refactor qemu_strtosz tests for less boilerplate'
012/19:[down] 'cutils: Allow NULL str in qemu_strtosz'
013/19:[----] [--] 'numa: Check for qemu_strtosz_MiB error'
014/19:[down] 'test-cutils: Add more coverage to qemu_strtosz11;rgb:1e1e/1e1e/1e1e'
015/19:[0178] [FC] 'cutils: Set value in all qemu_strtosz* error paths'
016/19:[----] [--] 'cutils: Set value in all integral qemu_strto* error paths'
017/19:[down] 'cutils: Use parse_uint in qemu_strtosz for negative rejection'
018/19:[0018] [FC] 'cutils: Improve qemu_strtod* error paths'
019/19:[0107] [FC] 'cutils: Improve qemu_strtosz handling of fractions'


Eric Blake (19):
  test-cutils: Avoid g_assert in unit tests
  test-cutils: Use g_assert_cmpuint where appropriate
  test-cutils: Test integral qemu_strto* value on failures
  test-cutils: Test more integer corner cases
  cutils: Fix wraparound parsing in qemu_strtoui
  cutils: Document differences between parse_uint and qemu_strtou64
  cutils: Adjust signature of parse_uint[_full]
  cutils: Allow NULL endptr in parse_uint()
  test-cutils: Add coverage of qemu_strtod
  test-cutils: Prepare for upcoming semantic change in qemu_strtosz
  test-cutils: Refactor qemu_strtosz tests for less boilerplate
  cutils: Allow NULL str in qemu_strtosz
  numa: Check for qemu_strtosz_MiB error
  test-cutils: Add more coverage to qemu_strtosz11;rgb:1e1e/1e1e/1e1e
  cutils: Set value in all qemu_strtosz* error paths
  cutils: Set value in all integral qemu_strto* error paths
  cutils: Use parse_uint in qemu_strtosz for negative rejection
  cutils: Improve qemu_strtod* error paths
  cutils: Improve qemu_strtosz handling of fractions

 include/qemu/cutils.h         |    5 +-
 audio/audio_legacy.c          |    4 +-
 block/gluster.c               |    4 +-
 block/nfs.c                   |    4 +-
 blockdev.c                    |    4 +-
 contrib/ivshmem-server/main.c |    4 +-
 hw/core/numa.c                |   11 +-
 qapi/opts-visitor.c           |   10 +-
 tests/unit/test-cutils.c      | 2340 ++++++++++++++++++++++++---------
 ui/vnc.c                      |    4 +-
 util/cutils.c                 |  251 ++--
 util/guest-random.c           |    4 +-
 util/qemu-sockets.c           |   10 +-
 13 files changed, 1891 insertions(+), 764 deletions(-)


base-commit: 278238505d28d292927bff7683f39fb4fbca7fd1
-- 
2.40.1



^ permalink raw reply	[flat|nested] 48+ messages in thread

* [PATCH v2 01/19] test-cutils: Avoid g_assert in unit tests
  2023-05-12  2:10 [PATCH v2 00/19] Fix qemu_strtosz() read-out-of-bounds Eric Blake
@ 2023-05-12  2:10 ` Eric Blake
  2023-05-12  3:20   ` Philippe Mathieu-Daudé
  2023-05-12 12:11   ` Eric Blake
  2023-05-12  2:10 ` [PATCH v2 02/19] test-cutils: Use g_assert_cmpuint where appropriate Eric Blake
                   ` (18 subsequent siblings)
  19 siblings, 2 replies; 48+ messages in thread
From: Eric Blake @ 2023-05-12  2:10 UTC (permalink / raw)
  To: qemu-devel; +Cc: hreitz, armbru, richard.henderson

glib documentation[1] is clear: g_assert() should be avoided in unit
tests because it is ineffective if G_DISABLE_ASSERT is defined; unit
tests should stick to constructs based on g_assert_true() instead.
Note that since commit 262a69f428, we intentionally state that you
cannot define G_DISABLE_ASSERT that while building qemu; but our code
can be copied to other projects without that restriction, so we should
be consistent.

For most of the replacements in this patch, using g_assert_cmpstr()
would be a regression in quality - although it would helpfully display
the string contents of both pointers on test failure, here, we really
do care about pointer equality, not just string content equality.  But
when a NULL pointer is expected, g_assert_null works fine.

[1] https://libsoup.org/glib/glib-Testing.html#g-assert

Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Hanna Czenczek <hreitz@redhat.com>
---
 tests/unit/test-cutils.c | 324 +++++++++++++++++++--------------------
 1 file changed, 162 insertions(+), 162 deletions(-)

diff --git a/tests/unit/test-cutils.c b/tests/unit/test-cutils.c
index 3c4f8754202..0202ac0d5b3 100644
--- a/tests/unit/test-cutils.c
+++ b/tests/unit/test-cutils.c
@@ -1,7 +1,7 @@
 /*
  * cutils.c unit-tests
  *
- * Copyright (C) 2013 Red Hat Inc.
+ * Copyright Red Hat
  *
  * Authors:
  *  Eduardo Habkost <ehabkost@redhat.com>
@@ -40,7 +40,7 @@ static void test_parse_uint_null(void)

     g_assert_cmpint(r, ==, -EINVAL);
     g_assert_cmpint(i, ==, 0);
-    g_assert(endptr == NULL);
+    g_assert_null(endptr);
 }

 static void test_parse_uint_empty(void)
@@ -55,7 +55,7 @@ static void test_parse_uint_empty(void)

     g_assert_cmpint(r, ==, -EINVAL);
     g_assert_cmpint(i, ==, 0);
-    g_assert(endptr == str);
+    g_assert_true(endptr == str);
 }

 static void test_parse_uint_whitespace(void)
@@ -70,7 +70,7 @@ static void test_parse_uint_whitespace(void)

     g_assert_cmpint(r, ==, -EINVAL);
     g_assert_cmpint(i, ==, 0);
-    g_assert(endptr == str);
+    g_assert_true(endptr == str);
 }


@@ -86,7 +86,7 @@ static void test_parse_uint_invalid(void)

     g_assert_cmpint(r, ==, -EINVAL);
     g_assert_cmpint(i, ==, 0);
-    g_assert(endptr == str);
+    g_assert_true(endptr == str);
 }


@@ -102,7 +102,7 @@ static void test_parse_uint_trailing(void)

     g_assert_cmpint(r, ==, 0);
     g_assert_cmpint(i, ==, 123);
-    g_assert(endptr == str + 3);
+    g_assert_true(endptr == str + 3);
 }

 static void test_parse_uint_correct(void)
@@ -117,7 +117,7 @@ static void test_parse_uint_correct(void)

     g_assert_cmpint(r, ==, 0);
     g_assert_cmpint(i, ==, 123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
 }

 static void test_parse_uint_octal(void)
@@ -132,7 +132,7 @@ static void test_parse_uint_octal(void)

     g_assert_cmpint(r, ==, 0);
     g_assert_cmpint(i, ==, 0123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
 }

 static void test_parse_uint_decimal(void)
@@ -147,7 +147,7 @@ static void test_parse_uint_decimal(void)

     g_assert_cmpint(r, ==, 0);
     g_assert_cmpint(i, ==, 123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
 }


@@ -163,7 +163,7 @@ static void test_parse_uint_llong_max(void)

     g_assert_cmpint(r, ==, 0);
     g_assert_cmpint(i, ==, (unsigned long long)LLONG_MAX + 1);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));

     g_free(str);
 }
@@ -180,7 +180,7 @@ static void test_parse_uint_overflow(void)

     g_assert_cmpint(r, ==, -ERANGE);
     g_assert_cmpint(i, ==, ULLONG_MAX);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
 }

 static void test_parse_uint_negative(void)
@@ -195,7 +195,7 @@ static void test_parse_uint_negative(void)

     g_assert_cmpint(r, ==, -ERANGE);
     g_assert_cmpint(i, ==, 0);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
 }


@@ -235,7 +235,7 @@ static void test_qemu_strtoi_correct(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 12345);
-    g_assert(endptr == str + 5);
+    g_assert_true(endptr == str + 5);
 }

 static void test_qemu_strtoi_null(void)
@@ -248,7 +248,7 @@ static void test_qemu_strtoi_null(void)
     err = qemu_strtoi(NULL, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert(endptr == NULL);
+    g_assert_null(endptr);
 }

 static void test_qemu_strtoi_empty(void)
@@ -262,7 +262,7 @@ static void test_qemu_strtoi_empty(void)
     err = qemu_strtoi(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert(endptr == str);
+    g_assert_true(endptr == str);
 }

 static void test_qemu_strtoi_whitespace(void)
@@ -276,7 +276,7 @@ static void test_qemu_strtoi_whitespace(void)
     err = qemu_strtoi(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert(endptr == str);
+    g_assert_true(endptr == str);
 }

 static void test_qemu_strtoi_invalid(void)
@@ -290,7 +290,7 @@ static void test_qemu_strtoi_invalid(void)
     err = qemu_strtoi(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert(endptr == str);
+    g_assert_true(endptr == str);
 }

 static void test_qemu_strtoi_trailing(void)
@@ -305,7 +305,7 @@ static void test_qemu_strtoi_trailing(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 123);
-    g_assert(endptr == str + 3);
+    g_assert_true(endptr == str + 3);
 }

 static void test_qemu_strtoi_octal(void)
@@ -320,7 +320,7 @@ static void test_qemu_strtoi_octal(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 0123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));

     res = 999;
     endptr = &f;
@@ -328,7 +328,7 @@ static void test_qemu_strtoi_octal(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 0123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
 }

 static void test_qemu_strtoi_decimal(void)
@@ -343,7 +343,7 @@ static void test_qemu_strtoi_decimal(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));

     str = "123";
     res = 999;
@@ -352,7 +352,7 @@ static void test_qemu_strtoi_decimal(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
 }

 static void test_qemu_strtoi_hex(void)
@@ -367,7 +367,7 @@ static void test_qemu_strtoi_hex(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 0x123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));

     str = "0x123";
     res = 999;
@@ -376,7 +376,7 @@ static void test_qemu_strtoi_hex(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 0x123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));

     str = "0x";
     res = 999;
@@ -385,7 +385,7 @@ static void test_qemu_strtoi_hex(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 0);
-    g_assert(endptr == str + 1);
+    g_assert_true(endptr == str + 1);
 }

 static void test_qemu_strtoi_max(void)
@@ -400,7 +400,7 @@ static void test_qemu_strtoi_max(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, INT_MAX);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
     g_free(str);
 }

@@ -416,7 +416,7 @@ static void test_qemu_strtoi_overflow(void)

     g_assert_cmpint(err, ==, -ERANGE);
     g_assert_cmpint(res, ==, INT_MAX);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
     g_free(str);
 }

@@ -432,7 +432,7 @@ static void test_qemu_strtoi_underflow(void)

     g_assert_cmpint(err, ==, -ERANGE);
     g_assert_cmpint(res, ==, INT_MIN);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
     g_free(str);
 }

@@ -448,7 +448,7 @@ static void test_qemu_strtoi_negative(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, -321);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
 }

 static void test_qemu_strtoi_full_correct(void)
@@ -473,7 +473,7 @@ static void test_qemu_strtoi_full_null(void)
     err = qemu_strtoi(NULL, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert(endptr == NULL);
+    g_assert_null(endptr);
 }

 static void test_qemu_strtoi_full_empty(void)
@@ -535,7 +535,7 @@ static void test_qemu_strtoui_correct(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpuint(res, ==, 12345);
-    g_assert(endptr == str + 5);
+    g_assert_true(endptr == str + 5);
 }

 static void test_qemu_strtoui_null(void)
@@ -548,7 +548,7 @@ static void test_qemu_strtoui_null(void)
     err = qemu_strtoui(NULL, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert(endptr == NULL);
+    g_assert_null(endptr);
 }

 static void test_qemu_strtoui_empty(void)
@@ -562,7 +562,7 @@ static void test_qemu_strtoui_empty(void)
     err = qemu_strtoui(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert(endptr == str);
+    g_assert_true(endptr == str);
 }

 static void test_qemu_strtoui_whitespace(void)
@@ -576,7 +576,7 @@ static void test_qemu_strtoui_whitespace(void)
     err = qemu_strtoui(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert(endptr == str);
+    g_assert_true(endptr == str);
 }

 static void test_qemu_strtoui_invalid(void)
@@ -590,7 +590,7 @@ static void test_qemu_strtoui_invalid(void)
     err = qemu_strtoui(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert(endptr == str);
+    g_assert_true(endptr == str);
 }

 static void test_qemu_strtoui_trailing(void)
@@ -605,7 +605,7 @@ static void test_qemu_strtoui_trailing(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpuint(res, ==, 123);
-    g_assert(endptr == str + 3);
+    g_assert_true(endptr == str + 3);
 }

 static void test_qemu_strtoui_octal(void)
@@ -620,7 +620,7 @@ static void test_qemu_strtoui_octal(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpuint(res, ==, 0123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));

     res = 999;
     endptr = &f;
@@ -628,7 +628,7 @@ static void test_qemu_strtoui_octal(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpuint(res, ==, 0123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
 }

 static void test_qemu_strtoui_decimal(void)
@@ -643,7 +643,7 @@ static void test_qemu_strtoui_decimal(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpuint(res, ==, 123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));

     str = "123";
     res = 999;
@@ -652,7 +652,7 @@ static void test_qemu_strtoui_decimal(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpuint(res, ==, 123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
 }

 static void test_qemu_strtoui_hex(void)
@@ -667,7 +667,7 @@ static void test_qemu_strtoui_hex(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmphex(res, ==, 0x123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));

     str = "0x123";
     res = 999;
@@ -676,7 +676,7 @@ static void test_qemu_strtoui_hex(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmphex(res, ==, 0x123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));

     str = "0x";
     res = 999;
@@ -685,7 +685,7 @@ static void test_qemu_strtoui_hex(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmphex(res, ==, 0);
-    g_assert(endptr == str + 1);
+    g_assert_true(endptr == str + 1);
 }

 static void test_qemu_strtoui_max(void)
@@ -700,7 +700,7 @@ static void test_qemu_strtoui_max(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmphex(res, ==, UINT_MAX);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
     g_free(str);
 }

@@ -716,7 +716,7 @@ static void test_qemu_strtoui_overflow(void)

     g_assert_cmpint(err, ==, -ERANGE);
     g_assert_cmphex(res, ==, UINT_MAX);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
     g_free(str);
 }

@@ -732,7 +732,7 @@ static void test_qemu_strtoui_underflow(void)

     g_assert_cmpint(err, ==, -ERANGE);
     g_assert_cmpuint(res, ==, (unsigned int)-1);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
     g_free(str);
 }

@@ -748,7 +748,7 @@ static void test_qemu_strtoui_negative(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpuint(res, ==, (unsigned int)-321);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
 }

 static void test_qemu_strtoui_full_correct(void)
@@ -830,7 +830,7 @@ static void test_qemu_strtol_correct(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 12345);
-    g_assert(endptr == str + 5);
+    g_assert_true(endptr == str + 5);
 }

 static void test_qemu_strtol_null(void)
@@ -843,7 +843,7 @@ static void test_qemu_strtol_null(void)
     err = qemu_strtol(NULL, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert(endptr == NULL);
+    g_assert_null(endptr);
 }

 static void test_qemu_strtol_empty(void)
@@ -857,7 +857,7 @@ static void test_qemu_strtol_empty(void)
     err = qemu_strtol(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert(endptr == str);
+    g_assert_true(endptr == str);
 }

 static void test_qemu_strtol_whitespace(void)
@@ -871,7 +871,7 @@ static void test_qemu_strtol_whitespace(void)
     err = qemu_strtol(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert(endptr == str);
+    g_assert_true(endptr == str);
 }

 static void test_qemu_strtol_invalid(void)
@@ -885,7 +885,7 @@ static void test_qemu_strtol_invalid(void)
     err = qemu_strtol(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert(endptr == str);
+    g_assert_true(endptr == str);
 }

 static void test_qemu_strtol_trailing(void)
@@ -900,7 +900,7 @@ static void test_qemu_strtol_trailing(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 123);
-    g_assert(endptr == str + 3);
+    g_assert_true(endptr == str + 3);
 }

 static void test_qemu_strtol_octal(void)
@@ -915,7 +915,7 @@ static void test_qemu_strtol_octal(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 0123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));

     res = 999;
     endptr = &f;
@@ -923,7 +923,7 @@ static void test_qemu_strtol_octal(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 0123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
 }

 static void test_qemu_strtol_decimal(void)
@@ -938,7 +938,7 @@ static void test_qemu_strtol_decimal(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));

     str = "123";
     res = 999;
@@ -947,7 +947,7 @@ static void test_qemu_strtol_decimal(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
 }

 static void test_qemu_strtol_hex(void)
@@ -962,7 +962,7 @@ static void test_qemu_strtol_hex(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 0x123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));

     str = "0x123";
     res = 999;
@@ -971,7 +971,7 @@ static void test_qemu_strtol_hex(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 0x123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));

     str = "0x";
     res = 999;
@@ -980,7 +980,7 @@ static void test_qemu_strtol_hex(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 0);
-    g_assert(endptr == str + 1);
+    g_assert_true(endptr == str + 1);
 }

 static void test_qemu_strtol_max(void)
@@ -995,7 +995,7 @@ static void test_qemu_strtol_max(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, LONG_MAX);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
     g_free(str);
 }

@@ -1011,7 +1011,7 @@ static void test_qemu_strtol_overflow(void)

     g_assert_cmpint(err, ==, -ERANGE);
     g_assert_cmpint(res, ==, LONG_MAX);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
 }

 static void test_qemu_strtol_underflow(void)
@@ -1026,7 +1026,7 @@ static void test_qemu_strtol_underflow(void)

     g_assert_cmpint(err, ==, -ERANGE);
     g_assert_cmpint(res, ==, LONG_MIN);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
 }

 static void test_qemu_strtol_negative(void)
@@ -1041,7 +1041,7 @@ static void test_qemu_strtol_negative(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, -321);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
 }

 static void test_qemu_strtol_full_correct(void)
@@ -1066,7 +1066,7 @@ static void test_qemu_strtol_full_null(void)
     err = qemu_strtol(NULL, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert(endptr == NULL);
+    g_assert_null(endptr);
 }

 static void test_qemu_strtol_full_empty(void)
@@ -1128,7 +1128,7 @@ static void test_qemu_strtoul_correct(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpuint(res, ==, 12345);
-    g_assert(endptr == str + 5);
+    g_assert_true(endptr == str + 5);
 }

 static void test_qemu_strtoul_null(void)
@@ -1141,7 +1141,7 @@ static void test_qemu_strtoul_null(void)
     err = qemu_strtoul(NULL, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert(endptr == NULL);
+    g_assert_null(endptr);
 }

 static void test_qemu_strtoul_empty(void)
@@ -1155,7 +1155,7 @@ static void test_qemu_strtoul_empty(void)
     err = qemu_strtoul(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert(endptr == str);
+    g_assert_true(endptr == str);
 }

 static void test_qemu_strtoul_whitespace(void)
@@ -1169,7 +1169,7 @@ static void test_qemu_strtoul_whitespace(void)
     err = qemu_strtoul(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert(endptr == str);
+    g_assert_true(endptr == str);
 }

 static void test_qemu_strtoul_invalid(void)
@@ -1183,7 +1183,7 @@ static void test_qemu_strtoul_invalid(void)
     err = qemu_strtoul(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert(endptr == str);
+    g_assert_true(endptr == str);
 }

 static void test_qemu_strtoul_trailing(void)
@@ -1198,7 +1198,7 @@ static void test_qemu_strtoul_trailing(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpuint(res, ==, 123);
-    g_assert(endptr == str + 3);
+    g_assert_true(endptr == str + 3);
 }

 static void test_qemu_strtoul_octal(void)
@@ -1213,7 +1213,7 @@ static void test_qemu_strtoul_octal(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpuint(res, ==, 0123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));

     res = 999;
     endptr = &f;
@@ -1221,7 +1221,7 @@ static void test_qemu_strtoul_octal(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpuint(res, ==, 0123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
 }

 static void test_qemu_strtoul_decimal(void)
@@ -1236,7 +1236,7 @@ static void test_qemu_strtoul_decimal(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpuint(res, ==, 123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));

     str = "123";
     res = 999;
@@ -1245,7 +1245,7 @@ static void test_qemu_strtoul_decimal(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpuint(res, ==, 123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
 }

 static void test_qemu_strtoul_hex(void)
@@ -1260,7 +1260,7 @@ static void test_qemu_strtoul_hex(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmphex(res, ==, 0x123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));

     str = "0x123";
     res = 999;
@@ -1269,7 +1269,7 @@ static void test_qemu_strtoul_hex(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmphex(res, ==, 0x123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));

     str = "0x";
     res = 999;
@@ -1278,7 +1278,7 @@ static void test_qemu_strtoul_hex(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmphex(res, ==, 0);
-    g_assert(endptr == str + 1);
+    g_assert_true(endptr == str + 1);
 }

 static void test_qemu_strtoul_max(void)
@@ -1293,7 +1293,7 @@ static void test_qemu_strtoul_max(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmphex(res, ==, ULONG_MAX);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
     g_free(str);
 }

@@ -1309,7 +1309,7 @@ static void test_qemu_strtoul_overflow(void)

     g_assert_cmpint(err, ==, -ERANGE);
     g_assert_cmphex(res, ==, ULONG_MAX);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
 }

 static void test_qemu_strtoul_underflow(void)
@@ -1324,7 +1324,7 @@ static void test_qemu_strtoul_underflow(void)

     g_assert_cmpint(err, ==, -ERANGE);
     g_assert_cmpuint(res, ==, -1ul);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
 }

 static void test_qemu_strtoul_negative(void)
@@ -1339,7 +1339,7 @@ static void test_qemu_strtoul_negative(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpuint(res, ==, -321ul);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
 }

 static void test_qemu_strtoul_full_correct(void)
@@ -1421,7 +1421,7 @@ static void test_qemu_strtoi64_correct(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 12345);
-    g_assert(endptr == str + 5);
+    g_assert_true(endptr == str + 5);
 }

 static void test_qemu_strtoi64_null(void)
@@ -1434,7 +1434,7 @@ static void test_qemu_strtoi64_null(void)
     err = qemu_strtoi64(NULL, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert(endptr == NULL);
+    g_assert_null(endptr);
 }

 static void test_qemu_strtoi64_empty(void)
@@ -1448,7 +1448,7 @@ static void test_qemu_strtoi64_empty(void)
     err = qemu_strtoi64(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert(endptr == str);
+    g_assert_true(endptr == str);
 }

 static void test_qemu_strtoi64_whitespace(void)
@@ -1462,7 +1462,7 @@ static void test_qemu_strtoi64_whitespace(void)
     err = qemu_strtoi64(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert(endptr == str);
+    g_assert_true(endptr == str);
 }

 static void test_qemu_strtoi64_invalid(void)
@@ -1476,7 +1476,7 @@ static void test_qemu_strtoi64_invalid(void)
     err = qemu_strtoi64(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert(endptr == str);
+    g_assert_true(endptr == str);
 }

 static void test_qemu_strtoi64_trailing(void)
@@ -1491,7 +1491,7 @@ static void test_qemu_strtoi64_trailing(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 123);
-    g_assert(endptr == str + 3);
+    g_assert_true(endptr == str + 3);
 }

 static void test_qemu_strtoi64_octal(void)
@@ -1506,7 +1506,7 @@ static void test_qemu_strtoi64_octal(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 0123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));

     endptr = &f;
     res = 999;
@@ -1514,7 +1514,7 @@ static void test_qemu_strtoi64_octal(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 0123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
 }

 static void test_qemu_strtoi64_decimal(void)
@@ -1529,7 +1529,7 @@ static void test_qemu_strtoi64_decimal(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));

     str = "123";
     endptr = &f;
@@ -1538,7 +1538,7 @@ static void test_qemu_strtoi64_decimal(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
 }

 static void test_qemu_strtoi64_hex(void)
@@ -1553,7 +1553,7 @@ static void test_qemu_strtoi64_hex(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 0x123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));

     str = "0x123";
     endptr = &f;
@@ -1562,7 +1562,7 @@ static void test_qemu_strtoi64_hex(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 0x123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));

     str = "0x";
     endptr = &f;
@@ -1571,7 +1571,7 @@ static void test_qemu_strtoi64_hex(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 0);
-    g_assert(endptr == str + 1);
+    g_assert_true(endptr == str + 1);
 }

 static void test_qemu_strtoi64_max(void)
@@ -1586,7 +1586,7 @@ static void test_qemu_strtoi64_max(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, LLONG_MAX);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
     g_free(str);
 }

@@ -1602,7 +1602,7 @@ static void test_qemu_strtoi64_overflow(void)

     g_assert_cmpint(err, ==, -ERANGE);
     g_assert_cmpint(res, ==, LLONG_MAX);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
 }

 static void test_qemu_strtoi64_underflow(void)
@@ -1617,7 +1617,7 @@ static void test_qemu_strtoi64_underflow(void)

     g_assert_cmpint(err, ==, -ERANGE);
     g_assert_cmpint(res, ==, LLONG_MIN);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
 }

 static void test_qemu_strtoi64_negative(void)
@@ -1632,7 +1632,7 @@ static void test_qemu_strtoi64_negative(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, -321);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
 }

 static void test_qemu_strtoi64_full_correct(void)
@@ -1717,7 +1717,7 @@ static void test_qemu_strtou64_correct(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpuint(res, ==, 12345);
-    g_assert(endptr == str + 5);
+    g_assert_true(endptr == str + 5);
 }

 static void test_qemu_strtou64_null(void)
@@ -1730,7 +1730,7 @@ static void test_qemu_strtou64_null(void)
     err = qemu_strtou64(NULL, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert(endptr == NULL);
+    g_assert_null(endptr);
 }

 static void test_qemu_strtou64_empty(void)
@@ -1744,7 +1744,7 @@ static void test_qemu_strtou64_empty(void)
     err = qemu_strtou64(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert(endptr == str);
+    g_assert_true(endptr == str);
 }

 static void test_qemu_strtou64_whitespace(void)
@@ -1758,7 +1758,7 @@ static void test_qemu_strtou64_whitespace(void)
     err = qemu_strtou64(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert(endptr == str);
+    g_assert_true(endptr == str);
 }

 static void test_qemu_strtou64_invalid(void)
@@ -1772,7 +1772,7 @@ static void test_qemu_strtou64_invalid(void)
     err = qemu_strtou64(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert(endptr == str);
+    g_assert_true(endptr == str);
 }

 static void test_qemu_strtou64_trailing(void)
@@ -1787,7 +1787,7 @@ static void test_qemu_strtou64_trailing(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpuint(res, ==, 123);
-    g_assert(endptr == str + 3);
+    g_assert_true(endptr == str + 3);
 }

 static void test_qemu_strtou64_octal(void)
@@ -1802,7 +1802,7 @@ static void test_qemu_strtou64_octal(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpuint(res, ==, 0123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));

     endptr = &f;
     res = 999;
@@ -1810,7 +1810,7 @@ static void test_qemu_strtou64_octal(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpuint(res, ==, 0123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
 }

 static void test_qemu_strtou64_decimal(void)
@@ -1825,7 +1825,7 @@ static void test_qemu_strtou64_decimal(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpuint(res, ==, 123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));

     str = "123";
     endptr = &f;
@@ -1834,7 +1834,7 @@ static void test_qemu_strtou64_decimal(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpuint(res, ==, 123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
 }

 static void test_qemu_strtou64_hex(void)
@@ -1849,7 +1849,7 @@ static void test_qemu_strtou64_hex(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmphex(res, ==, 0x123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));

     str = "0x123";
     endptr = &f;
@@ -1858,7 +1858,7 @@ static void test_qemu_strtou64_hex(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmphex(res, ==, 0x123);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));

     str = "0x";
     endptr = &f;
@@ -1867,7 +1867,7 @@ static void test_qemu_strtou64_hex(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmphex(res, ==, 0);
-    g_assert(endptr == str + 1);
+    g_assert_true(endptr == str + 1);
 }

 static void test_qemu_strtou64_max(void)
@@ -1882,7 +1882,7 @@ static void test_qemu_strtou64_max(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmphex(res, ==, ULLONG_MAX);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
     g_free(str);
 }

@@ -1898,7 +1898,7 @@ static void test_qemu_strtou64_overflow(void)

     g_assert_cmpint(err, ==, -ERANGE);
     g_assert_cmphex(res, ==, ULLONG_MAX);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
 }

 static void test_qemu_strtou64_underflow(void)
@@ -1913,7 +1913,7 @@ static void test_qemu_strtou64_underflow(void)

     g_assert_cmpint(err, ==, -ERANGE);
     g_assert_cmphex(res, ==, -1ull);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
 }

 static void test_qemu_strtou64_negative(void)
@@ -1928,7 +1928,7 @@ static void test_qemu_strtou64_negative(void)

     g_assert_cmpint(err, ==, 0);
     g_assert_cmpuint(res, ==, -321ull);
-    g_assert(endptr == str + strlen(str));
+    g_assert_true(endptr == str + strlen(str));
 }

 static void test_qemu_strtou64_full_correct(void)
@@ -2013,7 +2013,7 @@ static void test_qemu_strtosz_simple(void)
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 0);
-    g_assert(endptr == str + 1);
+    g_assert_true(endptr == str + 1);

     /* Leading 0 gives decimal results, not octal */
     str = "08";
@@ -2022,7 +2022,7 @@ static void test_qemu_strtosz_simple(void)
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 8);
-    g_assert(endptr == str + 2);
+    g_assert_true(endptr == str + 2);

     /* Leading space is ignored */
     str = " 12345";
@@ -2031,7 +2031,7 @@ static void test_qemu_strtosz_simple(void)
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 12345);
-    g_assert(endptr == str + 6);
+    g_assert_true(endptr == str + 6);

     res = 0xbaadf00d;
     err = qemu_strtosz(str, NULL, &res);
@@ -2044,7 +2044,7 @@ static void test_qemu_strtosz_simple(void)
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 0x1fffffffffffff);
-    g_assert(endptr == str + 16);
+    g_assert_true(endptr == str + 16);

     str = "9007199254740992"; /* 2^53 */
     endptr = str;
@@ -2052,7 +2052,7 @@ static void test_qemu_strtosz_simple(void)
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 0x20000000000000);
-    g_assert(endptr == str + 16);
+    g_assert_true(endptr == str + 16);

     str = "9007199254740993"; /* 2^53+1 */
     endptr = str;
@@ -2060,7 +2060,7 @@ static void test_qemu_strtosz_simple(void)
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 0x20000000000001);
-    g_assert(endptr == str + 16);
+    g_assert_true(endptr == str + 16);

     str = "18446744073709549568"; /* 0xfffffffffffff800 (53 msbs set) */
     endptr = str;
@@ -2068,7 +2068,7 @@ static void test_qemu_strtosz_simple(void)
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 0xfffffffffffff800);
-    g_assert(endptr == str + 20);
+    g_assert_true(endptr == str + 20);

     str = "18446744073709550591"; /* 0xfffffffffffffbff */
     endptr = str;
@@ -2076,7 +2076,7 @@ static void test_qemu_strtosz_simple(void)
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 0xfffffffffffffbff);
-    g_assert(endptr == str + 20);
+    g_assert_true(endptr == str + 20);

     str = "18446744073709551615"; /* 0xffffffffffffffff */
     endptr = str;
@@ -2084,7 +2084,7 @@ static void test_qemu_strtosz_simple(void)
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 0xffffffffffffffff);
-    g_assert(endptr == str + 20);
+    g_assert_true(endptr == str + 20);
 }

 static void test_qemu_strtosz_hex(void)
@@ -2100,7 +2100,7 @@ static void test_qemu_strtosz_hex(void)
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 0);
-    g_assert(endptr == str + 3);
+    g_assert_true(endptr == str + 3);

     str = "0xab";
     endptr = str;
@@ -2108,7 +2108,7 @@ static void test_qemu_strtosz_hex(void)
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 171);
-    g_assert(endptr == str + 4);
+    g_assert_true(endptr == str + 4);

     str = "0xae";
     endptr = str;
@@ -2116,7 +2116,7 @@ static void test_qemu_strtosz_hex(void)
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 174);
-    g_assert(endptr == str + 4);
+    g_assert_true(endptr == str + 4);
 }

 static void test_qemu_strtosz_units(void)
@@ -2139,56 +2139,56 @@ static void test_qemu_strtosz_units(void)
     err = qemu_strtosz_MiB(none, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, MiB);
-    g_assert(endptr == none + 1);
+    g_assert_true(endptr == none + 1);

     endptr = NULL;
     res = 0xbaadf00d;
     err = qemu_strtosz(b, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 1);
-    g_assert(endptr == b + 2);
+    g_assert_true(endptr == b + 2);

     endptr = NULL;
     res = 0xbaadf00d;
     err = qemu_strtosz(k, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, KiB);
-    g_assert(endptr == k + 2);
+    g_assert_true(endptr == k + 2);

     endptr = NULL;
     res = 0xbaadf00d;
     err = qemu_strtosz(m, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, MiB);
-    g_assert(endptr == m + 2);
+    g_assert_true(endptr == m + 2);

     endptr = NULL;
     res = 0xbaadf00d;
     err = qemu_strtosz(g, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, GiB);
-    g_assert(endptr == g + 2);
+    g_assert_true(endptr == g + 2);

     endptr = NULL;
     res = 0xbaadf00d;
     err = qemu_strtosz(t, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, TiB);
-    g_assert(endptr == t + 2);
+    g_assert_true(endptr == t + 2);

     endptr = NULL;
     res = 0xbaadf00d;
     err = qemu_strtosz(p, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, PiB);
-    g_assert(endptr == p + 2);
+    g_assert_true(endptr == p + 2);

     endptr = NULL;
     res = 0xbaadf00d;
     err = qemu_strtosz(e, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, EiB);
-    g_assert(endptr == e + 2);
+    g_assert_true(endptr == e + 2);
 }

 static void test_qemu_strtosz_float(void)
@@ -2204,7 +2204,7 @@ static void test_qemu_strtosz_float(void)
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, EiB / 2);
-    g_assert(endptr == str + 4);
+    g_assert_true(endptr == str + 4);

     /* For convenience, a fraction of 0 is tolerated even on bytes */
     str = "1.0B";
@@ -2213,7 +2213,7 @@ static void test_qemu_strtosz_float(void)
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 1);
-    g_assert(endptr == str + 4);
+    g_assert_true(endptr == str + 4);

     /* An empty fraction is tolerated */
     str = "1.k";
@@ -2222,7 +2222,7 @@ static void test_qemu_strtosz_float(void)
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 1024);
-    g_assert(endptr == str + 3);
+    g_assert_true(endptr == str + 3);

     /* For convenience, we permit values that are not byte-exact */
     str = "12.345M";
@@ -2231,7 +2231,7 @@ static void test_qemu_strtosz_float(void)
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, (uint64_t) (12.345 * MiB + 0.5));
-    g_assert(endptr == str + 7);
+    g_assert_true(endptr == str + 7);
 }

 static void test_qemu_strtosz_invalid(void)
@@ -2246,35 +2246,35 @@ static void test_qemu_strtosz_invalid(void)
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
     g_assert_cmpint(res, ==, 0xbaadf00d);
-    g_assert(endptr == str);
+    g_assert_true(endptr == str);

     str = " \t ";
     endptr = NULL;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
     g_assert_cmpint(res, ==, 0xbaadf00d);
-    g_assert(endptr == str);
+    g_assert_true(endptr == str);

     str = "crap";
     endptr = NULL;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
     g_assert_cmpint(res, ==, 0xbaadf00d);
-    g_assert(endptr == str);
+    g_assert_true(endptr == str);

     str = "inf";
     endptr = NULL;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
     g_assert_cmpint(res, ==, 0xbaadf00d);
-    g_assert(endptr == str);
+    g_assert_true(endptr == str);

     str = "NaN";
     endptr = NULL;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
     g_assert_cmpint(res, ==, 0xbaadf00d);
-    g_assert(endptr == str);
+    g_assert_true(endptr == str);

     /* Fractional values require scale larger than bytes */
     str = "1.1B";
@@ -2282,14 +2282,14 @@ static void test_qemu_strtosz_invalid(void)
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
     g_assert_cmpint(res, ==, 0xbaadf00d);
-    g_assert(endptr == str);
+    g_assert_true(endptr == str);

     str = "1.1";
     endptr = NULL;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
     g_assert_cmpint(res, ==, 0xbaadf00d);
-    g_assert(endptr == str);
+    g_assert_true(endptr == str);

     /* No floating point exponents */
     str = "1.5e1k";
@@ -2297,14 +2297,14 @@ static void test_qemu_strtosz_invalid(void)
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
     g_assert_cmpint(res, ==, 0xbaadf00d);
-    g_assert(endptr == str);
+    g_assert_true(endptr == str);

     str = "1.5E+0k";
     endptr = NULL;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
     g_assert_cmpint(res, ==, 0xbaadf00d);
-    g_assert(endptr == str);
+    g_assert_true(endptr == str);

     /* No hex fractions */
     str = "0x1.8k";
@@ -2312,7 +2312,7 @@ static void test_qemu_strtosz_invalid(void)
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
     g_assert_cmpint(res, ==, 0xbaadf00d);
-    g_assert(endptr == str);
+    g_assert_true(endptr == str);

     /* No suffixes */
     str = "0x18M";
@@ -2320,7 +2320,7 @@ static void test_qemu_strtosz_invalid(void)
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
     g_assert_cmpint(res, ==, 0xbaadf00d);
-    g_assert(endptr == str);
+    g_assert_true(endptr == str);

     /* No negative values */
     str = "-0";
@@ -2328,14 +2328,14 @@ static void test_qemu_strtosz_invalid(void)
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
     g_assert_cmpint(res, ==, 0xbaadf00d);
-    g_assert(endptr == str);
+    g_assert_true(endptr == str);

     str = "-1";
     endptr = NULL;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
     g_assert_cmpint(res, ==, 0xbaadf00d);
-    g_assert(endptr == str);
+    g_assert_true(endptr == str);
 }

 static void test_qemu_strtosz_trailing(void)
@@ -2351,7 +2351,7 @@ static void test_qemu_strtosz_trailing(void)
     err = qemu_strtosz_MiB(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 123 * MiB);
-    g_assert(endptr == str + 3);
+    g_assert_true(endptr == str + 3);

     res = 0xbaadf00d;
     err = qemu_strtosz(str, NULL, &res);
@@ -2364,7 +2364,7 @@ static void test_qemu_strtosz_trailing(void)
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 1024);
-    g_assert(endptr == str + 2);
+    g_assert_true(endptr == str + 2);

     res = 0xbaadf00d;
     err = qemu_strtosz(str, NULL, &res);
@@ -2377,7 +2377,7 @@ static void test_qemu_strtosz_trailing(void)
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 0);
-    g_assert(endptr == str + 1);
+    g_assert_true(endptr == str + 1);

     res = 0xbaadf00d;
     err = qemu_strtosz(str, NULL, &res);
@@ -2390,7 +2390,7 @@ static void test_qemu_strtosz_trailing(void)
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 0);
-    g_assert(endptr == str + 2);
+    g_assert_true(endptr == str + 2);

     res = 0xbaadf00d;
     err = qemu_strtosz(str, NULL, &res);
@@ -2403,7 +2403,7 @@ static void test_qemu_strtosz_trailing(void)
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 123);
-    g_assert(endptr == str + 3);
+    g_assert_true(endptr == str + 3);

     res = 0xbaadf00d;
     err = qemu_strtosz(str, NULL, &res);
@@ -2423,14 +2423,14 @@ static void test_qemu_strtosz_erange(void)
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, -ERANGE);
     g_assert_cmpint(res, ==, 0xbaadf00d);
-    g_assert(endptr == str + 20);
+    g_assert_true(endptr == str + 20);

     str = "20E";
     endptr = NULL;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, -ERANGE);
     g_assert_cmpint(res, ==, 0xbaadf00d);
-    g_assert(endptr == str + 3);
+    g_assert_true(endptr == str + 3);
 }

 static void test_qemu_strtosz_metric(void)
@@ -2446,7 +2446,7 @@ static void test_qemu_strtosz_metric(void)
     err = qemu_strtosz_metric(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 12345000);
-    g_assert(endptr == str + 6);
+    g_assert_true(endptr == str + 6);

     str = "12.345M";
     endptr = str;
@@ -2454,7 +2454,7 @@ static void test_qemu_strtosz_metric(void)
     err = qemu_strtosz_metric(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, 12345000);
-    g_assert(endptr == str + 7);
+    g_assert_true(endptr == str + 7);
 }

 static void test_freq_to_str(void)
-- 
2.40.1



^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v2 02/19] test-cutils: Use g_assert_cmpuint where appropriate
  2023-05-12  2:10 [PATCH v2 00/19] Fix qemu_strtosz() read-out-of-bounds Eric Blake
  2023-05-12  2:10 ` [PATCH v2 01/19] test-cutils: Avoid g_assert in unit tests Eric Blake
@ 2023-05-12  2:10 ` Eric Blake
  2023-05-12  2:10 ` [PATCH v2 03/19] test-cutils: Test integral qemu_strto* value on failures Eric Blake
                   ` (17 subsequent siblings)
  19 siblings, 0 replies; 48+ messages in thread
From: Eric Blake @ 2023-05-12  2:10 UTC (permalink / raw)
  To: qemu-devel; +Cc: hreitz, armbru, richard.henderson

When debugging test failures, seeing unsigned values as large positive
values rather than negative values matters (assuming glib 2.78+; given
that I just fixed a bug in glib 2.76 [1] where g_assert_cmpuint
displays signed instead of unsigned values).  No impact when the test
is passing, but using a consistent style will matter more in upcoming
test additions.  Also, some tests are better with cmphex.

While at it, fix some spacing and minor typing issues spotted nearby.

[1] https://gitlab.gnome.org/GNOME/glib/-/issues/2997

Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Hanna Czenczek <hreitz@redhat.com>
---
 tests/unit/test-cutils.c | 148 +++++++++++++++++++--------------------
 1 file changed, 74 insertions(+), 74 deletions(-)

diff --git a/tests/unit/test-cutils.c b/tests/unit/test-cutils.c
index 0202ac0d5b3..38bd3990207 100644
--- a/tests/unit/test-cutils.c
+++ b/tests/unit/test-cutils.c
@@ -39,7 +39,7 @@ static void test_parse_uint_null(void)
     r = parse_uint(NULL, &i, &endptr, 0);

     g_assert_cmpint(r, ==, -EINVAL);
-    g_assert_cmpint(i, ==, 0);
+    g_assert_cmpuint(i, ==, 0);
     g_assert_null(endptr);
 }

@@ -54,7 +54,7 @@ static void test_parse_uint_empty(void)
     r = parse_uint(str, &i, &endptr, 0);

     g_assert_cmpint(r, ==, -EINVAL);
-    g_assert_cmpint(i, ==, 0);
+    g_assert_cmpuint(i, ==, 0);
     g_assert_true(endptr == str);
 }

@@ -69,7 +69,7 @@ static void test_parse_uint_whitespace(void)
     r = parse_uint(str, &i, &endptr, 0);

     g_assert_cmpint(r, ==, -EINVAL);
-    g_assert_cmpint(i, ==, 0);
+    g_assert_cmpuint(i, ==, 0);
     g_assert_true(endptr == str);
 }

@@ -85,7 +85,7 @@ static void test_parse_uint_invalid(void)
     r = parse_uint(str, &i, &endptr, 0);

     g_assert_cmpint(r, ==, -EINVAL);
-    g_assert_cmpint(i, ==, 0);
+    g_assert_cmpuint(i, ==, 0);
     g_assert_true(endptr == str);
 }

@@ -101,7 +101,7 @@ static void test_parse_uint_trailing(void)
     r = parse_uint(str, &i, &endptr, 0);

     g_assert_cmpint(r, ==, 0);
-    g_assert_cmpint(i, ==, 123);
+    g_assert_cmpuint(i, ==, 123);
     g_assert_true(endptr == str + 3);
 }

@@ -116,7 +116,7 @@ static void test_parse_uint_correct(void)
     r = parse_uint(str, &i, &endptr, 0);

     g_assert_cmpint(r, ==, 0);
-    g_assert_cmpint(i, ==, 123);
+    g_assert_cmpuint(i, ==, 123);
     g_assert_true(endptr == str + strlen(str));
 }

@@ -131,7 +131,7 @@ static void test_parse_uint_octal(void)
     r = parse_uint(str, &i, &endptr, 0);

     g_assert_cmpint(r, ==, 0);
-    g_assert_cmpint(i, ==, 0123);
+    g_assert_cmpuint(i, ==, 0123);
     g_assert_true(endptr == str + strlen(str));
 }

@@ -146,7 +146,7 @@ static void test_parse_uint_decimal(void)
     r = parse_uint(str, &i, &endptr, 10);

     g_assert_cmpint(r, ==, 0);
-    g_assert_cmpint(i, ==, 123);
+    g_assert_cmpuint(i, ==, 123);
     g_assert_true(endptr == str + strlen(str));
 }

@@ -162,7 +162,7 @@ static void test_parse_uint_llong_max(void)
     r = parse_uint(str, &i, &endptr, 0);

     g_assert_cmpint(r, ==, 0);
-    g_assert_cmpint(i, ==, (unsigned long long)LLONG_MAX + 1);
+    g_assert_cmpuint(i, ==, (unsigned long long)LLONG_MAX + 1);
     g_assert_true(endptr == str + strlen(str));

     g_free(str);
@@ -179,7 +179,7 @@ static void test_parse_uint_overflow(void)
     r = parse_uint(str, &i, &endptr, 0);

     g_assert_cmpint(r, ==, -ERANGE);
-    g_assert_cmpint(i, ==, ULLONG_MAX);
+    g_assert_cmpuint(i, ==, ULLONG_MAX);
     g_assert_true(endptr == str + strlen(str));
 }

@@ -194,7 +194,7 @@ static void test_parse_uint_negative(void)
     r = parse_uint(str, &i, &endptr, 0);

     g_assert_cmpint(r, ==, -ERANGE);
-    g_assert_cmpint(i, ==, 0);
+    g_assert_cmpuint(i, ==, 0);
     g_assert_true(endptr == str + strlen(str));
 }

@@ -208,7 +208,7 @@ static void test_parse_uint_full_trailing(void)
     r = parse_uint_full(str, &i, 0);

     g_assert_cmpint(r, ==, -EINVAL);
-    g_assert_cmpint(i, ==, 0);
+    g_assert_cmpuint(i, ==, 0);
 }

 static void test_parse_uint_full_correct(void)
@@ -220,7 +220,7 @@ static void test_parse_uint_full_correct(void)
     r = parse_uint_full(str, &i, 0);

     g_assert_cmpint(r, ==, 0);
-    g_assert_cmpint(i, ==, 123);
+    g_assert_cmpuint(i, ==, 123);
 }

 static void test_qemu_strtoi_correct(void)
@@ -428,7 +428,7 @@ static void test_qemu_strtoi_underflow(void)
     int res = 999;
     int err;

-    err  = qemu_strtoi(str, &endptr, 0, &res);
+    err = qemu_strtoi(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -ERANGE);
     g_assert_cmpint(res, ==, INT_MIN);
@@ -479,10 +479,10 @@ static void test_qemu_strtoi_full_null(void)
 static void test_qemu_strtoi_full_empty(void)
 {
     const char *str = "";
-    int res = 999L;
+    int res = 999;
     int err;

-    err =  qemu_strtoi(str, NULL, 0, &res);
+    err = qemu_strtoi(str, NULL, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
 }
@@ -728,7 +728,7 @@ static void test_qemu_strtoui_underflow(void)
     unsigned int res = 999;
     int err;

-    err  = qemu_strtoui(str, &endptr, 0, &res);
+    err = qemu_strtoui(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -ERANGE);
     g_assert_cmpuint(res, ==, (unsigned int)-1);
@@ -1022,7 +1022,7 @@ static void test_qemu_strtol_underflow(void)
     long res = 999;
     int err;

-    err  = qemu_strtol(str, &endptr, 0, &res);
+    err = qemu_strtol(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -ERANGE);
     g_assert_cmpint(res, ==, LONG_MIN);
@@ -1075,7 +1075,7 @@ static void test_qemu_strtol_full_empty(void)
     long res = 999L;
     int err;

-    err =  qemu_strtol(str, NULL, 0, &res);
+    err = qemu_strtol(str, NULL, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
 }
@@ -1320,7 +1320,7 @@ static void test_qemu_strtoul_underflow(void)
     unsigned long res = 999;
     int err;

-    err  = qemu_strtoul(str, &endptr, 0, &res);
+    err = qemu_strtoul(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -ERANGE);
     g_assert_cmpuint(res, ==, -1ul);
@@ -1613,7 +1613,7 @@ static void test_qemu_strtoi64_underflow(void)
     int64_t res = 999;
     int err;

-    err  = qemu_strtoi64(str, &endptr, 0, &res);
+    err = qemu_strtoi64(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -ERANGE);
     g_assert_cmpint(res, ==, LLONG_MIN);
@@ -1909,7 +1909,7 @@ static void test_qemu_strtou64_underflow(void)
     uint64_t res = 999;
     int err;

-    err  = qemu_strtou64(str, &endptr, 0, &res);
+    err = qemu_strtou64(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -ERANGE);
     g_assert_cmphex(res, ==, -1ull);
@@ -2012,7 +2012,7 @@ static void test_qemu_strtosz_simple(void)
     res = 0xbaadf00d;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
-    g_assert_cmpint(res, ==, 0);
+    g_assert_cmpuint(res, ==, 0);
     g_assert_true(endptr == str + 1);

     /* Leading 0 gives decimal results, not octal */
@@ -2021,7 +2021,7 @@ static void test_qemu_strtosz_simple(void)
     res = 0xbaadf00d;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
-    g_assert_cmpint(res, ==, 8);
+    g_assert_cmpuint(res, ==, 8);
     g_assert_true(endptr == str + 2);

     /* Leading space is ignored */
@@ -2030,20 +2030,20 @@ static void test_qemu_strtosz_simple(void)
     res = 0xbaadf00d;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
-    g_assert_cmpint(res, ==, 12345);
+    g_assert_cmpuint(res, ==, 12345);
     g_assert_true(endptr == str + 6);

     res = 0xbaadf00d;
     err = qemu_strtosz(str, NULL, &res);
     g_assert_cmpint(err, ==, 0);
-    g_assert_cmpint(res, ==, 12345);
+    g_assert_cmpuint(res, ==, 12345);

     str = "9007199254740991"; /* 2^53-1 */
     endptr = str;
     res = 0xbaadf00d;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
-    g_assert_cmpint(res, ==, 0x1fffffffffffff);
+    g_assert_cmphex(res, ==, 0x1fffffffffffffULL);
     g_assert_true(endptr == str + 16);

     str = "9007199254740992"; /* 2^53 */
@@ -2051,7 +2051,7 @@ static void test_qemu_strtosz_simple(void)
     res = 0xbaadf00d;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
-    g_assert_cmpint(res, ==, 0x20000000000000);
+    g_assert_cmphex(res, ==, 0x20000000000000ULL);
     g_assert_true(endptr == str + 16);

     str = "9007199254740993"; /* 2^53+1 */
@@ -2059,7 +2059,7 @@ static void test_qemu_strtosz_simple(void)
     res = 0xbaadf00d;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
-    g_assert_cmpint(res, ==, 0x20000000000001);
+    g_assert_cmphex(res, ==, 0x20000000000001ULL);
     g_assert_true(endptr == str + 16);

     str = "18446744073709549568"; /* 0xfffffffffffff800 (53 msbs set) */
@@ -2067,7 +2067,7 @@ static void test_qemu_strtosz_simple(void)
     res = 0xbaadf00d;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
-    g_assert_cmpint(res, ==, 0xfffffffffffff800);
+    g_assert_cmphex(res, ==, 0xfffffffffffff800ULL);
     g_assert_true(endptr == str + 20);

     str = "18446744073709550591"; /* 0xfffffffffffffbff */
@@ -2075,7 +2075,7 @@ static void test_qemu_strtosz_simple(void)
     res = 0xbaadf00d;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
-    g_assert_cmpint(res, ==, 0xfffffffffffffbff);
+    g_assert_cmphex(res, ==, 0xfffffffffffffbffULL);
     g_assert_true(endptr == str + 20);

     str = "18446744073709551615"; /* 0xffffffffffffffff */
@@ -2083,7 +2083,7 @@ static void test_qemu_strtosz_simple(void)
     res = 0xbaadf00d;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
-    g_assert_cmpint(res, ==, 0xffffffffffffffff);
+    g_assert_cmphex(res, ==, 0xffffffffffffffffULL);
     g_assert_true(endptr == str + 20);
 }

@@ -2099,7 +2099,7 @@ static void test_qemu_strtosz_hex(void)
     res = 0xbaadf00d;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
-    g_assert_cmpint(res, ==, 0);
+    g_assert_cmpuint(res, ==, 0);
     g_assert_true(endptr == str + 3);

     str = "0xab";
@@ -2107,7 +2107,7 @@ static void test_qemu_strtosz_hex(void)
     res = 0xbaadf00d;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
-    g_assert_cmpint(res, ==, 171);
+    g_assert_cmpuint(res, ==, 171);
     g_assert_true(endptr == str + 4);

     str = "0xae";
@@ -2115,7 +2115,7 @@ static void test_qemu_strtosz_hex(void)
     res = 0xbaadf00d;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
-    g_assert_cmpint(res, ==, 174);
+    g_assert_cmpuint(res, ==, 174);
     g_assert_true(endptr == str + 4);
 }

@@ -2138,56 +2138,56 @@ static void test_qemu_strtosz_units(void)
     res = 0xbaadf00d;
     err = qemu_strtosz_MiB(none, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
-    g_assert_cmpint(res, ==, MiB);
+    g_assert_cmpuint(res, ==, MiB);
     g_assert_true(endptr == none + 1);

     endptr = NULL;
     res = 0xbaadf00d;
     err = qemu_strtosz(b, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
-    g_assert_cmpint(res, ==, 1);
+    g_assert_cmpuint(res, ==, 1);
     g_assert_true(endptr == b + 2);

     endptr = NULL;
     res = 0xbaadf00d;
     err = qemu_strtosz(k, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
-    g_assert_cmpint(res, ==, KiB);
+    g_assert_cmpuint(res, ==, KiB);
     g_assert_true(endptr == k + 2);

     endptr = NULL;
     res = 0xbaadf00d;
     err = qemu_strtosz(m, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
-    g_assert_cmpint(res, ==, MiB);
+    g_assert_cmpuint(res, ==, MiB);
     g_assert_true(endptr == m + 2);

     endptr = NULL;
     res = 0xbaadf00d;
     err = qemu_strtosz(g, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
-    g_assert_cmpint(res, ==, GiB);
+    g_assert_cmpuint(res, ==, GiB);
     g_assert_true(endptr == g + 2);

     endptr = NULL;
     res = 0xbaadf00d;
     err = qemu_strtosz(t, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
-    g_assert_cmpint(res, ==, TiB);
+    g_assert_cmpuint(res, ==, TiB);
     g_assert_true(endptr == t + 2);

     endptr = NULL;
     res = 0xbaadf00d;
     err = qemu_strtosz(p, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
-    g_assert_cmpint(res, ==, PiB);
+    g_assert_cmpuint(res, ==, PiB);
     g_assert_true(endptr == p + 2);

     endptr = NULL;
     res = 0xbaadf00d;
     err = qemu_strtosz(e, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
-    g_assert_cmpint(res, ==, EiB);
+    g_assert_cmpuint(res, ==, EiB);
     g_assert_true(endptr == e + 2);
 }

@@ -2203,7 +2203,7 @@ static void test_qemu_strtosz_float(void)
     res = 0xbaadf00d;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
-    g_assert_cmpint(res, ==, EiB / 2);
+    g_assert_cmpuint(res, ==, EiB / 2);
     g_assert_true(endptr == str + 4);

     /* For convenience, a fraction of 0 is tolerated even on bytes */
@@ -2212,7 +2212,7 @@ static void test_qemu_strtosz_float(void)
     res = 0xbaadf00d;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
-    g_assert_cmpint(res, ==, 1);
+    g_assert_cmpuint(res, ==, 1);
     g_assert_true(endptr == str + 4);

     /* An empty fraction is tolerated */
@@ -2221,7 +2221,7 @@ static void test_qemu_strtosz_float(void)
     res = 0xbaadf00d;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
-    g_assert_cmpint(res, ==, 1024);
+    g_assert_cmpuint(res, ==, 1024);
     g_assert_true(endptr == str + 3);

     /* For convenience, we permit values that are not byte-exact */
@@ -2230,7 +2230,7 @@ static void test_qemu_strtosz_float(void)
     res = 0xbaadf00d;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
-    g_assert_cmpint(res, ==, (uint64_t) (12.345 * MiB + 0.5));
+    g_assert_cmpuint(res, ==, (uint64_t) (12.345 * MiB + 0.5));
     g_assert_true(endptr == str + 7);
 }

@@ -2245,35 +2245,35 @@ static void test_qemu_strtosz_invalid(void)
     endptr = NULL;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpint(res, ==, 0xbaadf00d);
+    g_assert_cmphex(res, ==, 0xbaadf00d);
     g_assert_true(endptr == str);

     str = " \t ";
     endptr = NULL;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpint(res, ==, 0xbaadf00d);
+    g_assert_cmphex(res, ==, 0xbaadf00d);
     g_assert_true(endptr == str);

     str = "crap";
     endptr = NULL;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpint(res, ==, 0xbaadf00d);
+    g_assert_cmphex(res, ==, 0xbaadf00d);
     g_assert_true(endptr == str);

     str = "inf";
     endptr = NULL;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpint(res, ==, 0xbaadf00d);
+    g_assert_cmphex(res, ==, 0xbaadf00d);
     g_assert_true(endptr == str);

     str = "NaN";
     endptr = NULL;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpint(res, ==, 0xbaadf00d);
+    g_assert_cmphex(res, ==, 0xbaadf00d);
     g_assert_true(endptr == str);

     /* Fractional values require scale larger than bytes */
@@ -2281,14 +2281,14 @@ static void test_qemu_strtosz_invalid(void)
     endptr = NULL;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpint(res, ==, 0xbaadf00d);
+    g_assert_cmphex(res, ==, 0xbaadf00d);
     g_assert_true(endptr == str);

     str = "1.1";
     endptr = NULL;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpint(res, ==, 0xbaadf00d);
+    g_assert_cmphex(res, ==, 0xbaadf00d);
     g_assert_true(endptr == str);

     /* No floating point exponents */
@@ -2296,14 +2296,14 @@ static void test_qemu_strtosz_invalid(void)
     endptr = NULL;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpint(res, ==, 0xbaadf00d);
+    g_assert_cmphex(res, ==, 0xbaadf00d);
     g_assert_true(endptr == str);

     str = "1.5E+0k";
     endptr = NULL;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpint(res, ==, 0xbaadf00d);
+    g_assert_cmphex(res, ==, 0xbaadf00d);
     g_assert_true(endptr == str);

     /* No hex fractions */
@@ -2311,7 +2311,7 @@ static void test_qemu_strtosz_invalid(void)
     endptr = NULL;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpint(res, ==, 0xbaadf00d);
+    g_assert_cmphex(res, ==, 0xbaadf00d);
     g_assert_true(endptr == str);

     /* No suffixes */
@@ -2319,7 +2319,7 @@ static void test_qemu_strtosz_invalid(void)
     endptr = NULL;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpint(res, ==, 0xbaadf00d);
+    g_assert_cmphex(res, ==, 0xbaadf00d);
     g_assert_true(endptr == str);

     /* No negative values */
@@ -2327,14 +2327,14 @@ static void test_qemu_strtosz_invalid(void)
     endptr = NULL;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpint(res, ==, 0xbaadf00d);
+    g_assert_cmphex(res, ==, 0xbaadf00d);
     g_assert_true(endptr == str);

     str = "-1";
     endptr = NULL;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpint(res, ==, 0xbaadf00d);
+    g_assert_cmphex(res, ==, 0xbaadf00d);
     g_assert_true(endptr == str);
 }

@@ -2350,65 +2350,65 @@ static void test_qemu_strtosz_trailing(void)
     res = 0xbaadf00d;
     err = qemu_strtosz_MiB(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
-    g_assert_cmpint(res, ==, 123 * MiB);
+    g_assert_cmpuint(res, ==, 123 * MiB);
     g_assert_true(endptr == str + 3);

     res = 0xbaadf00d;
     err = qemu_strtosz(str, NULL, &res);
     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpint(res, ==, 0xbaadf00d);
+    g_assert_cmphex(res, ==, 0xbaadf00d);

     str = "1kiB";
     endptr = NULL;
     res = 0xbaadf00d;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
-    g_assert_cmpint(res, ==, 1024);
+    g_assert_cmpuint(res, ==, 1024);
     g_assert_true(endptr == str + 2);

     res = 0xbaadf00d;
     err = qemu_strtosz(str, NULL, &res);
     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpint(res, ==, 0xbaadf00d);
+    g_assert_cmphex(res, ==, 0xbaadf00d);

     str = "0x";
     endptr = NULL;
     res = 0xbaadf00d;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
-    g_assert_cmpint(res, ==, 0);
+    g_assert_cmpuint(res, ==, 0);
     g_assert_true(endptr == str + 1);

     res = 0xbaadf00d;
     err = qemu_strtosz(str, NULL, &res);
     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpint(res, ==, 0xbaadf00d);
+    g_assert_cmphex(res, ==, 0xbaadf00d);

     str = "0.NaN";
     endptr = NULL;
     res = 0xbaadf00d;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
-    g_assert_cmpint(res, ==, 0);
+    g_assert_cmpuint(res, ==, 0);
     g_assert_true(endptr == str + 2);

     res = 0xbaadf00d;
     err = qemu_strtosz(str, NULL, &res);
     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpint(res, ==, 0xbaadf00d);
+    g_assert_cmphex(res, ==, 0xbaadf00d);

     str = "123-45";
     endptr = NULL;
     res = 0xbaadf00d;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
-    g_assert_cmpint(res, ==, 123);
+    g_assert_cmpuint(res, ==, 123);
     g_assert_true(endptr == str + 3);

     res = 0xbaadf00d;
     err = qemu_strtosz(str, NULL, &res);
     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpint(res, ==, 0xbaadf00d);
+    g_assert_cmphex(res, ==, 0xbaadf00d);
 }

 static void test_qemu_strtosz_erange(void)
@@ -2422,14 +2422,14 @@ static void test_qemu_strtosz_erange(void)
     endptr = NULL;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, -ERANGE);
-    g_assert_cmpint(res, ==, 0xbaadf00d);
+    g_assert_cmphex(res, ==, 0xbaadf00d);
     g_assert_true(endptr == str + 20);

     str = "20E";
     endptr = NULL;
     err = qemu_strtosz(str, &endptr, &res);
     g_assert_cmpint(err, ==, -ERANGE);
-    g_assert_cmpint(res, ==, 0xbaadf00d);
+    g_assert_cmphex(res, ==, 0xbaadf00d);
     g_assert_true(endptr == str + 3);
 }

@@ -2445,7 +2445,7 @@ static void test_qemu_strtosz_metric(void)
     res = 0xbaadf00d;
     err = qemu_strtosz_metric(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
-    g_assert_cmpint(res, ==, 12345000);
+    g_assert_cmpuint(res, ==, 12345000);
     g_assert_true(endptr == str + 6);

     str = "12.345M";
@@ -2453,7 +2453,7 @@ static void test_qemu_strtosz_metric(void)
     res = 0xbaadf00d;
     err = qemu_strtosz_metric(str, &endptr, &res);
     g_assert_cmpint(err, ==, 0);
-    g_assert_cmpint(res, ==, 12345000);
+    g_assert_cmpuint(res, ==, 12345000);
     g_assert_true(endptr == str + 7);
 }

-- 
2.40.1



^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v2 03/19] test-cutils: Test integral qemu_strto* value on failures
  2023-05-12  2:10 [PATCH v2 00/19] Fix qemu_strtosz() read-out-of-bounds Eric Blake
  2023-05-12  2:10 ` [PATCH v2 01/19] test-cutils: Avoid g_assert in unit tests Eric Blake
  2023-05-12  2:10 ` [PATCH v2 02/19] test-cutils: Use g_assert_cmpuint where appropriate Eric Blake
@ 2023-05-12  2:10 ` Eric Blake
  2023-05-12  2:10 ` [PATCH v2 04/19] test-cutils: Test more integer corner cases Eric Blake
                   ` (16 subsequent siblings)
  19 siblings, 0 replies; 48+ messages in thread
From: Eric Blake @ 2023-05-12  2:10 UTC (permalink / raw)
  To: qemu-devel; +Cc: hreitz, armbru, richard.henderson

We are inconsistent on the contents of *value after a strto* parse
failure.  I found the following behaviors:

- parse_uint() and parse_uint_full(), which document that *value is
  slammed to 0 on all EINVAL failures and 0 or UINT_MAX on ERANGE
  failures, and has unit tests for that (note that parse_uint requires
  non-NULL endptr, and does not fail with EINVAL for trailing junk)

- qemu_strtosz(), which leaves *value untouched on all failures (both
  EINVAL and ERANGE), and has unit tests but not documentation for
  that

- qemu_strtoi() and other integral friends, which document *value on
  ERANGE failures but is unspecified on EINVAL (other than implicitly
  by comparison to libc strto*); there, *value is untouched for NULL
  string, slammed to 0 on no conversion, and left at the prefix value
  on NULL endptr; unit tests do not consistently check the value

- qemu_strtod(), which documents *value on ERANGE failures but is
  unspecified on EINVAL; there, *value is untouched for NULL string,
  slammed to 0.0 for no conversion, and left at the prefix value on
  NULL endptr; there are no unit tests (other than indirectly through
  qemu_strtosz)

- qemu_strtod_finite(), which documents *value on ERANGE failures but
  is unspecified on EINVAL; there, *value is left at the prefix for
  'inf' or 'nan' and untouched in all other cases; there are no unit
  tests (other than indirectly through qemu_strtosz)

Upcoming patches will change behaviors for consistency, but it's best
to first have more unit test coverage to see the impact of those
changes.

Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Hanna Czenczek <hreitz@redhat.com>
---
 tests/unit/test-cutils.c | 58 +++++++++++++++++++++++++++++++++++-----
 1 file changed, 51 insertions(+), 7 deletions(-)

diff --git a/tests/unit/test-cutils.c b/tests/unit/test-cutils.c
index 38bd3990207..1eeaf21ae22 100644
--- a/tests/unit/test-cutils.c
+++ b/tests/unit/test-cutils.c
@@ -248,6 +248,7 @@ static void test_qemu_strtoi_null(void)
     err = qemu_strtoi(NULL, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpint(res, ==, 999);
     g_assert_null(endptr);
 }

@@ -262,6 +263,7 @@ static void test_qemu_strtoi_empty(void)
     err = qemu_strtoi(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpint(res, ==, 0);
     g_assert_true(endptr == str);
 }

@@ -276,6 +278,7 @@ static void test_qemu_strtoi_whitespace(void)
     err = qemu_strtoi(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpint(res, ==, 0);
     g_assert_true(endptr == str);
 }

@@ -290,6 +293,7 @@ static void test_qemu_strtoi_invalid(void)
     err = qemu_strtoi(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpint(res, ==, 0);
     g_assert_true(endptr == str);
 }

@@ -473,6 +477,7 @@ static void test_qemu_strtoi_full_null(void)
     err = qemu_strtoi(NULL, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpint(res, ==, 999);
     g_assert_null(endptr);
 }

@@ -485,6 +490,7 @@ static void test_qemu_strtoi_full_empty(void)
     err = qemu_strtoi(str, NULL, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpint(res, ==, 0);
 }

 static void test_qemu_strtoi_full_negative(void)
@@ -502,18 +508,19 @@ static void test_qemu_strtoi_full_negative(void)
 static void test_qemu_strtoi_full_trailing(void)
 {
     const char *str = "123xxx";
-    int res;
+    int res = 999;
     int err;

     err = qemu_strtoi(str, NULL, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpint(res, ==, 123);
 }

 static void test_qemu_strtoi_full_max(void)
 {
     char *str = g_strdup_printf("%d", INT_MAX);
-    int res;
+    int res = 999;
     int err;

     err = qemu_strtoi(str, NULL, 0, &res);
@@ -548,6 +555,7 @@ static void test_qemu_strtoui_null(void)
     err = qemu_strtoui(NULL, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpuint(res, ==, 999);
     g_assert_null(endptr);
 }

@@ -562,6 +570,7 @@ static void test_qemu_strtoui_empty(void)
     err = qemu_strtoui(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpuint(res, ==, 0);
     g_assert_true(endptr == str);
 }

@@ -576,6 +585,7 @@ static void test_qemu_strtoui_whitespace(void)
     err = qemu_strtoui(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpuint(res, ==, 0);
     g_assert_true(endptr == str);
 }

@@ -590,6 +600,7 @@ static void test_qemu_strtoui_invalid(void)
     err = qemu_strtoui(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpuint(res, ==, 0);
     g_assert_true(endptr == str);
 }

@@ -771,6 +782,7 @@ static void test_qemu_strtoui_full_null(void)
     err = qemu_strtoui(NULL, NULL, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpuint(res, ==, 999);
 }

 static void test_qemu_strtoui_full_empty(void)
@@ -782,7 +794,9 @@ static void test_qemu_strtoui_full_empty(void)
     err = qemu_strtoui(str, NULL, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpuint(res, ==, 0);
 }
+
 static void test_qemu_strtoui_full_negative(void)
 {
     const char *str = " \t -321";
@@ -797,12 +811,13 @@ static void test_qemu_strtoui_full_negative(void)
 static void test_qemu_strtoui_full_trailing(void)
 {
     const char *str = "123xxx";
-    unsigned int res;
+    unsigned int res = 999;
     int err;

     err = qemu_strtoui(str, NULL, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpuint(res, ==, 123);
 }

 static void test_qemu_strtoui_full_max(void)
@@ -843,6 +858,7 @@ static void test_qemu_strtol_null(void)
     err = qemu_strtol(NULL, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpint(res, ==, 999);
     g_assert_null(endptr);
 }

@@ -857,6 +873,7 @@ static void test_qemu_strtol_empty(void)
     err = qemu_strtol(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpint(res, ==, 0);
     g_assert_true(endptr == str);
 }

@@ -871,6 +888,7 @@ static void test_qemu_strtol_whitespace(void)
     err = qemu_strtol(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpint(res, ==, 0);
     g_assert_true(endptr == str);
 }

@@ -885,6 +903,7 @@ static void test_qemu_strtol_invalid(void)
     err = qemu_strtol(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpint(res, ==, 0);
     g_assert_true(endptr == str);
 }

@@ -1066,6 +1085,7 @@ static void test_qemu_strtol_full_null(void)
     err = qemu_strtol(NULL, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpint(res, ==, 999);
     g_assert_null(endptr);
 }

@@ -1078,6 +1098,7 @@ static void test_qemu_strtol_full_empty(void)
     err = qemu_strtol(str, NULL, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpint(res, ==, 0);
 }

 static void test_qemu_strtol_full_negative(void)
@@ -1095,18 +1116,19 @@ static void test_qemu_strtol_full_negative(void)
 static void test_qemu_strtol_full_trailing(void)
 {
     const char *str = "123xxx";
-    long res;
+    long res = 999;
     int err;

     err = qemu_strtol(str, NULL, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpint(res, ==, 123);
 }

 static void test_qemu_strtol_full_max(void)
 {
     char *str = g_strdup_printf("%ld", LONG_MAX);
-    long res;
+    long res = 999;
     int err;

     err = qemu_strtol(str, NULL, 0, &res);
@@ -1141,6 +1163,7 @@ static void test_qemu_strtoul_null(void)
     err = qemu_strtoul(NULL, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpuint(res, ==, 999);
     g_assert_null(endptr);
 }

@@ -1155,6 +1178,7 @@ static void test_qemu_strtoul_empty(void)
     err = qemu_strtoul(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpuint(res, ==, 0);
     g_assert_true(endptr == str);
 }

@@ -1169,6 +1193,7 @@ static void test_qemu_strtoul_whitespace(void)
     err = qemu_strtoul(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpuint(res, ==, 0);
     g_assert_true(endptr == str);
 }

@@ -1183,6 +1208,7 @@ static void test_qemu_strtoul_invalid(void)
     err = qemu_strtoul(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpuint(res, ==, 0);
     g_assert_true(endptr == str);
 }

@@ -1362,6 +1388,7 @@ static void test_qemu_strtoul_full_null(void)
     err = qemu_strtoul(NULL, NULL, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpuint(res, ==, 999);
 }

 static void test_qemu_strtoul_full_empty(void)
@@ -1373,7 +1400,9 @@ static void test_qemu_strtoul_full_empty(void)
     err = qemu_strtoul(str, NULL, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpuint(res, ==, 0);
 }
+
 static void test_qemu_strtoul_full_negative(void)
 {
     const char *str = " \t -321";
@@ -1388,12 +1417,13 @@ static void test_qemu_strtoul_full_negative(void)
 static void test_qemu_strtoul_full_trailing(void)
 {
     const char *str = "123xxx";
-    unsigned long res;
+    unsigned long res = 999;
     int err;

     err = qemu_strtoul(str, NULL, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpuint(res, ==, 123);
 }

 static void test_qemu_strtoul_full_max(void)
@@ -1434,6 +1464,7 @@ static void test_qemu_strtoi64_null(void)
     err = qemu_strtoi64(NULL, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpint(res, ==, 999);
     g_assert_null(endptr);
 }

@@ -1448,6 +1479,7 @@ static void test_qemu_strtoi64_empty(void)
     err = qemu_strtoi64(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpint(res, ==, 0);
     g_assert_true(endptr == str);
 }

@@ -1462,6 +1494,7 @@ static void test_qemu_strtoi64_whitespace(void)
     err = qemu_strtoi64(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpint(res, ==, 0);
     g_assert_true(endptr == str);
 }

@@ -1476,6 +1509,7 @@ static void test_qemu_strtoi64_invalid(void)
     err = qemu_strtoi64(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpint(res, ==, 0);
     g_assert_true(endptr == str);
 }

@@ -1655,6 +1689,7 @@ static void test_qemu_strtoi64_full_null(void)
     err = qemu_strtoi64(NULL, NULL, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpint(res, ==, 999);
 }

 static void test_qemu_strtoi64_full_empty(void)
@@ -1666,6 +1701,7 @@ static void test_qemu_strtoi64_full_empty(void)
     err = qemu_strtoi64(str, NULL, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpint(res, ==, 0);
 }

 static void test_qemu_strtoi64_full_negative(void)
@@ -1689,13 +1725,14 @@ static void test_qemu_strtoi64_full_trailing(void)
     err = qemu_strtoi64(str, NULL, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpint(res, ==, 123);
 }

 static void test_qemu_strtoi64_full_max(void)
 {

     char *str = g_strdup_printf("%lld", LLONG_MAX);
-    int64_t res;
+    int64_t res = 999;
     int err;

     err = qemu_strtoi64(str, NULL, 0, &res);
@@ -1730,6 +1767,7 @@ static void test_qemu_strtou64_null(void)
     err = qemu_strtou64(NULL, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpuint(res, ==, 999);
     g_assert_null(endptr);
 }

@@ -1744,6 +1782,7 @@ static void test_qemu_strtou64_empty(void)
     err = qemu_strtou64(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpuint(res, ==, 0);
     g_assert_true(endptr == str);
 }

@@ -1758,6 +1797,7 @@ static void test_qemu_strtou64_whitespace(void)
     err = qemu_strtou64(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpuint(res, ==, 0);
     g_assert_true(endptr == str);
 }

@@ -1772,6 +1812,7 @@ static void test_qemu_strtou64_invalid(void)
     err = qemu_strtou64(str, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpuint(res, ==, 0);
     g_assert_true(endptr == str);
 }

@@ -1951,6 +1992,7 @@ static void test_qemu_strtou64_full_null(void)
     err = qemu_strtou64(NULL, NULL, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpuint(res, ==, 999);
 }

 static void test_qemu_strtou64_full_empty(void)
@@ -1962,6 +2004,7 @@ static void test_qemu_strtou64_full_empty(void)
     err = qemu_strtou64(str, NULL, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpuint(res, ==, 0);
 }

 static void test_qemu_strtou64_full_negative(void)
@@ -1985,6 +2028,7 @@ static void test_qemu_strtou64_full_trailing(void)
     err = qemu_strtou64(str, NULL, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpuint(res, ==, 18446744073709551614ULL);
 }

 static void test_qemu_strtou64_full_max(void)
-- 
2.40.1



^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v2 04/19] test-cutils: Test more integer corner cases
  2023-05-12  2:10 [PATCH v2 00/19] Fix qemu_strtosz() read-out-of-bounds Eric Blake
                   ` (2 preceding siblings ...)
  2023-05-12  2:10 ` [PATCH v2 03/19] test-cutils: Test integral qemu_strto* value on failures Eric Blake
@ 2023-05-12  2:10 ` Eric Blake
  2023-05-19 14:27   ` Hanna Czenczek
  2023-05-12  2:10 ` [PATCH v2 05/19] cutils: Fix wraparound parsing in qemu_strtoui Eric Blake
                   ` (15 subsequent siblings)
  19 siblings, 1 reply; 48+ messages in thread
From: Eric Blake @ 2023-05-12  2:10 UTC (permalink / raw)
  To: qemu-devel; +Cc: hreitz, armbru, richard.henderson

We have quite a few undertested and underdocumented integer parsing
corner cases.  To ensure that any changes we make in the code are
intentional rather than accidental semantic changes, it is time to add
more unit tests of existing behavior.

In particular, this demonstrates that parse_uint() and qemu_strtou64()
behave differently.  For "-0", it's hard to argue why parse_uint needs
to reject it (it's not a negative integer), but the documentation sort
of mentions it; but it is intentional that all other negative values
are treated as ERANGE with value 0 (compared to qemu_strtou64()
treating "-2" as success and UINT64_MAX-1, for example).

Also, when mixing overflow/underflow with a check for no trailing
junk, parse_uint_full favors ERANGE over EINVAL, while qemu_strto[iu]*
favor EINVAL.  This behavior is outside the C standard, so we can pick
whatever we want, but it would be nice to be consistent.

Note that C requires that "9223372036854775808" fail strtoll() with
ERANGE/INT64_MAX, but "-9223372036854775808" pass with INT64_MIN; we
weren't testing this.  For strtol(), the behavior depends on whether
long is 32- or 64-bits (the cutoff point either being the same as
strtoll() or at "-2147483648").  Meanwhile, C is clear that
"-18446744073709551615" pass stroull() (but not strtoll) with value 1,
even though we want it to fail parse_uint().  And although
qemu_strtoui() has no C counterpart, it makes more sense if we design
it like 32-bit strtoul() (that is, where "-4294967296" be an alternate
acceptable spelling for "1".  We aren't there yet, so some of the
tests added in this patch have FIXME comments.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 tests/unit/test-cutils.c | 799 ++++++++++++++++++++++++++++++++++++---
 1 file changed, 738 insertions(+), 61 deletions(-)

diff --git a/tests/unit/test-cutils.c b/tests/unit/test-cutils.c
index 1eeaf21ae22..89c10f5307a 100644
--- a/tests/unit/test-cutils.c
+++ b/tests/unit/test-cutils.c
@@ -150,7 +150,6 @@ static void test_parse_uint_decimal(void)
     g_assert_true(endptr == str + strlen(str));
 }

-
 static void test_parse_uint_llong_max(void)
 {
     unsigned long long i = 999;
@@ -168,16 +167,51 @@ static void test_parse_uint_llong_max(void)
     g_free(str);
 }

+static void test_parse_uint_max(void)
+{
+    unsigned long long i = 999;
+    char f = 'X';
+    char *endptr = &f;
+    char *str = g_strdup_printf("%llu", ULLONG_MAX);
+    int r;
+
+    r = parse_uint(str, &i, &endptr, 0);
+
+    g_assert_cmpint(r, ==, 0);
+    g_assert_cmpuint(i, ==, ULLONG_MAX);
+    g_assert_true(endptr == str + strlen(str));
+
+    g_free(str);
+}
+
 static void test_parse_uint_overflow(void)
 {
-    unsigned long long i = 999;
+    unsigned long long i;
     char f = 'X';
-    char *endptr = &f;
-    const char *str = "99999999999999999999999999999999999999";
+    char *endptr;
+    const char *str;
     int r;

+    i = 999;
+    endptr = &f;
+    str = "99999999999999999999999999999999999999";
     r = parse_uint(str, &i, &endptr, 0);
+    g_assert_cmpint(r, ==, -ERANGE);
+    g_assert_cmpuint(i, ==, ULLONG_MAX);
+    g_assert_true(endptr == str + strlen(str));

+    i = 999;
+    endptr = &f;
+    str = "0x10000000000000000"; /* 65 bits, 64-bit sign bit clear */
+    r = parse_uint(str, &i, &endptr, 0);
+    g_assert_cmpint(r, ==, -ERANGE);
+    g_assert_cmpuint(i, ==, ULLONG_MAX);
+    g_assert_true(endptr == str + strlen(str));
+
+    i = 999;
+    endptr = &f;
+    str = "0x18000000080000000"; /* 65 bits, 64-bit sign bit set */
+    r = parse_uint(str, &i, &endptr, 0);
     g_assert_cmpint(r, ==, -ERANGE);
     g_assert_cmpuint(i, ==, ULLONG_MAX);
     g_assert_true(endptr == str + strlen(str));
@@ -198,6 +232,20 @@ static void test_parse_uint_negative(void)
     g_assert_true(endptr == str + strlen(str));
 }

+static void test_parse_uint_negzero(void)
+{
+    unsigned long long i = 999;
+    char f = 'X';
+    char *endptr = &f;
+    const char *str = " -0";
+    int r;
+
+    r = parse_uint(str, &i, &endptr, 0);
+
+    g_assert_cmpint(r, ==, -ERANGE);
+    g_assert_cmpuint(i, ==, 0);
+    g_assert_true(endptr == str + strlen(str));
+}

 static void test_parse_uint_full_trailing(void)
 {
@@ -223,6 +271,19 @@ static void test_parse_uint_full_correct(void)
     g_assert_cmpuint(i, ==, 123);
 }

+static void test_parse_uint_full_erange_junk(void)
+{
+    /* FIXME - inconsistent with qemu_strto* which favors EINVAL */
+    unsigned long long i = 999;
+    const char *str = "-2junk";
+    int r;
+
+    r = parse_uint_full(str, &i, 0);
+
+    g_assert_cmpint(r, ==, -ERANGE /* FIXME -EINVAL */);
+    g_assert_cmpuint(i, ==, 0);
+}
+
 static void test_qemu_strtoi_correct(void)
 {
     const char *str = "12345 foo";
@@ -410,7 +471,39 @@ static void test_qemu_strtoi_max(void)

 static void test_qemu_strtoi_overflow(void)
 {
-    char *str = g_strdup_printf("%lld", (long long)INT_MAX + 1ll);
+    const char *str;
+    const char *endptr;
+    int res;
+    int err;
+
+    str = "2147483648"; /* INT_MAX + 1ll */
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtoi(str, &endptr, 0, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpint(res, ==, INT_MAX);
+    g_assert_true(endptr == str + strlen(str));
+
+    str = "0x10000000000000000"; /* 65 bits, 32-bit sign bit clear */
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtoi(str, &endptr, 0, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpint(res, ==, INT_MAX);
+    g_assert_true(endptr == str + strlen(str));
+
+    str = "0x18000000080000000"; /* 65 bits, 32-bit sign bit set */
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtoi(str, &endptr, 0, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpint(res, ==, INT_MAX);
+    g_assert_true(endptr == str + strlen(str));
+}
+
+static void test_qemu_strtoi_min(void)
+{
+    char *str = g_strdup_printf("%d", INT_MIN);
     char f = 'X';
     const char *endptr = &f;
     int res = 999;
@@ -418,26 +511,50 @@ static void test_qemu_strtoi_overflow(void)

     err = qemu_strtoi(str, &endptr, 0, &res);

-    g_assert_cmpint(err, ==, -ERANGE);
-    g_assert_cmpint(res, ==, INT_MAX);
+    g_assert_cmpint(err, ==, 0);
+    g_assert_cmpint(res, ==, INT_MIN);
     g_assert_true(endptr == str + strlen(str));
     g_free(str);
 }

 static void test_qemu_strtoi_underflow(void)
 {
-    char *str = g_strdup_printf("%lld", (long long)INT_MIN - 1ll);
-    char f = 'X';
-    const char *endptr = &f;
-    int res = 999;
+    const char *str;
+    const char *endptr;
+    int res;
     int err;

+    str = "-2147483649"; /* INT_MIN - 1ll */
+    endptr = "somewhere";
+    res = 999;
     err = qemu_strtoi(str, &endptr, 0, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpint(res, ==, INT_MIN);
+    g_assert_true(endptr == str + strlen(str));
+
+    str = "-18446744073709551615"; /* -UINT64_MAX (not 1) */
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtoi(str, &endptr, 0, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpint(res, ==, INT_MIN);
+    g_assert_true(endptr == str + strlen(str));

+    str = "-0x10000000000000000"; /* 65 bits, 32-bit sign bit clear */
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtoi(str, &endptr, 0, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpint(res, ==, INT_MIN);
+    g_assert_true(endptr == str + strlen(str));
+
+    str = "-0x18000000080000000"; /* 65 bits, 32-bit sign bit set */
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtoi(str, &endptr, 0, &res);
     g_assert_cmpint(err, ==, -ERANGE);
     g_assert_cmpint(res, ==, INT_MIN);
     g_assert_true(endptr == str + strlen(str));
-    g_free(str);
 }

 static void test_qemu_strtoi_negative(void)
@@ -455,6 +572,21 @@ static void test_qemu_strtoi_negative(void)
     g_assert_true(endptr == str + strlen(str));
 }

+static void test_qemu_strtoi_negzero(void)
+{
+    const char *str = " -0";
+    char f = 'X';
+    const char *endptr = &f;
+    int res = 999;
+    int err;
+
+    err = qemu_strtoi(str, &endptr, 0, &res);
+
+    g_assert_cmpint(err, ==, 0);
+    g_assert_cmpint(res, ==, 0);
+    g_assert_true(endptr == str + strlen(str));
+}
+
 static void test_qemu_strtoi_full_correct(void)
 {
     const char *str = "123";
@@ -505,6 +637,18 @@ static void test_qemu_strtoi_full_negative(void)
     g_assert_cmpint(res, ==, -321);
 }

+static void test_qemu_strtoi_full_negzero(void)
+{
+    const char *str = " -0";
+    int res = 999;
+    int err;
+
+    err = qemu_strtoi(str, NULL, 0, &res);
+
+    g_assert_cmpint(err, ==, 0);
+    g_assert_cmpint(res, ==, 0);
+}
+
 static void test_qemu_strtoi_full_trailing(void)
 {
     const char *str = "123xxx";
@@ -530,6 +674,19 @@ static void test_qemu_strtoi_full_max(void)
     g_free(str);
 }

+static void test_qemu_strtoi_full_erange_junk(void)
+{
+    /* EINVAL has priority over ERANGE */
+    const char *str = "-9999999999junk";
+    int res = 999;
+    int err;
+
+    err = qemu_strtoi(str, NULL, 0, &res);
+
+    g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpint(res, ==, INT_MIN);
+}
+
 static void test_qemu_strtoui_correct(void)
 {
     const char *str = "12345 foo";
@@ -699,6 +856,22 @@ static void test_qemu_strtoui_hex(void)
     g_assert_true(endptr == str + 1);
 }

+static void test_qemu_strtoui_wrap(void)
+{
+    /* FIXME - wraparound should be consistent with 32-bit strtoul */
+    const char *str = "-4294967295"; /* 1 mod 2^32 */
+    char f = 'X';
+    const char *endptr = &f;
+    unsigned int res = 999;
+    int err;
+
+    err = qemu_strtoui(str, &endptr, 0, &res);
+
+    g_assert_cmpint(err, ==, -ERANGE /* FIXME 0 */);
+    g_assert_cmphex(res, ==, UINT_MAX /* FIXME 1 */);
+    g_assert_true(endptr == str + strlen(str));
+}
+
 static void test_qemu_strtoui_max(void)
 {
     char *str = g_strdup_printf("%u", UINT_MAX);
@@ -717,34 +890,75 @@ static void test_qemu_strtoui_max(void)

 static void test_qemu_strtoui_overflow(void)
 {
-    char *str = g_strdup_printf("%lld", (long long)UINT_MAX + 1ll);
-    char f = 'X';
-    const char *endptr = &f;
-    unsigned int res = 999;
+    const char *str;
+    const char *endptr;
+    unsigned int res;
     int err;

+    str = "4294967296"; /* UINT_MAX + 1ll */
+    endptr = "somewhere";
+    res = 999;
     err = qemu_strtoui(str, &endptr, 0, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpint(res, ==, UINT_MAX);
+    g_assert_true(endptr == str + strlen(str));

+    str = "0x10000000000000000"; /* 65 bits, 32-bit sign bit clear */
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtoui(str, &endptr, 0, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpint(res, ==, UINT_MAX);
+    g_assert_true(endptr == str + strlen(str));
+
+    str = "0x18000000080000000"; /* 65 bits, 32-bit sign bit set */
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtoui(str, &endptr, 0, &res);
     g_assert_cmpint(err, ==, -ERANGE);
-    g_assert_cmphex(res, ==, UINT_MAX);
+    g_assert_cmpint(res, ==, UINT_MAX);
     g_assert_true(endptr == str + strlen(str));
-    g_free(str);
 }

 static void test_qemu_strtoui_underflow(void)
 {
-    char *str = g_strdup_printf("%lld", (long long)INT_MIN - 1ll);
-    char f = 'X';
-    const char *endptr = &f;
-    unsigned int res = 999;
+    const char *str;
+    const char *endptr;
+    unsigned int res;
     int err;

+    str = "-4294967296"; /* -(long long)UINT_MAX - 1ll */
+    endptr = "somewhere";
+    res = 999;
     err = qemu_strtoui(str, &endptr, 0, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpint(res, ==, UINT_MAX);
+    g_assert_true(endptr == str + strlen(str));
+
+    /* FIXME - overflow should be consistent with 32-bit strtoul */
+    str = "-18446744073709551615"; /* -UINT64_MAX (not 1) */
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtoui(str, &endptr, 0, &res);
+    g_assert_cmpint(err, ==, 0 /* FIXME -ERANGE */);
+    g_assert_cmpint(res, ==, 1 /* FIXME UINT_MAX */);
+    g_assert_true(endptr == str + strlen(str));

+    str = "-0x10000000000000000"; /* 65 bits, 32-bit sign bit clear */
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtoui(str, &endptr, 0, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpint(res, ==, UINT_MAX);
+    g_assert_true(endptr == str + strlen(str));
+
+    str = "-0x18000000080000000"; /* 65 bits, 32-bit sign bit set */
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtoui(str, &endptr, 0, &res);
     g_assert_cmpint(err, ==, -ERANGE);
-    g_assert_cmpuint(res, ==, (unsigned int)-1);
+    g_assert_cmpint(res, ==, UINT_MAX);
     g_assert_true(endptr == str + strlen(str));
-    g_free(str);
 }

 static void test_qemu_strtoui_negative(void)
@@ -762,6 +976,21 @@ static void test_qemu_strtoui_negative(void)
     g_assert_true(endptr == str + strlen(str));
 }

+static void test_qemu_strtoui_negzero(void)
+{
+    const char *str = " -0";
+    char f = 'X';
+    const char *endptr = &f;
+    unsigned int res = 999;
+    int err;
+
+    err = qemu_strtoui(str, &endptr, 0, &res);
+
+    g_assert_cmpint(err, ==, 0);
+    g_assert_cmpuint(res, ==, 0);
+    g_assert_true(endptr == str + strlen(str));
+}
+
 static void test_qemu_strtoui_full_correct(void)
 {
     const char *str = "123";
@@ -808,6 +1037,17 @@ static void test_qemu_strtoui_full_negative(void)
     g_assert_cmpuint(res, ==, (unsigned int)-321);
 }

+static void test_qemu_strtoui_full_negzero(void)
+{
+    const char *str = " -0";
+    unsigned int res = 999;
+    int err;
+
+    err = qemu_strtoui(str, NULL, 0, &res);
+    g_assert_cmpint(err, ==, 0);
+    g_assert_cmpuint(res, ==, 0);
+}
+
 static void test_qemu_strtoui_full_trailing(void)
 {
     const char *str = "123xxx";
@@ -833,6 +1073,19 @@ static void test_qemu_strtoui_full_max(void)
     g_free(str);
 }

+static void test_qemu_strtoui_full_erange_junk(void)
+{
+    /* EINVAL has priority over ERANGE */
+    const char *str = "-9999999999junk";
+    unsigned int res = 999;
+    int err;
+
+    err = qemu_strtoui(str, NULL, 0, &res);
+
+    g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpint(res, ==, UINT_MAX);
+}
+
 static void test_qemu_strtol_correct(void)
 {
     const char *str = "12345 foo";
@@ -1020,7 +1273,40 @@ static void test_qemu_strtol_max(void)

 static void test_qemu_strtol_overflow(void)
 {
-    const char *str = "99999999999999999999999999999999999999999999";
+    const char *str;
+    const char *endptr;
+    long res;
+    int err;
+
+    /* 1 more than LONG_MAX */
+    str = LONG_MAX == INT_MAX ? "2147483648" : "9223372036854775808";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtol(str, &endptr, 0, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpint(res, ==, LONG_MAX);
+    g_assert_true(endptr == str + strlen(str));
+
+    str = "0x10000000000000000"; /* 65 bits, either sign bit position clear */
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtol(str, &endptr, 0, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpint(res, ==, LONG_MAX);
+    g_assert_true(endptr == str + strlen(str));
+
+    str = "0x18000000080000000"; /* 65 bits, either sign bit position set */
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtol(str, &endptr, 0, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpint(res, ==, LONG_MAX);
+    g_assert_true(endptr == str + strlen(str));
+}
+
+static void test_qemu_strtol_min(void)
+{
+    char *str = g_strdup_printf("%ld", LONG_MIN);
     char f = 'X';
     const char *endptr = &f;
     long res = 999;
@@ -1028,21 +1314,50 @@ static void test_qemu_strtol_overflow(void)

     err = qemu_strtol(str, &endptr, 0, &res);

-    g_assert_cmpint(err, ==, -ERANGE);
-    g_assert_cmpint(res, ==, LONG_MAX);
+    g_assert_cmpint(err, ==, 0);
+    g_assert_cmpint(res, ==, LONG_MIN);
     g_assert_true(endptr == str + strlen(str));
+    g_free(str);
 }

 static void test_qemu_strtol_underflow(void)
 {
-    const char *str = "-99999999999999999999999999999999999999999999";
-    char f = 'X';
-    const char *endptr = &f;
-    long res = 999;
+    const char *str;
+    const char *endptr;
+    long res;
     int err;

+    /* 1 less than LONG_MIN */
+    str = LONG_MIN == INT_MIN ? "-2147483649" : "-9223372036854775809";
+    endptr = "somewhere";
+    res = 999;
     err = qemu_strtol(str, &endptr, 0, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpint(res, ==, LONG_MIN);
+    g_assert_true(endptr == str + strlen(str));

+    if (LONG_MAX == INT_MAX) {
+        str = "-18446744073709551615"; /* -UINT64_MAX (not 1) */
+        endptr = "somewhere";
+        res = 999;
+        err = qemu_strtol(str, &endptr, 0, &res);
+        g_assert_cmpint(err, ==, -ERANGE);
+        g_assert_cmpint(res, ==, LONG_MIN);
+        g_assert_true(endptr == str + strlen(str));
+    }
+
+    str = "-0x10000000000000000"; /* 65 bits, either sign bit position clear */
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtol(str, &endptr, 0, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpint(res, ==, LONG_MIN);
+    g_assert_true(endptr == str + strlen(str));
+
+    str = "-0x18000000080000000"; /* 65 bits, either sign bit position set */
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtol(str, &endptr, 0, &res);
     g_assert_cmpint(err, ==, -ERANGE);
     g_assert_cmpint(res, ==, LONG_MIN);
     g_assert_true(endptr == str + strlen(str));
@@ -1063,6 +1378,21 @@ static void test_qemu_strtol_negative(void)
     g_assert_true(endptr == str + strlen(str));
 }

+static void test_qemu_strtol_negzero(void)
+{
+    const char *str = " -0";
+    char f = 'X';
+    const char *endptr = &f;
+    long res = 999;
+    int err;
+
+    err = qemu_strtol(str, &endptr, 0, &res);
+
+    g_assert_cmpint(err, ==, 0);
+    g_assert_cmpint(res, ==, 0);
+    g_assert_true(endptr == str + strlen(str));
+}
+
 static void test_qemu_strtol_full_correct(void)
 {
     const char *str = "123";
@@ -1113,6 +1443,18 @@ static void test_qemu_strtol_full_negative(void)
     g_assert_cmpint(res, ==, -321);
 }

+static void test_qemu_strtol_full_negzero(void)
+{
+    const char *str = " -0";
+    long res = 999;
+    int err;
+
+    err = qemu_strtol(str, NULL, 0, &res);
+
+    g_assert_cmpint(err, ==, 0);
+    g_assert_cmpint(res, ==, 0);
+}
+
 static void test_qemu_strtol_full_trailing(void)
 {
     const char *str = "123xxx";
@@ -1138,6 +1480,19 @@ static void test_qemu_strtol_full_max(void)
     g_free(str);
 }

+static void test_qemu_strtol_full_erange_junk(void)
+{
+    /* EINVAL has priority over ERANGE */
+    const char *str = "-99999999999999999999junk";
+    long res = 999;
+    int err;
+
+    err = qemu_strtol(str, NULL, 0, &res);
+
+    g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpint(res, ==, LONG_MIN);
+}
+
 static void test_qemu_strtoul_correct(void)
 {
     const char *str = "12345 foo";
@@ -1307,6 +1662,23 @@ static void test_qemu_strtoul_hex(void)
     g_assert_true(endptr == str + 1);
 }

+static void test_qemu_strtoul_wrap(void)
+{
+    const char *str;
+    char f = 'X';
+    const char *endptr = &f;
+    unsigned long res = 999;
+    int err;
+
+    /* 1 mod 2^(sizeof(long)*8) */
+    str = LONG_MAX == INT_MAX ? "-4294967295" : "-18446744073709551615";
+    err = qemu_strtoul(str, &endptr, 0, &res);
+
+    g_assert_cmpint(err, ==, 0);
+    g_assert_cmphex(res, ==, 1);
+    g_assert_true(endptr == str + strlen(str));
+}
+
 static void test_qemu_strtoul_max(void)
 {
     char *str = g_strdup_printf("%lu", ULONG_MAX);
@@ -1325,31 +1697,67 @@ static void test_qemu_strtoul_max(void)

 static void test_qemu_strtoul_overflow(void)
 {
-    const char *str = "99999999999999999999999999999999999999999999";
-    char f = 'X';
-    const char *endptr = &f;
-    unsigned long res = 999;
+    const char *str;
+    const char *endptr;
+    unsigned long res;
     int err;

+    /* 1 more than ULONG_MAX */
+    str = ULONG_MAX == UINT_MAX ? "4294967296" : "18446744073709551616";
+    endptr = "somewhere";
+    res = 999;
     err = qemu_strtoul(str, &endptr, 0, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpint(res, ==, ULONG_MAX);
+    g_assert_true(endptr == str + strlen(str));

+    str = "0x10000000000000000"; /* 65 bits, either sign bit position clear */
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtoul(str, &endptr, 0, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpint(res, ==, ULONG_MAX);
+    g_assert_true(endptr == str + strlen(str));
+
+    str = "0x18000000080000000"; /* 65 bits, either sign bit position set */
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtoul(str, &endptr, 0, &res);
     g_assert_cmpint(err, ==, -ERANGE);
-    g_assert_cmphex(res, ==, ULONG_MAX);
+    g_assert_cmpint(res, ==, ULONG_MAX);
     g_assert_true(endptr == str + strlen(str));
 }

 static void test_qemu_strtoul_underflow(void)
 {
-    const char *str = "-99999999999999999999999999999999999999999999";
-    char f = 'X';
-    const char *endptr = &f;
-    unsigned long res = 999;
+    const char *str;
+    const char *endptr;
+    unsigned long res;
     int err;

+    /* 1 less than -ULONG_MAX */
+    str = ULONG_MAX == UINT_MAX ? "-4294967297" : "-18446744073709551617";
+    endptr = "somewhere";
+    res = 999;
     err = qemu_strtoul(str, &endptr, 0, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpint(res, ==, ULONG_MAX);
+    g_assert_true(endptr == str + strlen(str));

+    str = "-0x10000000000000000"; /* 65 bits, either sign bit position clear */
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtoul(str, &endptr, 0, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpint(res, ==, ULONG_MAX);
+    g_assert_true(endptr == str + strlen(str));
+
+    str = "-0x18000000080000000"; /* 65 bits, either sign bit position set */
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtoul(str, &endptr, 0, &res);
     g_assert_cmpint(err, ==, -ERANGE);
-    g_assert_cmpuint(res, ==, -1ul);
+    g_assert_cmpint(res, ==, ULONG_MAX);
     g_assert_true(endptr == str + strlen(str));
 }

@@ -1368,6 +1776,21 @@ static void test_qemu_strtoul_negative(void)
     g_assert_true(endptr == str + strlen(str));
 }

+static void test_qemu_strtoul_negzero(void)
+{
+    const char *str = " -0";
+    char f = 'X';
+    const char *endptr = &f;
+    unsigned long res = 999;
+    int err;
+
+    err = qemu_strtoul(str, &endptr, 0, &res);
+
+    g_assert_cmpint(err, ==, 0);
+    g_assert_cmpuint(res, ==, 0);
+    g_assert_true(endptr == str + strlen(str));
+}
+
 static void test_qemu_strtoul_full_correct(void)
 {
     const char *str = "123";
@@ -1414,6 +1837,17 @@ static void test_qemu_strtoul_full_negative(void)
     g_assert_cmpuint(res, ==, -321ul);
 }

+static void test_qemu_strtoul_full_negzero(void)
+{
+    const char *str = " -0";
+    unsigned long res = 999;
+    int err;
+
+    err = qemu_strtoul(str, NULL, 0, &res);
+    g_assert_cmpint(err, ==, 0);
+    g_assert_cmpuint(res, ==, 0);
+}
+
 static void test_qemu_strtoul_full_trailing(void)
 {
     const char *str = "123xxx";
@@ -1439,6 +1873,19 @@ static void test_qemu_strtoul_full_max(void)
     g_free(str);
 }

+static void test_qemu_strtoul_full_erange_junk(void)
+{
+    /* EINVAL has priority over ERANGE */
+    const char *str = "-99999999999999999999junk";
+    unsigned long res = 999;
+    int err;
+
+    err = qemu_strtoul(str, NULL, 0, &res);
+
+    g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpint(res, ==, ULONG_MAX);
+}
+
 static void test_qemu_strtoi64_correct(void)
 {
     const char *str = "12345 foo";
@@ -1626,22 +2073,39 @@ static void test_qemu_strtoi64_max(void)

 static void test_qemu_strtoi64_overflow(void)
 {
-    const char *str = "99999999999999999999999999999999999999999999";
-    char f = 'X';
-    const char *endptr = &f;
-    int64_t res = 999;
+    const char *str;
+    const char *endptr;
+    int64_t res;
     int err;

+    str = "9223372036854775808"; /* 1 more than INT64_MAX */
+    endptr = "somewhere";
+    res = 999;
     err = qemu_strtoi64(str, &endptr, 0, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpint(res, ==, INT64_MAX);
+    g_assert_true(endptr == str + strlen(str));

+    str = "0x10000000000000000"; /* 65 bits, 64-bit sign bit clear */
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtoi64(str, &endptr, 0, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpint(res, ==, INT64_MAX);
+    g_assert_true(endptr == str + strlen(str));
+
+    str = "0x18000000080000000"; /* 65 bits, 64-bit sign bit set */
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtoi64(str, &endptr, 0, &res);
     g_assert_cmpint(err, ==, -ERANGE);
-    g_assert_cmpint(res, ==, LLONG_MAX);
+    g_assert_cmpint(res, ==, INT64_MAX);
     g_assert_true(endptr == str + strlen(str));
 }

-static void test_qemu_strtoi64_underflow(void)
+static void test_qemu_strtoi64_min(void)
 {
-    const char *str = "-99999999999999999999999999999999999999999999";
+    char *str = g_strdup_printf("%lld", LLONG_MIN);
     char f = 'X';
     const char *endptr = &f;
     int64_t res = 999;
@@ -1649,9 +2113,42 @@ static void test_qemu_strtoi64_underflow(void)

     err = qemu_strtoi64(str, &endptr, 0, &res);

-    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpint(err, ==, 0);
     g_assert_cmpint(res, ==, LLONG_MIN);
     g_assert_true(endptr == str + strlen(str));
+    g_free(str);
+}
+
+static void test_qemu_strtoi64_underflow(void)
+{
+    const char *str;
+    const char *endptr;
+    int64_t res;
+    int err;
+
+    str = "-9223372036854775809"; /* 1 less than INT64_MIN */
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtoi64(str, &endptr, 0, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpint(res, ==, INT64_MIN);
+    g_assert_true(endptr == str + strlen(str));
+
+    str = "-0x10000000000000000"; /* 65 bits, 64-bit sign bit clear */
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtoi64(str, &endptr, 0, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpint(res, ==, INT64_MIN);
+    g_assert_true(endptr == str + strlen(str));
+
+    str = "-0x18000000080000000"; /* 65 bits, 64-bit sign bit set */
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtoi64(str, &endptr, 0, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpint(res, ==, INT64_MIN);
+    g_assert_true(endptr == str + strlen(str));
 }

 static void test_qemu_strtoi64_negative(void)
@@ -1669,6 +2166,21 @@ static void test_qemu_strtoi64_negative(void)
     g_assert_true(endptr == str + strlen(str));
 }

+static void test_qemu_strtoi64_negzero(void)
+{
+    const char *str = " -0";
+    char f = 'X';
+    const char *endptr = &f;
+    int64_t res = 999;
+    int err;
+
+    err = qemu_strtoi64(str, &endptr, 0, &res);
+
+    g_assert_cmpint(err, ==, 0);
+    g_assert_cmpint(res, ==, 0);
+    g_assert_true(endptr == str + strlen(str));
+}
+
 static void test_qemu_strtoi64_full_correct(void)
 {
     const char *str = "123";
@@ -1716,6 +2228,18 @@ static void test_qemu_strtoi64_full_negative(void)
     g_assert_cmpint(res, ==, -321);
 }

+static void test_qemu_strtoi64_full_negzero(void)
+{
+    const char *str = " -0";
+    int64_t res = 999;
+    int err;
+
+    err = qemu_strtoi64(str, NULL, 0, &res);
+
+    g_assert_cmpint(err, ==, 0);
+    g_assert_cmpint(res, ==, 0);
+}
+
 static void test_qemu_strtoi64_full_trailing(void)
 {
     const char *str = "123xxx";
@@ -1742,6 +2266,19 @@ static void test_qemu_strtoi64_full_max(void)
     g_free(str);
 }

+static void test_qemu_strtoi64_full_erange_junk(void)
+{
+    /* EINVAL has priority over ERANGE */
+    const char *str = "-99999999999999999999junk";
+    int64_t res = 999;
+    int err;
+
+    err = qemu_strtoi64(str, NULL, 0, &res);
+
+    g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpint(res, ==, INT64_MIN);
+}
+
 static void test_qemu_strtou64_correct(void)
 {
     const char *str = "12345 foo";
@@ -1911,6 +2448,21 @@ static void test_qemu_strtou64_hex(void)
     g_assert_true(endptr == str + 1);
 }

+static void test_qemu_strtou64_wrap(void)
+{
+    const char *str = "-18446744073709551615"; /* 1 mod 2^64 */
+    char f = 'X';
+    const char *endptr = &f;
+    uint64_t res = 999;
+    int err;
+
+    err = qemu_strtou64(str, &endptr, 0, &res);
+
+    g_assert_cmpint(err, ==, 0);
+    g_assert_cmpuint(res, ==, 1);
+    g_assert_true(endptr == str + strlen(str));
+}
+
 static void test_qemu_strtou64_max(void)
 {
     char *str = g_strdup_printf("%llu", ULLONG_MAX);
@@ -1929,31 +2481,65 @@ static void test_qemu_strtou64_max(void)

 static void test_qemu_strtou64_overflow(void)
 {
-    const char *str = "99999999999999999999999999999999999999999999";
-    char f = 'X';
-    const char *endptr = &f;
-    uint64_t res = 999;
+    const char *str;
+    const char *endptr;
+    uint64_t res;
     int err;

+    str = "18446744073709551616"; /* 1 more than UINT64_MAX */
+    endptr = "somewhere";
+    res = 999;
     err = qemu_strtou64(str, &endptr, 0, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpint(res, ==, UINT64_MAX);
+    g_assert_true(endptr == str + strlen(str));

+    str = "0x10000000000000000"; /* 65 bits, 64-bit sign bit clear */
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtou64(str, &endptr, 0, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpint(res, ==, UINT64_MAX);
+    g_assert_true(endptr == str + strlen(str));
+
+    str = "0x18000000080000000"; /* 65 bits, 64-bit sign bit set */
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtou64(str, &endptr, 0, &res);
     g_assert_cmpint(err, ==, -ERANGE);
-    g_assert_cmphex(res, ==, ULLONG_MAX);
+    g_assert_cmpint(res, ==, UINT64_MAX);
     g_assert_true(endptr == str + strlen(str));
 }

 static void test_qemu_strtou64_underflow(void)
 {
-    const char *str = "-99999999999999999999999999999999999999999999";
-    char f = 'X';
-    const char *endptr = &f;
-    uint64_t res = 999;
+    const char *str;
+    const char *endptr;
+    uint64_t res;
     int err;

+    str = "-99999999999999999999999999999999999999999999";
+    endptr = "somewhere";
+    res = 999;
     err = qemu_strtou64(str, &endptr, 0, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpint(res, ==, UINT64_MAX);
+    g_assert_true(endptr == str + strlen(str));

+    str = "-0x10000000000000000"; /* 65 bits, 64-bit sign bit clear */
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtou64(str, &endptr, 0, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpint(res, ==, UINT64_MAX);
+    g_assert_true(endptr == str + strlen(str));
+
+    str = "-0x18000000080000000"; /* 65 bits, 64-bit sign bit set */
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtou64(str, &endptr, 0, &res);
     g_assert_cmpint(err, ==, -ERANGE);
-    g_assert_cmphex(res, ==, -1ull);
+    g_assert_cmpint(res, ==, UINT64_MAX);
     g_assert_true(endptr == str + strlen(str));
 }

@@ -1972,6 +2558,21 @@ static void test_qemu_strtou64_negative(void)
     g_assert_true(endptr == str + strlen(str));
 }

+static void test_qemu_strtou64_negzero(void)
+{
+    const char *str = " -0";
+    char f = 'X';
+    const char *endptr = &f;
+    uint64_t res = 999;
+    int err;
+
+    err = qemu_strtou64(str, &endptr, 0, &res);
+
+    g_assert_cmpint(err, ==, 0);
+    g_assert_cmpuint(res, ==, 0);
+    g_assert_true(endptr == str + strlen(str));
+}
+
 static void test_qemu_strtou64_full_correct(void)
 {
     const char *str = "18446744073709551614";
@@ -2019,6 +2620,18 @@ static void test_qemu_strtou64_full_negative(void)
     g_assert_cmpuint(res, ==, -321ull);
 }

+static void test_qemu_strtou64_full_negzero(void)
+{
+    const char *str = " -0";
+    uint64_t res = 999;
+    int err;
+
+    err = qemu_strtou64(str, NULL, 0, &res);
+
+    g_assert_cmpint(err, ==, 0);
+    g_assert_cmpuint(res, ==, 0);
+}
+
 static void test_qemu_strtou64_full_trailing(void)
 {
     const char *str = "18446744073709551614xxxxxx";
@@ -2044,6 +2657,19 @@ static void test_qemu_strtou64_full_max(void)
     g_free(str);
 }

+static void test_qemu_strtou64_full_erange_junk(void)
+{
+    /* EINVAL has priority over ERANGE */
+    const char *str = "-99999999999999999999junk";
+    uint64_t res = 999;
+    int err;
+
+    err = qemu_strtou64(str, NULL, 0, &res);
+
+    g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpint(res, ==, UINT64_MAX);
+}
+
 static void test_qemu_strtosz_simple(void)
 {
     const char *str;
@@ -2585,12 +3211,16 @@ int main(int argc, char **argv)
     g_test_add_func("/cutils/parse_uint/octal", test_parse_uint_octal);
     g_test_add_func("/cutils/parse_uint/decimal", test_parse_uint_decimal);
     g_test_add_func("/cutils/parse_uint/llong_max", test_parse_uint_llong_max);
+    g_test_add_func("/cutils/parse_uint/max", test_parse_uint_max);
     g_test_add_func("/cutils/parse_uint/overflow", test_parse_uint_overflow);
     g_test_add_func("/cutils/parse_uint/negative", test_parse_uint_negative);
+    g_test_add_func("/cutils/parse_uint/negzero", test_parse_uint_negzero);
     g_test_add_func("/cutils/parse_uint_full/trailing",
                     test_parse_uint_full_trailing);
     g_test_add_func("/cutils/parse_uint_full/correct",
                     test_parse_uint_full_correct);
+    g_test_add_func("/cutils/parse_uint_full/erange_junk",
+                    test_parse_uint_full_erange_junk);

     /* qemu_strtoi() tests */
     g_test_add_func("/cutils/qemu_strtoi/correct",
@@ -2615,10 +3245,14 @@ int main(int argc, char **argv)
                     test_qemu_strtoi_max);
     g_test_add_func("/cutils/qemu_strtoi/overflow",
                     test_qemu_strtoi_overflow);
+    g_test_add_func("/cutils/qemu_strtoi/min",
+                    test_qemu_strtoi_min);
     g_test_add_func("/cutils/qemu_strtoi/underflow",
                     test_qemu_strtoi_underflow);
     g_test_add_func("/cutils/qemu_strtoi/negative",
                     test_qemu_strtoi_negative);
+    g_test_add_func("/cutils/qemu_strtoi/negzero",
+                    test_qemu_strtoi_negzero);
     g_test_add_func("/cutils/qemu_strtoi_full/correct",
                     test_qemu_strtoi_full_correct);
     g_test_add_func("/cutils/qemu_strtoi_full/null",
@@ -2627,10 +3261,14 @@ int main(int argc, char **argv)
                     test_qemu_strtoi_full_empty);
     g_test_add_func("/cutils/qemu_strtoi_full/negative",
                     test_qemu_strtoi_full_negative);
+    g_test_add_func("/cutils/qemu_strtoi_full/negzero",
+                    test_qemu_strtoi_full_negzero);
     g_test_add_func("/cutils/qemu_strtoi_full/trailing",
                     test_qemu_strtoi_full_trailing);
     g_test_add_func("/cutils/qemu_strtoi_full/max",
                     test_qemu_strtoi_full_max);
+    g_test_add_func("/cutils/qemu_strtoi_full/erange_junk",
+                    test_qemu_strtoi_full_erange_junk);

     /* qemu_strtoui() tests */
     g_test_add_func("/cutils/qemu_strtoui/correct",
@@ -2651,6 +3289,8 @@ int main(int argc, char **argv)
                     test_qemu_strtoui_decimal);
     g_test_add_func("/cutils/qemu_strtoui/hex",
                     test_qemu_strtoui_hex);
+    g_test_add_func("/cutils/qemu_strtoui/wrap",
+                    test_qemu_strtoui_wrap);
     g_test_add_func("/cutils/qemu_strtoui/max",
                     test_qemu_strtoui_max);
     g_test_add_func("/cutils/qemu_strtoui/overflow",
@@ -2659,6 +3299,8 @@ int main(int argc, char **argv)
                     test_qemu_strtoui_underflow);
     g_test_add_func("/cutils/qemu_strtoui/negative",
                     test_qemu_strtoui_negative);
+    g_test_add_func("/cutils/qemu_strtoui/negzero",
+                    test_qemu_strtoui_negzero);
     g_test_add_func("/cutils/qemu_strtoui_full/correct",
                     test_qemu_strtoui_full_correct);
     g_test_add_func("/cutils/qemu_strtoui_full/null",
@@ -2667,10 +3309,14 @@ int main(int argc, char **argv)
                     test_qemu_strtoui_full_empty);
     g_test_add_func("/cutils/qemu_strtoui_full/negative",
                     test_qemu_strtoui_full_negative);
+    g_test_add_func("/cutils/qemu_strtoui_full/negzero",
+                    test_qemu_strtoui_full_negzero);
     g_test_add_func("/cutils/qemu_strtoui_full/trailing",
                     test_qemu_strtoui_full_trailing);
     g_test_add_func("/cutils/qemu_strtoui_full/max",
                     test_qemu_strtoui_full_max);
+    g_test_add_func("/cutils/qemu_strtoui_full/erange_junk",
+                    test_qemu_strtoui_full_erange_junk);

     /* qemu_strtol() tests */
     g_test_add_func("/cutils/qemu_strtol/correct",
@@ -2695,10 +3341,14 @@ int main(int argc, char **argv)
                     test_qemu_strtol_max);
     g_test_add_func("/cutils/qemu_strtol/overflow",
                     test_qemu_strtol_overflow);
+    g_test_add_func("/cutils/qemu_strtol/min",
+                    test_qemu_strtol_min);
     g_test_add_func("/cutils/qemu_strtol/underflow",
                     test_qemu_strtol_underflow);
     g_test_add_func("/cutils/qemu_strtol/negative",
                     test_qemu_strtol_negative);
+    g_test_add_func("/cutils/qemu_strtol/negzero",
+                    test_qemu_strtol_negzero);
     g_test_add_func("/cutils/qemu_strtol_full/correct",
                     test_qemu_strtol_full_correct);
     g_test_add_func("/cutils/qemu_strtol_full/null",
@@ -2707,10 +3357,14 @@ int main(int argc, char **argv)
                     test_qemu_strtol_full_empty);
     g_test_add_func("/cutils/qemu_strtol_full/negative",
                     test_qemu_strtol_full_negative);
+    g_test_add_func("/cutils/qemu_strtol_full/negzero",
+                    test_qemu_strtol_full_negzero);
     g_test_add_func("/cutils/qemu_strtol_full/trailing",
                     test_qemu_strtol_full_trailing);
     g_test_add_func("/cutils/qemu_strtol_full/max",
                     test_qemu_strtol_full_max);
+    g_test_add_func("/cutils/qemu_strtol_full/erange_junk",
+                    test_qemu_strtol_full_erange_junk);

     /* qemu_strtoul() tests */
     g_test_add_func("/cutils/qemu_strtoul/correct",
@@ -2731,6 +3385,8 @@ int main(int argc, char **argv)
                     test_qemu_strtoul_decimal);
     g_test_add_func("/cutils/qemu_strtoul/hex",
                     test_qemu_strtoul_hex);
+    g_test_add_func("/cutils/qemu_strtoul/wrap",
+                    test_qemu_strtoul_wrap);
     g_test_add_func("/cutils/qemu_strtoul/max",
                     test_qemu_strtoul_max);
     g_test_add_func("/cutils/qemu_strtoul/overflow",
@@ -2739,6 +3395,8 @@ int main(int argc, char **argv)
                     test_qemu_strtoul_underflow);
     g_test_add_func("/cutils/qemu_strtoul/negative",
                     test_qemu_strtoul_negative);
+    g_test_add_func("/cutils/qemu_strtoul/negzero",
+                    test_qemu_strtoul_negzero);
     g_test_add_func("/cutils/qemu_strtoul_full/correct",
                     test_qemu_strtoul_full_correct);
     g_test_add_func("/cutils/qemu_strtoul_full/null",
@@ -2747,10 +3405,14 @@ int main(int argc, char **argv)
                     test_qemu_strtoul_full_empty);
     g_test_add_func("/cutils/qemu_strtoul_full/negative",
                     test_qemu_strtoul_full_negative);
+    g_test_add_func("/cutils/qemu_strtoul_full/negzero",
+                    test_qemu_strtoul_full_negzero);
     g_test_add_func("/cutils/qemu_strtoul_full/trailing",
                     test_qemu_strtoul_full_trailing);
     g_test_add_func("/cutils/qemu_strtoul_full/max",
                     test_qemu_strtoul_full_max);
+    g_test_add_func("/cutils/qemu_strtoul_full/erange_junk",
+                    test_qemu_strtoul_full_erange_junk);

     /* qemu_strtoi64() tests */
     g_test_add_func("/cutils/qemu_strtoi64/correct",
@@ -2761,8 +3423,7 @@ int main(int argc, char **argv)
                     test_qemu_strtoi64_empty);
     g_test_add_func("/cutils/qemu_strtoi64/whitespace",
                     test_qemu_strtoi64_whitespace);
-    g_test_add_func("/cutils/qemu_strtoi64/invalid"
-                    ,
+    g_test_add_func("/cutils/qemu_strtoi64/invalid",
                     test_qemu_strtoi64_invalid);
     g_test_add_func("/cutils/qemu_strtoi64/trailing",
                     test_qemu_strtoi64_trailing);
@@ -2776,10 +3437,14 @@ int main(int argc, char **argv)
                     test_qemu_strtoi64_max);
     g_test_add_func("/cutils/qemu_strtoi64/overflow",
                     test_qemu_strtoi64_overflow);
+    g_test_add_func("/cutils/qemu_strtoi64/min",
+                    test_qemu_strtoi64_min);
     g_test_add_func("/cutils/qemu_strtoi64/underflow",
                     test_qemu_strtoi64_underflow);
     g_test_add_func("/cutils/qemu_strtoi64/negative",
                     test_qemu_strtoi64_negative);
+    g_test_add_func("/cutils/qemu_strtoi64/negzero",
+                    test_qemu_strtoi64_negzero);
     g_test_add_func("/cutils/qemu_strtoi64_full/correct",
                     test_qemu_strtoi64_full_correct);
     g_test_add_func("/cutils/qemu_strtoi64_full/null",
@@ -2788,10 +3453,14 @@ int main(int argc, char **argv)
                     test_qemu_strtoi64_full_empty);
     g_test_add_func("/cutils/qemu_strtoi64_full/negative",
                     test_qemu_strtoi64_full_negative);
+    g_test_add_func("/cutils/qemu_strtoi64_full/negzero",
+                    test_qemu_strtoi64_full_negzero);
     g_test_add_func("/cutils/qemu_strtoi64_full/trailing",
                     test_qemu_strtoi64_full_trailing);
     g_test_add_func("/cutils/qemu_strtoi64_full/max",
                     test_qemu_strtoi64_full_max);
+    g_test_add_func("/cutils/qemu_strtoi64_full/erange_junk",
+                    test_qemu_strtoi64_full_erange_junk);

     /* qemu_strtou64() tests */
     g_test_add_func("/cutils/qemu_strtou64/correct",
@@ -2812,6 +3481,8 @@ int main(int argc, char **argv)
                     test_qemu_strtou64_decimal);
     g_test_add_func("/cutils/qemu_strtou64/hex",
                     test_qemu_strtou64_hex);
+    g_test_add_func("/cutils/qemu_strtou64/wrap",
+                    test_qemu_strtou64_wrap);
     g_test_add_func("/cutils/qemu_strtou64/max",
                     test_qemu_strtou64_max);
     g_test_add_func("/cutils/qemu_strtou64/overflow",
@@ -2820,6 +3491,8 @@ int main(int argc, char **argv)
                     test_qemu_strtou64_underflow);
     g_test_add_func("/cutils/qemu_strtou64/negative",
                     test_qemu_strtou64_negative);
+    g_test_add_func("/cutils/qemu_strtou64/negzero",
+                    test_qemu_strtou64_negzero);
     g_test_add_func("/cutils/qemu_strtou64_full/correct",
                     test_qemu_strtou64_full_correct);
     g_test_add_func("/cutils/qemu_strtou64_full/null",
@@ -2828,10 +3501,14 @@ int main(int argc, char **argv)
                     test_qemu_strtou64_full_empty);
     g_test_add_func("/cutils/qemu_strtou64_full/negative",
                     test_qemu_strtou64_full_negative);
+    g_test_add_func("/cutils/qemu_strtou64_full/negzero",
+                    test_qemu_strtou64_full_negzero);
     g_test_add_func("/cutils/qemu_strtou64_full/trailing",
                     test_qemu_strtou64_full_trailing);
     g_test_add_func("/cutils/qemu_strtou64_full/max",
                     test_qemu_strtou64_full_max);
+    g_test_add_func("/cutils/qemu_strtou64_full/erange_junk",
+                    test_qemu_strtou64_full_erange_junk);

     g_test_add_func("/cutils/strtosz/simple",
                     test_qemu_strtosz_simple);
-- 
2.40.1



^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v2 05/19] cutils: Fix wraparound parsing in qemu_strtoui
  2023-05-12  2:10 [PATCH v2 00/19] Fix qemu_strtosz() read-out-of-bounds Eric Blake
                   ` (3 preceding siblings ...)
  2023-05-12  2:10 ` [PATCH v2 04/19] test-cutils: Test more integer corner cases Eric Blake
@ 2023-05-12  2:10 ` Eric Blake
  2023-05-18 13:34   ` Eric Blake
  2023-05-19 14:42   ` Hanna Czenczek
  2023-05-12  2:10 ` [PATCH v2 06/19] cutils: Document differences between parse_uint and qemu_strtou64 Eric Blake
                   ` (14 subsequent siblings)
  19 siblings, 2 replies; 48+ messages in thread
From: Eric Blake @ 2023-05-12  2:10 UTC (permalink / raw)
  To: qemu-devel; +Cc: hreitz, armbru, richard.henderson

While we were matching 32-bit strtol in qemu_strtoi, our use of a
64-bit parse was leaking through for some inaccurate answers in
qemu_strtoui in comparison to a 32-bit strtoul.  Fix those, and update
the testsuite now that our bounds checks are correct.

Our int wrappers would be a lot easier to write if libc had a
guaranteed 32-bit parser even on platforms with 64-bit long.

Fixes: 473a2a331e ("cutils: add qemu_strtoi & qemu_strtoui parsers for int/unsigned int types", v2.12.0)
Signed-off-by: Eric Blake <eblake@redhat.com>
---
 tests/unit/test-cutils.c | 11 +++++------
 util/cutils.c            | 14 ++++++++++----
 2 files changed, 15 insertions(+), 10 deletions(-)

diff --git a/tests/unit/test-cutils.c b/tests/unit/test-cutils.c
index 89c10f5307a..08989d1d3ac 100644
--- a/tests/unit/test-cutils.c
+++ b/tests/unit/test-cutils.c
@@ -858,7 +858,7 @@ static void test_qemu_strtoui_hex(void)

 static void test_qemu_strtoui_wrap(void)
 {
-    /* FIXME - wraparound should be consistent with 32-bit strtoul */
+    /* wraparound is consistent with 32-bit strtoul */
     const char *str = "-4294967295"; /* 1 mod 2^32 */
     char f = 'X';
     const char *endptr = &f;
@@ -867,8 +867,8 @@ static void test_qemu_strtoui_wrap(void)

     err = qemu_strtoui(str, &endptr, 0, &res);

-    g_assert_cmpint(err, ==, -ERANGE /* FIXME 0 */);
-    g_assert_cmphex(res, ==, UINT_MAX /* FIXME 1 */);
+    g_assert_cmpint(err, ==, 0);
+    g_assert_cmphex(res, ==, 1);
     g_assert_true(endptr == str + strlen(str));
 }

@@ -935,13 +935,12 @@ static void test_qemu_strtoui_underflow(void)
     g_assert_cmpint(res, ==, UINT_MAX);
     g_assert_true(endptr == str + strlen(str));

-    /* FIXME - overflow should be consistent with 32-bit strtoul */
     str = "-18446744073709551615"; /* -UINT64_MAX (not 1) */
     endptr = "somewhere";
     res = 999;
     err = qemu_strtoui(str, &endptr, 0, &res);
-    g_assert_cmpint(err, ==, 0 /* FIXME -ERANGE */);
-    g_assert_cmpint(res, ==, 1 /* FIXME UINT_MAX */);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpint(res, ==, UINT_MAX);
     g_assert_true(endptr == str + strlen(str));

     str = "-0x10000000000000000"; /* 65 bits, 32-bit sign bit clear */
diff --git a/util/cutils.c b/util/cutils.c
index 5887e744140..997ddcd09e5 100644
--- a/util/cutils.c
+++ b/util/cutils.c
@@ -466,10 +466,16 @@ int qemu_strtoui(const char *nptr, const char **endptr, int base,
     if (errno == ERANGE) {
         *result = -1;
     } else {
-        if (lresult > UINT_MAX) {
-            *result = UINT_MAX;
-            errno = ERANGE;
-        } else if (lresult < INT_MIN) {
+        /*
+         * Note that platforms with 32-bit strtoul accept input in the
+         * range [-4294967295, 4294967295]; but we used 64-bit
+         * strtoull which wraps -18446744073709551615 to 1.  Reject
+         * positive values that contain '-', and wrap all valid
+         * negative values.
+         */
+        if (lresult > UINT_MAX ||
+            lresult < -(long long)UINT_MAX ||
+            (lresult > 0 && memchr(nptr, '-', ep - nptr))) {
             *result = UINT_MAX;
             errno = ERANGE;
         } else {
-- 
2.40.1



^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v2 06/19] cutils: Document differences between parse_uint and qemu_strtou64
  2023-05-12  2:10 [PATCH v2 00/19] Fix qemu_strtosz() read-out-of-bounds Eric Blake
                   ` (4 preceding siblings ...)
  2023-05-12  2:10 ` [PATCH v2 05/19] cutils: Fix wraparound parsing in qemu_strtoui Eric Blake
@ 2023-05-12  2:10 ` Eric Blake
  2023-05-19 14:44   ` Hanna Czenczek
  2023-05-12  2:10 ` [PATCH v2 07/19] cutils: Adjust signature of parse_uint[_full] Eric Blake
                   ` (13 subsequent siblings)
  19 siblings, 1 reply; 48+ messages in thread
From: Eric Blake @ 2023-05-12  2:10 UTC (permalink / raw)
  To: qemu-devel; +Cc: hreitz, armbru, richard.henderson

These two functions are subtly different, and not just because of
swapped parameter order.  It took me adding better unit tests to
figure out why.  Document the differences to make it more obvious to
developers trying to pick which one to use, as well as to aid in
upcoming semantic changes.

While touching the documentation, adjust a mis-statement: parse_uint
does not return -EINVAL on invalid base, but assert()s, like all the
other qemu_strto* functions that take a base argument.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 util/cutils.c | 20 ++++++++++++--------
 1 file changed, 12 insertions(+), 8 deletions(-)

diff --git a/util/cutils.c b/util/cutils.c
index 997ddcd09e5..4e3cc6e3605 100644
--- a/util/cutils.c
+++ b/util/cutils.c
@@ -604,6 +604,8 @@ int qemu_strtoi64(const char *nptr, const char **endptr, int base,
  * Convert string @nptr to an uint64_t.
  *
  * Works like qemu_strtoul(), except it stores UINT64_MAX on overflow.
+ * (If you want to prohibit negative numbers that wrap around to
+ * positive, use parse_uint()).
  */
 int qemu_strtou64(const char *nptr, const char **endptr, int base,
                   uint64_t *result)
@@ -714,7 +716,8 @@ const char *qemu_strchrnul(const char *s, int c)
  *
  * @s: String to parse
  * @value: Destination for parsed integer value
- * @endptr: Destination for pointer to first character not consumed
+ * @endptr: Destination for pointer to first character not consumed, must
+ * not be %NULL
  * @base: integer base, between 2 and 36 inclusive, or 0
  *
  * Parse unsigned integer
@@ -722,15 +725,16 @@ const char *qemu_strchrnul(const char *s, int c)
  * Parsed syntax is like strtoull()'s: arbitrary whitespace, a single optional
  * '+' or '-', an optional "0x" if @base is 0 or 16, one or more digits.
  *
- * If @s is null, or @base is invalid, or @s doesn't start with an
- * integer in the syntax above, set *@value to 0, *@endptr to @s, and
- * return -EINVAL.
+ * If @s is null, or @s doesn't start with an integer in the syntax
+ * above, set *@value to 0, *@endptr to @s, and return -EINVAL.
  *
  * Set *@endptr to point right beyond the parsed integer (even if the integer
  * overflows or is negative, all digits will be parsed and *@endptr will
  * point right beyond them).
  *
  * If the integer is negative, set *@value to 0, and return -ERANGE.
+ * (If you want to allow negative numbers that wrap around within
+ * bounds, use qemu_strtou64()).
  *
  * If the integer overflows unsigned long long, set *@value to
  * ULLONG_MAX, and return -ERANGE.
@@ -787,10 +791,10 @@ out:
  *
  * Parse unsigned integer from entire string
  *
- * Have the same behavior of parse_uint(), but with an additional check
- * for additional data after the parsed number. If extra characters are present
- * after the parsed number, the function will return -EINVAL, and *@v will
- * be set to 0.
+ * Have the same behavior of parse_uint(), but with an additional
+ * check for additional data after the parsed number. If extra
+ * characters are present after a non-overflowing parsed number, the
+ * function will return -EINVAL, and *@v will be set to 0.
  */
 int parse_uint_full(const char *s, unsigned long long *value, int base)
 {
-- 
2.40.1



^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v2 07/19] cutils: Adjust signature of parse_uint[_full]
  2023-05-12  2:10 [PATCH v2 00/19] Fix qemu_strtosz() read-out-of-bounds Eric Blake
                   ` (5 preceding siblings ...)
  2023-05-12  2:10 ` [PATCH v2 06/19] cutils: Document differences between parse_uint and qemu_strtou64 Eric Blake
@ 2023-05-12  2:10 ` Eric Blake
  2023-05-12 16:25   ` Eric Blake
  2023-05-19 14:51   ` Hanna Czenczek
  2023-05-12  2:10 ` [PATCH v2 08/19] cutils: Allow NULL endptr in parse_uint() Eric Blake
                   ` (12 subsequent siblings)
  19 siblings, 2 replies; 48+ messages in thread
From: Eric Blake @ 2023-05-12  2:10 UTC (permalink / raw)
  To: qemu-devel
  Cc: hreitz, armbru, richard.henderson, Gerd Hoffmann,
	Marc-André Lureau, Kevin Wolf, Peter Lieven, Michael Roth,
	Daniel P. Berrangé,
	open list:GLUSTER, open list:GLUSTER

It's already confusing that we have two very similar functions for
wrapping the parse of a 64-bit unsigned value, differing mainly on
whether they permit leading '-'.  Adjust the signature of parse_uint()
and parse_uint_full() to be like all of qemu_strto*(): put the result
parameter last, use the same types (uint64_t is not always the same as
unsigned long long, and mark endptr const (only latter affects the
rare caller of parse_uint).  Adjust all callers in the tree.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 include/qemu/cutils.h         |   5 +-
 audio/audio_legacy.c          |   4 +-
 block/gluster.c               |   4 +-
 block/nfs.c                   |   4 +-
 blockdev.c                    |   4 +-
 contrib/ivshmem-server/main.c |   4 +-
 qapi/opts-visitor.c           |  10 +--
 tests/unit/test-cutils.c      | 113 +++++++++++++++-------------------
 ui/vnc.c                      |   4 +-
 util/cutils.c                 |  13 ++--
 util/guest-random.c           |   4 +-
 util/qemu-sockets.c           |  10 +--
 12 files changed, 82 insertions(+), 97 deletions(-)

diff --git a/include/qemu/cutils.h b/include/qemu/cutils.h
index 92c436d8c70..92c927a6a35 100644
--- a/include/qemu/cutils.h
+++ b/include/qemu/cutils.h
@@ -163,9 +163,8 @@ int qemu_strtou64(const char *nptr, const char **endptr, int base,
 int qemu_strtod(const char *nptr, const char **endptr, double *result);
 int qemu_strtod_finite(const char *nptr, const char **endptr, double *result);

-int parse_uint(const char *s, unsigned long long *value, char **endptr,
-               int base);
-int parse_uint_full(const char *s, unsigned long long *value, int base);
+int parse_uint(const char *s, const char **endptr, int base, uint64_t *value);
+int parse_uint_full(const char *s, int base, uint64_t *value);

 int qemu_strtosz(const char *nptr, const char **end, uint64_t *result);
 int qemu_strtosz_MiB(const char *nptr, const char **end, uint64_t *result);
diff --git a/audio/audio_legacy.c b/audio/audio_legacy.c
index b848001ff70..dc72ba55e9a 100644
--- a/audio/audio_legacy.c
+++ b/audio/audio_legacy.c
@@ -35,8 +35,8 @@

 static uint32_t toui32(const char *str)
 {
-    unsigned long long ret;
-    if (parse_uint_full(str, &ret, 10) || ret > UINT32_MAX) {
+    uint64_t ret;
+    if (parse_uint_full(str, 10, &ret) || ret > UINT32_MAX) {
         dolog("Invalid integer value `%s'\n", str);
         exit(1);
     }
diff --git a/block/gluster.c b/block/gluster.c
index 185a83e5e53..ad5fadbe793 100644
--- a/block/gluster.c
+++ b/block/gluster.c
@@ -424,7 +424,7 @@ static struct glfs *qemu_gluster_glfs_init(BlockdevOptionsGluster *gconf,
     int ret;
     int old_errno;
     SocketAddressList *server;
-    unsigned long long port;
+    uint64_t port;

     glfs = glfs_find_preopened(gconf->volume);
     if (glfs) {
@@ -445,7 +445,7 @@ static struct glfs *qemu_gluster_glfs_init(BlockdevOptionsGluster *gconf,
                                    server->value->u.q_unix.path, 0);
             break;
         case SOCKET_ADDRESS_TYPE_INET:
-            if (parse_uint_full(server->value->u.inet.port, &port, 10) < 0 ||
+            if (parse_uint_full(server->value->u.inet.port, 10, &port) < 0 ||
                 port > 65535) {
                 error_setg(errp, "'%s' is not a valid port number",
                            server->value->u.inet.port);
diff --git a/block/nfs.c b/block/nfs.c
index 006045d71a6..fabea0386a3 100644
--- a/block/nfs.c
+++ b/block/nfs.c
@@ -114,13 +114,13 @@ static int nfs_parse_uri(const char *filename, QDict *options, Error **errp)
     qdict_put_str(options, "path", uri->path);

     for (i = 0; i < qp->n; i++) {
-        unsigned long long val;
+        uint64_t val;
         if (!qp->p[i].value) {
             error_setg(errp, "Value for NFS parameter expected: %s",
                        qp->p[i].name);
             goto out;
         }
-        if (parse_uint_full(qp->p[i].value, &val, 0)) {
+        if (parse_uint_full(qp->p[i].value, 0, &val)) {
             error_setg(errp, "Illegal value for NFS parameter: %s",
                        qp->p[i].name);
             goto out;
diff --git a/blockdev.c b/blockdev.c
index d141ca7a2d5..162eaffae16 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -341,10 +341,10 @@ static bool parse_stats_intervals(BlockAcctStats *stats, QList *intervals,
         switch (qobject_type(entry->value)) {

         case QTYPE_QSTRING: {
-            unsigned long long length;
+            uint64_t length;
             const char *str = qstring_get_str(qobject_to(QString,
                                                          entry->value));
-            if (parse_uint_full(str, &length, 10) == 0 &&
+            if (parse_uint_full(str, 10, &length) == 0 &&
                 length > 0 && length <= UINT_MAX) {
                 block_acct_add_interval(stats, (unsigned) length);
             } else {
diff --git a/contrib/ivshmem-server/main.c b/contrib/ivshmem-server/main.c
index 224dbeb547e..5901f17707e 100644
--- a/contrib/ivshmem-server/main.c
+++ b/contrib/ivshmem-server/main.c
@@ -69,7 +69,7 @@ static void
 ivshmem_server_parse_args(IvshmemServerArgs *args, int argc, char *argv[])
 {
     int c;
-    unsigned long long v;
+    uint64_t v;
     Error *err = NULL;

     while ((c = getopt(argc, argv, "hvFp:S:m:M:l:n:")) != -1) {
@@ -112,7 +112,7 @@ ivshmem_server_parse_args(IvshmemServerArgs *args, int argc, char *argv[])
             break;

         case 'n': /* number of vectors */
-            if (parse_uint_full(optarg, &v, 0) < 0) {
+            if (parse_uint_full(optarg, 0, &v) < 0) {
                 fprintf(stderr, "cannot parse n_vectors\n");
                 ivshmem_server_help(argv[0]);
                 exit(1);
diff --git a/qapi/opts-visitor.c b/qapi/opts-visitor.c
index 587f31baf6b..8812d23677a 100644
--- a/qapi/opts-visitor.c
+++ b/qapi/opts-visitor.c
@@ -454,8 +454,8 @@ opts_type_uint64(Visitor *v, const char *name, uint64_t *obj, Error **errp)
     OptsVisitor *ov = to_ov(v);
     const QemuOpt *opt;
     const char *str;
-    unsigned long long val;
-    char *endptr;
+    uint64_t val;
+    const char *endptr;

     if (ov->list_mode == LM_UNSIGNED_INTERVAL) {
         *obj = ov->range_next.u;
@@ -471,17 +471,17 @@ opts_type_uint64(Visitor *v, const char *name, uint64_t *obj, Error **errp)
     /* we've gotten past lookup_scalar() */
     assert(ov->list_mode == LM_NONE || ov->list_mode == LM_IN_PROGRESS);

-    if (parse_uint(str, &val, &endptr, 0) == 0 && val <= UINT64_MAX) {
+    if (parse_uint(str, &endptr, 0, &val) == 0 && val <= UINT64_MAX) {
         if (*endptr == '\0') {
             *obj = val;
             processed(ov, name);
             return true;
         }
         if (*endptr == '-' && ov->list_mode == LM_IN_PROGRESS) {
-            unsigned long long val2;
+            uint64_t val2;

             str = endptr + 1;
-            if (parse_uint_full(str, &val2, 0) == 0 &&
+            if (parse_uint_full(str, 0, &val2) == 0 &&
                 val2 <= UINT64_MAX && val <= val2 &&
                 val2 - val < OPTS_VISITOR_RANGE_MAX) {
                 ov->range_next.u = val;
diff --git a/tests/unit/test-cutils.c b/tests/unit/test-cutils.c
index 08989d1d3ac..0c7d07b3297 100644
--- a/tests/unit/test-cutils.c
+++ b/tests/unit/test-cutils.c
@@ -31,12 +31,11 @@

 static void test_parse_uint_null(void)
 {
-    unsigned long long i = 999;
-    char f = 'X';
-    char *endptr = &f;
+    uint64_t i = 999;
+    const char *endptr = "somewhere";
     int r;

-    r = parse_uint(NULL, &i, &endptr, 0);
+    r = parse_uint(NULL, &endptr, 0, &i);

     g_assert_cmpint(r, ==, -EINVAL);
     g_assert_cmpuint(i, ==, 0);
@@ -45,13 +44,12 @@ static void test_parse_uint_null(void)

 static void test_parse_uint_empty(void)
 {
-    unsigned long long i = 999;
-    char f = 'X';
-    char *endptr = &f;
+    uint64_t i = 999;
+    const char *endptr = "somewhere";
     const char *str = "";
     int r;

-    r = parse_uint(str, &i, &endptr, 0);
+    r = parse_uint(str, &endptr, 0, &i);

     g_assert_cmpint(r, ==, -EINVAL);
     g_assert_cmpuint(i, ==, 0);
@@ -60,13 +58,12 @@ static void test_parse_uint_empty(void)

 static void test_parse_uint_whitespace(void)
 {
-    unsigned long long i = 999;
-    char f = 'X';
-    char *endptr = &f;
+    uint64_t i = 999;
+    const char *endptr = "somewhere";
     const char *str = "   \t   ";
     int r;

-    r = parse_uint(str, &i, &endptr, 0);
+    r = parse_uint(str, &endptr, 0, &i);

     g_assert_cmpint(r, ==, -EINVAL);
     g_assert_cmpuint(i, ==, 0);
@@ -76,13 +73,12 @@ static void test_parse_uint_whitespace(void)

 static void test_parse_uint_invalid(void)
 {
-    unsigned long long i = 999;
-    char f = 'X';
-    char *endptr = &f;
+    uint64_t i = 999;
+    const char *endptr = "somewhere";
     const char *str = " \t xxx";
     int r;

-    r = parse_uint(str, &i, &endptr, 0);
+    r = parse_uint(str, &endptr, 0, &i);

     g_assert_cmpint(r, ==, -EINVAL);
     g_assert_cmpuint(i, ==, 0);
@@ -92,13 +88,12 @@ static void test_parse_uint_invalid(void)

 static void test_parse_uint_trailing(void)
 {
-    unsigned long long i = 999;
-    char f = 'X';
-    char *endptr = &f;
+    uint64_t i = 999;
+    const char *endptr = "somewhere";
     const char *str = "123xxx";
     int r;

-    r = parse_uint(str, &i, &endptr, 0);
+    r = parse_uint(str, &endptr, 0, &i);

     g_assert_cmpint(r, ==, 0);
     g_assert_cmpuint(i, ==, 123);
@@ -107,13 +102,12 @@ static void test_parse_uint_trailing(void)

 static void test_parse_uint_correct(void)
 {
-    unsigned long long i = 999;
-    char f = 'X';
-    char *endptr = &f;
+    uint64_t i = 999;
+    const char *endptr = "somewhere";
     const char *str = "123";
     int r;

-    r = parse_uint(str, &i, &endptr, 0);
+    r = parse_uint(str, &endptr, 0, &i);

     g_assert_cmpint(r, ==, 0);
     g_assert_cmpuint(i, ==, 123);
@@ -122,13 +116,12 @@ static void test_parse_uint_correct(void)

 static void test_parse_uint_octal(void)
 {
-    unsigned long long i = 999;
-    char f = 'X';
-    char *endptr = &f;
+    uint64_t i = 999;
+    const char *endptr = "somewhere";
     const char *str = "0123";
     int r;

-    r = parse_uint(str, &i, &endptr, 0);
+    r = parse_uint(str, &endptr, 0, &i);

     g_assert_cmpint(r, ==, 0);
     g_assert_cmpuint(i, ==, 0123);
@@ -137,13 +130,12 @@ static void test_parse_uint_octal(void)

 static void test_parse_uint_decimal(void)
 {
-    unsigned long long i = 999;
-    char f = 'X';
-    char *endptr = &f;
+    uint64_t i = 999;
+    const char *endptr = "somewhere";
     const char *str = "0123";
     int r;

-    r = parse_uint(str, &i, &endptr, 10);
+    r = parse_uint(str, &endptr, 10, &i);

     g_assert_cmpint(r, ==, 0);
     g_assert_cmpuint(i, ==, 123);
@@ -152,13 +144,12 @@ static void test_parse_uint_decimal(void)

 static void test_parse_uint_llong_max(void)
 {
-    unsigned long long i = 999;
-    char f = 'X';
-    char *endptr = &f;
+    uint64_t i = 999;
+    const char *endptr = "somewhere";
     char *str = g_strdup_printf("%llu", (unsigned long long)LLONG_MAX + 1);
     int r;

-    r = parse_uint(str, &i, &endptr, 0);
+    r = parse_uint(str, &endptr, 0, &i);

     g_assert_cmpint(r, ==, 0);
     g_assert_cmpuint(i, ==, (unsigned long long)LLONG_MAX + 1);
@@ -169,13 +160,12 @@ static void test_parse_uint_llong_max(void)

 static void test_parse_uint_max(void)
 {
-    unsigned long long i = 999;
-    char f = 'X';
-    char *endptr = &f;
+    uint64_t i = 999;
+    const char *endptr = "somewhere";
     char *str = g_strdup_printf("%llu", ULLONG_MAX);
     int r;

-    r = parse_uint(str, &i, &endptr, 0);
+    r = parse_uint(str, &endptr, 0, &i);

     g_assert_cmpint(r, ==, 0);
     g_assert_cmpuint(i, ==, ULLONG_MAX);
@@ -186,32 +176,31 @@ static void test_parse_uint_max(void)

 static void test_parse_uint_overflow(void)
 {
-    unsigned long long i;
-    char f = 'X';
-    char *endptr;
+    uint64_t i;
+    const char *endptr = "somewhere";
     const char *str;
     int r;

     i = 999;
-    endptr = &f;
+    endptr = "somewhere";
     str = "99999999999999999999999999999999999999";
-    r = parse_uint(str, &i, &endptr, 0);
+    r = parse_uint(str, &endptr, 0, &i);
     g_assert_cmpint(r, ==, -ERANGE);
     g_assert_cmpuint(i, ==, ULLONG_MAX);
     g_assert_true(endptr == str + strlen(str));

     i = 999;
-    endptr = &f;
+    endptr = "somewhere";
     str = "0x10000000000000000"; /* 65 bits, 64-bit sign bit clear */
-    r = parse_uint(str, &i, &endptr, 0);
+    r = parse_uint(str, &endptr, 0, &i);
     g_assert_cmpint(r, ==, -ERANGE);
     g_assert_cmpuint(i, ==, ULLONG_MAX);
     g_assert_true(endptr == str + strlen(str));

     i = 999;
-    endptr = &f;
+    endptr = "somewhere";
     str = "0x18000000080000000"; /* 65 bits, 64-bit sign bit set */
-    r = parse_uint(str, &i, &endptr, 0);
+    r = parse_uint(str, &endptr, 0, &i);
     g_assert_cmpint(r, ==, -ERANGE);
     g_assert_cmpuint(i, ==, ULLONG_MAX);
     g_assert_true(endptr == str + strlen(str));
@@ -219,13 +208,12 @@ static void test_parse_uint_overflow(void)

 static void test_parse_uint_negative(void)
 {
-    unsigned long long i = 999;
-    char f = 'X';
-    char *endptr = &f;
+    uint64_t i = 999;
+    const char *endptr = "somewhere";
     const char *str = " \t -321";
     int r;

-    r = parse_uint(str, &i, &endptr, 0);
+    r = parse_uint(str, &endptr, 0, &i);

     g_assert_cmpint(r, ==, -ERANGE);
     g_assert_cmpuint(i, ==, 0);
@@ -234,13 +222,12 @@ static void test_parse_uint_negative(void)

 static void test_parse_uint_negzero(void)
 {
-    unsigned long long i = 999;
-    char f = 'X';
-    char *endptr = &f;
+    uint64_t i = 999;
+    const char *endptr = "somewhere";
     const char *str = " -0";
     int r;

-    r = parse_uint(str, &i, &endptr, 0);
+    r = parse_uint(str, &endptr, 0, &i);

     g_assert_cmpint(r, ==, -ERANGE);
     g_assert_cmpuint(i, ==, 0);
@@ -249,11 +236,11 @@ static void test_parse_uint_negzero(void)

 static void test_parse_uint_full_trailing(void)
 {
-    unsigned long long i = 999;
+    uint64_t i = 999;
     const char *str = "123xxx";
     int r;

-    r = parse_uint_full(str, &i, 0);
+    r = parse_uint_full(str, 0, &i);

     g_assert_cmpint(r, ==, -EINVAL);
     g_assert_cmpuint(i, ==, 0);
@@ -261,11 +248,11 @@ static void test_parse_uint_full_trailing(void)

 static void test_parse_uint_full_correct(void)
 {
-    unsigned long long i = 999;
+    uint64_t i = 999;
     const char *str = "123";
     int r;

-    r = parse_uint_full(str, &i, 0);
+    r = parse_uint_full(str, 0, &i);

     g_assert_cmpint(r, ==, 0);
     g_assert_cmpuint(i, ==, 123);
@@ -274,11 +261,11 @@ static void test_parse_uint_full_correct(void)
 static void test_parse_uint_full_erange_junk(void)
 {
     /* FIXME - inconsistent with qemu_strto* which favors EINVAL */
-    unsigned long long i = 999;
+    uint64_t i = 999;
     const char *str = "-2junk";
     int r;

-    r = parse_uint_full(str, &i, 0);
+    r = parse_uint_full(str, 0, &i);

     g_assert_cmpint(r, ==, -ERANGE /* FIXME -EINVAL */);
     g_assert_cmpuint(i, ==, 0);
diff --git a/ui/vnc.c b/ui/vnc.c
index 9d8a24dd8a6..92964dcc0c0 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -3728,7 +3728,7 @@ static int vnc_display_get_address(const char *addrstr,
     } else {
         const char *port;
         size_t hostlen;
-        unsigned long long baseport = 0;
+        uint64_t baseport = 0;
         InetSocketAddress *inet;

         port = strrchr(addrstr, ':');
@@ -3776,7 +3776,7 @@ static int vnc_display_get_address(const char *addrstr,
             }
         } else {
             int offset = reverse ? 0 : 5900;
-            if (parse_uint_full(port, &baseport, 10) < 0) {
+            if (parse_uint_full(port, 10, &baseport) < 0) {
                 error_setg(errp, "can't convert to a number: %s", port);
                 goto cleanup;
             }
diff --git a/util/cutils.c b/util/cutils.c
index 4e3cc6e3605..8ab0cae5e75 100644
--- a/util/cutils.c
+++ b/util/cutils.c
@@ -715,10 +715,10 @@ const char *qemu_strchrnul(const char *s, int c)
  * parse_uint:
  *
  * @s: String to parse
- * @value: Destination for parsed integer value
  * @endptr: Destination for pointer to first character not consumed, must
  * not be %NULL
  * @base: integer base, between 2 and 36 inclusive, or 0
+ * @value: Destination for parsed integer value
  *
  * Parse unsigned integer
  *
@@ -741,8 +741,7 @@ const char *qemu_strchrnul(const char *s, int c)
  *
  * Else, set *@value to the parsed integer, and return 0.
  */
-int parse_uint(const char *s, unsigned long long *value, char **endptr,
-               int base)
+int parse_uint(const char *s, const char **endptr, int base, uint64_t *value)
 {
     int r = 0;
     char *endp = (char *)s;
@@ -786,8 +785,8 @@ out:
  * parse_uint_full:
  *
  * @s: String to parse
- * @value: Destination for parsed integer value
  * @base: integer base, between 2 and 36 inclusive, or 0
+ * @value: Destination for parsed integer value
  *
  * Parse unsigned integer from entire string
  *
@@ -796,12 +795,12 @@ out:
  * characters are present after a non-overflowing parsed number, the
  * function will return -EINVAL, and *@v will be set to 0.
  */
-int parse_uint_full(const char *s, unsigned long long *value, int base)
+int parse_uint_full(const char *s, int base, uint64_t *value)
 {
-    char *endp;
+    const char *endp;
     int r;

-    r = parse_uint(s, value, &endp, base);
+    r = parse_uint(s, &endp, base, value);
     if (r < 0) {
         return r;
     }
diff --git a/util/guest-random.c b/util/guest-random.c
index a24d27624c6..9465dda085d 100644
--- a/util/guest-random.c
+++ b/util/guest-random.c
@@ -89,8 +89,8 @@ void qemu_guest_random_seed_thread_part2(uint64_t seed)

 int qemu_guest_random_seed_main(const char *optarg, Error **errp)
 {
-    unsigned long long seed;
-    if (parse_uint_full(optarg, &seed, 0)) {
+    uint64_t seed;
+    if (parse_uint_full(optarg, 0, &seed)) {
         error_setg(errp, "Invalid seed number: %s", optarg);
         return -1;
     } else {
diff --git a/util/qemu-sockets.c b/util/qemu-sockets.c
index c06a4dce77c..892d33f5e6b 100644
--- a/util/qemu-sockets.c
+++ b/util/qemu-sockets.c
@@ -249,12 +249,12 @@ static int inet_listen_saddr(InetSocketAddress *saddr,

     /* lookup */
     if (port_offset) {
-        unsigned long long baseport;
+        uint64_t baseport;
         if (strlen(port) == 0) {
             error_setg(errp, "port not specified");
             return -1;
         }
-        if (parse_uint_full(port, &baseport, 10) < 0) {
+        if (parse_uint_full(port, 10, &baseport) < 0) {
             error_setg(errp, "can't convert to a number: %s", port);
             return -1;
         }
@@ -732,19 +732,19 @@ static bool vsock_parse_vaddr_to_sockaddr(const VsockSocketAddress *vaddr,
                                           struct sockaddr_vm *svm,
                                           Error **errp)
 {
-    unsigned long long val;
+    uint64_t val;

     memset(svm, 0, sizeof(*svm));
     svm->svm_family = AF_VSOCK;

-    if (parse_uint_full(vaddr->cid, &val, 10) < 0 ||
+    if (parse_uint_full(vaddr->cid, 10, &val) < 0 ||
         val > UINT32_MAX) {
         error_setg(errp, "Failed to parse cid '%s'", vaddr->cid);
         return false;
     }
     svm->svm_cid = val;

-    if (parse_uint_full(vaddr->port, &val, 10) < 0 ||
+    if (parse_uint_full(vaddr->port, 10, &val) < 0 ||
         val > UINT32_MAX) {
         error_setg(errp, "Failed to parse port '%s'", vaddr->port);
         return false;
-- 
2.40.1



^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v2 08/19] cutils: Allow NULL endptr in parse_uint()
  2023-05-12  2:10 [PATCH v2 00/19] Fix qemu_strtosz() read-out-of-bounds Eric Blake
                   ` (6 preceding siblings ...)
  2023-05-12  2:10 ` [PATCH v2 07/19] cutils: Adjust signature of parse_uint[_full] Eric Blake
@ 2023-05-12  2:10 ` Eric Blake
  2023-05-12 16:44   ` Eric Blake
  2023-05-19 14:54   ` Hanna Czenczek
  2023-05-12  2:10 ` [PATCH v2 09/19] test-cutils: Add coverage of qemu_strtod Eric Blake
                   ` (11 subsequent siblings)
  19 siblings, 2 replies; 48+ messages in thread
From: Eric Blake @ 2023-05-12  2:10 UTC (permalink / raw)
  To: qemu-devel; +Cc: hreitz, armbru, richard.henderson

All the qemu_strto*() functions permit a NULL endptr, just like their
libc counterparts, leaving parse_uint() as the oddball that caused
SEGFAULT on NULL and required the user to call parse_uint_full()
instead.  Relax things for consistency, even though the testsuite is
the only impacted caller.  Add one more unit test to ensure even
parse_uint_full(NULL, 0, &value) works.  This also fixes our code to
uniformly favor EINVAL over ERANGE when both apply.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 tests/unit/test-cutils.c | 18 ++++++++++++++++--
 util/cutils.c            | 34 ++++++++++++----------------------
 2 files changed, 28 insertions(+), 24 deletions(-)

diff --git a/tests/unit/test-cutils.c b/tests/unit/test-cutils.c
index 0c7d07b3297..d3076c3fec1 100644
--- a/tests/unit/test-cutils.c
+++ b/tests/unit/test-cutils.c
@@ -260,14 +260,26 @@ static void test_parse_uint_full_correct(void)

 static void test_parse_uint_full_erange_junk(void)
 {
-    /* FIXME - inconsistent with qemu_strto* which favors EINVAL */
+    /* EINVAL has priority over ERANGE */
     uint64_t i = 999;
     const char *str = "-2junk";
     int r;

     r = parse_uint_full(str, 0, &i);

-    g_assert_cmpint(r, ==, -ERANGE /* FIXME -EINVAL */);
+    g_assert_cmpint(r, ==, -EINVAL);
+    g_assert_cmpuint(i, ==, 0);
+}
+
+static void test_parse_uint_full_null(void)
+{
+    uint64_t i = 999;
+    const char *str = NULL;
+    int r;
+
+    r = parse_uint_full(str, 0, &i);
+
+    g_assert_cmpint(r, ==, -EINVAL);
     g_assert_cmpuint(i, ==, 0);
 }

@@ -3207,6 +3219,8 @@ int main(int argc, char **argv)
                     test_parse_uint_full_correct);
     g_test_add_func("/cutils/parse_uint_full/erange_junk",
                     test_parse_uint_full_erange_junk);
+    g_test_add_func("/cutils/parse_uint_full/null",
+                    test_parse_uint_full_null);

     /* qemu_strtoi() tests */
     g_test_add_func("/cutils/qemu_strtoi/correct",
diff --git a/util/cutils.c b/util/cutils.c
index 8ab0cae5e75..e599924a0c4 100644
--- a/util/cutils.c
+++ b/util/cutils.c
@@ -715,8 +715,7 @@ const char *qemu_strchrnul(const char *s, int c)
  * parse_uint:
  *
  * @s: String to parse
- * @endptr: Destination for pointer to first character not consumed, must
- * not be %NULL
+ * @endptr: Destination for pointer to first character not consumed
  * @base: integer base, between 2 and 36 inclusive, or 0
  * @value: Destination for parsed integer value
  *
@@ -730,7 +729,8 @@ const char *qemu_strchrnul(const char *s, int c)
  *
  * Set *@endptr to point right beyond the parsed integer (even if the integer
  * overflows or is negative, all digits will be parsed and *@endptr will
- * point right beyond them).
+ * point right beyond them).  If @endptr is %NULL, any trailing character
+ * instead causes a result of -EINVAL with *@value of 0.
  *
  * If the integer is negative, set *@value to 0, and return -ERANGE.
  * (If you want to allow negative numbers that wrap around within
@@ -777,7 +777,12 @@ int parse_uint(const char *s, const char **endptr, int base, uint64_t *value)

 out:
     *value = val;
-    *endptr = endp;
+    if (endptr) {
+        *endptr = endp;
+    } else if (s && *endp) {
+        r = -EINVAL;
+        *value = 0;
+    }
     return r;
 }

@@ -788,28 +793,13 @@ out:
  * @base: integer base, between 2 and 36 inclusive, or 0
  * @value: Destination for parsed integer value
  *
- * Parse unsigned integer from entire string
+ * Parse unsigned integer from entire string, rejecting any trailing slop.
  *
- * Have the same behavior of parse_uint(), but with an additional
- * check for additional data after the parsed number. If extra
- * characters are present after a non-overflowing parsed number, the
- * function will return -EINVAL, and *@v will be set to 0.
+ * Shorthand for parse_uint(s, NULL, base, value).
  */
 int parse_uint_full(const char *s, int base, uint64_t *value)
 {
-    const char *endp;
-    int r;
-
-    r = parse_uint(s, &endp, base, value);
-    if (r < 0) {
-        return r;
-    }
-    if (*endp) {
-        *value = 0;
-        return -EINVAL;
-    }
-
-    return 0;
+    return parse_uint(s, NULL, base, value);
 }

 int qemu_parse_fd(const char *param)
-- 
2.40.1



^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v2 09/19] test-cutils: Add coverage of qemu_strtod
  2023-05-12  2:10 [PATCH v2 00/19] Fix qemu_strtosz() read-out-of-bounds Eric Blake
                   ` (7 preceding siblings ...)
  2023-05-12  2:10 ` [PATCH v2 08/19] cutils: Allow NULL endptr in parse_uint() Eric Blake
@ 2023-05-12  2:10 ` Eric Blake
  2023-05-19 15:05   ` Hanna Czenczek
  2023-05-12  2:10 ` [PATCH v2 10/19] test-cutils: Prepare for upcoming semantic change in qemu_strtosz Eric Blake
                   ` (10 subsequent siblings)
  19 siblings, 1 reply; 48+ messages in thread
From: Eric Blake @ 2023-05-12  2:10 UTC (permalink / raw)
  To: qemu-devel; +Cc: hreitz, armbru, richard.henderson

It's hard to tweak code for consistency if I can't prove what will or
won't break from those tweaks.  Time to add unit tests for
qemu_strtod() and qemu_strtod_finite().

Among other things, I wrote a check whether we have C99 semantics for
strtod("0x1") (which MUST parse hex numbers) rather than C89 (which
must stop parsing at 'x').  These days, I suspect that is okay; but if
it fails CI checks, knowing the difference will help us decide what we
want to do about it.  Note that C2x, while not final at the time of
this patch, has been considering whether to make strtol("0b1") parse
as 1 with no slop instead of the C17 parse of 0 with slop "b1"; that
decision may also bleed over to strtod().  But for now, I didn't think
it worth adding unit tests on that front (to strtol or strtod) as
things may still change.

Likewise, there are plenty more corner cases of strtod proper that I
don't explicitly test here, but there are enough unit tests added here
that it covers all the branches reached in our wrappers.  In
particular, it demonstrates the difference on when *value is left
uninitialized, which an upcoming patch will normalize.

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

---

v2: Added g_assert_false(signbit(res)) anywhere I used
g_assert_cmpfloat(res,==,0.0); add a test for strtod() hex parsing and
handling of junk after ERANGE, which is major enough that I dropped
R-b
---
 tests/unit/test-cutils.c | 510 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 510 insertions(+)

diff --git a/tests/unit/test-cutils.c b/tests/unit/test-cutils.c
index d3076c3fec1..1763839a157 100644
--- a/tests/unit/test-cutils.c
+++ b/tests/unit/test-cutils.c
@@ -25,6 +25,8 @@
  * THE SOFTWARE.
  */

+#include <math.h>
+
 #include "qemu/osdep.h"
 #include "qemu/cutils.h"
 #include "qemu/units.h"
@@ -2668,6 +2670,485 @@ static void test_qemu_strtou64_full_erange_junk(void)
     g_assert_cmpint(res, ==, UINT64_MAX);
 }

+static void test_qemu_strtod_simple(void)
+{
+    const char *str;
+    const char *endptr;
+    int err;
+    double res;
+
+    /* no radix or exponent */
+    str = "1";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod(str, &endptr, &res);
+    g_assert_cmpint(err, ==, 0);
+    g_assert_cmpfloat(res, ==, 1.0);
+    g_assert_true(endptr == str + 1);
+
+    /* leading space and sign */
+    str = " -0.0";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod(str, &endptr, &res);
+    g_assert_cmpint(err, ==, 0);
+    g_assert_cmpfloat(res, ==, -0.0);
+    g_assert_true(signbit(res));
+    g_assert_true(endptr == str + 5);
+
+    /* fraction only */
+    str = "+.5";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod(str, &endptr, &res);
+    g_assert_cmpint(err, ==, 0);
+    g_assert_cmpfloat(res, ==, 0.5);
+    g_assert_true(endptr == str + 3);
+
+    /* exponent */
+    str = "1.e+1";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod(str, &endptr, &res);
+    g_assert_cmpint(err, ==, 0);
+    g_assert_cmpfloat(res, ==, 10.0);
+    g_assert_true(endptr == str + 5);
+
+    /* hex without radix */
+    str = "0x10";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod(str, &endptr, &res);
+    g_assert_cmpint(err, ==, 0);
+    g_assert_cmpfloat(res, ==, 16.0);
+    g_assert_true(endptr == str + 4);
+}
+
+static void test_qemu_strtod_einval(void)
+{
+    const char *str;
+    const char *endptr;
+    int err;
+    double res;
+
+    /* empty */
+    str = "";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod(str, &endptr, &res);
+    g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpfloat(res, ==, 0.0);
+    g_assert_false(signbit(res));
+    g_assert_true(endptr == str);
+
+    /* NULL */
+    str = NULL;
+    endptr = "random";
+    res = 999;
+    err = qemu_strtod(str, &endptr, &res);
+    g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpfloat(res, ==, 999.0);
+    g_assert_null(endptr);
+
+    /* not recognizable */
+    str = " junk";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod(str, &endptr, &res);
+    g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpfloat(res, ==, 0.0);
+    g_assert_false(signbit(res));
+    g_assert_true(endptr == str);
+}
+
+static void test_qemu_strtod_erange(void)
+{
+    const char *str;
+    const char *endptr;
+    int err;
+    double res;
+
+    /* overflow */
+    str = "9e999";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod(str, &endptr, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpfloat(res, ==, HUGE_VAL);
+    g_assert_true(endptr == str + 5);
+
+    str = "-9e+999";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod(str, &endptr, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpfloat(res, ==, -HUGE_VAL);
+    g_assert_true(endptr == str + 7);
+
+    /* underflow */
+    str = "-9e-999";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod(str, &endptr, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpfloat(res, >=, -DBL_MIN);
+    g_assert_cmpfloat(res, <=, -0.0);
+    g_assert_true(signbit(res));
+    g_assert_true(endptr == str + 7);
+}
+
+static void test_qemu_strtod_nonfinite(void)
+{
+    const char *str;
+    const char *endptr;
+    int err;
+    double res;
+
+    /* infinity */
+    str = "inf";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod(str, &endptr, &res);
+    g_assert_cmpint(err, ==, 0);
+    g_assert_true(isinf(res));
+    g_assert_false(signbit(res));
+    g_assert_true(endptr == str + 3);
+
+    str = "-infinity";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod(str, &endptr, &res);
+    g_assert_cmpint(err, ==, 0);
+    g_assert_true(isinf(res));
+    g_assert_true(signbit(res));
+    g_assert_true(endptr == str + 9);
+
+    /* not a number */
+    str = " NaN";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod(str, &endptr, &res);
+    g_assert_cmpint(err, ==, 0);
+    g_assert_true(isnan(res));
+    g_assert_true(endptr == str + 4);
+}
+
+static void test_qemu_strtod_trailing(void)
+{
+    const char *str;
+    const char *endptr;
+    int err;
+    double res;
+
+    /* trailing whitespace */
+    str = "1. ";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod(str, &endptr, &res);
+    g_assert_cmpint(err, ==, 0);
+    g_assert_cmpfloat(res, ==, 1.0);
+    g_assert_true(endptr == str + 2);
+
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod(str, NULL, &res);
+    g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpfloat(res, ==, 1.0);
+
+    /* trailing e is not an exponent */
+    str = ".5e";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod(str, &endptr, &res);
+    g_assert_cmpint(err, ==, 0);
+    g_assert_cmpfloat(res, ==, 0.5);
+    g_assert_true(endptr == str + 2);
+
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod(str, NULL, &res);
+    g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpfloat(res, ==, 0.5);
+
+    /* trailing ( not part of long NaN */
+    str = "nan(";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod(str, &endptr, &res);
+    g_assert_cmpint(err, ==, 0);
+    g_assert_true(isnan(res));
+    g_assert_true(endptr == str + 3);
+
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod(str, NULL, &res);
+    g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_true(isnan(res));
+}
+
+static void test_qemu_strtod_erange_junk(void)
+{
+    const char *str;
+    const char *endptr;
+    int err;
+    double res;
+
+    /* EINVAL has priority over ERANGE */
+    str = "1e-999junk";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod(str, &endptr, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpfloat(res, <=, DBL_MIN);
+    g_assert_cmpfloat(res, >=, 0.0);
+    g_assert_false(signbit(res));
+    g_assert_true(endptr == str + 6);
+
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod(str, NULL, &res);
+    g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpfloat(res, ==, 0.0);
+    g_assert_false(signbit(res));
+}
+
+static void test_qemu_strtod_finite_simple(void)
+{
+    const char *str;
+    const char *endptr;
+    int err;
+    double res;
+
+    /* no radix or exponent */
+    str = "1";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod_finite(str, &endptr, &res);
+    g_assert_cmpint(err, ==, 0);
+    g_assert_cmpfloat(res, ==, 1.0);
+    g_assert_true(endptr == str + 1);
+
+    /* leading space and sign */
+    str = " -0.0";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod_finite(str, &endptr, &res);
+    g_assert_cmpint(err, ==, 0);
+    g_assert_cmpfloat(res, ==, -0.0);
+    g_assert_true(signbit(res));
+    g_assert_true(endptr == str + 5);
+
+    /* fraction only */
+    str = "+.5";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod_finite(str, &endptr, &res);
+    g_assert_cmpint(err, ==, 0);
+    g_assert_cmpfloat(res, ==, 0.5);
+    g_assert_true(endptr == str + 3);
+
+    /* exponent */
+    str = "1.e+1";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod_finite(str, &endptr, &res);
+    g_assert_cmpint(err, ==, 0);
+    g_assert_cmpfloat(res, ==, 10.0);
+    g_assert_true(endptr == str + 5);
+
+    /* hex without radix */
+    str = "0x10";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod(str, &endptr, &res);
+    g_assert_cmpint(err, ==, 0);
+    g_assert_cmpfloat(res, ==, 16.0);
+    g_assert_true(endptr == str + 4);
+}
+
+static void test_qemu_strtod_finite_einval(void)
+{
+    const char *str;
+    const char *endptr;
+    int err;
+    double res;
+
+    /* empty */
+    str = "";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod_finite(str, &endptr, &res);
+    g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpfloat(res, ==, 999.0);
+    g_assert_true(endptr == str);
+
+    /* NULL */
+    str = NULL;
+    endptr = "random";
+    res = 999;
+    err = qemu_strtod_finite(str, &endptr, &res);
+    g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpfloat(res, ==, 999.0);
+    g_assert_null(endptr);
+
+    /* not recognizable */
+    str = " junk";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod_finite(str, &endptr, &res);
+    g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpfloat(res, ==, 999.0);
+    g_assert_true(endptr == str);
+}
+
+static void test_qemu_strtod_finite_erange(void)
+{
+    const char *str;
+    const char *endptr;
+    int err;
+    double res;
+
+    /* overflow */
+    str = "9e999";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod_finite(str, &endptr, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpfloat(res, ==, HUGE_VAL);
+    g_assert_true(endptr == str + 5);
+
+    str = "-9e+999";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod_finite(str, &endptr, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpfloat(res, ==, -HUGE_VAL);
+    g_assert_true(endptr == str + 7);
+
+    /* underflow */
+    str = "-9e-999";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod_finite(str, &endptr, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpfloat(res, >=, -DBL_MIN);
+    g_assert_cmpfloat(res, <=, -0.0);
+    g_assert_true(signbit(res));
+    g_assert_true(endptr == str + 7);
+}
+
+static void test_qemu_strtod_finite_nonfinite(void)
+{
+    const char *str;
+    const char *endptr;
+    int err;
+    double res;
+
+    /* infinity */
+    str = "inf";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod_finite(str, &endptr, &res);
+    g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpfloat(res, ==, 999.0);
+    g_assert_true(endptr == str);
+
+    str = "-infinity";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod_finite(str, &endptr, &res);
+    g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpfloat(res, ==, 999.0);
+    g_assert_true(endptr == str);
+
+    /* not a number */
+    str = " NaN";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod_finite(str, &endptr, &res);
+    g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpfloat(res, ==, 999.0);
+    g_assert_true(endptr == str);
+}
+
+static void test_qemu_strtod_finite_trailing(void)
+{
+    const char *str;
+    const char *endptr;
+    int err;
+    double res;
+
+    /* trailing whitespace */
+    str = "1. ";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod_finite(str, &endptr, &res);
+    g_assert_cmpint(err, ==, 0);
+    g_assert_cmpfloat(res, ==, 1.0);
+    g_assert_true(endptr == str + 2);
+
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod_finite(str, NULL, &res);
+    g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpfloat(res, ==, 999.0);
+
+    /* trailing e is not an exponent */
+    str = ".5e";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod_finite(str, &endptr, &res);
+    g_assert_cmpint(err, ==, 0);
+    g_assert_cmpfloat(res, ==, 0.5);
+    g_assert_true(endptr == str + 2);
+
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod_finite(str, NULL, &res);
+    g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpfloat(res, ==, 999.0);
+
+    /* trailing ( not part of long NaN */
+    str = "nan(";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod_finite(str, &endptr, &res);
+    g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpfloat(res, ==, 999.0);
+    g_assert_true(endptr == str);
+
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod_finite(str, NULL, &res);
+    g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpfloat(res, ==, 999.0);
+}
+
+static void test_qemu_strtod_finite_erange_junk(void)
+{
+    const char *str;
+    const char *endptr;
+    int err;
+    double res;
+
+    /* EINVAL has priority over ERANGE */
+    str = "1e-999junk";
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod_finite(str, &endptr, &res);
+    g_assert_cmpint(err, ==, -ERANGE);
+    g_assert_cmpfloat(res, <=, DBL_MIN);
+    g_assert_cmpfloat(res, >=, 0.0);
+    g_assert_false(signbit(res));
+    g_assert_true(endptr == str + 6);
+
+    endptr = "somewhere";
+    res = 999;
+    err = qemu_strtod_finite(str, NULL, &res);
+    g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpfloat(res, ==, 999.0);
+}
+
 static void test_qemu_strtosz_simple(void)
 {
     const char *str;
@@ -3510,6 +3991,35 @@ int main(int argc, char **argv)
     g_test_add_func("/cutils/qemu_strtou64_full/erange_junk",
                     test_qemu_strtou64_full_erange_junk);

+    /* qemu_strtod() tests */
+    g_test_add_func("/cutils/qemu_strtod/simple",
+                    test_qemu_strtod_simple);
+    g_test_add_func("/cutils/qemu_strtod/einval",
+                    test_qemu_strtod_einval);
+    g_test_add_func("/cutils/qemu_strtod/erange",
+                    test_qemu_strtod_erange);
+    g_test_add_func("/cutils/qemu_strtod/nonfinite",
+                    test_qemu_strtod_nonfinite);
+    g_test_add_func("/cutils/qemu_strtod/trailing",
+                    test_qemu_strtod_trailing);
+    g_test_add_func("/cutils/qemu_strtod/erange_junk",
+                    test_qemu_strtod_erange_junk);
+
+    /* qemu_strtod_finite() tests */
+    g_test_add_func("/cutils/qemu_strtod_finite/simple",
+                    test_qemu_strtod_finite_simple);
+    g_test_add_func("/cutils/qemu_strtod_finite/einval",
+                    test_qemu_strtod_finite_einval);
+    g_test_add_func("/cutils/qemu_strtod_finite/erange",
+                    test_qemu_strtod_finite_erange);
+    g_test_add_func("/cutils/qemu_strtod_finite/nonfinite",
+                    test_qemu_strtod_finite_nonfinite);
+    g_test_add_func("/cutils/qemu_strtod_finite/trailing",
+                    test_qemu_strtod_finite_trailing);
+    g_test_add_func("/cutils/qemu_strtod_finite/erange_junk",
+                    test_qemu_strtod_finite_erange_junk);
+
+    /* qemu_strtosz() tests */
     g_test_add_func("/cutils/strtosz/simple",
                     test_qemu_strtosz_simple);
     g_test_add_func("/cutils/strtosz/hex",
-- 
2.40.1



^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v2 10/19] test-cutils: Prepare for upcoming semantic change in qemu_strtosz
  2023-05-12  2:10 [PATCH v2 00/19] Fix qemu_strtosz() read-out-of-bounds Eric Blake
                   ` (8 preceding siblings ...)
  2023-05-12  2:10 ` [PATCH v2 09/19] test-cutils: Add coverage of qemu_strtod Eric Blake
@ 2023-05-12  2:10 ` Eric Blake
  2023-05-12  2:10 ` [PATCH v2 11/19] test-cutils: Refactor qemu_strtosz tests for less boilerplate Eric Blake
                   ` (9 subsequent siblings)
  19 siblings, 0 replies; 48+ messages in thread
From: Eric Blake @ 2023-05-12  2:10 UTC (permalink / raw)
  To: qemu-devel; +Cc: hreitz, armbru, richard.henderson

A quick search for 'qemu_strtosz' in the code base shows that outside
of the testsuite, the ONLY place that passes a non-NULL pointer to
@endptr of any variant of a size parser is in hmp.c (the 'o' parser of
monitor_parse_arguments), and that particular caller warns of
"extraneous characters at the end of line" unless the trailing bytes
are purely whitespace.  Thus, it makes no semantic difference at the
high level whether we parse "1.5e1k" as "1" + ".5e1" + "k" (an attempt
to use scientific notation in strtod with a scaling suffix of 'k' with
no trailing junk, but which qemu_strtosz says should fail with
EINVAL), or as "1.5e" + "1k" (a valid size with scaling suffix of 'e'
for exabytes, followed by two junk bytes) - either way, any user
passing such a string will get an error message about a parse failure.

However, an upcoming patch to qemu_strtosz will fix other corner case
bugs in handling the fractional portion of a size, and in doing so, it
is easier to declare that qemu_strtosz() itself stops parsing at the
first 'e' rather than blindly consuming whatever strtod() will
recognize.  Once that is fixed, the difference will be visible at the
low level (getting a valid parse with trailing garbage when @endptr is
non-NULL, while continuing to get -EINVAL when @endptr is NULL); this
is easier to demonstrate by moving the affected strings from
test_qemu_strtosz_invalid() (which declares them as always -EINVAL) to
test_qemu_strtosz_trailing() (where @endptr affects behavior, for now
with FIXME comments).

Note that a similar argument could be made for having "0x1.5" or
"0x1M" parse as 0x1 with ".5" or "M" as trailing junk, instead of
blindly treating it as -EINVAL; however, as these cases do not suffer
from the same problems as floating point, they are not worth changing
at this time.

Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Hanna Czenczek <hreitz@redhat.com>
---
 tests/unit/test-cutils.c | 42 ++++++++++++++++++++++++++--------------
 1 file changed, 27 insertions(+), 15 deletions(-)

diff --git a/tests/unit/test-cutils.c b/tests/unit/test-cutils.c
index 1763839a157..fe9be690faf 100644
--- a/tests/unit/test-cutils.c
+++ b/tests/unit/test-cutils.c
@@ -3440,21 +3440,6 @@ static void test_qemu_strtosz_invalid(void)
     g_assert_cmphex(res, ==, 0xbaadf00d);
     g_assert_true(endptr == str);

-    /* No floating point exponents */
-    str = "1.5e1k";
-    endptr = NULL;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmphex(res, ==, 0xbaadf00d);
-    g_assert_true(endptr == str);
-
-    str = "1.5E+0k";
-    endptr = NULL;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmphex(res, ==, 0xbaadf00d);
-    g_assert_true(endptr == str);
-
     /* No hex fractions */
     str = "0x1.8k";
     endptr = NULL;
@@ -3558,6 +3543,33 @@ static void test_qemu_strtosz_trailing(void)
     err = qemu_strtosz(str, NULL, &res);
     g_assert_cmpint(err, ==, -EINVAL);
     g_assert_cmphex(res, ==, 0xbaadf00d);
+
+    /* FIXME should stop parse after 'e'. No floating point exponents */
+    str = "1.5e1k";
+    endptr = NULL;
+    res = 0xbaadf00d;
+    err = qemu_strtosz(str, &endptr, &res);
+    g_assert_cmpint(err, ==, -EINVAL /* FIXME 0 */);
+    g_assert_cmphex(res, ==, 0xbaadf00d /* FIXME EiB * 1.5 */);
+    g_assert_true(endptr == str /* FIXME + 4 */);
+
+    res = 0xbaadf00d;
+    err = qemu_strtosz(str, NULL, &res);
+    g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpint(res, ==, 0xbaadf00d);
+
+    str = "1.5E+0k";
+    endptr = NULL;
+    res = 0xbaadf00d;
+    err = qemu_strtosz(str, &endptr, &res);
+    g_assert_cmpint(err, ==, -EINVAL /* FIXME 0 */);
+    g_assert_cmphex(res, ==, 0xbaadf00d /* FIXME EiB * 1.5 */);
+    g_assert_true(endptr == str /* FIXME + 4 */);
+
+    res = 0xbaadf00d;
+    err = qemu_strtosz(str, NULL, &res);
+    g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmphex(res, ==, 0xbaadf00d);
 }

 static void test_qemu_strtosz_erange(void)
-- 
2.40.1



^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v2 11/19] test-cutils: Refactor qemu_strtosz tests for less boilerplate
  2023-05-12  2:10 [PATCH v2 00/19] Fix qemu_strtosz() read-out-of-bounds Eric Blake
                   ` (9 preceding siblings ...)
  2023-05-12  2:10 ` [PATCH v2 10/19] test-cutils: Prepare for upcoming semantic change in qemu_strtosz Eric Blake
@ 2023-05-12  2:10 ` Eric Blake
  2023-05-19 15:13   ` Hanna Czenczek
  2023-05-12  2:10 ` [PATCH v2 12/19] cutils: Allow NULL str in qemu_strtosz Eric Blake
                   ` (8 subsequent siblings)
  19 siblings, 1 reply; 48+ messages in thread
From: Eric Blake @ 2023-05-12  2:10 UTC (permalink / raw)
  To: qemu-devel; +Cc: hreitz, armbru, richard.henderson

No need to copy-and-paste lots of boilerplate per string tested, when
we can consolidate that behind helper functions.  Plus, this adds a
bit more coverage (we now test all strings both with and without
endptr, whereas before some tests skipped the NULL endptr case), which
exposed a SEGFAULT on qemu_strtosz(NULL, NULL, &val) that will be
parsed in an upcoming patch.

Note that duplicating boilerplate has one advantage lost here - a
failed test tells you which line number failed; but a helper function
does not show the call stack that reached the failure.  Since we call
the helper more than once within many of the "unit tests", even the
unit test name doesn't point out which call is failing.  But that only
matters when tests fail (they normally pass); at which point I'm
debugging the failures under gdb anyways, so I'm not too worried about
it.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 tests/unit/test-cutils.c | 503 ++++++++-------------------------------
 1 file changed, 100 insertions(+), 403 deletions(-)

diff --git a/tests/unit/test-cutils.c b/tests/unit/test-cutils.c
index fe9be690faf..5c9ed78be93 100644
--- a/tests/unit/test-cutils.c
+++ b/tests/unit/test-cutils.c
@@ -3149,473 +3149,170 @@ static void test_qemu_strtod_finite_erange_junk(void)
     g_assert_cmpfloat(res, ==, 999.0);
 }

+typedef int (*qemu_strtosz_fn)(const char *, const char **, uint64_t *);
+static void do_strtosz_full(const char *str, qemu_strtosz_fn fn,
+                            int exp_ptr_ret, uint64_t exp_ptr_val,
+                            size_t exp_ptr_offset, int exp_null_ret,
+                            uint64_t exp_null_val)
+{
+    const char *endptr = "somewhere";
+    uint64_t val = 0xbaadf00d;
+    int ret;
+
+    ret = fn(str, &endptr, &val);
+    g_assert_cmpint(ret, ==, exp_ptr_ret);
+    g_assert_cmpuint(val, ==, exp_ptr_val);
+    g_assert_true(endptr == str + exp_ptr_offset);
+
+    val = 0xbaadf00d;
+    ret = fn(str, NULL, &val);
+    g_assert_cmpint(ret, ==, exp_null_ret);
+    g_assert_cmpuint(val, ==, exp_null_val);
+}
+
+static void do_strtosz(const char *str, int exp_ret, uint64_t exp_val,
+                       size_t exp_offset)
+{
+    do_strtosz_full(str, qemu_strtosz, exp_ret, exp_val, exp_offset,
+                    exp_ret, exp_val);
+}
+
+static void do_strtosz_MiB(const char *str, int exp_ret, uint64_t exp_val,
+                           size_t exp_offset)
+{
+    do_strtosz_full(str, qemu_strtosz_MiB, exp_ret, exp_val, exp_offset,
+                    exp_ret, exp_val);
+}
+
+static void do_strtosz_metric(const char *str, int exp_ret, uint64_t exp_val,
+                              size_t exp_offset)
+{
+    do_strtosz_full(str, qemu_strtosz_metric, exp_ret, exp_val, exp_offset,
+                    exp_ret, exp_val);
+}
+
 static void test_qemu_strtosz_simple(void)
 {
-    const char *str;
-    const char *endptr;
-    int err;
-    uint64_t res;
-
-    str = "0";
-    endptr = str;
-    res = 0xbaadf00d;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, 0);
-    g_assert_cmpuint(res, ==, 0);
-    g_assert_true(endptr == str + 1);
+    do_strtosz("0", 0, 0, 1);

     /* Leading 0 gives decimal results, not octal */
-    str = "08";
-    endptr = str;
-    res = 0xbaadf00d;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, 0);
-    g_assert_cmpuint(res, ==, 8);
-    g_assert_true(endptr == str + 2);
+    do_strtosz("08", 0, 8, 2);

     /* Leading space is ignored */
-    str = " 12345";
-    endptr = str;
-    res = 0xbaadf00d;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, 0);
-    g_assert_cmpuint(res, ==, 12345);
-    g_assert_true(endptr == str + 6);
+    do_strtosz(" 12345", 0, 12345, 6);

-    res = 0xbaadf00d;
-    err = qemu_strtosz(str, NULL, &res);
-    g_assert_cmpint(err, ==, 0);
-    g_assert_cmpuint(res, ==, 12345);
+    /* 2^53-1 */
+    do_strtosz("9007199254740991", 0, 0x1fffffffffffffULL, 16);

-    str = "9007199254740991"; /* 2^53-1 */
-    endptr = str;
-    res = 0xbaadf00d;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, 0);
-    g_assert_cmphex(res, ==, 0x1fffffffffffffULL);
-    g_assert_true(endptr == str + 16);
+    /* 2^53 */
+    do_strtosz("9007199254740992", 0, 0x20000000000000ULL, 16);

-    str = "9007199254740992"; /* 2^53 */
-    endptr = str;
-    res = 0xbaadf00d;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, 0);
-    g_assert_cmphex(res, ==, 0x20000000000000ULL);
-    g_assert_true(endptr == str + 16);
+    /* 2^53+1 */
+    do_strtosz("9007199254740993", 0, 0x20000000000001ULL, 16);

-    str = "9007199254740993"; /* 2^53+1 */
-    endptr = str;
-    res = 0xbaadf00d;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, 0);
-    g_assert_cmphex(res, ==, 0x20000000000001ULL);
-    g_assert_true(endptr == str + 16);
+    /* 0xfffffffffffff800 (53 msbs set) */
+    do_strtosz("18446744073709549568", 0, 0xfffffffffffff800ULL, 20);

-    str = "18446744073709549568"; /* 0xfffffffffffff800 (53 msbs set) */
-    endptr = str;
-    res = 0xbaadf00d;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, 0);
-    g_assert_cmphex(res, ==, 0xfffffffffffff800ULL);
-    g_assert_true(endptr == str + 20);
+    /* 0xfffffffffffffbff */
+    do_strtosz("18446744073709550591", 0, 0xfffffffffffffbffULL, 20);

-    str = "18446744073709550591"; /* 0xfffffffffffffbff */
-    endptr = str;
-    res = 0xbaadf00d;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, 0);
-    g_assert_cmphex(res, ==, 0xfffffffffffffbffULL);
-    g_assert_true(endptr == str + 20);
-
-    str = "18446744073709551615"; /* 0xffffffffffffffff */
-    endptr = str;
-    res = 0xbaadf00d;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, 0);
-    g_assert_cmphex(res, ==, 0xffffffffffffffffULL);
-    g_assert_true(endptr == str + 20);
+    /* 0xffffffffffffffff */
+    do_strtosz("18446744073709551615", 0, 0xffffffffffffffffULL, 20);
 }

 static void test_qemu_strtosz_hex(void)
 {
-    const char *str;
-    const char *endptr;
-    int err;
-    uint64_t res;
+    do_strtosz("0x0", 0, 0, 3);

-    str = "0x0";
-    endptr = str;
-    res = 0xbaadf00d;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, 0);
-    g_assert_cmpuint(res, ==, 0);
-    g_assert_true(endptr == str + 3);
+    do_strtosz("0xab", 0, 171, 4);

-    str = "0xab";
-    endptr = str;
-    res = 0xbaadf00d;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, 0);
-    g_assert_cmpuint(res, ==, 171);
-    g_assert_true(endptr == str + 4);
-
-    str = "0xae";
-    endptr = str;
-    res = 0xbaadf00d;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, 0);
-    g_assert_cmpuint(res, ==, 174);
-    g_assert_true(endptr == str + 4);
+    do_strtosz("0xae", 0, 174, 4);
 }

 static void test_qemu_strtosz_units(void)
 {
-    const char *none = "1";
-    const char *b = "1B";
-    const char *k = "1K";
-    const char *m = "1M";
-    const char *g = "1G";
-    const char *t = "1T";
-    const char *p = "1P";
-    const char *e = "1E";
-    int err;
-    const char *endptr;
-    uint64_t res;
-
     /* default is M */
-    endptr = NULL;
-    res = 0xbaadf00d;
-    err = qemu_strtosz_MiB(none, &endptr, &res);
-    g_assert_cmpint(err, ==, 0);
-    g_assert_cmpuint(res, ==, MiB);
-    g_assert_true(endptr == none + 1);
+    do_strtosz_MiB("1", 0, MiB, 1);

-    endptr = NULL;
-    res = 0xbaadf00d;
-    err = qemu_strtosz(b, &endptr, &res);
-    g_assert_cmpint(err, ==, 0);
-    g_assert_cmpuint(res, ==, 1);
-    g_assert_true(endptr == b + 2);
+    do_strtosz("1B", 0, 1, 2);

-    endptr = NULL;
-    res = 0xbaadf00d;
-    err = qemu_strtosz(k, &endptr, &res);
-    g_assert_cmpint(err, ==, 0);
-    g_assert_cmpuint(res, ==, KiB);
-    g_assert_true(endptr == k + 2);
-
-    endptr = NULL;
-    res = 0xbaadf00d;
-    err = qemu_strtosz(m, &endptr, &res);
-    g_assert_cmpint(err, ==, 0);
-    g_assert_cmpuint(res, ==, MiB);
-    g_assert_true(endptr == m + 2);
-
-    endptr = NULL;
-    res = 0xbaadf00d;
-    err = qemu_strtosz(g, &endptr, &res);
-    g_assert_cmpint(err, ==, 0);
-    g_assert_cmpuint(res, ==, GiB);
-    g_assert_true(endptr == g + 2);
-
-    endptr = NULL;
-    res = 0xbaadf00d;
-    err = qemu_strtosz(t, &endptr, &res);
-    g_assert_cmpint(err, ==, 0);
-    g_assert_cmpuint(res, ==, TiB);
-    g_assert_true(endptr == t + 2);
-
-    endptr = NULL;
-    res = 0xbaadf00d;
-    err = qemu_strtosz(p, &endptr, &res);
-    g_assert_cmpint(err, ==, 0);
-    g_assert_cmpuint(res, ==, PiB);
-    g_assert_true(endptr == p + 2);
-
-    endptr = NULL;
-    res = 0xbaadf00d;
-    err = qemu_strtosz(e, &endptr, &res);
-    g_assert_cmpint(err, ==, 0);
-    g_assert_cmpuint(res, ==, EiB);
-    g_assert_true(endptr == e + 2);
+    do_strtosz("1K", 0, KiB, 2);
+    do_strtosz("1M", 0, MiB, 2);
+    do_strtosz("1G", 0, GiB, 2);
+    do_strtosz("1T", 0, TiB, 2);
+    do_strtosz("1P", 0, PiB, 2);
+    do_strtosz("1E", 0, EiB, 2);
 }

 static void test_qemu_strtosz_float(void)
 {
-    const char *str;
-    int err;
-    const char *endptr;
-    uint64_t res;
-
-    str = "0.5E";
-    endptr = str;
-    res = 0xbaadf00d;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, 0);
-    g_assert_cmpuint(res, ==, EiB / 2);
-    g_assert_true(endptr == str + 4);
+    do_strtosz("0.5E", 0, EiB / 2, 4);

     /* For convenience, a fraction of 0 is tolerated even on bytes */
-    str = "1.0B";
-    endptr = str;
-    res = 0xbaadf00d;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, 0);
-    g_assert_cmpuint(res, ==, 1);
-    g_assert_true(endptr == str + 4);
+    do_strtosz("1.0B", 0, 1, 4);

     /* An empty fraction is tolerated */
-    str = "1.k";
-    endptr = str;
-    res = 0xbaadf00d;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, 0);
-    g_assert_cmpuint(res, ==, 1024);
-    g_assert_true(endptr == str + 3);
+    do_strtosz("1.k", 0, 1024, 3);

     /* For convenience, we permit values that are not byte-exact */
-    str = "12.345M";
-    endptr = str;
-    res = 0xbaadf00d;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, 0);
-    g_assert_cmpuint(res, ==, (uint64_t) (12.345 * MiB + 0.5));
-    g_assert_true(endptr == str + 7);
+    do_strtosz("12.345M", 0, (uint64_t) (12.345 * MiB + 0.5), 7);
 }

 static void test_qemu_strtosz_invalid(void)
 {
-    const char *str;
-    const char *endptr;
-    int err;
-    uint64_t res = 0xbaadf00d;
-
-    str = "";
-    endptr = NULL;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmphex(res, ==, 0xbaadf00d);
-    g_assert_true(endptr == str);
-
-    str = " \t ";
-    endptr = NULL;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmphex(res, ==, 0xbaadf00d);
-    g_assert_true(endptr == str);
-
-    str = "crap";
-    endptr = NULL;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmphex(res, ==, 0xbaadf00d);
-    g_assert_true(endptr == str);
-
-    str = "inf";
-    endptr = NULL;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmphex(res, ==, 0xbaadf00d);
-    g_assert_true(endptr == str);
-
-    str = "NaN";
-    endptr = NULL;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmphex(res, ==, 0xbaadf00d);
-    g_assert_true(endptr == str);
+    do_strtosz("", -EINVAL, 0xbaadf00d, 0);
+    do_strtosz(" \t ", -EINVAL, 0xbaadf00d, 0);
+    do_strtosz("crap", -EINVAL, 0xbaadf00d, 0);
+    do_strtosz("inf", -EINVAL, 0xbaadf00d, 0);
+    do_strtosz("NaN", -EINVAL, 0xbaadf00d, 0);

     /* Fractional values require scale larger than bytes */
-    str = "1.1B";
-    endptr = NULL;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmphex(res, ==, 0xbaadf00d);
-    g_assert_true(endptr == str);
-
-    str = "1.1";
-    endptr = NULL;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmphex(res, ==, 0xbaadf00d);
-    g_assert_true(endptr == str);
+    do_strtosz("1.1B", -EINVAL, 0xbaadf00d, 0);
+    do_strtosz("1.1", -EINVAL, 0xbaadf00d, 0);

     /* No hex fractions */
-    str = "0x1.8k";
-    endptr = NULL;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmphex(res, ==, 0xbaadf00d);
-    g_assert_true(endptr == str);
+    do_strtosz("0x1.8k", -EINVAL, 0xbaadf00d, 0);

     /* No suffixes */
-    str = "0x18M";
-    endptr = NULL;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmphex(res, ==, 0xbaadf00d);
-    g_assert_true(endptr == str);
+    do_strtosz("0x18M", -EINVAL, 0xbaadf00d, 0);

     /* No negative values */
-    str = "-0";
-    endptr = NULL;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmphex(res, ==, 0xbaadf00d);
-    g_assert_true(endptr == str);
-
-    str = "-1";
-    endptr = NULL;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmphex(res, ==, 0xbaadf00d);
-    g_assert_true(endptr == str);
+    do_strtosz("-0", -EINVAL, 0xbaadf00d, 0);
+    do_strtosz("-1", -EINVAL, 0xbaadf00d, 0);
 }

 static void test_qemu_strtosz_trailing(void)
 {
-    const char *str;
-    const char *endptr;
-    int err;
-    uint64_t res;
+    do_strtosz_full("123xxx", qemu_strtosz_MiB, 0, 123 * MiB, 3,
+                    -EINVAL, 0xbaadf00d);

-    str = "123xxx";
-    endptr = NULL;
-    res = 0xbaadf00d;
-    err = qemu_strtosz_MiB(str, &endptr, &res);
-    g_assert_cmpint(err, ==, 0);
-    g_assert_cmpuint(res, ==, 123 * MiB);
-    g_assert_true(endptr == str + 3);
-
-    res = 0xbaadf00d;
-    err = qemu_strtosz(str, NULL, &res);
-    g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmphex(res, ==, 0xbaadf00d);
-
-    str = "1kiB";
-    endptr = NULL;
-    res = 0xbaadf00d;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, 0);
-    g_assert_cmpuint(res, ==, 1024);
-    g_assert_true(endptr == str + 2);
-
-    res = 0xbaadf00d;
-    err = qemu_strtosz(str, NULL, &res);
-    g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmphex(res, ==, 0xbaadf00d);
-
-    str = "0x";
-    endptr = NULL;
-    res = 0xbaadf00d;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, 0);
-    g_assert_cmpuint(res, ==, 0);
-    g_assert_true(endptr == str + 1);
-
-    res = 0xbaadf00d;
-    err = qemu_strtosz(str, NULL, &res);
-    g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmphex(res, ==, 0xbaadf00d);
-
-    str = "0.NaN";
-    endptr = NULL;
-    res = 0xbaadf00d;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, 0);
-    g_assert_cmpuint(res, ==, 0);
-    g_assert_true(endptr == str + 2);
-
-    res = 0xbaadf00d;
-    err = qemu_strtosz(str, NULL, &res);
-    g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmphex(res, ==, 0xbaadf00d);
-
-    str = "123-45";
-    endptr = NULL;
-    res = 0xbaadf00d;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, 0);
-    g_assert_cmpuint(res, ==, 123);
-    g_assert_true(endptr == str + 3);
-
-    res = 0xbaadf00d;
-    err = qemu_strtosz(str, NULL, &res);
-    g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmphex(res, ==, 0xbaadf00d);
+    do_strtosz_full("1kiB", qemu_strtosz, 0, 1024, 2, -EINVAL, 0xbaadf00d);
+    do_strtosz_full("0x", qemu_strtosz, 0, 0, 1, -EINVAL, 0xbaadf00d);
+    do_strtosz_full("0.NaN", qemu_strtosz, 0, 0, 2, -EINVAL, 0xbaadf00d);
+    do_strtosz_full("123-45", qemu_strtosz, 0, 123, 3, -EINVAL, 0xbaadf00d);

     /* FIXME should stop parse after 'e'. No floating point exponents */
-    str = "1.5e1k";
-    endptr = NULL;
-    res = 0xbaadf00d;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, -EINVAL /* FIXME 0 */);
-    g_assert_cmphex(res, ==, 0xbaadf00d /* FIXME EiB * 1.5 */);
-    g_assert_true(endptr == str /* FIXME + 4 */);
+    do_strtosz_full("1.5e1k", qemu_strtosz, -EINVAL /* FIXME 0 */,
+                    0xbaadf00d /* FIXME EiB * 1.5 */, 0 /* FIXME 4 */,
+                    -EINVAL, 0xbaadf00d);

-    res = 0xbaadf00d;
-    err = qemu_strtosz(str, NULL, &res);
-    g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpint(res, ==, 0xbaadf00d);
-
-    str = "1.5E+0k";
-    endptr = NULL;
-    res = 0xbaadf00d;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, -EINVAL /* FIXME 0 */);
-    g_assert_cmphex(res, ==, 0xbaadf00d /* FIXME EiB * 1.5 */);
-    g_assert_true(endptr == str /* FIXME + 4 */);
-
-    res = 0xbaadf00d;
-    err = qemu_strtosz(str, NULL, &res);
-    g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmphex(res, ==, 0xbaadf00d);
+    do_strtosz_full("1.5E+0k", qemu_strtosz, -EINVAL /* FIXME 0 */,
+                    0xbaadf00d /* FIXME EiB * 1.5 */, 0 /* FIXME 4 */,
+                    -EINVAL, 0xbaadf00d);
 }

 static void test_qemu_strtosz_erange(void)
 {
-    const char *str;
-    const char *endptr;
-    int err;
-    uint64_t res = 0xbaadf00d;
+    /* 2^64; see strtosz_simple for 2^64-1 */
+    do_strtosz("18446744073709551616", -ERANGE, 0xbaadf00d, 20);

-    str = "18446744073709551616"; /* 2^64; see strtosz_simple for 2^64-1 */
-    endptr = NULL;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, -ERANGE);
-    g_assert_cmphex(res, ==, 0xbaadf00d);
-    g_assert_true(endptr == str + 20);
-
-    str = "20E";
-    endptr = NULL;
-    err = qemu_strtosz(str, &endptr, &res);
-    g_assert_cmpint(err, ==, -ERANGE);
-    g_assert_cmphex(res, ==, 0xbaadf00d);
-    g_assert_true(endptr == str + 3);
+    do_strtosz("20E", -ERANGE, 0xbaadf00d, 3);
 }

 static void test_qemu_strtosz_metric(void)
 {
-    const char *str;
-    int err;
-    const char *endptr;
-    uint64_t res;
-
-    str = "12345k";
-    endptr = str;
-    res = 0xbaadf00d;
-    err = qemu_strtosz_metric(str, &endptr, &res);
-    g_assert_cmpint(err, ==, 0);
-    g_assert_cmpuint(res, ==, 12345000);
-    g_assert_true(endptr == str + 6);
-
-    str = "12.345M";
-    endptr = str;
-    res = 0xbaadf00d;
-    err = qemu_strtosz_metric(str, &endptr, &res);
-    g_assert_cmpint(err, ==, 0);
-    g_assert_cmpuint(res, ==, 12345000);
-    g_assert_true(endptr == str + 7);
+    do_strtosz_metric("12345k", 0, 12345000, 6);
+    do_strtosz_metric("12.345M", 0, 12345000, 7);
 }

 static void test_freq_to_str(void)
-- 
2.40.1



^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v2 12/19] cutils: Allow NULL str in qemu_strtosz
  2023-05-12  2:10 [PATCH v2 00/19] Fix qemu_strtosz() read-out-of-bounds Eric Blake
                   ` (10 preceding siblings ...)
  2023-05-12  2:10 ` [PATCH v2 11/19] test-cutils: Refactor qemu_strtosz tests for less boilerplate Eric Blake
@ 2023-05-12  2:10 ` Eric Blake
  2023-05-12  3:25   ` Philippe Mathieu-Daudé
  2023-05-19 15:15   ` Hanna Czenczek
  2023-05-12  2:10 ` [PATCH v2 13/19] numa: Check for qemu_strtosz_MiB error Eric Blake
                   ` (7 subsequent siblings)
  19 siblings, 2 replies; 48+ messages in thread
From: Eric Blake @ 2023-05-12  2:10 UTC (permalink / raw)
  To: qemu-devel; +Cc: hreitz, armbru, richard.henderson

All the other qemu_strto* and parse_uint allow a NULL str.  Having
qemu_strtosz crash on qemu_strtosz(NULL, NULL, &value) is an easy fix
that adds some consistency between our string parsers.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 tests/unit/test-cutils.c | 3 +++
 util/cutils.c            | 2 +-
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/tests/unit/test-cutils.c b/tests/unit/test-cutils.c
index 5c9ed78be93..1936c7b5795 100644
--- a/tests/unit/test-cutils.c
+++ b/tests/unit/test-cutils.c
@@ -3260,6 +3260,9 @@ static void test_qemu_strtosz_float(void)

 static void test_qemu_strtosz_invalid(void)
 {
+    do_strtosz(NULL, -EINVAL, 0xbaadf00d, 0);
+
+    /* Must parse at least one digit */
     do_strtosz("", -EINVAL, 0xbaadf00d, 0);
     do_strtosz(" \t ", -EINVAL, 0xbaadf00d, 0);
     do_strtosz("crap", -EINVAL, 0xbaadf00d, 0);
diff --git a/util/cutils.c b/util/cutils.c
index e599924a0c4..91c90673aba 100644
--- a/util/cutils.c
+++ b/util/cutils.c
@@ -306,7 +306,7 @@ static int do_strtosz(const char *nptr, const char **end,
 out:
     if (end) {
         *end = endptr;
-    } else if (*endptr) {
+    } else if (nptr && *endptr) {
         retval = -EINVAL;
     }
     if (retval == 0) {
-- 
2.40.1



^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v2 13/19] numa: Check for qemu_strtosz_MiB error
  2023-05-12  2:10 [PATCH v2 00/19] Fix qemu_strtosz() read-out-of-bounds Eric Blake
                   ` (11 preceding siblings ...)
  2023-05-12  2:10 ` [PATCH v2 12/19] cutils: Allow NULL str in qemu_strtosz Eric Blake
@ 2023-05-12  2:10 ` Eric Blake
  2023-05-12  2:10 ` [PATCH v2 14/19] test-cutils: Add more coverage to qemu_strtosz11; rgb:1e1e/1e1e/1e1e Eric Blake
                   ` (6 subsequent siblings)
  19 siblings, 0 replies; 48+ messages in thread
From: Eric Blake @ 2023-05-12  2:10 UTC (permalink / raw)
  To: qemu-devel
  Cc: hreitz, armbru, richard.henderson, Eduardo Habkost,
	Marcel Apfelbaum, Philippe Mathieu-Daudé,
	Yanan Wang

As shown in the previous commit, qemu_strtosz_MiB sometimes leaves the
result value untouched (we have to audit further to learn that in that
case, the QAPI generator says that visit_type_NumaOptions() will have
zero-initialized it), and sometimes leaves it with the value of a
partial parse before -EINVAL occurs because of trailing garbage.
Rather than blindly treating any string the user may throw at us as
valid, we should check for parse failures.

Fixes: cc001888 ("numa: fixup parsed NumaNodeOptions earlier", v2.11.0)
Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Hanna Czenczek <hreitz@redhat.com>
---
 hw/core/numa.c | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/hw/core/numa.c b/hw/core/numa.c
index d8d36b16d80..f08956ddb0f 100644
--- a/hw/core/numa.c
+++ b/hw/core/numa.c
@@ -531,10 +531,17 @@ static int parse_numa(void *opaque, QemuOpts *opts, Error **errp)
     /* Fix up legacy suffix-less format */
     if ((object->type == NUMA_OPTIONS_TYPE_NODE) && object->u.node.has_mem) {
         const char *mem_str = qemu_opt_get(opts, "mem");
-        qemu_strtosz_MiB(mem_str, NULL, &object->u.node.mem);
+        int ret = qemu_strtosz_MiB(mem_str, NULL, &object->u.node.mem);
+
+        if (ret < 0) {
+            error_setg_errno(&err, -ret, "could not parse memory size '%s'",
+                             mem_str);
+        }
     }

-    set_numa_options(ms, object, &err);
+    if (!err) {
+        set_numa_options(ms, object, &err);
+    }

     qapi_free_NumaOptions(object);
     if (err) {
-- 
2.40.1



^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v2 14/19] test-cutils: Add more coverage to qemu_strtosz11; rgb:1e1e/1e1e/1e1e
  2023-05-12  2:10 [PATCH v2 00/19] Fix qemu_strtosz() read-out-of-bounds Eric Blake
                   ` (12 preceding siblings ...)
  2023-05-12  2:10 ` [PATCH v2 13/19] numa: Check for qemu_strtosz_MiB error Eric Blake
@ 2023-05-12  2:10 ` Eric Blake
  2023-05-19 15:26   ` [PATCH v2 14/19] test-cutils: Add more coverage to qemu_strtosz11;rgb:1e1e/1e1e/1e1e Hanna Czenczek
  2023-05-12  2:10 ` [PATCH v2 15/19] cutils: Set value in all qemu_strtosz* error paths Eric Blake
                   ` (5 subsequent siblings)
  19 siblings, 1 reply; 48+ messages in thread
From: Eric Blake @ 2023-05-12  2:10 UTC (permalink / raw)
  To: qemu-devel; +Cc: hreitz, armbru, richard.henderson

Add some more strings that the user might send our way.  In
particular, some of these additions include FIXME comments showing
where our parser doesn't quite behave the way we want.

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

---

v2: even more tests added, pad a string to avoid out-of-bounds
randomness [Hanna]
---
 tests/unit/test-cutils.c | 147 +++++++++++++++++++++++++++++++++++----
 1 file changed, 135 insertions(+), 12 deletions(-)

diff --git a/tests/unit/test-cutils.c b/tests/unit/test-cutils.c
index 1936c7b5795..7800caf9b0e 100644
--- a/tests/unit/test-cutils.c
+++ b/tests/unit/test-cutils.c
@@ -3162,7 +3162,12 @@ static void do_strtosz_full(const char *str, qemu_strtosz_fn fn,
     ret = fn(str, &endptr, &val);
     g_assert_cmpint(ret, ==, exp_ptr_ret);
     g_assert_cmpuint(val, ==, exp_ptr_val);
-    g_assert_true(endptr == str + exp_ptr_offset);
+    if (str) {
+        g_assert_true(endptr == str + exp_ptr_offset);
+    } else {
+        g_assert_cmpint(exp_ptr_offset, ==, 0);
+        g_assert_null(endptr);
+    }

     val = 0xbaadf00d;
     ret = fn(str, NULL, &val);
@@ -3198,8 +3203,8 @@ static void test_qemu_strtosz_simple(void)
     /* Leading 0 gives decimal results, not octal */
     do_strtosz("08", 0, 8, 2);

-    /* Leading space is ignored */
-    do_strtosz(" 12345", 0, 12345, 6);
+    /* Leading space and + are ignored */
+    do_strtosz(" +12345", 0, 12345, 7);

     /* 2^53-1 */
     do_strtosz("9007199254740991", 0, 0x1fffffffffffffULL, 16);
@@ -3226,17 +3231,27 @@ static void test_qemu_strtosz_hex(void)

     do_strtosz("0xab", 0, 171, 4);

-    do_strtosz("0xae", 0, 174, 4);
+    do_strtosz(" +0xae", 0, 174, 6);
 }

 static void test_qemu_strtosz_units(void)
 {
-    /* default is M */
+    /* default scale depends on function */
+    do_strtosz("1", 0, 1, 1);
     do_strtosz_MiB("1", 0, MiB, 1);
+    do_strtosz_metric("1", 0, 1, 1);

+    /* Explicit byte suffix works for all functions */
     do_strtosz("1B", 0, 1, 2);
+    do_strtosz_MiB("1B", 0, 1, 2);
+    do_strtosz_metric("1B", 0, 1, 2);

+    /* Expose the scale */
     do_strtosz("1K", 0, KiB, 2);
+    do_strtosz_MiB("1K", 0, KiB, 2);
+    do_strtosz_metric("1K", 0, 1000, 2);
+
+    /* Other suffixes, see also test_qemu_strtosz_metric */
     do_strtosz("1M", 0, MiB, 2);
     do_strtosz("1G", 0, GiB, 2);
     do_strtosz("1T", 0, TiB, 2);
@@ -3248,14 +3263,37 @@ static void test_qemu_strtosz_float(void)
 {
     do_strtosz("0.5E", 0, EiB / 2, 4);

+    /* Implied M suffix okay */
+    do_strtosz_MiB("0.5", 0, MiB / 2, 3);
+
     /* For convenience, a fraction of 0 is tolerated even on bytes */
     do_strtosz("1.0B", 0, 1, 4);

-    /* An empty fraction is tolerated */
+    /* An empty fraction tail is tolerated */
     do_strtosz("1.k", 0, 1024, 3);

+    /* FIXME An empty fraction head should be tolerated */
+    do_strtosz(" .5k", -EINVAL /* FIXME 0 */, 0xbaadf00d /* FIXME 512 */,
+               0 /* FIXME 4 */);
+
     /* For convenience, we permit values that are not byte-exact */
     do_strtosz("12.345M", 0, (uint64_t) (12.345 * MiB + 0.5), 7);
+
+    /* FIXME Fraction tail should round correctly */
+    do_strtosz("1.9999k", 0, 2048, 7);
+    do_strtosz("1.9999999999999999999999999999999999999999999999999999k", 0,
+               1024 /* FIXME 2048 */, 55);
+
+    /* FIXME ERANGE underflow in the fraction tail should not matter for 'k' */
+    do_strtosz("1."
+               "00000000000000000000000000000000000000000000000000"
+               "00000000000000000000000000000000000000000000000000"
+               "00000000000000000000000000000000000000000000000000"
+               "00000000000000000000000000000000000000000000000000"
+               "00000000000000000000000000000000000000000000000000"
+               "00000000000000000000000000000000000000000000000000"
+               "00000000000000000000000000000000000000000000000000"
+               "1k", 0, 1 /* FIXME 1024 */, 354);
 }

 static void test_qemu_strtosz_invalid(void)
@@ -3265,57 +3303,142 @@ static void test_qemu_strtosz_invalid(void)
     /* Must parse at least one digit */
     do_strtosz("", -EINVAL, 0xbaadf00d, 0);
     do_strtosz(" \t ", -EINVAL, 0xbaadf00d, 0);
-    do_strtosz("crap", -EINVAL, 0xbaadf00d, 0);
+    do_strtosz(".", -EINVAL, 0xbaadf00d, 0);
+    do_strtosz(" .", -EINVAL, 0xbaadf00d, 0);
+    do_strtosz(" .k", -EINVAL, 0xbaadf00d, 0);
     do_strtosz("inf", -EINVAL, 0xbaadf00d, 0);
     do_strtosz("NaN", -EINVAL, 0xbaadf00d, 0);

+    /* Lone suffix is not okay */
+    do_strtosz("k", -EINVAL, 0xbaadf00d, 0);
+    do_strtosz(" M", -EINVAL, 0xbaadf00d, 0);
+
     /* Fractional values require scale larger than bytes */
     do_strtosz("1.1B", -EINVAL, 0xbaadf00d, 0);
     do_strtosz("1.1", -EINVAL, 0xbaadf00d, 0);

+    /* FIXME underflow in the fraction tail should matter for 'B' */
+    do_strtosz("1.00001B", -EINVAL, 0xbaadf00d, 0);
+    do_strtosz("1.00000000000000000001B", 0 /* FIXME -EINVAL */,
+               1 /* FIXME 0xbaadf00d */, 23 /* FIXME 0 */);
+    do_strtosz("1."
+               "00000000000000000000000000000000000000000000000000"
+               "00000000000000000000000000000000000000000000000000"
+               "00000000000000000000000000000000000000000000000000"
+               "00000000000000000000000000000000000000000000000000"
+               "00000000000000000000000000000000000000000000000000"
+               "00000000000000000000000000000000000000000000000000"
+               "00000000000000000000000000000000000000000000000000"
+               "1B", 0 /* FIXME -EINVAL */, 1 /* FIXME 0xbaadf00d */,
+               354 /* FIXME 0 */);
+
     /* No hex fractions */
     do_strtosz("0x1.8k", -EINVAL, 0xbaadf00d, 0);
+    do_strtosz("0x1.k", -EINVAL, 0xbaadf00d, 0);

-    /* No suffixes */
+    /* No hex suffixes */
     do_strtosz("0x18M", -EINVAL, 0xbaadf00d, 0);
+    do_strtosz("0x1p1", -EINVAL, 0xbaadf00d, 0);

-    /* No negative values */
-    do_strtosz("-0", -EINVAL, 0xbaadf00d, 0);
-    do_strtosz("-1", -EINVAL, 0xbaadf00d, 0);
+    /* decimal in place of scaling suffix */
+    do_strtosz("1.1.k", -EINVAL, 0xbaadf00d, 0);
+    do_strtosz("1.1.", -EINVAL, 0xbaadf00d, 0);
 }

 static void test_qemu_strtosz_trailing(void)
 {
+    /* Trailing whitespace */
+    do_strtosz_full("1k ", qemu_strtosz, 0, 1024, 2, -EINVAL, 0xbaadf00d);
+
+    /* Unknown suffix overrides even implied scale*/
+    do_strtosz_full("123xxx", qemu_strtosz, 0, 123, 3, -EINVAL, 0xbaadf00d);
+
+    /* Implied scale allows partial parse */
     do_strtosz_full("123xxx", qemu_strtosz_MiB, 0, 123 * MiB, 3,
                     -EINVAL, 0xbaadf00d);
+    do_strtosz_full("1.5.k", qemu_strtosz_MiB, 0, 1.5 * MiB, 3,
+                    -EINVAL, 0xbaadf00d);

+    /* Junk after one-byte suffix */
     do_strtosz_full("1kiB", qemu_strtosz, 0, 1024, 2, -EINVAL, 0xbaadf00d);
+
+    /* Incomplete hex is an unknown suffix */
     do_strtosz_full("0x", qemu_strtosz, 0, 0, 1, -EINVAL, 0xbaadf00d);
+
+    /* Hex literals use only one leading zero */
+    do_strtosz_full("00x1", qemu_strtosz, 0, 0, 2, -EINVAL, 0xbaadf00d);
+
+    /* No support for binary literals; 'b' is valid suffix */
+    do_strtosz_full("0b1000", qemu_strtosz, 0, 0, 2, -EINVAL, 0xbaadf00d);
+
+    /* Junk after decimal */
     do_strtosz_full("0.NaN", qemu_strtosz, 0, 0, 2, -EINVAL, 0xbaadf00d);
+
+    /* Although negatives are invalid, '-' may be in trailing junk */
     do_strtosz_full("123-45", qemu_strtosz, 0, 123, 3, -EINVAL, 0xbaadf00d);
+    do_strtosz_full(" 123 - 45", qemu_strtosz, 0, 123, 4, -EINVAL, 0xbaadf00d);

     /* FIXME should stop parse after 'e'. No floating point exponents */
     do_strtosz_full("1.5e1k", qemu_strtosz, -EINVAL /* FIXME 0 */,
                     0xbaadf00d /* FIXME EiB * 1.5 */, 0 /* FIXME 4 */,
                     -EINVAL, 0xbaadf00d);
-
     do_strtosz_full("1.5E+0k", qemu_strtosz, -EINVAL /* FIXME 0 */,
                     0xbaadf00d /* FIXME EiB * 1.5 */, 0 /* FIXME 4 */,
                     -EINVAL, 0xbaadf00d);
+
+    /*
+     * FIXME overflow in fraction is so buggy it can read beyond bounds
+     * if we don't stuff extra \0 in our literal
+     */
+    do_strtosz_full("1.5E999\0\0" /* FIXME 1.5E999" */, qemu_strtosz,
+                    0, 1 /* FIXME EiB * 1.5 */, 8 /* FIXME 4 */,
+                    0 /* FIXME -EINVAL */, 1 /* FIXME 0xbaadf00d */);
 }

 static void test_qemu_strtosz_erange(void)
 {
+    /* FIXME negative values fit better as ERANGE */
+    do_strtosz(" -0", -EINVAL /* FIXME -ERANGE */, 0xbaadf00d, 0 /* FIXME 3 */);
+    do_strtosz("-1", -EINVAL /* FIXME -ERANGE */, 0xbaadf00d, 0 /* FIXME 2 */);
+    do_strtosz_full("-2M", qemu_strtosz, -EINVAL /* FIXME -ERANGE */,
+                    0xbaadf00d, 0 /* FIXME 2 */, -EINVAL, 0xbaadf00d);
+    do_strtosz(" -.0", -EINVAL /* FIXME -ERANGE */, 0xbaadf00d,
+               0 /* FIXME 4 */);
+    do_strtosz_full("-.1k", qemu_strtosz, -EINVAL /* FIXME -ERANGE */,
+                    0xbaadf00d, 0 /* FIXME 3 */, -EINVAL, 0xbaadf00d);
+    do_strtosz_full(" -."
+                    "00000000000000000000000000000000000000000000000000"
+                    "00000000000000000000000000000000000000000000000000"
+                    "00000000000000000000000000000000000000000000000000"
+                    "00000000000000000000000000000000000000000000000000"
+                    "00000000000000000000000000000000000000000000000000"
+                    "00000000000000000000000000000000000000000000000000"
+                    "00000000000000000000000000000000000000000000000000"
+                    "1M", qemu_strtosz, -EINVAL /* FIXME -ERANGE */,
+                    0xbaadf00d, 0 /* FIXME 354 */, -EINVAL, 0xbaadf00d);
+
     /* 2^64; see strtosz_simple for 2^64-1 */
     do_strtosz("18446744073709551616", -ERANGE, 0xbaadf00d, 20);

     do_strtosz("20E", -ERANGE, 0xbaadf00d, 3);
+
+    /* FIXME Fraction tail can cause ERANGE overflow */
+    do_strtosz("15.9999999999999999999999999999999999999999999999999999E",
+               0 /* FIXME -ERANGE */, 15ULL * EiB /* FIXME 0xbaadf00d */, 56);
+
+    /* EINVAL has priority over ERANGE */
+    do_strtosz_full("100000Pjunk", qemu_strtosz, -ERANGE, 0xbaadf00d, 7,
+                    -EINVAL, 0xbaadf00d);
 }

 static void test_qemu_strtosz_metric(void)
 {
     do_strtosz_metric("12345k", 0, 12345000, 6);
     do_strtosz_metric("12.345M", 0, 12345000, 7);
+
+    /* Fraction is affected by floating-point rounding */
+    /* This would be 0xfffffffffffffbff with infinite precision */
+    do_strtosz_metric("18.446744073709550591E", 0, 0xfffffffffffffc0cULL, 22);
 }

 static void test_freq_to_str(void)
-- 
2.40.1



^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v2 15/19] cutils: Set value in all qemu_strtosz* error paths
  2023-05-12  2:10 [PATCH v2 00/19] Fix qemu_strtosz() read-out-of-bounds Eric Blake
                   ` (13 preceding siblings ...)
  2023-05-12  2:10 ` [PATCH v2 14/19] test-cutils: Add more coverage to qemu_strtosz11; rgb:1e1e/1e1e/1e1e Eric Blake
@ 2023-05-12  2:10 ` Eric Blake
  2023-05-19 15:29   ` Hanna Czenczek
  2023-05-12  2:10 ` [PATCH v2 16/19] cutils: Set value in all integral qemu_strto* " Eric Blake
                   ` (4 subsequent siblings)
  19 siblings, 1 reply; 48+ messages in thread
From: Eric Blake @ 2023-05-12  2:10 UTC (permalink / raw)
  To: qemu-devel; +Cc: hreitz, armbru, richard.henderson

Making callers determine whether or not *value was populated on error
is not nice for usability.  Pre-patch, we have unit tests that check
that *result is left unchanged on most EINVAL errors and set to 0 on
many ERANGE errors.  This is subtly different from libc strtoumax()
behavior which returns UINT64_MAX on ERANGE errors, as well as
different from our parse_uint() which slams to 0 on EINVAL on the
grounds that we want our functions to be harder to mis-use than
strtoumax().

Let's audit callers:

- hw/core/numa.c:parse_numa() fixed in the previous patch to check for
  errors

- migration/migration-hmp-cmds.c:hmp_migrate_set_parameter(),
  monitor/hmp.c:monitor_parse_arguments(),
  qapi/opts-visitor.c:opts_type_size(),
  qapi/qobject-input-visitor.c:qobject_input_type_size_keyval(),
  qemu-img.c:cvtnum_full(), qemu-io-cmds.c:cvtnum(),
  target/i386/cpu.c:x86_cpu_parse_featurestr(), and
  util/qemu-option.c:parse_option_size() appear to reject all failures
  (although some with distinct messages for ERANGE as opposed to
  EINVAL), so it doesn't matter what is in the value parameter on
  error.

- All remaining callers are in the testsuite, where we can tweak our
  expectations to match our new desired behavior.

Advancing to the end of the string parsed on overflow (ERANGE), while
still returning 0, makes sense (UINT64_MAX as a size is unlikely to be
useful); likewise, our size parsing code is complex enough that it's
easier to always return 0 when endptr is NULL but trailing garbage was
found, rather than trying to return the value of the prefix actually
parsed (no current caller cared about the value of the prefix).

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

---

v2: cutils.c unchanged, but rebasing test suite is significant enough
that I doropped Hanna's R-b
---
 tests/unit/test-cutils.c | 106 +++++++++++++++++++--------------------
 util/cutils.c            |  17 +++++--
 2 files changed, 63 insertions(+), 60 deletions(-)

diff --git a/tests/unit/test-cutils.c b/tests/unit/test-cutils.c
index 7800caf9b0e..4a1baf05ca6 100644
--- a/tests/unit/test-cutils.c
+++ b/tests/unit/test-cutils.c
@@ -3273,7 +3273,7 @@ static void test_qemu_strtosz_float(void)
     do_strtosz("1.k", 0, 1024, 3);

     /* FIXME An empty fraction head should be tolerated */
-    do_strtosz(" .5k", -EINVAL /* FIXME 0 */, 0xbaadf00d /* FIXME 512 */,
+    do_strtosz(" .5k", -EINVAL /* FIXME 0 */, 0 /* FIXME 512 */,
                0 /* FIXME 4 */);

     /* For convenience, we permit values that are not byte-exact */
@@ -3298,29 +3298,29 @@ static void test_qemu_strtosz_float(void)

 static void test_qemu_strtosz_invalid(void)
 {
-    do_strtosz(NULL, -EINVAL, 0xbaadf00d, 0);
+    do_strtosz(NULL, -EINVAL, 0, 0);

     /* Must parse at least one digit */
-    do_strtosz("", -EINVAL, 0xbaadf00d, 0);
-    do_strtosz(" \t ", -EINVAL, 0xbaadf00d, 0);
-    do_strtosz(".", -EINVAL, 0xbaadf00d, 0);
-    do_strtosz(" .", -EINVAL, 0xbaadf00d, 0);
-    do_strtosz(" .k", -EINVAL, 0xbaadf00d, 0);
-    do_strtosz("inf", -EINVAL, 0xbaadf00d, 0);
-    do_strtosz("NaN", -EINVAL, 0xbaadf00d, 0);
+    do_strtosz("", -EINVAL, 0, 0);
+    do_strtosz(" \t ", -EINVAL, 0, 0);
+    do_strtosz(".", -EINVAL, 0, 0);
+    do_strtosz(" .", -EINVAL, 0, 0);
+    do_strtosz(" .k", -EINVAL, 0, 0);
+    do_strtosz("inf", -EINVAL, 0, 0);
+    do_strtosz("NaN", -EINVAL, 0, 0);

     /* Lone suffix is not okay */
-    do_strtosz("k", -EINVAL, 0xbaadf00d, 0);
-    do_strtosz(" M", -EINVAL, 0xbaadf00d, 0);
+    do_strtosz("k", -EINVAL, 0, 0);
+    do_strtosz(" M", -EINVAL, 0, 0);

     /* Fractional values require scale larger than bytes */
-    do_strtosz("1.1B", -EINVAL, 0xbaadf00d, 0);
-    do_strtosz("1.1", -EINVAL, 0xbaadf00d, 0);
+    do_strtosz("1.1B", -EINVAL, 0, 0);
+    do_strtosz("1.1", -EINVAL, 0, 0);

     /* FIXME underflow in the fraction tail should matter for 'B' */
-    do_strtosz("1.00001B", -EINVAL, 0xbaadf00d, 0);
+    do_strtosz("1.00001B", -EINVAL, 0, 0);
     do_strtosz("1.00000000000000000001B", 0 /* FIXME -EINVAL */,
-               1 /* FIXME 0xbaadf00d */, 23 /* FIXME 0 */);
+               1 /* FIXME 0 */, 23 /* FIXME 0 */);
     do_strtosz("1."
                "00000000000000000000000000000000000000000000000000"
                "00000000000000000000000000000000000000000000000000"
@@ -3329,62 +3329,60 @@ static void test_qemu_strtosz_invalid(void)
                "00000000000000000000000000000000000000000000000000"
                "00000000000000000000000000000000000000000000000000"
                "00000000000000000000000000000000000000000000000000"
-               "1B", 0 /* FIXME -EINVAL */, 1 /* FIXME 0xbaadf00d */,
+               "1B", 0 /* FIXME -EINVAL */, 1 /* FIXME 0 */,
                354 /* FIXME 0 */);

     /* No hex fractions */
-    do_strtosz("0x1.8k", -EINVAL, 0xbaadf00d, 0);
-    do_strtosz("0x1.k", -EINVAL, 0xbaadf00d, 0);
+    do_strtosz("0x1.8k", -EINVAL, 0, 0);
+    do_strtosz("0x1.k", -EINVAL, 0, 0);

     /* No hex suffixes */
-    do_strtosz("0x18M", -EINVAL, 0xbaadf00d, 0);
-    do_strtosz("0x1p1", -EINVAL, 0xbaadf00d, 0);
+    do_strtosz("0x18M", -EINVAL, 0, 0);
+    do_strtosz("0x1p1", -EINVAL, 0, 0);

     /* decimal in place of scaling suffix */
-    do_strtosz("1.1.k", -EINVAL, 0xbaadf00d, 0);
-    do_strtosz("1.1.", -EINVAL, 0xbaadf00d, 0);
+    do_strtosz("1.1.k", -EINVAL, 0, 0);
+    do_strtosz("1.1.", -EINVAL, 0, 0);
 }

 static void test_qemu_strtosz_trailing(void)
 {
     /* Trailing whitespace */
-    do_strtosz_full("1k ", qemu_strtosz, 0, 1024, 2, -EINVAL, 0xbaadf00d);
+    do_strtosz_full("1k ", qemu_strtosz, 0, 1024, 2, -EINVAL, 0);

     /* Unknown suffix overrides even implied scale*/
-    do_strtosz_full("123xxx", qemu_strtosz, 0, 123, 3, -EINVAL, 0xbaadf00d);
+    do_strtosz_full("123xxx", qemu_strtosz, 0, 123, 3, -EINVAL, 0);

     /* Implied scale allows partial parse */
-    do_strtosz_full("123xxx", qemu_strtosz_MiB, 0, 123 * MiB, 3,
-                    -EINVAL, 0xbaadf00d);
-    do_strtosz_full("1.5.k", qemu_strtosz_MiB, 0, 1.5 * MiB, 3,
-                    -EINVAL, 0xbaadf00d);
+    do_strtosz_full("123xxx", qemu_strtosz_MiB, 0, 123 * MiB, 3, -EINVAL, 0);
+    do_strtosz_full("1.5.k", qemu_strtosz_MiB, 0, 1.5 * MiB, 3, -EINVAL, 0);

     /* Junk after one-byte suffix */
-    do_strtosz_full("1kiB", qemu_strtosz, 0, 1024, 2, -EINVAL, 0xbaadf00d);
+    do_strtosz_full("1kiB", qemu_strtosz, 0, 1024, 2, -EINVAL, 0);

     /* Incomplete hex is an unknown suffix */
-    do_strtosz_full("0x", qemu_strtosz, 0, 0, 1, -EINVAL, 0xbaadf00d);
+    do_strtosz_full("0x", qemu_strtosz, 0, 0, 1, -EINVAL, 0);

     /* Hex literals use only one leading zero */
-    do_strtosz_full("00x1", qemu_strtosz, 0, 0, 2, -EINVAL, 0xbaadf00d);
+    do_strtosz_full("00x1", qemu_strtosz, 0, 0, 2, -EINVAL, 0);

     /* No support for binary literals; 'b' is valid suffix */
-    do_strtosz_full("0b1000", qemu_strtosz, 0, 0, 2, -EINVAL, 0xbaadf00d);
+    do_strtosz_full("0b1000", qemu_strtosz, 0, 0, 2, -EINVAL, 0);

     /* Junk after decimal */
-    do_strtosz_full("0.NaN", qemu_strtosz, 0, 0, 2, -EINVAL, 0xbaadf00d);
+    do_strtosz_full("0.NaN", qemu_strtosz, 0, 0, 2, -EINVAL, 0);

     /* Although negatives are invalid, '-' may be in trailing junk */
-    do_strtosz_full("123-45", qemu_strtosz, 0, 123, 3, -EINVAL, 0xbaadf00d);
-    do_strtosz_full(" 123 - 45", qemu_strtosz, 0, 123, 4, -EINVAL, 0xbaadf00d);
+    do_strtosz_full("123-45", qemu_strtosz, 0, 123, 3, -EINVAL, 0);
+    do_strtosz_full(" 123 - 45", qemu_strtosz, 0, 123, 4, -EINVAL, 0);

     /* FIXME should stop parse after 'e'. No floating point exponents */
     do_strtosz_full("1.5e1k", qemu_strtosz, -EINVAL /* FIXME 0 */,
-                    0xbaadf00d /* FIXME EiB * 1.5 */, 0 /* FIXME 4 */,
-                    -EINVAL, 0xbaadf00d);
+                    0 /* FIXME EiB * 1.5 */, 0 /* FIXME 4 */,
+                    -EINVAL, 0);
     do_strtosz_full("1.5E+0k", qemu_strtosz, -EINVAL /* FIXME 0 */,
-                    0xbaadf00d /* FIXME EiB * 1.5 */, 0 /* FIXME 4 */,
-                    -EINVAL, 0xbaadf00d);
+                    0 /* FIXME EiB * 1.5 */, 0 /* FIXME 4 */,
+                    -EINVAL, 0);

     /*
      * FIXME overflow in fraction is so buggy it can read beyond bounds
@@ -3392,20 +3390,19 @@ static void test_qemu_strtosz_trailing(void)
      */
     do_strtosz_full("1.5E999\0\0" /* FIXME 1.5E999" */, qemu_strtosz,
                     0, 1 /* FIXME EiB * 1.5 */, 8 /* FIXME 4 */,
-                    0 /* FIXME -EINVAL */, 1 /* FIXME 0xbaadf00d */);
+                    0 /* FIXME -EINVAL */, 1 /* FIXME 0 */);
 }

 static void test_qemu_strtosz_erange(void)
 {
     /* FIXME negative values fit better as ERANGE */
-    do_strtosz(" -0", -EINVAL /* FIXME -ERANGE */, 0xbaadf00d, 0 /* FIXME 3 */);
-    do_strtosz("-1", -EINVAL /* FIXME -ERANGE */, 0xbaadf00d, 0 /* FIXME 2 */);
-    do_strtosz_full("-2M", qemu_strtosz, -EINVAL /* FIXME -ERANGE */,
-                    0xbaadf00d, 0 /* FIXME 2 */, -EINVAL, 0xbaadf00d);
-    do_strtosz(" -.0", -EINVAL /* FIXME -ERANGE */, 0xbaadf00d,
-               0 /* FIXME 4 */);
-    do_strtosz_full("-.1k", qemu_strtosz, -EINVAL /* FIXME -ERANGE */,
-                    0xbaadf00d, 0 /* FIXME 3 */, -EINVAL, 0xbaadf00d);
+    do_strtosz(" -0", -EINVAL /* FIXME -ERANGE */, 0, 0 /* FIXME 3 */);
+    do_strtosz("-1", -EINVAL /* FIXME -ERANGE */, 0, 0 /* FIXME 2 */);
+    do_strtosz_full("-2M", qemu_strtosz, -EINVAL /* FIXME -ERANGE */, 0,
+                    0 /* FIXME 2 */, -EINVAL, 0);
+    do_strtosz(" -.0", -EINVAL /* FIXME -ERANGE */, 0, 0 /* FIXME 4 */);
+    do_strtosz_full("-.1k", qemu_strtosz, -EINVAL /* FIXME -ERANGE */, 0,
+                    0 /* FIXME 3 */, -EINVAL, 0);
     do_strtosz_full(" -."
                     "00000000000000000000000000000000000000000000000000"
                     "00000000000000000000000000000000000000000000000000"
@@ -3414,21 +3411,20 @@ static void test_qemu_strtosz_erange(void)
                     "00000000000000000000000000000000000000000000000000"
                     "00000000000000000000000000000000000000000000000000"
                     "00000000000000000000000000000000000000000000000000"
-                    "1M", qemu_strtosz, -EINVAL /* FIXME -ERANGE */,
-                    0xbaadf00d, 0 /* FIXME 354 */, -EINVAL, 0xbaadf00d);
+                    "1M", qemu_strtosz, -EINVAL /* FIXME -ERANGE */, 0,
+                    0 /* FIXME 354 */, -EINVAL, 0);

     /* 2^64; see strtosz_simple for 2^64-1 */
-    do_strtosz("18446744073709551616", -ERANGE, 0xbaadf00d, 20);
+    do_strtosz("18446744073709551616", -ERANGE, 0, 20);

-    do_strtosz("20E", -ERANGE, 0xbaadf00d, 3);
+    do_strtosz("20E", -ERANGE, 0, 3);

     /* FIXME Fraction tail can cause ERANGE overflow */
     do_strtosz("15.9999999999999999999999999999999999999999999999999999E",
-               0 /* FIXME -ERANGE */, 15ULL * EiB /* FIXME 0xbaadf00d */, 56);
+               0 /* FIXME -ERANGE */, 15ULL * EiB /* FIXME 0 */, 56);

     /* EINVAL has priority over ERANGE */
-    do_strtosz_full("100000Pjunk", qemu_strtosz, -ERANGE, 0xbaadf00d, 7,
-                    -EINVAL, 0xbaadf00d);
+    do_strtosz_full("100000Pjunk", qemu_strtosz, -ERANGE, 0, 7, -EINVAL, 0);
 }

 static void test_qemu_strtosz_metric(void)
diff --git a/util/cutils.c b/util/cutils.c
index 91c90673aba..24955d3ca94 100644
--- a/util/cutils.c
+++ b/util/cutils.c
@@ -205,13 +205,15 @@ static int64_t suffix_mul(char suffix, int64_t unit)
  *
  * The end pointer will be returned in *end, if not NULL.  If there is
  * no fraction, the input can be decimal or hexadecimal; if there is a
- * fraction, then the input must be decimal and there must be a suffix
- * (possibly by @default_suffix) larger than Byte, and the fractional
- * portion may suffer from precision loss or rounding.  The input must
- * be positive.
+ * non-zero fraction, then the input must be decimal and there must be
+ * a suffix (possibly by @default_suffix) larger than Byte, and the
+ * fractional portion may suffer from precision loss or rounding.  The
+ * input must be positive.
  *
  * Return -ERANGE on overflow (with *@end advanced), and -EINVAL on
- * other error (with *@end left unchanged).
+ * other error (with *@end at @nptr).  Unlike strtoull, *@result is
+ * set to 0 on all errors, as returning UINT64_MAX on overflow is less
+ * likely to be usable as a size.
  */
 static int do_strtosz(const char *nptr, const char **end,
                       const char default_suffix, int64_t unit,
@@ -311,6 +313,11 @@ out:
     }
     if (retval == 0) {
         *result = val;
+    } else {
+        *result = 0;
+        if (end && retval == -EINVAL) {
+            *end = nptr;
+        }
     }

     return retval;
-- 
2.40.1



^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v2 16/19] cutils: Set value in all integral qemu_strto* error paths
  2023-05-12  2:10 [PATCH v2 00/19] Fix qemu_strtosz() read-out-of-bounds Eric Blake
                   ` (14 preceding siblings ...)
  2023-05-12  2:10 ` [PATCH v2 15/19] cutils: Set value in all qemu_strtosz* error paths Eric Blake
@ 2023-05-12  2:10 ` Eric Blake
  2023-05-12  2:10 ` [PATCH v2 17/19] cutils: Use parse_uint in qemu_strtosz for negative rejection Eric Blake
                   ` (3 subsequent siblings)
  19 siblings, 0 replies; 48+ messages in thread
From: Eric Blake @ 2023-05-12  2:10 UTC (permalink / raw)
  To: qemu-devel; +Cc: hreitz, armbru, richard.henderson

Our goal in writing qemu_strtoi() and friends is to have an interface
harder to abuse than libc's strtol().  Leaving the return value
uninitialized on some but not all error paths does not lend itself
well to this goal; and our documentation wasn't helpful on what to
expect.

Note that the previous patch changed all qemu_strtosz() EINVAL error
paths to slam value to 0 rather than stay uninitialized, even when the
EINVAL eror occurs because of trailing junk.  But for the remaining
integral qemu_strto*, it's easier to return the parsed value than to
force things back to zero, in part because of how check_strtox_error
works; in part because people expect that from libc strto* (while
there is no libc strtosz to compare to), and in part because doing so
creates less churn in the testsuite.

Here, the list of affected callers is much longer ('git grep
"qemu_strto[ui]" *.c **/*.c | grep -v tests/ |wc -l' outputs 87,
although a few of those are the implementation in in cutils.c), so
touching as little as possible is the wisest course of action.

Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Hanna Czenczek <hreitz@redhat.com>

---

v2: commit message tweaked, but code unchanged, so R-b applied
---
 tests/unit/test-cutils.c | 24 +++++++++++------------
 util/cutils.c            | 42 +++++++++++++++++++++++++---------------
 2 files changed, 38 insertions(+), 28 deletions(-)

diff --git a/tests/unit/test-cutils.c b/tests/unit/test-cutils.c
index 4a1baf05ca6..1272638582a 100644
--- a/tests/unit/test-cutils.c
+++ b/tests/unit/test-cutils.c
@@ -310,7 +310,7 @@ static void test_qemu_strtoi_null(void)
     err = qemu_strtoi(NULL, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpint(res, ==, 999);
+    g_assert_cmpint(res, ==, 0);
     g_assert_null(endptr);
 }

@@ -610,7 +610,7 @@ static void test_qemu_strtoi_full_null(void)
     err = qemu_strtoi(NULL, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpint(res, ==, 999);
+    g_assert_cmpint(res, ==, 0);
     g_assert_null(endptr);
 }

@@ -713,7 +713,7 @@ static void test_qemu_strtoui_null(void)
     err = qemu_strtoui(NULL, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpuint(res, ==, 999);
+    g_assert_cmpuint(res, ==, 0);
     g_assert_null(endptr);
 }

@@ -1011,7 +1011,7 @@ static void test_qemu_strtoui_full_null(void)
     err = qemu_strtoui(NULL, NULL, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpuint(res, ==, 999);
+    g_assert_cmpuint(res, ==, 0);
 }

 static void test_qemu_strtoui_full_empty(void)
@@ -1111,7 +1111,7 @@ static void test_qemu_strtol_null(void)
     err = qemu_strtol(NULL, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpint(res, ==, 999);
+    g_assert_cmpint(res, ==, 0);
     g_assert_null(endptr);
 }

@@ -1415,7 +1415,7 @@ static void test_qemu_strtol_full_null(void)
     err = qemu_strtol(NULL, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpint(res, ==, 999);
+    g_assert_cmpint(res, ==, 0);
     g_assert_null(endptr);
 }

@@ -1518,7 +1518,7 @@ static void test_qemu_strtoul_null(void)
     err = qemu_strtoul(NULL, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpuint(res, ==, 999);
+    g_assert_cmpuint(res, ==, 0);
     g_assert_null(endptr);
 }

@@ -1811,7 +1811,7 @@ static void test_qemu_strtoul_full_null(void)
     err = qemu_strtoul(NULL, NULL, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpuint(res, ==, 999);
+    g_assert_cmpuint(res, ==, 0);
 }

 static void test_qemu_strtoul_full_empty(void)
@@ -1911,7 +1911,7 @@ static void test_qemu_strtoi64_null(void)
     err = qemu_strtoi64(NULL, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpint(res, ==, 999);
+    g_assert_cmpint(res, ==, 0);
     g_assert_null(endptr);
 }

@@ -2201,7 +2201,7 @@ static void test_qemu_strtoi64_full_null(void)
     err = qemu_strtoi64(NULL, NULL, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpint(res, ==, 999);
+    g_assert_cmpint(res, ==, 0);
 }

 static void test_qemu_strtoi64_full_empty(void)
@@ -2304,7 +2304,7 @@ static void test_qemu_strtou64_null(void)
     err = qemu_strtou64(NULL, &endptr, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpuint(res, ==, 999);
+    g_assert_cmpuint(res, ==, 0);
     g_assert_null(endptr);
 }

@@ -2593,7 +2593,7 @@ static void test_qemu_strtou64_full_null(void)
     err = qemu_strtou64(NULL, NULL, 0, &res);

     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpuint(res, ==, 999);
+    g_assert_cmpuint(res, ==, 0);
 }

 static void test_qemu_strtou64_full_empty(void)
diff --git a/util/cutils.c b/util/cutils.c
index 24955d3ca94..b5a6641fa0f 100644
--- a/util/cutils.c
+++ b/util/cutils.c
@@ -384,12 +384,13 @@ static int check_strtox_error(const char *nptr, char *ep,
  *
  * @nptr may be null, and no conversion is performed then.
  *
- * If no conversion is performed, store @nptr in *@endptr and return
- * -EINVAL.
+ * If no conversion is performed, store @nptr in *@endptr, 0 in
+ * @result, and return -EINVAL.
  *
  * If @endptr is null, and the string isn't fully converted, return
- * -EINVAL.  This is the case when the pointer that would be stored in
- * a non-null @endptr points to a character other than '\0'.
+ * -EINVAL with @result set to the parsed value.  This is the case
+ * when the pointer that would be stored in a non-null @endptr points
+ * to a character other than '\0'.
  *
  * If the conversion overflows @result, store INT_MAX in @result,
  * and return -ERANGE.
@@ -407,6 +408,7 @@ int qemu_strtoi(const char *nptr, const char **endptr, int base,

     assert((unsigned) base <= 36 && base != 1);
     if (!nptr) {
+        *result = 0;
         if (endptr) {
             *endptr = nptr;
         }
@@ -436,12 +438,13 @@ int qemu_strtoi(const char *nptr, const char **endptr, int base,
  *
  * @nptr may be null, and no conversion is performed then.
  *
- * If no conversion is performed, store @nptr in *@endptr and return
- * -EINVAL.
+ * If no conversion is performed, store @nptr in *@endptr, 0 in
+ * @result, and return -EINVAL.
  *
  * If @endptr is null, and the string isn't fully converted, return
- * -EINVAL.  This is the case when the pointer that would be stored in
- * a non-null @endptr points to a character other than '\0'.
+ * -EINVAL with @result set to the parsed value.  This is the case
+ * when the pointer that would be stored in a non-null @endptr points
+ * to a character other than '\0'.
  *
  * If the conversion overflows @result, store UINT_MAX in @result,
  * and return -ERANGE.
@@ -460,6 +463,7 @@ int qemu_strtoui(const char *nptr, const char **endptr, int base,

     assert((unsigned) base <= 36 && base != 1);
     if (!nptr) {
+        *result = 0;
         if (endptr) {
             *endptr = nptr;
         }
@@ -501,12 +505,13 @@ int qemu_strtoui(const char *nptr, const char **endptr, int base,
  *
  * @nptr may be null, and no conversion is performed then.
  *
- * If no conversion is performed, store @nptr in *@endptr and return
- * -EINVAL.
+ * If no conversion is performed, store @nptr in *@endptr, 0 in
+ * @result, and return -EINVAL.
  *
  * If @endptr is null, and the string isn't fully converted, return
- * -EINVAL.  This is the case when the pointer that would be stored in
- * a non-null @endptr points to a character other than '\0'.
+ * -EINVAL with @result set to the parsed value.  This is the case
+ * when the pointer that would be stored in a non-null @endptr points
+ * to a character other than '\0'.
  *
  * If the conversion overflows @result, store LONG_MAX in @result,
  * and return -ERANGE.
@@ -523,6 +528,7 @@ int qemu_strtol(const char *nptr, const char **endptr, int base,

     assert((unsigned) base <= 36 && base != 1);
     if (!nptr) {
+        *result = 0;
         if (endptr) {
             *endptr = nptr;
         }
@@ -543,12 +549,13 @@ int qemu_strtol(const char *nptr, const char **endptr, int base,
  *
  * @nptr may be null, and no conversion is performed then.
  *
- * If no conversion is performed, store @nptr in *@endptr and return
- * -EINVAL.
+ * If no conversion is performed, store @nptr in *@endptr, 0 in
+ * @result, and return -EINVAL.
  *
  * If @endptr is null, and the string isn't fully converted, return
- * -EINVAL.  This is the case when the pointer that would be stored in
- * a non-null @endptr points to a character other than '\0'.
+ * -EINVAL with @result set to the parsed value.  This is the case
+ * when the pointer that would be stored in a non-null @endptr points
+ * to a character other than '\0'.
  *
  * If the conversion overflows @result, store ULONG_MAX in @result,
  * and return -ERANGE.
@@ -566,6 +573,7 @@ int qemu_strtoul(const char *nptr, const char **endptr, int base,

     assert((unsigned) base <= 36 && base != 1);
     if (!nptr) {
+        *result = 0;
         if (endptr) {
             *endptr = nptr;
         }
@@ -594,6 +602,7 @@ int qemu_strtoi64(const char *nptr, const char **endptr, int base,

     assert((unsigned) base <= 36 && base != 1);
     if (!nptr) {
+        *result = 0;
         if (endptr) {
             *endptr = nptr;
         }
@@ -621,6 +630,7 @@ int qemu_strtou64(const char *nptr, const char **endptr, int base,

     assert((unsigned) base <= 36 && base != 1);
     if (!nptr) {
+        *result = 0;
         if (endptr) {
             *endptr = nptr;
         }
-- 
2.40.1



^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v2 17/19] cutils: Use parse_uint in qemu_strtosz for negative rejection
  2023-05-12  2:10 [PATCH v2 00/19] Fix qemu_strtosz() read-out-of-bounds Eric Blake
                   ` (15 preceding siblings ...)
  2023-05-12  2:10 ` [PATCH v2 16/19] cutils: Set value in all integral qemu_strto* " Eric Blake
@ 2023-05-12  2:10 ` Eric Blake
  2023-05-12 19:34   ` Eric Blake
  2023-05-12  2:10 ` [PATCH v2 18/19] cutils: Improve qemu_strtod* error paths Eric Blake
                   ` (2 subsequent siblings)
  19 siblings, 1 reply; 48+ messages in thread
From: Eric Blake @ 2023-05-12  2:10 UTC (permalink / raw)
  To: qemu-devel; +Cc: hreitz, armbru, richard.henderson

Rather than open-coding two different ways to check for an unwanted
negative sign, reuse the same code in both functions.  That way, if we
decide down the road to accept "-0" instead of rejecting it, we have
fewer places to change.  Also, it means we now get ERANGE instead of
EINVAL for negative values in qemu_strtosz, which is reasonable for
what it represents.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 tests/unit/test-cutils.c | 7 +++----
 util/cutils.c            | 8 ++------
 2 files changed, 5 insertions(+), 10 deletions(-)

diff --git a/tests/unit/test-cutils.c b/tests/unit/test-cutils.c
index 1272638582a..b8ad4d7fbac 100644
--- a/tests/unit/test-cutils.c
+++ b/tests/unit/test-cutils.c
@@ -3396,10 +3396,9 @@ static void test_qemu_strtosz_trailing(void)
 static void test_qemu_strtosz_erange(void)
 {
     /* FIXME negative values fit better as ERANGE */
-    do_strtosz(" -0", -EINVAL /* FIXME -ERANGE */, 0, 0 /* FIXME 3 */);
-    do_strtosz("-1", -EINVAL /* FIXME -ERANGE */, 0, 0 /* FIXME 2 */);
-    do_strtosz_full("-2M", qemu_strtosz, -EINVAL /* FIXME -ERANGE */, 0,
-                    0 /* FIXME 2 */, -EINVAL, 0);
+    do_strtosz(" -0", -ERANGE, 0, 3);
+    do_strtosz("-1", -ERANGE, 0, 2);
+    do_strtosz_full("-2M", qemu_strtosz, -ERANGE, 0, 2, -EINVAL, 0);
     do_strtosz(" -.0", -EINVAL /* FIXME -ERANGE */, 0, 0 /* FIXME 4 */);
     do_strtosz_full("-.1k", qemu_strtosz, -EINVAL /* FIXME -ERANGE */, 0,
                     0 /* FIXME 3 */, -EINVAL, 0);
diff --git a/util/cutils.c b/util/cutils.c
index b5a6641fa0f..550abbe5c06 100644
--- a/util/cutils.c
+++ b/util/cutils.c
@@ -201,6 +201,7 @@ static int64_t suffix_mul(char suffix, int64_t unit)
  * - hex with scaling suffix, such as 0x20M
  * - octal, such as 08
  * - fractional hex, such as 0x1.8
+ * - negative values, including -0
  * - floating point exponents, such as 1e3
  *
  * The end pointer will be returned in *end, if not NULL.  If there is
@@ -226,15 +227,10 @@ static int do_strtosz(const char *nptr, const char **end,
     int64_t mul;

     /* Parse integral portion as decimal. */
-    retval = qemu_strtou64(nptr, &endptr, 10, &val);
+    retval = parse_uint(nptr, &endptr, 10, &val);
     if (retval) {
         goto out;
     }
-    if (memchr(nptr, '-', endptr - nptr) != NULL) {
-        endptr = nptr;
-        retval = -EINVAL;
-        goto out;
-    }
     if (val == 0 && (*endptr == 'x' || *endptr == 'X')) {
         /* Input looks like hex; reparse, and insist on no fraction or suffix. */
         retval = qemu_strtou64(nptr, &endptr, 16, &val);
-- 
2.40.1



^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v2 18/19] cutils: Improve qemu_strtod* error paths
  2023-05-12  2:10 [PATCH v2 00/19] Fix qemu_strtosz() read-out-of-bounds Eric Blake
                   ` (16 preceding siblings ...)
  2023-05-12  2:10 ` [PATCH v2 17/19] cutils: Use parse_uint in qemu_strtosz for negative rejection Eric Blake
@ 2023-05-12  2:10 ` Eric Blake
  2023-05-18 13:47   ` Eric Blake
  2023-05-12  2:10 ` [PATCH v2 19/19] cutils: Improve qemu_strtosz handling of fractions Eric Blake
  2023-05-12 12:24 ` [PATCH v2 00/19] Fix qemu_strtosz() read-out-of-bounds Eric Blake
  19 siblings, 1 reply; 48+ messages in thread
From: Eric Blake @ 2023-05-12  2:10 UTC (permalink / raw)
  To: qemu-devel; +Cc: hreitz, armbru, richard.henderson

Previous patches changed all integral qemu_strto*() error paths to
guarantee that *value is never left uninitialized.  Do likewise for
qemu_strtod.  Also, tighten qemu_strtod_finite() to never return a
non-finite value (prior to this patch, we were rejecting "inf" with
-EINVAL and unspecified result 0.0, but failing "9e999" with -ERANGE
and HUGE_VAL - which is infinite on IEEE machines - despite our
function claiming to recognize only finite values).

Auditing callers, we have no external callers of qemu_strtod, and
among the callers of qemu_strtod_finite:

- qapi/qobject-input-visitor.c:qobject_input_type_number_keyval() and
  qapi/string-input-visitor.c:parse_type_number() which reject all
  errors (does not matter what we store)

- utils/cutils.c:do_strtosz() incorrectly assumes that *endptr points
  to '.' on all failures (that is, it is not distinguishing between
  EINVAL and ERANGE; and therefore still does the WRONG THING for
  "9.9e999".  The change here does not entirely fix that (a later
  patch will tackle this more systematically), but at least the value
  of endptr is now less likely to be out of bounds on overflow

- our testsuite, which we can update to match what we document

Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Hanna Czenczek <hreitz@redhat.com>

---

v2: no change to cutils.c; commit message tweaks and testsuite rebase
were minor enough to keep R-b
---
 tests/unit/test-cutils.c | 63 +++++++++++++++++++++++-----------------
 util/cutils.c            | 32 +++++++++++---------
 2 files changed, 55 insertions(+), 40 deletions(-)

diff --git a/tests/unit/test-cutils.c b/tests/unit/test-cutils.c
index b8ad4d7fbac..8f2dd335f13 100644
--- a/tests/unit/test-cutils.c
+++ b/tests/unit/test-cutils.c
@@ -2747,7 +2747,8 @@ static void test_qemu_strtod_einval(void)
     res = 999;
     err = qemu_strtod(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpfloat(res, ==, 999.0);
+    g_assert_cmpfloat(res, ==, 0.0);
+    g_assert_false(signbit(res));
     g_assert_null(endptr);

     /* not recognizable */
@@ -2979,7 +2980,8 @@ static void test_qemu_strtod_finite_einval(void)
     res = 999;
     err = qemu_strtod_finite(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpfloat(res, ==, 999.0);
+    g_assert_cmpfloat(res, ==, 0.0);
+    g_assert_false(signbit(res));
     g_assert_true(endptr == str);

     /* NULL */
@@ -2988,7 +2990,8 @@ static void test_qemu_strtod_finite_einval(void)
     res = 999;
     err = qemu_strtod_finite(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpfloat(res, ==, 999.0);
+    g_assert_cmpfloat(res, ==, 0.0);
+    g_assert_false(signbit(res));
     g_assert_null(endptr);

     /* not recognizable */
@@ -2997,7 +3000,8 @@ static void test_qemu_strtod_finite_einval(void)
     res = 999;
     err = qemu_strtod_finite(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpfloat(res, ==, 999.0);
+    g_assert_cmpfloat(res, ==, 0.0);
+    g_assert_false(signbit(res));
     g_assert_true(endptr == str);
 }

@@ -3008,24 +3012,26 @@ static void test_qemu_strtod_finite_erange(void)
     int err;
     double res;

-    /* overflow */
+    /* overflow turns into EINVAL */
     str = "9e999";
     endptr = "somewhere";
     res = 999;
     err = qemu_strtod_finite(str, &endptr, &res);
-    g_assert_cmpint(err, ==, -ERANGE);
-    g_assert_cmpfloat(res, ==, HUGE_VAL);
-    g_assert_true(endptr == str + 5);
+    g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpfloat(res, ==, 0.0);
+    g_assert_false(signbit(res));
+    g_assert_true(endptr == str);

     str = "-9e+999";
     endptr = "somewhere";
     res = 999;
     err = qemu_strtod_finite(str, &endptr, &res);
-    g_assert_cmpint(err, ==, -ERANGE);
-    g_assert_cmpfloat(res, ==, -HUGE_VAL);
-    g_assert_true(endptr == str + 7);
+    g_assert_cmpint(err, ==, -EINVAL);
+    g_assert_cmpfloat(res, ==, 0.0);
+    g_assert_false(signbit(res));
+    g_assert_true(endptr == str);

-    /* underflow */
+    /* underflow is still possible */
     str = "-9e-999";
     endptr = "somewhere";
     res = 999;
@@ -3050,7 +3056,8 @@ static void test_qemu_strtod_finite_nonfinite(void)
     res = 999;
     err = qemu_strtod_finite(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpfloat(res, ==, 999.0);
+    g_assert_cmpfloat(res, ==, 0.0);
+    g_assert_false(signbit(res));
     g_assert_true(endptr == str);

     str = "-infinity";
@@ -3058,7 +3065,8 @@ static void test_qemu_strtod_finite_nonfinite(void)
     res = 999;
     err = qemu_strtod_finite(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpfloat(res, ==, 999.0);
+    g_assert_cmpfloat(res, ==, 0.0);
+    g_assert_false(signbit(res));
     g_assert_true(endptr == str);

     /* not a number */
@@ -3067,7 +3075,8 @@ static void test_qemu_strtod_finite_nonfinite(void)
     res = 999;
     err = qemu_strtod_finite(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpfloat(res, ==, 999.0);
+    g_assert_cmpfloat(res, ==, 0.0);
+    g_assert_false(signbit(res));
     g_assert_true(endptr == str);
 }

@@ -3091,7 +3100,8 @@ static void test_qemu_strtod_finite_trailing(void)
     res = 999;
     err = qemu_strtod_finite(str, NULL, &res);
     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpfloat(res, ==, 999.0);
+    g_assert_cmpfloat(res, ==, 1.0);
+    g_assert_false(signbit(res));

     /* trailing e is not an exponent */
     str = ".5e";
@@ -3106,7 +3116,7 @@ static void test_qemu_strtod_finite_trailing(void)
     res = 999;
     err = qemu_strtod_finite(str, NULL, &res);
     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpfloat(res, ==, 999.0);
+    g_assert_cmpfloat(res, ==, 0.5);

     /* trailing ( not part of long NaN */
     str = "nan(";
@@ -3114,14 +3124,16 @@ static void test_qemu_strtod_finite_trailing(void)
     res = 999;
     err = qemu_strtod_finite(str, &endptr, &res);
     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpfloat(res, ==, 999.0);
+    g_assert_cmpfloat(res, ==, 0.0);
+    g_assert_false(signbit(res));
     g_assert_true(endptr == str);

     endptr = "somewhere";
     res = 999;
     err = qemu_strtod_finite(str, NULL, &res);
     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpfloat(res, ==, 999.0);
+    g_assert_cmpfloat(res, ==, 0.0);
+    g_assert_false(signbit(res));
 }

 static void test_qemu_strtod_finite_erange_junk(void)
@@ -3146,7 +3158,8 @@ static void test_qemu_strtod_finite_erange_junk(void)
     res = 999;
     err = qemu_strtod_finite(str, NULL, &res);
     g_assert_cmpint(err, ==, -EINVAL);
-    g_assert_cmpfloat(res, ==, 999.0);
+    g_assert_cmpfloat(res, ==, 0.0);
+    g_assert_false(signbit(res));
 }

 typedef int (*qemu_strtosz_fn)(const char *, const char **, uint64_t *);
@@ -3384,13 +3397,9 @@ static void test_qemu_strtosz_trailing(void)
                     0 /* FIXME EiB * 1.5 */, 0 /* FIXME 4 */,
                     -EINVAL, 0);

-    /*
-     * FIXME overflow in fraction is so buggy it can read beyond bounds
-     * if we don't stuff extra \0 in our literal
-     */
-    do_strtosz_full("1.5E999\0\0" /* FIXME 1.5E999" */, qemu_strtosz,
-                    0, 1 /* FIXME EiB * 1.5 */, 8 /* FIXME 4 */,
-                    0 /* FIXME -EINVAL */, 1 /* FIXME 0 */);
+    /* FIXME overflow in fraction is still buggy */
+    do_strtosz_full("1.5E999", qemu_strtosz, 0, 1 /* FIXME EiB * 1.5 */,
+                    2 /* FIXME 4 */, -EINVAL, 0);
 }

 static void test_qemu_strtosz_erange(void)
diff --git a/util/cutils.c b/util/cutils.c
index 550abbe5c06..25c95b0933e 100644
--- a/util/cutils.c
+++ b/util/cutils.c
@@ -653,12 +653,13 @@ int qemu_strtou64(const char *nptr, const char **endptr, int base,
  *
  * @nptr may be null, and no conversion is performed then.
  *
- * If no conversion is performed, store @nptr in *@endptr and return
- * -EINVAL.
+ * If no conversion is performed, store @nptr in *@endptr, +0.0 in
+ * @result, and return -EINVAL.
  *
  * If @endptr is null, and the string isn't fully converted, return
- * -EINVAL. This is the case when the pointer that would be stored in
- * a non-null @endptr points to a character other than '\0'.
+ * -EINVAL with @result set to the parsed value.  This is the case
+ * when the pointer that would be stored in a non-null @endptr points
+ * to a character other than '\0'.
  *
  * If the conversion overflows, store +/-HUGE_VAL in @result, depending
  * on the sign, and return -ERANGE.
@@ -673,6 +674,7 @@ int qemu_strtod(const char *nptr, const char **endptr, double *result)
     char *ep;

     if (!nptr) {
+        *result = 0.0;
         if (endptr) {
             *endptr = nptr;
         }
@@ -687,24 +689,28 @@ int qemu_strtod(const char *nptr, const char **endptr, double *result)
 /**
  * Convert string @nptr to a finite double.
  *
- * Works like qemu_strtod(), except that "NaN" and "inf" are rejected
- * with -EINVAL and no conversion is performed.
+ * Works like qemu_strtod(), except that "NaN", "inf", and strings
+ * that cause ERANGE overflow errors are rejected with -EINVAL as if
+ * no conversion is performed, storing 0.0 into @result regardless of
+ * any sign.  -ERANGE failures for underflow still preserve the parsed
+ * sign.
  */
 int qemu_strtod_finite(const char *nptr, const char **endptr, double *result)
 {
-    double tmp;
+    const char *tmp;
     int ret;

-    ret = qemu_strtod(nptr, endptr, &tmp);
-    if (!ret && !isfinite(tmp)) {
+    ret = qemu_strtod(nptr, &tmp, result);
+    if (!isfinite(*result)) {
         if (endptr) {
             *endptr = nptr;
         }
+        *result = 0.0;
+        ret = -EINVAL;
+    } else if (endptr) {
+        *endptr = tmp;
+    } else if (*tmp) {
         ret = -EINVAL;
-    }
-
-    if (ret != -EINVAL) {
-        *result = tmp;
     }
     return ret;
 }
-- 
2.40.1



^ permalink raw reply related	[flat|nested] 48+ messages in thread

* [PATCH v2 19/19] cutils: Improve qemu_strtosz handling of fractions
  2023-05-12  2:10 [PATCH v2 00/19] Fix qemu_strtosz() read-out-of-bounds Eric Blake
                   ` (17 preceding siblings ...)
  2023-05-12  2:10 ` [PATCH v2 18/19] cutils: Improve qemu_strtod* error paths Eric Blake
@ 2023-05-12  2:10 ` Eric Blake
  2023-05-19 15:36   ` Hanna Czenczek
  2023-05-12 12:24 ` [PATCH v2 00/19] Fix qemu_strtosz() read-out-of-bounds Eric Blake
  19 siblings, 1 reply; 48+ messages in thread
From: Eric Blake @ 2023-05-12  2:10 UTC (permalink / raw)
  To: qemu-devel; +Cc: hreitz, armbru, richard.henderson

We have several limitations and bugs worth fixing; they are
inter-related enough that it is not worth splitting this patch into
smaller pieces:

* ".5k" should work to specify 512, just as "0.5k" does
* "1.9999k" and "1." + "9"*50 + "k" should both produce the same
  result of 2048 after rounding
* "1." + "0"*350 + "1B" should not be treated the same as "1.0B";
  underflow in the fraction should not be lost
* "7.99e99" and "7.99e999" look similar, but our code was doing a
  read-out-of-bounds on the latter because it was not expecting ERANGE
  due to overflow. While we document that scientific notation is not
  supported, and the previous patch actually fixed
  qemu_strtod_finite() to no longer return ERANGE overflows, it is
  easier to pre-filter than to try and determine after the fact if
  strtod() consumed more than we wanted.  Note that this is a
  low-level semantic change (when endptr is not NULL, we can now
  successfully parse with a scale of 'E' and then report trailing
  junk, instead of failing outright with EINVAL); but an earlier
  commit already argued that this is not a high-level semantic change
  since the only caller passing in a non-NULL endptr also checks that
  the tail is whitespace-only.

Fixes: https://gitlab.com/qemu-project/qemu/-/issues/1629
Fixes: cf923b78 ("utils: Improve qemu_strtosz() to have 64 bits of precision", 6.0.0)
Fixes: 7625a1ed ("utils: Use fixed-point arithmetic in qemu_strtosz", 6.0.0)
Signed-off-by: Eric Blake <eblake@redhat.com>

---

v2: more changes, handle negatives differently, catch fractions that
round to 0 but don't underflow [Hanna]
---
 tests/unit/test-cutils.c | 50 +++++++++-------------
 util/cutils.c            | 89 ++++++++++++++++++++++++++++++----------
 2 files changed, 86 insertions(+), 53 deletions(-)

diff --git a/tests/unit/test-cutils.c b/tests/unit/test-cutils.c
index 8f2dd335f13..67c3de00c82 100644
--- a/tests/unit/test-cutils.c
+++ b/tests/unit/test-cutils.c
@@ -3285,19 +3285,18 @@ static void test_qemu_strtosz_float(void)
     /* An empty fraction tail is tolerated */
     do_strtosz("1.k", 0, 1024, 3);

-    /* FIXME An empty fraction head should be tolerated */
-    do_strtosz(" .5k", -EINVAL /* FIXME 0 */, 0 /* FIXME 512 */,
-               0 /* FIXME 4 */);
+    /* An empty fraction head is tolerated */
+    do_strtosz(" .5k", 0, 512, 4);

     /* For convenience, we permit values that are not byte-exact */
     do_strtosz("12.345M", 0, (uint64_t) (12.345 * MiB + 0.5), 7);

-    /* FIXME Fraction tail should round correctly */
+    /* Fraction tail can round up */
     do_strtosz("1.9999k", 0, 2048, 7);
     do_strtosz("1.9999999999999999999999999999999999999999999999999999k", 0,
-               1024 /* FIXME 2048 */, 55);
+               2048, 55);

-    /* FIXME ERANGE underflow in the fraction tail should not matter for 'k' */
+    /* ERANGE underflow in the fraction tail does not matter for 'k' */
     do_strtosz("1."
                "00000000000000000000000000000000000000000000000000"
                "00000000000000000000000000000000000000000000000000"
@@ -3306,7 +3305,7 @@ static void test_qemu_strtosz_float(void)
                "00000000000000000000000000000000000000000000000000"
                "00000000000000000000000000000000000000000000000000"
                "00000000000000000000000000000000000000000000000000"
-               "1k", 0, 1 /* FIXME 1024 */, 354);
+               "1k", 0, 1024, 354);
 }

 static void test_qemu_strtosz_invalid(void)
@@ -3330,10 +3329,9 @@ static void test_qemu_strtosz_invalid(void)
     do_strtosz("1.1B", -EINVAL, 0, 0);
     do_strtosz("1.1", -EINVAL, 0, 0);

-    /* FIXME underflow in the fraction tail should matter for 'B' */
+    /* 'B' cannot have any nonzero fraction, even with rounding or underflow */
     do_strtosz("1.00001B", -EINVAL, 0, 0);
-    do_strtosz("1.00000000000000000001B", 0 /* FIXME -EINVAL */,
-               1 /* FIXME 0 */, 23 /* FIXME 0 */);
+    do_strtosz("1.00000000000000000001B", -EINVAL, 0, 0);
     do_strtosz("1."
                "00000000000000000000000000000000000000000000000000"
                "00000000000000000000000000000000000000000000000000"
@@ -3342,8 +3340,7 @@ static void test_qemu_strtosz_invalid(void)
                "00000000000000000000000000000000000000000000000000"
                "00000000000000000000000000000000000000000000000000"
                "00000000000000000000000000000000000000000000000000"
-               "1B", 0 /* FIXME -EINVAL */, 1 /* FIXME 0 */,
-               354 /* FIXME 0 */);
+               "1B", -EINVAL, 0, 0);

     /* No hex fractions */
     do_strtosz("0x1.8k", -EINVAL, 0, 0);
@@ -3389,28 +3386,20 @@ static void test_qemu_strtosz_trailing(void)
     do_strtosz_full("123-45", qemu_strtosz, 0, 123, 3, -EINVAL, 0);
     do_strtosz_full(" 123 - 45", qemu_strtosz, 0, 123, 4, -EINVAL, 0);

-    /* FIXME should stop parse after 'e'. No floating point exponents */
-    do_strtosz_full("1.5e1k", qemu_strtosz, -EINVAL /* FIXME 0 */,
-                    0 /* FIXME EiB * 1.5 */, 0 /* FIXME 4 */,
-                    -EINVAL, 0);
-    do_strtosz_full("1.5E+0k", qemu_strtosz, -EINVAL /* FIXME 0 */,
-                    0 /* FIXME EiB * 1.5 */, 0 /* FIXME 4 */,
-                    -EINVAL, 0);
-
-    /* FIXME overflow in fraction is still buggy */
-    do_strtosz_full("1.5E999", qemu_strtosz, 0, 1 /* FIXME EiB * 1.5 */,
-                    2 /* FIXME 4 */, -EINVAL, 0);
+    /* Parse stops at 'e', which is not a floating point exponent */
+    do_strtosz_full("1.5e1k", qemu_strtosz, 0, EiB * 1.5, 4, -EINVAL, 0);
+    do_strtosz_full("1.5E+0k", qemu_strtosz, 0, EiB * 1.5, 4, -EINVAL, 0);
+    do_strtosz_full("1.5E999", qemu_strtosz, 0, EiB * 1.5, 4, -EINVAL, 0);
 }

 static void test_qemu_strtosz_erange(void)
 {
-    /* FIXME negative values fit better as ERANGE */
+    /* no negative values */
     do_strtosz(" -0", -ERANGE, 0, 3);
     do_strtosz("-1", -ERANGE, 0, 2);
     do_strtosz_full("-2M", qemu_strtosz, -ERANGE, 0, 2, -EINVAL, 0);
-    do_strtosz(" -.0", -EINVAL /* FIXME -ERANGE */, 0, 0 /* FIXME 4 */);
-    do_strtosz_full("-.1k", qemu_strtosz, -EINVAL /* FIXME -ERANGE */, 0,
-                    0 /* FIXME 3 */, -EINVAL, 0);
+    do_strtosz(" -.0", -ERANGE, 0, 4);
+    do_strtosz_full("-.1k", qemu_strtosz, -ERANGE, 0, 3, -EINVAL, 0);
     do_strtosz_full(" -."
                     "00000000000000000000000000000000000000000000000000"
                     "00000000000000000000000000000000000000000000000000"
@@ -3419,17 +3408,16 @@ static void test_qemu_strtosz_erange(void)
                     "00000000000000000000000000000000000000000000000000"
                     "00000000000000000000000000000000000000000000000000"
                     "00000000000000000000000000000000000000000000000000"
-                    "1M", qemu_strtosz, -EINVAL /* FIXME -ERANGE */, 0,
-                    0 /* FIXME 354 */, -EINVAL, 0);
+                    "1M", qemu_strtosz, -ERANGE, 0, 354, -EINVAL, 0);

     /* 2^64; see strtosz_simple for 2^64-1 */
     do_strtosz("18446744073709551616", -ERANGE, 0, 20);

     do_strtosz("20E", -ERANGE, 0, 3);

-    /* FIXME Fraction tail can cause ERANGE overflow */
+    /* Fraction tail can cause ERANGE overflow */
     do_strtosz("15.9999999999999999999999999999999999999999999999999999E",
-               0 /* FIXME -ERANGE */, 15ULL * EiB /* FIXME 0 */, 56);
+               -ERANGE, 0, 56);

     /* EINVAL has priority over ERANGE */
     do_strtosz_full("100000Pjunk", qemu_strtosz, -ERANGE, 0, 7, -EINVAL, 0);
diff --git a/util/cutils.c b/util/cutils.c
index 25c95b0933e..20b732176fa 100644
--- a/util/cutils.c
+++ b/util/cutils.c
@@ -194,15 +194,18 @@ static int64_t suffix_mul(char suffix, int64_t unit)
  * - 12345 - decimal, scale determined by @default_suffix and @unit
  * - 12345{bBkKmMgGtTpPeE} - decimal, scale determined by suffix and @unit
  * - 12345.678{kKmMgGtTpPeE} - decimal, scale determined by suffix, and
- *   fractional portion is truncated to byte
+ *   fractional portion is truncated to byte, either side of . may be empty
  * - 0x7fEE - hexadecimal, unit determined by @default_suffix
  *
  * The following are intentionally not supported
- * - hex with scaling suffix, such as 0x20M
- * - octal, such as 08
- * - fractional hex, such as 0x1.8
- * - negative values, including -0
- * - floating point exponents, such as 1e3
+ * - hex with scaling suffix, such as 0x20M (0x1b is 27, not 1)
+ * - octal, such as 08 (parsed as decimal instead)
+ * - binary, such as 0b1000 (parsed as 0b with trailing garbage "1000")
+ * - fractional hex, such as 0x1.8 (parsed as 0 with trailing garbage "x1.8")
+ * - negative values, including -0 (fail with -ERANGE)
+ * - floating point exponents, such as 1e3 (parsed as 1e with trailing
+ *   garbage "3") or 0x1p3 (parsed as 1 with trailing garbage "p3")
+ * - non-finite values, such as inf or NaN (fail with -EINVAL)
  *
  * The end pointer will be returned in *end, if not NULL.  If there is
  * no fraction, the input can be decimal or hexadecimal; if there is a
@@ -221,17 +224,17 @@ static int do_strtosz(const char *nptr, const char **end,
                       uint64_t *result)
 {
     int retval;
-    const char *endptr, *f;
+    const char *endptr;
     unsigned char c;
-    uint64_t val, valf = 0;
+    uint64_t val = 0, valf = 0;
     int64_t mul;

     /* Parse integral portion as decimal. */
     retval = parse_uint(nptr, &endptr, 10, &val);
-    if (retval) {
+    if (retval == -ERANGE || !nptr) {
         goto out;
     }
-    if (val == 0 && (*endptr == 'x' || *endptr == 'X')) {
+    if (retval == 0 && val == 0 && (*endptr == 'x' || *endptr == 'X')) {
         /* Input looks like hex; reparse, and insist on no fraction or suffix. */
         retval = qemu_strtou64(nptr, &endptr, 16, &val);
         if (retval) {
@@ -242,27 +245,69 @@ static int do_strtosz(const char *nptr, const char **end,
             retval = -EINVAL;
             goto out;
         }
-    } else if (*endptr == '.') {
+    } else if (*endptr == '.' || (endptr == nptr && strchr(nptr, '.'))) {
         /*
          * Input looks like a fraction.  Make sure even 1.k works
-         * without fractional digits.  If we see an exponent, treat
-         * the entire input as invalid instead.
+         * without fractional digits.  strtod tries to treat 'e' as an
+         * exponent, but we want to treat it as a scaling suffix;
+         * doing this requires modifying a copy of the fraction.
          */
-        double fraction;
+        double fraction = 0.0;

-        f = endptr;
-        retval = qemu_strtod_finite(f, &endptr, &fraction);
-        if (retval) {
+        if (retval == 0 && *endptr == '.' && !isdigit(endptr[1])) {
+            /* If we got here, we parsed at least one digit already. */
             endptr++;
-        } else if (memchr(f, 'e', endptr - f) || memchr(f, 'E', endptr - f)) {
-            endptr = nptr;
-            retval = -EINVAL;
-            goto out;
         } else {
-            /* Extract into a 64-bit fixed-point fraction. */
+            char *e;
+            const char *tail;
+            g_autofree char *copy = g_strdup(endptr);
+
+            e = strchr(copy, 'e');
+            if (e) {
+                *e = '\0';
+            }
+            e = strchr(copy, 'E');
+            if (e) {
+                *e = '\0';
+            }
+            /*
+             * If this is a floating point, we are guaranteed that '.'
+             * appears before any possible digits in copy.  If it is
+             * not a floating point, strtod will fail.  Either way,
+             * there is now no exponent in copy, so if it parses, we
+             * know 0.0 <= abs(result) <= 1.0 (after rounding), and
+             * ERANGE is only possible on underflow which is okay.
+             */
+            retval = qemu_strtod_finite(copy, &tail, &fraction);
+            endptr += tail - copy;
+            if (signbit(fraction)) {
+                retval = -ERANGE;
+                goto out;
+            }
+        }
+
+        /* Extract into a 64-bit fixed-point fraction. */
+        if (fraction == 1.0) {
+            if (val == UINT64_MAX) {
+                retval = -ERANGE;
+                goto out;
+            }
+            val++;
+        } else if (retval == -ERANGE) {
+            /* See comments above about underflow */
+            valf = 1;
+            retval = 0;
+        } else {
+            /* We want non-zero valf for any non-zero fraction */
             valf = (uint64_t)(fraction * 0x1p64);
+            if (valf == 0 && fraction > 0.0) {
+                valf = 1;
+            }
         }
     }
+    if (retval) {
+        goto out;
+    }
     c = *endptr;
     mul = suffix_mul(c, unit);
     if (mul > 0) {
-- 
2.40.1



^ permalink raw reply related	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 01/19] test-cutils: Avoid g_assert in unit tests
  2023-05-12  2:10 ` [PATCH v2 01/19] test-cutils: Avoid g_assert in unit tests Eric Blake
@ 2023-05-12  3:20   ` Philippe Mathieu-Daudé
  2023-05-12 12:11   ` Eric Blake
  1 sibling, 0 replies; 48+ messages in thread
From: Philippe Mathieu-Daudé @ 2023-05-12  3:20 UTC (permalink / raw)
  To: Eric Blake, qemu-devel; +Cc: hreitz, armbru, richard.henderson

On 12/5/23 04:10, Eric Blake wrote:
> glib documentation[1] is clear: g_assert() should be avoided in unit
> tests because it is ineffective if G_DISABLE_ASSERT is defined; unit
> tests should stick to constructs based on g_assert_true() instead.
> Note that since commit 262a69f428, we intentionally state that you
> cannot define G_DISABLE_ASSERT that while building qemu; but our code
> can be copied to other projects without that restriction, so we should
> be consistent.
> 
> For most of the replacements in this patch, using g_assert_cmpstr()
> would be a regression in quality - although it would helpfully display
> the string contents of both pointers on test failure, here, we really
> do care about pointer equality, not just string content equality.  But
> when a NULL pointer is expected, g_assert_null works fine.
> 
> [1] https://libsoup.org/glib/glib-Testing.html#g-assert
> 
> Signed-off-by: Eric Blake <eblake@redhat.com>
> Reviewed-by: Hanna Czenczek <hreitz@redhat.com>
> ---
>   tests/unit/test-cutils.c | 324 +++++++++++++++++++--------------------
>   1 file changed, 162 insertions(+), 162 deletions(-)

Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>



^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 12/19] cutils: Allow NULL str in qemu_strtosz
  2023-05-12  2:10 ` [PATCH v2 12/19] cutils: Allow NULL str in qemu_strtosz Eric Blake
@ 2023-05-12  3:25   ` Philippe Mathieu-Daudé
  2023-05-19 15:15   ` Hanna Czenczek
  1 sibling, 0 replies; 48+ messages in thread
From: Philippe Mathieu-Daudé @ 2023-05-12  3:25 UTC (permalink / raw)
  To: Eric Blake, qemu-devel; +Cc: hreitz, armbru, richard.henderson

On 12/5/23 04:10, Eric Blake wrote:
> All the other qemu_strto* and parse_uint allow a NULL str.  Having
> qemu_strtosz crash on qemu_strtosz(NULL, NULL, &value) is an easy fix
> that adds some consistency between our string parsers.
> 
> Signed-off-by: Eric Blake <eblake@redhat.com>
> ---
>   tests/unit/test-cutils.c | 3 +++
>   util/cutils.c            | 2 +-
>   2 files changed, 4 insertions(+), 1 deletion(-)

Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>



^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 01/19] test-cutils: Avoid g_assert in unit tests
  2023-05-12  2:10 ` [PATCH v2 01/19] test-cutils: Avoid g_assert in unit tests Eric Blake
  2023-05-12  3:20   ` Philippe Mathieu-Daudé
@ 2023-05-12 12:11   ` Eric Blake
  1 sibling, 0 replies; 48+ messages in thread
From: Eric Blake @ 2023-05-12 12:11 UTC (permalink / raw)
  To: qemu-devel; +Cc: hreitz, armbru, richard.henderson

On Thu, May 11, 2023 at 09:10:15PM -0500, Eric Blake wrote:
> glib documentation[1] is clear: g_assert() should be avoided in unit
> tests because it is ineffective if G_DISABLE_ASSERT is defined; unit
> tests should stick to constructs based on g_assert_true() instead.
> Note that since commit 262a69f428, we intentionally state that you
> cannot define G_DISABLE_ASSERT that while building qemu; but our code

s/that //

> can be copied to other projects without that restriction, so we should
> be consistent.
> 
> For most of the replacements in this patch, using g_assert_cmpstr()
> would be a regression in quality - although it would helpfully display
> the string contents of both pointers on test failure, here, we really
> do care about pointer equality, not just string content equality.  But
> when a NULL pointer is expected, g_assert_null works fine.
> 
> [1] https://libsoup.org/glib/glib-Testing.html#g-assert
> 
> Signed-off-by: Eric Blake <eblake@redhat.com>
> Reviewed-by: Hanna Czenczek <hreitz@redhat.com>
> ---
>  tests/unit/test-cutils.c | 324 +++++++++++++++++++--------------------
>  1 file changed, 162 insertions(+), 162 deletions(-)
> 

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org



^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 00/19] Fix qemu_strtosz() read-out-of-bounds
  2023-05-12  2:10 [PATCH v2 00/19] Fix qemu_strtosz() read-out-of-bounds Eric Blake
                   ` (18 preceding siblings ...)
  2023-05-12  2:10 ` [PATCH v2 19/19] cutils: Improve qemu_strtosz handling of fractions Eric Blake
@ 2023-05-12 12:24 ` Eric Blake
  19 siblings, 0 replies; 48+ messages in thread
From: Eric Blake @ 2023-05-12 12:24 UTC (permalink / raw)
  To: qemu-devel; +Cc: hreitz, armbru, richard.henderson

On Thu, May 11, 2023 at 09:10:14PM -0500, Eric Blake wrote:
> v1 was here:
> https://lists.gnu.org/archive/html/qemu-devel/2023-05/msg01988.html
> 
> since then:
> - make parse_uint easier to use, then use it in qemu_strtosz
> - add even more unit tests
> - fix a bug in qemu_strtoui
> - avoid dereferencing randome memory during unit tests [Hanna]
> - other cleanups as I found them
> - compress the strtosz unit tests (the major cause of the large
>   interdiff statistics)
> 
> backport-diff looks like:
> 
> 001/19:[----] [--] 'test-cutils: Avoid g_assert in unit tests'
> 002/19:[----] [--] 'test-cutils: Use g_assert_cmpuint where appropriate'
> 003/19:[----] [--] 'test-cutils: Test integral qemu_strto* value on failures'
> 004/19:[down] 'test-cutils: Test more integer corner cases'
> 005/19:[down] 'cutils: Fix wraparound parsing in qemu_strtoui'
> 006/19:[down] 'cutils: Document differences between parse_uint and qemu_strtou64'
> 007/19:[down] 'cutils: Adjust signature of parse_uint[_full]'
> 008/19:[down] 'cutils: Allow NULL endptr in parse_uint()'
> 009/19:[0147] [FC] 'test-cutils: Add coverage of qemu_strtod'
> 010/19:[----] [--] 'test-cutils: Prepare for upcoming semantic change in qemu_strtosz'
> 011/19:[down] 'test-cutils: Refactor qemu_strtosz tests for less boilerplate'
> 012/19:[down] 'cutils: Allow NULL str in qemu_strtosz'
> 013/19:[----] [--] 'numa: Check for qemu_strtosz_MiB error'
> 014/19:[down] 'test-cutils: Add more coverage to qemu_strtosz11;rgb:1e1e/1e1e/1e1e'

Not sure how I managed to corrupt that subject line while rebasing
(looks like a read race on /dev/tty where my editor intercepted bytes
intended to go to the shell's terminal); a corrected version is now
available at:

git fetch https://repo.or.cz/qemu/ericb.git strtosz
https://repo.or.cz/qemu/ericb.git/tree/refs/heads/strtosz

and with that fixed, this line changes to:

014/18:[0335] [FC] 'test-cutils: Add more coverage to qemu_strtosz'

> 015/19:[0178] [FC] 'cutils: Set value in all qemu_strtosz* error paths'
> 016/19:[----] [--] 'cutils: Set value in all integral qemu_strto* error paths'
> 017/19:[down] 'cutils: Use parse_uint in qemu_strtosz for negative rejection'
> 018/19:[0018] [FC] 'cutils: Improve qemu_strtod* error paths'
> 019/19:[0107] [FC] 'cutils: Improve qemu_strtosz handling of fractions'
> 

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org



^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 07/19] cutils: Adjust signature of parse_uint[_full]
  2023-05-12  2:10 ` [PATCH v2 07/19] cutils: Adjust signature of parse_uint[_full] Eric Blake
@ 2023-05-12 16:25   ` Eric Blake
  2023-05-19 14:51   ` Hanna Czenczek
  1 sibling, 0 replies; 48+ messages in thread
From: Eric Blake @ 2023-05-12 16:25 UTC (permalink / raw)
  To: qemu-devel
  Cc: hreitz, armbru, richard.henderson, Gerd Hoffmann,
	Marc-André Lureau, Kevin Wolf, Peter Lieven, Michael Roth,
	Daniel P. Berrangé,
	open list:GLUSTER, open list:GLUSTER


On Thu, May 11, 2023 at 09:10:21PM -0500, Eric Blake wrote:
> 
> It's already confusing that we have two very similar functions for
> wrapping the parse of a 64-bit unsigned value, differing mainly on
> whether they permit leading '-'.  Adjust the signature of parse_uint()
> and parse_uint_full() to be like all of qemu_strto*(): put the result
> parameter last, use the same types (uint64_t is not always the same as
> unsigned long long, and mark endptr const (only latter affects the

I blame my late night editing.  Looks better as:

...use the same types (uint64_t and unsigned long long have the same
width, but are not always the same type), and mark endptr const (this
latter change only affects the...

> rare caller of parse_uint).  Adjust all callers in the tree.
> 
> Signed-off-by: Eric Blake <eblake@redhat.com>
> ---
>  include/qemu/cutils.h         |   5 +-
>  audio/audio_legacy.c          |   4 +-
>  block/gluster.c               |   4 +-
>  block/nfs.c                   |   4 +-
>  blockdev.c                    |   4 +-
>  contrib/ivshmem-server/main.c |   4 +-
>  qapi/opts-visitor.c           |  10 +--
>  tests/unit/test-cutils.c      | 113 +++++++++++++++-------------------
>  ui/vnc.c                      |   4 +-
>  util/cutils.c                 |  13 ++--
>  util/guest-random.c           |   4 +-
>  util/qemu-sockets.c           |  10 +--
>  12 files changed, 82 insertions(+), 97 deletions(-)
> 

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org



^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 08/19] cutils: Allow NULL endptr in parse_uint()
  2023-05-12  2:10 ` [PATCH v2 08/19] cutils: Allow NULL endptr in parse_uint() Eric Blake
@ 2023-05-12 16:44   ` Eric Blake
  2023-05-19 14:54   ` Hanna Czenczek
  1 sibling, 0 replies; 48+ messages in thread
From: Eric Blake @ 2023-05-12 16:44 UTC (permalink / raw)
  To: qemu-devel; +Cc: hreitz, armbru, richard.henderson


On Thu, May 11, 2023 at 09:10:22PM -0500, Eric Blake wrote:
> 
> All the qemu_strto*() functions permit a NULL endptr, just like their
> libc counterparts, leaving parse_uint() as the oddball that caused
> SEGFAULT on NULL and required the user to call parse_uint_full()
> instead.  Relax things for consistency, even though the testsuite is
> the only impacted caller.  Add one more unit test to ensure even
> parse_uint_full(NULL, 0, &value) works.  This also fixes our code to
> uniformly favor EINVAL over ERANGE when both apply.
> 
> Signed-off-by: Eric Blake <eblake@redhat.com>
> ---
>  tests/unit/test-cutils.c | 18 ++++++++++++++++--
>  util/cutils.c            | 34 ++++++++++++----------------------
>  2 files changed, 28 insertions(+), 24 deletions(-)

> 
> @@ -788,28 +793,13 @@ out:
>   * @base: integer base, between 2 and 36 inclusive, or 0
>   * @value: Destination for parsed integer value
>   *
> - * Parse unsigned integer from entire string
> + * Parse unsigned integer from entire string, rejecting any trailing slop.
>   *
> - * Have the same behavior of parse_uint(), but with an additional
> - * check for additional data after the parsed number. If extra
> - * characters are present after a non-overflowing parsed number, the
> - * function will return -EINVAL, and *@v will be set to 0.
> + * Shorthand for parse_uint(s, NULL, base, value).

I'm just now noticing that I also had a nice side effect of removing
the reference to *@v when the parameter is actually named value.

>   */
>  int parse_uint_full(const char *s, int base, uint64_t *value)

I don't know if there is an easy way to test our doc comments for
mismatch from parameter names.

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org



^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 17/19] cutils: Use parse_uint in qemu_strtosz for negative rejection
  2023-05-12  2:10 ` [PATCH v2 17/19] cutils: Use parse_uint in qemu_strtosz for negative rejection Eric Blake
@ 2023-05-12 19:34   ` Eric Blake
  2023-05-19 15:32     ` Hanna Czenczek
  0 siblings, 1 reply; 48+ messages in thread
From: Eric Blake @ 2023-05-12 19:34 UTC (permalink / raw)
  To: qemu-devel; +Cc: hreitz, armbru, richard.henderson


On Thu, May 11, 2023 at 09:10:31PM -0500, Eric Blake wrote:
> 
> Rather than open-coding two different ways to check for an unwanted
> negative sign, reuse the same code in both functions.  That way, if we
> decide down the road to accept "-0" instead of rejecting it, we have
> fewer places to change.  Also, it means we now get ERANGE instead of
> EINVAL for negative values in qemu_strtosz, which is reasonable for
> what it represents.
> 
> Signed-off-by: Eric Blake <eblake@redhat.com>
> ---
>  tests/unit/test-cutils.c | 7 +++----
>  util/cutils.c            | 8 ++------
>  2 files changed, 5 insertions(+), 10 deletions(-)

Returning ERANGE instead of EINVAL changes the expected output for
iotests 49 and 178; this needs to be squashed in:

diff --git i/tests/qemu-iotests/049.out w/tests/qemu-iotests/049.out
index 8719c91b483..34e1b452e6e 100644
--- i/tests/qemu-iotests/049.out
+++ w/tests/qemu-iotests/049.out
@@ -92,13 +92,10 @@ Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 extended_l2=off comp
 == 3. Invalid sizes ==

 qemu-img create -f qcow2 TEST_DIR/t.qcow2 -- -1024
-qemu-img: Invalid image size specified. You may use k, M, G, T, P or E suffixes for
-qemu-img: kilobytes, megabytes, gigabytes, terabytes, petabytes and exabytes.
+qemu-img: Invalid image size specified. Must be between 0 and 9223372036854775807.

 qemu-img create -f qcow2 -o size=-1024 TEST_DIR/t.qcow2
-qemu-img: TEST_DIR/t.qcow2: Parameter 'size' expects a non-negative number below 2^64
-Optional suffix k, M, G, T, P or E means kilo-, mega-, giga-, tera-, peta-
-and exabytes, respectively.
+qemu-img: TEST_DIR/t.qcow2: Value '-1024' is out of range for parameter 'size'

 qemu-img create -f qcow2 TEST_DIR/t.qcow2 -- -1k
 qemu-img: Invalid image size specified. You may use k, M, G, T, P or E suffixes for
diff --git i/tests/qemu-iotests/178.out.qcow2 w/tests/qemu-iotests/178.out.qcow2
index 0d51fe401ec..fe193fd5f4f 100644
--- i/tests/qemu-iotests/178.out.qcow2
+++ w/tests/qemu-iotests/178.out.qcow2
@@ -13,8 +13,7 @@ qemu-img: Invalid option list: ,
 qemu-img: Invalid parameter 'snapshot.foo'
 qemu-img: Failed in parsing snapshot param 'snapshot.foo=bar'
 qemu-img: --output must be used with human or json as argument.
-qemu-img: Invalid image size specified. You may use k, M, G, T, P or E suffixes for
-qemu-img: kilobytes, megabytes, gigabytes, terabytes, petabytes and exabytes.
+qemu-img: Invalid image size specified. Must be between 0 and 9223372036854775807.
 qemu-img: Unknown file format 'foo'

 == Size calculation for a new file (human) ==
diff --git i/tests/qemu-iotests/178.out.raw w/tests/qemu-iotests/178.out.raw
index 116241ddef2..445e460fad9 100644
--- i/tests/qemu-iotests/178.out.raw
+++ w/tests/qemu-iotests/178.out.raw
@@ -13,8 +13,7 @@ qemu-img: Invalid option list: ,
 qemu-img: Invalid parameter 'snapshot.foo'
 qemu-img: Failed in parsing snapshot param 'snapshot.foo=bar'
 qemu-img: --output must be used with human or json as argument.
-qemu-img: Invalid image size specified. You may use k, M, G, T, P or E suffixes for
-qemu-img: kilobytes, megabytes, gigabytes, terabytes, petabytes and exabytes.
+qemu-img: Invalid image size specified. Must be between 0 and 9223372036854775807.
 qemu-img: Unknown file format 'foo'

 == Size calculation for a new file (human) ==


-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org



^ permalink raw reply related	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 05/19] cutils: Fix wraparound parsing in qemu_strtoui
  2023-05-12  2:10 ` [PATCH v2 05/19] cutils: Fix wraparound parsing in qemu_strtoui Eric Blake
@ 2023-05-18 13:34   ` Eric Blake
  2023-05-19 14:42   ` Hanna Czenczek
  1 sibling, 0 replies; 48+ messages in thread
From: Eric Blake @ 2023-05-18 13:34 UTC (permalink / raw)
  To: qemu-devel, qemu-stable; +Cc: hreitz, armbru, richard.henderson

On Thu, May 11, 2023 at 09:10:19PM -0500, Eric Blake wrote:
> 
> While we were matching 32-bit strtol in qemu_strtoi, our use of a
> 64-bit parse was leaking through for some inaccurate answers in
> qemu_strtoui in comparison to a 32-bit strtoul.  Fix those, and update
> the testsuite now that our bounds checks are correct.
> 
> Our int wrappers would be a lot easier to write if libc had a
> guaranteed 32-bit parser even on platforms with 64-bit long.
> 
> Fixes: 473a2a331e ("cutils: add qemu_strtoi & qemu_strtoui parsers for int/unsigned int types", v2.12.0)
> Signed-off-by: Eric Blake <eblake@redhat.com>
> ---
>  tests/unit/test-cutils.c | 11 +++++------
>  util/cutils.c            | 14 ++++++++++----
>  2 files changed, 15 insertions(+), 10 deletions(-)

cc'ing qemu-stable as this is a bug fix, but given its age, it's not a
recent regression and therefore probably not essential for backport

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org



^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 18/19] cutils: Improve qemu_strtod* error paths
  2023-05-12  2:10 ` [PATCH v2 18/19] cutils: Improve qemu_strtod* error paths Eric Blake
@ 2023-05-18 13:47   ` Eric Blake
  0 siblings, 0 replies; 48+ messages in thread
From: Eric Blake @ 2023-05-18 13:47 UTC (permalink / raw)
  To: qemu-devel, qemu-stable; +Cc: hreitz, armbru, richard.henderson

On Thu, May 11, 2023 at 09:10:32PM -0500, Eric Blake wrote:
> 
> Previous patches changed all integral qemu_strto*() error paths to
> guarantee that *value is never left uninitialized.  Do likewise for
> qemu_strtod.  Also, tighten qemu_strtod_finite() to never return a
> non-finite value (prior to this patch, we were rejecting "inf" with
> -EINVAL and unspecified result 0.0, but failing "9e999" with -ERANGE
> and HUGE_VAL - which is infinite on IEEE machines - despite our
> function claiming to recognize only finite values).
> 
> Auditing callers, we have no external callers of qemu_strtod, and
> among the callers of qemu_strtod_finite:
> 
> - qapi/qobject-input-visitor.c:qobject_input_type_number_keyval() and
>   qapi/string-input-visitor.c:parse_type_number() which reject all
>   errors (does not matter what we store)
> 
> - utils/cutils.c:do_strtosz() incorrectly assumes that *endptr points
>   to '.' on all failures (that is, it is not distinguishing between
>   EINVAL and ERANGE; and therefore still does the WRONG THING for
>   "9.9e999".  The change here does not entirely fix that (a later
>   patch will tackle this more systematically), but at least the value
>   of endptr is now less likely to be out of bounds on overflow
> 
> - our testsuite, which we can update to match what we document
> 
> Signed-off-by: Eric Blake <eblake@redhat.com>
> Reviewed-by: Hanna Czenczek <hreitz@redhat.com>
> 
> ---

cc'ing qemu-stable for comments, since this patch fixes a sanitizer
issue flagged in https://gitlab.com/qemu-project/qemu/-/issues/1629.
While we decided the read-out-of-bounds in qemu_strtosz() is probably
not CVE-worthy (if you have control over the command line or QMP, you
can probably do much worse than cause a size parser to segfault), it
does raise the question of whether this patch is useful for
qemu-stable.  What's more, taking this patch in isolation without all
the prerequisite patches is probably sufficient to prevent the
read-out-of-bounds, but still leaves qemu_strtosz() with some odd
corner cases; while taking the whole series is a much bigger
committment but easier to analyze as a unit given that a lot of thte
series is testsuite unit test additions.  But either way, we'd need
reviews on this series if anyone thinks it warrants backports to
stable releases.

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org



^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 04/19] test-cutils: Test more integer corner cases
  2023-05-12  2:10 ` [PATCH v2 04/19] test-cutils: Test more integer corner cases Eric Blake
@ 2023-05-19 14:27   ` Hanna Czenczek
  2023-05-19 15:17     ` Eric Blake
  0 siblings, 1 reply; 48+ messages in thread
From: Hanna Czenczek @ 2023-05-19 14:27 UTC (permalink / raw)
  To: Eric Blake, qemu-devel; +Cc: armbru, richard.henderson

On 12.05.23 04:10, Eric Blake wrote:
> We have quite a few undertested and underdocumented integer parsing
> corner cases.  To ensure that any changes we make in the code are
> intentional rather than accidental semantic changes, it is time to add
> more unit tests of existing behavior.
>
> In particular, this demonstrates that parse_uint() and qemu_strtou64()
> behave differently.  For "-0", it's hard to argue why parse_uint needs
> to reject it (it's not a negative integer), but the documentation sort
> of mentions it; but it is intentional that all other negative values
> are treated as ERANGE with value 0 (compared to qemu_strtou64()
> treating "-2" as success and UINT64_MAX-1, for example).
>
> Also, when mixing overflow/underflow with a check for no trailing
> junk, parse_uint_full favors ERANGE over EINVAL, while qemu_strto[iu]*
> favor EINVAL.  This behavior is outside the C standard, so we can pick
> whatever we want, but it would be nice to be consistent.
>
> Note that C requires that "9223372036854775808" fail strtoll() with
> ERANGE/INT64_MAX, but "-9223372036854775808" pass with INT64_MIN; we
> weren't testing this.  For strtol(), the behavior depends on whether
> long is 32- or 64-bits (the cutoff point either being the same as
> strtoll() or at "-2147483648").  Meanwhile, C is clear that
> "-18446744073709551615" pass stroull() (but not strtoll) with value 1,
> even though we want it to fail parse_uint().  And although
> qemu_strtoui() has no C counterpart, it makes more sense if we design
> it like 32-bit strtoul() (that is, where "-4294967296" be an alternate
> acceptable spelling for "1".  We aren't there yet, so some of the
> tests added in this patch have FIXME comments.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
> ---
>   tests/unit/test-cutils.c | 799 ++++++++++++++++++++++++++++++++++++---
>   1 file changed, 738 insertions(+), 61 deletions(-)
>
> diff --git a/tests/unit/test-cutils.c b/tests/unit/test-cutils.c
> index 1eeaf21ae22..89c10f5307a 100644
> --- a/tests/unit/test-cutils.c
> +++ b/tests/unit/test-cutils.c

[...]

> @@ -717,34 +890,75 @@ static void test_qemu_strtoui_max(void)
>
>   static void test_qemu_strtoui_overflow(void)
>   {
> -    char *str = g_strdup_printf("%lld", (long long)UINT_MAX + 1ll);
> -    char f = 'X';
> -    const char *endptr = &f;
> -    unsigned int res = 999;
> +    const char *str;
> +    const char *endptr;
> +    unsigned int res;
>       int err;
>
> +    str = "4294967296"; /* UINT_MAX + 1ll */
> +    endptr = "somewhere";
> +    res = 999;
>       err = qemu_strtoui(str, &endptr, 0, &res);
> +    g_assert_cmpint(err, ==, -ERANGE);
> +    g_assert_cmpint(res, ==, UINT_MAX);

Why cmpint and not cmpuint here?  (I see you’re using cmpint instead of 
cmpuint in many strtou* test functions below, too.)

[...]

> @@ -1325,31 +1697,67 @@ static void test_qemu_strtoul_max(void)

[...]

>   static void test_qemu_strtoul_underflow(void)
>   {
> -    const char *str = "-99999999999999999999999999999999999999999999";
> -    char f = 'X';
> -    const char *endptr = &f;
> -    unsigned long res = 999;
> +    const char *str;
> +    const char *endptr;
> +    unsigned long res;
>       int err;
>
> +    /* 1 less than -ULONG_MAX */
> +    str = ULONG_MAX == UINT_MAX ? "-4294967297" : "-18446744073709551617";

Technically these are 2 less than -ULONG_MAX, not 1 less.

Hanna



^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 05/19] cutils: Fix wraparound parsing in qemu_strtoui
  2023-05-12  2:10 ` [PATCH v2 05/19] cutils: Fix wraparound parsing in qemu_strtoui Eric Blake
  2023-05-18 13:34   ` Eric Blake
@ 2023-05-19 14:42   ` Hanna Czenczek
  2023-05-19 16:31     ` Eric Blake
  1 sibling, 1 reply; 48+ messages in thread
From: Hanna Czenczek @ 2023-05-19 14:42 UTC (permalink / raw)
  To: Eric Blake, qemu-devel; +Cc: armbru, richard.henderson

On 12.05.23 04:10, Eric Blake wrote:
> While we were matching 32-bit strtol in qemu_strtoi, our use of a
> 64-bit parse was leaking through for some inaccurate answers in
> qemu_strtoui in comparison to a 32-bit strtoul.  Fix those, and update
> the testsuite now that our bounds checks are correct.
>
> Our int wrappers would be a lot easier to write if libc had a
> guaranteed 32-bit parser even on platforms with 64-bit long.
>
> Fixes: 473a2a331e ("cutils: add qemu_strtoi & qemu_strtoui parsers for int/unsigned int types", v2.12.0)
> Signed-off-by: Eric Blake <eblake@redhat.com>
> ---
>   tests/unit/test-cutils.c | 11 +++++------
>   util/cutils.c            | 14 ++++++++++----
>   2 files changed, 15 insertions(+), 10 deletions(-)

Reviewed-by: Hanna Czenczek <hreitz@redhat.com>

> diff --git a/util/cutils.c b/util/cutils.c
> index 5887e744140..997ddcd09e5 100644
> --- a/util/cutils.c
> +++ b/util/cutils.c
> @@ -466,10 +466,16 @@ int qemu_strtoui(const char *nptr, const char **endptr, int base,
>       if (errno == ERANGE) {
>           *result = -1;
>       } else {
> -        if (lresult > UINT_MAX) {
> -            *result = UINT_MAX;
> -            errno = ERANGE;
> -        } else if (lresult < INT_MIN) {
> +        /*
> +         * Note that platforms with 32-bit strtoul accept input in the
> +         * range [-4294967295, 4294967295]; but we used 64-bit
> +         * strtoull which wraps -18446744073709551615 to 1.  Reject
> +         * positive values that contain '-', and wrap all valid
> +         * negative values.
> +         */
> +        if (lresult > UINT_MAX ||
> +            lresult < -(long long)UINT_MAX ||
> +            (lresult > 0 && memchr(nptr, '-', ep - nptr))) {
>               *result = UINT_MAX;
>               errno = ERANGE;
>           } else {

Just a question whether I guessed correctly, because there’s no comment 
on the matter: We store the (supposedly unsigned) result of strtoull() 
in a signed long long because e.g. -1 is mapped to ULLONG_MAX, so the 
valid unsigned ranges would be [0, UINT_MAX] \cup [ULLONG_MAX - UINT_MAX 
+ 1, ULLONG_MAX], which is more cumbersome to check than the [-UINT_MAX, 
UINT_MAX] range?  (And we’d need to exclude strings with - in them if 
ullresult > UINT_MAX rather than > 0, probably)

Hanna



^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 06/19] cutils: Document differences between parse_uint and qemu_strtou64
  2023-05-12  2:10 ` [PATCH v2 06/19] cutils: Document differences between parse_uint and qemu_strtou64 Eric Blake
@ 2023-05-19 14:44   ` Hanna Czenczek
  0 siblings, 0 replies; 48+ messages in thread
From: Hanna Czenczek @ 2023-05-19 14:44 UTC (permalink / raw)
  To: Eric Blake, qemu-devel; +Cc: armbru, richard.henderson

On 12.05.23 04:10, Eric Blake wrote:
> These two functions are subtly different, and not just because of
> swapped parameter order.  It took me adding better unit tests to
> figure out why.  Document the differences to make it more obvious to
> developers trying to pick which one to use, as well as to aid in
> upcoming semantic changes.
>
> While touching the documentation, adjust a mis-statement: parse_uint
> does not return -EINVAL on invalid base, but assert()s, like all the
> other qemu_strto* functions that take a base argument.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
> ---
>   util/cutils.c | 20 ++++++++++++--------
>   1 file changed, 12 insertions(+), 8 deletions(-)

Reviewed-by: Hanna Czenczek <hreitz@redhat.com>



^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 07/19] cutils: Adjust signature of parse_uint[_full]
  2023-05-12  2:10 ` [PATCH v2 07/19] cutils: Adjust signature of parse_uint[_full] Eric Blake
  2023-05-12 16:25   ` Eric Blake
@ 2023-05-19 14:51   ` Hanna Czenczek
  1 sibling, 0 replies; 48+ messages in thread
From: Hanna Czenczek @ 2023-05-19 14:51 UTC (permalink / raw)
  To: Eric Blake, qemu-devel
  Cc: armbru, richard.henderson, Gerd Hoffmann, Marc-André Lureau,
	Kevin Wolf, Peter Lieven, Michael Roth, Daniel P. Berrangé,
	open list:GLUSTER, open list:GLUSTER

On 12.05.23 04:10, Eric Blake wrote:
> It's already confusing that we have two very similar functions for
> wrapping the parse of a 64-bit unsigned value, differing mainly on
> whether they permit leading '-'.  Adjust the signature of parse_uint()
> and parse_uint_full() to be like all of qemu_strto*(): put the result
> parameter last, use the same types (uint64_t is not always the same as
> unsigned long long, and mark endptr const (only latter affects the
> rare caller of parse_uint).  Adjust all callers in the tree.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
> ---
>   include/qemu/cutils.h         |   5 +-
>   audio/audio_legacy.c          |   4 +-
>   block/gluster.c               |   4 +-
>   block/nfs.c                   |   4 +-
>   blockdev.c                    |   4 +-
>   contrib/ivshmem-server/main.c |   4 +-
>   qapi/opts-visitor.c           |  10 +--
>   tests/unit/test-cutils.c      | 113 +++++++++++++++-------------------
>   ui/vnc.c                      |   4 +-
>   util/cutils.c                 |  13 ++--
>   util/guest-random.c           |   4 +-
>   util/qemu-sockets.c           |  10 +--
>   12 files changed, 82 insertions(+), 97 deletions(-)

[...]

> diff --git a/tests/unit/test-cutils.c b/tests/unit/test-cutils.c
> index 08989d1d3ac..0c7d07b3297 100644
> --- a/tests/unit/test-cutils.c
> +++ b/tests/unit/test-cutils.c

[...]

> @@ -186,32 +176,31 @@ static void test_parse_uint_max(void)
>
>   static void test_parse_uint_overflow(void)
>   {
> -    unsigned long long i;
> -    char f = 'X';
> -    char *endptr;
> +    uint64_t i;
> +    const char *endptr = "somewhere";

The initialization here is technically not necessary because it’s reset 
above the parse_uint() call below.

Anyway:

Reviewed-by: Hanna Czenczek <hreitz@redhat.com>

>       const char *str;
>       int r;
>
>       i = 999;
> -    endptr = &f;
> +    endptr = "somewhere";
>       str = "99999999999999999999999999999999999999";
> -    r = parse_uint(str, &i, &endptr, 0);
> +    r = parse_uint(str, &endptr, 0, &i);
>       g_assert_cmpint(r, ==, -ERANGE);
>       g_assert_cmpuint(i, ==, ULLONG_MAX);
>       g_assert_true(endptr == str + strlen(str));



^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 08/19] cutils: Allow NULL endptr in parse_uint()
  2023-05-12  2:10 ` [PATCH v2 08/19] cutils: Allow NULL endptr in parse_uint() Eric Blake
  2023-05-12 16:44   ` Eric Blake
@ 2023-05-19 14:54   ` Hanna Czenczek
  1 sibling, 0 replies; 48+ messages in thread
From: Hanna Czenczek @ 2023-05-19 14:54 UTC (permalink / raw)
  To: Eric Blake, qemu-devel; +Cc: armbru, richard.henderson

On 12.05.23 04:10, Eric Blake wrote:
> All the qemu_strto*() functions permit a NULL endptr, just like their
> libc counterparts, leaving parse_uint() as the oddball that caused
> SEGFAULT on NULL and required the user to call parse_uint_full()
> instead.  Relax things for consistency, even though the testsuite is
> the only impacted caller.  Add one more unit test to ensure even
> parse_uint_full(NULL, 0, &value) works.  This also fixes our code to
> uniformly favor EINVAL over ERANGE when both apply.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
> ---
>   tests/unit/test-cutils.c | 18 ++++++++++++++++--
>   util/cutils.c            | 34 ++++++++++++----------------------
>   2 files changed, 28 insertions(+), 24 deletions(-)

Reviewed-by: Hanna Czenczek <hreitz@redhat.com>



^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 09/19] test-cutils: Add coverage of qemu_strtod
  2023-05-12  2:10 ` [PATCH v2 09/19] test-cutils: Add coverage of qemu_strtod Eric Blake
@ 2023-05-19 15:05   ` Hanna Czenczek
  2023-05-19 17:52     ` Eric Blake
  0 siblings, 1 reply; 48+ messages in thread
From: Hanna Czenczek @ 2023-05-19 15:05 UTC (permalink / raw)
  To: Eric Blake, qemu-devel; +Cc: armbru, richard.henderson

On 12.05.23 04:10, Eric Blake wrote:
> It's hard to tweak code for consistency if I can't prove what will or
> won't break from those tweaks.  Time to add unit tests for
> qemu_strtod() and qemu_strtod_finite().
>
> Among other things, I wrote a check whether we have C99 semantics for
> strtod("0x1") (which MUST parse hex numbers) rather than C89 (which
> must stop parsing at 'x').  These days, I suspect that is okay; but if
> it fails CI checks, knowing the difference will help us decide what we
> want to do about it.  Note that C2x, while not final at the time of
> this patch, has been considering whether to make strtol("0b1") parse
> as 1 with no slop instead of the C17 parse of 0 with slop "b1"; that
> decision may also bleed over to strtod().  But for now, I didn't think
> it worth adding unit tests on that front (to strtol or strtod) as
> things may still change.
>
> Likewise, there are plenty more corner cases of strtod proper that I
> don't explicitly test here, but there are enough unit tests added here
> that it covers all the branches reached in our wrappers.  In
> particular, it demonstrates the difference on when *value is left
> uninitialized, which an upcoming patch will normalize.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
>
> ---
>
> v2: Added g_assert_false(signbit(res)) anywhere I used
> g_assert_cmpfloat(res,==,0.0); add a test for strtod() hex parsing and
> handling of junk after ERANGE, which is major enough that I dropped
> R-b
> ---
>   tests/unit/test-cutils.c | 510 +++++++++++++++++++++++++++++++++++++++
>   1 file changed, 510 insertions(+)
>
> diff --git a/tests/unit/test-cutils.c b/tests/unit/test-cutils.c
> index d3076c3fec1..1763839a157 100644
> --- a/tests/unit/test-cutils.c
> +++ b/tests/unit/test-cutils.c

[...]

> +static void test_qemu_strtod_erange_junk(void)
> +{
> +    const char *str;
> +    const char *endptr;
> +    int err;
> +    double res;
> +
> +    /* EINVAL has priority over ERANGE */

By being placed here, this comment confused me a bit, because the first 
case does return ERANGE.  So I’d prefer it above the second case, where 
we actually expect EINVAL, but understand that’s a personal preference.  
(Same for the _finite_ variant)

Reviewed-by: Hanna Czenczek <hreitz@redhat.com>

> +    str = "1e-999junk";
> +    endptr = "somewhere";
> +    res = 999;
> +    err = qemu_strtod(str, &endptr, &res);
> +    g_assert_cmpint(err, ==, -ERANGE);
> +    g_assert_cmpfloat(res, <=, DBL_MIN);
> +    g_assert_cmpfloat(res, >=, 0.0);
> +    g_assert_false(signbit(res));
> +    g_assert_true(endptr == str + 6);
> +
> +    endptr = "somewhere";
> +    res = 999;
> +    err = qemu_strtod(str, NULL, &res);
> +    g_assert_cmpint(err, ==, -EINVAL);
> +    g_assert_cmpfloat(res, ==, 0.0);
> +    g_assert_false(signbit(res));
> +}



^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 11/19] test-cutils: Refactor qemu_strtosz tests for less boilerplate
  2023-05-12  2:10 ` [PATCH v2 11/19] test-cutils: Refactor qemu_strtosz tests for less boilerplate Eric Blake
@ 2023-05-19 15:13   ` Hanna Czenczek
  2023-05-19 17:54     ` Eric Blake
  0 siblings, 1 reply; 48+ messages in thread
From: Hanna Czenczek @ 2023-05-19 15:13 UTC (permalink / raw)
  To: Eric Blake, qemu-devel; +Cc: armbru, richard.henderson

On 12.05.23 04:10, Eric Blake wrote:
> No need to copy-and-paste lots of boilerplate per string tested, when
> we can consolidate that behind helper functions.  Plus, this adds a
> bit more coverage (we now test all strings both with and without
> endptr, whereas before some tests skipped the NULL endptr case), which
> exposed a SEGFAULT on qemu_strtosz(NULL, NULL, &val) that will be
> parsed in an upcoming patch.
>
> Note that duplicating boilerplate has one advantage lost here - a
> failed test tells you which line number failed; but a helper function
> does not show the call stack that reached the failure.  Since we call
> the helper more than once within many of the "unit tests", even the
> unit test name doesn't point out which call is failing.  But that only
> matters when tests fail (they normally pass); at which point I'm
> debugging the failures under gdb anyways, so I'm not too worried about
> it.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
> ---
>   tests/unit/test-cutils.c | 503 ++++++++-------------------------------
>   1 file changed, 100 insertions(+), 403 deletions(-)

Reviewed-by: Hanna Czenczek <hreitz@redhat.com>



^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 12/19] cutils: Allow NULL str in qemu_strtosz
  2023-05-12  2:10 ` [PATCH v2 12/19] cutils: Allow NULL str in qemu_strtosz Eric Blake
  2023-05-12  3:25   ` Philippe Mathieu-Daudé
@ 2023-05-19 15:15   ` Hanna Czenczek
  1 sibling, 0 replies; 48+ messages in thread
From: Hanna Czenczek @ 2023-05-19 15:15 UTC (permalink / raw)
  To: Eric Blake, qemu-devel; +Cc: armbru, richard.henderson

On 12.05.23 04:10, Eric Blake wrote:
> All the other qemu_strto* and parse_uint allow a NULL str.  Having
> qemu_strtosz crash on qemu_strtosz(NULL, NULL, &value) is an easy fix
> that adds some consistency between our string parsers.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
> ---
>   tests/unit/test-cutils.c | 3 +++
>   util/cutils.c            | 2 +-
>   2 files changed, 4 insertions(+), 1 deletion(-)

Reviewed-by: Hanna Czenczek <hreitz@redhat.com>



^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 04/19] test-cutils: Test more integer corner cases
  2023-05-19 14:27   ` Hanna Czenczek
@ 2023-05-19 15:17     ` Eric Blake
  0 siblings, 0 replies; 48+ messages in thread
From: Eric Blake @ 2023-05-19 15:17 UTC (permalink / raw)
  To: Hanna Czenczek; +Cc: qemu-devel, armbru, richard.henderson

On Fri, May 19, 2023 at 04:27:10PM +0200, Hanna Czenczek wrote:
> On 12.05.23 04:10, Eric Blake wrote:
> > We have quite a few undertested and underdocumented integer parsing
> > corner cases.  To ensure that any changes we make in the code are
> > intentional rather than accidental semantic changes, it is time to add
> > more unit tests of existing behavior.
> > 
> > In particular, this demonstrates that parse_uint() and qemu_strtou64()
> > behave differently.  For "-0", it's hard to argue why parse_uint needs
> > to reject it (it's not a negative integer), but the documentation sort
> > of mentions it; but it is intentional that all other negative values
> > are treated as ERANGE with value 0 (compared to qemu_strtou64()
> > treating "-2" as success and UINT64_MAX-1, for example).
> > 
> > Also, when mixing overflow/underflow with a check for no trailing
> > junk, parse_uint_full favors ERANGE over EINVAL, while qemu_strto[iu]*
> > favor EINVAL.  This behavior is outside the C standard, so we can pick
> > whatever we want, but it would be nice to be consistent.
> > 
> > Note that C requires that "9223372036854775808" fail strtoll() with
> > ERANGE/INT64_MAX, but "-9223372036854775808" pass with INT64_MIN; we
> > weren't testing this.  For strtol(), the behavior depends on whether
> > long is 32- or 64-bits (the cutoff point either being the same as
> > strtoll() or at "-2147483648").  Meanwhile, C is clear that
> > "-18446744073709551615" pass stroull() (but not strtoll) with value 1,
> > even though we want it to fail parse_uint().  And although
> > qemu_strtoui() has no C counterpart, it makes more sense if we design
> > it like 32-bit strtoul() (that is, where "-4294967296" be an alternate
> > acceptable spelling for "1".  We aren't there yet, so some of the
> > tests added in this patch have FIXME comments.
> > 
> > Signed-off-by: Eric Blake <eblake@redhat.com>
> > ---
> >   tests/unit/test-cutils.c | 799 ++++++++++++++++++++++++++++++++++++---
> >   1 file changed, 738 insertions(+), 61 deletions(-)
> > 
> > diff --git a/tests/unit/test-cutils.c b/tests/unit/test-cutils.c
> > index 1eeaf21ae22..89c10f5307a 100644
> > --- a/tests/unit/test-cutils.c
> > +++ b/tests/unit/test-cutils.c
> 
> [...]
> 
> > @@ -717,34 +890,75 @@ static void test_qemu_strtoui_max(void)
> > 
> >   static void test_qemu_strtoui_overflow(void)
> >   {
> > -    char *str = g_strdup_printf("%lld", (long long)UINT_MAX + 1ll);
> > -    char f = 'X';
> > -    const char *endptr = &f;
> > -    unsigned int res = 999;
> > +    const char *str;
> > +    const char *endptr;
> > +    unsigned int res;
> >       int err;
> > 
> > +    str = "4294967296"; /* UINT_MAX + 1ll */
> > +    endptr = "somewhere";
> > +    res = 999;
> >       err = qemu_strtoui(str, &endptr, 0, &res);
> > +    g_assert_cmpint(err, ==, -ERANGE);
> > +    g_assert_cmpint(res, ==, UINT_MAX);
> 
> Why cmpint and not cmpuint here?  (I see you’re using cmpint instead of
> cmpuint in many strtou* test functions below, too.)

Probably a combination of copy-paste vs rebase patch re-ordering.
Yes, all test_*strtoui* tests should be using
g_assert_cmpuint(res...).  Will fix.

> 
> [...]
> 
> > @@ -1325,31 +1697,67 @@ static void test_qemu_strtoul_max(void)
> 
> [...]
> 
> >   static void test_qemu_strtoul_underflow(void)
> >   {
> > -    const char *str = "-99999999999999999999999999999999999999999999";
> > -    char f = 'X';
> > -    const char *endptr = &f;
> > -    unsigned long res = 999;
> > +    const char *str;
> > +    const char *endptr;
> > +    unsigned long res;
> >       int err;
> > 
> > +    /* 1 less than -ULONG_MAX */
> > +    str = ULONG_MAX == UINT_MAX ? "-4294967297" : "-18446744073709551617";
> 
> Technically these are 2 less than -ULONG_MAX, not 1 less.

Indeed.  Both constants should end in 6, not 7.  Will fix.

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org



^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 14/19] test-cutils: Add more coverage to qemu_strtosz11;rgb:1e1e/1e1e/1e1e
  2023-05-12  2:10 ` [PATCH v2 14/19] test-cutils: Add more coverage to qemu_strtosz11; rgb:1e1e/1e1e/1e1e Eric Blake
@ 2023-05-19 15:26   ` Hanna Czenczek
  2023-05-19 18:02     ` Eric Blake
  0 siblings, 1 reply; 48+ messages in thread
From: Hanna Czenczek @ 2023-05-19 15:26 UTC (permalink / raw)
  To: Eric Blake, qemu-devel; +Cc: armbru, richard.henderson

On 12.05.23 04:10, Eric Blake wrote:
> Add some more strings that the user might send our way.  In
> particular, some of these additions include FIXME comments showing
> where our parser doesn't quite behave the way we want.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
>
> ---
>
> v2: even more tests added, pad a string to avoid out-of-bounds
> randomness [Hanna]
> ---
>   tests/unit/test-cutils.c | 147 +++++++++++++++++++++++++++++++++++----
>   1 file changed, 135 insertions(+), 12 deletions(-)

The subject line appears as if it contained an ANSI escape sequence.

> diff --git a/tests/unit/test-cutils.c b/tests/unit/test-cutils.c
> index 1936c7b5795..7800caf9b0e 100644
> --- a/tests/unit/test-cutils.c
> +++ b/tests/unit/test-cutils.c
> @@ -3162,7 +3162,12 @@ static void do_strtosz_full(const char *str, qemu_strtosz_fn fn,
>       ret = fn(str, &endptr, &val);
>       g_assert_cmpint(ret, ==, exp_ptr_ret);
>       g_assert_cmpuint(val, ==, exp_ptr_val);
> -    g_assert_true(endptr == str + exp_ptr_offset);
> +    if (str) {
> +        g_assert_true(endptr == str + exp_ptr_offset);
> +    } else {
> +        g_assert_cmpint(exp_ptr_offset, ==, 0);
> +        g_assert_null(endptr);
> +    }

This patch adds no new cases that call do_strtosz*() with a NULL str – 
did you intent for this to go into patch 12?

Regardless (with the subject fixed, though):

Reviewed-by: Hanna Czenczek <hreitz@redhat.com>



^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 15/19] cutils: Set value in all qemu_strtosz* error paths
  2023-05-12  2:10 ` [PATCH v2 15/19] cutils: Set value in all qemu_strtosz* error paths Eric Blake
@ 2023-05-19 15:29   ` Hanna Czenczek
  0 siblings, 0 replies; 48+ messages in thread
From: Hanna Czenczek @ 2023-05-19 15:29 UTC (permalink / raw)
  To: Eric Blake, qemu-devel; +Cc: armbru, richard.henderson

On 12.05.23 04:10, Eric Blake wrote:
> Making callers determine whether or not *value was populated on error
> is not nice for usability.  Pre-patch, we have unit tests that check
> that *result is left unchanged on most EINVAL errors and set to 0 on
> many ERANGE errors.  This is subtly different from libc strtoumax()
> behavior which returns UINT64_MAX on ERANGE errors, as well as
> different from our parse_uint() which slams to 0 on EINVAL on the
> grounds that we want our functions to be harder to mis-use than
> strtoumax().
>
> Let's audit callers:
>
> - hw/core/numa.c:parse_numa() fixed in the previous patch to check for
>    errors
>
> - migration/migration-hmp-cmds.c:hmp_migrate_set_parameter(),
>    monitor/hmp.c:monitor_parse_arguments(),
>    qapi/opts-visitor.c:opts_type_size(),
>    qapi/qobject-input-visitor.c:qobject_input_type_size_keyval(),
>    qemu-img.c:cvtnum_full(), qemu-io-cmds.c:cvtnum(),
>    target/i386/cpu.c:x86_cpu_parse_featurestr(), and
>    util/qemu-option.c:parse_option_size() appear to reject all failures
>    (although some with distinct messages for ERANGE as opposed to
>    EINVAL), so it doesn't matter what is in the value parameter on
>    error.
>
> - All remaining callers are in the testsuite, where we can tweak our
>    expectations to match our new desired behavior.
>
> Advancing to the end of the string parsed on overflow (ERANGE), while
> still returning 0, makes sense (UINT64_MAX as a size is unlikely to be
> useful); likewise, our size parsing code is complex enough that it's
> easier to always return 0 when endptr is NULL but trailing garbage was
> found, rather than trying to return the value of the prefix actually
> parsed (no current caller cared about the value of the prefix).
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
>
> ---
>
> v2: cutils.c unchanged, but rebasing test suite is significant enough
> that I doropped Hanna's R-b
> ---
>   tests/unit/test-cutils.c | 106 +++++++++++++++++++--------------------
>   util/cutils.c            |  17 +++++--
>   2 files changed, 63 insertions(+), 60 deletions(-)

Reviewed-by: Hanna Czenczek <hreitz@redhat.com>



^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 17/19] cutils: Use parse_uint in qemu_strtosz for negative rejection
  2023-05-12 19:34   ` Eric Blake
@ 2023-05-19 15:32     ` Hanna Czenczek
  0 siblings, 0 replies; 48+ messages in thread
From: Hanna Czenczek @ 2023-05-19 15:32 UTC (permalink / raw)
  To: Eric Blake, qemu-devel; +Cc: armbru, richard.henderson

On 12.05.23 21:34, Eric Blake wrote:
> On Thu, May 11, 2023 at 09:10:31PM -0500, Eric Blake wrote:
>> Rather than open-coding two different ways to check for an unwanted
>> negative sign, reuse the same code in both functions.  That way, if we
>> decide down the road to accept "-0" instead of rejecting it, we have
>> fewer places to change.  Also, it means we now get ERANGE instead of
>> EINVAL for negative values in qemu_strtosz, which is reasonable for
>> what it represents.
>>
>> Signed-off-by: Eric Blake <eblake@redhat.com>
>> ---
>>   tests/unit/test-cutils.c | 7 +++----
>>   util/cutils.c            | 8 ++------
>>   2 files changed, 5 insertions(+), 10 deletions(-)
> Returning ERANGE instead of EINVAL changes the expected output for
> iotests 49 and 178; this needs to be squashed in:
>
> diff --git i/tests/qemu-iotests/049.out w/tests/qemu-iotests/049.out
> index 8719c91b483..34e1b452e6e 100644
> --- i/tests/qemu-iotests/049.out
> +++ w/tests/qemu-iotests/049.out
> @@ -92,13 +92,10 @@ Formatting 'TEST_DIR/t.qcow2', fmt=qcow2 cluster_size=65536 extended_l2=off comp
>   == 3. Invalid sizes ==
>
>   qemu-img create -f qcow2 TEST_DIR/t.qcow2 -- -1024
> -qemu-img: Invalid image size specified. You may use k, M, G, T, P or E suffixes for
> -qemu-img: kilobytes, megabytes, gigabytes, terabytes, petabytes and exabytes.
> +qemu-img: Invalid image size specified. Must be between 0 and 9223372036854775807.
>
>   qemu-img create -f qcow2 -o size=-1024 TEST_DIR/t.qcow2
> -qemu-img: TEST_DIR/t.qcow2: Parameter 'size' expects a non-negative number below 2^64
> -Optional suffix k, M, G, T, P or E means kilo-, mega-, giga-, tera-, peta-
> -and exabytes, respectively.
> +qemu-img: TEST_DIR/t.qcow2: Value '-1024' is out of range for parameter 'size'
>
>   qemu-img create -f qcow2 TEST_DIR/t.qcow2 -- -1k
>   qemu-img: Invalid image size specified. You may use k, M, G, T, P or E suffixes for
> diff --git i/tests/qemu-iotests/178.out.qcow2 w/tests/qemu-iotests/178.out.qcow2
> index 0d51fe401ec..fe193fd5f4f 100644
> --- i/tests/qemu-iotests/178.out.qcow2
> +++ w/tests/qemu-iotests/178.out.qcow2
> @@ -13,8 +13,7 @@ qemu-img: Invalid option list: ,
>   qemu-img: Invalid parameter 'snapshot.foo'
>   qemu-img: Failed in parsing snapshot param 'snapshot.foo=bar'
>   qemu-img: --output must be used with human or json as argument.
> -qemu-img: Invalid image size specified. You may use k, M, G, T, P or E suffixes for
> -qemu-img: kilobytes, megabytes, gigabytes, terabytes, petabytes and exabytes.
> +qemu-img: Invalid image size specified. Must be between 0 and 9223372036854775807.
>   qemu-img: Unknown file format 'foo'
>
>   == Size calculation for a new file (human) ==
> diff --git i/tests/qemu-iotests/178.out.raw w/tests/qemu-iotests/178.out.raw
> index 116241ddef2..445e460fad9 100644
> --- i/tests/qemu-iotests/178.out.raw
> +++ w/tests/qemu-iotests/178.out.raw
> @@ -13,8 +13,7 @@ qemu-img: Invalid option list: ,
>   qemu-img: Invalid parameter 'snapshot.foo'
>   qemu-img: Failed in parsing snapshot param 'snapshot.foo=bar'
>   qemu-img: --output must be used with human or json as argument.
> -qemu-img: Invalid image size specified. You may use k, M, G, T, P or E suffixes for
> -qemu-img: kilobytes, megabytes, gigabytes, terabytes, petabytes and exabytes.
> +qemu-img: Invalid image size specified. Must be between 0 and 9223372036854775807.
>   qemu-img: Unknown file format 'foo'
>
>   == Size calculation for a new file (human) ==

With that squashed in:

Reviewed-by: Hanna Czenczek <hreitz@redhat.com>



^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 19/19] cutils: Improve qemu_strtosz handling of fractions
  2023-05-12  2:10 ` [PATCH v2 19/19] cutils: Improve qemu_strtosz handling of fractions Eric Blake
@ 2023-05-19 15:36   ` Hanna Czenczek
  0 siblings, 0 replies; 48+ messages in thread
From: Hanna Czenczek @ 2023-05-19 15:36 UTC (permalink / raw)
  To: Eric Blake, qemu-devel; +Cc: armbru, richard.henderson

On 12.05.23 04:10, Eric Blake wrote:
> We have several limitations and bugs worth fixing; they are
> inter-related enough that it is not worth splitting this patch into
> smaller pieces:
>
> * ".5k" should work to specify 512, just as "0.5k" does
> * "1.9999k" and "1." + "9"*50 + "k" should both produce the same
>    result of 2048 after rounding
> * "1." + "0"*350 + "1B" should not be treated the same as "1.0B";
>    underflow in the fraction should not be lost
> * "7.99e99" and "7.99e999" look similar, but our code was doing a
>    read-out-of-bounds on the latter because it was not expecting ERANGE
>    due to overflow. While we document that scientific notation is not
>    supported, and the previous patch actually fixed
>    qemu_strtod_finite() to no longer return ERANGE overflows, it is
>    easier to pre-filter than to try and determine after the fact if
>    strtod() consumed more than we wanted.  Note that this is a
>    low-level semantic change (when endptr is not NULL, we can now
>    successfully parse with a scale of 'E' and then report trailing
>    junk, instead of failing outright with EINVAL); but an earlier
>    commit already argued that this is not a high-level semantic change
>    since the only caller passing in a non-NULL endptr also checks that
>    the tail is whitespace-only.
>
> Fixes: https://gitlab.com/qemu-project/qemu/-/issues/1629
> Fixes: cf923b78 ("utils: Improve qemu_strtosz() to have 64 bits of precision", 6.0.0)
> Fixes: 7625a1ed ("utils: Use fixed-point arithmetic in qemu_strtosz", 6.0.0)
> Signed-off-by: Eric Blake <eblake@redhat.com>
>
> ---
>
> v2: more changes, handle negatives differently, catch fractions that
> round to 0 but don't underflow [Hanna]
> ---
>   tests/unit/test-cutils.c | 50 +++++++++-------------
>   util/cutils.c            | 89 ++++++++++++++++++++++++++++++----------
>   2 files changed, 86 insertions(+), 53 deletions(-)

Reviewed-by: Hanna Czenczek <hreitz@redhat.com>



^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 05/19] cutils: Fix wraparound parsing in qemu_strtoui
  2023-05-19 14:42   ` Hanna Czenczek
@ 2023-05-19 16:31     ` Eric Blake
  0 siblings, 0 replies; 48+ messages in thread
From: Eric Blake @ 2023-05-19 16:31 UTC (permalink / raw)
  To: Hanna Czenczek; +Cc: qemu-devel, armbru, richard.henderson

On Fri, May 19, 2023 at 04:42:11PM +0200, Hanna Czenczek wrote:
> On 12.05.23 04:10, Eric Blake wrote:
> > While we were matching 32-bit strtol in qemu_strtoi, our use of a
> > 64-bit parse was leaking through for some inaccurate answers in
> > qemu_strtoui in comparison to a 32-bit strtoul.  Fix those, and update
> > the testsuite now that our bounds checks are correct.
> > 
> > Our int wrappers would be a lot easier to write if libc had a
> > guaranteed 32-bit parser even on platforms with 64-bit long.
> > 
> > Fixes: 473a2a331e ("cutils: add qemu_strtoi & qemu_strtoui parsers for int/unsigned int types", v2.12.0)
> > Signed-off-by: Eric Blake <eblake@redhat.com>
> > ---
> >   tests/unit/test-cutils.c | 11 +++++------
> >   util/cutils.c            | 14 ++++++++++----
> >   2 files changed, 15 insertions(+), 10 deletions(-)
> 
> Reviewed-by: Hanna Czenczek <hreitz@redhat.com>
> 
> > diff --git a/util/cutils.c b/util/cutils.c
> > index 5887e744140..997ddcd09e5 100644
> > --- a/util/cutils.c
> > +++ b/util/cutils.c
> > @@ -466,10 +466,16 @@ int qemu_strtoui(const char *nptr, const char **endptr, int base,

Adding context:

long long lresult;
...
lresult = strtoull(nptr, &ep, base);

> >       if (errno == ERANGE) {
> >           *result = -1;
> >       } else {
> > -        if (lresult > UINT_MAX) {
> > -            *result = UINT_MAX;
> > -            errno = ERANGE;
> > -        } else if (lresult < INT_MIN) {
> > +        /*
> > +         * Note that platforms with 32-bit strtoul accept input in the
> > +         * range [-4294967295, 4294967295]; but we used 64-bit
> > +         * strtoull which wraps -18446744073709551615 to 1.  Reject
> > +         * positive values that contain '-', and wrap all valid
> > +         * negative values.
> > +         */
> > +        if (lresult > UINT_MAX ||
> > +            lresult < -(long long)UINT_MAX ||
> > +            (lresult > 0 && memchr(nptr, '-', ep - nptr))) {
> >               *result = UINT_MAX;
> >               errno = ERANGE;
> >           } else {
> 
> Just a question whether I guessed correctly, because there’s no comment on
> the matter: We store the (supposedly unsigned) result of strtoull() in a
> signed long long because e.g. -1 is mapped to ULLONG_MAX, so the valid
> unsigned ranges would be [0, UINT_MAX] \cup [ULLONG_MAX - UINT_MAX + 1,
> ULLONG_MAX], which is more cumbersome to check than the [-UINT_MAX,
> UINT_MAX] range?  (And we’d need to exclude strings with - in them if
> ullresult > UINT_MAX rather than > 0, probably)

Yes.  Not only will I take that as a hint that I should improve my
commit message, but you have pointed out a hole in my unit testing and
which is not fixed here; namely, strings like "-0xffffffffffff0000"
are accepted when they should be rejected.

I'm thinking the commit message should be something along the lines of
the following:

Note that strtoull takes two input ranges that overlap into a single
output range: [-0xffffffffffffffff, -1] which maps to [1,
0xffffffffffffffff], and [0, 0xffffffffffffffff] maps to itself.  The
output value alone does not tell us whether the input was in the
desired range [-0xffffffff, 0xffffffff], or in one of the other two
ranges that also map to the desired range [-0xffffffffffffffff,
-0xffffffff00000001] and [0xffffffff00000001, 0xffffffffffffffff], so
we need to do some additional filtering.  Merely checking whether
strlen(input) is longer than strlen("-4294967295") is not going to
work, because ("000000000000000000000000000") is longer but in range;
but we can check for the presence or absence of '-' in the input being
inconsistent with the resulting sign when the number is cast as a
signed long long.

v3 of this series will come soon, once you finish finding anything
else in v2 I need to fix.

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org



^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 09/19] test-cutils: Add coverage of qemu_strtod
  2023-05-19 15:05   ` Hanna Czenczek
@ 2023-05-19 17:52     ` Eric Blake
  2023-05-22 10:56       ` Hanna Czenczek
  0 siblings, 1 reply; 48+ messages in thread
From: Eric Blake @ 2023-05-19 17:52 UTC (permalink / raw)
  To: Hanna Czenczek; +Cc: qemu-devel, armbru, richard.henderson

On Fri, May 19, 2023 at 05:05:20PM +0200, Hanna Czenczek wrote:
> On 12.05.23 04:10, Eric Blake wrote:
> > It's hard to tweak code for consistency if I can't prove what will or
> > won't break from those tweaks.  Time to add unit tests for
> > qemu_strtod() and qemu_strtod_finite().
> > 
> > Among other things, I wrote a check whether we have C99 semantics for
> > strtod("0x1") (which MUST parse hex numbers) rather than C89 (which
> > must stop parsing at 'x').  These days, I suspect that is okay; but if
> > it fails CI checks, knowing the difference will help us decide what we
> > want to do about it.  Note that C2x, while not final at the time of
> > this patch, has been considering whether to make strtol("0b1") parse
> > as 1 with no slop instead of the C17 parse of 0 with slop "b1"; that
> > decision may also bleed over to strtod().  But for now, I didn't think
> > it worth adding unit tests on that front (to strtol or strtod) as
> > things may still change.
> > 
> > Likewise, there are plenty more corner cases of strtod proper that I
> > don't explicitly test here, but there are enough unit tests added here
> > that it covers all the branches reached in our wrappers.  In
> > particular, it demonstrates the difference on when *value is left
> > uninitialized, which an upcoming patch will normalize.
> > 
> > Signed-off-by: Eric Blake <eblake@redhat.com>
> > 
> > ---
> > 
> > v2: Added g_assert_false(signbit(res)) anywhere I used
> > g_assert_cmpfloat(res,==,0.0); add a test for strtod() hex parsing and
> > handling of junk after ERANGE, which is major enough that I dropped
> > R-b
> > ---
> >   tests/unit/test-cutils.c | 510 +++++++++++++++++++++++++++++++++++++++
> >   1 file changed, 510 insertions(+)
> > 
> > diff --git a/tests/unit/test-cutils.c b/tests/unit/test-cutils.c
> > index d3076c3fec1..1763839a157 100644
> > --- a/tests/unit/test-cutils.c
> > +++ b/tests/unit/test-cutils.c
> 
> [...]
> 
> > +static void test_qemu_strtod_erange_junk(void)
> > +{
> > +    const char *str;
> > +    const char *endptr;
> > +    int err;
> > +    double res;
> > +
> > +    /* EINVAL has priority over ERANGE */
> 
> By being placed here, this comment confused me a bit, because the first case
> does return ERANGE.  So I’d prefer it above the second case, where we
> actually expect EINVAL, but understand that’s a personal preference.  (Same
> for the _finite_ variant)

The test is what happens when both conditions apply.  For
qemu_strtod("1e-999junk", &endptr), only ERANGE applies (because
"junk" is returned in endptr); it is not until
qemu_strtod("1e-999junk", NULL) where EINVAL is also possible
(trailing junk takes precedence over underflow).  For qemu_strtosz(),
I made it a bit more obvious by writing a helper function that shows
both errno values in a single line, rather than spreading out the
boilerplate over multiple lines.

Should I do a similar helper function for qemu_strtod[_finite] in v3?

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org



^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 11/19] test-cutils: Refactor qemu_strtosz tests for less boilerplate
  2023-05-19 15:13   ` Hanna Czenczek
@ 2023-05-19 17:54     ` Eric Blake
  0 siblings, 0 replies; 48+ messages in thread
From: Eric Blake @ 2023-05-19 17:54 UTC (permalink / raw)
  To: Hanna Czenczek; +Cc: qemu-devel, armbru, richard.henderson

On Fri, May 19, 2023 at 05:13:52PM +0200, Hanna Czenczek wrote:
> On 12.05.23 04:10, Eric Blake wrote:
> > No need to copy-and-paste lots of boilerplate per string tested, when
> > we can consolidate that behind helper functions.  Plus, this adds a
> > bit more coverage (we now test all strings both with and without
> > endptr, whereas before some tests skipped the NULL endptr case), which
> > exposed a SEGFAULT on qemu_strtosz(NULL, NULL, &val) that will be
> > parsed in an upcoming patch.

s/parsed/fixed/

> > 
> > Note that duplicating boilerplate has one advantage lost here - a
> > failed test tells you which line number failed; but a helper function
> > does not show the call stack that reached the failure.  Since we call
> > the helper more than once within many of the "unit tests", even the
> > unit test name doesn't point out which call is failing.  But that only
> > matters when tests fail (they normally pass); at which point I'm
> > debugging the failures under gdb anyways, so I'm not too worried about
> > it.
> > 
> > Signed-off-by: Eric Blake <eblake@redhat.com>
> > ---
> >   tests/unit/test-cutils.c | 503 ++++++++-------------------------------
> >   1 file changed, 100 insertions(+), 403 deletions(-)
> 
> Reviewed-by: Hanna Czenczek <hreitz@redhat.com>

I'm debating about using similar boilerplate compression for the
strtol and strtod tests in v3 of the series.  I can definitively state
that debugging a failed test was harder (had to open up gdb instead of
just seeing the line number of the failure), but that overall I got to
focus a lot more on the tests rather than the boilerplate.

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org



^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 14/19] test-cutils: Add more coverage to qemu_strtosz11;rgb:1e1e/1e1e/1e1e
  2023-05-19 15:26   ` [PATCH v2 14/19] test-cutils: Add more coverage to qemu_strtosz11;rgb:1e1e/1e1e/1e1e Hanna Czenczek
@ 2023-05-19 18:02     ` Eric Blake
  0 siblings, 0 replies; 48+ messages in thread
From: Eric Blake @ 2023-05-19 18:02 UTC (permalink / raw)
  To: Hanna Czenczek; +Cc: qemu-devel, armbru, richard.henderson

On Fri, May 19, 2023 at 05:26:12PM +0200, Hanna Czenczek wrote:
> On 12.05.23 04:10, Eric Blake wrote:
> > Add some more strings that the user might send our way.  In
> > particular, some of these additions include FIXME comments showing
> > where our parser doesn't quite behave the way we want.
> > 
> > Signed-off-by: Eric Blake <eblake@redhat.com>
> > 
> > ---
> > 
> > v2: even more tests added, pad a string to avoid out-of-bounds
> > randomness [Hanna]
> > ---
> >   tests/unit/test-cutils.c | 147 +++++++++++++++++++++++++++++++++++----
> >   1 file changed, 135 insertions(+), 12 deletions(-)
> 
> The subject line appears as if it contained an ANSI escape sequence.

Yep, and I even flagged that in reply to the cover letter.

> 
> > diff --git a/tests/unit/test-cutils.c b/tests/unit/test-cutils.c
> > index 1936c7b5795..7800caf9b0e 100644
> > --- a/tests/unit/test-cutils.c
> > +++ b/tests/unit/test-cutils.c
> > @@ -3162,7 +3162,12 @@ static void do_strtosz_full(const char *str, qemu_strtosz_fn fn,
> >       ret = fn(str, &endptr, &val);
> >       g_assert_cmpint(ret, ==, exp_ptr_ret);
> >       g_assert_cmpuint(val, ==, exp_ptr_val);
> > -    g_assert_true(endptr == str + exp_ptr_offset);
> > +    if (str) {
> > +        g_assert_true(endptr == str + exp_ptr_offset);
> > +    } else {
> > +        g_assert_cmpint(exp_ptr_offset, ==, 0);
> > +        g_assert_null(endptr);
> > +    }
> 
> This patch adds no new cases that call do_strtosz*() with a NULL str – did
> you intent for this to go into patch 12?

Oh, indeed - it was patch 12 that added do_strtosz(NULL, -EINVAL,
0xbaadf00d, 0); it's a shame the compiler doesn't complain about 'NULL
+ 0' as being an odd expression.  Yes, I'll hoist this hunk to 12 for
v3...

> 
> Regardless (with the subject fixed, though):
> 
> Reviewed-by: Hanna Czenczek <hreitz@redhat.com>

...while keeping your R-b.

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org



^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 09/19] test-cutils: Add coverage of qemu_strtod
  2023-05-19 17:52     ` Eric Blake
@ 2023-05-22 10:56       ` Hanna Czenczek
  2023-05-22 12:59         ` Eric Blake
  0 siblings, 1 reply; 48+ messages in thread
From: Hanna Czenczek @ 2023-05-22 10:56 UTC (permalink / raw)
  To: Eric Blake; +Cc: qemu-devel, armbru, richard.henderson

On 19.05.23 19:52, Eric Blake wrote:
> On Fri, May 19, 2023 at 05:05:20PM +0200, Hanna Czenczek wrote:
>> On 12.05.23 04:10, Eric Blake wrote:
>>> It's hard to tweak code for consistency if I can't prove what will or
>>> won't break from those tweaks.  Time to add unit tests for
>>> qemu_strtod() and qemu_strtod_finite().
>>>
>>> Among other things, I wrote a check whether we have C99 semantics for
>>> strtod("0x1") (which MUST parse hex numbers) rather than C89 (which
>>> must stop parsing at 'x').  These days, I suspect that is okay; but if
>>> it fails CI checks, knowing the difference will help us decide what we
>>> want to do about it.  Note that C2x, while not final at the time of
>>> this patch, has been considering whether to make strtol("0b1") parse
>>> as 1 with no slop instead of the C17 parse of 0 with slop "b1"; that
>>> decision may also bleed over to strtod().  But for now, I didn't think
>>> it worth adding unit tests on that front (to strtol or strtod) as
>>> things may still change.
>>>
>>> Likewise, there are plenty more corner cases of strtod proper that I
>>> don't explicitly test here, but there are enough unit tests added here
>>> that it covers all the branches reached in our wrappers.  In
>>> particular, it demonstrates the difference on when *value is left
>>> uninitialized, which an upcoming patch will normalize.
>>>
>>> Signed-off-by: Eric Blake <eblake@redhat.com>
>>>
>>> ---
>>>
>>> v2: Added g_assert_false(signbit(res)) anywhere I used
>>> g_assert_cmpfloat(res,==,0.0); add a test for strtod() hex parsing and
>>> handling of junk after ERANGE, which is major enough that I dropped
>>> R-b
>>> ---
>>>    tests/unit/test-cutils.c | 510 +++++++++++++++++++++++++++++++++++++++
>>>    1 file changed, 510 insertions(+)
>>>
>>> diff --git a/tests/unit/test-cutils.c b/tests/unit/test-cutils.c
>>> index d3076c3fec1..1763839a157 100644
>>> --- a/tests/unit/test-cutils.c
>>> +++ b/tests/unit/test-cutils.c
>> [...]
>>
>>> +static void test_qemu_strtod_erange_junk(void)
>>> +{
>>> +    const char *str;
>>> +    const char *endptr;
>>> +    int err;
>>> +    double res;
>>> +
>>> +    /* EINVAL has priority over ERANGE */
>> By being placed here, this comment confused me a bit, because the first case
>> does return ERANGE.  So I’d prefer it above the second case, where we
>> actually expect EINVAL, but understand that’s a personal preference.  (Same
>> for the _finite_ variant)
> The test is what happens when both conditions apply.  For
> qemu_strtod("1e-999junk", &endptr), only ERANGE applies (because
> "junk" is returned in endptr); it is not until
> qemu_strtod("1e-999junk", NULL) where EINVAL is also possible
> (trailing junk takes precedence over underflow).

Yep; it’s just that because the comment is directly above one test case, 
I assumed it applied to just that case, and was looking for the EINVAL 
there.  Only then I realized that EINVAL won’t occur there, and the 
comment instead points out the difference between the two cases there are.

> For qemu_strtosz(),
> I made it a bit more obvious by writing a helper function that shows
> both errno values in a single line, rather than spreading out the
> boilerplate over multiple lines.
>
> Should I do a similar helper function for qemu_strtod[_finite] in v3?

I mean, from my perspective, all I can see is that it would make 
reviewing v3 more tedious…



^ permalink raw reply	[flat|nested] 48+ messages in thread

* Re: [PATCH v2 09/19] test-cutils: Add coverage of qemu_strtod
  2023-05-22 10:56       ` Hanna Czenczek
@ 2023-05-22 12:59         ` Eric Blake
  0 siblings, 0 replies; 48+ messages in thread
From: Eric Blake @ 2023-05-22 12:59 UTC (permalink / raw)
  To: Hanna Czenczek; +Cc: qemu-devel, armbru, richard.henderson

On Mon, May 22, 2023 at 12:56:31PM +0200, Hanna Czenczek wrote:
> > > > +static void test_qemu_strtod_erange_junk(void)
> > > > +{
> > > > +    const char *str;
> > > > +    const char *endptr;
> > > > +    int err;
> > > > +    double res;
> > > > +
> > > > +    /* EINVAL has priority over ERANGE */
> > > By being placed here, this comment confused me a bit, because the first case
> > > does return ERANGE.  So I’d prefer it above the second case, where we
> > > actually expect EINVAL, but understand that’s a personal preference.  (Same
> > > for the _finite_ variant)
> > The test is what happens when both conditions apply.  For
> > qemu_strtod("1e-999junk", &endptr), only ERANGE applies (because
> > "junk" is returned in endptr); it is not until
> > qemu_strtod("1e-999junk", NULL) where EINVAL is also possible
> > (trailing junk takes precedence over underflow).
> 
> Yep; it’s just that because the comment is directly above one test case, I
> assumed it applied to just that case, and was looking for the EINVAL there. 
> Only then I realized that EINVAL won’t occur there, and the comment instead
> points out the difference between the two cases there are.
> 
> > For qemu_strtosz(),
> > I made it a bit more obvious by writing a helper function that shows
> > both errno values in a single line, rather than spreading out the
> > boilerplate over multiple lines.
> > 
> > Should I do a similar helper function for qemu_strtod[_finite] in v3?
> 
> I mean, from my perspective, all I can see is that it would make reviewing
> v3 more tedious…

Okay, v3 will NOT include a helper function for strtoi or strtod (but
the helper already in place for strtosz remains).

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org



^ permalink raw reply	[flat|nested] 48+ messages in thread

end of thread, other threads:[~2023-05-22 13:00 UTC | newest]

Thread overview: 48+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-05-12  2:10 [PATCH v2 00/19] Fix qemu_strtosz() read-out-of-bounds Eric Blake
2023-05-12  2:10 ` [PATCH v2 01/19] test-cutils: Avoid g_assert in unit tests Eric Blake
2023-05-12  3:20   ` Philippe Mathieu-Daudé
2023-05-12 12:11   ` Eric Blake
2023-05-12  2:10 ` [PATCH v2 02/19] test-cutils: Use g_assert_cmpuint where appropriate Eric Blake
2023-05-12  2:10 ` [PATCH v2 03/19] test-cutils: Test integral qemu_strto* value on failures Eric Blake
2023-05-12  2:10 ` [PATCH v2 04/19] test-cutils: Test more integer corner cases Eric Blake
2023-05-19 14:27   ` Hanna Czenczek
2023-05-19 15:17     ` Eric Blake
2023-05-12  2:10 ` [PATCH v2 05/19] cutils: Fix wraparound parsing in qemu_strtoui Eric Blake
2023-05-18 13:34   ` Eric Blake
2023-05-19 14:42   ` Hanna Czenczek
2023-05-19 16:31     ` Eric Blake
2023-05-12  2:10 ` [PATCH v2 06/19] cutils: Document differences between parse_uint and qemu_strtou64 Eric Blake
2023-05-19 14:44   ` Hanna Czenczek
2023-05-12  2:10 ` [PATCH v2 07/19] cutils: Adjust signature of parse_uint[_full] Eric Blake
2023-05-12 16:25   ` Eric Blake
2023-05-19 14:51   ` Hanna Czenczek
2023-05-12  2:10 ` [PATCH v2 08/19] cutils: Allow NULL endptr in parse_uint() Eric Blake
2023-05-12 16:44   ` Eric Blake
2023-05-19 14:54   ` Hanna Czenczek
2023-05-12  2:10 ` [PATCH v2 09/19] test-cutils: Add coverage of qemu_strtod Eric Blake
2023-05-19 15:05   ` Hanna Czenczek
2023-05-19 17:52     ` Eric Blake
2023-05-22 10:56       ` Hanna Czenczek
2023-05-22 12:59         ` Eric Blake
2023-05-12  2:10 ` [PATCH v2 10/19] test-cutils: Prepare for upcoming semantic change in qemu_strtosz Eric Blake
2023-05-12  2:10 ` [PATCH v2 11/19] test-cutils: Refactor qemu_strtosz tests for less boilerplate Eric Blake
2023-05-19 15:13   ` Hanna Czenczek
2023-05-19 17:54     ` Eric Blake
2023-05-12  2:10 ` [PATCH v2 12/19] cutils: Allow NULL str in qemu_strtosz Eric Blake
2023-05-12  3:25   ` Philippe Mathieu-Daudé
2023-05-19 15:15   ` Hanna Czenczek
2023-05-12  2:10 ` [PATCH v2 13/19] numa: Check for qemu_strtosz_MiB error Eric Blake
2023-05-12  2:10 ` [PATCH v2 14/19] test-cutils: Add more coverage to qemu_strtosz11; rgb:1e1e/1e1e/1e1e Eric Blake
2023-05-19 15:26   ` [PATCH v2 14/19] test-cutils: Add more coverage to qemu_strtosz11;rgb:1e1e/1e1e/1e1e Hanna Czenczek
2023-05-19 18:02     ` Eric Blake
2023-05-12  2:10 ` [PATCH v2 15/19] cutils: Set value in all qemu_strtosz* error paths Eric Blake
2023-05-19 15:29   ` Hanna Czenczek
2023-05-12  2:10 ` [PATCH v2 16/19] cutils: Set value in all integral qemu_strto* " Eric Blake
2023-05-12  2:10 ` [PATCH v2 17/19] cutils: Use parse_uint in qemu_strtosz for negative rejection Eric Blake
2023-05-12 19:34   ` Eric Blake
2023-05-19 15:32     ` Hanna Czenczek
2023-05-12  2:10 ` [PATCH v2 18/19] cutils: Improve qemu_strtod* error paths Eric Blake
2023-05-18 13:47   ` Eric Blake
2023-05-12  2:10 ` [PATCH v2 19/19] cutils: Improve qemu_strtosz handling of fractions Eric Blake
2023-05-19 15:36   ` Hanna Czenczek
2023-05-12 12:24 ` [PATCH v2 00/19] Fix qemu_strtosz() read-out-of-bounds Eric Blake

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.