CertC-ERR34¶
Detect errors when converting a string to a number
Required inputs: IR
The process of parsing an integer or floating-point number from a string can
produce many errors. The string might not contain a number. It might contain a
number of the correct type that is out of range (such as an integer that is
larger than
INT_MAX). The string may also contain extra information after the
number, which may or may not be useful after the conversion. These error
conditions must be detected and addressed when a string-to-number conversion is
performed using a C Standard Library function.
The
strtol(),
strtoll(),
strtoimax(),
strtoul(), strtoull(),
strtoumax(), strtof(),strtod(), and
strtold() functions convert the initial portion of a null-terminated byte
string to a
long int,
long long int,
intmax_t,
unsigned long int,
unsigned long long int, uintmax_t, float,
double, and
long double representation, respectively.
Use one of the C Standard Library
strto*() functions to parse an integer or floating-point
number from a string. These functions provide more robust error handling than
alternative solutions. Also, use the
strtol() function to convert to a
smaller signed integer type such as
signed int,
signed short, and
signed char, testing the result
against the range limits for that type. Likewise, use the
strtoul() function to convert to a
smaller unsigned integer type such as
unsigned int,
unsigned short, and
unsigned char, and test the result
against the range limits for that type. These range tests do nothing if
the smaller type happens to have the same size and representation for a
particular implementation.
Noncompliant Code Example (
atoi())
This noncompliant code example converts the string token stored in the
buff to a signed integer value using the
atoi() function:
#include <stdlib.h>
void func(const char *buff) {
int si;
if (buff) {
si = atoi(buff);
} else {
/* Handle error */
}
}
The
atoi(),
atol(),
atoll(), and
atof() functions convert the initial portion of a string token to
int,
long int, long long int, and
double representation, respectively. Except for the behavior
on error, they are equivalent to
atoi: (int)strtol(nptr, (char **)NULL, 10) atol: strtol(nptr, (char **)NULL, 10) atoll: strtoll(nptr, (char **)NULL, 10) atof: strtod(nptr, (char **)NULL)
Unfortunately,
atoi() and related functions lack a mechanism for reporting errors
for invalid values. Specifically, these functions:
- do not need to set
errnoon an error; - have undefined behavior if the value of the result cannot be represented;
- return 0 (or 0.0) if the string does not represent an integer (or decimal), which is indistinguishable from a correctly formatted, zero-denoting input string.
Noncompliant Example (
sscanf())
This noncompliant example uses the
sscanf() function to convert a string token to an integer. The
sscanf() function has the same limitations as
atoi():
#include <stdio.h>
void func(const char *buff) {
int matches;
int si;
if (buff) {
matches = sscanf(buff, "%d", &si);
if (matches != 1) {
/* Handle error */
}
} else {
/* Handle error */
}
}
The
sscanf() function returns the number of input items successfully
matched and assigned, which can be fewer than provided for, or even 0 in the
event of an early matching failure. However,
sscanf() fails to report the other errors reported by
strtol(), such as numeric overflow.
Compliant Solution (
strtol())
The
strtol(),
strtoll(),
strtoimax()),
strtoul(), strtoull(), strtoumax(), strtof(),
strtod(), and
strtold() functions convert a null-terminated byte string
to
long int,
long long int,
intmax_t,
unsigned long int,
unsigned long long int, uintmax_t, float, double, and
long double representation, respectively.
This compliant solution uses
strtol() to convert a string token to an integer and ensures that
the value is in the range of
int:
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
void func(const char *buff) {
char *end;
int si;
errno = 0;
const long sl = strtol(buff, &end, 10);
if (end == buff) {
fprintf(stderr, "%s: not a decimal number\n", buff);
} else if ('\0' != *end) {
fprintf(stderr, "%s: extra characters at end of input: %s\n", buff, end);
} else if ((LONG_MIN == sl || LONG_MAX == sl) && ERANGE == errno) {
fprintf(stderr, "%s out of range of type long\n", buff);
} else if (sl > INT_MAX) {
fprintf(stderr, "%ld greater than INT_MAX\n", sl);
} else if (sl < INT_MIN) {
fprintf(stderr, "%ld less than INT_MIN\n", sl);
} else {
si = (int)sl;
/* Process si */
}
}
Risk Assessment
It is rare for a violation of this rule to result in a security vulnerability unless it occurs in security-sensitive code. However, violations of this rule can easily result in lost or misinterpreted data.
| Recommendation | Severity | Likelihood | Remediation Cost | Priority | Level |
|---|---|---|---|---|---|
| ERR34-C | Medium | Unlikely | Medium | P4 | L3 |
Related Guidelines
| Taxonomy | Taxonomy item | Relationship |
|---|---|---|
| CERT C | INT06-CPP. Use strtol() or a related function to convert a string token to an integer | Prior to 2018-01-12: CERT: Unspecified Relationship |
| CWE 2.11 | CWE-676, Use of potentially dangerous function | 2017-05-18: CERT: Rule subset of CWE |
| CWE 2.11 | CWE-758 | 2017-06-29: CERT: Partial overlap |
Bibliography
| [ ISO/IEC 9899:2011] | Subclause 7.22.1, "Numeric conversion functions" Subclause 7.21.6, "Formatted input/output functions" |
| [ Klein 2002] |
Possible Messages
Key |
Text |
Severity |
Disabled |
|---|---|---|---|
forbidden_libheader_symbol_use |
Usage of forbidden entity from <{}>. |
None |
False |
scanf_conversion_to_number |
Potential numeric overflow: do not use functions of scanf() family to convert a string to number. |
None |
False |
Options¶
This rule shares the following common options: exclude_in_macros, exclude_messages_in_system_headers, excludes, extend_exclude_to_macro_invocations, includes, justification_checker, languages, post_processing, provider, report_at, severity
The following places define options that affect this rule: Stylechecks, Analysis-GlobalOptions
included_headers¶
included_headers : bool = True
scanf_functions¶
scanf_functions
Members of the scanf family to check.Type: dict[bauhaus.analysis.config.QualifiedName, typing.Tuple[str, int, typing.Optional[int]]]
Default:
{ 'fscanf': ('scanf', 1, 2), 'scanf': ('scanf', 0, 1), 'sscanf': ('scanf', 1, 2), 'vfscanf': ('scanf', 1, None), 'vscanf': ('scanf', 0, None), 'vsscanf': ('scanf', 1, None) }
symbol_header¶
symbol_header : set[str] = set()
symbols¶
symbols : set[bauhaus.analysis.config.QualifiedName] = {'atof', 'atoi', 'atol', 'atoll'}
system_symbol_header¶
system_symbol_header : set[str] = {'stdio', 'stdlib'}
translate_header_name¶
translate_header_name : bool = True
stdlib{.h} -> cstdlib).
user_symbol_header¶
user_symbol_header : set[str] = set()