Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

lib: vsprintf: Fix handling of number field widths in vsscanf

The existing code attempted to handle numbers by doing a strto[u]l(),
ignoring the field width, and then repeatedly dividing to extract the
field out of the full converted value. If the string contains a run of
valid digits longer than will fit in a long or long long, this would
overflow and no amount of dividing can recover the correct value.

This patch fixes vsscanf() to obey number field widths when parsing
the number.

A new _parse_integer_limit() is added that takes a limit for the number
of characters to parse. The number field conversion in vsscanf is changed
to use this new function.

If a number starts with a radix prefix, the field width must be long
enough for at last one digit after the prefix. If not, it will be handled
like this:

sscanf("0x4", "%1i", &i): i=0, scanning continues with the 'x'
sscanf("0x4", "%2i", &i): i=0, scanning continues with the '4'

This is consistent with the observed behaviour of userland sscanf.

Note that this patch does NOT fix the problem of a single field value
overflowing the target type. So for example:

sscanf("123456789abcdef", "%x", &i);

Will not produce the correct result because the value obviously overflows
INT_MAX. But sscanf will report a successful conversion.

Note that where a very large number is used to mean "unlimited", the value
INT_MAX is used for consistency with the behaviour of vsnprintf().

Signed-off-by: Richard Fitzgerald <rf@opensource.cirrus.com>
Reviewed-by: Petr Mladek <pmladek@suse.com>
Signed-off-by: Petr Mladek <pmladek@suse.com>
Link: https://lore.kernel.org/r/20210514161206.30821-2-rf@opensource.cirrus.com

authored by

Richard Fitzgerald and committed by
Petr Mladek
900fdc45 11b3dda5

+60 -37
+10 -3
lib/kstrtox.c
··· 39 39 40 40 /* 41 41 * Convert non-negative integer string representation in explicitly given radix 42 - * to an integer. 42 + * to an integer. A maximum of max_chars characters will be converted. 43 + * 43 44 * Return number of characters consumed maybe or-ed with overflow bit. 44 45 * If overflow occurs, result integer (incorrect) is still returned. 45 46 * 46 47 * Don't you dare use this function. 47 48 */ 48 - unsigned int _parse_integer(const char *s, unsigned int base, unsigned long long *p) 49 + unsigned int _parse_integer_limit(const char *s, unsigned int base, unsigned long long *p, 50 + size_t max_chars) 49 51 { 50 52 unsigned long long res; 51 53 unsigned int rv; 52 54 53 55 res = 0; 54 56 rv = 0; 55 - while (1) { 57 + while (max_chars--) { 56 58 unsigned int c = *s; 57 59 unsigned int lc = c | 0x20; /* don't tolower() this line */ 58 60 unsigned int val; ··· 82 80 } 83 81 *p = res; 84 82 return rv; 83 + } 84 + 85 + unsigned int _parse_integer(const char *s, unsigned int base, unsigned long long *p) 86 + { 87 + return _parse_integer_limit(s, base, p, INT_MAX); 85 88 } 86 89 87 90 static int _kstrtoull(const char *s, unsigned int base, unsigned long long *res)
+2
lib/kstrtox.h
··· 4 4 5 5 #define KSTRTOX_OVERFLOW (1U << 31) 6 6 const char *_parse_integer_fixup_radix(const char *s, unsigned int *base); 7 + unsigned int _parse_integer_limit(const char *s, unsigned int base, unsigned long long *res, 8 + size_t max_chars); 7 9 unsigned int _parse_integer(const char *s, unsigned int base, unsigned long long *res); 8 10 9 11 #endif
+48 -34
lib/vsprintf.c
··· 53 53 #include <linux/string_helpers.h> 54 54 #include "kstrtox.h" 55 55 56 + static unsigned long long simple_strntoull(const char *startp, size_t max_chars, 57 + char **endp, unsigned int base) 58 + { 59 + const char *cp; 60 + unsigned long long result = 0ULL; 61 + size_t prefix_chars; 62 + unsigned int rv; 63 + 64 + cp = _parse_integer_fixup_radix(startp, &base); 65 + prefix_chars = cp - startp; 66 + if (prefix_chars < max_chars) { 67 + rv = _parse_integer_limit(cp, base, &result, max_chars - prefix_chars); 68 + /* FIXME */ 69 + cp += (rv & ~KSTRTOX_OVERFLOW); 70 + } else { 71 + /* Field too short for prefix + digit, skip over without converting */ 72 + cp = startp + max_chars; 73 + } 74 + 75 + if (endp) 76 + *endp = (char *)cp; 77 + 78 + return result; 79 + } 80 + 56 81 /** 57 82 * simple_strtoull - convert a string to an unsigned long long 58 83 * @cp: The start of the string ··· 88 63 */ 89 64 unsigned long long simple_strtoull(const char *cp, char **endp, unsigned int base) 90 65 { 91 - unsigned long long result; 92 - unsigned int rv; 93 - 94 - cp = _parse_integer_fixup_radix(cp, &base); 95 - rv = _parse_integer(cp, base, &result); 96 - /* FIXME */ 97 - cp += (rv & ~KSTRTOX_OVERFLOW); 98 - 99 - if (endp) 100 - *endp = (char *)cp; 101 - 102 - return result; 66 + return simple_strntoull(cp, INT_MAX, endp, base); 103 67 } 104 68 EXPORT_SYMBOL(simple_strtoull); 105 69 ··· 123 109 } 124 110 EXPORT_SYMBOL(simple_strtol); 125 111 112 + static long long simple_strntoll(const char *cp, size_t max_chars, char **endp, 113 + unsigned int base) 114 + { 115 + /* 116 + * simple_strntoull() safely handles receiving max_chars==0 in the 117 + * case cp[0] == '-' && max_chars == 1. 118 + * If max_chars == 0 we can drop through and pass it to simple_strntoull() 119 + * and the content of *cp is irrelevant. 120 + */ 121 + if (*cp == '-' && max_chars > 0) 122 + return -simple_strntoull(cp + 1, max_chars - 1, endp, base); 123 + 124 + return simple_strntoull(cp, max_chars, endp, base); 125 + } 126 + 126 127 /** 127 128 * simple_strtoll - convert a string to a signed long long 128 129 * @cp: The start of the string ··· 148 119 */ 149 120 long long simple_strtoll(const char *cp, char **endp, unsigned int base) 150 121 { 151 - if (*cp == '-') 152 - return -simple_strtoull(cp + 1, endp, base); 153 - 154 - return simple_strtoull(cp, endp, base); 122 + return simple_strntoll(cp, INT_MAX, endp, base); 155 123 } 156 124 EXPORT_SYMBOL(simple_strtoll); 157 125 ··· 3567 3541 break; 3568 3542 3569 3543 if (is_sign) 3570 - val.s = qualifier != 'L' ? 3571 - simple_strtol(str, &next, base) : 3572 - simple_strtoll(str, &next, base); 3544 + val.s = simple_strntoll(str, 3545 + field_width >= 0 ? field_width : INT_MAX, 3546 + &next, base); 3573 3547 else 3574 - val.u = qualifier != 'L' ? 3575 - simple_strtoul(str, &next, base) : 3576 - simple_strtoull(str, &next, base); 3577 - 3578 - if (field_width > 0 && next - str > field_width) { 3579 - if (base == 0) 3580 - _parse_integer_fixup_radix(str, &base); 3581 - while (next - str > field_width) { 3582 - if (is_sign) 3583 - val.s = div_s64(val.s, base); 3584 - else 3585 - val.u = div_u64(val.u, base); 3586 - --next; 3587 - } 3588 - } 3548 + val.u = simple_strntoull(str, 3549 + field_width >= 0 ? field_width : INT_MAX, 3550 + &next, base); 3589 3551 3590 3552 switch (qualifier) { 3591 3553 case 'H': /* that's 'hh' in format */