selftests/powerpc: Parse long/unsigned long value safely
authorBenjamin Gray <bgray@linux.ibm.com>
Fri, 3 Feb 2023 00:39:45 +0000 (11:39 +1100)
committerMichael Ellerman <mpe@ellerman.id.au>
Thu, 9 Feb 2023 12:56:45 +0000 (23:56 +1100)
Often a file is expected to hold an integral value. Existing functions
will use a C stdlib function like atoi or strtol to parse the file.
These operations are error prone, with complicated error conditions
(atoi returns 0 if not a number, and is undefined behaviour if not in
range. strtol returns 0 if not a number, and LONG_MIN/MAX if not in
range + sets errno to ERANGE).

Signed-off-by: Benjamin Gray <bgray@linux.ibm.com>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
Link: https://lore.kernel.org/r/20230203003947.38033-4-bgray@linux.ibm.com
tools/testing/selftests/powerpc/include/utils.h
tools/testing/selftests/powerpc/pmu/lib.c
tools/testing/selftests/powerpc/utils.c

index de5e3790f397f8efa7060b105afda1b8211d18e3..7c1fa385824c680f6ec82793e3390ebc87016e3d 100644 (file)
@@ -33,6 +33,13 @@ void *get_auxv_entry(int type);
 
 int pick_online_cpu(void);
 
+int parse_intmax(const char *buffer, size_t count, intmax_t *result, int base);
+int parse_uintmax(const char *buffer, size_t count, uintmax_t *result, int base);
+int parse_int(const char *buffer, size_t count, int *result, int base);
+int parse_uint(const char *buffer, size_t count, unsigned int *result, int base);
+int parse_long(const char *buffer, size_t count, long *result, int base);
+int parse_ulong(const char *buffer, size_t count, unsigned long *result, int base);
+
 int read_file(const char *path, char *buf, size_t count, size_t *len);
 int write_file(const char *path, const char *buf, size_t count);
 int read_debugfs_file(const char *debugfs_file, char *buf, size_t count);
index 960915304a655911626fa90204831501e18de7bf..1cfc13a25aee13d05fd0aa962567237b933fd8e3 100644 (file)
@@ -192,7 +192,6 @@ bool require_paranoia_below(int level)
 {
        int err;
        long current;
-       char *end;
        char buf[16] = {0};
 
        err = read_file(PARANOID_PATH, buf, sizeof(buf) - 1, NULL);
@@ -201,9 +200,8 @@ bool require_paranoia_below(int level)
                return false;
        }
 
-       current = strtol(buf, &end, 10);
-
-       if (end == buf) {
+       err = parse_long(buf, sizeof(buf), &current, 10);
+       if (err) {
                printf("Couldn't parse " PARANOID_PATH "?\n");
                return false;
        }
index 495299a79f50dcf2761a4da4dbb32bd4d2adf971..ddfd871881bff8b705dd19ba31b75e6a0d13b624 100644 (file)
@@ -8,6 +8,8 @@
 #include <elf.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
 #include <link.h>
 #include <sched.h>
 #include <stdio.h>
@@ -123,6 +125,126 @@ int write_debugfs_file(const char *subpath, const char *buf, size_t count)
        return write_file(path, buf, count);
 }
 
+static int validate_int_parse(const char *buffer, size_t count, char *end)
+{
+       int err = 0;
+
+       /* Require at least one digit */
+       if (end == buffer) {
+               err = -EINVAL;
+               goto out;
+       }
+
+       /* Require all remaining characters be whitespace-ish */
+       for (; end < buffer + count; end++) {
+               if (*end == '\0')
+                       break;
+
+               if (*end != ' ' && *end != '\n') {
+                       err = -EINVAL;
+                       goto out;
+               }
+       }
+
+out:
+       errno = -err;
+       return err;
+}
+
+static int parse_bounded_int(const char *buffer, size_t count, intmax_t *result,
+                            int base, intmax_t min, intmax_t max)
+{
+       int err;
+       char *end;
+
+       errno = 0;
+       *result = strtoimax(buffer, &end, base);
+
+       if (errno)
+               return -errno;
+
+       err = validate_int_parse(buffer, count, end);
+       if (err)
+               goto out;
+
+       if (*result < min || *result > max)
+               err = -EOVERFLOW;
+
+out:
+       errno = -err;
+       return err;
+}
+
+static int parse_bounded_uint(const char *buffer, size_t count, uintmax_t *result,
+                             int base, uintmax_t max)
+{
+       int err = 0;
+       char *end;
+
+       errno = 0;
+       *result = strtoumax(buffer, &end, base);
+
+       if (errno)
+               return -errno;
+
+       err = validate_int_parse(buffer, count, end);
+       if (err)
+               goto out;
+
+       if (*result > max)
+               err = -EOVERFLOW;
+
+out:
+       errno = -err;
+       return err;
+}
+
+int parse_intmax(const char *buffer, size_t count, intmax_t *result, int base)
+{
+       return parse_bounded_int(buffer, count, result, base, INTMAX_MIN, INTMAX_MAX);
+}
+
+int parse_uintmax(const char *buffer, size_t count, uintmax_t *result, int base)
+{
+       return parse_bounded_uint(buffer, count, result, base, UINTMAX_MAX);
+}
+
+int parse_int(const char *buffer, size_t count, int *result, int base)
+{
+       intmax_t parsed;
+       int err = parse_bounded_int(buffer, count, &parsed, base, INT_MIN, INT_MAX);
+
+       *result = parsed;
+       return err;
+}
+
+int parse_uint(const char *buffer, size_t count, unsigned int *result, int base)
+{
+       uintmax_t parsed;
+       int err = parse_bounded_uint(buffer, count, &parsed, base, UINT_MAX);
+
+       *result = parsed;
+       return err;
+}
+
+int parse_long(const char *buffer, size_t count, long *result, int base)
+{
+       intmax_t parsed;
+       int err = parse_bounded_int(buffer, count, &parsed, base, LONG_MIN, LONG_MAX);
+
+       *result = parsed;
+       return err;
+}
+
+int parse_ulong(const char *buffer, size_t count, unsigned long *result, int base)
+{
+       uintmax_t parsed;
+       int err = parse_bounded_uint(buffer, count, &parsed, base, ULONG_MAX);
+
+       *result = parsed;
+       return err;
+}
+
 void *find_auxv_entry(int type, char *auxv)
 {
        ElfW(auxv_t) *p;
@@ -224,9 +346,7 @@ int read_debugfs_int(const char *debugfs_file, int *result)
        if (err)
                return err;
 
-       *result = atoi(value);
-
-       return 0;
+       return parse_int(value, sizeof(value), result, 10);
 }
 
 int write_debugfs_int(const char *debugfs_file, int result)