Introduction
In the world of software development and security, a race condition is a term that indicates an occurrence in software development and information safety, where the result of a process depends on the execution times of multiple operations. These race conditions come into play when two or more processes gain entry to shared resources like files, memory, or variables and struggle to make changes simultaneously.
When these processes are not well synchronized in many situations, they can lead to severe data consistency problems and even security vulnerabilities. Exploiting race condition vulnerabilities would allow attackers to perform undesirable actions like unauthorized access, modifying data, or executing malicious code.
In 2024, a critical vulnerability, CVE-2024–6387, was discovered in OpenSSH. Attackers exploited this vulnerability to achieve remote code execution (RCE) by exploiting a race condition in the SIGALRM signal manager on OpenSSH servers running on glibc-based Linux systems.
What is Race Condition?
In a real-life situation, let’s say we have a bank account with a balance of $500. Two people are each trying to withdraw $500 from the account simultaneously. If there is a Race Condition weakness in the system, both withdrawals can be made successfully, even though there should not actually be enough money for both withdrawals.
The occurrence of a race condition describes a scenario in programming that takes the result of actions or processes into account depending on the time or sequence of execution of various processes. Such an incident transpires when multiple processes interfere with one resource concurrently; if the processes are not executed in a specific order, the outcome can differ from case to case. It implies that the outcome is contingent upon how these processes are programmed and scheduled to run.
To better understand Race Condition and its implications, we need to delve into some fundamental concepts related to computer memory and the heap.
What is Memory
Memory refers to the physical devices used to store information in a computer. It includes various types, such as RAM (Random Access Memory), which is volatile and used for temporary storage, and storage devices like hard drives and SSDs, which provide non-volatile, long-term storage. Memory is crucial for software functioning, storing the data and instructions required for executing processes.
What is the Heap
The heap is a region of a computer’s memory that is used for dynamic memory allocation. Unlike the stack, where memory allocation and deallocation are handled automatically, the heap requires manual management by the programmer. Memory allocated on the heap persists until it is explicitly freed, allowing for flexible data management that may vary in size or lifetime. Vulnerabilities in heap management, such as race conditions, can lead to serious security issues like unauthorized data access or code execution.
Reading from the resource: Both processes read the current value of the shared resource, which starts with 5.
Changing the value: Each process increases its local value by 1. Now, each process’s local value is 6.
Writing back to the resource: Each process writes its updated value back to the shared resource. The final value in the resource is 6, although we would expect it to be 7 if the actions were fine.
Imagine you have a system where two processes (programs or pieces of software) need to access the same file simultaneously, one to read from it and the other to write to it.
Process 1 (the writer process): Write new data to the file.
Process 2 (the reader process): reads data from the file to process it.
If these two processes work simultaneously and are not properly coordinated, problems can arise:
Concurrent reading and writing: If the process’s write operation starts writing new data just as the reader process starts reading data, the reader may read some of the old data and some of the new data. This can lead to the data being read being incorrect or incomplete.
Mismatched timing: If the reader process reads data just as the writer deletes or modifies it, the reader may read incomplete or incorrect data.
Example with Python:
# The writer process
with open(“data.txt”, “w”) as file:
file.write(“This is the new data”)# The reader process
with open(“data.txt”, “r”) as file:
data = file.read()
print (data)
In such a scenario, if the processes are not coordinated, the reader reads inconsistent data.
CVE-2024–6387
The CVE-2024–6387 vulnerability is due to a race condition in OpenSSH, where insecure actions in the server’s operations are performed on asynchronous signals (async-signal-unsafe functions) from the SIGALRM signal handler. This mode can be exploited to manipulate the heap memory, causing the server to process actions incorrectly and achieve remote code execution (RCE) with root privileges. In this vulnerability, the attacker takes advantage of the competition between the server’s processing time and the information sent from it to exploit the heap memory, leading to incorrect server actions.
Heap memory vulnerabilities result from improper use of memory management, such as uncontrolled memory allocations and deallocations. In such cases, the attacker can exploit these vulnerabilities to alter the program’s behavior, read or write insecure data, and execute malicious code. The attacker exploits the race condition in the SIGALRM signal handler, executed on the heap, to cause incorrect operations in the heap memory, ultimately leading to the execution of malicious code.
Example with Python code:
perform_ssh_handshake
This function performs the SSH protocol handshake process.
- First it sends the attacker’s version of SSH to the server.
- It then gets the SSH version from the server.
- It sends a KEX_INIT packet to the server.
- Finally it receives the KEX_INIT packet from the server.
- If all steps succeed, the function returns True; Otherwise, it returns False.
prepare_heap
This function prepares the memory (heap) of the server to exploit the vulnerability.
It sends a series of packets to the server to create heap holes.
The process involves sending large and small packets alternately to create a memory structure suitable for exploiting the vulnerability.
In addition, it creates fake file structures in the server’s memory.
Each step involves a short wait to avoid overloading the server and ensure that the packets are processed properly.
time_final_packet
This function measures the server’s processing time.
- It sends two measurement packages before and after processing.
- It measures the time it takes the server to respond to each packet.
- It calculates the processing time by subtracting the response times.
- It prints the calculated processing time.
attempt_race_condition
This function attempts to exploit the race condition.
- It creates a final package that contains the vulnerability exploit.
- It sends the entire packet, except for the last byte, to the server.
- It measures the time and waits for the appropriate time to send the last byte.
- It tries to get a response from the server to check if the exploit was successful.
- If the exploit is successful, it prints an appropriate message.
perform_exploit
This function performs the entire exploit.
- It sets the success ranking to False and performs the seeding of a random time (random seed).
- It loops over all possible glibc_base addresses.
- Within each address, it makes 20,000 attempts to exploit the vulnerability.
- It performs all the steps: connecting to the server, shaking hands, preparing the memory, measuring time, and attempting to exploit.
- If the exploit succeeds, it prints out success and exits the loop.
In CVE-2024–6387, the attacker exploits competition between the server’s packet processing operations and the packets it sends. The exact timing at which the attacker sends the final packet is critical to exploiting the vulnerability. The vulnerability allows an attacker to achieve remote code execution with root privileges by exploiting this race condition.
How To Mitigate From Race Conditions?
Race condition vulnerabilities can lead to unexpected results and even serious security vulnerabilities. To avoid such situations, some several methods and techniques can be used:
1. Process synchronization
Process synchronization is a technique that ensures that processes do not perform critical operations at the same time. Here are some common mechanisms for synchronizing processes:
Mutexes (Mutual Exclusion Locks): Locks that ensure that only one process can access a certain resource at any given time. A pass that wants to access the resource first “locks” the lock, performs the critical operation, “releases” the lock.
Semaphores: mechanisms that allow a limited number of processes to reach a certain resource at the same time. There are two main types:
Binary semaphores: Similar to mutexes, only one process can access a resource at any given time.
Counting semaphores: allow to reach a limited number of processes at the same time.
Locks: general locks that allow coordination between processes. There are additional read and write mechanisms, which allow read access to several processes at the same time, but write access to only one process at any given time.
Step 1: Process 1 locks the shared resource to start performing critical operations.
Step 2: Process 1 reads the current value of the resource, modifies it, and writes the updated value back to the resource.
Step 3: Process 1 unlocks.
Step 4: Process 2 waits until the resource is free, then locks the resource to start performing critical operations.
Step 5: Process 2 reads the current value of the resource, modifies it, and writes the updated value back to the resource.
Step 6: Process 2 unlocks.
Result: The final value of the resource will be correct and predictable.
2. Atomic approach
An atomic approach is a technique that guarantees that critical operations will be performed in their entirety without the possibility of interference from other processes. That is, the entire operation is performed as a single unit without the possibility of another process intervening in the middle. Atomic operations are supported by hardware or by software libraries that allow these operations to be performed safely.
3. Proper planning of the software
Proper planning of the software can prevent race condition situations by intelligently planning the access to shared resources:
Avoiding parallel access: Designing processes so that they do not have to access shared resources at the same time.
Use of appropriate data structures: Use of data structures that allow safe and synchronized access, such as race-safe queues (thread-safe queues).
Separation of processes: design so that each process performs a certain part of the work and does not interfere with the work of other processes.
Conclusion
Race condition vulnerabilities, such as CVE-2024–6387 in OpenSSH, result from a situation where processes access or modify a shared resource at the same time, leading to unexpected results and security vulnerabilities. In order to avoid such situations, it is important to use process synchronization techniques such as mutexes, semaphores and locks, to perform operations in an atomic manner and to make sure that the software is properly planned. Understanding and implementing these principles improves the reliability and security of the software systems and prevents the exploitation of critical vulnerabilities.
+ There are no comments
Add yours