From 19f8532921d6f797fc555d8cc36e24775b3ef57a Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Wed, 12 Aug 2026 22:43:24 -0700 Subject: [PATCH 1/2] Guard against a null hook list in HttpHookState::Scope::init 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. --- src/api/HttpHookState.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/api/HttpHookState.cc b/src/api/HttpHookState.cc index 1221bfa0b40..cdcf5368888 100644 --- a/src/api/HttpHookState.cc +++ b/src/api/HttpHookState.cc @@ -75,10 +75,11 @@ HttpHookState::getNext() void HttpHookState::Scope::init(HttpAPIHooks const *feature_hooks, TSHttpHookID id) { + // operator[] yields nullptr for an out of range id, which candidate() already tolerates. _hooks = (*feature_hooks)[id]; _p = nullptr; - _c = _hooks->head(); + _c = _hooks ? _hooks->head() : nullptr; } APIHook const * From daee03ea63398113e76613a96a6d2f6726ad8c92 Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Thu, 13 Aug 2026 10:04:35 -0700 Subject: [PATCH 2/2] Correct the rationale in the comment 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. --- src/api/HttpHookState.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/api/HttpHookState.cc b/src/api/HttpHookState.cc index cdcf5368888..d6c988fc73c 100644 --- a/src/api/HttpHookState.cc +++ b/src/api/HttpHookState.cc @@ -75,7 +75,8 @@ HttpHookState::getNext() void HttpHookState::Scope::init(HttpAPIHooks const *feature_hooks, TSHttpHookID id) { - // operator[] yields nullptr for an out of range id, which candidate() already tolerates. + // operator[] yields nullptr for an out of range id, and a release build can reach that: the only thing rejecting a + // bad hook id upstream is an assert that compiles out. Leave the scope empty rather than dereferencing null. _hooks = (*feature_hooks)[id]; _p = nullptr;