Commit 046a37bd53f479915bcd5041e0834dad576371a2

Authored by Sonny Rao
Committed by Wolfgang Denk
1 parent 9785c905cf

Add safe vsnprintf and snprintf library functions

From: Sonny Rao <sonnyrao@chromium.org>

These functions are useful in U-Boot because they allow a graceful failure
rather than an unpredictable stack overflow when printf() buffers are
exceeded.

Mostly copied from the Linux kernel. I copied vscnprintf and
scnprintf so we can change printf and vprintf to use the safe
implementation but still return the correct values.

(Simon Glass <sjg@chromium.org> modified this commit a little)

Signed-off-by: Sonny Rao <sonnyrao@chromium.org>

Showing 3 changed files with 241 additions and 52 deletions Side-by-side Diff

... ... @@ -655,6 +655,15 @@
655 655 to get the character out. Baud rates will need to default
656 656 to something sensible.
657 657  
  658 +- Safe printf() functions
  659 + Define CONFIG_SYS_VSNPRINTF to compile in safe versions of
  660 + the printf() functions. These are defined in
  661 + include/vsprintf.h and include snprintf(), vsnprintf() and
  662 + so on. Code size increase is approximately 300-500 bytes.
  663 + If this option is not given then these functions will
  664 + silently discard their buffer size argument - this means
  665 + you are not getting any overflow checking in this case.
  666 +
658 667 - Boot Delay: CONFIG_BOOTDELAY - in seconds
659 668 Delay before automatically booting the default image;
660 669 set to -1 to disable autoboot.
... ... @@ -36,5 +36,24 @@
36 36 int vsprintf(char *buf, const char *fmt, va_list args);
37 37 char *simple_itoa(ulong i);
38 38  
  39 +#ifdef CONFIG_SYS_VSNPRINTF
  40 +int snprintf(char *buf, size_t size, const char *fmt, ...)
  41 + __attribute__ ((format (__printf__, 3, 4)));
  42 +int scnprintf(char *buf, size_t size, const char *fmt, ...)
  43 + __attribute__ ((format (__printf__, 3, 4)));
  44 +int vsnprintf(char *buf, size_t size, const char *fmt, va_list args);
  45 +int vscnprintf(char *buf, size_t size, const char *fmt, va_list args);
  46 +#else
  47 +/*
  48 + * Use macros to silently drop the size parameter. Note that the 'cn'
  49 + * versions are the same as the 'n' versions since the functions assume
  50 + * there is always enough buffer space when !CONFIG_SYS_VSNPRINTF
  51 + */
  52 +#define snprintf(buf, size, fmt, args...) sprintf(buf, fmt, ##args)
  53 +#define scnprintf(buf, size, fmt, args...) sprintf(buf, fmt, ##args)
  54 +#define vsnprintf(buf, size, fmt, args...) vsprintf(buf, fmt, ##args)
  55 +#define vscnprintf(buf, size, fmt, args...) vsprintf(buf, fmt, ##args)
  56 +#endif /* CONFIG_SYS_VSNPRINTF */
  57 +
39 58 #endif
... ... @@ -26,6 +26,9 @@
26 26 # define NUM_TYPE long long
27 27 #define noinline __attribute__((noinline))
28 28  
  29 +/* some reluctance to put this into a new limits.h, so it is here */
  30 +#define INT_MAX ((int)(~0U>>1))
  31 +
29 32 const char hex_asc[] = "0123456789abcdef";
30 33 #define hex_asc_lo(x) hex_asc[((x) & 0x0f)]
31 34 #define hex_asc_hi(x) hex_asc[((x) & 0xf0) >> 4]
... ... @@ -291,7 +294,22 @@
291 294 #define SMALL 32 /* Must be 32 == 0x20 */
292 295 #define SPECIAL 64 /* 0x */
293 296  
294   -static char *number(char *buf, unsigned NUM_TYPE num, int base, int size, int precision, int type)
  297 +#ifdef CONFIG_SYS_VSNPRINTF
  298 +/*
  299 + * Macro to add a new character to our output string, but only if it will
  300 + * fit. The macro moves to the next character position in the output string.
  301 + */
  302 +#define ADDCH(str, ch) do { \
  303 + if ((str) < end) \
  304 + *(str) = (ch); \
  305 + ++str; \
  306 + } while (0)
  307 +#else
  308 +#define ADDCH(str, ch) (*(str)++ = (ch))
  309 +#endif
  310 +
  311 +static char *number(char *buf, char *end, unsigned NUM_TYPE num,
  312 + int base, int size, int precision, int type)
295 313 {
296 314 /* we are called with base 8, 10 or 16, only, thus don't need "G..." */
297 315 static const char digits[16] = "0123456789ABCDEF"; /* "GHIJKLMNOPQRSTUVWXYZ"; */
298 316  
299 317  
300 318  
301 319  
302 320  
303 321  
304 322  
305 323  
306 324  
... ... @@ -353,37 +371,40 @@
353 371 precision = i;
354 372 /* leading space padding */
355 373 size -= precision;
356   - if (!(type & (ZEROPAD+LEFT)))
357   - while(--size >= 0)
358   - *buf++ = ' ';
  374 + if (!(type & (ZEROPAD + LEFT))) {
  375 + while (--size >= 0)
  376 + ADDCH(buf, ' ');
  377 + }
359 378 /* sign */
360 379 if (sign)
361   - *buf++ = sign;
  380 + ADDCH(buf, sign);
362 381 /* "0x" / "0" prefix */
363 382 if (need_pfx) {
364   - *buf++ = '0';
  383 + ADDCH(buf, '0');
365 384 if (base == 16)
366   - *buf++ = ('X' | locase);
  385 + ADDCH(buf, 'X' | locase);
367 386 }
368 387 /* zero or space padding */
369 388 if (!(type & LEFT)) {
370 389 char c = (type & ZEROPAD) ? '0' : ' ';
  390 +
371 391 while (--size >= 0)
372   - *buf++ = c;
  392 + ADDCH(buf, c);
373 393 }
374 394 /* hmm even more zero padding? */
375 395 while (i <= --precision)
376   - *buf++ = '0';
  396 + ADDCH(buf, '0');
377 397 /* actual digits of result */
378 398 while (--i >= 0)
379   - *buf++ = tmp[i];
  399 + ADDCH(buf, tmp[i]);
380 400 /* trailing space padding */
381 401 while (--size >= 0)
382   - *buf++ = ' ';
  402 + ADDCH(buf, ' ');
383 403 return buf;
384 404 }
385 405  
386   -static char *string(char *buf, char *s, int field_width, int precision, int flags)
  406 +static char *string(char *buf, char *end, char *s, int field_width,
  407 + int precision, int flags)
387 408 {
388 409 int len, i;
389 410  
390 411  
391 412  
392 413  
... ... @@ -394,16 +415,16 @@
394 415  
395 416 if (!(flags & LEFT))
396 417 while (len < field_width--)
397   - *buf++ = ' ';
  418 + ADDCH(buf, ' ');
398 419 for (i = 0; i < len; ++i)
399   - *buf++ = *s++;
  420 + ADDCH(buf, *s++);
400 421 while (len < field_width--)
401   - *buf++ = ' ';
  422 + ADDCH(buf, ' ');
402 423 return buf;
403 424 }
404 425  
405 426 #ifdef CONFIG_CMD_NET
406   -static char *mac_address_string(char *buf, u8 *addr, int field_width,
  427 +static char *mac_address_string(char *buf, char *end, u8 *addr, int field_width,
407 428 int precision, int flags)
408 429 {
409 430 char mac_addr[6 * 3]; /* (6 * 2 hex digits), 5 colons and trailing zero */
410 431  
... ... @@ -417,10 +438,11 @@
417 438 }
418 439 *p = '\0';
419 440  
420   - return string(buf, mac_addr, field_width, precision, flags & ~SPECIAL);
  441 + return string(buf, end, mac_addr, field_width, precision,
  442 + flags & ~SPECIAL);
421 443 }
422 444  
423   -static char *ip6_addr_string(char *buf, u8 *addr, int field_width,
  445 +static char *ip6_addr_string(char *buf, char *end, u8 *addr, int field_width,
424 446 int precision, int flags)
425 447 {
426 448 char ip6_addr[8 * 5]; /* (8 * 4 hex digits), 7 colons and trailing zero */
427 449  
... ... @@ -435,10 +457,11 @@
435 457 }
436 458 *p = '\0';
437 459  
438   - return string(buf, ip6_addr, field_width, precision, flags & ~SPECIAL);
  460 + return string(buf, end, ip6_addr, field_width, precision,
  461 + flags & ~SPECIAL);
439 462 }
440 463  
441   -static char *ip4_addr_string(char *buf, u8 *addr, int field_width,
  464 +static char *ip4_addr_string(char *buf, char *end, u8 *addr, int field_width,
442 465 int precision, int flags)
443 466 {
444 467 char ip4_addr[4 * 4]; /* (4 * 3 decimal digits), 3 dots and trailing zero */
... ... @@ -456,7 +479,8 @@
456 479 }
457 480 *p = '\0';
458 481  
459   - return string(buf, ip4_addr, field_width, precision, flags & ~SPECIAL);
  482 + return string(buf, end, ip4_addr, field_width, precision,
  483 + flags & ~SPECIAL);
460 484 }
461 485 #endif
462 486  
463 487  
... ... @@ -478,10 +502,12 @@
478 502 * function pointers are really function descriptors, which contain a
479 503 * pointer to the real address.
480 504 */
481   -static char *pointer(const char *fmt, char *buf, void *ptr, int field_width, int precision, int flags)
  505 +static char *pointer(const char *fmt, char *buf, char *end, void *ptr,
  506 + int field_width, int precision, int flags)
482 507 {
483 508 if (!ptr)
484   - return string(buf, "(null)", field_width, precision, flags);
  509 + return string(buf, end, "(null)", field_width, precision,
  510 + flags);
485 511  
486 512 #ifdef CONFIG_CMD_NET
487 513 switch (*fmt) {
488 514  
489 515  
... ... @@ -489,15 +515,18 @@
489 515 flags |= SPECIAL;
490 516 /* Fallthrough */
491 517 case 'M':
492   - return mac_address_string(buf, ptr, field_width, precision, flags);
  518 + return mac_address_string(buf, end, ptr, field_width,
  519 + precision, flags);
493 520 case 'i':
494 521 flags |= SPECIAL;
495 522 /* Fallthrough */
496 523 case 'I':
497 524 if (fmt[1] == '6')
498   - return ip6_addr_string(buf, ptr, field_width, precision, flags);
  525 + return ip6_addr_string(buf, end, ptr, field_width,
  526 + precision, flags);
499 527 if (fmt[1] == '4')
500   - return ip4_addr_string(buf, ptr, field_width, precision, flags);
  528 + return ip4_addr_string(buf, end, ptr, field_width,
  529 + precision, flags);
501 530 flags &= ~SPECIAL;
502 531 break;
503 532 }
504 533  
505 534  
506 535  
507 536  
508 537  
... ... @@ -507,27 +536,31 @@
507 536 field_width = 2*sizeof(void *);
508 537 flags |= ZEROPAD;
509 538 }
510   - return number(buf, (unsigned long) ptr, 16, field_width, precision, flags);
  539 + return number(buf, end, (unsigned long)ptr, 16, field_width,
  540 + precision, flags);
511 541 }
512 542  
513 543 /**
514   - * vsprintf - Format a string and place it in a buffer
515   - * @buf: The buffer to place the result into
516   - * @fmt: The format string to use
517   - * @args: Arguments for the format string
  544 + * Format a string and place it in a buffer (base function)
518 545 *
519   - * This function follows C99 vsprintf, but has some extensions:
  546 + * @param buf The buffer to place the result into
  547 + * @param size The size of the buffer, including the trailing null space
  548 + * @param fmt The format string to use
  549 + * @param args Arguments for the format string
  550 + * @return The number characters which would be generated for the given
  551 + * input, excluding the trailing '\0', as per ISO C99. Note that fewer
  552 + * characters may be written if this number of characters is >= size.
  553 + *
  554 + * This function follows C99 vsnprintf, but has some extensions:
520 555 * %pS output the name of a text symbol
521 556 * %pF output the name of a function pointer
522 557 * %pR output the address range in a struct resource
523 558 *
524   - * The function returns the number of characters written
525   - * into @buf.
526   - *
527 559 * Call this function if you are already dealing with a va_list.
528   - * You probably want sprintf() instead.
  560 + * You probably want snprintf() instead.
529 561 */
530   -int vsprintf(char *buf, const char *fmt, va_list args)
  562 +static int vsnprintf_internal(char *buf, size_t size, const char *fmt,
  563 + va_list args)
531 564 {
532 565 unsigned NUM_TYPE num;
533 566 int base;
534 567  
535 568  
... ... @@ -542,12 +575,20 @@
542 575 /* 'z' support added 23/7/1999 S.H. */
543 576 /* 'z' changed to 'Z' --davidm 1/25/99 */
544 577 /* 't' added for ptrdiff_t */
  578 + char *end = buf + size;
545 579  
  580 +#ifdef CONFIG_SYS_VSNPRINTF
  581 + /* Make sure end is always >= buf - do we want this in U-Boot? */
  582 + if (end < buf) {
  583 + end = ((void *)-1);
  584 + size = end - buf;
  585 + }
  586 +#endif
546 587 str = buf;
547 588  
548 589 for (; *fmt ; ++fmt) {
549 590 if (*fmt != '%') {
550   - *str++ = *fmt;
  591 + ADDCH(str, *fmt);
551 592 continue;
552 593 }
553 594  
554 595  
555 596  
556 597  
557 598  
... ... @@ -609,20 +650,22 @@
609 650  
610 651 switch (*fmt) {
611 652 case 'c':
612   - if (!(flags & LEFT))
  653 + if (!(flags & LEFT)) {
613 654 while (--field_width > 0)
614   - *str++ = ' ';
615   - *str++ = (unsigned char) va_arg(args, int);
  655 + ADDCH(str, ' ');
  656 + }
  657 + ADDCH(str, (unsigned char) va_arg(args, int));
616 658 while (--field_width > 0)
617   - *str++ = ' ';
  659 + ADDCH(str, ' ');
618 660 continue;
619 661  
620 662 case 's':
621   - str = string(str, va_arg(args, char *), field_width, precision, flags);
  663 + str = string(str, end, va_arg(args, char *),
  664 + field_width, precision, flags);
622 665 continue;
623 666  
624 667 case 'p':
625   - str = pointer(fmt+1, str,
  668 + str = pointer(fmt+1, str, end,
626 669 va_arg(args, void *),
627 670 field_width, precision, flags);
628 671 /* Skip all alphanumeric pointer suffixes */
... ... @@ -641,7 +684,7 @@
641 684 continue;
642 685  
643 686 case '%':
644   - *str++ = '%';
  687 + ADDCH(str, '%');
645 688 continue;
646 689  
647 690 /* integer number formats - set up the flags and "break" */
648 691  
... ... @@ -662,9 +705,9 @@
662 705 break;
663 706  
664 707 default:
665   - *str++ = '%';
  708 + ADDCH(str, '%');
666 709 if (*fmt)
667   - *str++ = *fmt;
  710 + ADDCH(str, *fmt);
668 711 else
669 712 --fmt;
670 713 continue;
671 714  
672 715  
673 716  
674 717  
... ... @@ -688,17 +731,135 @@
688 731 if (flags & SIGN)
689 732 num = (signed int) num;
690 733 }
691   - str = number(str, num, base, field_width, precision, flags);
  734 + str = number(str, end, num, base, field_width, precision,
  735 + flags);
692 736 }
  737 +
  738 +#ifdef CONFIG_SYS_VSNPRINTF
  739 + if (size > 0) {
  740 + ADDCH(str, '\0');
  741 + if (str > end)
  742 + end[-1] = '\0';
  743 + }
  744 +#else
693 745 *str = '\0';
  746 +#endif
  747 + /* the trailing null byte doesn't count towards the total */
694 748 return str-buf;
695 749 }
696 750  
  751 +#ifdef CONFIG_SYS_VSNPRINTF
  752 +int vsnprintf(char *buf, size_t size, const char *fmt,
  753 + va_list args)
  754 +{
  755 + return vsnprintf_internal(buf, size, fmt, args);
  756 +}
  757 +
697 758 /**
698   - * sprintf - Format a string and place it in a buffer
699   - * @buf: The buffer to place the result into
700   - * @fmt: The format string to use
701   - * @...: Arguments for the format string
  759 + * Format a string and place it in a buffer (va_list version)
  760 + *
  761 + * @param buf The buffer to place the result into
  762 + * @param size The size of the buffer, including the trailing null space
  763 + * @param fmt The format string to use
  764 + * @param args Arguments for the format string
  765 + * @return the number of characters which have been written into
  766 + * the @buf not including the trailing '\0'. If @size is == 0 the function
  767 + * returns 0.
  768 + *
  769 + * If you're not already dealing with a va_list consider using scnprintf().
  770 + *
  771 + * See the vsprintf() documentation for format string extensions over C99.
  772 + */
  773 +int vscnprintf(char *buf, size_t size, const char *fmt, va_list args)
  774 +{
  775 + int i;
  776 +
  777 + i = vsnprintf(buf, size, fmt, args);
  778 +
  779 + if (likely(i < size))
  780 + return i;
  781 + if (size != 0)
  782 + return size - 1;
  783 + return 0;
  784 +}
  785 +
  786 +/**
  787 + * Format a string and place it in a buffer
  788 + *
  789 + * @param buf The buffer to place the result into
  790 + * @param size The size of the buffer, including the trailing null space
  791 + * @param fmt The format string to use
  792 + * @param ... Arguments for the format string
  793 + * @return the number of characters which would be
  794 + * generated for the given input, excluding the trailing null,
  795 + * as per ISO C99. If the return is greater than or equal to
  796 + * @size, the resulting string is truncated.
  797 + *
  798 + * See the vsprintf() documentation for format string extensions over C99.
  799 + */
  800 +int snprintf(char *buf, size_t size, const char *fmt, ...)
  801 +{
  802 + va_list args;
  803 + int i;
  804 +
  805 + va_start(args, fmt);
  806 + i = vsnprintf(buf, size, fmt, args);
  807 + va_end(args);
  808 +
  809 + return i;
  810 +}
  811 +
  812 +/**
  813 + * Format a string and place it in a buffer
  814 + *
  815 + * @param buf The buffer to place the result into
  816 + * @param size The size of the buffer, including the trailing null space
  817 + * @param fmt The format string to use
  818 + * @param ... Arguments for the format string
  819 + *
  820 + * The return value is the number of characters written into @buf not including
  821 + * the trailing '\0'. If @size is == 0 the function returns 0.
  822 + *
  823 + * See the vsprintf() documentation for format string extensions over C99.
  824 + */
  825 +
  826 +int scnprintf(char *buf, size_t size, const char *fmt, ...)
  827 +{
  828 + va_list args;
  829 + int i;
  830 +
  831 + va_start(args, fmt);
  832 + i = vscnprintf(buf, size, fmt, args);
  833 + va_end(args);
  834 +
  835 + return i;
  836 +}
  837 +#endif /* CONFIG_SYS_VSNPRINT */
  838 +
  839 +/**
  840 + * Format a string and place it in a buffer (va_list version)
  841 + *
  842 + * @param buf The buffer to place the result into
  843 + * @param fmt The format string to use
  844 + * @param args Arguments for the format string
  845 + *
  846 + * The function returns the number of characters written
  847 + * into @buf. Use vsnprintf() or vscnprintf() in order to avoid
  848 + * buffer overflows.
  849 + *
  850 + * If you're not already dealing with a va_list consider using sprintf().
  851 + */
  852 +int vsprintf(char *buf, const char *fmt, va_list args)
  853 +{
  854 + return vsnprintf_internal(buf, INT_MAX, fmt, args);
  855 +}
  856 +
  857 +/**
  858 + * Format a string and place it in a buffer
  859 + *
  860 + * @param buf The buffer to place the result into
  861 + * @param fmt The format string to use
  862 + * @param ... Arguments for the format string
702 863 *
703 864 * The function returns the number of characters written
704 865 * into @buf.