Talos Vulnerability Report

TALOS-2019-0800

Nest Labs Nest Cam IQ Indoor WeaveCASEEngine::DecodeCertificateInfo denial-of-service vulnerability

August 19, 2019
CVE Number

CVE-2019-5037

Summary

An exploitable denial-of-service vulnerability exists in the Weave certificate loading functionality of the Nest Cam IQ Indoor camera, version 4620002. A specially crafted weave packet can cause an integer overflow and an out-of-bounds read on unmapped memory to occur, resulting in a denial of service. An attacker can send a specially crafted packet to trigger this vulnerability.

Tested Versions

Nest Labs Nest Cam IQ Indoor version 4620002

Product URLs

https://store.nest.com/product/nest-cam-iq/NC3200US

CVSSv3 Score

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

CWE

CWE-190: Integer Overflow Or Wraparound

Details

The Nest Cam IQ Indoor is one of Nest Labs' most expensive and advanced devices. The surveillance camera integrates with Google Assistant, contains facial recognition technology and even has the ability to act as an 6lowpan hub for other less powerful internet-of-things devices. The main protocol that is used for setup and initial communications of Nest devices is Weave, a protocol designed strictly for IoT devices, which can run over TCP, UDP, Bluetooth and 6lowpan.

Before going into the details of the bug itself, a quick overview of the Weave protocol and terminology is needed, so for brevity, here's a packet dissection of a sample weave packet:

--------TCP Message Layer-----------  //[1]
Mesage_Length   : 0x0146
Message Version : 0x1
Message_Id      : 0x00000004
Message_Flags   : 0x0300    : [ Dstnode|Srcnode ]
Src_Node        : 0000000000000002
Dst_Node        : 0000000000000001
Encryption      : None
--------Exchange Layer-----------    //[2]
Exchange Ver|Flag   :  0x11       :  [ Initiator ]
Exchange MsgType    :  0x1        : kMsgType_PASEInitiatorStep1 //[3]
Exchange ExchangeID :  0x70e3
Exchange ProfileID  :  0x00000004 : kWeaveProfile_Security  //[4]
--------Data Layer-----------   //[5]
kMsgType_PASEInitiatorStep1
controlHeader: 0x8011213f
sizeHeader: 0x01070e0e
ProtocolConfig: 0x235a0004
AltConfig[0]: 0x235a0001
gx: 0xe, zkpxgr: 0xe, zkpxb: 0x7
[...]

Weave messages consist of three layers: message, exchange and data. The message layer [1] and exchange layer [2] are both variable sized and consist of little-endian fields as listed above. The data layer [5] is strictly dependent on the MessageType [3] and ProfileId [4], and every combination thereof generally has a unique message structure, which can be seen from all the parsed fields below [5]. The ProfileID is fittingly used to determine which Weave "Profile" to talk with, and likewise, the MessageType is essentially the opcode for the given profile.

For the purposes of this writeup, let's consider the MessageType kMsgType_CASEBeginSessionRequest. This request is what a client sends when it wants the maximum amount of permissions that one can get via Weave, and it allows access to all Weave requests that are included in the given device. Unlike PASE, which requires a simple 6-8 character pairing code, CASE authentication requires that one present a fully valid weave certificate chain to the device, and also that the certificate chain has a common root of trust to the Certificate Authorities that are currently configured on the device.

A sample CASEBeginSessionRequest looks as such:

--------TCP Message Layer-----------
Mesage_Length   : 0x01a7
Message Version : 0x1
Message_Id      : 0x0000000a
Message_Flags   : 0x0200    : [ Srcnode ]
Src_Node        : 0000000000000002
Encryption      : None
--------Exchange Layer-----------
Exchange Ver|Flag   :  0x11       |  [ Initiator ]
Exchange MsgType    :  0x0a       | kMsgType_CASEBeginSessionRequest
Exchange ExchangeID :  0xbfd7
Exchange ProfileID  :  0x00000004 | kWeaveProfile_Security
--------Data Layer-----------
kMsgType_CASEBeginSessionRequest
controlHeader: 0x81  (32-bit KeyConfirm)
AlternateConfigCount: 0x1
AlternateCurveCount: 0x2
ECDHPubKey_PointLen: 0x39
CertInfoLength: 0xf8
PayloadLength: 0x0
ProtocolConfig: 0x235a0002
CurveId: 0x235a0025
SessionKeyId: 0x21e5
AlternateConfigs[0]: 0x235a0001
AlternateCurveIds[0]: 0x235a001b
AlternateCurveIds[1]: 0x235a0025
ECDHPubKey: // [8]
\x04\x3d\xd5\x3d\x9d\xd9\xda\xca\x73\x13\xcf\xd8\x94\xc7\x54\xa1\x5f\x8e\x08\x2c\x56\x01\xee\x9a\xb3\x32\xee\xef\x16\x35\x93\x77\x2f\x59\xd6\x3f\xcb\x23\xa6\x0e\x6b\x1c\x95\x08\xca\x0f\x0e\xf7\xdc\x2c\x9b\x0f\x66\xb3\x32\x95\x07
CertInfo: \x00\x35\x01\x30\x01\x08<insert weave certificate chain here>\x18\x18\x18\x95 //[6]
Payload: <empty>
Signature:  //[7] \x08\x00\x30\x01\x1c\x3c\x10\x7e\xb6\x8c\x8a\x37\x3d\x5c\x77\x33\x1b\x46\x02\x52\x30\x20\x45\x90\x2b\xa8\xb9\xde\x66\x39\x1c\x91\x27\x02\x1c\x51\x4c\x3a\x06\x64\x3c\x16\xa5\x53\x8f\xf0\x16\x4b\x32\xa1\x42\x94\x28\x6a\x52\x05\xa3\x2d\xd2\xd4\xd3\x43\x56\x18

Giving a summary of the process, the server will take the above client's request, extract the certificate chain [6] and verify that there's a common trust anchor somewhere along the way. After this, the server will verify that the message has been signed by the private key of the valid certificate by checking the DER encoded ECDSA signature located at [7]. Then, by using the client's ephemeral ECDH public key [8] and their own ephemeral ECDH private key, the client and server will generate a shared secret of length 32.

At this point, it should be noted that CASE and PASE are the same process from here on (with the exception of the permissions granted). The shared secret will then be extended via HKDF in order to generate three different keys, a data key, an integrity key and also a KeyConfirmation key, which are then used for AES128CBC encryption and SHA256 hashing.

Turning back to the vulnerability itself, let us examine the parsing of the BeginCASESessionRequest:

WEAVE_ERROR BeginSessionRequestMessage::DecodeHead(PacketBuffer *msgBuf){
WEAVE_ERROR err = WEAVE_NO_ERROR;
uint8_t *p = msgBuf->Start();
uint16_t msgLen = msgBuf->DataLength(); // max ~1670
uint16_t msgLenWithoutSig;
uint8_t controlHeader;

// Verify we can read the fixed length portion of the message without running into the end of the buffer.
VerifyOrExit(msgLen > 18, err = WEAVE_ERROR_MESSAGE_INCOMPLETE);

// Parse and decode the control header.
controlHeader = *p++; // 1
EncryptionType = controlHeader & kCASEHeader_EncryptionTypeMask;
PerformKeyConfirm = ((controlHeader & kCASEHeader_PerformKeyConfirmFlag) != 0);
VerifyOrExit((controlHeader & kCASEHeader_ControlHeaderUnusedBits) == 0, err = WEAVE_ERROR_INVALID_ARGUMENT);

// Parse the alternate config count, alternate curve count and DH public key length.
AlternateConfigCount = *p++;            
VerifyOrExit(AlternateConfigCount <= kMaxAlternateProtocolConfigs, err = WEAVE_ERROR_INVALID_ARGUMENT);
AlternateCurveCount = *p++;
VerifyOrExit(AlternateCurveCount <= kMaxAlternateCurveIds, err = WEAVE_ERROR_INVALID_ARGUMENT);
ECDHPublicKey.ECPointLen = *p++;             //[9]

// Parse the certificate information length.
CertInfoLength = LittleEndian::Read16(p);    //[10]

// Parse the payload length.
PayloadLength = LittleEndian::Read16(p);     //[11]

// Parse the proposed protocol config.
ProtocolConfig = LittleEndian::Read32(p); 

// Parse the proposed curve id.
CurveId = LittleEndian::Read32(p);

// Parse the session key id.
SessionKeyId = LittleEndian::Read16(p);

// Verify the overall message length is consistent with the claimed field sizes.
msgLenWithoutSig = HeadLength() + ECDHPublicKey.ECPointLen + CertInfoLength + PayloadLength; //[12]
VerifyOrExit(msgLen > msgLenWithoutSig, err = WEAVE_ERROR_MESSAGE_INCOMPLETE);  //[13]

At [9], [10], and [11], we can see that we have full control of the ECDHPublicKey.ECPointlen, CertInfoLength, and Payload Length fields. Then, at [12], we can also see that these fields are added together into the msgLenWithoutSig variable, which is a uint_16, which causes us to be able to wrap around and bypass the check at [13]. So what does that give us?

for (uint8_t i = 0; i < AlternateConfigCount; i++)           
     AlternateConfigs[i] = LittleEndian::Read32(p);

// Parse the alternate curves list.
for (uint8_t i = 0; i < AlternateCurveCount; i++)
    AlternateCurveIds[i] = LittleEndian::Read32(p); 

// Save a pointer to the DH public key.
ECDHPublicKey.ECPoint = p; //[14]
p += ECDHPublicKey.ECPointLen; 

// Save a pointer to the initiator's certificate information.
CertInfo = p;  // [15]
p += CertInfoLength; 

// Save a pointer to the payload data.
Payload = p;   // [16]
p += PayloadLength; 

// Save a pointer to the signature and compute the signature length.
Signature = p;
SignatureLength = msgLen - msgLenWithoutSig; 

exit:
    return err;
}

Turns out we can set the ECDHPublicKey.ECPoint to length of 0xFF, the CertInfo to length of 0xFFFF, and also the Payload length to 0xFFFF. Unfortunately, the only useful one of these options is setting the CertInfo to 0xFFFF, as the Payload is never used and only a fixed maximum of the ECPoint variable is used. This brings us to the WeaveCASEEngine::DecodeCertificateInfo function:

WEAVE_ERROR WeaveCASEEngine::DecodeCertificateInfo(BeginSessionMessageBase& msg, WeaveCertificateSet& certSet,
                                               WeaveDN& entityCertDN, CertificateKeyId& entityCertSubjectKeyId)
{
    WEAVE_ERROR err;
    TLVReader reader;
    WeaveCertificateData *entityCert = NULL;

    // Begin decoding the certificate information structure.
    reader.Init(msg.CertInfo, msg.CertInfoLength);

By having our message's CertInfoLength set to 0xFFFF, we can create a WeaveTLVReader with a maximum length of 0xFFFF, even though we normally would only ever have a maximum of the size of our current packet buffer (~0x62F). Continuing on in the function:

err = reader.Next() //[21]  

if (err == WEAVE_NO_ERROR && reader.GetTag() == ContextTag(kTag_CASECertificateInfo_EntityCertificate)) 
    {                                                                     
        // Load the authenticating entity's certificate into the certificate set.
        err = certSet.LoadCert(reader, kDecodeFlag_GenerateTBSHash, entityCert); //[17]
        SuccessOrExit(err);

        entityCertDN = entityCert->SubjectDN;
        entityCertSubjectKeyId = entityCert->SubjectKeyId;

        err = reader.Next();
    }

    // Look for the EntityCertificateRef element and fail if found.
    // NOTE: This version of the code does not support the use of certificate reference to identify the certificate.
    if (err == WEAVE_NO_ERROR && reader.GetTag() == ContextTag(kTag_CASECertificateInfo_EntityCertificateRef))
    {                                                                         
        ExitNow(err = WEAVE_ERROR_UNSUPPORTED_CERT_FORMAT); // TODO: use better error //[18]
    }

    // Look for the RelatedCertificates element. If found, load the contained certificates into the certificate set.
    if (err == WEAVE_NO_ERROR && reader.GetTag() == ContextTag(kTag_CASECertificateInfo_RelatedCertificates)) {
        err = certSet.LoadCerts(reader, kDecodeFlag_GenerateTBSHash);      
        SuccessOrExit(err); //[19]

        err = reader.Next();
    }

    // Skip the TrustAnchors element if present.  This represents information an initiator provides to the
    // responder about what certificates it trusts, allowing the responder to select an appropriate entity
    // certificate to respond with. This code assumes that the local node only has a single entity certificate,
    // and thus its that or nothing.
    if (err == WEAVE_NO_ERROR && reader.GetTag() == ContextTag(kTag_CASECertificateInfo_TrustAnchors))
    {                                                                      
        err = reader.Next();  //[20]
    }

As shown above, there are four different elements that get read in from the CertInfo field. At [17], the code tries to read in the Client's certificate, and at [19] the code reads in the client's Certificate Chain. Interestingly, the code at [18] and [20], are basically ignored elements. Even though the code at [17] and [19] are the most complex, the easiest way to actually exploit this bug is to go with code path [20]. At [21], we already advanced the WeaveTLVReader by one element, and at [20], we cause the WeaveTLVReader to skip over another element without ever processing to see if it was valid or not. Due to this, if we cause the reader.Next at [20] to skip over an element of rather large length (perhaps 0xFFE0), the reader will then advance its read pointer by this much.

Normally, a TLVReader is always bounded by the maximum length that it was initialized with, but due to the integer overflow inside of DecodeCertificateInfo, we can cause the TLVReader to advance to unmapped memory, causing a crash.

Crash Information

Thread 10 "nldaemon" received signal SIGSEGV, Segmentation fault.
?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????[ registers ]???$
$x0       : 0x000000556d83cbea
$x1       : 0x000000556d83cbea
$x2       : 0x000000000000f000
$x3       : 0x000000783827fb18  ?  0x0000007838499000  ?  0x00000078384f0000  ?  0x000000556d7aa1d8  ?  0x000000556d59c22c  ?  <nlWeaveCASEAuth::GetNodeCertInfo(bool,+0> sub sp,  sp,  #0x40
$x4       : 0x000000783827f640  ?  0x000000783827f6b0  ?  0x000000783827f610  ?  0x0000000000000000
$x5       : 0x000000783827f780  ?  0x000000783827fa00  ?  0x000000783827fb60  ?  0x000000783827fba0  ?  0x000000783827fcc0  ?  0x000000783827fd10  ?  0x000000783827fd80  ?  0x000000783827fe40
$x6       : 0x000000783827f7d2  ?  0xf000000000783827 ("'8x"?)
$x7       : 0x6966697472654365 ("eCertifi"?)
$x8       : 0xaa45f89f34824c29
$x9       : 0xaa45f89f34824c29
$x10      : 0x0000000000000001
$x11      : 0x00000000ffffffd8
$x12      : 0x000000783827f610  ?  0x0000000000000000
$x13      : 0x0000000000000000
$x14      : 0x0000007839146358  ?  0x3736353433323130 ("01234567"?)
$x15      : 0x0000000000000000
$sp       : 0x000000783827f840  ?  0x000000783827f890  ?  0x000000783827f8c0  ?  0x000000783827f8f0  ?  0x000000783827f920  ?  0x000000783827f950  ?  0x000000783827fa00  ?  0x000000783827fb60
$pc       : 0x000000556d6390a4  ?  <nl::Weave::TLV::TLVReader::ReadElement()+56> ldrb w0,  [x0]
$cpsr     : [fast interrupt overflow CARRY ZERO negative]
$fpsr     : 0x0000000000000010
$fpcr     : 0x0000000000000000
?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????[ stack ]????
0x000000783827f840?+0x00: 0x000000783827f890  ?  0x000000783827f8c0  ?  0x000000783827f8f0  ?  0x000000783827f920  ?  0x000000783827f950  ?  0x000000783827fa00  ?  0x000000783827fb60   ? $sp
0x000000783827f848?+0x08: 0x000000556d639048  ?  <nl::Weave::TLV::TLVReader::SkipToEndOfContainer()+220> str w0,  [x29, #32]
0x000000783827f850?+0x10: 0x0000ffea6d59c570
0x000000783827f858?+0x18: 0x000000783827f998  ?  0x3c3c1f7000000004
0x000000783827f860?+0x20: 0x000000783827f890  ?  0x000000783827f8c0  ?  0x000000783827f8f0  ?  0x000000783827f920  ?  0x000000783827f950  ?  0x000000783827fa00  ?  0x000000783827fb60
0x000000783827f868?+0x28: 0x000000556d639028  ?  <nl::Weave::TLV::TLVReader::SkipToEndOfContainer()+188> str w0,  [x29, #32]
0x000000783827f870?+0x30: 0x000000e03827f890
0x000000783827f878?+0x38: 0x000000783827f998  ?  0x3c3c1f7000000004
??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????[ code:aarch64 ]????
   0x556d63908c <nl::Weave::TLV::TLVReader::ReadElement()+32> cmp    w0,  wzr
   0x556d639090 <nl::Weave::TLV::TLVReader::ReadElement()+36> b.eq   0x556d63909c <_ZN2nl5Weave3TLV9TLVReader11ReadElementEv+48>
   0x556d639094 <nl::Weave::TLV::TLVReader::ReadElement()+40> ldr    w0,  [x29, #76]
   0x556d639098 <nl::Weave::TLV::TLVReader::ReadElement()+44> b      0x556d6392ac <_ZN2nl5Weave3TLV9TLVReader11ReadElementEv+576>
   0x556d63909c <nl::Weave::TLV::TLVReader::ReadElement()+48> ldr    x0,  [x29, #24]
   0x556d6390a0 <nl::Weave::TLV::TLVReader::ReadElement()+52> ldr    x0,  [x0, #48]
 ? 0x556d6390a4 <nl::Weave::TLV::TLVReader::ReadElement()+56> ldrb   w0,  [x0]
   0x556d6390a8 <nl::Weave::TLV::TLVReader::ReadElement()+60> uxth   w1,  w0
   0x556d6390ac <nl::Weave::TLV::TLVReader::ReadElement()+64> ldr    x0,  [x29, #24]
   0x556d6390b0 <nl::Weave::TLV::TLVReader::ReadElement()+68> strh   w1,  [x0, #76]
   0x556d6390b4 <nl::Weave::TLV::TLVReader::ReadElement()+72> ldr    x0,  [x29, #24]
   0x556d6390b8 <nl::Weave::TLV::TLVReader::ReadElement()+76> bl     0x556d63986c <_ZNK2nl5Weave3TLV9TLVReader11ElementTypeEv>
?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????[ trace ]????
[#0] 0x556d6390a4 ? Name: nl::Weave::TLV::TLVReader::ReadElement()()...
[#1] 0x556d639048 ? Name: nl::Weave::TLV::TLVReader::SkipToEndOfContainer()()...
[#2] 0x556d638c5c ? Name: nl::Weave::TLV::TLVReader::ExitContainer(nl::Weave::TLV::TLVType)()...
[#3] 0x556d638e7c ? Name: nl::Weave::TLV::TLVReader::Skip()()...
[#4] 0x556d638d1c ? Name: nl::Weave::TLV::TLVReader::Next()()...
[#5] 0x556d64b080 ? Name: nl::Weave::Profiles::Security::CASE::WeaveCASEEngine::DecodeCertificateInfo(nl::Weave::Pr...
[#6] 0x556d64ab6c ? Name: nl::Weave::Profiles::Security::CASE::WeaveCASEEngine::VerifySignature(nl::Weave::Profiles...
[#7] 0x556d649ac4 ? Name: nl::Weave::Profiles::Security::CASE::WeaveCASEEngine::ProcessBeginSessionRequest(nl::Weav...
??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
0x000000556d6390a4 in nl::Weave::TLV::TLVReader::ReadElement() ()
<(^_^)> info reg x0
x0             0x556d83cbea     0x556d83cbea
<(^_^)> x/10gx $x0
0x556d83cbea:   Cannot access memory at address 0x556d83cbea

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 Wyatt and Claudio Bozzato of Cisco Talos.