A major goal of the software development process is to deliver high quality and secure products on time. To achieve this, development teams must focus on writing secure code and resolving potential issues during the code review process. In this article, we will dive into common C/C++ vulnerabilities, including integer overflows, incorrect type conversion, and string vulnerabilities. Using examples, we will show you how poor coding practices can help attackers gain control over an application’s execution flow. By crafting exploits for these vulnerabilities, attackers can perform malicious activities such as code injection. So this article will also guide you on the best practices for preventing these kinds of bugs from creeping into your code.
One of the most common string vulnerabilities is including user input in format strings. When you use an I/O function whose format strings contains tainted data, an attacker can control its content to crash vulnerable processes. They can also view memory content, check contents of the stack, or modify a memory location. Format strings containing user input are particularly dangerous because the attacker can execute malicious code by taking advantage of the vulnerable process.
Here is a non-compliant code sample showing how the POSIX syslog() function is prone to this kind of vulnerability. Notice how the message contains untrusted input that is passed as a format-string argument to fprintf().
To fix this, we will not include the untrusted user input in our format string, but instead pass the input as a variadic argument to syslog( ). The complaint code will be as follows:
The Samba (CVE-2009-1886) and Ettercap (CVE-2005-1796) vulnerabilities are two examples of security flaws that result from violating the format-string rules. Both can allow an attacker to execute arbitrary code through the format string.
Invalid string format
Incorrectly formatted strings can lead to abnormal program termination, memory corruption, or any other form of undefined behavior. For this reason, you should always avoid mistakes when creating format strings. This includes using:
- Invalid conversion specifiers
- Flag characters that are incompatible with conversion specifiers
- Length modifiers that are incompatible with conversion specifiers
- Arguments of type other than int for precision or width
- Invalid combinations of argument types, length modifiers, flag characters, or conversion specifiers
In the code snippet below, there’s a mismatch, between the error_type argument and the S specifier. Similarly, the error_msg argument is incorrectly matched to the d specifier. This could eventually result in access violation because cout() interprets the error_type argument as a pointer.
To fix this, we ensure that arguments to the cout() function are matched to the right conversion specifications as follows:
The CWE-686 is a common vulnerability associated with invalid string formats. It involves the software calling a function or procedure, but the argument specified is of a wrong data type, which eventually leads to a weakness. This is common in instances where some variable arguments cannot be enforced during compilation, or where there is inexplicit casting.
The CWE-134 is another weakness associated with an uncontrolled format string. Here, the function accepts a format string from an external source as an argument. This can potentially lead to a data representation problem, buffer overflow, or a denial of service.
Unsigned integer wrapping
A programmer’s C/C++ code is often prone to unsigned integer wrap, particularly when they do not understand the rules of integer conversion when implementing arithmetic operations. This occurs when the underlying integer representation cannot be used in place of the resulting value. Integer wraps are more likely to expose your system to security vulnerabilities if the values are used in:
- Integer operands used in pointer arithmetic and array indexing
- Postfix expressions just before square brackets [ ]
- Functional arguments to a memory allocation function
- Assignment expressions used to declare a variable-length array
- Code that is critical to security
An integer wrap can occur when multiplying two operands resulting in insufficient memory size allocation, as shown in the code snippet below. Here, the signed int value is converted to size t before the multiplication operation. This means multiplication will occur between two unsigned size t integers
To ensure there the program flow does not result in an unsigned integer wrap, we will test the operands:
The same concept applies if when adding unsigned operands. Check this code sample which is vulnerable to an unsigned integer wrap
To ensure no unsigned wrap occurs and minimize possible vulnerabilities, we should perform a pre-condition test as shown
The CVE-2009-1385 and CVE-2014-4377 are example vulnerabilities that occur due to an unsigned integer wrap. Both can cause a buffer overflow, thereby allowing an attacker to execute malicious code.
Poor implementation when converting integers to pointers and vice versa can lead to undesired consequences. Things usually go south when you:
- Convert an integer to a pointer and the resulting pointer type is aligned incorrectly or fails to point to the referenced type
- Convert a pointer to an integer when the resulting type cannot be correctly represented as an integer
- Mapping between the integers and pointers is inconsistent with the environment’s addressing structure
Below is an example vulnerable code that converts the pointer ptr to an integer value. The nine bits (number) are used for holding the flag value before the result is converted into a pointer. The code does not comply to the conversion rules because a 32-bit interger cannot represent 64-bit pointers.
Using struct to store the ptr and flag value can help solve the issue as shown:
CWE 704 is an example vulnerability that stems from incorrect type conversion. The weakness is usually introduced in the design or implementation stage, where the application cannot convert a resource or object from one type to another correctly.
With C++ being one of the most popular programming languages, it is important for every developer to stay on top of things by learning the best coding practices. The above are some of the main issues that make C and C++ applications vulnerable. So, use the suggestions provided throughout the article to keep security vulnerabilities at a low. As a norm, always validate user input before it flows in your system. Happy coding!