Bug description
_Py_Mangle() in Python/symtable.c calls PyUnicode_READ_CHAR(ident, 1) without first checking that ident has at least two characters. If ident is a one-character string (e.g. "_"), this reads one code point past the end of the unicode buffer.
The read is silent in release builds because a 1-char ASCII unicode object has enough padding for the null terminator, so ASAN does not trip. But it is still UB per the PyUnicode_READ_CHAR contract.
Reproducer
Call path
_Py_Mangle entry check:
if (privateobj == NULL || !PyUnicode_Check(privateobj) ||
PyUnicode_READ_CHAR(ident, 0) != '_' ||
PyUnicode_READ_CHAR(ident, 1) != '_') {
return Py_NewRef(ident);
}
For ident == "", READ_CHAR(ident, 0) != '' is false, so READ_CHAR(ident, 1) is evaluated — out of bounds.
Comparison
_Py_IsPrivateName() in the same file does guard with nlen < 3 before indexing. The guard is missing from _Py_Mangle().
Suggested fix
Add PyUnicode_GET_LENGTH(ident) < 2 || to the condition.
Linked PRs
Bug description
_Py_Mangle()inPython/symtable.ccallsPyUnicode_READ_CHAR(ident, 1)without first checking thatidenthas at least two characters. Ifidentis a one-character string (e.g."_"), this reads one code point past the end of the unicode buffer.The read is silent in release builds because a 1-char ASCII unicode object has enough padding for the null terminator, so ASAN does not trip. But it is still UB per the
PyUnicode_READ_CHARcontract.Reproducer
Call path
_Py_Mangle entry check:
For ident == "", READ_CHAR(ident, 0) != '' is false, so READ_CHAR(ident, 1) is evaluated — out of bounds.
Comparison
_Py_IsPrivateName() in the same file does guard with nlen < 3 before indexing. The guard is missing from _Py_Mangle().
Suggested fix
Add PyUnicode_GET_LENGTH(ident) < 2 || to the condition.
Linked PRs