svn commit: r216967 - head/usr.sbin/rtprio
Bruce Evans
brde at optusnet.com.au
Tue Jan 4 20:56:23 UTC 2011
On Tue, 4 Jan 2011, Garrett Cooper wrote:
> On Jan 4, 2011, at 10:22 AM, John Baldwin wrote:
>
>> On Tuesday, January 04, 2011 12:41:51 pm Garrett Cooper wrote:
>>> On Tue, Jan 4, 2011 at 9:27 AM, Konstantin Belousov <kib at freebsd.org> wrote:
>>>> Author: kib
>>>> Date: Tue Jan 4 17:27:17 2011
>>>> New Revision: 216967
>>>> URL: http://svn.freebsd.org/changeset/base/216967
>>>>
>>>> Log:
>>>> Use errx() instead of err() in parseint. There is usually no interesting
>>>> information in errno.
>>>>
>>>> Noted by: Garrett Cooper <yanegomi gmail com>
>>>> MFC after: 1 week
>>>
>>> Someday it would be nice if we could have one standard / libraritized
>>> set of functions that properly handle overflow and all that good stuff
>>> (as bde has suggested to me elsewhere), but this is good enough for
>>> today.
>>
>> strtonum(3)?
>
> bde said (back in 11/15):
>
> "It is good to fix the blind truncation, but overflow is never graceful.
>
> I will review the patch for errors like the ones in corresponding user
> code (dd get_num(), strtonum(), expand_number()... get_num() is almost
> OK, but the newer more public APIs are broken as designed)."
>
> So there's some concern that he has with those APIs still.. the
> fact that I was trying to use a similar API to parse numbers in strings
> in the kernel (strtoq in tunable work) didn't make things any better
> unfortunately.
Part of the brokenness is that strtonum() uses the long long abomination
nstead of [u]intmax_t. humanize_number() and expand_number() also haven't
caught up with C99 -- they use uint64_t.
% Modified: head/usr.sbin/rtprio/rtprio.c
% ==============================================================================
% --- head/usr.sbin/rtprio/rtprio.c Tue Jan 4 17:18:53 2011 (r216966)
% +++ head/usr.sbin/rtprio/rtprio.c Tue Jan 4 17:27:17 2011 (r216967)
% @@ -133,9 +133,9 @@ parseint(const char *str, const char *er
% errno = 0;
% res = strtol(str, &endp, 10);
% if (errno != 0 || endp == str || *endp != '\0')
% - err(1, "%s must be a number", errname);
% + errx(1, "%s must be a number", errname);
% if (res >= INT_MAX)
% - err(1, "Integer overflow parsing %s", errname);
% + errx(1, "Integer overflow parsing %s", errname);
% return (res);
% }
Unfortunately this has many of the common bugs for using the strto*()
family:
- although it carefully sets errno, it uses errno in a bad way. When
strtol() sets ERANGE, this is misinterpreted as a generic error so
the misleading message "%s must be a number" is printed for numbers
that are out of range. The previous version was better since it also
printed the strerror(ERANGE). OTOH, the other error (EINVAL) reported
in errno is detected better by the checks on endp. errno being set to
EINVAL is a POSIX mistake^Wextextension. Portable code still needs to
use the endp checks. rtprio isn't portable but it shouldn't set a bad
example.
- the other part of the overflow checking is mostly wrong too: strtol()
returns LONG_MAX for range errors, but also returns LONG_MIN for range
errors; LONG_MAX may or may not exceed INT_MAX; on 32-bit arches it
happens to equal INT_MAX. So:
(a) overflow of negative values is not detected. Except, all cases of
overflow are detected by the generic check on errno and misreported.
(b) when LONG_MAX exceeds INT_MAX, there is no problem (yet) when res
exceed INT_MAX since although there is no overflow yet, there
would be when parseint() returns int. When res equals INT_MAX,
there is an off-by-1 error -- a non-overflow case is misreported
as overflow. This is almost harmless since a value of INT_MAX
is out of range for a pid. The error message is just slightly
misleading -- the value is too large, although not overflow.
(c) when LONG_MAX equals INT_MAX, res cannot INT_MAX. No problem when
the infinite-precision value exceeds INT_MAX (this is overflow).
When res equals INT_MAX, there may or may not be overflow. Both
cases are reported as overflow. This is mostly harmless, as in (b).
(d) Overflow may occur immediately when parseint() returns, since its
value is stored in a pid_t with no further checks. pid_t happens to
have type int on all supported arches, so there is no problem in
practice.
(e) Negative values for the pid are not errors, but have a special meaning.
The code for handling negative values is laborious and has at least
the following bugs:
(i) proc = abs(proc) overflows on all supported arches when
proc = INT_MIN
(ii) only leading '-' signs are detected by the ad hoc parsing.
strtol() will find '-' signs after leading whitespace. Thus
negative pids may reach the kernel.
(iii) isdigit() is applied to a value that may be negative.
As a result of these bugs, the rtprio passed to the kernel can be way
out of the valid range [0..31]. Hopefully the kernel knows how to check
ranges so the result is just late detection of the invalid option with
an error message less specific than it could be. I get "rtprio: rtprio:
No such process" for "rtprio 22 -33333333" with an old version of
rtprio(1) using atoi(). Similarly with enough more 3's to overflow to
LONG_MIN. Bit with 21 3's, the error changes to the weird "No such file
or directory".
Summary: all the strto*() interfaces are too hard to use. When fixing use
of atoi(), you want to be able to use the same API (maybe s/atoi/xatoi/,
where xatoi exits on error) and not have to write a buggy parse for each
case or even change to a more complicated API like strtonum(). Here the
change from atoi() seems to only gain detection of garbage input like
"1garbage". Even using atoi(), most programs need range checking that
has nothing to do with INT_MAX.
Bruce
More information about the svn-src-head
mailing list