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).
Add a dedicated parse function that accounts for these error conditions so tests can safely parse numbers without undetected bad data. It's a bit ugly to generate the functions through a macro, but it beats copying the error check logic multiple times over. Signed-off-by: Benjamin Gray <bg...@linux.ibm.com> --- .../testing/selftests/powerpc/include/utils.h | 5 ++ tools/testing/selftests/powerpc/pmu/lib.c | 9 ++-- tools/testing/selftests/powerpc/utils.c | 53 +++++++++++++++++-- 3 files changed, 59 insertions(+), 8 deletions(-) diff --git a/tools/testing/selftests/powerpc/include/utils.h b/tools/testing/selftests/powerpc/include/utils.h index de5e3790f397..b82e143a07c6 100644 --- a/tools/testing/selftests/powerpc/include/utils.h +++ b/tools/testing/selftests/powerpc/include/utils.h @@ -33,6 +33,11 @@ void *get_auxv_entry(int type); int pick_online_cpu(void); +int parse_int(const char *buffer, size_t count, int *result, int base); +int parse_long(const char *buffer, size_t count, long *result, int base); +int parse_uint(const char *buffer, size_t count, unsigned int *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); diff --git a/tools/testing/selftests/powerpc/pmu/lib.c b/tools/testing/selftests/powerpc/pmu/lib.c index e8960e7a1271..771658278f55 100644 --- a/tools/testing/selftests/powerpc/pmu/lib.c +++ b/tools/testing/selftests/powerpc/pmu/lib.c @@ -192,16 +192,15 @@ bool require_paranoia_below(int level) { int err; long current; - char *end, buf[16]; + char buf[16] = {0}; + char *end; - if ((err = read_file(PARANOID_PATH, buf, sizeof(buf), NULL))) { + if ((err = read_file(PARANOID_PATH, buf, sizeof(buf) - 1, NULL))) { printf("Couldn't read " PARANOID_PATH "?\n"); return false; } - current = strtol(buf, &end, 10); - - if (end == buf) { + if ((err = parse_long(buf, sizeof(buf), ¤t, 10))) { printf("Couldn't parse " PARANOID_PATH "?\n"); return false; } diff --git a/tools/testing/selftests/powerpc/utils.c b/tools/testing/selftests/powerpc/utils.c index 8593e67ce779..c82539fd44f1 100644 --- a/tools/testing/selftests/powerpc/utils.c +++ b/tools/testing/selftests/powerpc/utils.c @@ -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> @@ -113,6 +115,53 @@ int write_debugfs_file(const char *subpath, const char *buf, size_t count) return write_file(path, buf, count); } +#define TYPE_MIN(x) \ + _Generic((x), \ + int: INT_MIN, \ + long: LONG_MIN, \ + unsigned int: 0, \ + unsigned long: 0) + +#define TYPE_MAX(x) \ + _Generic((x), \ + int: INT_MAX, \ + long: LONG_MAX, \ + unsigned int: INT_MAX, \ + unsigned long: LONG_MAX) + +#define define_parse_number(fn, type, super_type) \ + int fn(const char *buffer, size_t count, type *result, int base) \ + { \ + char *end; \ + super_type parsed; \ + \ + errno = 0; \ + parsed = _Generic(parsed, \ + intmax_t: strtoimax, \ + uintmax_t: strtoumax)(buffer, &end, base); \ + \ + if (errno == ERANGE || \ + parsed < TYPE_MIN(*result) || parsed > TYPE_MAX(*result)) \ + return ERANGE; \ + \ + /* Require at least one digit */ \ + if (end == buffer) \ + return EINVAL; \ + \ + /* Require all remaining characters be whitespace-ish */ \ + for (; end < buffer + count; end++) \ + if (!(*end == ' ' || *end == '\n' || *end == '\0')) \ + return EINVAL; \ + \ + *result = parsed; \ + return 0; \ + } + +define_parse_number(parse_int, int, intmax_t); +define_parse_number(parse_long, long, intmax_t); +define_parse_number(parse_uint, unsigned int, uintmax_t); +define_parse_number(parse_ulong, unsigned long, uintmax_t); + void *find_auxv_entry(int type, char *auxv) { ElfW(auxv_t) *p; @@ -213,9 +262,7 @@ int read_debugfs_int(const char *debugfs_file, int *result) if ((err = read_debugfs_file(debugfs_file, value, sizeof(value) - 1))) 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) -- 2.38.1