From: Al Viro <viro@ZenIV.linux.org.uk>
To: Linus Torvalds <torvalds@linux-foundation.org>
Cc: "Allan, Bruce W" <bruce.w.allan@intel.com>,
"linux-kernel@vger.kernel.org" <linux-kernel@vger.kernel.org>,
Jan Beulich <JBeulich@suse.com>,
Alexey Dobriyan <adobriyan@gmail.com>
Subject: Re: bug in sscanf()?
Date: Tue, 14 Jan 2014 02:47:26 +0000 [thread overview]
Message-ID: <20140114024725.GS10323@ZenIV.linux.org.uk> (raw)
In-Reply-To: <CA+55aFy_V8ze2CPmHY0Ga-K-DX_SATVu-Tb=_nKq_1Rb5WqaUQ@mail.gmail.com>
On Tue, Jan 14, 2014 at 07:22:49AM +0700, Linus Torvalds wrote:
> On Tue, Jan 14, 2014 at 6:30 AM, Al Viro <viro@zeniv.linux.org.uk> wrote:
> >
> > Comments?
>
> Do we have actual users of this? Because I'd almost be inclined to say
> "we just don't support field widths on sscanf() and will warn" unless
> there are users.
>
> We've done that before. The kernel has various limited functions. See
> the whole snprint() issue with %n, which we decided that supporting
> the full semantics was actually a big mistake and we actively
> *removed* code that had been misguidedly added just because people
> thought we should do everything a standard user library does..
>
> Limiting our problem space is a *good* thing, not a bad thing.
>
> If it's possible, of course, and we don't have nasty users.
We do, unfortunately... Not the trigger for that bug, but yes, we do have
places that pass things like %2hhx to sscanf(). Or stuff like
sscanf(oh->name, "timer%2d", &id);
Or
if (sscanf(location, "%03d%c%02d^%02d#%d",
rack, &type, bay, slot, slab) != 5)
return -1;
(nice cargo-culting there, BTW - somebody got too used to printf).
So no, we can't just drop all field width support - in-tree code will break.
Actually, it looks like correct version wouldn't be more complex than what we
have now. Something like [completely untested]
while ((c = *fmt) != '\0') {
/* whitespace matches any amount of whitespace */
if (isspace(c)) {
str = skip_spaces(str);
continue;
}
/* non-% matches itself, %% matches solitary % */
if (c != '%' || (c = *fmt++) == '%') {
if (c == *str++)
continue;
break;
}
/* %*conversion means "convert but don't store" */
suppress = c == '*';
if (suppress)
c = *fmt++;
/* optional field width */
width = -1;
if (isdigit(c)) {
width = c;
while (isdigit(c = *fmt++)) {
width = width * 10 + c - '0';
if (width > MAX_SHRT)
goto Invalid;
}
if (!width)
goto Invalid;
}
/* qualifier */
switch (c) {
case 'h':
if ((c = *fmt++) == 'h')
qualifier = 'H'; /* hh: char */
else
qualifier = 'h'; /* h: short */
break;
case 'l':
if ((c = *fmt++) == 'l')
qualifier = 'L'; /* ll: long long */
else
qualifier = 'l'; /* l: long */
break;
case 'j': /* j: intmax_t, aka long long */
case 'L': /* L: long long */
case 'z': /* z: size_t */
case 'Z': /* Z: alias for 'z' */
case 't': /* t: ptrdiff_t */
qualifier = c;
c = *fmt++;
break;
default:
qualifier = 0;
}
if (c == 'n') {
if (suppress)
continue; /* maybe goto Invalid */
negative = 0;
is_signed = 1;
val = str - buf;
num--;
goto Store_it;
}
/* %c */
if (c == 'c') {
/* might be worth complaining about qualifiers? */
/* %c is %1c */
if (width == -1)
width = 1;
if (!suppress) {
char *p = (char *)va_arg(args, char*);
if (!*str)
break;
do {
*p++ = *str++;
} while (--width && *str);
num++;
} else {
while (*str++ && --width)
;
}
continue;
}
/* everything but %c, %n and %[ skips whitespaces */
str = skip_spaces(str);
if (!*str)
break;
/* %s */
if (c == 's') {
if (width == -1)
width = SHRT_MAX;
if (!suppress) {
char *p = (char *)va_arg(args, char*);
/* now copy until next white space */
while (*str && !isspace(*str) && width--)
*p++ = *str++;
*p = '\0';
num++;
} else {
while (*str && !isspace(*str) && width--)
str++;
}
continue;
}
/* at that point it's numeric or bust */
base = 10;
is_signed = 0;
negative = 0;
sign = 0;
switch (c) {
case 'o':
base = 8;
break;
case 'x':
base = 16;
break;
case 'i':
base = 0;
case 'd':
is_signed = 1;
case 'u':
break;
default:
goto Invalid;
}
/* optional sign - counts towards width limit */
switch (*str) {
case '-':
negative = 1;
case '+':
str++;
width--;
}
rv = parse_number(str, width, base, &val);
if (rv < 0)
goto Conversion_failed;
str += rv;
Store_it:
#define STORE(stype, utype) \
if (is_signed) { \
if ((val - negative) & (-(u64)(1 + (utype)~0ULL)/2)) \
goto Conversion_failed; \
if (negative) \
val = -val; \
if (!suppress) \
*va_arg(args, stype *) = (stype)val; \
} else { \
if (val & -(u64)(1 + (utype)~0ULL)) \
goto Conversion_failed; \
if (negative) \
val = -val; \
if (!suppress) \
*va_arg(args, utype *) = (utype)val; \
} \
if (!suppress) \
num++;
switch (qualifier) {
case 'H':
STORE(signed char, unsigned char);
break;
case 'h':
STORE(short, unsigned short);
break;
case 'l':
STORE(long, unsigned long);
case 'L':
case 'j':
STORE(long long, unsigned long long);
case 'z':
case 'Z':
STORE(long, size_t);
case 't':
STORE(ptrdiff_t, unsigned long);
break;
default:
STORE(int, unsigned);
}
}
out:
return num;
Invalid:
/* probably complain about invalid format string */
Conversion_failed:
return num;
with parse_number(str, width, base, p) being something along the lines of
const char *s = str;
u64 val = 0;
if (!width || !isdigit(*s))
return -1;
if (!base) {
base = 10;
if (*s == '0' && width > 1) {
if (s[1] == 'x' || s[1] == 'X') {
base = 16;
s += 2;
if (width == 2 || !isxdigit(*s))
return -1;
width -= 2;
} else {
base = 8;
}
}
}
while (*s && width) {
unsigned d = base;
if (isdigit(*s))
d = *s - '0';
else if (isxdigit(*s))
d = _tolower(*s) - 'a' + 10;
if (d >= base)
break;
/*
* Check for overflow only if we are within range of
* it in the max base we support (16)
*/
if (unlikely(val & (~0ull << 60))) {
if (val > div_u64(ULLONG_MAX - d, base))
return -1;
}
val = val * base + d;
s++;
width--;
}
*p = val;
return s - str;
next prev parent reply other threads:[~2014-01-14 2:47 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2014-01-09 18:57 bug in sscanf()? Allan, Bruce W
2014-01-13 23:30 ` Al Viro
2014-01-14 0:22 ` Linus Torvalds
2014-01-14 0:32 ` Allan, Bruce W
2014-01-14 2:47 ` Al Viro [this message]
2014-01-14 2:52 ` Al Viro
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20140114024725.GS10323@ZenIV.linux.org.uk \
--to=viro@zeniv.linux.org.uk \
--cc=JBeulich@suse.com \
--cc=adobriyan@gmail.com \
--cc=bruce.w.allan@intel.com \
--cc=linux-kernel@vger.kernel.org \
--cc=torvalds@linux-foundation.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.