Project

General

Profile

Bug #10471

ld(1) amd64 LD->LE TLS transition causes memory corruption

Added by Rich Lowe 10 months ago. Updated 9 months ago.

Status:
Closed
Priority:
Normal
Assignee:
Category:
tools - gate/build tools
Start date:
2019-02-27
Due date:
% Done:

100%

Estimated time:
Difficulty:
Medium
Tags:
needs-triage

Description

Memory corruption was observed in libsodium's salsa20 implementation.
After some investigation, we found the thread-local state of the rng was
being corrupted.

In a structure:

typedef struct Salsa20Random_ {
    int           initialized;
    size_t        rnd32_outleft;
    unsigned char key[crypto_stream_salsa20_KEYBYTES];
    unsigned char rnd32[16U * SALSA20_RANDOM_BLOCK_SIZE];
    uint64_t      nonce;
} Salsa20Random;

We found we were overwriting rnd32_outleft with a huge value, investigation
found that value was actually (part of) the key.

Disassembly showed:

stream.nonce = sodium_hrtime();

  call   -0x1d26  <PLT=libc.so.1`gettimeofday>
  testl  %eax,%eax
  jne    -0x22    <randombytes_salsa20_random_stir+0x140>
  imulq  $0xf4240,-0xd0(%rbp),%rax
  addq   -0xc8(%rbp),%rax
  movq   %rax,0xfffffffffffffbc0(%rbx)

memset(stream.rnd32, 0, sizeof stream.rnd32);
stream.rnd32_outleft = (size_t) 0U;

   leaq   0xfffffffffffffbc0(%rax),%rdi
   movq   %rax,%rbx
   xorl   %eax,%eax
   movq   $0x0,0xfffffffffffffbc0(%rbx)
   testl  %edx,%edx
   repz stosq %rax,(%rdi)

It is immediately and incredibly suspicious that all of these address calculations are using
the same offset, despite referring to different members!

Investigating the generated asm we find:

memset(stream.rnd32, 0, sizeof stream.rnd32);
stream.rnd32_outleft = (size_t) 0U;

    movl    $128, %ecx
    leaq    48+stream@dtpoff(%rax), %rdi
    movq    %rax, %rbx
    xorl    %eax, %eax
    movq    $0, 8+stream@dtpoff(%rbx)
    testl    %edx, %edx
    rep stosq

Offsets intact.

Looking at how this assembles, we find the offsets are placed into the addend
of the dtpoff relocation:

  R_AMD64_DTPOFF32                       0xee               0x30  .rela.text     stream
  R_AMD64_DTPOFF32                       0xfa                0x8  .rela.text     stream

The reason they're disappearing is, in hindsight, painfully obvious. When the
link-editor transitions a DTPOFF32 relocation to TPOFF32 (from local-dynamic
to local-executable), it explicitly 0s the addend!

    case R_AMD64_DTPOFF32:
        /*
         * LD->LE
         *
         * Transition:
         *    0x00 leaq x1@dtpoff(%rax), %rcx
         * To:
         *    0x00 leaq x1@tpoff(%rax), %rcx
         */
        DBG_CALL(Dbg_reloc_transition(ofl->ofl_lml, M_MACH,
            R_AMD64_TPOFF32, arsp, ld_reloc_sym_name));
        arsp->rel_rtype = R_AMD64_TPOFF32;
        arsp->rel_raddend = 0;
        return (FIX_RELOC);

It must stop.

History

#1

Updated by Electric Monk 9 months ago

  • % Done changed from 0 to 100
  • Status changed from In Progress to Closed

git commit 3c30f56df862431210f98c7c8f13cf1362b9c574

commit  3c30f56df862431210f98c7c8f13cf1362b9c574
Author: Richard Lowe <richlowe@richlowe.net>
Date:   2019-03-06T23:07:45.000Z

    10471 ld(1) amd64 LD->LE TLS transition causes memory corruption
    Reviewed by: Jason King <jason.king@joyent.com>
    Reviewed by: Toomas Soome <tsoome@me.com>
    Approved by: Robert Mustacchi <rm@joyent.com>

Also available in: Atom PDF