Talos Vulnerability Report

TALOS-2019-0801

Nest Labs Openweave Weave tool Print-TLV code execution vulnerability

August 19, 2019
CVE Number

CVE-2019-5038

Summary

An exploitable command execution vulnerability exists in the print-tlv command of Weave tool. A specially crafted weave TLV can trigger a stack-based buffer overflow, resulting in code execution. An attacker can trigger this vulnerability by convincing the user to open a specially crafted Weave command.

Tested Versions

Nest Labs Openweave-core 4.0.2

Product URLs

https://openweave.io/

CVSSv3 Score

7.5 - CVSS:3.0/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H

CWE

CWE-121: Stack-based Buffer Overflow

Details

Openweave is the open-source implementation of the Weave protocol, which was initially developed by Nest Labs in order to provide a Session-Layer agnostic method of communication between their internet-of-things devices. The weave binary is a utility tool that helps with various aspects of interacting with Weave, such as key conversion, cert conversion, and most relevantly, printing out weave TLV's. The weave binary can also be found on Nest devices such as the Nest Cam IQ Indoor camera.

The weave print-tlv command takes an input file and will output a detailed description of the inner TLV as such:

0 0x7f1127b7a007, tag[Fully Qualified (6 Bytes)]: 0x0::0xf::0x1, type: Structure (0x15), container:                                        
1       0x7f1127b7a009, tag[Context Specific]: 0x1, type: Array (0x16), container:                                                         
2               0x7f1127b7a00a, tag[Anonymous]: 0xffffffff, type: Structure (0x15), container:                                             
3                       0x7f1127b7a00d, tag[Context Specific]: 0x1, type: Data (0x10), length: 9, value: 0x7f1127b7a00d                    
3                       0x7f1127b7a019, tag[Context Specific]: 0x2, type: Unsigned Fixed Point (0x04), value: 4                            
3                       0x7f1127b7a01b, tag[Context Specific]: 0x3, type: Path (0x17), container:                                          
4                               0x7f1127b7a025, tag[Context Specific]: 0x13, type: Unsigned Fixed Point (0x04), value: 1780101555471515649 
3                       0x7f1127b7a02c, tag[Context Specific]: 0x4, type: Unsigned Fixed Point (0x04), value: 430515093
3                       0x7f1127b7a032, tag[Context Specific]: 0x5, type: Unsigned Fixed Point (0x04), value: 752009493
3                       0x7f1127b7a034, tag[Context Specific]: 0x6, type: Path (0x17), container:
4                               0x7f1127b7a03e, tag[Context Specific]: 0x13, type: Unsigned Fixed Point (0x04), value: 1780101555471515649
3                       0x7f1127b7a042, tag[Context Specific]: 0x7, type: Unsigned Fixed Point (0x04), value: 2
3                       0x7f1127b7a048, tag[Context Specific]: 0x8, type: Unsigned Fixed Point (0x04), value: 593100821
3                       0x7f1127b7a04b, tag[Context Specific]: 0xa, type: Data (0x10), length: 49, value: 0x7f1127b7a04b

Due to the nature of WeaveTLVs, one can enter a 'structure' with a "\x15" byte and exit it with a "\x18" byte. As seen on the left side of the above output, there is an indicator of how nested the TLV is (i.e. how many containers we have currently entered). The function that handles this nesting is the following:

static WEAVE_ERROR Iterate(TLVReader &aReader, size_t aDepth, IterateHandler aHandler, void *aContext, bool aRecurse)
{
    WEAVE_ERROR retval = WEAVE_NO_ERROR;

    if (aReader.GetType() == kTLVType_NotSpecified)
    {
        retval = aReader.Next();
        SuccessOrExit(retval);
    }

    do
    {
        const TLVType theType = aReader.GetType();

        retval = (aHandler)(aReader, aDepth, aContext); //[1]
        SuccessOrExit(retval);

        if (aRecurse && TLVTypeIsContainer(theType))
        {
            TLVType containerType;

            retval = aReader.EnterContainer(containerType);
            SuccessOrExit(retval);

            retval = Iterate(aReader, aDepth + 1, aHandler, aContext, aRecurse); //[2]
            if (retval != WEAVE_END_OF_TLV)
                SuccessOrExit(retval);

            retval = aReader.ExitContainer(containerType);
            SuccessOrExit(retval);
        }
    } while ((retval = aReader.Next()) == WEAVE_NO_ERROR);

exit:
    return retval;
}

At [1], we can see that the iterator function callback is called, and at [2], we see the Iterate function can actually recurse to itself if we end up entering another container. It's also worth noting that there's not really a limit to how big the aDepth variable can get here. Regardless, for the weave print-tlv binary, the Iterator handler is given as such:

WEAVE_ERROR DumpHandler(const TLVReader &aReader, size_t aDepth, void *aContext)
{
    static const char   tabs[] = "                                                   ";
    char                tabbuf[48]; //[3]
    WEAVE_ERROR         retval = WEAVE_NO_ERROR;
    DumpContext *       context;

    VerifyOrExit(aContext != NULL, retval = WEAVE_ERROR_INVALID_ARGUMENT);

    context = static_cast<DumpContext *>(aContext);

    VerifyOrExit(context->mWriter != NULL, retval = WEAVE_ERROR_INVALID_ARGUMENT);

    strncpy(tabbuf, tabs, aDepth); //[4]

    tabbuf[aDepth] = 0;

    DumpHandler(context->mWriter,
                tabbuf,
                aReader,
                aDepth);

 exit:
    return retval;
}

At [3], we see that that a length 48 buffer for '\t' chars will be written to in order to generate the nested output given before, and at [4], we can see the line responsible for actually writing these tabs. As one might guess, since there's no limit to the aDepth variable, the tab strncpy will quickly start to run out of bounds.

While the source of the strncpy is not user controlled, since the length of the tabs variable is only 0x14 tab chars long, the tabbuf stack variable will get written with 0x14 tabs and then the rest is filled with null bytes, resulting in a stack based buffer overflow of null bytes.

Crash Information

==119438==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffc78f294b0 at pc 0x000000546dff bp 0x7ffc78f29450 sp 0x7ffc78f29448
WRITE of size 1 at 0x7ffc78f294b0 thread T0
    #0 0x546dfe in nl::Weave::TLV::Debug::DumpHandler(nl::Weave::TLV::TLVReader const&, unsigned long, void*) /src/lib/core/WeaveTLVDebug.cpp:349:20
    #1 0x551b86 in nl::Weave::TLV::Utilities::Iterate(nl::Weave::TLV::TLVReader&, unsigned long, int (*)(nl::Weave::TLV::TLVReader const&, unsigned long, void*), void*, bool) src/lib/core/WeaveTLVUtilities.cpp:77:18
    #2 0x551da3 in nl::Weave::TLV::Utilities::Iterate(nl::Weave::TLV::TLVReader&, unsigned long, int (*)(nl::Weave::TLV::TLVReader const&, unsigned long, void*), void*, bool) /src/lib/core/WeaveTLVUtilities.cpp:87:22
    #3 0x551da3 in nl::Weave::TLV::Utilities::Iterate(nl::Weave::TLV::TLVReader&, unsigned long, int (*)(nl::Weave::TLV::TLVReader const&, unsigned long, void*), void*, bool) /src/lib/core/WeaveTLVUtilities.cpp:87:22
    #4 0x551da3 in nl::Weave::TLV::Utilities::Iterate(nl::Weave::TLV::TLVReader&, unsigned long, int (*)(nl::Weave::TLV::TLVReader const&, unsigned long, void*), void*, bool) /src/lib/core/WeaveTLVUtilities.cpp:87:22
[..]
$rax   : 0x0000000000000000
$rbx   : 0x00007ffc248f5200  →  0x0000000000000000
$rcx   : 0x0000000000545517  →  <nl::Weave::TLV::Utilities::Iterate(nl::Weave::TLV::TLVReader+0> add rsp, 0x58
$rdx   : 0x00000000ffffffeb  →  0x0000000000000000
$rsp   : 0x00007ffc248f3c60  →  0x00007ffc248f52c0  →  0x0000000000537280  →  <_DumpWriter(char+0> jmp 0x537288 <_DumpWriter(char const*,  ...)+8>
$rbp   : 0x00007ffc248f5200  →  0x0000000000000000
$rsi   : 0x0000000000000fc1
$rdi   : 0x00007ffc248f5200  →  0x0000000000000000
$rip   : 0x0000000000544eb7  →  <nl::Weave::TLV::TLVReader::ReadElement()+55> movzx eax, BYTE PTR [rax]
$r8    : 0x00007f339cdc8f00  →  0x00007f339cdc8f00  →  [loop detected]
$r9    : 0x0000000000000001
$r10   : 0x0000000000000000
$r11   : 0x0000000000000001
$r12   : 0x00000000005441b0  →  0x00000fcfb8d28548  →  0x0000000000000000
$r13   : 0x00007ffc248f52c0  →  0x0000000000537280  →  <_DumpWriter(char+0> jmp 0x537288 <_DumpWriter(char const*,  ...)+8>
$r14   : 0x0000000000000038
$r15   : 0x0000000000000001
$eflags: [carry PARITY adjust ZERO sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
───[ stack ]────
0x00007ffc248f3c60│+0x00: 0x00007ffc248f52c0  →  0x0000000000537280  →  <_DumpWriter(char+0> jmp 0x537288 <_DumpWriter(char const*,  ...)+8>     ← $rsp
0x00007ffc248f3c68│+0x08: 0x0000000000000038 ("8"?)
0x00007ffc248f3c70│+0x10: 0x0000000000000001
0x00007ffc248f3c78│+0x18: 0x0000000000544d69  →  <nl::Weave::TLV::TLVReader::SkipData()+9> sub eax, 0xc
0x00007ffc248f3c80│+0x20: 0x00007ffc248f5200  →  0x0000000000000000
0x00007ffc248f3c88│+0x28: 0x00000000005451f8  →  <nl::Weave::TLV::TLVReader::Skip()+88> mov eax, DWORD PTR [rsp+0xc]
0x00007ffc248f3c90│+0x30: 0x0000000009090909
0x00007ffc248f3c98│+0x38: 0x0000000000000000
───[ code:i386:x86-64 ]────
     0x544ea3 <nl::Weave::TLV::TLVReader::ReadElement()+35> pop    r13
     0x544ea5 <nl::Weave::TLV::TLVReader::ReadElement()+37> ret    
     0x544ea6 <nl::Weave::TLV::TLVReader::ReadElement()+38> nop    WORD PTR cs:[rax+rax*1+0x0]
     0x544eb0 <nl::Weave::TLV::TLVReader::ReadElement()+48> mov    rax, QWORD PTR [rbx+0x30]
     0x544eb4 <nl::Weave::TLV::TLVReader::ReadElement()+52> mov    rdi, rbx
 →   0x544eb7 <nl::Weave::TLV::TLVReader::ReadElement()+55> movzx  eax, BYTE PTR [rax]
     0x544eba <nl::Weave::TLV::TLVReader::ReadElement()+58> mov    WORD PTR [rbx+0x4c], ax
     0x544ebe <nl::Weave::TLV::TLVReader::ReadElement()+62> call   0x544750 <nl::Weave::TLV::TLVReader::ElementType() const>
     0x544ec3 <nl::Weave::TLV::TLVReader::ReadElement()+67> movzx  esi, al
     0x544ec6 <nl::Weave::TLV::TLVReader::ReadElement()+70> mov    ecx, eax
     0x544ec8 <nl::Weave::TLV::TLVReader::ReadElement()+72> mov    eax, 0xfc3
─────[ source:../../src/lib/core/WeaveTLVReader.cpp+1297 ]────
   1293      if (err != WEAVE_NO_ERROR)
   1294          return err;
   1295  
   1296      // Get the element's control byte.
 → 1297      mControlByte = *mReadPoint;
   1298
   1299      // Extract the element type from the control byte. Fail if it's invalid.
   1300      elemType = ElementType();
   1301      if (!IsValidTLVType(elemType))
─────[ trace ]────
[#0] 0x544eb7 → Name: nl::Weave::TLV::TLVReader::ReadElement(this=0x7ffc248f5200)...
[#1] 0x545250 → Name: nl::Weave::TLV::TLVReader::Next(this=0x7ffc248f5200)...
[#2] 0x54544a → Name: nl::Weave::TLV::Utilities::Iterate(aReader=@0x7ffc248f5200, aDepth=0x38, aHandler=0x5...
[#3] 0x545427 → Name: nl::Weave::TLV::Utilities::Iterate(aReader=@0x7ffc248f5240, aDepth=0x37, aHandler=0x5...
[#4] 0x545427 → Name: nl::Weave::TLV::Utilities::Iterate(aReader=@0x7ffc248f5240, aDepth=0x36, aHandler=0x5...
[#5] 0x545427 → Name: nl::Weave::TLV::Utilities::Iterate(aReader=@0x7ffc248f5240, aDepth=0x35, aHandler=0x5...
[#6] 0x545427 → Name: nl::Weave::TLV::Utilities::Iterate(aReader=@0x7ffc248f5240, aDepth=0x34, aHandler=0x5...
[#7] 0x545427 → Name: nl::Weave::TLV::Utilities::Iterate(aReader=@0x7ffc248f5240, aDepth=0x33, aHandler=0x5...
───────
nl::Weave::TLV::TLVReader::ReadElement (this=0x7ffc248f5200) at ../../src/lib/core/WeaveTLVReader.cpp:1297
1297        mControlByte = *mReadPoint;

Timeline

2019-04-18 - Vendor Disclosure
2019-05-20 - Vendor completed analysis
2019-06-18 - Follow up with vendor
2019-07-02 - 90 day notice; Vendor advised updates scheduled for release mid-July
2019-07-18 - Vendor advised fix will release end of July and be tested in the field
2019-07-26 - Extended disclosure date to 2019-08-15
2019-08-19 - Public Release

Credit

Discovered by Lilith (>_>) of Cisco Talos.