Integer and String Vulnerabilities in C
Software security is a critical topic that has been the focus of attention of many researchers and professionals over the years. One of the reasons this subject does not lose relevance is the number of vulnerabilities that become known each day. According to NVD (2006), a vulnerability can be defined as “a weakness in the computational logic (e.g., code) found in software and hardware components that, when exploited, results in a negative impact on confidentiality, integrity, or availability”. The numbers surrounding this type of computational issue give a more concrete panorama about its criticality. Seacord (2013) states that in a period of nine years — from 2004 to 2012 — a total of 45,135 vulnerabilities were reported and cataloged by the National Vulnerabilities Database — NVD — of the National Institute of Standards and Technology — NIST. Yet, only in the year 2019, the NVD received 18,938 new entries.
In terms of secure programming, two categories of vulnerabilities have been exploited by attackers so that to damage the correct running flow of a software. The first one is String-related vulnerabilities. Basically, failures of this category cause a kind of security threat known as buffer overflow. Another widespread category of vulnerabilities is Integer-related. The most common errors found out in this category are overflow and signed errors. All those situations can lead to unexpected behaviors and the system can end up in an unsecured state. In scenarios like those ones, mitigation strategies turned to avoid jeopardizing a program or even a whole system generally are based on a solid knowledge of the employed programming language and in an accurate manner to explore its resources.
The correct and accurate manipulation of Strings in the C programming language must take into account that a String is a sequence of characters null-terminated. An imprecise comprehension of that String representation generally leads to some of the most commons errors: unbounded string copies, off-by-one errors, null-termination errors, and string truncation. The piece of code below shows a typical example of an unbounded string copy. The program is prepared to receive eight characters at most from the standard input (stdin). Nevertheless, the use of gets() function does not make it possible to limit the number of characters entered by a user. Because of this, a malicious user could explore this issue by entering more than eight characters and pass them to the program. As the null-terminator is overlapped in this scenario, other memory positions not allocated by the program are used to store extra characters which can lead the program to unexpected behaviors.
On the other hand, the second code below describes a practical example where an off-by-one error takes place. As well as unbounded string copies, off-by-one errors are related to writing characters outside the bounds of a string. In such an example, a string of length 10 is correctly stored in the variable source whose storing capacity is accurately defined. The vulnerability starts with the memory allocation for the string dest. This operation uses the function strlen that counts the number of characters of a string until to find the null-terminator. Thus, a position for the character responsible for indicating the string termination is not reserved for the dest string. As the loop copying characters from string source to string dest starts from position 1, the last command writes the zero character outside the bounds of the string dest. The off-by-one error is established and the program can behaviors in an unpredictable way.
Most of the errors and vulnerabilities in the manipulation of integers involve insufficient limits checking of the variables storing data of this type. The code below presents a common error generated by an imprecise type conversion (Seacord, 2013). Although the function checks if the informed size parameter value respects the limit for the maximum array size, there is no checking related to the signal of the parameter value. Thus, a negative size passed to the function will be considered as allowed by it and the malloc function will be invoked with a negative value. As malloc expects arguments of the type size_t, it converts the size value to a large unsigned number which can results in a value larger the defined in MAX_ARRAY_SIZE.
Another typical situation that can lead to integer-related errors was referenced by Dannenberg et al (2010) as unsigned integer wrapping. The following code shows an excerpt of code describing such a situation. As the scenario is related to a custom data struct (element_t) the internal computation wrapped by the calloc function can outcome a memory space smaller than the required to store the data struct. A direct consequence is a buffer overflow error
According to Kuperman et al (2005), “one the best ways to prevent the exploitation of buffer overflow vulnerabilities is to detect and eliminate them from the source code before the software is put to use”. Considering the vulnerabilities presented in the first two codes above, the first one could be straightforwardly mitigated by changing the use of the function gets by the fgets as recommended by the Software Engineering Institute (2007). That alternative function “reads at most one less than a specified number of characters from a stream into an array”. The second one can be fixed by considering the position for the null-terminator during the dest string allocation and adjusting the indexes defined for the loop responsible for the character copies. The code below shows the off-by-one error fixed.
The integer-related samples of vulnerabilities described by the other two pieces of code can also be worked around through very punctual solutions. The conversion error problem is suppressed just by changing the type of the parameter size from int to size_t since the last one offers the guarantee of the accurate precision to represent the size of any data structure (CPP Reference, 2020). A solution for the unsigned integer wrapping situation presented above is proposed by Dannenberg et al (2010). The authors explain that an instance of std::bad_array_new_length must be throw in order to report an integer overflow.
This analysis of both string and integers vulnerabilities showed that many of the related errors could be avoided by means of a programming work more accurately and by using specific resources of the programming language in accordingly scenarios. Moreover, the adoption of some programming practices could contribute to the development of more confident software. As stated by Dannenberg at all (2010), pre-condition testing is an approach that can be used to eliminate many cases of integer vulnerabilities. However, that same strategy can also solve many string-related errors since checking bounds of the string involved in running operations can avoid several buffer overflow issues.