Skip to content

Commit

Permalink
selftests/powerpc: Parse long/unsigned long value safely
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Benjamin Gray authored and Michael Ellerman committed Feb 9, 2023
1 parent 121d340 commit d1bc05b
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 7 deletions.
7 changes: 7 additions & 0 deletions tools/testing/selftests/powerpc/include/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
6 changes: 2 additions & 4 deletions tools/testing/selftests/powerpc/pmu/lib.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;
}
Expand Down
126 changes: 123 additions & 3 deletions tools/testing/selftests/powerpc/utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit d1bc05b

Please sign in to comment.