Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does using the try statement avoid a race condition?

When determining whether or not a file exists, how does using the try statement avoid a "race condition"?

I'm asking because a highly upvoted answer (update: it was deleted) seems to imply that using os.path.exists() creates an opportunity that would not exist otherwise.

The example given is:

try:    with open(filename): pass except IOError:    print 'Oh dear.' 

But I'm not understanding how that avoids a race condition compared to:

if not os.path.exists(filename):     print 'Oh dear.' 

How does calling os.path.exists(filename) allow the attacker to do something with the file that they could not already do?

like image 543
Honest Abe Avatar asked Jan 29 '13 02:01

Honest Abe


People also ask

How can a race condition be avoided?

Race conditions can be avoided by proper thread synchronization in critical sections. Thread synchronization can be achieved using a synchronized block of Java code. Thread synchronization can also be achieved using other synchronization constructs like locks or atomic variables like java.

What is race condition and how it can be overcome?

It can be eliminated by using no more than two levels of gating. An essential race condition occurs when an input has two transitions in less than the total feedback propagation time. Sometimes they are cured using inductive delay line elements to effectively increase the time duration of an input signal.

How does the race condition occurs?

When race conditions occur. A race condition occurs when two threads access a shared variable at the same time. The first thread reads the variable, and the second thread reads the same value from the variable.


2 Answers

The race condition is, of course, between your program and some other code that operates on file (race condition always requires at least two parallel processes or threads, see this for details). That means using open() instead of exists() may really help only in two situations:

  1. You check for existence of a file that is created or deleted by some background process (however, if you run inside a web server, that often means there are many copies of your process running in parallel to process HTTP requests, so for web apps race condition is possible even if there are no other programs).
  2. There may be some malicious program running that is trying to crash your code by destroying the file at the moments you expect it to exist.

exists() just performs a single check. If file exists, it may be deleted a microsecond after exists() returned True. If file is absent, it may be created immediately.

However, open() not just tests for file existence, but also opens the file (and does these two actions atomically, so nothing can happen between the check and the opening). Usually files can not be deleted while they are open by someone. That means that inside with you may be completely sure: file really exists now since it is open. Though it's true only inside with, and the file still may be deleted immediately after with block exits, putting code that needs file to exist inside with guarantees that code will not fail.

like image 198
Ellioh Avatar answered Sep 21 '22 00:09

Ellioh


Here's an example of usage:

try:     with open('filename') as f:         do_stuff_that_depends_on_the_existence_of_the_file(f) except IOError as e:     print 'Trouble opening file' 

If you are opening the file with any access at all, then the OS will guarantee that the file exists, or else it will fail with an error. If the access is exclusive, any other process in contention for the file will either be blocked by you, or block you.

The try is just a way to detect the error or success of the act of opening the file, since file I/O APIs in Python typically do not have return codes (exceptions are used instead). So to really answer your question, it's not the try that avoids the race condition, it's the open. It's basically the same in C (on which Python is based), but without exceptions. Read this for more information.

Note that you would probably want to execute code that depends on access to the file inside the try block. Once you close the file, its existence is no longer guaranteed.

Calling os.path.exists merely gives a snapshot at a moment in time when the file may or may not exist, and you have no knowledge of the existence of the file once os.path.exists returns. Malevolent code or unexpected logic may delete or change the file when you are not expecting it. It is akin to turning your head to check that a road is clear before driving into it. Once you turn your head back, you have nothing but a guess about what is going on where you are no longer looking. Holding the file open guarantees an extended consistent state, something not possible (for good or ill) when driving. :)

Your suggestion of checking that a file does not exist rather than using try/open is still insufficient because of the snapshot nature of os.path.exists. Unfortunately I know of no way to prevent files from being created in a directory in all cases, so I think it is best to check for the positive existence of a file, rather than its absence.

like image 43
Randall Cook Avatar answered Sep 22 '22 00:09

Randall Cook