Unofficial patch for the Apple SSL/TLS bug in SecureTransport 55741 (CVE-2014-1266)

Please read our main article, Anatomy of a "goto fail" - Apple's SSL bug explained, plus an unofficial patch! before going any further.

Note that you must have Apple's developer tools (Xcode) installed before you proceed, because you need to use the codesign utility to authorise your patched version of the SecureTransport system library.

The patched file will be signed "ad-hoc," so it can ony be used on your Mac.

So if you try it, and it works, please don't redistribute your modified file, for licensing and safety reasons.

What we're patching is the double-goto-fail bug in sslKeyExchange.c.

This bug is also known as CVE-2014-1266:

 . .
hashOut.data = hashes + SSL_MD5_DIGEST_LEN;
hashOut.length = SSL_SHA1_DIGEST_LEN;
if ((err = SSLFreeBuffer(&hashCtx)) != 0)
    goto fail;
if ((err = ReadyHash(&SSLHashSHA1, &hashCtx)) != 0)
    goto fail;
if ((err = SSLHashSHA1.update(&hashCtx, &clientRandom)) != 0)
    goto fail;
if ((err = SSLHashSHA1.update(&hashCtx, &serverRandom)) != 0)
    goto fail;
if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
    goto fail;
    goto fail;  /* MISTAKE! THIS LINE SHOULD NOT BE HERE */
if ((err = SSLHashSHA1.final(&hashCtx, &hashOut)) != 0)
    goto fail;

err = sslRawVerify(...);
. . .

The double-goto-fail code in Apple's library compiles to the following assembler code:

086c39 7561            jnz    fail          
086c3b 4c8d3d860f1e00  lea    r15, [rip+_SSLHashSHA1]
086c42 488db538ffffff  lea    rsi, [rbp-0xc8]  
086c49 4c89ff          mov    rdi, r15         
086c4c e833a1fcff      call   _ReadyHash         
086c51 89c3            mov    ebx, eax         
086c53 85db            test   ebx, ebx         
086c55 7545            jnz    fail          
086c57 4d8b7718        mov    r14, [r15+0x18]  
086c5b 488dbd38ffffff  lea    rdi, [rbp-0xc8]  
086c62 488db528ffffff  lea    rsi, [rbp-0xd8]  
086c69 41ffd6          call   r14              
086c6c 89c3            mov    ebx, eax         
086c6e 85db            test   ebx, ebx         
086c70 752a            jnz    fail          
086c72 488dbd38ffffff  lea    rdi, [rbp-0xc8]  
086c79 488db518ffffff  lea    rsi, [rbp-0xe8]  
086c80 41ffd6          call   r14              
086c83 89c3            mov    ebx, eax         
086c85 85db            test   ebx, ebx         
086c87 7513            jnz    fail          
086c89 488dbd38ffffff  lea    rdi, [rbp-0xc8]  
086c90 488db558ffffff  lea    rsi, [rbp-0xa8]  
086c97 41ffd6          call   r14              
086c9a 89c3            mov    ebx, eax               
086c9c 488dbd08ffffff  lea    rdi, [rbp-0xf8]  
086ca3 e8d875fcff      call   _SSLFreeBuffer 

The snippets that do this:

call   r14       ; Call HashSHA1.update()         
mov    ebx, eax  ; Put return value in EBX        
test   ebx, ebx  ; Test for EBX non zero (i.e. error)
jnz    fail      ; If so, goto fail

are followed by the buggy one, which you might have thought would compile something like this:

call   r14       ; Call HashSHA1.update()         
mov    ebx, eax  ; Put return value in EBX        
test   ebx, ebx  ; Test for EBX non zero (i.e. error)
jnz    fail      ; If so, goto fail
jmp    fail      ; And then wrongly goto fail anyway

If that were the case, we could just patch away the incorrect jump, and life would be good.

But compiler optimisation has given us:

call   r14             ; Call HashSHA1.update()
mov    ebx, eax        ; Get return value - no check needed 
lea    rdi, [rbp-0xf8] ; And prepare to exit regardless 
call   _SSLFreeBuffer

The code that the incorrect goto fail skipped over has been optimised away, so there is no call to the HashSHA1.final() function, and so no easy way to call the sslRawVerify() code at all.

In short, the code can't be patched to work properly, but it can be patched to fail safely all the time, in contrast with its current tendency to fail wrongly.

We'll change the buggy code by "borrowing" the call r14 instruction to produce:

mov    ebx, 0x1         
lea    rdi, [rbp-0xf8]  
call   _SSLFreeBuffer    

The function now always fails with a non-zero result (I chose the value 1), whether there was a real error or merely the error caused by the bug.

So, entirely for academic interest (please don't try this at home!), you could do this:

Now change the following bytes in the copy of the library file you just made (I used the free Hex Fiend binary editor):

That should change the code in the library as follows:

Now add your own digital signature to the patched file so OS X will accept it, and copy the patched-and-signed file into place in the /System directory:

Reboot, and you should have the new, hackily-patched library in place.

The codesign step above signs the hacked library with an "ad-hoc" signature, meaning that it will work on your Mac only.

That's just as well.

You can't spread this untested fix to others: they have to take the steps for themselves.

To undo the change if it doesn't work, copy the Security.­original.­in-case back over the patched version of Security and reboot once more.

For a "before-and-after" test of the patch, visit SSL expert Adam Langley's excellent test site https://www.imperialviolet.org:1266/ before you patch:

Then try again after you patch; Safari should be stopped short by the error that is now correctly reported by SecureTransport:

Remember, however: this patch is unvalidated, unwarranted and not recommended for actual use.

It exists:

  • For fun.
  • To suggest that emergency "fixes" don't always fix, but often can only work around problems.
  • To show what C code looks like when compiled to assembler.
  • To give some insight into how unauthorised hacks, for good and bad, can be achieved.
  • To introduce the OS X codesign utility and Apple's code signing protection.

Enjoy.