Repository navigation
🐛 fix(css): keep merge property scan in the body - #972
Merged
gaborbernat merged 1 commit intoOct 2, 2026
Merged
Conversation
gaborbernat
force-pushed
the
fix/css-minify-unbounded-reads
branch
from
October 1, 2026 23:25
62b7e87 to
920e8ee
Compare
Merging this PR will not alter performance
Comparing Footnotes
|
gaborbernat
force-pushed
the
fix/css-minify-unbounded-reads
branch
2 times, most recently
from
October 1, 2026 23:49
2f7cb78 to
92db80f
Compare
gaborbernat
enabled auto-merge (squash)
October 2, 2026 00:08
gaborbernat
force-pushed
the
fix/css-minify-unbounded-reads
branch
from
October 2, 2026 00:21
92db80f to
b7b5796
Compare
Before merging a rule into an earlier one, the minifier reads property names back out of each rendered body to find conflicts. The scan searched for a declaration's ':' with no length bound, and the parser keeps a ';' inside [...] blocks and escapes, so a piece such as the tail of c:d[;e] has no ':' and sent the search past the heap buffer. The scan now reads one declaration at a time inside the body and counts a colon-less piece as a name, which can only block a merge. Ending each piece at its own ';' and clamping a stray ')' at depth 0 also stops a later property from hiding in the previous name, which had let a merge move a rule past one that overrides it. cbuf_put_run now skips an empty run, whose source is a NULL buffer that glibc's memcpy declares non-null, as sbuf_put_run already does.
gaborbernat
force-pushed
the
fix/css-minify-unbounded-reads
branch
from
October 2, 2026 00:24
b7b5796 to
0244ff7
Compare
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.
Before the CSS minifier merges a rule into an earlier one, it checks that no rule in between sets a conflicting property, reading the names back out of each rendered declaration body.
css_body_next_propsearched for a declaration's:with no length bound. When the last;-separated piece of a body held no:, as in[:;a,c:d[;e]orc:d\;e, the search ran past the body into adjacent heap memory until some later:turned up.clean.minify_cssand<style>minification underMinify(minify_css=CSSMinify())then crash.The scan now reads one declaration at a time and stops at the body's end. A
;at paren depth 0 ends a declaration, its name runs to the first:, and a piece with no:counts as one name. The parser keeps;inside[...]blocks and escapes, so the scan can split one declaration into several pieces; an extra name can only add a conflict and block a merge. A stray)now leaves the depth at 0, ascss_read_untildoes.The old scan also crossed a
;while looking for the next:, which hid properties. In.t{color:blue}.u{c:d[;e];color:red}.t{color:green}it reade];coloras one name, missed that.usetscolor, and folded the last.tinto the first, so red beat green on an element matching both. A stray)in.u{c:d);color:red}did the same. Both now keep the three rules apart.The other minifiers take property names from their parser instead of rescanning output. tdewolff/parse ends a declaration on
;or}at nesting level 0 or at EOF and reports one without a:as an error grammar. rust-cssparser callsexpect_coloninsideparse_until_after(Semicolon), which skips nested blocks and stops at EOF.css-tree, which csso uses, checks
eofand requires the colon witheat(Colon). lightningcss merges only adjacent rules and compares parsedPropertyvalues. clean-css records the name at the:inside a loop bounded by the source length.cbuf_put_runalso handedmemcpya NULL source for an empty run. A nested rule with no selector (a{{b:c}}) and a declaration with no value (a{b:}) each render a buffer that never allocated. Its length is 0, because acss_bufgrows its length only after a successful reserve, so nothing was read; glibc still declaresmemcpy's source non-null, which makes the call undefined.cbuf_put_runnow returns early on an empty run, assbuf_put_runalready does.This fixes the CSS minifier heap over-read on a declaration piece without a colon, tracked privately in GHSA-mg53-v965-qg5r.