diff --git a/src/RtpVideoQueue.c b/src/RtpVideoQueue.c index 3d61254..d5dbe94 100644 --- a/src/RtpVideoQueue.c +++ b/src/RtpVideoQueue.c @@ -535,6 +535,10 @@ static void submitCompletedFrame(PRTP_VIDEO_QUEUE queue) { } } +uint32_t RtpvGetCurrentFrameNumber(PRTP_VIDEO_QUEUE queue) { + return queue->currentFrameNumber; +} + int RtpvAddPacket(PRTP_VIDEO_QUEUE queue, PRTP_PACKET packet, int length, PRTPV_QUEUE_ENTRY packetEntry) { if (isBefore16(packet->sequenceNumber, queue->nextContiguousSequenceNumber)) { // Reject packets behind our current buffer window diff --git a/src/RtpVideoQueue.h b/src/RtpVideoQueue.h index 42ce466..ec42c04 100644 --- a/src/RtpVideoQueue.h +++ b/src/RtpVideoQueue.h @@ -53,4 +53,5 @@ typedef struct _RTP_VIDEO_QUEUE { void RtpvInitializeQueue(PRTP_VIDEO_QUEUE queue); void RtpvCleanupQueue(PRTP_VIDEO_QUEUE queue); int RtpvAddPacket(PRTP_VIDEO_QUEUE queue, PRTP_PACKET packet, int length, PRTPV_QUEUE_ENTRY packetEntry); +uint32_t RtpvGetCurrentFrameNumber(PRTP_VIDEO_QUEUE queue); void RtpvSubmitQueuedPackets(PRTP_VIDEO_QUEUE queue); diff --git a/src/Video.h b/src/Video.h index 173ea56..3df2eae 100644 --- a/src/Video.h +++ b/src/Video.h @@ -14,7 +14,7 @@ typedef struct _QUEUED_DECODE_UNIT { // for FEC stays a multiple of 16 too. typedef struct _ENC_VIDEO_HEADER { uint8_t iv[12]; - uint32_t unused; + uint32_t frameNumber; uint8_t tag[16]; } ENC_VIDEO_HEADER, *PENC_VIDEO_HEADER; diff --git a/src/VideoStream.c b/src/VideoStream.c index b1d367c..c605ef8 100644 --- a/src/VideoStream.c +++ b/src/VideoStream.c @@ -187,6 +187,31 @@ static void VideoReceiveThreadProc(void* context) { if (encrypted) { PENC_VIDEO_HEADER encHeader = (PENC_VIDEO_HEADER)encryptedBuffer; + // If this frame is below our current frame number, discard it before decryption + // to save CPU cycles decrypting FEC shards for a frame we already reassembled. + // + // Since this is happening _before_ decryption, this packet is not trusted yet. + // It's imperative that we do not mutate any state based on this packet until + // after it has been decrypted successfully! + // + // It's possible for an attacker to inject a fake packet that has any value of + // header fields they want, however this provides them no benefit because we will + // simply drop said packet here (if it's below the current frame number) or it + // will pass this check and be dropped during decryption (if contents is tampered) + // or after decryption in the RTP queue (if it's a replay of a previous authentic + // packet from the host). + // + // In short, an attacker spoofing this value via MITM or sending malicious values + // impersonating the host from off-link doesn't gain them anything. If they have + // a true MITM, they can DoS our connection by just dropping all our traffic, so + // tampering with packets to fail this check doesn't accomplish anything they + // couldn't already do. If they're not on-link, we just throw their malicious + // traffic away (as mentioned in the paragraph above) and continue accepting + // legitmate video traffic. + if (encHeader->frameNumber && LE32(encHeader->frameNumber) < RtpvGetCurrentFrameNumber(&rtpQueue)) { + continue; + } + if (!PltDecryptMessage(decryptionCtx, ALGORITHM_AES_GCM, 0, (unsigned char*)StreamConfig.remoteInputAesKey, sizeof(StreamConfig.remoteInputAesKey), encHeader->iv, sizeof(encHeader->iv),