Jump to content

Printf: Difference between revisions

From Wikipedia, the free encyclopedia
Content deleted Content added
Removed subheading "Format stack attach" because it seems wrong. Maybe "Format string attack" was intended
Limitations: Removed this, it is hardly a "limitation", the solution is obvious, and there isn't any better method
Line 323: Line 323:


For most printf-family functions, there is a variant that accepts [[va_list|{{code|va_list}}]] rather than a variable length parameter list. For example, there is a {{code|vfprintf}}, {{code|vsprintf}}, {{code|vsnprintf}}.
For most printf-family functions, there is a variant that accepts [[va_list|{{code|va_list}}]] rather than a variable length parameter list. For example, there is a {{code|vfprintf}}, {{code|vsprintf}}, {{code|vsnprintf}}.

==Limitations==

===Tabular output===

Using only field widths to provide for tabulation, as with a format like {{code|%8d%8d%8d}} for three integers in three 8-character columns, will not guarantee that field separation will be retained if large numbers occur in the data:

<syntaxhighlight lang="output">
1234567 1234567 1234567
123 123 123
123 12345678123
</syntaxhighlight>

Loss of field separation can easily lead to corrupt output. In systems which encourage the use of programs as building blocks in scripts, such corrupt data can often be forwarded into and corrupt further processing, regardless of whether the original programmer expected the output would only be read by human eyes. Such problems can be eliminated by including explicit delimiters, even spaces, in all tabular output formats. Simply changing the dangerous example from before to {{code| %7d %7d %7d}} addresses this, formatting identically until numbers become larger, but then explicitly preventing them from becoming merged on output due to the explicitly included spaces:

<syntaxhighlight lang="output">
1234567 1234567 1234567
123 123 123
123 12345678 123
</syntaxhighlight>

Similar strategies apply to string data.


==Vulnerabilities==
==Vulnerabilities==

Revision as of 11:53, 17 April 2024

An example of the printf function

printf is a C standard library function that formats text and writes it to standard output.

The name, printf is short for print formatted where print refers to output to a printer although the functions are not limited to printer output.

The standard library provides many other similar functions that form a family of printf-like functions. These functions accept a format string parameter and a variable number of value parameters that the function serializes per the format string and writes to an output stream or a string buffer.

The format string is encoded as a template language consisting of verbatim text and format specifiers that each specify how to serialize a value. As the format string is processed left-to-right, a subsequent value is used for each format specifier found. A format specifier starts with a % character and has one or more following characters that specify how to serialize a value.

The format string syntax and semantics is the same for all of the functions in the printf-like family.

Mismatch between the format specifiers and count and type of values can cause a crash or vulnerability.

The printf format string is complementary to the scanf format string, which provides formatted input (lexing a.k.a. parsing). Both format strings provide relatively simple functionality compared to other template engines, lexers and parsers.

The formatting design has been copied in other programming languages.

History

1950s: Fortran

Early programming languages like Fortran used special statements with different syntax from other calculations to build formatting descriptions.[1] In this example, the format is specified on line 601, and the PRINT[a] command refers to it by line number:

      PRINT 601, IA, IB, AREA
 601  FORMAT (4H A= ,I5,5H  B= ,I5,8H  AREA= ,F10.2, 13H SQUARE UNITS)

Hereby:

  • 4H indicates a string of 4 characters " A= " (H means Hollerith Field);
  • I5 indicates an integer field of width 5;
  • F10.2 indicates a floating-point field of width 10 with 2 digits after the decimal point.

An output with input arguments 100, 200, and 1500.25 might look like this:

 A=   100  B=   200  AREA=    1500.25 SQUARE UNITS

1960s: BCPL and ALGOL 68

In 1967, BCPL appeared.[2] Its library included the writef routine.[3] An example application looks like this:

WRITEF("%I2-QUEENS PROBLEM HAS %I5 SOLUTIONS*N", NUMQUEENS, COUNT)

Hereby:

  • %I2 indicates an integer of width 2 (the order of the format specification's field width and type is reversed compared to C's printf);
  • %I5 indicates an integer of width 5;
  • *N is a BCPL language escape sequence representing a newline character (for which C uses the escape sequence \n).

In 1968, ALGOL 68 had a more function-like API, but still used special syntax (the $ delimiters surround special formatting syntax):

printf(($"Color "g", number1 "6d,", number2 "4zd,", hex "16r2d,", float "-d.2d,", unsigned value"-3d"."l$,
         "red", 123456, 89, BIN 255, 3.14, 250));

In contrast to Fortran, using normal function calls and data types simplifies the language and compiler, and allows the implementation of the input/output to be written in the same language. These advantages outweigh the disadvantages (such as a complete lack of type safety in many instances) and in most newer languages I/O is not part of the syntax.

1970s: C

In 1973, printf is included as a C routine as part of Version 4 Unix.[4]

1990s: Shell command

In 1990, a printf shell command is attested as part of 4.3BSD-Reno. It is modeled after the standard library function.[5]

In 1991, a printf command is bundled with GNU shellutils (now part of GNU Core Utilities).

Format specifier

Formatting a value is specified as markup in the format string. For example, the following outputs "Your age is " and then the value of variable age in decimal format.

printf("Your age is %d", age);

Syntax

The syntax for a format specifier is:

%[parameter][flags][width][.precision][length]type

Parameter field

The parameter field is optional. If included, then matching specifiers to values is not sequential. The numeric value, n, selects the nth value parameter.

Character Description
n$ n is the index of the value parameter to serialize using this format specifier

This is a POSIX extension; not C99.

This field allows for using the same value multiple times in a format string instead of having to pass the value multiple times. If a specifier includes this field, then subsequent specifiers must also.

For example,

printf("%2$d %2$#x; %1$d %1$#x",16,17)

outputs: 17 0x11; 16 0x10.

This field is particularly useful for localizing messages to different natural languages that often use different word order.

In Microsoft Windows, support for this feature is via a different function, printf_p.

Flags field

The flags field can be zero or more of (in any order):

Character Description
-
(minus)
Left-align the output of this placeholder. (The default is to right-align the output.)
+
(plus)
Prepends a plus for positive signed-numeric types. positive = +, negative = -.
(The default does not prepend anything in front of positive numbers.)

(space)
Prepends a space for positive signed-numeric types. positive = , negative = -. This flag is ignored if the + flag exists.
(The default does not prepend anything in front of positive numbers.)
0
(zero)
When the 'width' option is specified, prepends zeros for numeric types. (The default prepends spaces.)
For example, printf("%4X",3) produces 3, while printf("%04X",3) produces 0003.
'
(apostrophe)
The integer or exponent of a decimal has the thousands grouping separator applied.
#
(hash)
Alternate form:
For g and G types, trailing zeros are not removed.
For f, F, e, E, g, G types, the output always contains a decimal point.
For o, x, X types, the text 0, 0x, 0X, respectively, is prepended to non-zero numbers.

Width field

The width field specifies the minimum number of characters to output and is typically used to pad fixed-width fields in tabulated output, where the fields would otherwise be narrower. It does not cause truncation of a wider value.

The width field may be omitted, or a numeric integer value, or a dynamic value when passed as another argument when indicated by an asterisk *.[6] For example, printf("%*d", 5, 10) outputs 10, with a total width of 5 characters.

Though not part of the width field, a leading zero is interpreted as the zero-padding flag mentioned above, and a negative value is treated as the positive value in conjunction with the left-alignment - flag also mentioned above.

Precision field

The precision field usually specifies a maximum limit of the output, depending on the particular formatting type. For floating-point numeric types, it specifies the number of digits to the right of the decimal point that the output should be rounded. For the string type, it limits the number of characters that should be output, after which the string is truncated.

The precision field may be omitted, or a numeric integer value, or a dynamic value when passed as another argument when indicated by an asterisk *. For example, printf("%.*s", 3, "abcdef") outputs abc.

Length field

The length field can be omitted or be any of:

Character Description
hh For integer types, causes printf to expect an int-sized integer argument which was promoted from a char.
h For integer types, causes printf to expect an int-sized integer argument which was promoted from a short.
l For integer types, causes printf to expect a long-sized integer argument.

For floating-point types, this is ignored. float arguments are always promoted to double when used in a varargs call.[7]

ll For integer types, causes printf to expect a long long-sized integer argument.
L For floating-point types, causes printf to expect a long double argument.
z For integer types, causes printf to expect a size_t-sized integer argument.
j For integer types, causes printf to expect a intmax_t-sized integer argument.
t For integer types, causes printf to expect a ptrdiff_t-sized integer argument.

Platform-specific length options came to exist prior to widespread use of the ISO C99 extensions, including:

Characters Description Commonly found platforms
I For signed integer types, causes printf to expect ptrdiff_t-sized integer argument; for unsigned integer types, causes printf to expect size_t-sized integer argument. Win32/Win64
I32 For integer types, causes printf to expect a 32-bit (double word) integer argument. Win32/Win64
I64 For integer types, causes printf to expect a 64-bit (quad word) integer argument. Win32/Win64
q For integer types, causes printf to expect a 64-bit (quad word) integer argument. BSD

ISO C99 includes the inttypes.h header file that includes a number of macros for platform-independent printf coding. For example: printf("%" PRId64, t); specifies decimal format for a 64-bit signed integer. Since the macros evaluate to a string literal, and the compiler concatenates adjacent string literals, the expression "%" PRId64 compiles to a single string.

Macros include:

Macro Description
PRId32 Typically equivalent to I32d (Win32/Win64) or d
PRId64 Typically equivalent to I64d (Win32/Win64), lld (32-bit platforms) or ld (64-bit platforms)
PRIi32 Typically equivalent to I32i (Win32/Win64) or i
PRIi64 Typically equivalent to I64i (Win32/Win64), lli (32-bit platforms) or li (64-bit platforms)
PRIu32 Typically equivalent to I32u (Win32/Win64) or u
PRIu64 Typically equivalent to I64u (Win32/Win64), llu (32-bit platforms) or lu (64-bit platforms)
PRIx32 Typically equivalent to I32x (Win32/Win64) or x
PRIx64 Typically equivalent to I64x (Win32/Win64), llx (32-bit platforms) or lx (64-bit platforms)

Type field

The type field can be any of:

Character Description
% Prints a literal % character (this type does not accept any flags, width, precision, length fields).
d, i int as a signed integer. %d and %i are synonymous for output, but are different when used with scanf for input (where using %i will interpret a number as hexadecimal if it's preceded by 0x, and octal if it's preceded by 0.)
u Print decimal unsigned int.
f, F double in normal (fixed-point) notation. f and F only differs in how the strings for an infinite number or NaN are printed (inf, infinity and nan for f; INF, INFINITY and NAN for F).
e, E double value in standard form (d.ddddd). An E conversion uses the letter E (rather than e) to introduce the exponent. The exponent always contains at least two digits; if the value is zero, the exponent is 00. In Windows, the exponent contains three digits by default, e.g. 1.5e002, but this can be altered by Microsoft-specific _set_output_format function.
g, G double in either normal or exponential notation, whichever is more appropriate for its magnitude. g uses lower-case letters, G uses upper-case letters. This type differs slightly from fixed-point notation in that insignificant zeroes to the right of the decimal point are not included. Also, the decimal point is not included on whole numbers.
x, X unsigned int as a hexadecimal number. x uses lower-case letters and X uses upper-case.
o unsigned int in octal.
s null-terminated string.
c char (character).
p void* (pointer to void) in an implementation-defined format.
a, A double in hexadecimal notation, starting with 0x or 0X. a uses lower-case letters, A uses upper-case letters.[8][9] (C++11 iostreams have a hexfloat that works the same).
n Print nothing, but writes the number of characters written so far into an integer pointer parameter.
In Java this prints a newline.[10]

Custom data type formatting

A common way to handle formatting with a custom data type is to format the custom data type value into a string, then use the %s specifier to include the serialized value in a larger message.

Some printf-like functions allow extensions to the escape-character-based mini-language, thus allowing the programmer to use a specific formatting function for non-builtin types. One is the (now deprecated) glibc's register_printf_function(). However, it is rarely used due to the fact that it conflicts with static format string checking. Another is Vstr custom formatters, which allows adding multi-character format names.

Some applications (like the Apache HTTP Server) include their own printf-like function, and embed extensions into it. However these all tend to have the same problems that register_printf_function() has.

The Linux kernel printk function supports a number of ways to display kernel structures using the generic %p specification, by appending additional format characters.[11] For example, %pI4 prints an IPv4 address in dotted-decimal form. This allows static format string checking (of the %p portion) at the expense of full compatibility with normal printf.

Family

Variants of printf provide the formatting features but with additional or slightly different behavior.

fprintf outputs to a system file object which allows output to other than standard output.

sprintf writes to a string buffer instead of standard output.

snprintf provides a level of safety over sprintf since the caller provides a length (n) parameter that specifies the maximum number or chars to write to the buffer.

For most printf-family functions, there is a variant that accepts va_list rather than a variable length parameter list. For example, there is a vfprintf, vsprintf, vsnprintf.

Vulnerabilities

Extra value parameters are ignored, but if the format string has more format specifiers than value parameters passed the behavior is undefined. For some C compilers, an extra format specifier results in consuming a value even though there isn't one. This can allow the format string attack. Generally, for C, arguments are be passed on the stack. If too few arguments are passed, then printf can read past the end of the stackframe, thus allowing an attacker to read the stack.

Some compilers, like the GNU Compiler Collection, will statically check the format strings of printf-like functions and warn about problems (when using the flags -Wall or -Wformat). GCC will also warn about user-defined printf-style functions if the non-standard "format" __attribute__ is applied to the function.

Uncontrolled format string exploit

The format string is often a string literal, which allows static analysis of the function call. However, the format string can be the value of a variable, which allows for dynamic formatting but also a security vulnerability known as an uncontrolled format string exploit.

Memory write

Although an outputting function on the surface, printf allows writing to a memory location specified by an argument via %n. This functionality is occasionally used as a part of more elaborate format-string attacks.[12]

The %n functionality also makes printf accidentally Turing-complete even with a well-formed set of arguments. A game of tic-tac-toe written in the format string is a winner of the 27th IOCCC.[13]

Programming languages with printf

Notable programming languages that include printf or printf-like functionality.

Excluded are languages that use format strings that deviate from the style in this article (such as AMPL and Elixir), languages that inherit their implementation from the JVM or other environment (such as Clojure and Scala), and languages that do not have a standard native printf implementation but have external libraries which emulate printf behavior (such as JavaScript).

See also

Notes

  1. ^ According to the 1956 Fortran manual[1], the PRINT command prints on the attached printer. The manual also introduces the command WRITE OUTPUT TAPE that also uses the FORMAT statement to write on a tape unit.

References

  1. ^ a b Backus, John Warner; Beeber, R. J.; Best, Sheldon F.; Goldberg, Richard; Herrick, Harlan L.; Hughes, R. A.; Mitchell, L. B.; Nelson, Robert A.; Nutt, Roy; Sayre, David; Sheridan, Peter B.; Stern, Harold; Ziller, Irving (15 October 1956). Sayre, David (ed.). The FORTRAN Automatic Coding System for the IBM 704 EDPM: Programmer's Reference Manual (PDF). New York, USA: Applied Science Division and Programming Research Department, International Business Machines Corporation. pp. 26–30. Archived (PDF) from the original on 4 July 2022. Retrieved 4 July 2022. (2+51+1 pages)
  2. ^ "BCPL". cl.cam.ac.uk. Retrieved 19 March 2018.
  3. ^ Richards, Martin; Whitby-Strevens, Colin (1979). BCPL - the language and its compiler. Cambridge University Press. p. 50.
  4. ^ McIlroy, M. D. (1987). A Research Unix reader: annotated excerpts from the Programmer's Manual, 1971–1986 (PDF) (Technical report). CSTR. Bell Labs. 139.
  5. ^ "printf (4.3+Reno BSD)". man.freebsd.org. Retrieved 1 April 2024.
  6. ^ "printf". cplusplus.com. Retrieved 10 June 2020.
  7. ^ ISO/IEC (1999). ISO/IEC 9899:1999(E): Programming Languages – C §7.19.6.1 para 7.
  8. ^ ""The GNU C Library Reference Manual", "12.12.3 Table of Output Conversions"". Gnu.org. Retrieved 17 March 2014.
  9. ^ "printf" (%a added in C99)
  10. ^ "Formatting Numeric Print Output". The Java Tutorials. Oracle Inc. Retrieved 19 March 2018.
  11. ^ "Linux kernel Documentation/printk-formats.txt". Git.kernel.org. Archived from the original on 29 April 2015. Retrieved 17 March 2014.
  12. ^ https://www.exploit-db.com/docs/english/28476-linux-format-string-exploitation.pdf [bare URL PDF]
  13. ^ "Best of show – abuse of libc". Ioccc.org. Retrieved 5 May 2022.
  14. ^ ""The Open Group Base Specifications Issue 7, 2018 edition", "POSIX awk", "Output Statements"". pubs.opengroup.org. Retrieved 29 May 2022.
  15. ^ "Printf Standard Library". The Julia Language Manual. Retrieved 22 February 2021.
  16. ^ "Built-in Types: printf-style String Formatting", The Python Standard Library, Python Software Foundation, retrieved 24 February 2021