Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LowerTransportLayer.checkAgainstReplayAttack Improperly Records Sequence Number for Segmented Messages #345

Closed
MarcianoPreciado opened this issue Apr 14, 2021 · 6 comments · Fixed by #346
Labels
bug Something isn't working

Comments

@MarcianoPreciado
Copy link

Describe the bug
If you send both segmented and unsegmented messages from a node to the app, eventually the sequence number will be malformed and much greater than the actual sequence number send by the device. All messages between this event and the sequence number catching up will be discarded.

This results in my device being unusable with your library, which we have already invested a lot of time and money in.

To Reproduce
Steps to reproduce the behavior:

  1. Make a device that alternates between segmented and unsegmented messages
  2. Receive these messages through the app
  3. Log received receivedSeqAuth and localSeqAuth in LowerTransportLayer.checkAgainstReplayAttack
  4. See that eventually the localSeqAuth is suddenly thousands higher than it should be (message it is generated from always seems to be a segmented message)
  5. All messages from device are discarded until receivedSeqAuth catches up with insanely large localSeqAuth thousands of messages later.

Expected behavior
Generally, as messages are received, the localSeqAuth should increment by 1 or 2 points with each message, regardless of segmentation.

Logs / Screenshots

"sequence": { "receivedSeqAuth": 8182, "networkpduContents": Network PDU (ivi: 0, nid: 0x45, ctl: 0, ttl: 7, seq: 8186, src: 0004, dst: FFFF, transportPdu: 0x7CA162DF3AA03D3D0C855E4BA46574F7, netMic: 0xB50C9FE3),  "localSeqAuth.uint64Value": 8182, "networdPDU.messageSequence": 8182, "meshNetwork.ivIndex": 0,  date: "2021-03-04 20:41:30 +0000",},
    "sequence": { "receivedSeqAuth": 8182, "networkpduContents": Network PDU (ivi: 0, nid: 0x45, ctl: 0, ttl: 7, seq: 8188, src: 0004, dst: FFFF, transportPdu: 0x6BEE47D650BE11A88F10D147108E2042, netMic: 0xC8C59F6A),  "localSeqAuth.uint64Value": 8182, "networdPDU.messageSequence": 8182, "meshNetwork.ivIndex": 0,  date: "2021-03-04 20:41:30 +0000",},
    "sequence": { "receivedSeqAuth": 8190, "networkpduContents": Network PDU (ivi: 0, nid: 0x45, ctl: 0, ttl: 7, seq: 8190, src: 0004, dst: FFFF, transportPdu: 0x69374CCA846EFE9CBA05324ACCA60CB5, netMic: 0x72CD9866),  "localSeqAuth.uint64Value": 8182, "networdPDU.messageSequence": 8190, "meshNetwork.ivIndex": 0,  date: "2021-03-04 20:41:31 +0000",},
    "sequence": { "receivedSeqAuth": 8190, "networkpduContents": Network PDU (ivi: 0, nid: 0x45, ctl: 0, ttl: 7, seq: 8191, src: 0004, dst: FFFF, transportPdu: 0xD23DA45203C7921395D5762069, netMic: 0x58C13E58),  "localSeqAuth.uint64Value": 8190, "networdPDU.messageSequence": 8190, "meshNetwork.ivIndex": 0,  date: "2021-03-04 20:41:31 +0000",},
    "sequence": { "receivedSeqAuth": 16382, "networkpduContents": Network PDU (ivi: 0, nid: 0x45, ctl: 0, ttl: 7, seq: 8192, src: 0004, dst: FFFF, transportPdu: 0x7EB09E30E7E8C45198C70ECDE3FA6440, netMic: 0x2D99DB13),  "localSeqAuth.uint64Value": 8190, "networdPDU.messageSequence": 16382, "meshNetwork.ivIndex": 0,  date: "2021-03-04 20:41:31 +0000",},
    "sequence": { "receivedSeqAuth": 16382, "networkpduContents": Network PDU (ivi: 0, nid: 0x45, ctl: 0, ttl: 7, seq: 8194, src: 0004, dst: FFFF, transportPdu: 0x9FF7AEFCB7981FB038EBC5AD37C262E6, netMic: 0xDDD35AB8),  "localSeqAuth.uint64Value": 16382, "networdPDU.messageSequence": 16382, "meshNetwork.ivIndex": 0,  date: "2021-03-04 20:41:31 +0000",},
    "sequence": { "receivedSeqAuth": 16382, "networkpduContents": Network PDU (ivi: 0, nid: 0x45, ctl: 0, ttl: 7, seq: 8196, src: 0004, dst: FFFF, transportPdu: 0x50EEF7777223389D606C2A557DA61875, netMic: 0xA686B1F5),  "localSeqAuth.uint64Value": 16382, "networdPDU.messageSequence": 16382, "meshNetwork.ivIndex": 0,  date: "2021-03-04 20:41:31 +0000",},
    "sequence": { "receivedSeqAuth": 8198, "networkpduContents": Network PDU (ivi: 0, nid: 0x45, ctl: 0, ttl: 7, seq: 8198, src: 0004, dst: FFFF, transportPdu: 0xC1484A7B48D068BA49AFD63662CE18EE, netMic: 0xC4E449D0),  "localSeqAuth.uint64Value": 16382, "networdPDU.messageSequence": 8198, "meshNetwork.ivIndex": 0,  date: "2021-03-04 20:41:32 +0000",},
    "sequence": { "receivedSeqAuth": 8198, "networkpduContents": Network PDU (ivi: 0, nid: 0x45, ctl: 0, ttl: 7, seq: 8199, src: 0004, dst: FFFF, transportPdu: 0x69B15BB7509BDDB9DE86D1449C, netMic: 0x89F03E29),  "localSeqAuth.uint64Value": 16382, "networdPDU.messageSequence": 8198, "meshNetwork.ivIndex": 0,  date: "2021-03-04 20:41:32 +0000",},
    "sequence": { "receivedSeqAuth": 8198, "networkpduContents": Network PDU (ivi: 0, nid: 0x45, ctl: 0, ttl: 7, seq: 8200, src: 0004, dst: FFFF, transportPdu: 0x38FBE790EE54DAF02C6F9359BB9A7795, netMic: 0x3D096AE5),  "localSeqAuth.uint64Value": 16382, "networdPDU.messageSequence": 8198, "meshNetwork.ivIndex": 0,  date: "2021-03-04 20:41:32 +0000",},
    "sequence": { "receivedSeqAuth": 8198, "networkpduContents": Network PDU (ivi: 0, nid: 0x45, ctl: 0, ttl: 7, seq: 8202, src: 0004, dst: FFFF, transportPdu: 0x85FB3292F4A3E26F4A0F39C520342B76, netMic: 0xE9B46A84),  "localSeqAuth.uint64Value": 16382, "networdPDU.messageSequence": 8198, "meshNetwork.ivIndex": 0,  date: "2021-03-04 20:41:32 +0000",},

The important fields to note above are receivedSeqAuth, Network PDU.ivi, Network PDU.seq, and localSeqAuth.uint64Value

@MarcianoPreciado
Copy link
Author

MarcianoPreciado commented Apr 14, 2021

I think the problem lies in this extension:

private extension NetworkPdu {
    
    /// Whether the Network PDU contains a segmented Lower Transport PDU,
    /// or not.
    var isSegmented: Bool {
        return transportPdu[0] & 0x80 > 1
    }
    
    /// The 24-bit Seq Auth used to transmit the first segment of a
    /// segmented message, or the 24-bit sequence number of an unsegmented
    /// message.
    var messageSequence: UInt32 {
        if isSegmented {
            let sequenceZero = (UInt16(transportPdu[1] & 0x7F) << 6) | UInt16(transportPdu[2] >> 2)
            return (sequence & 0xFFE000) | UInt32(sequenceZero)
        } else {
            return sequence
        }
    }
    
}

Because later on this is how it gets used:

func checkAgainstReplayAttack(_ networkPdu: NetworkPdu) -> Bool {
        // Don't check messages sent to other Nodes.
        guard !networkPdu.destination.isUnicast ||
              meshNetwork.localProvisioner?.node?.hasAllocatedAddress(networkPdu.destination) ?? false else {
            return true
        }
        let sequence = networkPdu.messageSequence
        let receivedSeqAuth = (UInt64(networkPdu.ivIndex) << 24) | UInt64(sequence)
...
...
...
            // Validate.
            guard receivedSeqAuth > localSeqAuth || missed ||
                  (reassemblyInProgress && receivedSeqAuth == localSeqAuth) else {
                // Ignore that message.
                logger?.w(.lowerTransport, "Discarding packet (seqAuth: \(receivedSeqAuth), expected > \(localSeqAuth))")
                return false
            }
            
            // The message is valid. Remember the previous SeqAuth.
            let newPreviousSeqAuth = min(receivedSeqAuth, localSeqAuth)
...
...
...
}

Somehow the segmented seqAuth becomes greater than (ivIndex << 24) | seqNum which is not standard compliant.

@MarcianoPreciado
Copy link
Author

MarcianoPreciado commented Apr 14, 2021

In fact, the "Mesh Profile Section 3.5.3.1 Segmentation" states:

... the SeqAuth value can be derived from the IV Index, SeqZero, and SEQ in any of the segments, by determining the largest SeqAuth value for which SeqZero is between SEQ - 8191 and SEQ inclusive and using the same IV Index. For example, if the received SEQ of a message was 0x647262, the IV Index was 0x58437AF2, and the received SeqZero value was 0x1849, then the SeqAuth value is 0x58437AF2645849. If the received SEQ of a message was 0x647262 and the received SeqZero value was 0x1263, then the SeqAuth value is 0x58437AF2645263.

When using the example values from above in the messageSequence computed variable, the wrong result is yielded on both counts.

Taking the implementation from the nRF5-SDK-for-Mesh's implementation, you can see that it is quite different. It also yields the correct results.

static uint32_t seqauth_sequence_number_get(uint32_t seqnum, uint16_t seqzero)
{
    if ((seqnum & TRANSPORT_SAR_SEQZERO_MASK) < seqzero)
    {
        return ((seqnum - ((seqnum & TRANSPORT_SAR_SEQZERO_MASK) - seqzero) - (TRANSPORT_SAR_SEQZERO_MASK + 1)));
    }
    else
    {
        return ((seqnum - ((seqnum & TRANSPORT_SAR_SEQZERO_MASK) - seqzero)));
    }
}

Note: TRANSPORT_SAR_SEQZERO_MASK is defined as 8191

@philips77
Copy link
Member

Thank you for the bug report and detailed explanation. I'll look into it now.

@philips77 philips77 added the bug Something isn't working label Apr 15, 2021
@philips77
Copy link
Member

The issue was, as you said, related to how the message sequence was calculated.

@philips77
Copy link
Member

Released in 3.1.2.

@philips77
Copy link
Member

Hi, could you, please, verify that it's working now with 3.1.2?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants