Race Condition Vulnerability
Race condition occurs when multiple threads read and write the same variable i.e. they have access to some shared data and they try to change it at the same time. In such a scenario threads are “racing” each other to access/change the data.
This is a major security vulnerability [CWE-362], and by manipulating the timing of actions anomalous results might appear. This vulnerability arises during a TOCTOU (time-of-check, time-of-use) window.
Flow of File Access during it’s TOCTOU Window
So, what if we lock the file during this TOCTOU window itself!
- General Misconception –
A trivial cure to this vulnerability could be locking the file itself during this check-and-use window, because then no other process can use the file during the time window.
Seems easy, then why isn’t this practical? Why can’t we use this approach to solve the race condition problem?
The answer is simply that such a vulnerability could not actually be prevented with just locking the file.
- Problems while locking the file –
A file is locked out for other processes only if it is already in open state. This process is called check-and-open process and during this time it is impossible to lock a file. Any locks created can be ignored by the attacking or the malicious process.
What actually happens is that the call to Open() does not block an attack on a locked file. When the file is available for a check-and-open process, the file actually is open to any access/ change. So it’s impossible to lock a file at this point of time. This makes any kind of locks virtually non-existent to the malicious processes.
Internally it is using the sleep_time which doubles at every attempt. More commonly this is referred to as a spinlock or the busy form of waiting. Also there is always a possibility of the file getting locked indefinitely i.e. danger of getting stuck in a deadlock.
- What would happen even if we were somehow able to lock the file?
Let’s try to lock the file and see what could be the possible drawbacks. The most common locking mechanism that is available is atomic file locking. It is done using a lockfile to create a unique file on the same filesystem. We make use of link() to make a link to the lockfile for any kind of access to the file.
- If link() returns 0, the lock is successful.
The most common fix available is to store the PID of the application in the lock file, which is checked against the active PID at that time. Then again a flaw with this fix is that PID may have been reused.
- Actual Solutions –
A better solution is to rather than creating locks on the file as a whole, lock the parts of the file to different processes.
When a process wants to write into a file, it first asks the kernel to lock that file or a part of it. As long as the process keeps the lock, no other process can ask to lock the same part of the file. Hence you could see that issue with concurrency is getting resolved like this.
In the same way, a process asks for locking before reading the content of a file, which ensures no changes will be made as long as the lock is kept.
Differentiating these different kind of locks is done by the system itself. The system has the capability to distinguish between the locks required for file reading and those required for file writing. This kind of locking system is achieved by the flock() system call. Flock() call can have different values :
- LOCK_SH (lock for reading)
- LOCK_EX (for writing)
- LOCK_UN (release of the lock)
Using these separate call we can tell what kind of locks are necessary.
A point to note here is that, many processes can be benefited from a reading lock simultaneously since no one will attempt to change the file content. However, only one process can benefit from a lock for writing at a given time which is currently using it. Thus no other lock can be allowed at the same time, even for reading.
This kind of carefully crafted system works well with applications that can ask the kernel to reserve their access (their lock) before reading or writing to an important system file. Hence this way of selectively locking the file is very much practical than our initial approach. So, while you are trying to implement your own file system for directories you could take advantage of this secure coding technique to prevent a potential CWE-362 (Race Condition Vulnerability).