Skip to content

Commit 73d73d6

Browse files
committed
fix: Document#remove_namespaces! use-after-free bug
1 parent 5f58b34 commit 73d73d6

File tree

3 files changed

+68
-1
lines changed

3 files changed

+68
-1
lines changed

ext/nokogiri/xml_document.c

+5-1
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,11 @@ recursively_remove_namespaces_from_node(xmlNodePtr node)
104104
(node->type == XML_XINCLUDE_START) ||
105105
(node->type == XML_XINCLUDE_END)) &&
106106
node->nsDef) {
107-
xmlFreeNsList(node->nsDef);
107+
xmlNsPtr curr = node->nsDef;
108+
while (curr) {
109+
noko_xml_document_pin_namespace(curr, node->doc);
110+
curr = curr->next;
111+
}
108112
node->nsDef = NULL;
109113
}
110114

test/test_compaction.rb

+22
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,27 @@
3939

4040
doc.at_xpath("//root:first", "root" => "http://example.com/root").namespace_scopes.inspect
4141
end
42+
43+
it "remove_namespaces!" do
44+
skip unless GC.respond_to?(:verify_compaction_references)
45+
46+
doc = Nokogiri::XML(<<~XML)
47+
<root xmlns:a="http://a.flavorjon.es/" xmlns:b="http://b.flavorjon.es/">
48+
<a:foo>hello from a</a:foo>
49+
<b:foo>hello from b</b:foo>
50+
<container xmlns:c="http://c.flavorjon.es/">
51+
<c:foo c:attr='attr-value'>hello from c</c:foo>
52+
</container>
53+
</root>
54+
XML
55+
56+
namespaces = doc.root.namespaces
57+
namespaces.each(&:inspect)
58+
doc.remove_namespaces!
59+
60+
GC.verify_compaction_references(double_heap: true, toward: :empty)
61+
62+
namespaces.each(&:inspect)
63+
end
4264
end
4365
end

test/test_memory_leak.rb

+41
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,47 @@ def test_leaking_namespace_node_strings_with_prefix
191191
end
192192
end
193193

194+
def test_document_remove_namespaces_with_ruby_objects
195+
xml = <<~XML
196+
<root xmlns:a="http://a.flavorjon.es/" xmlns:b="http://b.flavorjon.es/">
197+
<a:foo>hello from a</a:foo>
198+
<b:foo>hello from b</b:foo>
199+
<container xmlns:c="http://c.flavorjon.es/">
200+
<c:foo c:attr='attr-value'>hello from c</c:foo>
201+
</container>
202+
</root>
203+
XML
204+
205+
20.times do
206+
10_000.times do
207+
doc = Nokogiri::XML::Document.parse(xml)
208+
doc.namespaces.each(&:inspect)
209+
doc.remove_namespaces!
210+
end
211+
puts MemInfo.rss
212+
end
213+
end
214+
215+
def test_document_remove_namespaces_without_ruby_objects
216+
xml = <<~XML
217+
<root xmlns:a="http://a.flavorjon.es/" xmlns:b="http://b.flavorjon.es/">
218+
<a:foo>hello from a</a:foo>
219+
<b:foo>hello from b</b:foo>
220+
<container xmlns:c="http://c.flavorjon.es/">
221+
<c:foo c:attr='attr-value'>hello from c</c:foo>
222+
</container>
223+
</root>
224+
XML
225+
226+
20.times do
227+
20_000.times do
228+
doc = Nokogiri::XML::Document.parse(xml)
229+
doc.remove_namespaces!
230+
end
231+
puts MemInfo.rss
232+
end
233+
end
234+
194235
def test_leaking_dtd_nodes_after_internal_subset_removal
195236
# see https://github.com/sparklemotion/nokogiri/issues/1784
196237
100_000.times do |i|

0 commit comments

Comments
 (0)