srtp: read Cryptex header extension from the input buffer on unprotect - #819
Merged
pabuhler merged 2 commits intoAug 13, 2026
Merged
Conversation
srtp_cryptex_unprotect_init() read the RTP header-extension profile and length from the rtp parameter, which on the unprotect path is the output buffer. srtp_unprotect() documents that rtp "can be the same as srtp to support in-place io", so a distinct output buffer is a supported calling mode, and in that mode the output buffer has not been written yet when this function runs -- the first write, memcpy(rtp, srtp, enc_start), happens in the caller afterwards. Cryptex detection and the enc_start adjustment were therefore derived from whatever the caller's output buffer happened to contain rather than from the packet that arrived. Two lines above the call site, the same extension length is already read from srtp, so the two paths disagreed. Read both fields from srtp instead. In-place callers are unaffected because srtp == rtp there. Add a regression test that unprotects a reference Cryptex packet into a zeroed output buffer distinct from the input. The existing not-in-place coverage copies the packet into a scratch input buffer and passes the original packet buffer as the output, so the output buffer already holds the ciphertext and the wrong-buffer read returns the right bytes by accident; that is why this was not caught. Without the fix the new test fails at offset 12 with c0 (the Cryptex profile, still ciphertext) where be (the restored plaintext profile) is expected. With the fix the full suite passes in both in-place and not-in-place modes.
pabuhler
approved these changes
Aug 10, 2026
pabuhler
left a comment
Member
There was a problem hiding this comment.
Thank you for reporting and fixing this.
There is a single white space needed for the format check, if you make that change then this ready for merging. (If you like I can also fix that).
I think it is fine to have a separate test at this point. I have made a note to change call_srtp_unprotect2() in such away that this would have been caught earlier.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to the report I sent to
libsrtp-security@lists.packetizer.comon 2026-08-03. Pascal asked for a public PR with a small failing test, so here it is. This is a correctness bug, not a security issue, and I am not requesting a CVE for it.What is wrong
srtp_cryptex_unprotect_init()reads the RTP header-extensionprofile_specificandlengthfields from thertpparameter. On the unprotect pathrtpis the output buffer:srtp_unprotect()documents thatrtp"can be the same assrtpto support in-place io", so a distinct output buffer is a supported calling mode. In that mode the output buffer has not been written when this function runs — the first write ismemcpy(rtp, srtp, enc_start)in the caller, atsrtp/srtp.c:2346, after the call returns.So Cryptex detection, and the
enc_startadjustment derived from the extension length, come from whatever the caller's output buffer happened to contain rather than from the packet that arrived.Two lines above the call site the same field is already read from
srtp:Both call sites (
srtp_unprotect_aeadandsrtp_unprotect) behave the same way.The fix
Read both fields from
srtp. In-place callers are unaffected, sincesrtp == rtpfor them.srtp_cryptex_protect_init()is left alone: on the protect pathrtpis the input, so its use ofrtpis correct.Why the existing tests did not catch it
call_srtp_unprotect2()intest/srtp_driver.c, underuse_srtp_not_in_place_io_api, does:The input is a scratch copy and the output is the original packet buffer — which still holds the ciphertext. Reading the profile from the output buffer therefore returns the correct bytes by accident, and
srtp_driver -npasses.The test
srtp_test_cryptex_not_in_place_distinct_buffer()unprotects the existing 1-byte-header-extension reference Cryptex packet into a zeroed buffer that is distinct from the input, which is what a caller handing libsrtp a fresh output buffer would do.Before the fix:
Offset 12 is the extension profile: the output still carries
c0de(Cryptex, i.e. the ciphertext form) wherebedeis expected, because Cryptex was never detected and the header extension was never restored.After the fix:
Verification
ctest: 12/12 pass, includingsrtp_driverandsrtp_driver_not_in_place_iosrtp_driver -vandsrtp_driver -v -n: both exit 0 with no failuresI could not run
clang-format— it is not available on this machine — so if CI flags formatting, tell me and I will fix it up.Happy to extend the test to the 2-byte and CSRC vectors, or to fold it into the existing
srtp_validate_cryptex()loop instead of a standalone test, if you would prefer either shape.