Commit 046a37bd53f479915bcd5041e0834dad576371a2
Committed by
Wolfgang Denk
1 parent
9785c905cf
Exists in
master
and in
54 other branches
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
README
... | ... | @@ -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. |
include/vsprintf.h
... | ... | @@ -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 |
lib/vsprintf.c
... | ... | @@ -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. |