Bookix
Bookix

Reputation: 115

Compiler behavior?

I am reviewing some source code and I was wondering if the following was thread safe? I have heard of compiler or CPU instruction/read reordering (would it have something to do with branch prediction?) and the Data->unsafe_variable variable below can be modified at any time by another thread.

My question is: depending on how the compiler/CPU reorder read/writes, would it be possible that the below code would allow the Data->unsafe_variable to be fetched twice? (see 2nd snippet)

Note: I do not worry about the first access, any data can be there as long as it does not pass the 'if', I am just concerned by the possibility that the data would be fetched another time after the 'if'. I was also wondering if the cast into volatile here would help preventing a double fetch?

int function(void* Data) {

    // Data is allocated on the heap
    // What it contains at this point is not important
    size_t _varSize = ((volatile DATA *)Data)->unsafe_variable;

    if (_varSize > x * y)
    {
        return FALSE;
    }

    // I do not want Data->unsafe_variable to be fetch once this point reached,
    // I want to use the value "supposedly" stored in _varSize
    // Would any compiler/CPU reordering would allow it to be double fetched?

    size_t size = _varSize - t * q;

    function_xy(size);

    return TRUE;
}

Basically I do not want the program to behave like this for security reasons:

    _varSize = ((volatile DATA *)Data)->unsafe_variable;
    if (_varSize > x * y)
    {
        return FALSE;
    }

    size_t size = ((volatile DATA *)Data)->unsafe_variable - t * q;
    function10(size);

I am simplifying here and they cannot use mutex. However, would it be safer to use _ReadWriteBarrier() or MemoryBarrier() after the fist line instead of a volatile cast? (VS compiler)

Edit: Giving slightly more context to the code.

Upvotes: 4

Views: 252

Answers (3)

David Schwartz
David Schwartz

Reputation: 182763

The code is broken for many reasons. I'll just point out one of the more subtle ones as others have pointed out the more obvious ones. The object is not volatile. Casting a pointer to a pointer to a volatile object doesn't make the object volatile, it just lies to the compiler.

But there's a much bigger point -- you are going about this totally the wrong way. You are supposed to be checking whether the code is correct, that is, whether it is guaranteed to work. You aren't clever enough, nobody is, to think of every possible way the system might fail to do what you assume it will do. So instead, just don't make those assumptions.

Thinking about things like CPU read re-ordering is totally wrong. You should expect the CPU to do what, and only what, it is required to do. You should definitely not think about specific mechanisms by which it might fail, but only whether it is guaranteed to work.

What you are doing is like trying to figure out if an employee is guaranteed to show up for work by checking if he had his flu shot, checking if he is still alive, and so on. You can't check for, or even think of, every possible way he might fail to show up. So if find that you have to check those kinds of things, then it's not guaranteed and relying on it is broken. Period.

You cannot make reliable code by saying "the CPU doesn't do anything that can break this, so it's okay". You can make reliable code by saying "I make sure my code doesn't rely on anything that isn't guaranteed by the relevant standards."

You are provided with all the tools you need to do the job, including memory barriers, atomic operations, mutexes, and so on. Please use them.

You are not clever enough to think of every way something not guaranteed to work might fail. And you have a plethora of things that are guaranteed to work. Fix this code, and if possible, have a talk with the person who wrote it about using proper synchronization.

This sounds a bit ranty, and I apologize for that. But I've seen too much code that used "tricks" like this that worked perfectly on the test machines but then broke when a new CPU came out, a new compiler, or a new version of the OS. Fixing code like this can be an incredible pain because these hacks hide the actual synchronization requirements. The right answer is almost always to code clearly and precisely what you actually want, rather than to assume that you'll get it because you don't know of any reason you won't.

This is valuable advice from painful experience.

Upvotes: 4

Suma
Suma

Reputation: 34403

The only portable solution for C++ is C++11 atomics, which is available in upcoming VS 2012.

As for C, I do not know if recent C standards bring some portable facilities, I am not following that, but as you are using Visal Studio, it does not matter anyway, as Microsoft is not implementing recent C standards.

Still, if you know you are developing for Visual Studio, you can rely on guarantees provided by this compiler, which apply to both C and C++. Some of them are implicit (accessing volatile variables implies also some memory barriers applied), some are explicit, like using _MemoryBarrier intrinsic.

The whole topic of the memory model is discussed in depth in Lockless Programming Considerations for Xbox 360 and Microsoft Windows, this should give you a good overview. Beware: the topic you are entering is full of hard topics and nasty surprises.

Note: Relying on volatile is not portable, but if you are using old C / C++ standards, there is no portable solution anyway, therefore be prepared to facing the need of reimplementing this for different platform should the need ever arise. When writing portable threaded code, volatile is considered almost useless:

For multi-threaded programming, there two key issues that volatile is often mistakenly thought to address:

  • atomicity

  • memory consistency, i.e. the order of a thread's operations as seen by another thread.

Upvotes: 0

James Kanze
James Kanze

Reputation: 153909

The standard(s) are clear. If any thread may be modifying the object, all accesses, in all threads, must be synchronized, or you have undefined behavior.

Upvotes: 1

Related Questions