[PATCH 1/5] lib/vsprintf.c: make sure vsnprintf() never returns a negative value

Simon Glass sjg at chromium.org
Sat Jun 19 19:32:10 CEST 2021


Hi Rasmus,

On Thu, 27 May 2021 at 17:01, Rasmus Villemoes
<rasmus.villemoes at prevas.dk> wrote:
>
> On 21/05/2021 16.42, Heinrich Schuchardt wrote:
> > On 21.05.21 16:27, Tom Rini wrote:
> >> On Fri, May 21, 2021 at 04:15:39PM +0200, Heinrich Schuchardt wrote:
> >>> On 21.05.21 14:53, Rasmus Villemoes wrote:
> >>>> On 20/05/2021 19.51, Simon Glass wrote:
> >>>>> Hi Rasmus,
> >>>>>
> >>>>> On Thu, 20 May 2021 at 04:05, Rasmus Villemoes
> >>>>> <rasmus.villemoes at prevas.dk> wrote:
> >>>>>>
> >>>>>> Most callers (or callers of callers, etc.) of vsnprintf() are not
> >>>>>> prepared for it to return a negative value.
> >>>>>>
> >>>>>> The only case where that can currently happen is %pD, and it's IMO
> >>>>>> more user-friendly to produce some output that clearly shows that some
> >>>>>> "impossible" thing happened instead of having the message completely
> >>>>>> ignored - or mishandled as for example log.c would currently do.
> >>>>>>
> >>>>>> Signed-off-by: Rasmus Villemoes <rasmus.villemoes at prevas.dk>
> >>>>>> ---
> >>>>>>  lib/vsprintf.c | 10 +---------
> >>>>>>  1 file changed, 1 insertion(+), 9 deletions(-)
> >>>>>
> >>>>> I think that is debatable. If we want the calling code to be fixed,
> >>>>> then it needs to get an error code back. Otherwise the error will be
> >>>>> apparent to the user but (perhaps) not ever debugged.
> >>>>
> >>>> But it is not the calling code that is at fault for the vsnprintf()
> >>>> implementation (1) being able to fail and (2) actually encountering an
> >>>> ENOMEM situation. There's _nothing_ the calling code can do about that.
> >>>
> >>> include/vsnprintf.h states:
> >>>
> >>> "This function follows C99 vsnprintf, but has some extensions:".
> >>>
> >>> The C99 spec says:
> >>>
> >>> "The vsnprintf function returns the number of characters that would have
> >>> been written had n been sufficiently large, not counting  the
> >>> terminating  null  character, or a negative value if an encoding error
> >>> occurred."
> >>>
> >>> It is obvious that the calling code needs to be fixed if it cannot
> >>> handle negative return values.
> >>>
> >>> So NAK to the patch.
> >>>
> >>> Best regards
> >>>
> >>> Heinrich
> >>>
> >>>>
> >>>> The calling code can be said to be responsible for not passing NULL
> >>>> pointers, but that case is actually handled gracefully in various places
> >>>> in the printf code (both for %pD, but also plain %s).
> >>>>
> >>>>> The definition of printf() allows for the possibility of a negative
> >>>>> return value.
> >>>>
> >>>> First, please distinguish printf() from vsnprintf(). The former (in the
> >>>> normal userspace version) obviously can fail for the obvious EIO, ENOSPC
> >>>> reasons. The latter is indeed allowed to fail per the posix spec, but
> >>>> from a QoI perspective, I'd say it's much better to have a guarantee
> >>>> _for our particular implementation_ that it does not fail (meaning:
> >>>> returns a negative result). There's simply too many direct and indirect
> >>>> users of vsnprintf() that assume the result is non-negative; if we do
> >>>> not provide that guarantee, the alternative is to play a whack-a-mole
> >>>> game and add tons of error-checking code (adding bloat to the image),
> >>>> with almost never any good way to handle it.
> >>>>
> >>>> Take that log_info(" ... %pD") as an example. Suppose we "fix" log.c so
> >>>> that it ignores the message if vsnprintf (or vscnprintf, whatever)
> >>>> returns a negative result, just as print() currently does [which is the
> >>>> other thing that log_info could end up being handled by]. That means
> >>>> nothing gets printed on the console, and nobody gets told about the
> >>>> ENOMEM. In contrast, with this patch, we get
> >>>>
> >>>>   Booting <%pD:ENOMEM>
> >>>>
> >>>> printed on the console, so at least _some_ part of the message gets out,
> >>>> and it's apparent that something odd happened. Of course, all of that is
> >>>> in the entirely unlikely sitation where the (efi) allocation would
> >>>> actually fail.
> >>>>
> >>>> If we don't want that <%pD:ENOMEM> thing, I'd still argue that we should
> >>>> ensure vsnprintf returns non-negative; e.g. by changing the "return
> >>>> PTR_ERR()" to a "goto out", i.e. simply stop the processing of the
> >>>> format string at the %pD which failed, but still go through the epilogue
> >>>> that ensures the resulting string becomes nul-terminated (another
> >>>> reasonable assumption made by tons of callers), and return how much got
> >>>> printed till then.
> >>
> >> So, how can we fix the callers without the above noted problems?
> >>
> >
> > The assumption that vsnprintf() is used to print to the console and that
> > writing some arbitrary string to the buffer is allowable is utterly wrong.
> >
> > vsnprintf_internal() is used to implement snprintf(). snprintf() is used
> > in numerous places where it will not lead to console output.
> >
> > Trying to solve one problem this patch creates a bunch of new ones.
>
> Heinrich, you do realize that the error handling you added in 256060e
> when you made it possible for vsnprintf() to return something negative
> is broken and incomplete? In multiple ways, even.
>
> First, let's look at vscnprint, which wasn't touched by 256060e.
>
> int vscnprintf(char *buf, size_t size, const char *fmt, va_list args)
> {
>         int i;
>
>         i = vsnprintf(buf, size, fmt, args);
>
>         if (likely(i < size))
>                 return i;
>
> Integer promotion says that, should i be -ENOMEM or some other random
> -Esomething, that comparison is false (for any realistic value of the
> size parameter), so we won't actually pass on that negative value.
> Instead, we'll fall through to the logic that handles "oh, vsnprintf()
> didn't have enough room, so the length of the generated string, which is
> what I'm supposed to return, is size-1".
>
> Hence printf(), which uses vscnprintf(), would in that case receive
> CONFIG_SYS_PBSIZE-1 as result, and then it would go on to puts() the
> printbuffer[] - which isn't a nul-terminated string because you did that
> early return, so it has stack garbage.
>
> Let us look at printf() in more detail. Assuming vscnprintf() forwarded
> a negative value directly, do you see the problem here:
>
>         uint i;
>
>         i = vscnprintf(printbuffer, sizeof(printbuffer), fmt, args);
>
>         /* Handle error */
>         if (i <= 0)
>                 return i;
>
> So even if vscnprintf() was updated, printf() _still_ would go on to
> pass stack garbage to puts().
>
> ====
>
> Let me re-iterate what I believe any vsnprintf-implementation (and its
> close wrappers [v]s[c][n]printf) in a kernel or bootloader context must
> satisfy:
>
> - never return a negative value
> - given a non-zero size argument, must guarantee that the output buffer
> is a proper nul-terminated string.
>
> Without those guarantees, bugs like those described above will creep in,
> even if somebody fixes the issues I just pointed out. I'm not gonna send
> band-aid patches which will just propagate the problems to all other users.
>
> Not only do these guarantees make it easier to use the sprintf family,
> it also avoids lots of code bloat from unnecessary 'ret < 0' checks. It
> really has nothing to do with whether the output is destined for the
> console, _all_ users (direct or through several layers of helpers) of
> the sprintf family benefit from an implementation that provides these
> guarantees.

Gosh what a lot of stuff to read. Thank you both for all the analysis.
I feel that s...printf() should never return an error code, so I agree
with Rasmus, I suppose.

EFI's oddity here is just going to have to be EFI's problem. I am not
becoming more sympathetic to the EFI-ification of firmware, nor does
this sort of corner case progress the cause

If we need to return an error somehow, then I suggest:

- create a new function, e.g. vsprintf_err()
- carry around a flag in vsprintf.c to indicate the behaviour on error
- put support for the flag behind a CONFIG to avoid code-size bloat

Reviewed-by: Simon Glass <sjg at chromium.org>

(but please update docs for pointer() to explain args and return value)

Regards,
Simon


More information about the U-Boot mailing list