简体   繁体   中英

Can short-circuit evaluation trigger a race condition?

I like code golf. In light of CVE-2016-5195 , I was wondering if any of my "golfed" code can trigger race conditions.

Let's say we have two functions, both of which return a boolean value, and we are initializing a boolean variable called result :

result = foo() || bar();

In an ideal world, we have two scenarios:

  1. foo returns true . Do not call bar . result equals true . [short-circuit scenario]
  2. foo returns false . Do call bar() . result equals what bar returns.

My question: Will there ever be a time when short-circuit evaluation is violated, and bar is called despite foo returning true , or even worse, bar is called before foo is called, perhaps because of multi-threading? If so, can you provide a piece of code that would trigger such behavior?

Your answer may be about any language(s) in which this syntax is valid, although I suppose some languages will be more strict about this kind of thing than others.

Race conditions occur when the outcome of a series of operations depends on the sequence upon which they are executed.

The && and || operators in C++ guarantee left-to-right evaluation and to not evaluate the second operator if the first one is false / true, respectively. Since the sequence of operations is guaranteed, there is no race condition between operations of foo and operations of bar . There can still be race conditions between the operations in each one , though.

Code that violates the aforementioned guarantees is not C++ code, and likewise conforming C++ compilers will never emit code that violates those guarantees.

The case you are referring to is not a race condition. Let's get back again your question:

Will there ever be a time when short-circuit evaluation is violated, and bar is called despite foo returning true, or even worse, bar is called before foo is called, perhaps because of multi-threading?

I guess the right question would be:

Will there ever be a time when bar is called AFTER foo is called with false result, but while it's called foo will return true if will be called again ?

Ok, let's do some code with potential race condition state.

#define BUFFER_SIZE 0x1000
char globalBuffer[BUFFER_SIZE];

bool foo() { // have user an access to the path_to_file file?
   return access(path_to_file, 0666) != 0; // path_to_file declared somewhere
}

bool bar() {
    FILE *file = fopen(path_to_file, "r");
    if (file == NULL) return false;
    char *ptr = gfets(globalBuffer, BUFFER_SIZE, file);
    if (ptr == NULL) return false;
    return true;
}
...
result = foo() || bar(); // if foo is false, then user have an access
printf("%s", globalBuffer);

Let's imagine that we can control path_to_file.

Race condition will be occuried, if we make an infinite loop like this

#!/bin/bash

while :
do
    ln -s /path/to/good/access/file /path/to/file
    rm -f /path/to/file
    ln -s /etc/shadow /path/to/file
done

After some attempts, if your application has suid bit - I will read contents of the /etc/shadow

But let's return back to your question:

No, no way. If foo will return true, bar not will be called. Even from other thread, if you have multi-threading. Each thread has its own registers, own stack. So, in case of two function calls in the same expression you should beleive to the c++ standard. But it doesn't mean that you code is safe. And race condition one of 100 possible problems.

Note that C++ supports operator overloading, but that short circuiting is a feature only of builtin operators (pre c++17), not user provided overloads.

Ie in all current versions of C++ it is unsafe to assume that short circuiting will occur unless you know both types and know that no free function operator|| is shadowing.

See for p0145 for how this is changing in c++17.

(bold is additions, italics removal)

Change paragraph 5/2 as follows: [ Note: Operators can be overloaded, that is, given meaning when applied to expressions of class type (Clause 9) or enumeration type (7.2). Uses of overloaded operators are transformed into function calls as described in 13.5. Overloaded operators obey the rules for syntax and evaluation order specified in Clause 5, but the requirements of operand type*,* and value category, and evaluation order are replaced by the rules for function call. Relations between operators, such as ++a meaning a+=1, are not guaranteed for overloaded operators (13.5), and are not guaranteed for operands of type bool. —end note ]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM