Skip to content

[ML] Fail gracefully when restoring a categorizer with an out-of-range token ID - #3143

Open
edsavage wants to merge 4 commits into
elastic:mainfrom
edsavage:2875-categorizer-restore-token-bounds
Open

[ML] Fail gracefully when restoring a categorizer with an out-of-range token ID#3143
edsavage wants to merge 4 commits into
elastic:mainfrom
edsavage:2875-categorizer-restore-token-bounds

Conversation

@edsavage

@edsavage edsavage commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes a residual native crash (SIGSEGV) when restoring a categorizer from an inconsistent or truncated state document, seen on a 9.6.0-SNAPSHOT build (see #2875).

CTokenListDataCategorizerBase::acceptRestoreTraverser restores a list of tokens (m_TokenIdLookup) and a list of categories. Each restored category references token IDs (its base and common-unique token IDs). Those IDs are later used to index m_TokenIdLookup without a bounds check — e.g. in cacheReverseSearch:

const CTokenInfoItem& info{m_TokenIdLookup[tokenId]};      // out of range -> garbage
... m_ReverseSearchCreator->addInOrderCommonToken(m_TokenIdLookup[tokenId].str(), ...);

m_TokenIdLookup is a Boost multi-index container whose random-access operator[] performs no bounds checking. If a corrupt/truncated state leaves a category referencing a token ID at or beyond the end of the restored lookup, this is an out-of-bounds access that returns a garbage CTokenInfoItem; calling .str() on it then dereferences a bogus pointer, crashing the autodetect process with a SIGSEGV inside libc (strlen/memcpy) rather than failing the restore.

This matches the crash signature reported in #2875 on 9.6: si_signo 11, si_code 1 (SEGV_MAPERR), crash PC in libc.so.6. The graceful invalid-state handling added in #2895 / #2898 does not cover this particular path.

Fix

Validate, at the end of acceptRestoreTraverser, that every token ID referenced by a restored category exists in the restored token ID lookup. If any is out of range, log an error and fail the restore gracefully (returning false), consistent with the intent of #2895 / #2898, instead of proceeding with an inconsistent state that crashes later.

Testing

Added two unit tests in CTokenListDataCategorizerTest:

  • testRestoreWithInconsistentTokenIdFailsGracefully — a state with a single token but a category referencing token ID 5; restore now returns false (previously undefined behaviour / crash).
  • testRestoreWithConsistentTokenIdSucceeds — a valid state referencing the only valid token ID (0) still restores successfully, confirming the new check does not reject good state.

Both pass, along with the existing testPersist round-trip test (so valid states are unaffected).

Relates to #2875

…e token ID

An inconsistent or truncated categorizer state document can leave a
restored category referencing a token ID at or beyond the end of the
restored token ID lookup. That ID was later used to index the token ID
lookup unchecked (for example when building a reverse search), which is
an out-of-bounds access that can crash the autodetect process with a
SIGSEGV inside libc rather than failing the restore.

Validate, at the end of CTokenListDataCategorizerBase::acceptRestoreTraverser,
that every token ID referenced by a restored category exists in the
restored token ID lookup, and fail the restore gracefully if not. This is
consistent with the graceful invalid-state handling added in elastic#2895/elastic#2898.

Relates to elastic#2875

Co-authored-by: Cursor <cursoragent@cursor.com>
@elasticsearchmachine

Copy link
Copy Markdown

Pinging @elastic/ml-core (Team:ML)

@elasticsearchmachine

Copy link
Copy Markdown

Hi @edsavage, I've created a changelog YAML for you.

A category's ordered common token begin/end indices are restored directly
from the state document and used to index the category's base token list
unchecked (for example in updateOrderedCommonTokenIds and
containsCommonInOrderTokensInOrder). A corrupt or truncated state can set
an end index beyond the base token list, causing an out-of-bounds access.

Extend the restore validation to also require, per category, that the
ordered common token bounds describe a valid sub-range of the base token
list (begin <= end <= baseTokenIds().size()), failing the restore
gracefully otherwise.

Relates to elastic#2875

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@edsavage edsavage added auto-backport Automatically merge backport PRs when CI passes v9.5.1 v9.4.6 v8.19.20 v9.5.2 and removed v9.5.1 labels Aug 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto-backport Automatically merge backport PRs when CI passes >bug :ml v8.19.20 v9.4.6 v9.5.2 v9.6.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants