Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upPrevent the compiler from converting "secure" memcmp() into canonical one. #102
Conversation
richsalz
added
the
reviewed
label
Jan 21, 2016
This comment has been minimized.
This comment has been minimized.
|
+1 |
briansmith
referenced this pull request
Jan 22, 2016
Open
Improve constant-time comparison `CRYPTO_memcmp` #95
This comment has been minimized.
This comment has been minimized.
|
So I wonder if this problem returns if we change |
This comment has been minimized.
This comment has been minimized.
|
@kroeckx Do you mean the new code or the original code? |
This comment has been minimized.
This comment has been minimized.
|
The new code.
|
This comment has been minimized.
This comment has been minimized.
briansmith
commented on crypto/cryptlib.c in ad0c467
Jan 25, 2016
|
Why does |
This comment has been minimized.
This comment has been minimized.
|
The compiler is not allowed to change the behavior of code if that change affects sequence of I/O library calls and reads/writes to volatile variables. This change doesn't strictly guarantee that those variables are treated as volatile (because original variables are not necessarily declared as volatile) but current compilers treat |
This comment has been minimized.
This comment has been minimized.
|
@kroeckx Such change should not affect the new code. If the compiler treats |
This comment has been minimized.
This comment has been minimized.
|
This comment has been minimized.
This comment has been minimized.
|
@briansmith Nice!!! Here's what I found so far:
So this is a bug in gcc which must be fixed. I'd say it's a serious bug because we have no idea what exactly causes it and so it may surface again later. However at this moment its impact isn't large. |
This comment has been minimized.
This comment has been minimized.
nicowilliams
commented
Jan 26, 2016
|
@briansmith Careful! You didn't have the same source code in both cases. In the first case you had this:
while in the second case you had this:
Subtle -but huge- difference. |
This comment has been minimized.
This comment has been minimized.
nicowilliams
commented
Jan 26, 2016
|
And this is the difference:
So it seems the case has to be to |
nicowilliams
reviewed
Jan 26, 2016
| /* Must use "pointers to volatile" to tell the compiler to never | ||
| * attempt optimizing the reads. Otherwise the compiler is allowed | ||
| * to use LTO and convert this function into canonical version. */ | ||
| const volatile unsigned char *a = in_a; |
This comment has been minimized.
This comment has been minimized.
nicowilliams
Jan 26, 2016
Evidently this should be const volatile unsigned char * volatile a = in_a; and const volatile unsigned char * volatile b = in_b;.
EDIT: but only because that is a workaround to known gcc bugs. Performance-wise it's best as you have it, const volatile unsigned char *a = in_a;.
This comment has been minimized.
This comment has been minimized.
Dmitry-Me
Jan 27, 2016
Author
Contributor
- This makes code larger and much slower.
- It doesn't actually make it any better. If memory pointed to wasn't volatile originally then it isn't any more volatile with this pointer declaration. The compiler is required to preserve the sequence of reads and writes for the pointer itself but not for the pointed to memory.
This comment has been minimized.
This comment has been minimized.
nicowilliams
Jan 27, 2016
@Dmitry-Me Yes, it makes it slower. That's kinda the point. It makes it slower still than we'd like, but I don't see how to fix this in a C-coded memcmp_s(). As to point (2), I see that the spec makes accesses to volatile objects through non-volatile pointers undefined, but not the opposite, and it makes no reference to casts, so my reading is that the cast to a volatile-qualified type has to be honored.
This comment has been minimized.
This comment has been minimized.
nicowilliams
commented
Jan 26, 2016
|
I'd not know about gcc.godbolt.org. Very nice. I especially like the colorize option: EDIT: Wrong link. Also, gcc.godbolt.org has a shortened permalink option. Lastly, it seems that it doesn't update the window location as you change the source and options, but the permalink button does capture the latest source options. |
This comment has been minimized.
This comment has been minimized.
IMO, that is almost the opposite conclusion to draw. The correct conclusion is that we can't rely on tricks with These examples came from the thread on Twitter here: https://twitter.com/BRIAN_____/status/691580542207160321. The conclusions from that thread were:
John's code (the second example) works, however I don't consider it reliable enough to rely on. |
This comment has been minimized.
This comment has been minimized.
|
My other concern with it is that I don't think the C standard guarantees that it needs to read everything if it knows the result already, and so could in theory check that x != 0 and break in that case. |
This comment has been minimized.
This comment has been minimized.
nicowilliams
commented
Jan 27, 2016
|
@briansmith That's true, but nonetheless one has to do something. In the shortest term using volatile this way works for many compilers. In the medium term the alternative is assembly-coding such functions, but that takes more time. In the longer term we need the next C standard to do something useful here. |
This comment has been minimized.
This comment has been minimized.
|
@briansmith This is not even a bug in gcc. Have you noted that the array must have size 1, 2 or 4? That makes it eligible for being allocated in a register. That's why there're no writes - no memory to overwrite in the first place. If you insert a |
This comment has been minimized.
This comment has been minimized.
|
@kroeckx If the compiler decides to treat memory pointed to by |
This comment has been minimized.
This comment has been minimized.
I agree 100%.
Good point.
I'm not convinced on this point. However, I don't want perfect to be the enemy of the good. |
This comment has been minimized.
This comment has been minimized.
|
@briansmith How about this code?
The following is emitted:
My point is that your original code which only declares an array and then overwrites it and then the array is never accessed doesn't really need much protection because how would that array happen to contain any sensitive data? |
This comment has been minimized.
This comment has been minimized.
nicowilliams
commented
Jan 27, 2016
|
@briansmith Looking at N1570 (so we all can, and accepting that that's just almost-C11 while OpenSSL can't use C11 yet) the spec specifically says (
I believe this means that the compiler has to honor the cast to Note that even if the object is automatic and small, the compiler would have to honor the |
This comment has been minimized.
This comment has been minimized.
nicowilliams
commented
Jan 27, 2016
|
I can also imagine a very aggressive LTO that optimizes away calls to assembly-coded The bottom-line is that this sort of change should be accepted, though assembly-coded implementations would be better in the longer term. |
This comment has been minimized.
This comment has been minimized.
nicowilliams
commented
Jan 27, 2016
|
Oh, and perhaps these functions' prototypes should have pointer to volatile-qualified objects as arguments, forcing the cast at the call sites precisely to disable aggressive LTO. |
This comment has been minimized.
This comment has been minimized.
|
@nicowilliams The spec talks about accessing an "object defined with a volatile-qualified type" a.k.a. "volatile object". That is, all the requirements on "volatile" only apply when the original object being pointed at was qualified
Exactly. That means you can't rely on it to work any particular way.
In the same document, you can read the definition of
I agree. |
This comment has been minimized.
This comment has been minimized.
|
So has everyone come to consensus here? What should openssl do? |
This comment has been minimized.
This comment has been minimized.
nicowilliams
commented
Jan 27, 2016
|
@richsalz The proposed patch can't make things worse, and does make things better for at least some compilers. The consensus is to take this sort of patch in the short-term; longer-term an assembly-coded function might be better. I do believe it would be more prudent to have the prototype declare the pointer arguments as pointing to volatile, but that can be left for another day. @briansmith I believe you're misreading the spec. Semantics point 6 refers to access to a volatile-qualified type object via non-volatile-qualified pointer types (but not the reverse) and talks about "defined", but semantics point 7 is the one that we really care about and it doesn't say or imply that the object must have been defined as volatile. It makes sense that point 6 refers to the object having been defined as volatile... After all, consider this contrived case:
There the object was defined as non-volatile, therefore accessing it via
But also, if C was as you say, then one could not use |
This comment has been minimized.
This comment has been minimized.
nicowilliams
commented
Jan 27, 2016
|
@richsalz @briansmith The spec says (6.3.2.3, point 2):
In other words, casting a pointer to non-volatile type to a pointer to volatile type works. The behavior where the pointer itself has to also be volatile is just a gcc bug. |
nicowilliams
reviewed
Jan 27, 2016
| @@ -401,8 +401,11 @@ void *OPENSSL_stderr(void) { return stderr; } | |||
| int CRYPTO_memcmp(const void *in_a, const void *in_b, size_t len) | |||
This comment has been minimized.
This comment has been minimized.
nicowilliams
Jan 27, 2016
The prototype needs to read:
int CRYPTO_memcmp(const volatile void *in_a, const volatile void *in_b, size_t len)
in order to defeat possibly very aggressive LTO.
This comment has been minimized.
This comment has been minimized.
|
@nicowilliams It's not even a bug in gcc in terms of "gcc deliberately failing to emit writes and refusing to overwrite memory". In this specific case (an array fits into a register and its address is not passed anywhere) the whole array is allocated in a register, so there's no memory which could have been overwritten. |
This comment has been minimized.
This comment has been minimized.
nicowilliams
commented
Jan 28, 2016
|
@Dmitry-Me The array's address is most certainly passed: because the array is passed to a function, the passed value decays to a pointer. That it fits in a register is irrelevant. That the function is inlined is also irrelevant. The pointer was declared as pointing to a volatile object, therefore the memory accesses should have been performed, therefore it was a gcc bug. |
This comment has been minimized.
This comment has been minimized.
|
@nicowilliams Here's my guess of what happens. First the function is inlined, then the compiler sees that the variable fits into a register and also all operations can be carried out without allocating memory, then it decides that no code needs to be emitted. |
This comment has been minimized.
This comment has been minimized.
nicowilliams
commented
Jan 29, 2016
|
@Dmitry-Me That's a good guess, but nonetheless that would be a compiler bug, as inlining shouldn't change the arrays-decay-to-pointers semantics, and therefore the pointer-to-volatile conversion had to be honored. We know how to work around the bug: declare the object and the pointer volatile in the function's prototype, then assign those arguments to a non-volatile pointer to volatile in the function. For some compilers declaring the argument pointer volatile itself has not impact when the argument is passed via registers anyways, but it does disable the gcc bug in question. http://goo.gl/WdOoti -- all compilers in their list don't optimize away this
Now, because these compiler bugs have happened and could be worse in other compilers, it'd be nice if OpenSSL could test this at build time and warn. Constructing such a test will be fun. |
This comment has been minimized.
This comment has been minimized.
|
So in master and 1.0.2 we make the parameters void; see 98ab576 for example. Closing this. If there's more to do, please open a new PR. :) |
richsalz
closed this
Jan 30, 2016
This comment has been minimized.
This comment has been minimized.
nicowilliams
commented
Jan 30, 2016
|
@richsalz Making the pointer itself in the prototype would work around that GCC bug. Otherwise it LGTM. |
This comment has been minimized.
This comment has been minimized.
|
sorry, i don't know what you mean? and my comment about making "the parameters void" should have been "make them volatile" :) |
This comment has been minimized.
This comment has been minimized.
|
There is no GCC bug. There is a lot of not understanding what |
This comment has been minimized.
This comment has been minimized.
nicowilliams
commented
Jan 30, 2016
|
@richsalz Making the prototype this:
works around a GCC bug and doesn't break with the other compilers. The |
This comment has been minimized.
This comment has been minimized.
|
And then the body doesn't match the prototype? I don't like that. |
This comment has been minimized.
This comment has been minimized.
nicowilliams
commented
Jan 31, 2016
|
The function's definition and declaration have to match, naturally. The arguments should be pointers to volatile, and they should be volatile pointers to volatile only to work around that gcc bug. The pointers |
This comment has been minimized.
This comment has been minimized.
|
your initial comment confused me. submitting for review, thanks. |
This comment has been minimized.
This comment has been minimized.
|
commit 769adcf thanks! |
This comment has been minimized.
This comment has been minimized.
nicowilliams
commented
Jan 31, 2016
|
Excellent! |
This comment has been minimized.
This comment has been minimized.
|
Well, not really excellent. Code looks like magic an isn't proper commented. Which means it'll be broken sooner or later. |
This comment has been minimized.
This comment has been minimized.
Ok? |
This comment has been minimized.
This comment has been minimized.
|
@richsalz Not even close to OK. I'll submit a PR in a day or two. |
This comment has been minimized.
This comment has been minimized.
|
Really? Shrug, okay, look forward to seeing it. |
Dmitry-Me commentedMay 5, 2014
Current implementation of "secure" memcmp() is not that secure - the compiler can use LTO and see that the calling code only tests for zero values and then convert it into canonical, not "secure" version.