Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Doc/c-api/memoryview.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ A :class:`memoryview` object exposes the C level :ref:`buffer interface
any other object.


.. c:var:: PyTypeObject PyMemoryView_Type
This instance of :c:type:`PyTypeObject` represents the Python memoryview
type. This is the same object as :class:`memoryview` in the Python layer.


.. c:function:: PyObject *PyMemoryView_FromObject(PyObject *obj)
Create a memoryview object from an object that provides the buffer interface.
Expand Down
8 changes: 8 additions & 0 deletions Doc/data/stable_abi.dat

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions Doc/library/pyexpat.rst
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,15 @@ otherwise stated.

.. method:: xmlparser.ExternalEntityRefHandler(context, base, systemId, publicId)

.. warning::

Implementing a handler that accesses local files and/or the network
may create a vulnerability to
`external entity attacks <https://en.wikipedia.org/wiki/XML_external_entity_attack>`_
if :class:`xmlparser` is used with user-provided XML content.
Please reflect on your `threat model <https://en.wikipedia.org/wiki/Threat_model>`_
before implementing this handler.

Called for references to external entities. *base* is the current base, as set
by a previous call to :meth:`SetBase`. The public and system identifiers,
*systemId* and *publicId*, are strings if given; if the public identifier is not
Expand Down
22 changes: 17 additions & 5 deletions Doc/library/xml.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,22 @@ XML security

An attacker can abuse XML features to carry out denial of service attacks,
access local files, generate network connections to other machines, or
circumvent firewalls.

Expat versions lower than 2.6.0 may be vulnerable to "billion laughs",
"quadratic blowup" and "large tokens". Python may be vulnerable if it uses such
older versions of Expat as a system-provided library.
circumvent firewalls when attacker-controlled XML is being parsed,
in Python or elsewhere.

The built-in XML parsers of Python rely on the library `libexpat`_, commonly
called Expat, for parsing XML.

By default, Expat itself does not access local files or create network
connections.

Expat versions lower than 2.7.2 may be vulnerable to the "billion laughs",
"quadratic blowup" and "large tokens" vulnerabilities, or to disproportional
use of dynamic memory.
Python bundles a copy of Expat, and whether Python uses the bundled or a
system-wide Expat, depends on how the Python interpreter
:option:`has been configured <--with-system-expat>` in your environment.
Python may be vulnerable if it uses such older versions of Expat.
Check :const:`!pyexpat.EXPAT_VERSION`.

:mod:`xmlrpc` is **vulnerable** to the "decompression bomb" attack.
Expand Down Expand Up @@ -90,5 +101,6 @@ large tokens
be used to cause denial of service in the application parsing XML.
The issue is known as :cve:`2023-52425`.

.. _libexpat: https://github.com/libexpat/libexpat
.. _Billion Laughs: https://en.wikipedia.org/wiki/Billion_laughs
.. _ZIP bomb: https://en.wikipedia.org/wiki/Zip_bomb
31 changes: 26 additions & 5 deletions Include/internal/pycore_stackref.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ PyAPI_FUNC(PyObject *) _Py_stackref_get_object(_PyStackRef ref);
PyAPI_FUNC(PyObject *) _Py_stackref_close(_PyStackRef ref, const char *filename, int linenumber);
PyAPI_FUNC(_PyStackRef) _Py_stackref_create(PyObject *obj, uint16_t flags, const char *filename, int linenumber);
PyAPI_FUNC(void) _Py_stackref_record_borrow(_PyStackRef ref, const char *filename, int linenumber);
PyAPI_FUNC(_PyStackRef) _Py_stackref_get_borrowed_from(_PyStackRef ref, const char *filename, int linenumber);
PyAPI_FUNC(void) _Py_stackref_set_borrowed_from(_PyStackRef ref, _PyStackRef borrowed_from, const char *filename, int linenumber);
extern void _Py_stackref_associate(PyInterpreterState *interp, PyObject *obj, _PyStackRef ref);

static const _PyStackRef PyStackRef_NULL = { .index = 0 };
Expand Down Expand Up @@ -248,7 +250,12 @@ _PyStackRef_DUP(_PyStackRef ref, const char *filename, int linenumber)
} else {
flags = Py_TAG_REFCNT;
}
return _Py_stackref_create(obj, flags, filename, linenumber);
_PyStackRef new_ref = _Py_stackref_create(obj, flags, filename, linenumber);
if (flags == Py_TAG_REFCNT && !_Py_IsImmortal(obj)) {
_PyStackRef borrowed_from = _Py_stackref_get_borrowed_from(ref, filename, linenumber);
_Py_stackref_set_borrowed_from(new_ref, borrowed_from, filename, linenumber);
}
return new_ref;
}
#define PyStackRef_DUP(REF) _PyStackRef_DUP(REF, __FILE__, __LINE__)

Expand All @@ -259,6 +266,7 @@ _PyStackRef_CLOSE_SPECIALIZED(_PyStackRef ref, destructor destruct, const char *
assert(!PyStackRef_IsNull(ref));
assert(!PyStackRef_IsTaggedInt(ref));
PyObject *obj = _Py_stackref_close(ref, filename, linenumber);
assert(Py_REFCNT(obj) > 0);
if (PyStackRef_RefcountOnObject(ref)) {
_Py_DECREF_SPECIALIZED(obj, destruct);
}
Expand All @@ -274,7 +282,11 @@ _PyStackRef_Borrow(_PyStackRef ref, const char *filename, int linenumber)
return ref;
}
PyObject *obj = _Py_stackref_get_object(ref);
return _Py_stackref_create(obj, Py_TAG_REFCNT, filename, linenumber);
_PyStackRef new_ref = _Py_stackref_create(obj, Py_TAG_REFCNT, filename, linenumber);
if (!_Py_IsImmortal(obj)) {
_Py_stackref_set_borrowed_from(new_ref, ref, filename, linenumber);
}
return new_ref;
}
#define PyStackRef_Borrow(REF) _PyStackRef_Borrow((REF), __FILE__, __LINE__)

Expand Down Expand Up @@ -310,13 +322,22 @@ PyStackRef_IsHeapSafe(_PyStackRef ref)
static inline _PyStackRef
_PyStackRef_MakeHeapSafe(_PyStackRef ref, const char *filename, int linenumber)
{
if (PyStackRef_IsHeapSafe(ref)) {
// Special references that can't be closed.
if (ref.index < INITIAL_STACKREF_INDEX) {
return ref;
}

bool heap_safe = PyStackRef_IsHeapSafe(ref);
PyObject *obj = _Py_stackref_close(ref, filename, linenumber);
Py_INCREF(obj);
return _Py_stackref_create(obj, 0, filename, linenumber);
uint16_t flags = 0;
if (heap_safe) {
// Close old ref and create a new one with the same flags.
// This is necessary for correct borrow checking.
flags = ref.index & Py_TAG_BITS;
} else {
Py_INCREF(obj);
}
return _Py_stackref_create(obj, flags, filename, linenumber);
}
#define PyStackRef_MakeHeapSafe(REF) _PyStackRef_MakeHeapSafe(REF, __FILE__, __LINE__)

Expand Down
9 changes: 0 additions & 9 deletions Include/internal/pycore_time.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,6 @@ extern int _PyTime_FromSecondsDouble(
// Clamp to [PyTime_MIN; PyTime_MAX] on overflow.
extern PyTime_t _PyTime_FromMicrosecondsClamp(PyTime_t us);

// Create a timestamp from a Python int object (number of nanoseconds).
// Export for '_lsprof' shared extension.
PyAPI_FUNC(int) _PyTime_FromLong(PyTime_t *t,
PyObject *obj);

// Convert a number of seconds (Python float or int) to a timestamp.
// Raise an exception and return -1 on error, return 0 on success.
// Export for '_socket' shared extension.
Expand Down Expand Up @@ -182,10 +177,6 @@ extern PyTime_t _PyTime_As100Nanoseconds(PyTime_t t,
_PyTime_round_t round);
#endif

// Convert a timestamp (number of nanoseconds) as a Python int object.
// Export for '_testinternalcapi' shared extension.
PyAPI_FUNC(PyObject*) _PyTime_AsLong(PyTime_t t);

#ifndef MS_WINDOWS
// Create a timestamp from a timeval structure.
// Raise an exception and return -1 on overflow, return 0 on success.
Expand Down
19 changes: 19 additions & 0 deletions Lib/test/test_free_threading/test_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,25 @@ def reader():

self.run_one(writer, reader)

def test_bases_change(self):
class BaseA:
pass

class Derived(BaseA):
pass

def writer():
for _ in range(1000):
class BaseB:
pass
Derived.__bases__ = (BaseB,)

def reader():
for _ in range(1000):
Derived.__base__

self.run_one(writer, reader)

def run_one(self, writer_func, reader_func):
barrier = threading.Barrier(NTHREADS)

Expand Down
21 changes: 21 additions & 0 deletions Lib/test/test_io/test_bufferedio.py
Original file line number Diff line number Diff line change
Expand Up @@ -962,6 +962,27 @@ def test_args_error(self):
with self.assertRaisesRegex(TypeError, "BufferedWriter"):
self.tp(self.BytesIO(), 1024, 1024, 1024)

def test_non_boolean_closed_attr(self):
# gh-140650: check TypeError is raised
class MockRawIOWithoutClosed(self.MockRawIO):
closed = NotImplemented

bufio = self.tp(MockRawIOWithoutClosed())
self.assertRaises(TypeError, bufio.write, b"")
self.assertRaises(TypeError, bufio.flush)
self.assertRaises(TypeError, bufio.close)

def test_closed_attr_raises(self):
class MockRawIOClosedRaises(self.MockRawIO):
@property
def closed(self):
raise ValueError("test")

bufio = self.tp(MockRawIOClosedRaises())
self.assertRaisesRegex(ValueError, "test", bufio.write, b"")
self.assertRaisesRegex(ValueError, "test", bufio.flush)
self.assertRaisesRegex(ValueError, "test", bufio.close)


class PyBufferedWriterTest(BufferedWriterTest, PyTestCase):
tp = pyio.BufferedWriter
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix race when updating :attr:`!type.__bases__` that could allow a read of :attr:`!type.__base__` to observe an inconsistent value on the free threaded build.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Dynamic borrow checking for stackrefs is added to ``Py_STACKREF_DEBUG``
mode. Patch by Mikhail Efimov.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix an issue where closing :class:`io.BufferedWriter` could crash if
the closed attribute raised an exception on access or could not be
converted to a boolean.
25 changes: 19 additions & 6 deletions Modules/_io/bufferedio.c
Original file line number Diff line number Diff line change
Expand Up @@ -362,16 +362,24 @@ _enter_buffered_busy(buffered *self)
}

#define IS_CLOSED(self) \
(!self->buffer || \
(!self->buffer ? 1 : \
(self->fast_closed_checks \
? _PyFileIO_closed(self->raw) \
: buffered_closed(self)))

#define CHECK_CLOSED(self, error_msg) \
if (IS_CLOSED(self) && (Py_SAFE_DOWNCAST(READAHEAD(self), Py_off_t, Py_ssize_t) == 0)) { \
PyErr_SetString(PyExc_ValueError, error_msg); \
return NULL; \
} \
do { \
int _closed = IS_CLOSED(self); \
if (_closed < 0) { \
return NULL; \
} \
if (_closed && \
(Py_SAFE_DOWNCAST(READAHEAD(self), Py_off_t, Py_ssize_t) == 0)) \
{ \
PyErr_SetString(PyExc_ValueError, error_msg); \
return NULL; \
} \
} while (0);

#define VALID_READ_BUFFER(self) \
(self->readable && self->read_end != -1)
Expand Down Expand Up @@ -2079,6 +2087,7 @@ _io_BufferedWriter_write_impl(buffered *self, Py_buffer *buffer)
PyObject *res = NULL;
Py_ssize_t written, avail, remaining;
Py_off_t offset;
int r;

CHECK_INITIALIZED(self)

Expand All @@ -2087,7 +2096,11 @@ _io_BufferedWriter_write_impl(buffered *self, Py_buffer *buffer)

/* Issue #31976: Check for closed file after acquiring the lock. Another
thread could be holding the lock while closing the file. */
if (IS_CLOSED(self)) {
r = IS_CLOSED(self);
if (r < 0) {
goto error;
}
if (r > 0) {
PyErr_SetString(PyExc_ValueError, "write to closed file");
goto error;
}
Expand Down
4 changes: 2 additions & 2 deletions Modules/_lsprof.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#include "pycore_call.h" // _PyObject_CallNoArgs()
#include "pycore_ceval.h" // _PyEval_SetProfile()
#include "pycore_pystate.h" // _PyThreadState_GET()
#include "pycore_time.h" // _PyTime_FromLong()
#include "pycore_time.h" // _PyTime_FromSecondsObject()
#include "pycore_typeobject.h" // _PyType_GetModuleState()
#include "pycore_unicodeobject.h" // _PyUnicode_EqualToASCIIString()

Expand Down Expand Up @@ -111,7 +111,7 @@ static PyTime_t CallExternalTimer(ProfilerObject *pObj)
if (pObj->externalTimerUnit > 0.0) {
/* interpret the result as an integer that will be scaled
in profiler_getstats() */
err = _PyTime_FromLong(&result, o);
err = PyLong_AsInt64(o, &result);
}
else {
/* interpret the result as a double measured in seconds.
Expand Down
2 changes: 1 addition & 1 deletion Modules/_randommodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,7 @@ _random_Random_getrandbits_impl(RandomObject *self, uint64_t k)
PyErr_NoMemory();
return NULL;
}
words = (k - 1u) / 32u + 1u;
words = (Py_ssize_t)((k - 1u) / 32u + 1u);
wordarray = (uint32_t *)PyMem_Malloc(words * 4);
if (wordarray == NULL) {
PyErr_NoMemory();
Expand Down
Loading
Loading