Skip to content

Fix GH-21682: Add NOT_SERIALIZABLE to ZipArchive, XMLWriter, XMLReader, SNMP, tidy, tidyNode#21694

Open
iliaal wants to merge 4 commits intophp:masterfrom
iliaal:fix/gh-21682-ziparchive-not-serializable
Open

Fix GH-21682: Add NOT_SERIALIZABLE to ZipArchive, XMLWriter, XMLReader, SNMP, tidy, tidyNode#21694
iliaal wants to merge 4 commits intophp:masterfrom
iliaal:fix/gh-21682-ziparchive-not-serializable

Conversation

@iliaal
Copy link
Copy Markdown
Contributor

@iliaal iliaal commented Apr 9, 2026

Fixes #21682

Six classes wrap native C handles but allow serialization. Unserializing them produces objects with NULL internal pointers.

Segfault:

  • tidyNode (ext/tidy): crashes on hasChildren(), dangling TidyNode pointer

Silent data loss:

  • ZipArchive (ext/zip): numFiles returns 0, archive contents gone
  • tidy (ext/tidy): body() returns NULL, cleanRepair() no-ops
  • SNMP (ext/snmp): methods run against a dead session without error

Throws on use:

  • XMLWriter (ext/xmlwriter): methods throw "Invalid or uninitialized XMLWriter object"
  • XMLReader (ext/xmlreader): read() throws "Data must be loaded before reading"

Adds @not-serializable to all six so serialize() throws instead of producing broken objects.

Two existing UAF exploit tests (bug72479.phpt, bug72434.phpt) relied on unserialize() instantiating SNMP and ZipArchive objects. With NOT_SERIALIZABLE, unserialize() rejects these classes outright, removing the UAF vector.

iliaal added 3 commits April 9, 2026 15:04
ZipArchive allowed serialization, producing a string that unserializes
into an empty object with no open file handle. Add the
@not-serializable annotation so serialize() throws an exception.

Closes phpGH-21682
These classes wrap native C handles (libxml2 writer/reader, SNMP
session, libTidy document/node) that cannot survive serialization.
Unserializing produces a broken object with NULL internal pointers.
bug72479.phpt and bug72434.phpt tested UAF vulnerabilities through
unserialize(). With NOT_SERIALIZABLE, unserialize() rejects these
classes entirely, preventing the UAF by construction. Update tests
to verify the rejection.
@iliaal iliaal force-pushed the fix/gh-21682-ziparchive-not-serializable branch from b7a9957 to b668c21 Compare April 9, 2026 19:04
@ndossche
Copy link
Copy Markdown
Member

ndossche commented Apr 9, 2026

One thing to keep in mind is that it may introduce issues analogous to #8996
Perhaps the not-serializable flag is not the right way to do it, I was never really convinced that also blocking children is the right thing to do.

@iliaal
Copy link
Copy Markdown
Contributor Author

iliaal commented Apr 9, 2026

Serializing these classes today: tidyNode segfaults on hasChildren(), ZipArchive/tidy silently lose state, XMLWriter/XMLReader throw on every method call. None produce a usable object.

Re child classes and GH-8996: only tidyNode is final, but unlike DOM none of these classes can capture or restore their internal C state. DOMDocument has saveXML()/loadXML(), which makes SerializableDomDocument viable. These classes have no equivalent. The underlying handles (xmlTextWriterPtr, TidyDoc, libzip archive, SNMP session) are opaque. A child adding __serialize would track its own state and rebuild from scratch on wakeup, not serialize the parent. The GH-8996 pattern doesn't apply here in practice.

Happy to switch to the DOM-style custom handler if you'd prefer that over NOT_SERIALIZABLE, I do think though it introduces complexity for little meaningful value though.

Copy link
Copy Markdown
Member

@ndossche ndossche left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough, see my nitpick remark.

@@ -1,29 +1,17 @@
--TEST--
Bug #72434: ZipArchive class Use After Free Vulnerability in PHP's GC algorithm and unserialize
--EXTENSIONS--
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test should be moved to ext/zip/tests

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fair enough, moved as asked.

Test requires the zip extension and tests ZipArchive behavior.
@iliaal iliaal requested a review from ndossche April 9, 2026 20:50
Copy link
Copy Markdown
Member

@DanielEScherzer DanielEScherzer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re child classes and #8996: only tidyNode is final, but unlike DOM none of these classes can capture or restore their internal C state

ZipArchive will have a way to capture this soon with #21497, see https://github.com/tstarling/php-src/blob/de15d91b44fe56d7e02c4ada8e6e7c0ea13ef146/ext/zip/tests/ZipArchive_closeString_basic.phpt, so allowing subclasses to be serialized might be useful - can you split out the ZipArchive changes? In the absence of a maintainer for ext/zip we should at least give more consideration after #21497 before deciding to prevent subclass serialization

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ZipArchive is missing the NOT_SERIALIZABLE flag

3 participants