Repository navigation
CFAttributedString: fix allocation, use-after-free and OOB bugs - #53
Merged
fredkiefer merged 3 commits intoAug 16, 2026
Merged
Conversation
DTW-Thalion
force-pushed
the
fix/cfattributedstring-alloc
branch
from
July 24, 2026 15:26
b74e0d6 to
da3c291
Compare
Contributor
|
A test failed here as well. |
Contributor
Author
I will look at these three within the hour - just finishing up something in libs-gui. |
Contributor
|
No worries, I think it will take quite a while until all of these are getting reviewed anyways lol. At least there is a proper CI now. |
CFMutableAttributedString was unusable: every operation corrupted memory.
* CFATTRIBUTESTRING_SIZE / CFMUTABLEATTRIBUTESTRING_SIZE computed the
instance's extra size as "sizeof(CFRuntimeClass) - sizeof(struct ...)"
instead of "sizeof(struct ...) - sizeof(CFRuntimeBase)", undersizing
the object so CFAttributedStringCreateMutable() wrote its own ivars
past the end of the allocation.
* CFAttributedStringGetBlankAttribute() created a dictionary, cached a
*copy* of it, released the original, and then dereferenced the
released original (use-after-free). Keep a reference to the cached
copy instead.
* InsertAttributesAtIndex() (grow) and RemoveAttributesAtIndex() (shrink)
passed the element count, not a byte count, to CFAllocatorReallocate()
and never updated _attribCap, so the attribute array was reallocated
far too small and overrun.
* CFAttributedStringCoalesce() compared each entry with its predecessor
starting at index range.location, reading element [-1] when the range
began at 0.
The attribute cache relies on CFBag being a counted collection, but
CFBagAddValue() did not increase the count of an already-present value
(GSHashTableAddValue only ever inserts new entries), so uncaching a shared
attribute -- e.g. the blank attribute -- freed it while it was still in
use. Add GSHashTableAddValueCounted() and use it from CFBagAddValue() so
the count tracks the number of references.
With these fixes the CFAttributedString tests pass (creation, attribute
get/set, and growing the attribute array past its initial capacity);
previously they crashed. A separate imbalance in the attribute cache's
reference accounting still leaks some cached attributes; that is left for
a follow-up.
CFAttributedStringSetAttributes passed the last touched run index to CFAttributedStringCoalesce as the length of the range to coalesce, so the scan could run past the end of the attribute array, comparing and then uncaching runs that were never allocated. That freed an attribute dictionary still referenced by a live run and later faulted when the run was uncached again. The coalesce scan now stops at the current run count. While here, refresh the local array pointer after an insertion that may reallocate the run array, and correct the run-removal shift, which stopped one element short and dropped the last run.
DTW-Thalion
force-pushed
the
fix/cfattributedstring-alloc
branch
from
July 27, 2026 17:45
64a887a to
97af53f
Compare
fredkiefer
approved these changes
Aug 16, 2026
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.
CFMutableAttributedString was unusable: every operation corrupted memory.
CFATTRIBUTESTRING_SIZE / CFMUTABLEATTRIBUTESTRING_SIZE computed the
instance's extra size as "sizeof(CFRuntimeClass) - sizeof(struct ...)"
instead of "sizeof(struct ...) - sizeof(CFRuntimeBase)", undersizing
the object so CFAttributedStringCreateMutable() wrote its own ivars
past the end of the allocation.
CFAttributedStringGetBlankAttribute() created a dictionary, cached a
copy of it, released the original, and then dereferenced the
released original (use-after-free). Keep a reference to the cached
copy instead.
InsertAttributesAtIndex() (grow) and RemoveAttributesAtIndex() (shrink)
passed the element count, not a byte count, to CFAllocatorReallocate()
and never updated _attribCap, so the attribute array was reallocated
far too small and overrun.
CFAttributedStringCoalesce() compared each entry with its predecessor
starting at index range.location, reading element [-1] when the range
began at 0.
The attribute cache relies on CFBag being a counted collection, but
CFBagAddValue() did not increase the count of an already-present value
(GSHashTableAddValue only ever inserts new entries), so uncaching a shared
attribute -- e.g. the blank attribute -- freed it while it was still in
use. Add GSHashTableAddValueCounted() and use it from CFBagAddValue() so
the count tracks the number of references.
With these fixes the CFAttributedString tests pass (creation, attribute
get/set, and growing the attribute array past its initial capacity);
previously they crashed. A separate imbalance in the attribute cache's
reference accounting still leaks some cached attributes; that is left for
a follow-up.