ICU-23531 Validate RBBI state table row fields and section offsets by ManoharPaturi · Pull Request #4257 · unicode-org/icu · GitHub
Skip to content

ICU-23531 Validate RBBI state table row fields and section offsets - #4257

Open
ManoharPaturi wants to merge 1 commit into
unicode-org:mainfrom
ManoharPaturi:icu-23531-rbbi-table-validation
Open

ManoharPaturi wants to merge 1 commit into
unicode-org:mainfrom
ManoharPaturi:icu-23531-rbbi-table-validation

Conversation

@ManoharPaturi

Copy link
Copy Markdown

Description

ICU-23531 reports a heap out of bounds write reachable from ubrk_openBinaryRules() when the compiled break rule blob contains crafted state table rows. This change validates the row fields and section offsets of rule data when it is loaded, so malformed data is rejected with U_INVALID_FORMAT_ERROR instead of being executed.

The unvalidated values, all read straight from the data blob:

  • RuleBasedBreakIterator::handleNext() writes fLookAheadMatches[row->fLookAhead]. The array is allocated with fLookAheadResultsSize entries from the same blob, and the only existing check is a U_ASSERT, which compiles away in release builds.
  • handleNext() also reads fLookAheadMatches[row->fAccepting], row->fNextState[category] (where category comes from the trie and is only asserted below fCatCount), and fRuleStatusTable[row->fTagsIdx] and the counted entries that follow it.
  • The section offsets (fFTable, fRTable, fTrie, fRuleSource, fStatusTable) are used as pointer offsets without checking that the section stays inside the buffer.

Reproducer, run against a build with address sanitizer, using rules compiled by ICU itself with one look-ahead slot rewritten from its real value to 200 while fLookAheadResultsSize is 3:

WRITE of size 4 at ... thread T0
    #0 icu_79::RuleBasedBreakIterator::handleNext()
    #1 icu_79::RuleBasedBreakIterator::BreakCache::populateFollowing()
    #2 icu_79::RuleBasedBreakIterator::next()
    #3 main rbbi_repro.cpp:78
0x... is located 788 bytes after 12-byte region
allocated by thread T0 here:
    #1 icu_79::RuleBasedBreakIterator::RuleBasedBreakIterator(unsigned char const*, unsigned int, UErrorCode&)
    #2 ubrk_openBinaryRules_79
    #3 main rbbi_repro.cpp:69

After this change the same program prints openBinaryRules: U_INVALID_FORMAT_ERROR and exits cleanly.

What the new validation checks in RBBIDataWrapper::init():

  • Every section offset plus length stays within fLength, checked in 64 bit arithmetic.
  • The declared rows of each state table fit within the table section, and each row is long enough for the fixed fields plus one entry per character category.
  • For every row: a look-ahead or accepting value above ACCEPTING_UNCONDITIONAL must be below fLookAheadResultsSize; a tag index must be inside the rule status table including its counted entries; every next-state value must address a row of its own table.
  • Every value in the character category trie must be below fCatCount.

The checks are permissive where the format allows it, for example section sizes are 8 byte aligned by the rule builder, so any padding remains valid.

Note: this complements the header offset checking in PR #3961, which covers the same init function from the init time out of bounds read half of ICU-23531. The row field validation and the trie value check here cover the run time write half; either PR is self sufficient.

Testing

RBBIAPITest/TestBinaryRuleRowFieldValidation was added. It compiles rules containing a look-ahead, takes them apart as a binary blob, and reopens the blob after corrupting, one at a time, a look-ahead slot, a next-state value, a tag index, and the trie section offset. Each variant must fail with U_INVALID_FORMAT_ERROR.

intltest rbbi passes in full with this change, so all shipped break iterator data still loads. cintltst also passes.

ICU-23531

Checklist

  • Required: Issue filed: ICU-23531
  • Required: The PR title must be prefixed with a JIRA Issue number. Example: "ICU-NNNNN Fix xyz"
  • Required: Each commit message must be prefixed with a JIRA Issue number. Example: "ICU-NNNNN Fix xyz"
  • Issue accepted (done by Technical Committee after discussion)
  • Tests included, if applicable
  • API docs and/or User Guide docs changed or added, if applicable
  • Approver: Feel free to merge on my behalf

Row fields read from compiled break rule data index run time arrays:
RuleBasedBreakIterator::handleNext() writes fLookAheadMatches at the
index taken from a row's look-ahead field, and reads next-state values
and rule status entries the same way. None of these were checked
against the sizes declared in the data, so a crafted rule blob opened
with ubrk_openBinaryRules() caused a heap out of bounds write while
iterating text.

Check the section offsets and lengths against the total data length
before use, and check every row of both state tables: look-ahead slots
and accepting values must fit the look-ahead results array, tag indexes
must fit the rule status table including the counted entries, next
state values must address rows of their own table, and trie values must
be below the category count. Malformed data now fails with
U_INVALID_FORMAT_ERROR.
Copilot AI balanced review requested due to automatic review settings October 4, 2026 03:06

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@CLAassistant

CLAassistant commented Oct 4, 2026 •

Copy link
Copy Markdown

@ManoharPaturi

Copy link
Copy Markdown
Author

@markusicu markusicu self-assigned this Oct 8, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants