oauth2: fix crash in decryptToken on spurious AES-CBC PKCS#7 success#44677
Closed
Copilot wants to merge 3 commits into
Closed
oauth2: fix crash in decryptToken on spurious AES-CBC PKCS#7 success#44677Copilot wants to merge 3 commits into
Copilot wants to merge 3 commits into
Conversation
Agent-Logs-Url: https://github.com/envoyproxy/envoy/sessions/5d7ded04-e0ba-4e55-b4cc-6e3d97d2ef33 Co-authored-by: phlax <454682+phlax@users.noreply.github.com>
Agent-Logs-Url: https://github.com/envoyproxy/envoy/sessions/5d7ded04-e0ba-4e55-b4cc-6e3d97d2ef33 Co-authored-by: phlax <454682+phlax@users.noreply.github.com>
Copilot
AI
changed the title
[WIP] Fix latent bug in OAuth2Filter decryptToken function
oauth2: fix crash in decryptToken on spurious AES-CBC PKCS#7 success
Apr 27, 2026
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.
OAuth2Filter::decryptTokenonly guarded againstEVP_DecryptFinal_exreturning failure. When AES-CBC decrypts ciphertext under the wrong key, PKCS#7 padding is coincidentally valid ~1/256 of the time, producing arbitrary binary bytes. Those bytes wereStrJoin-ed back into theCookie:header viasetReferenceKey, trippingHeaderStringValidator::assertValid()and crashing Envoy. Observed flaking inoauth_integration_testduring HMAC-secret rotation.Changes
source/extensions/filters/http/oauth2/filter.cc— After AES-CBC decryption, validate the plaintext withHttp::HeaderUtility::headerValueIsValid(the same predicateHeaderStringValidatoruses). Binary/invalid plaintext is treated identically to an explicit decryption failure: log the error and return the original ciphertext unchanged, letting HMAC validation naturally fail and re-redirect.test/extensions/filters/http/oauth2/filter_test.cc— Five new tests:DecryptTokenEmpty/DecryptTokenSameSecret— baseline correctnessDecryptTokenDecryptionFails— legacy/unencrypted token (explicit PKCS#7 failure)DecryptTokenSpuriousSuccessReturnsOriginalInput— decryption "succeeds" but plaintext contains bytes invalid as an HTTP header value → original ciphertext returnedGarbagePlaintextCookieDoesNotCrash— end-to-end viadecodeHeaders; confirms no crash, error log emitted, andRefreshTokencookie value preservedchangelogs/current.yaml— Bug fix entry underoauth2.Warning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
dl.google.com/build/bazel_root/install/fb2a7f6d344d2f4e335882534df59296/embedded_tools/jdk/bin/java bazel(envoy) --add-opens=java.base/java.lang=ALL-UNNAMED -Xverify:none -Djava.util.logging.config.file=/build/bazel_root/base/javalog.properties -Dcom.google.devtools.build.lib.util.LogHandlerQuerier.class=com.google.devtools.build.lib.util.SimpleLogHandler$HandlerQuerier -XX:-MaxFDLimit -Djava.library.path=/build/bazel_root/install/fb2a7f6d344d2f4e335882534df59296/embedded_tools/jdk/lib:/build/bazel_root/install/fb2a7f6d344d2f4e335882534df59296/embedded_tools/jdk/lib/server:/build/bazel_root/install/fb2a7f6d344d2f4e335882534df59296/ -Dfile.encoding=ISO-8859-1 -Duser.country= -Duser.language= -Duser.variant= -Xmx3g -DBAZEL_TRACK_SOURCE_DIRECTORIES=1 -Djavax.net.ssl.trustStore=/tmp/custom-cacerts -Djavax.net.ssl.trustStorePassword=changeit -jar /build/bazel_root/install/fb2a7f6d344d2f4e335882534df59296/A-server.jar --max_idle_secs=10800 --noshutdown_on_low_sys_mem --connect_timeout_secs=30(dns block)ftpmirror.gnu.org/build/bazel_root/install/fb2a7f6d344d2f4e335882534df59296/embedded_tools/jdk/bin/java bazel(envoy) --add-opens=java.base/java.lang=ALL-UNNAMED -Xverify:none -Djava.util.logging.config.file=/build/bazel_root/base/javalog.properties -Dcom.google.devtools.build.lib.util.LogHandlerQuerier.class=com.google.devtools.build.lib.util.SimpleLogHandler$HandlerQuerier -XX:-MaxFDLimit -Djava.library.path=/build/bazel_root/install/fb2a7f6d344d2f4e335882534df59296/embedded_tools/jdk/lib:/build/bazel_root/install/fb2a7f6d344d2f4e335882534df59296/embedded_tools/jdk/lib/server:/build/bazel_root/install/fb2a7f6d344d2f4e335882534df59296/ -Dfile.encoding=ISO-8859-1 -Duser.country= -Duser.language= -Duser.variant= -Xmx3g -DBAZEL_TRACK_SOURCE_DIRECTORIES=1 -Djavax.net.ssl.trustStore=/tmp/custom-cacerts -Djavax.net.ssl.trustStorePassword=changeit -jar /build/bazel_root/install/fb2a7f6d344d2f4e335882534df59296/A-server.jar --max_idle_secs=10800 --noshutdown_on_low_sys_mem --connect_timeout_secs=30(dns block)mirror.bazel.build/build/bazel_root/install/fb2a7f6d344d2f4e335882534df59296/embedded_tools/jdk/bin/java bazel(envoy) --add-opens=java.base/java.lang=ALL-UNNAMED -Xverify:none -Djava.util.logging.config.file=/build/bazel_root/base/javalog.properties -Dcom.google.devtools.build.lib.util.LogHandlerQuerier.class=com.google.devtools.build.lib.util.SimpleLogHandler$HandlerQuerier -XX:-MaxFDLimit -Djava.library.path=/build/bazel_root/install/fb2a7f6d344d2f4e335882534df59296/embedded_tools/jdk/lib:/build/bazel_root/install/fb2a7f6d344d2f4e335882534df59296/embedded_tools/jdk/lib/server:/build/bazel_root/install/fb2a7f6d344d2f4e335882534df59296/ -Dfile.encoding=ISO-8859-1 -Duser.country= -Duser.language= -Duser.variant= -Xmx3g -DBAZEL_TRACK_SOURCE_DIRECTORIES=1 -Djavax.net.ssl.trustStore=/tmp/custom-cacerts -Djavax.net.ssl.trustStorePassword=changeit -jar /build/bazel_root/install/fb2a7f6d344d2f4e335882534df59296/A-server.jar --max_idle_secs=10800 --noshutdown_on_low_sys_mem --connect_timeout_secs=30(dns block)pkgconfig.freedesktop.org/build/bazel_root/install/fb2a7f6d344d2f4e335882534df59296/embedded_tools/jdk/bin/java bazel(envoy) --add-opens=java.base/java.lang=ALL-UNNAMED -Xverify:none -Djava.util.logging.config.file=/build/bazel_root/base/javalog.properties -Dcom.google.devtools.build.lib.util.LogHandlerQuerier.class=com.google.devtools.build.lib.util.SimpleLogHandler$HandlerQuerier -XX:-MaxFDLimit -Djava.library.path=/build/bazel_root/install/fb2a7f6d344d2f4e335882534df59296/embedded_tools/jdk/lib:/build/bazel_root/install/fb2a7f6d344d2f4e335882534df59296/embedded_tools/jdk/lib/server:/build/bazel_root/install/fb2a7f6d344d2f4e335882534df59296/ -Dfile.encoding=ISO-8859-1 -Duser.country= -Duser.language= -Duser.variant= -Xmx3g -DBAZEL_TRACK_SOURCE_DIRECTORIES=1 -Djavax.net.ssl.trustStore=/tmp/custom-cacerts -Djavax.net.ssl.trustStorePassword=changeit -jar /build/bazel_root/install/fb2a7f6d344d2f4e335882534df59296/A-server.jar --max_idle_secs=10800 --noshutdown_on_low_sys_mem --connect_timeout_secs=30(dns block)If you need me to access, download, or install something from one of these locations, you can either:
Original prompt
Background
The OAuth2 HTTP filter encrypts the access / id / refresh token cookies with AES-256-CBC (introduced in #39059). On every request
OAuth2Filter::decryptAndUpdateOAuthTokenCookiesdecrypts those cookies and rewrites theCookie:header with the plaintext values before forwarding upstream.There is a latent bug in
OAuth2Filter::decryptTokenthat can crash Envoy with aHeaderStringValidatorassertion failure. It has been observed flaking CI onrelease/v1.37in//test/extensions/filters/http/oauth2:oauth_integration_test:The exact same code path is present on
mainandrelease/v1.38— seesource/extensions/filters/http/oauth2/filter.ccdecryptToken(the function that returns either the decrypted plaintext or, on failure, the original encrypted input).Root cause
decryptTokenonly handles the case whereEVP_DecryptFinal_exreturns failure (PKCS#7 padding check fails). However when the wrong key is used to AES-CBC-decrypt arbitrary ciphertext,EVP_DecryptFinal_exwill spuriously succeed with probability ~1/256 — whenever the last byte of the random "plaintext" happens to be0x01, plus smaller cases for0x02 0x02, etc. In that casedecrypt()returns success and the "plaintext" is arbitrary binary bytes (containing\n,\0,;, control chars, …).That binary string is then
StrJoined back into a newCookie:header value and pushed into the request viasetReferenceKey(Http::Headers::get().Cookie, …), which tripsHeaderString::assertValid()because the value is no longer a valid header field-value.In the failing log,
BearerTokenis unchanged (decryption properly failed → returned as-is) butRefreshTokenhas been replaced by 13 bytes of garbage — exactly the asymmetry expected from this race.The flakiness on
oauth_integration_testAuthenticationFlow/RefreshTokenFlowis explained by the SDS hmac-secret reload step (hmac_secret→hmac_secret_1): after rotation, the test re-uses the previously-encryptedTEST_ENCRYPTED_*cookies, so they get AES-decrypted under a different key on subsequent requests — effectively rolling the 1/256 dice on every test run.This affects two real production scenarios as well:
oauth2_encrypt_tokens-enabled build.Both are already commented as supported in
decryptToken; the comment is correct, the implementation is not.Fix
Validate the decrypted plaintext using
Http::HeaderUtility::headerValueIsValidbefore treating decryption as successful. Anything else means we have the wrong key (or unencrypted input), so fall through to the existing legacy / wrong-secret behavior — return the original encrypted input unchanged and let HMAC validation naturally fail and re-redirect.The change is in
source/extensions/filters/http/oauth2/filter.cc: