2026-09-25
While deprecated, CFXMLParser is still in macOS 27, and dies on seven bytes. Consider this code:
#include <CoreFoundation/CoreFoundation.h>
#include <CoreFoundation/CFXMLParser.h>
int main(void) {
// "<a>" in UTF-16 LE + a single dangling byte
const UInt8 bytes[] = {'<', 0, 'a', 0, '>', 0, 'X'};
CFDataRef data = CFDataCreate(NULL, bytes, sizeof bytes);
CFXMLTreeRef tree = CFXMLTreeCreateFromData(
NULL, data, NULL, kCFXMLParserSkipWhitespace,
kCFXMLNodeCurrentVersion);
if (tree) CFRelease(tree);
CFRelease(data);
return 0;
}
Compile with clang -framework CoreFoundation poc.c -o poc, then run ./poc. On macOS 27.0 (26A428, arm64), the process throws the following exception:
*** Terminating app due to uncaught exception 'NSMallocException',
reason: 'Failed to grow buffer to 70368744177664'
6. CoreFoundation growCharacterBuffer CFXMLInputStream.c
5. CoreFoundation getCharacterGuts CFXMLInputStream.c
4. CoreFoundation _inputStreamSkipWhitespace CFXMLInputStream.c
3. CoreFoundation parseTagContent CFXMLParser.c
2. CoreFoundation CFXMLParserParse CFXMLParser.c
1. CoreFoundation CFXMLTreeCreateFromDataWithError CFXMLParser.c
The whole bug is a confusion between bytes left versus UTF-16 characters left. The result is repeated buffer doubling until allocation fails, and a local process crash if the exception is uncaught.
At a high level, the bug essentially is:
while some bytes remain: # bytes based: 6 < 7, true
u = read_unichar() # needs 2 bytes, only 1 left => consumes nothing
if not u: # 0 complete characters
double_buffer() # wrong remedy
The detailed behavior is explained by the published CFXMLInputStream.c, called by CFXMLParser.c (the sources predate macOS 27).

Reading the last frames before the crash:
(4) _inputStreamSkipWhitespace requests the next character while a mark is set
(5) At byte offset 6 of 7:
- getCharacterGuts evaluates (6 >= 7) as false and doesn't report EOF.
- getCharacterGuts then calls fillCharacterBuffer which calls loadCharacters which computes (7 - 6) / sizeof(UniChar) = 1 / 2 = 0 complete characters and leaves the byte cursor at 6.
- fillCharacterBuffer treats the empty read as a need for more space and grows the buffer using growCharacterBuffer.
- The parser retries with the same byte unread.
(6) growCharacterBuffer doubles the buffer on each retry until an allocation request fails and CoreFoundation raises NSMallocException.
I'm not aware of an exposed path in an existing app.