Guard against a null hook list in HttpHookState::Scope::init - #13542
Draft
bryancall wants to merge 2 commits into
Draft
Guard against a null hook list in HttpHookState::Scope::init#13542bryancall wants to merge 2 commits into
bryancall wants to merge 2 commits into
Conversation
FeatureAPIHooks::operator[] returns nullptr for an out of range hook id, but Scope::init dereferenced the result unconditionally. HttpSM::state_api_callout has a default case that sets the hook id to -1 and then falls through to HttpHookState::init. The ink_assert that guards it compiles out in a release build, so a release binary reaching that path dereferences null instead of stopping. Scope::candidate already tests _hooks for null before using it, so the rest of the class is written to tolerate an empty scope. This makes init agree with it.
Scope::candidate tests _hooks for null to handle a scope that was cleared, which happens on any transaction without a session or transaction hook container. That is a routine state and has nothing to do with an out of range id, so citing it made an abnormal condition look ordinary. Say instead why a bad id can arrive in a release build.
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.
FeatureAPIHooks::operator[]returnsnullptrfor an out of range hook id:but
HttpHookState::Scope::initdereferenced the result without testing it:HttpSM::do_api_callout_internalhas adefault:case that sets the hook id to -1 and then falls through toHttpHookState::init:ink_assertcompiles out in a release build, so a release binary that reaches that case does not stop there. It callsinitwith an invalid id,operator[]hands backnullptr, and the dereference follows.ProxySession::do_api_callouthas the same debug-only-assert-then-init shape, so the guard covers two call sites.What this does and does not fix
To be precise about the benefit: this does not make a release build survive that path. With the scope left empty,
getNext()returnsnullptr,state_api_callouttakes the no-hooks path, andhandle_api_returnswitches on the sameapi_next_actionthat fell through in the first place, landing ondefault: ink_release_assert(!"Not reached");. The process still aborts a few frames later.What it does deliver is removing the undefined behavior: a null dereference in
Scope::initbecomes a deterministic, named abort that points at the actual bad state. That is a better crash to triage, and it makesScope::inithonor the nullptr contractoperator[]already documents.Scope
This is defense in depth, not a fix for an observed crash.
StateMachineAction_thas 13 relevant values and the switch handles all of them, so thedefault:is unreachable given non-corrupt state. I have no reproduction, and I am not claiming this explains any crash I have seen.A reasonable alternative is to make the failure explicit at its source instead, by promoting the
ink_assertinHttpSM::do_api_callout_internaltoink_release_assert. That would make the invariant local rather than depending on two switch case lists staying in sync. Happy to go that way instead if maintainers prefer it.Testing
Built with the
dev-asanpreset (-fsanitize=address,undefined) on Fedora 44 / gcc 16,-Werrorclean.traffic_server -R 1): 67 passed,REGRESSION_TEST DONE: PASSEDremap_aclandremap_acl_yamlexcluded for runtime)The 65 autest failures are pre-existing environment failures on this box, not regressions. I built unmodified master at the same base commit (
8aebe2c706) and ran the identical 561-test selection: it produces the same 476/65/40 and the same 61 distinct failing tests, so the failing set matches the baseline exactly. They are concentrated in TLS, QUIC/HTTP3, and config-reload tests.