Headline
CVE-2019-5038: TALOS-2019-0801 || Cisco Talos Intelligence Group
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.
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
Discovered by Lilith (>_>) of Cisco Talos.