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 errno on 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 intlong long int, intmax_tunsigned long intunsigned 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]
Excerpt from SEI CERT C Coding Standard: Rules for Developing Safe, Reliable, and Secure Systems (2016 Edition) and SEI CERT C Coding Standard [https://cmu-sei.github.io/secure-coding-standards/sei-cert-c-coding-standard/rules/error-handling-err/err34-c], Copyright (C) 1995-2026 Carnegie Mellon University. See section 9.4. "3rd-Party Licenses" in the documentation for full details.

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

included_headers

included_headers : bool = True

Whether the rule should also look in headers included by the configured symbol_header.
 

scanf_functions

scanf_functions

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)
}
Members of the scanf family to check.
 

symbol_header

symbol_header : set[str] = set()

Name of the system or user header files of which the symbols should not be used.
 

symbols

symbols : set[bauhaus.analysis.config.QualifiedName] = {'atof', 'atoi', 'atol', 'atoll'}

Names of symbols which are forbidden.
 

system_symbol_header

system_symbol_header : set[str] = {'stdio', 'stdlib'}

Name of the system header files of which the symbols should not be used.
 

translate_header_name

translate_header_name : bool = True

Whether to auto-translate the symbol_header (e.g. stdlib{.h} -> cstdlib).
 

user_symbol_header

user_symbol_header : set[str] = set()

Name of the user header files of which the symbols should not be used.