Created on 2020-05-27 07:41 by Devin Jeanpierre, last changed 2022-04-11 14:59 by admin. This issue is now closed.
`hmac.compare_digest` (via `_tscmp`) does not mark the accumulator variable `result` as volatile, which means that the compiler is allowed to short-circuit the comparison loop as long as it still reads from both strings.
In particular, when `result` is non-volatile, the compiler is allowed to change the loop from this:
```c
for (i=0; i < length; i++) {
result |= *left++ ^ *right++;
}
return (result == 0);
```
into (the moral equivalent of) this:
```c
for (i=0; i < length; i++) {
result |= *left++ ^ *right++;
if (result) {
for (; ++i < length;) {
*left++; *right++;
}
return 1;
}
}
return (result == 0);
```
(Code not tested.)
This might not seem like much, but it cuts out almost all of the data dependencies between `result`, `left`, and `right`, which in theory would free the CPU to race ahead using out of order execution -- it could execute code that depends on the result of `_tscmp`, even while `_tscmp` is still performing the volatile reads. (I have not actually benchmarked this. :)) In other words, this weird short circuiting could still actually improve performance. That, in turn, means that it would break constant-time guarantees.
(This is different from saying that it _would_ increase performance, but marking it volatile removes the worry.)
(Prior art/discussion: https://github.com/google/tink/commit/335291c42eecf29fca3d85fed6179d11287d253e )
I propose two changes, one trivial, and one that's more invasive:
1) Make `result` a `volatile unsigned char` instead of `unsigned char`.
2) When SSL is available, instead use `CRYPTO_memcmp` from OpenSSL/BoringSSL. We are, in effect, "rolling our own crypto". The SSL libraries are more strictly audited for timing issues, down to actually checking the generated machine code. As tools improve, those libraries will grow to use those tools. If we use their functions, we get the benefit of those audits and improvements.
+1 for both of these suggestions
Christian - Devin could likely use some help with the build/ifdef plumbing required for (2) to use CRYPTO_memcmp from Modules/_operator.c when OpenSSL is available.
GPS, I got you covered :) CRYPTO_memcmp() was on my TODO list for a while. Thanks for nagging me. _operator is a built-in module. I don't want to add libcrypto dependency to libpython. I copied the code, made some adjustments and added it to _hashopenssl.c.
Greg, is GH-20456 a bug fix / security enhancement or a new feature? I'm hesitant to backport it to 3.7 and 3.8. 3.9 might be ok.
I'd feel fine doing that for 3.9 given 3.9.0 is only in beta and this changes no public APIs. For 3.8 and 3.7 i wouldn't. Be sure to update the versionchanged in the docs if you choose to do it for 3.9.
New changeset db5aed931f8a617f7b63e773f62db468fe9c5ca1 by Christian Heimes in branch 'master': bpo-40791: Use CRYPTO_memcmp() for compare_digest (#20456) https://github.com/python/cpython/commit/db5aed931f8a617f7b63e773f62db468fe9c5ca1
New changeset 8183e11d87388e4e44e3242c42085b87a878f781 by Christian Heimes in branch '3.9': [3.9] bpo-40791: Use CRYPTO_memcmp() for compare_digest (GH-20456) (GH-20461) https://github.com/python/cpython/commit/8183e11d87388e4e44e3242c42085b87a878f781
New changeset 31729366e2bc09632e78f3896dbce0ae64914f28 by Devin Jeanpierre in branch 'master': bpo-40791: Make compare_digest more constant-time. (GH-20444) https://github.com/python/cpython/commit/31729366e2bc09632e78f3896dbce0ae64914f28
New changeset 97136d71a78a4b6b816f7e14acc52be426efcb6f by Miss Islington (bot) in branch '3.8': bpo-40791: Make compare_digest more constant-time. (GH-20444) https://github.com/python/cpython/commit/97136d71a78a4b6b816f7e14acc52be426efcb6f
New changeset c1bbca5b004b3f74d240ef8a76ff445cc1a27efb by Miss Islington (bot) in branch '3.9': bpo-40791: Make compare_digest more constant-time. (GH-20444) https://github.com/python/cpython/commit/c1bbca5b004b3f74d240ef8a76ff445cc1a27efb
New changeset db95802bdfac4d13db3e2a391ec7b9e2f8d92dbe by Miss Islington (bot) in branch '3.7': bpo-40791: Make compare_digest more constant-time. (GH-23438) https://github.com/python/cpython/commit/db95802bdfac4d13db3e2a391ec7b9e2f8d92dbe
Any reason this wasn't backported to 3.6? FWICS it's supposed to be security supported still.
New changeset 8bef9ebb1b88cfa4b2a38b93fe4ea22015d8254a by Miss Islington (bot) in branch '3.6': bpo-40791: Make compare_digest more constant-time. (GH-23438) (GH-23767) https://github.com/python/cpython/commit/8bef9ebb1b88cfa4b2a38b93fe4ea22015d8254a
> Any reason this wasn't backported to 3.6? Just an oversight. Thanks for pointing it out.
messages: + msg370094
type: security