Skip to content

Commit 57ccaa3

Browse files
authored
Merge pull request #50 from DTW-Thalion/fix/cftree-remove-uaf
CFTree: fix CFTreeRemove so it actually unlinks the node
2 parents ce75bd1 + b1d77e0 commit 57ccaa3

3 files changed

Lines changed: 78 additions & 14 deletions

File tree

‎ChangeLog‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@
1313
multi-batch, and repeated-enumeration cases.
1414

1515
2026-06-28 Todd White <todd.white@thalion.global>
16+
* Source/CFTree.c (CFTreeRemove): Unlink the node from its parent's
17+
list of children (it walked the wrong list and never unlinked,
18+
leaving a dangling pointer in the parent), update the parent's
19+
_lastChild, and clear the node's _parent and _nextSibling.
20+
* Tests/CFTree/basic.m: Regression tests for CFTreeRemove; release
21+
the tree before its (unretained) children.
1622
* Source/CFBinaryHeap.c (CFBinaryHeapCreateCopy): Enlarge the copy's
1723
capacity to at least the source count before copying the values.
1824
(CFBinaryHeapRemoveMinimumValue): Do nothing on an empty heap

‎Source/CFTree.c‎

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -195,18 +195,39 @@ CFTreePrependChild (CFTreeRef tree, CFTreeRef newChild)
195195
void
196196
CFTreeRemove (CFTreeRef tree)
197197
{
198-
CFTreeRef child;
199-
CFTreeRef previousSibling;
200-
201-
previousSibling = NULL;
202-
child = tree->_firstChild;
203-
while (child != previousSibling)
204-
child = child->_nextSibling;
205-
206-
if (previousSibling)
207-
previousSibling->_nextSibling = tree->_nextSibling;
208-
209-
CFTreeFinalize (tree);
198+
CFTreeRef parent = tree->_parent;
199+
200+
if (parent == NULL)
201+
return;
202+
203+
/* Unlink the tree from its parent's list of children. */
204+
if (parent->_firstChild == tree)
205+
{
206+
parent->_firstChild = tree->_nextSibling;
207+
}
208+
else
209+
{
210+
CFTreeRef previousSibling = parent->_firstChild;
211+
212+
while (previousSibling != NULL
213+
&& previousSibling->_nextSibling != tree)
214+
previousSibling = previousSibling->_nextSibling;
215+
if (previousSibling != NULL)
216+
previousSibling->_nextSibling = tree->_nextSibling;
217+
}
218+
219+
/* Fix up the parent's last-child pointer if we removed the last child. */
220+
if (parent->_lastChild == tree)
221+
{
222+
CFTreeRef last = parent->_firstChild;
223+
224+
while (last != NULL && last->_nextSibling != NULL)
225+
last = last->_nextSibling;
226+
parent->_lastChild = last;
227+
}
228+
229+
tree->_parent = NULL;
230+
tree->_nextSibling = NULL;
210231
}
211232

212233
void

‎Tests/CFTree/basic.m‎

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,47 @@ int main (void)
5252
CFRelease (t2);
5353
}
5454

55+
{
56+
/* CFTreeRemove must unlink the node from its parent. It used to walk
57+
the wrong list, never unlink, and prematurely finalize the node,
58+
leaving a dangling pointer in the parent. */
59+
CFTreeRef r = CFTreeCreate (NULL, &ctxt);
60+
CFTreeRef r1 = CFTreeCreate (NULL, &ctxt);
61+
CFTreeRef r2 = CFTreeCreate (NULL, &ctxt);
62+
CFTreeRef r3 = CFTreeCreate (NULL, &ctxt);
63+
64+
CFTreeAppendChild (r, r1);
65+
CFTreeAppendChild (r, r2);
66+
CFTreeAppendChild (r, r3);
67+
68+
CFTreeRemove (r2);
69+
PASS_CF(CFTreeGetChildCount (r) == 2
70+
&& CFTreeGetNextSibling (r1) == r3
71+
&& CFTreeGetParent (r2) == NULL,
72+
"CFTreeRemove unlinks a child from its parent.");
73+
74+
/* Removing the last child updates _lastChild, so a following append
75+
links onto the right node. */
76+
CFTreeRemove (r3);
77+
CFTreeAppendChild (r, r2);
78+
PASS_CF(CFTreeGetChildCount (r) == 2
79+
&& CFTreeGetChildAtIndex (r, 1) == r2,
80+
"Append after removing the last child works.");
81+
82+
CFRelease (r);
83+
CFRelease (r1);
84+
CFRelease (r2);
85+
CFRelease (r3);
86+
}
87+
88+
/* Release the parent before its children: CFTreeAppendChild() does not
89+
retain the child, so the children must outlive the tree that links
90+
them. */
91+
CFRelease (tree);
92+
5593
CFRelease (child1);
5694
CFRelease (child2);
5795
CFRelease (child3);
58-
CFRelease (tree);
59-
96+
6097
return 0;
6198
}

0 commit comments

Comments
 (0)