Race condition vulnerabilities
Race condition vulnerabilities are an artifact of parallel processing. The ability to run multiple different execution threads in parallel can create vulnerabilities that would not exist in single-threaded programs. The impact of these vulnerabilities range from exploitable errors to leakage of sensitive information via side effects, like in the Meltdown vulnerability.
Multithreading and parallel processing
A lot of modern computing is dependent upon software running quickly. Humans are impatient, to the point where even a second-long delay in a web page loading causes a significant drop in the number of people that actually visit a page. Since there are limits on how quickly a single execution thread can run on a computer, especially if memory accesses and I/O are required, modern computers contain multiple processes and support multi-threading.
As a result, the assumptions that a developer’s code will be the only program running on a machine at one time and that two adjacent instructions within a program will be executed one after another are invalid. While many developers are still stuck in a single-threaded mindset in development, the use of parallel programming is growing. However, this can create vulnerabilities.
Race condition vulnerabilities
Race condition vulnerabilities are created when assumptions about execution order and lack of concurrency are invalid. In general, it is best to never assume that a single thread of execution will be the only one accessing certain resources. If this assumption is proven invalid (and there are several ways that it can be), then things can break.
One example of this is the Denial of Service attack described in the classic Dining Philosophers Problem. The philosophers are sitting around a table, as shown above, and there are a number of chopsticks equal to the number of philosophers at the table. A philosopher needs two chopsticks to eat and won’t give one up after grabbing it until they’re done eating.
This scenario easily maps to computer science, i.e., with a thread that needs to acquire locks on multiple files to execute. In these “time of check vs. time of use” situations deadlock can occur, where it is impossible for the philosophers or program to continue.
Another potential issue with race conditions is the use of temporary files for storage of critical information. A developer may believe that this is safe, but if temporary filenames are predictable, this behavior is vulnerable to attack. An attacker may create or modify the temporary file in order to change the program’s execution flow or exploit another vulnerability (like a buffer overflow vulnerability in code processing “trusted” data). Depending on how a program uses these temporary files, the attack could be performed before the application runs or modify the file between legitimate read and write operations.
Meltdown: A race condition vulnerability
The famous Meltdown attack is an example of exploitation of a race condition vulnerability. In this case, the vulnerability is caused by parallel processing of fetching data from memory and checking if a user has authorization to access that memory.
Fetching data from memory can be slow. As a result, computers have implemented a few different ways to speed things up. One of these is caching, which involves storing frequently or recently-accessed memory addresses in a cache that can be accessed more quickly. Another is moving memory to a cache in parallel with checking to determine if a user should be permitted to access it. Meltdown exploits this parallelization.
In the Meltdown attack, the attacker requests a piece of data whose address is based on a privileged value in memory, i.e., base_address + X where X is a value stored at an address that the user should not be able to access. The computer simultaneously determines if the user is allowed to access the value of X and places the requested value (base_address + X) in the cache. Before providing the final value to the user, the computer realizes that they are not authorized to have access and denies access.
However, at this point, the requested memory address is already loaded into the cache. The attacker then requests the memory at all possible values of base_address + X and times how long it takes to fetch each piece of data. One of them will be fetched faster because it was the value cached in the previous operation. Based on its address, the attacker can calculate X, giving them access to the protected value.
This attack is only possible due to the parallel processing of checking the user’s permissions and moving the desired memory to the cache. If these operations were performed sequentially (as seems logical), then the value would never be moved to the cache. In the name of optimization, the security of the system is violated.
Protecting against race condition vulnerabilities
Race condition vulnerabilities are created when the result of computation depends upon the order of execution of parallel processes. Protecting against exploitation requires removing this dependency. Common methods include the use of mutexes and atomic operations to prevent race conditions within the code and the use of random temporary filenames to minimize the probability that an external process could predict and modify files containing important data. These protections need to be carefully designed to remove the possibility of deadlock, as described in the Dining Philosophers Problem.
However, it is also important to consider the side effects of execution, as demonstrated by the Meltdown attack. While the end result of execution would be the same regardless of whether the operations were performed in parallel or sequence (i.e., access denied), the order of processing impacted the final state of the system due to side effects. Protecting against race condition vulnerabilities requires considering all possible impacts of execution, not just the return value.