Skip to content

gh-156939: Fix xmlcharrefreplace() buffer overflow - #157109

Open
vstinner wants to merge 1 commit into
python:mainfrom
vstinner:xmlcharrefreplace
Open

gh-156939: Fix xmlcharrefreplace() buffer overflow#157109
vstinner wants to merge 1 commit into
python:mainfrom
vstinner:xmlcharrefreplace

Conversation

@vstinner

@vstinner vstinner commented Sep 7, 2026

Copy link
Copy Markdown
Member

Write into a temporay buffer to not write the trailing NUL byte.

@vstinner vstinner added the needs backport to 3.15 pre-release feature fixes, bugs and security fixes label Sep 7, 2026
@vstinner

vstinner commented Sep 7, 2026

Copy link
Copy Markdown
Member Author

I wrote a patch for Python 3.14 to check if it's also affected:

diff --git a/Include/internal/pycore_bytesobject.h b/Include/internal/pycore_bytesobject.h
index 8ea9b3ebb88..7ab96b109a5 100644
--- a/Include/internal/pycore_bytesobject.h
+++ b/Include/internal/pycore_bytesobject.h
@@ -86,6 +86,7 @@ typedef struct {
     /* Stack buffer */
     int use_small_buffer;
     char small_buffer[512];
+    char canary_byte;
 } _PyBytesWriter;
 
 /* Initialize a bytes writer
diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c
index 03245788bb1..be698236843 100644
--- a/Objects/bytesobject.c
+++ b/Objects/bytesobject.c
@@ -3456,6 +3456,7 @@ _PyBytesWriter_Init(_PyBytesWriter *writer)
     memset(writer->small_buffer, PYMEM_CLEANBYTE,
            sizeof(writer->small_buffer));
 #endif
+    writer->canary_byte = 0xAB;
 }
 
 void
@@ -3524,6 +3525,8 @@ _PyBytesWriter_CheckConsistency(_PyBytesWriter *writer, char *str)
     end = start + writer->allocated;
     assert(str != NULL);
     assert(start <= str && str <= end);
+
+    assert(writer->canary_byte == (char)0xAB);
     return 1;
 }
 #endif
@@ -3665,6 +3668,10 @@ _PyBytesWriter_Finish(_PyBytesWriter *writer, void *str)
     PyObject *result;
 
     assert(_PyBytesWriter_CheckConsistency(writer, str));
+    if (writer->canary_byte != (char)0xAB) {
+        fprintf(stderr, "PyBytesWriter: buffer overflow detected! abort\n");
+        abort();
+    }
 
     size = _PyBytesWriter_GetSize(writer, str);
     if (size == 0 && !writer->use_bytearray) {

I wrote a script to check for the buffer overflow in Python 3.14:

writer_small_buffer = 512
ch = '\u20ac'
ch_encoded = ch.encode('latin1', 'xmlcharrefreplace')

repeat = writer_small_buffer - len(ch_encoded)
data = 'x' * repeat + ch
res = data.encode('latin1', 'xmlcharrefreplace')
print(res)
print(len(res))

Output:

$ ./python x.py 
PyBytesWriter: buffer overflow detected! abort
Abandon                    (core dumped)./python x

So yes, Python 3.14, which uses the old internal _PyBytesWriter API, is also affected.

In Python 3.14, _PyBytesWriter has its "small buffer" at the end of the structure. So the NUL byte write is actually a buffer overflow writing in the stack. It's quite bad :-(

@vstinner vstinner added needs backport to 3.13 bugs and security fixes needs backport to 3.14 bugs and security fixes labels Sep 7, 2026
Write into a temporay buffer to not write the trailing NUL byte.
@vstinner vstinner added needs backport to 3.10 only security fixes needs backport to 3.11 only security fixes needs backport to 3.12 only security fixes type-security A security issue labels Sep 7, 2026
@vstinner

vstinner commented Sep 7, 2026

Copy link
Copy Markdown
Member Author

It seems like Python 3.10 to 3.16 are affected. (I didn't check older branches which no longer get security fixes.)

@vstinner
vstinner marked this pull request as ready for review September 7, 2026 20:46
@vstinner

vstinner commented Sep 7, 2026

Copy link
Copy Markdown
Member Author

@serhiy-storchaka: Would you mind to review this change?

Fix a buffer overflow in the ``xmlcharrefreplace`` error handler of 8-bit
encoding (such as ``ascii`` and ``latin1``). Previously, a buffer overflow
wrote one NUL byte in the stack memory if the output length was exactly 512
bytes. Patch by Victor Stinner.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

On Python 3.15 and 3.16, the new PyBytesWriter implementation is used. The NUL byte is written into PyBytesWriter.obj which is already a NULL pointer, so the write is harmless.

We should add the Changelog (NEWS) entry on Python 3.14 where the write occurs in the stack (buffer overflow).

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

Labels

awaiting core review needs backport to 3.10 only security fixes needs backport to 3.11 only security fixes needs backport to 3.12 only security fixes needs backport to 3.13 bugs and security fixes needs backport to 3.14 bugs and security fixes needs backport to 3.15 pre-release feature fixes, bugs and security fixes type-security A security issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant