GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — gh-156939: Fix xmlcharrefreplace() buffer overflow by vstinner · Pull Request #157109 · python/cpython

Conversation

@vstinner

@vstinner vstinner commented Sep 7, 2026 •
edited by bedevere-app Bot
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

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

  • Issue: C API: Detect buffer overflow in PyBytesWriter #156939

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@bedevere-app bedevere-app Bot mentioned this pull request Sep 7, 2026
C API: Detect buffer overflow in PyBytesWriter #156939
Closed
@vstinner vstinner added the needs backport to 3.15 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 :-(

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner
vstinner force-pushed the xmlcharrefreplace branch from d1adc33 to 3bdaf2c Compare September 7, 2026 20:30
@vstinner vstinner added needs backport to 3.13 only security fixes needs backport to 3.14 bugs and security fixes labels Sep 7, 2026
@vstinner
vstinner force-pushed the xmlcharrefreplace branch from 3bdaf2c to aada0ed Compare September 7, 2026 20:33
@vstinner
pythongh-156939: Fix xmlcharrefreplace() buffer overflow
1c8742d
Write into a temporay buffer to not write the trailing NUL byte.
@vstinner
vstinner force-pushed the xmlcharrefreplace branch from aada0ed to 1c8742d Compare September 7, 2026 20:34
@vstinner vstinner added needs backport to 3.10 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.)

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner
vstinner marked this pull request as ready for review September 7, 2026 20:46
@bedevere-app bedevere-app Bot added the awaiting core review label Sep 7, 2026
@vstinner

vstinner commented Sep 7, 2026

Copy link
Copy Markdown
Member Author

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

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

vstinner
vstinner commented Sep 7, 2026
View reviewed changes
Comment thread Misc/NEWS.d/next/Security/2026-09-07-22-32-49.gh-issue-156939.vXc73v.rst Resolved

Uh oh!

There was an error while loading. Please reload this page.

vstinner added 2 commits September 9, 2026 18:32
@vstinner
Remove NEWS entry
12f9480
@vstinner
Merge branch 'main' into xmlcharrefreplace
54efbf4
@vstinner vstinner removed type-security A security issue needs backport to 3.10 needs backport to 3.11 only security fixes needs backport to 3.12 only security fixes labels Sep 9, 2026
@serhiy-storchaka
serhiy-storchaka self-requested a review September 9, 2026 16:34
@vstinner

vstinner commented Sep 9, 2026

Copy link
Copy Markdown
Member Author

After looking at the code one more time, I decided that in fact, it's just a bugfix, not a security issue.

In the worst case, the code writes a NUL byte on the stack after the writer (allocated on the stack). But in fact, this write cannot corrupt other stack variables nor change the return address. The write is just ignored and nothing is corrupted.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner vstinner added the skip news label Sep 9, 2026
@vstinner

vstinner commented Sep 9, 2026

Copy link
Copy Markdown
Member Author

On Python 3.14 and older, the write only occurs if the output length is exactly 512 bytes.

On Python 3.15 and newer, the write only occurs if the output length is exactly 256 bytes. But on these Python versions, the write occurs in PyBytesObject.obj which is already a NULL pointer, so the write is harmless in practice.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

serhiy-storchaka
serhiy-storchaka reviewed Sep 9, 2026
View reviewed changes

@serhiy-storchaka serhiy-storchaka left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

What is the issue? We calculate the exact size of the result. Then PyBytesWriter should reserve the bytes object of that size (it is always followed by the terminating NUL). I do not see where we can make error in calculations.

BTW, using PyBytesWriter here is not needed. We know the size of the result, it is easier and faster to reserve the bytes object of that size.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Objects/unicodeobject.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

@vstinner
Fix minimum size
43830a2
len("&#0;") is 4 bytes.
@vstinner

vstinner commented Sep 9, 2026

Copy link
Copy Markdown
Member Author

What is the issue? We calculate the exact size of the result. Then PyBytesWriter should reserve the bytes object of that size (it is always followed by the terminating NUL). I do not see where we can make error in calculations.

For a size less than or equal to 256 bytes, PyBytesWriter uses a small buffer of 256 bytes. There is no reserved space for a trailing NUL byte. Writing a trailing NUL byte causes a buffer overflow. See my previous comment for details. (In Python 3.14, the buffer overflow occurs with a buffer of 512 bytes.)

I would prefer to not allow writing a trailing NUL byte in the PyBytesWriter API, since it would prevent implementing buffer overflow detection for example.

BTW, using PyBytesWriter here is not needed. We know the size of the result, it is easier and faster to reserve the bytes object of that size.

xmlcharrefreplace() is used by unicode_encode_ucs1() which uses PyBytesWriter. Using PyBytesWriter instead of allocating directly a bytes object should not have a signicant overhead. PyBytesWriter is convenient for error handlers which have to resize the bytes object. It's also convenient to use PyBytesWriter in all unicode_encode_ucs1() code paths to have the same API.

IMO PyBytesWriter is a better API to create bytes objects. It implements additional checks in debug mode. It has some nice features like returning a singleton for an empty string or a single byte. PEP 782 soft deprecated PyBytes_FromStringAndSize(NULL, size) API.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@serhiy-storchaka

serhiy-storchaka commented Sep 9, 2026

Copy link
Copy Markdown
Member

PyBytesWriter should either increase the size of that buffer to 2567, or use it only for a size less than or equal to 255 bytes.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner

vstinner commented Sep 9, 2026

Copy link
Copy Markdown
Member Author

PyBytesWriter should either increase the size of that buffer to 2567, or use it only for a size less than or equal to 255 bytes.

Reserving the last byte of the small buffer to allow writing a trailing NUL bytes prevents implementing buffer overflow detection. If we allow that, the buffer overflow detection raises an error since it detects a write outsize the allocated buffer.

The C API of PyBytesObject allocates an extra byte for a trailing NUL byte. It allows overwriting the trailing NUL byte with... a NUL byte. It's an convenient feature, but it's currently undocumented.

I don't think that we should allow writing an extra trailing NUL byte in the PyBytesWriter API.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner
vstinner merged commit 9398655 into python:main Sep 9, 2026
51 checks passed

Uh oh!

There was an error while loading. Please reload this page.

@vstinner
vstinner deleted the xmlcharrefreplace branch September 9, 2026 19:18
@miss-islington-app

miss-islington-app Bot commented Sep 9, 2026

Copy link
Copy Markdown

Thanks @vstinner for the PR 🌮🎉.. I'm working now to backport this PR to: 3.13, 3.14, 3.15.
🐍🍒⛏🤖

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-app bedevere-app Bot removed the awaiting core review label Sep 9, 2026
@bedevere-app

bedevere-app Bot commented Sep 9, 2026

Copy link
Copy Markdown

GH-157232 is a backport of this pull request to the 3.15 branch.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-app bedevere-app Bot removed the needs backport to 3.15 bugs and security fixes label Sep 9, 2026
@bedevere-app

bedevere-app Bot commented Sep 9, 2026

Copy link
Copy Markdown

GH-157233 is a backport of this pull request to the 3.14 branch.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-app bedevere-app Bot removed the needs backport to 3.14 bugs and security fixes label Sep 9, 2026
@bedevere-app

bedevere-app Bot commented Sep 9, 2026

Copy link
Copy Markdown

GH-157234 is a backport of this pull request to the 3.13 branch.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-app bedevere-app Bot removed the needs backport to 3.13 only security fixes label Sep 9, 2026
@vstinner

vstinner commented Sep 9, 2026

Copy link
Copy Markdown
Member Author

I merged my change to fix the buffer overflow and unblock PR gh-156943 (which requires this fix).

If needed, we can revisit the PyBytesWriter implementation later as soon as the API remains the same.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner vstinner mentioned this pull request Sep 9, 2026
gh-156939: Detect buffer overflow in PyBytesWriter in debug mode #156943
Merged
hugovk pushed a commit that referenced this pull request Sep 11, 2026
@miss-islington @vstinner
[3.15] gh-156939: Fix xmlcharrefreplace() buffer overflow (GH-157109) (…
0decd77
…#157232)

Co-authored-by: Victor Stinner <[email protected]>
clin1234 pushed a commit to clin1234/cpython that referenced this pull request Sep 12, 2026
@vstinner @clin1234
pythongh-156939: Fix xmlcharrefreplace() buffer overflow (python#157109)
931fa1f
Write into a temporary buffer to not write the trailing NUL byte
into the writer. Previously, the NUL byte was written outsize
the writer buffer.
johnslavik pushed a commit to johnslavik/cpython that referenced this pull request Sep 14, 2026
@vstinner @johnslavik
pythongh-156939: Fix xmlcharrefreplace() buffer overflow (python#157109)
c3acb00
Write into a temporary buffer to not write the trailing NUL byte
into the writer. Previously, the NUL byte was written outsize
the writer buffer.
vstinner added a commit that referenced this pull request Sep 14, 2026
@miss-islington @vstinner
[3.14] gh-156939: Fix xmlcharrefreplace() buffer overflow (GH-157109) (…
5d1f8ae
…#157233)

* gh-156939: Fix xmlcharrefreplace() buffer overflow (GH-157109)

Write into a temporary buffer to not write the trailing NUL byte
into the writer. Previously, the NUL byte was written outsize
the writer buffer.
(cherry picked from commit 9398655)

Co-authored-by: Victor Stinner <[email protected]>

* Replace _Py_MAX_UNICODE with MAX_UNICODE

---------

Co-authored-by: Victor Stinner <[email protected]>
vstinner added a commit that referenced this pull request Sep 15, 2026
@miss-islington @vstinner
[3.13] gh-156939: Fix xmlcharrefreplace() buffer overflow (GH-157109) (…
a471483
…#157234)

* gh-156939: Fix xmlcharrefreplace() buffer overflow (GH-157109)

Write into a temporary buffer to not write the trailing NUL byte
into the writer. Previously, the NUL byte was written outsize
the writer buffer.
(cherry picked from commit 9398655)

Co-authored-by: Victor Stinner <[email protected]>

* Replace _Py_MAX_UNICODE with MAX_UNICODE

---------

Co-authored-by: Victor Stinner <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@serhiy-storchaka serhiy-storchaka serhiy-storchaka left review comments

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

skip news

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

2 participants

@vstinner @serhiy-storchaka
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.