Aside from safety properties, can static analysis tools be used 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 a system can be made to cause harm by an attacker. Again, different coding guidelines for the avoidance of security issues are available, e.g., the SEI CERT C Coding Standard, which is aimed at safety, reliability and security simultaneously. With the ISO/IEC TS 179612013 an international norm for the development of security critical software has been established.
As we will show with a simple example 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 protocol (FTP). The PORT command is send by the client to the server to make the server open a reverse connection to the client. This connection can then be used 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 ip1 to 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 issue has been fixed, the code looked as follows:
The parameters ip1,ip2,ip3,ip4,port1,port2 extracted from the command send by the client reside in str. INET_ADDRSTRLEN is equal to 16, which is large enough to store an IPv4 address: 3 bytes per group (each containing one to three digits), 3 bytes 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.
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, the buffer overflow can be used to overwrite parts of the stack. In particular, the return address could be overwritten. Ultimately, this could lead to an attacker gaining control over the running uftpd process and potentially allow execution of arbitrary code.
Luckily, because of the %d in the format strings of both sscanf and sprintf, the buffer and thus the rest of the stack can only be overwritten by characters occurring in numbers: the numerals 0 to 9 and the –. In consequence, the return address cannot be overwritten arbitrarily and the potential impact of an attack is diminished.
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 stack return pointer. When using a buffer overflow to replace the pointer and thus 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, making it harder to actually exploit the buffer overflow with malicious intent. Again, the possible security impact is reduced but the application still crashes.
Using static analysis, the potential stack buffer overflow in our example can be detected during the implementation phase, allowing developers to replace any offending calls by better alternatives. In particular, for our example, 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 179612013. Here, the issue is detected as part of rule 5.40, which is titled Using a tainted value to write to an object using a formatted input or output function‘. [iso17961]
- Last, it can be detected as an instance of weakness 676 in the Common Weakness Enumeration, i.e., a use of a potentially dangerous function.
[by Dr. Sebastian Krings, Professional Services, R&D, Axivion GmbH]
Get in touch with Dr. Sebastian Krings by clicking the Email button below.
If you would like to find out more about Axivion or our Axivion Suite please follow the links or contact us directly via Chat, contactform or by Email. We invite you to get a free demo and we are always looking forward helping you.
[cert] SEI CERT C Coding Standard: Rules for Developing Safe, Reliable, and Secure Systems (2016 Edition)
[iso17961] CAN/CSA-ISO/IEC TS 1796