Aside from safety properties, can we use static analysis tools to detect security issues? Yes, as we will show by discussing a programming error in uftpd, an ftp server implemented in C. In particular, we will show how the error influences both safety as well as security and under which conditions it can be exploited. Furthermore, we will give a quick glance at techniques used to minimize the issue’s impact or to avoid this and comparable errors from the get go.
Recap: Safety vs. security
Safety (more precisely: functional safety, in the sense of ISO/IEC 61508, ISO 26262, and other derived standards) refers to the protection from errors or malfunctions, in particular with respect to dangers or risks of injury, loss of live or property or other undesired outcomes.
Several programming standards defining safety conditions and how to appropriately develop software and systems exist. An example prominently used in the automotive industry, the MISRA C software development guidelines were introduced in 1998 and have been updated several times since.
In contrast to functional safety, software security is more focused on deliberate actions explicitly targeted at providing harm. Rather than asking whether a system can cause harm due to a malfunction, security considerations deal with the question whether an attacker can make a system cause harm. Again, different coding guidelines for the avoidance of security issues are available. For instance, the SEI CERT C Coding Standard aims at safety, reliability and security simultaneously. With the ISO/IEC TS 17961:2013 an international norm for the development of security-critical software has been established.
As we will show below, software flaws often have both a safety as well as a security aspect. Accordingly, the aforementioned standards link to each other in various places and are interconnected. For several of them, mappings from one to another are provided. Safety standards such as the MISRA guidelines have taken up security aspects as well and vice-versa.
Our issue can be found as CVE-2020-5204 in common vulnerability databases such as vulndb. The issue has initially been discovered by Aaron Esau. It resides in the part of uftpd responsible for handling the PORT command of the file transfer protocol (FTP). The PORT command is send by the client to the server to make it open a reverse connection to the client. The client can then use this connection to transfer files.
To allow the server to connect back to them, clients need to provide an IP address and a port number. As FTP is a cleartext protocol, the client simply sends a string formatted as follows:
PORT(ip1,ip2,ip3,ip4,port1,port2). The server then uses
ip4 as the four groups of an IPv4 IP address and calculates the port to connect to as
port = (port1 * 256) + port2.
Following, let’s have a look at how uftpd used to extract the IP address from the given user command. Before the maintainers fixed the issue, the code looked as follows:
ip1,ip2,ip3,ip4,port1,port2 extracted from the command send by the client reside in
INET_ADDRSTRLEN is equal to 16, which is large enough to store an IPv4 address. Three bytes are used per group (each containing one to three digits). Three additional bytes are used for the dots and the null terminator. Using
sscanf, the six numbers are extracted from
str and stored in individual integer variables. Afterwards,
sprintf is used to recombine them into a string properly representing an IP address. However,
sprintf does not check the size of the buffer it writes to in any way! In consequence, if the client provides numbers which are too large,
sprintf will write more than what can be stored in
addr. Anything following
addr on the stack will be overwritten, resulting in a classic stack buffer overflow.
Impact to safety and security
To examine the impact of the issue, we have extracted the code shown above into a simple example application. If executed with number in valid ranges, the application behaves as expected:
Once one of the parameters is too large,
sprintf overwrites and thus replaces parts of the stack, preventing execution from continuing properly:
The program terminates immediately, which would be unacceptable for any safety critical component. Furthermore, when it comes to security, an attacker can use the buffer overflow to overwrite parts of the stack. In particular, anybody able to connect can overwrite the return address. Ultimately, this could lead to an attacker gaining control over the uftpd process and potentially allow execution of arbitrary code.
Luckily, because of the
%d in the format strings of both
sprintf, the buffer and thus the rest of the stack can only be overwritten by characters occurring in numbers: the numerals
9 and the
-. In consequence, an attacker cannot overwrite the return address arbitrarily, diminishing the potential impact of an attack.
To prevent the buffer overflow in the first place,
sprintf was replaced by
snprintf, which requires the caller to provide a maximum size to write and thus prevents overwriting:
Mitigation and avoidance
Stack buffer overflows are a very common issue. The Common Weakness Enumeration lists them in their list of the Top 25 Most Dangerous Software Weaknesses. Buffer Overflows have been responsible for some of the most prominent issues, with impacts ranging from databases to warships. In consequence, different techniques for their avoidance have been developed.
Stack canaries and randomization
The idea behind a stack canary is to place a random integer on the stack just before the return pointer. When using a buffer overflow to replace the pointer to gain control over the process, the canary is overwritten as well. Before a routine actually returns, the canary is verified. If it has been changed, the systems knows an overflow has occurred and stops execution.
With randomization, the address space used by an application is ordered and allocated more randomly. This effectively makes it harder to exploit a buffer overflow with malicious intent. Again, the possible security impact is reduced but the application still crashes.
Static analysis tools
Static analysis tools can detect the potential stack buffer overflow in our example during the implementation phase. This allows developers to replace any offending calls with better alternatives. In particular, for our example, generic description of the tool, as the Axivion Suite, offers several rule sets that would have uncovered the issue:
- A ruleset based on the SEI CERT C Coding Standard, which detects the buffer overflow as part of the STR31-C rule: ‘Guarantee that storage for strings has sufficient space for character data and the null terminator’ [cert].
- A ruleset based on the C Secure Coding rules as defined in ISO/IEC TS 17961:2013. Here, rule 5.40 (called ‘Using a tainted value to write to an object using a formatted input or output function’) detects the issue. [iso17961]
- Last, the issue is an instance of weakness 676 in the Common Weakness Enumeration, i.e., a use of a potentially dangerous function.
References and further information
- [cert] SEI CERT C Coding Standard: Rules for Developing Safe, Reliable, and Secure Systems (2016 Edition)
- [iso17961] CAN/CSA-ISO/IEC TS 17961:2018-01-01, Programming languages, their environments and system software interfaces – C secure coding rules
- Software quality is not a myth: Real examples