Skip to content

fix(voice): strip RTP padding before DAVE decryption - #3386

Closed
yangwhale wants to merge 2 commits into
Pycord-Development:masterfrom
yangwhale:fix/voice-strip-rtp-padding
Closed

yangwhale wants to merge 2 commits into
Pycord-Development:masterfrom
yangwhale:fix/voice-strip-rtp-padding

Conversation

@yangwhale

Copy link
Copy Markdown

Summary

RTP packets from Discord may carry tail padding. When the P bit is set in the RTP header, the last octet of the payload holds the padding length, including itself (RFC 3550 §5.1). py-cord already parses that bit into RTPPacket.padding (discord/voice/packets/rtp.py:100) but never acts on it.

That was harmless as long as the payload went straight to the Opus decoder — Opus tolerates trailing garbage. It is not harmless with DAVE: the E2EE layer looks for its frame marker at the tail of the payload, so padding buries it and DaveSession.decrypt raises DecryptionFailed(UnencryptedWhenPassthroughDisabled).

The failure mode is nasty because it is quiet. PacketDecryptor.decrypt_rtp catches the exception, logs at debug, and substitutes OPUS_SILENCE. So the symptom users report is not "decryption failed" — it is choppy, stuttering incoming audio, with a large share of frames silently replaced by silence.

The fix

Strip the padding before the payload is handed to dave.decrypt, and only when the P bit is set and the declared length is in range. An out-of-range length means the P bit can't be trusted, so the payload is passed through untouched — truncating a valid frame is worse than leaving a bad one alone.

The helper deliberately does not try to detect padding heuristically (e.g. "the tail is a run of identical bytes"). That would corrupt legitimate ciphertext that happens to look that way, and a corrupted frame is indistinguishable from a dropped one in the logs.

Prior art

discord.js hit exactly this bug and fixed it the same way in discordjs/discord.js#11449 (merged 2026-03-13), which closed #11419 and several linked issues. Their check is the same shape:

const hasPadding = buffer[0] && Boolean(buffer[0] & 0b100000);
if (hasPadding) {
  const paddingAmount = packet[packet.length - 1]!;
  if (paddingAmount < packet.length) {
    packet = packet.subarray(0, packet.length - paddingAmount);
  }
}

The Python side has the same bug and never got the same fix. snazzah/davey#15 is a report of this symptom filed against davey while using py-cord ("Most DAVE-encrypted audio packets fail to decrypt (~95% failure rate)"). It was correctly closed with "The davey package is not the issue here but the implementation of it in whatever library it is being used in." — the reporter's own diagnostic dump has the fingerprint in it (last4=05050505, last4=0f0f0f0f: runs of identical bytes whose value equals the run length), it just wasn't recognised as padding at the time.

Verification

Measured on a live voice channel with DAVE enabled, receiving from a desktop client, before and after this patch:

decrypted failed passthrough
before 577 29 0
after 290 0 0

A later run on the same build logged 255 successful frames with 0 failures, of which 3 were packets that actually had the P bit set and went through this code path — those are the frames that would have failed before. Audio quality went from audibly stuttering to clean.

Notes

  • Single-purpose change; no behaviour difference for packets without the P bit, and none at all for the non-DAVE path (the helper is only called on the DAVE branch's input).
  • No new dependencies, no public API change.

RTP packets may carry tail padding (RFC 3550 section 5.1). When the P bit
is set in the header, the last octet of the payload gives the padding
length, including itself. py-cord parses the P bit into RTPPacket.padding
but never acts on it.

This was harmless while the payload went straight to Opus, which ignores
trailing garbage. It is not harmless with DAVE: the E2EE layer looks for
its frame marker at the tail of the payload, so padding buries it and
DaveSession.decrypt raises DecryptionFailed. In practice this shows up as
choppy audio -- a large fraction of received frames silently replaced with
OPUS_SILENCE -- rather than as an obvious error.

discord.js hit the same bug and fixed it the same way in #11449.

Fixes the receive-side decryption failures for padded packets.
@pycord-app

pycord-app Bot commented Sep 11, 2026

Copy link
Copy Markdown

Thanks for opening this pull request!
Please make sure you have read the Contributing Guidelines and Code of Conduct.

This pull request can be checked-out with:

git fetch origin pull/3386/head:pr-3386
git checkout pr-3386

This pull request can be installed with:

pip install git+https://github.com/Pycord-Development/pycord@refs/pull/3386/head

@github-actions github-actions Bot added the invalid This doesn't seem right label Sep 11, 2026
@github-actions

Copy link
Copy Markdown
Contributor

This pull request does not follow the required pull request template.

Please use the default template (PULL_REQUEST_TEMPLATE.md) and fill out all required sections.

Problems detected:

Missing required section: "## Information"
Missing required section: "## Checklist"
The line "I have read the [Contributing Guidelines](https://github.com/Pycord-Development/pycord/blob/master/CONTRIBUTING.md)" is missing.
The line "AI Usage has been disclosed." is missing.

@github-actions github-actions Bot closed this Sep 11, 2026
@github-actions github-actions Bot locked as spam and limited conversation to collaborators Sep 11, 2026
@github-project-automation github-project-automation Bot moved this from Todo to Done in Pycord Sep 11, 2026
@Lulalaby

Copy link
Copy Markdown
Member

idiot

@vmphase

vmphase commented Sep 11, 2026

Copy link
Copy Markdown
Member

Just FWIW, this PR fixes literally nothing. Stale changes xD

@Pycord-Development Pycord-Development unlocked this conversation Sep 11, 2026
@Pycord-Development Pycord-Development locked and limited conversation to collaborators Sep 11, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

invalid This doesn't seem right

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

@discordjs/voice 0.19.x DAVE encryption causes reconnect loops and zero audio capture

3 participants