Commit 1184d66238 for openssl.org

commit 1184d66238ad4a5d0168e295e761a4a95fe8b679
Author: Mounir IDRASSI <mounir.idrassi@idrix.fr>
Date:   Sat Jun 27 11:39:18 2026 +0900

    test/strtoultest: add coverage for ossl_strtol, ossl_strtoint and test_strtoint

    Add direct unit tests for the three conversion helpers introduced to
    replace atoi()/atol():

      - ossl_strtol: signed parse, including negative values, LONG_MIN/MAX
        boundaries, ERANGE on overflow, no-consumption and NULL-endptr
        full-consumption rules, NULL result/str, and the internal errno reset;
      - ossl_strtoint: narrowing to int (INT_MIN/MAX pass, +/-1 fail), long
        overflow, plus a NULL result;
      - test_strtoint: the test-util helper wrapping OPENSSL_strtoul(),
        covering negative rejection, trailing-garbage rejection, empty input,
        int and unsigned overflow, and the INT_MAX boundary.

    Link strtoultest against the static libcrypto.a instead of the shared
    library, since the internal ossl_* symbols are not exported (the version
    script hides them with local: *).

    Assisted-by: OpenCode:GLM-5.2

    Reviewed-by: Tom Cosgrove <tom.cosgrove@arm.com>
    Reviewed-by: Tim Hudson <tjh@openssl.org>
    Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
    MergeDate: Wed Jul 29 17:02:19 2026
    (Merged from https://github.com/openssl/openssl/pull/31731)

diff --git a/test/build.info b/test/build.info
index 58f626f209..6694b97e5d 100644
--- a/test/build.info
+++ b/test/build.info
@@ -1430,7 +1430,7 @@ ENDIF

   SOURCE[strtoultest]=strtoultest.c
   INCLUDE[strtoultest]=../include ../apps/include
-  DEPEND[strtoultest]=../libcrypto libtestutil.a
+  DEPEND[strtoultest]=../libcrypto.a libtestutil.a

   SOURCE[bio_pw_callback_test]=bio_pw_callback_test.c
   INCLUDE[bio_pw_callback_test]=../include ../apps/include
diff --git a/test/strtoultest.c b/test/strtoultest.c
index fbeee4845a..d99fec3133 100644
--- a/test/strtoultest.c
+++ b/test/strtoultest.c
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016-2024 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright 2016-2026 The OpenSSL Project Authors. All Rights Reserved.
  *
  * Licensed under the Apache License 2.0 (the "License").  You may not use
  * this file except in compliance with the License.  You can obtain a copy
@@ -7,7 +7,11 @@
  * https://www.openssl.org/source/license.html
  */

+#include <errno.h>
+#include <limits.h>
+
 #include <openssl/crypto.h>
+#include <internal/cryptlib.h>
 #include <internal/nelem.h>
 #include "testutil.h"

@@ -87,8 +91,295 @@ static int test_strtoul(int idx)
     return 1;
 }

+struct strtol_test_entry {
+    char *input; /* the input string */
+    int base; /* the base we are converting in */
+    long expect_val; /* the expected value we should get */
+    int expect_err; /* the expected error we expect to receive */
+    size_t expect_endptr_offset; /* expected endptr offset (index into input) */
+};
+
+static struct strtol_test_entry strtol_tests[] = {
+    /* pass on conv "0" to 0 */
+    {
+        "0", 0, 0, 1, 1 },
+    /* pass on conv "12345" to 12345 */
+    {
+        "12345", 0, 12345, 1, 5 },
+    /* pass on conv "0x12345" to 0x12345, base 16 */
+    {
+        "0x12345", 0, 0x12345, 1, 7 },
+    /* pass on base 10 translation, endptr points to 'x' */
+    {
+        "0x12345", 10, 0, 1, 1 },
+    /* pass on conv " 123" to 123 (leading whitespace) */
+    {
+        " 123", 0, 123, 1, 4 },
+    /* pass on conv "-1" to -1 (signed negative) */
+    {
+        "-1", 0, -1, 1, 2 },
+    /* pass on conv "-12345" to -12345 */
+    {
+        "-12345", 0, -12345, 1, 6 },
+    /* pass on conv " -1" to -1 (whitespace + negative) */
+    {
+        " -1", 0, -1, 1, 3 },
+#if LONG_MAX == 2147483647
+    /* pass on LONG_MAX translation */
+    {
+        "2147483647", 0, LONG_MAX, 1, 10 },
+    /* pass on LONG_MIN translation */
+    {
+        "-2147483648", 0, LONG_MIN, 1, 11 },
+#else
+    /* pass on LONG_MAX translation */
+    {
+        "9223372036854775807", 0, LONG_MAX, 1, 19 },
+    /* pass on LONG_MIN translation */
+    {
+        "-9223372036854775808", 0, LONG_MIN, 1, 20 },
+#endif
+    /* fail on non-numerical input */
+    {
+        "abcd", 0, 0, 0, 0 },
+    /* pass on decimal input */
+    {
+        "1.0", 0, 1, 1, 1 },
+    /* Fail on decimal input without leading number */
+    {
+        ".1", 0, 0, 0, 0 }
+};
+
+static int test_strtol(int idx)
+{
+    long val;
+    char *endptr = NULL;
+    int err;
+    struct strtol_test_entry *test = &strtol_tests[idx];
+
+    err = ossl_strtol(test->input, &endptr, test->base, &val);
+
+    if (!TEST_int_eq(err, test->expect_err))
+        return 0;
+    if (!TEST_ptr_eq(endptr, &test->input[test->expect_endptr_offset]))
+        return 0;
+    if (test->expect_err == 1) {
+        if (!TEST_long_eq(val, test->expect_val))
+            return 0;
+    }
+    return 1;
+}
+
+/* Overflow/underflow must fail and leave errno set to ERANGE */
+static int test_strtol_overflow(void)
+{
+    long l;
+    int ret, saved_errno;
+
+    ret = ossl_strtol("99999999999999999999999", NULL, 0, &l);
+    saved_errno = errno;
+    if (!TEST_int_eq(ret, 0) || !TEST_int_eq(saved_errno, ERANGE))
+        return 0;
+
+    ret = ossl_strtol("-99999999999999999999999", NULL, 0, &l);
+    saved_errno = errno;
+    if (!TEST_int_eq(ret, 0) || !TEST_int_eq(saved_errno, ERANGE))
+        return 0;
+
+    return 1;
+}
+
+/*
+ * A NULL endptr requires the whole string to be consumed.  Pre-setting errno
+ * also verifies the internal errno = 0 reset.
+ */
+static int test_strtol_null_endptr(void)
+{
+    long l;
+    int ret;
+
+    /* full consumption succeeds */
+    ret = ossl_strtol("123", NULL, 0, &l);
+    if (!TEST_int_eq(ret, 1) || !TEST_long_eq(l, 123))
+        return 0;
+
+    /* leading whitespace + negative, full consumption succeeds */
+    ret = ossl_strtol("  -456", NULL, 0, &l);
+    if (!TEST_int_eq(ret, 1) || !TEST_long_eq(l, -456))
+        return 0;
+
+    /* trailing garbage with NULL endptr must fail */
+    ret = ossl_strtol("123abc", NULL, 0, &l);
+    if (!TEST_int_eq(ret, 0))
+        return 0;
+
+    /* pre-set errno; the internal reset must let a clean parse succeed */
+    errno = ERANGE;
+    ret = ossl_strtol("789", NULL, 0, &l);
+    if (!TEST_int_eq(ret, 1) || !TEST_long_eq(l, 789) || !TEST_int_eq(errno, 0))
+        return 0;
+
+    return 1;
+}
+
+/* NULL result / NULL str short-circuit to failure */
+static int test_strtol_null_args(void)
+{
+    long l;
+    int i;
+
+    /* ossl_strtol: result == NULL */
+    if (!TEST_int_eq(ossl_strtol("123", NULL, 0, NULL), 0))
+        return 0;
+    /* ossl_strtol: str == NULL */
+    if (!TEST_int_eq(ossl_strtol(NULL, NULL, 0, &l), 0))
+        return 0;
+    /* ossl_strtoint: result == NULL (short-circuits before ossl_strtol) */
+    if (!TEST_int_eq(ossl_strtoint("123", NULL, 0, NULL), 0))
+        return 0;
+    /* ossl_strtoint: str == NULL (rejected by ossl_strtol) */
+    if (!TEST_int_eq(ossl_strtoint(NULL, NULL, 0, &i), 0))
+        return 0;
+    return 1;
+}
+
+static struct strtol_test_entry strtoint_tests[] = {
+    /* pass on conv "0" to 0 */
+    {
+        "0", 0, 0, 1, 1 },
+    /* pass on conv "123" to 123 */
+    {
+        "123", 0, 123, 1, 3 },
+    /* pass on conv "-456" to -456 (signed, within int range) */
+    {
+        "-456", 0, -456, 1, 4 },
+#if INT_MAX == 2147483647
+    /* pass on INT_MAX translation */
+    {
+        "2147483647", 0, INT_MAX, 1, 10 },
+    /* pass on INT_MIN translation */
+    {
+        "-2147483648", 0, INT_MIN, 1, 11 },
+#endif
+    /* fail on non-numerical input */
+    {
+        "abcd", 0, 0, 0, 0 },
+    /* pass on decimal input */
+    {
+        "1.0", 0, 1, 1, 1 },
+    /* Fail on decimal input without leading number */
+    {
+        ".1", 0, 0, 0, 0 }
+};
+
+/*
+ * Driver named test_strtoint_arr so it does not clash with the test_strtoint()
+ * symbol under test.
+ */
+static int test_strtoint_arr(int idx)
+{
+    int val;
+    char *endptr = NULL;
+    int err;
+    struct strtol_test_entry *test = &strtoint_tests[idx];
+
+    err = ossl_strtoint(test->input, &endptr, test->base, &val);
+
+    if (!TEST_int_eq(err, test->expect_err))
+        return 0;
+    if (!TEST_ptr_eq(endptr, &test->input[test->expect_endptr_offset]))
+        return 0;
+    if (test->expect_err == 1) {
+        if (!TEST_int_eq(val, (int)test->expect_val))
+            return 0;
+    }
+    return 1;
+}
+
+/* Values outside [INT_MIN, INT_MAX] and long overflow all fail */
+static int test_strtoint_overflow(void)
+{
+    int i;
+
+    /* INT_MAX + 1 (narrowing failure, or long overflow on ILP32) */
+    if (!TEST_int_eq(ossl_strtoint("2147483648", NULL, 10, &i), 0))
+        return 0;
+    /* INT_MIN - 1 (narrowing failure, or long underflow on ILP32) */
+    if (!TEST_int_eq(ossl_strtoint("-2147483649", NULL, 10, &i), 0))
+        return 0;
+    /* long overflow: ossl_strtol fails */
+    if (!TEST_int_eq(ossl_strtoint("99999999999999999999999", NULL, 10, &i), 0))
+        return 0;
+    return 1;
+}
+
+struct strtoint_util_entry {
+    char *input; /* the input string */
+    int expect_val; /* the expected value we should get */
+    int expect_err; /* the expected error we expect to receive */
+};
+
+static struct strtoint_util_entry util_strtoint_tests[] = {
+    /* pass on conv "0" to 0 */
+    {
+        "0", 0, 1 },
+    /* pass on conv "123" to 123 */
+    {
+        "123", 123, 1 },
+    /* pass on conv " 45" to 45 (leading whitespace) */
+    {
+        " 45", 45, 1 },
+#if INT_MAX == 2147483647
+    /* pass on INT_MAX translation */
+    {
+        "2147483647", INT_MAX, 1 },
+#endif
+    /* fail on negative input */
+    {
+        "-1", 0, 0 },
+    /* fail on value > INT_MAX */
+    {
+        "2147483648", 0, 0 },
+    /* fail on unsigned overflow */
+    {
+        "99999999999999999999999", 0, 0 },
+    /* fail on non-numerical input */
+    {
+        "abcd", 0, 0 },
+    /* fail on trailing garbage (NULL-endptr full-consumption rule) */
+    {
+        "12abc", 0, 0 },
+    /* fail on empty input */
+    {
+        "", 0, 0 }
+};
+
+static int test_util_strtoint(int idx)
+{
+    int val;
+    int err;
+    struct strtoint_util_entry *test = &util_strtoint_tests[idx];
+
+    err = test_strtoint(test->input, &val);
+
+    if (!TEST_int_eq(err, test->expect_err))
+        return 0;
+    if (test->expect_err == 1) {
+        if (!TEST_int_eq(val, test->expect_val))
+            return 0;
+    }
+    return 1;
+}
+
 int setup_tests(void)
 {
     ADD_ALL_TESTS(test_strtoul, OSSL_NELEM(strtoul_tests));
+    ADD_ALL_TESTS(test_strtol, OSSL_NELEM(strtol_tests));
+    ADD_ALL_TESTS(test_strtoint_arr, OSSL_NELEM(strtoint_tests));
+    ADD_ALL_TESTS(test_util_strtoint, OSSL_NELEM(util_strtoint_tests));
+    ADD_TEST(test_strtol_overflow);
+    ADD_TEST(test_strtol_null_endptr);
+    ADD_TEST(test_strtol_null_args);
+    ADD_TEST(test_strtoint_overflow);
     return 1;
 }