Few programmers consider the issues around formatted I/O and type definitions. A programmer-defined integer type might be any type supported by the implementation, even a type larger than unsigned long long
.
For example, given an implementation that supports 128-bit unsigned integers and provides a uint_fast128_t
type, a programmer may define the following type:
typedef uint_fast128_t mytypedef_t;
Furthermore, the definition of programmer-defined types may change. This creates a problem using these types with formatted output functions such as printf()
and formatted input functions such as scanf()
(see FIO00-C. Take care when creating format strings).
The C99 intmax_t
and uintmax_t
types are capable of representing any value representable by any other integer types of the same signedness (see INT00-C. Understand the data model used by your implementation(s)). This allows conversion between programmer-defined integer types (of the same signedness) and intmax_t
and uintmax_t
. For example:
mytypedef_t x; uintmax_t temp; /* ... */ temp = x; /* always secure*/ /* ... change the value of temp ... */ if (temp <= MYTYPEDEF_MAX) { x = temp; }
Formatted I/O functions can be used to input and output greatest-width integer typed values. The j
length modifier in a format string indicates that the following d
, i
, o
, u
, x
, X
, or n
conversion specifier will apply to an argument with type intmax_t
or uintmax_t
. C99 also specifies the z
length modifier for use with arguments of type size_t
and the t
length modifier for arguments of type ptrdiff_t
.
In addition to programmer-defined types, there is no requirement that an implementation provides format length modifiers for implementation-defined integer types. For example, a machine with an implementation-defined 48-bit integer type may not provide format length modifiers for the type. Such a machine would still have to have a 64-bit long long
, with intmax_t
being at least that large.
Noncompliant Code Example (printf()
)
This noncompliant code example prints the value of x
as an unsigned long long
value, even though the value is of a programmer-defined integer type.
#include <stdio.h> mytypedef_t x; /* ... */ printf("%llu", (unsigned long long) x);
There is no guarantee that this code prints the correct value of x
, as x
may be too large to represent as an unsigned long long
.
Compliant Solution (printf()
)
The C99 intmax_t
and uintmax_t
can be safely used to perform formatted I/O with programmer-defined integer types. This is accomplished by converting signed programmer-defined integer types to intmax_t
and unsigned programmer-defined integer types to uintmax_t
, then outputting these values using the j
length modifier. Similarly, programmer-defined integer types can be input to variables of intmax_t
or uintmax_t
(whichever matches the signedness of the programmer-defined integer type) and then converted to programmer-defined integer types using appropriate range checks.
This compliant solution guarantees that the correct value of x
is printed, regardless of its length, provided that mytypedef_t
is an unsigned type.
#include <stdio.h> #include <inttypes.h> mytypedef_t x; /* ... */ printf("%ju", (uintmax_t) x);
Noncompliant Code Example (scanf()
)
The following noncompliant code example reads an unsigned long long
value from standard input and stores the result in x
, which is of a programmer-defined integer type.
#include <stdio.h> mytypedef_t x; /* ... */ if (scanf("%llu", &x) != 1) { /* handle error */ }
This noncompliant code example can result in a buffer overflow, if the size of mytypedef_t
is smaller than unsigned long long
, or it might result in an incorrect value if the size of mytypedef_t
is larger than unsigned long long
.
Compliant Solution (scanf()
)
This compliant solution guarantees that a correct value in the range of mytypedef_t
is read, or an error condition is detected, assuming the value of MYTYPEDEF_MAX
is correct as the largest value representable by mytypedef_t
.
#include <stdio.h> #include <inttypes.h> mytypedef_t x; uintmax_t temp; /* ... */ if (scanf("%ju", &temp) != 1) { /* handle error */ } if (temp > MYTYPEDEF_MAX) { /* handle error */ } else { x = temp;
Risk Assessment
Failure to use an appropriate conversion specifier when inputting or outputting programmer-defined integer types can result in buffer overflow and lost or misinterpreted data.
Recommendation |
Severity |
Likelihood |
Remediation Cost |
Priority |
Level |
---|---|---|---|---|---|
INT15-C |
high |
unlikely |
medium |
P6 |
L2 |
Automated Detection
Compass/ROSE can catch violations of this rule by scanning the printf()
and scanf()
family of functions. For each such function, any variable that corresponds to a "%d" qualifier (or any qualifier besides "%j"), and that variable is not one of the built-in types (char, short, int, long, long long) indicates a violation of this rule. To catch violations, ROSE would also have to recognize derived types in expressions, such as size_t.
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Other Languages
This rule appears in the C++ Secure Coding Standard as INT15-CPP. Use intmax_t or uintmax_t for formatted IO on programmer-defined integer types.
References
[ISO/IEC 9899-1999] Section 7.18.1.5, "Greatest-width integer types," and Section 7.19.6, "Formatted input/output functions"
[MITRE 07] CWE ID 681, "Incorrect Conversion between Numeric Types"