Mitigate Timing Attacks On Basic RPC Authorization #2845

pull grayleonard wants to merge 3 commits into bitcoin:master from grayleonard:patch-1 changing 1 files +13 −1
  1. grayleonard commented at 12:39 AM on July 23, 2013: none

    As per #2838 . Eliminates the possibility of timing attacks by changing the way the two passwords are compared. It iterates through each char in the strings, and if the two chars it is comparing aren't the same, then it adds 1 to nReturn and the function, once it's done comparing all the chars, will return false. Previously, the function would return false on the first char that didn't match, allowing a possible attacker to run a timing attack.

    See http://rdist.root.org/2010/01/07/timing-independent-array-comparison/ for reference.

  2. Mitigate Timing Attacks On Basic RPC Authorization
    As per #2838 . Eliminates the possibility of timing attacks by changing the way the two passwords are compared. See http://rdist.root.org/2010/01/07/timing-independent-array-comparison/ for reference.
    
    It iterates through each char in the strings, and if the two chars it is comparing aren't the same, then it adds 1 to nReturn and the function returns false. Previously, the function would return false on the first char that didn't match, allowing a possible attacker to run a timing attack.
    5d3228217c
  3. gmaxwell commented at 12:51 AM on July 23, 2013: contributor

    Can you also make the short password delay into an unconditional delay on failure?

    Your current code timing-leaks the length, but I don't know if I care. You could avoid this by only comparing the input number of characters every single time, with a min() on the offset (take care to avoid a fence post error) on the actual password. Then compare the lengths.

  4. grayleonard closed this on Jul 23, 2013

  5. grayleonard reopened this on Jul 23, 2013

  6. grayleonard commented at 1:04 AM on July 23, 2013: none

    Woops, didn't mean to close it. And I was thinking along the same lines, but with something a little simpler. If the lengths don't match, you can just compare the actual password with itself (to get the timing right), but add 1 to nReturn also so it returns false.

  7. gmaxwell commented at 1:06 AM on July 23, 2013: contributor

    The only reservation I have with that is that, depending on how you write it, it is code that the optimizer is very likely to optimize out. E.g. if it were if(len1==len2){} else {selfcompare} that will quite probably get optimized.

  8. Updated pull
    Fix to the timing-leak for the length of the password.
    bd8420dda7
  9. in src/bitcoinrpc.cpp:None in 5d3228217c outdated
     478 |      string strUserPass64 = strAuth.substr(6); boost::trim(strUserPass64);
     479 |      string strUserPass = DecodeBase64(strUserPass64);
     480 | -    return strUserPass == strRPCUserColonPass;
     481 | +
     482 | +    //Begin constant-time comparison
     483 | +    if (strUserPass.length() != strRPCUserColonPass.length())
    


    sipa commented at 1:13 AM on July 23, 2013:

    This means (theoretically) exposing the password length. If you go as far as making the comparison constant-time, maybe go all the way? Something like comparing x.at(i % x.size) ^ y.at(i % y.size). (not a perfect solution, as it will match in case one is a repetition of the other).


    gmaxwell commented at 1:17 AM on July 23, 2013:

    The % test (I would have just min()) is fine if you just add a |= len==len at the end.

  10. grayleonard commented at 1:18 AM on July 23, 2013: none

    I updated it to fix the length-leaking, seems like the simplest way to do it.

  11. gmaxwell commented at 2:12 AM on July 23, 2013: contributor

    @grayleonard The extra loop with the "return ++nResult == 0;" is ... a little perplexing. The % that sipa proposed (or the min) should actually result in simpler looking code. Just move the length check to the end, and use the % to make sure that both only access valid indexes. Care to give it a shot?

  12. theuni commented at 2:19 AM on July 23, 2013: member

    There's no telling what compilers will do to this. If you're that concerned about timing attacks, why not just do something like this pseudocode?

    const minwait = 50; //msec
    timeBefore = GetCurrentTime();
    result = val1 == val2;
    sleep(minwait - (GetCurrentTime() - timeBefore));
    return result;
    
  13. BitcoinPullTester commented at 2:19 AM on July 23, 2013: none

    Automatic sanity-testing: PASSED, see http://jenkins.bluematt.me/pull-tester/bd8420dda743c36940d3986fb7e81a2f195495f8 for binaries and test log. This test script verifies pulls every time they are updated. It, however, dies sometimes and fails to test properly. If you are waiting on a test, please check timestamps to verify that the test.log is moving at http://jenkins.bluematt.me/pull-tester/current/ Contact BlueMatt on freenode if something looks broken.

  14. gmaxwell commented at 2:22 AM on July 23, 2013: contributor

    Jeff argues that we should probably just drop basic auth entirely and require digest auth, which would solve this as a side effect.

  15. gmaxwell commented at 2:35 AM on July 23, 2013: contributor

    @theuni we're deep in pedantry land, but expecting very high accuracy sleeps results in sadness. It's perfectly possible to leak data through a sleep like that.

  16. theuni commented at 2:40 AM on July 23, 2013: member

    @gmaxwell fair point on pedantry, but if you're on a system with <50msec sleep precision, i'd guess you'd have bigger concerns. In this case, it'd likely even spoil the very attack the evildoer is attempting.

  17. jgarzik commented at 2:43 AM on July 23, 2013: contributor

    Also -1 on convoluted schemes that the compiler might try to micro-optimize, or might impact the authentication result.

    Heck, even unconditionally sleeping for a random interval would be better. Remove the 'if' check on password size.

    But yes, approaching 1.0 it is reasonable just to require Digest auth.

  18. grayleonard commented at 5:47 AM on July 23, 2013: none

    @theuni From what I've read it seems like there are two ways to mitigate timing attacks - one can hold all responses with a constant delay, say 50ms. The other returns faster, I've seen ~10ms in this instance from tests I've run, with a constant-time comparison algorithm. Either one works.

    I'm not sure how soon 1.0 is going to be available, but if we are treating this as a vulnerability instead of a bug it's important we get it out as soon as possible, regardless of the technique we use.

  19. (Hopefully) the last update to this pull request
    Simplified time-leaking solution, using min() to make sure only valid indexes are called.
    351229cfc0
  20. BitcoinPullTester commented at 3:19 PM on July 23, 2013: none

    Automatic sanity-testing: PASSED, see http://jenkins.bluematt.me/pull-tester/351229cfc0d0f6c54af8e4d7ac7426c16e776fb1 for binaries and test log. This test script verifies pulls every time they are updated. It, however, dies sometimes and fails to test properly. If you are waiting on a test, please check timestamps to verify that the test.log is moving at http://jenkins.bluematt.me/pull-tester/current/ Contact BlueMatt on freenode if something looks broken.

  21. in src/bitcoinrpc.cpp:None in 351229cfc0
     478 |      string strUserPass64 = strAuth.substr(6); boost::trim(strUserPass64);
     479 |      string strUserPass = DecodeBase64(strUserPass64);
     480 | -    return strUserPass == strRPCUserColonPass;
     481 | +
     482 | +    //Begin constant-time comparison
     483 | +    //XOR chars in each password together, and then adds either 0 or 1 (0 if they match) to nResult
    


    gavinandresen commented at 9:07 AM on August 5, 2013:

    comment incorrect now? Adds 1 if they do not match, does not add 0 if they do. Maybe: nResult += (length == length ? 1 : 2) ... and return nResult == 1; ... to get really-and-truly-ought-to-be-constant-time...

    Also, commits need to be squashed into one.

    Also style nitpick, space after for/if: for (size_t ...) and if (strUser...)

  22. sipa commented at 10:02 PM on August 15, 2013: member

    Superceded by #2886

  23. sipa closed this on Aug 15, 2013

  24. IntegralTeam referenced this in commit 1ba8694cd7 on Jun 4, 2019
  25. DrahtBot locked this on Sep 8, 2021

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-05-03 15:15 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me