Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into fix/future-callback…
Browse files Browse the repository at this point in the history
…-uaf-125966
  • Loading branch information
picnixz committed Oct 27, 2024
2 parents 11870aa + f819d43 commit d98f13c
Show file tree
Hide file tree
Showing 28 changed files with 436 additions and 60 deletions.
7 changes: 7 additions & 0 deletions Doc/library/inspect.rst
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,13 @@ attributes (see :ref:`import-mod-attrs` for module attributes):
Return ``True`` if the object is a bound method written in Python.


.. function:: ispackage(object)

Return ``True`` if the object is a :term:`package`.

.. versionadded:: 3.14


.. function:: isfunction(object)

Return ``True`` if the object is a Python function, which includes functions
Expand Down
3 changes: 3 additions & 0 deletions Doc/library/stdtypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3889,6 +3889,9 @@ copying.
.. versionchanged:: 3.5
memoryviews can now be indexed with tuple of integers.

.. versionchanged:: next
memoryview is now a :term:`generic type`.

:class:`memoryview` has several methods:

.. method:: __eq__(exporter)
Expand Down
8 changes: 8 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@ Other language changes
:mod:`copyable <copy>`.
(Contributed by Serhiy Storchaka in :gh:`125767`.)

* The :class:`memoryview` type now supports subscription,
making it a :term:`generic type`.
(Contributed by Brian Schubert in :gh:`126012`.)


New modules
===========
Expand Down Expand Up @@ -326,6 +330,10 @@ inspect
If true, string :term:`annotations <annotation>` are displayed without surrounding quotes.
(Contributed by Jelle Zijlstra in :gh:`101552`.)

* Add function :func:`inspect.ispackage` to determine whether an object is a
:term:`package` or not.
(Contributed by Zhikang Yan in :gh:`125634`.)


json
----
Expand Down
3 changes: 2 additions & 1 deletion Lib/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@ def _showtraceback(self, typ, value, tb, source):
# Set the line of text that the exception refers to
lines = source.splitlines()
if (source and typ is SyntaxError
and not value.text and len(lines) >= value.lineno):
and not value.text and value.lineno is not None
and len(lines) >= value.lineno):
value.text = lines[value.lineno - 1]
sys.last_exc = sys.last_value = value
if sys.excepthook is sys.__excepthook__:
Expand Down
11 changes: 8 additions & 3 deletions Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
Here are some of the useful functions provided by this module:
ismodule(), isclass(), ismethod(), isfunction(), isgeneratorfunction(),
isgenerator(), istraceback(), isframe(), iscode(), isbuiltin(),
isroutine() - check object types
ismodule(), isclass(), ismethod(), ispackage(), isfunction(),
isgeneratorfunction(), isgenerator(), istraceback(), isframe(),
iscode(), isbuiltin(), isroutine() - check object types
getmembers() - get members of an object that satisfy a given condition
getfile(), getsourcefile(), getsource() - find an object's source code
Expand Down Expand Up @@ -128,6 +128,7 @@
"ismethoddescriptor",
"ismethodwrapper",
"ismodule",
"ispackage",
"isroutine",
"istraceback",
"markcoroutinefunction",
Expand Down Expand Up @@ -186,6 +187,10 @@ def ismethod(object):
"""Return true if the object is an instance method."""
return isinstance(object, types.MethodType)

def ispackage(object):
"""Return true if the object is a package."""
return ismodule(object) and hasattr(object, "__path__")

def ismethoddescriptor(object):
"""Return true if the object is a method descriptor.
Expand Down
11 changes: 11 additions & 0 deletions Lib/test/audit-tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,17 @@ def hook(event, args):
_winapi.CreateNamedPipe(pipe_name, _winapi.PIPE_ACCESS_DUPLEX, 8, 2, 0, 0, 0, 0)


def test_assert_unicode():
import sys
sys.addaudithook(lambda *args: None)
try:
sys.audit(9)
except TypeError:
pass
else:
raise RuntimeError("Expected sys.audit(9) to fail.")


if __name__ == "__main__":
from test.support import suppress_msvcrt_asserts

Expand Down
5 changes: 5 additions & 0 deletions Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,11 @@ def requires_debug_ranges(reason='requires co_positions / debug_ranges'):

is_android = sys.platform == "android"

def skip_android_selinux(name):
return unittest.skipIf(
sys.platform == "android", f"Android blocks {name} with SELinux"
)

if sys.platform not in {"win32", "vxworks", "ios", "tvos", "watchos"}:
unix_shell = '/system/bin/sh' if is_android else '/bin/sh'
else:
Expand Down
73 changes: 71 additions & 2 deletions Lib/test/test_asyncio/test_futures.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,25 @@ def last_cb():
pass


class ReachableCode(Exception):
"""Exception to raise to indicate that some code was reached.
Use this exception if using mocks is not a good alternative.
"""


class SimpleEvilEventLoop(asyncio.base_events.BaseEventLoop):
"""Base class for UAF and other evil stuff requiring an evil event loop."""

def get_debug(self): # to suppress tracebacks
return False

def __del__(self):
# Automatically close the evil event loop to avoid warnings.
if not self.is_closed() and not self.is_running():
self.close()


class DuckFuture:
# Class that does not inherit from Future but aims to be duck-type
# compatible with it.
Expand Down Expand Up @@ -948,6 +967,7 @@ def __eq__(self, other):
fut.remove_done_callback(evil())

def test_evil_call_soon_list_mutation(self):
# see: https://github.com/python/cpython/issues/125969
called_on_fut_callback0 = False

pad = lambda: ...
Expand All @@ -962,9 +982,8 @@ def evil_call_soon(*args, **kwargs):
else:
called_on_fut_callback0 = True

fake_event_loop = lambda: ...
fake_event_loop = SimpleEvilEventLoop()
fake_event_loop.call_soon = evil_call_soon
fake_event_loop.get_debug = lambda: False # suppress traceback

with mock.patch.object(self, 'loop', fake_event_loop):
fut = self._new_future()
Expand Down Expand Up @@ -998,6 +1017,56 @@ def __eq__(self, other):
fut.add_done_callback(cb_pad())
fut.remove_done_callback(evil())

def test_use_after_free_on_fut_callback_0_with_evil__getattribute__(self):
# see: https://github.com/python/cpython/issues/125984

class EvilEventLoop(SimpleEvilEventLoop):
def call_soon(self, *args, **kwargs):
super().call_soon(*args, **kwargs)
raise ReachableCode

def __getattribute__(self, name):
nonlocal fut_callback_0
if name == 'call_soon':
fut.remove_done_callback(fut_callback_0)
del fut_callback_0
return object.__getattribute__(self, name)

evil_loop = EvilEventLoop()
with mock.patch.object(self, 'loop', evil_loop):
fut = self._new_future()
self.assertIs(fut.get_loop(), evil_loop)

fut_callback_0 = lambda: ...
fut.add_done_callback(fut_callback_0)
self.assertRaises(ReachableCode, fut.set_result, "boom")

def test_use_after_free_on_fut_context_0_with_evil__getattribute__(self):
# see: https://github.com/python/cpython/issues/125984

class EvilEventLoop(SimpleEvilEventLoop):
def call_soon(self, *args, **kwargs):
super().call_soon(*args, **kwargs)
raise ReachableCode

def __getattribute__(self, name):
if name == 'call_soon':
# resets the future's event loop
fut.__init__(loop=SimpleEvilEventLoop())
return object.__getattribute__(self, name)

evil_loop = EvilEventLoop()
with mock.patch.object(self, 'loop', evil_loop):
fut = self._new_future()
self.assertIs(fut.get_loop(), evil_loop)

fut_callback_0 = mock.Mock()
fut_context_0 = mock.Mock()
fut.add_done_callback(fut_callback_0, context=fut_context_0)
del fut_context_0
del fut_callback_0
self.assertRaises(ReachableCode, fut.set_result, "boom")


@unittest.skipUnless(hasattr(futures, '_CFuture'),
'requires the C _asyncio module')
Expand Down
7 changes: 7 additions & 0 deletions Lib/test/test_audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,5 +307,12 @@ def test_winapi_createnamedpipe(self):

self.assertEqual(actual, expected)

def test_assert_unicode(self):
# See gh-126018
returncode, _, stderr = self.run_python("test_assert_unicode")
if returncode:
self.fail(stderr)


if __name__ == "__main__":
unittest.main()
2 changes: 1 addition & 1 deletion Lib/test/test_genericalias.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@

class BaseTest(unittest.TestCase):
"""Test basics."""
generic_types = [type, tuple, list, dict, set, frozenset, enumerate,
generic_types = [type, tuple, list, dict, set, frozenset, enumerate, memoryview,
defaultdict, deque,
SequenceMatcher,
dircmp,
Expand Down
19 changes: 16 additions & 3 deletions Lib/test/test_inspect/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@

# Functions tested in this suite:
# ismodule, isclass, ismethod, isfunction, istraceback, isframe, iscode,
# isbuiltin, isroutine, isgenerator, isgeneratorfunction, getmembers,
# isbuiltin, isroutine, isgenerator, ispackage, isgeneratorfunction, getmembers,
# getdoc, getfile, getmodule, getsourcefile, getcomments, getsource,
# getclasstree, getargvalues, formatargvalues, currentframe,
# stack, trace, ismethoddescriptor, isdatadescriptor, ismethodwrapper
Expand Down Expand Up @@ -105,7 +105,7 @@ def unsorted_keyword_only_parameters_fn(*, throw, out, the, baby, with_,
class IsTestBase(unittest.TestCase):
predicates = set([inspect.isbuiltin, inspect.isclass, inspect.iscode,
inspect.isframe, inspect.isfunction, inspect.ismethod,
inspect.ismodule, inspect.istraceback,
inspect.ismodule, inspect.istraceback, inspect.ispackage,
inspect.isgenerator, inspect.isgeneratorfunction,
inspect.iscoroutine, inspect.iscoroutinefunction,
inspect.isasyncgen, inspect.isasyncgenfunction,
Expand All @@ -121,7 +121,10 @@ def istest(self, predicate, exp):
predicate == inspect.iscoroutinefunction) and \
other == inspect.isfunction:
continue
self.assertFalse(other(obj), 'not %s(%s)' % (other.__name__, exp))
if predicate == inspect.ispackage and other == inspect.ismodule:
self.assertTrue(predicate(obj), '%s(%s)' % (predicate.__name__, exp))
else:
self.assertFalse(other(obj), 'not %s(%s)' % (other.__name__, exp))

def test__all__(self):
support.check__all__(self, inspect, not_exported=("modulesbyfile",), extra=("get_annotations",))
Expand Down Expand Up @@ -201,7 +204,17 @@ def test_excluding_predicates(self):
self.assertFalse(inspect.ismethodwrapper(int))
self.assertFalse(inspect.ismethodwrapper(type("AnyClass", (), {})))

def test_ispackage(self):
self.istest(inspect.ispackage, 'asyncio')
self.istest(inspect.ispackage, 'importlib')
self.assertFalse(inspect.ispackage(inspect))
self.assertFalse(inspect.ispackage(mod))
self.assertFalse(inspect.ispackage(':)'))

class FakePackage:
__path__ = None

self.assertFalse(inspect.ispackage(FakePackage()))

def test_iscoroutine(self):
async_gen_coro = async_generator_function_example(1)
Expand Down
9 changes: 9 additions & 0 deletions Lib/test/test_pyrepl/test_interact.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,15 @@ def test_runsource_shows_syntax_error_for_failed_compilation(self):
console.runsource(source)
mock_showsyntaxerror.assert_called_once()

def test_runsource_survives_null_bytes(self):
console = InteractiveColoredConsole()
source = "\x00\n"
f = io.StringIO()
with contextlib.redirect_stdout(f), contextlib.redirect_stderr(f):
result = console.runsource(source)
self.assertFalse(result)
self.assertIn("source code string cannot contain null bytes", f.getvalue())

def test_no_active_future(self):
console = InteractiveColoredConsole()
source = dedent("""\
Expand Down
5 changes: 5 additions & 0 deletions Lib/test/test_pyrepl/test_pyrepl.py
Original file line number Diff line number Diff line change
Expand Up @@ -1313,6 +1313,11 @@ def test_proper_tracebacklimit(self):
self.assertIn("in x3", output)
self.assertIn("in <module>", output)

def test_null_byte(self):
output, exit_code = self.run_repl("\x00\nexit()\n")
self.assertEqual(exit_code, 0)
self.assertNotIn("TypeError", output)

def test_readline_history_file(self):
# skip, if readline module is not available
readline = import_module('readline')
Expand Down
5 changes: 5 additions & 0 deletions Lib/test/test_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -1111,6 +1111,7 @@ def test_sethostname(self):

@unittest.skipUnless(hasattr(socket, 'if_nameindex'),
'socket.if_nameindex() not available.')
@support.skip_android_selinux('if_nameindex')
def testInterfaceNameIndex(self):
interfaces = socket.if_nameindex()
for index, name in interfaces:
Expand All @@ -1127,6 +1128,7 @@ def testInterfaceNameIndex(self):

@unittest.skipUnless(hasattr(socket, 'if_indextoname'),
'socket.if_indextoname() not available.')
@support.skip_android_selinux('if_indextoname')
def testInvalidInterfaceIndexToName(self):
self.assertRaises(OSError, socket.if_indextoname, 0)
self.assertRaises(OverflowError, socket.if_indextoname, -1)
Expand All @@ -1146,6 +1148,7 @@ def testInvalidInterfaceIndexToName(self):

@unittest.skipUnless(hasattr(socket, 'if_nametoindex'),
'socket.if_nametoindex() not available.')
@support.skip_android_selinux('if_nametoindex')
def testInvalidInterfaceNameToIndex(self):
self.assertRaises(TypeError, socket.if_nametoindex, 0)
self.assertRaises(OSError, socket.if_nametoindex, '_DEADBEEF')
Expand Down Expand Up @@ -1878,6 +1881,7 @@ def test_getfqdn_filter_localhost(self):
@unittest.skipIf(sys.platform == 'win32', 'does not work on Windows')
@unittest.skipIf(AIX, 'Symbolic scope id does not work')
@unittest.skipUnless(hasattr(socket, 'if_nameindex'), "test needs socket.if_nameindex()")
@support.skip_android_selinux('if_nameindex')
def test_getaddrinfo_ipv6_scopeid_symbolic(self):
# Just pick up any network interface (Linux, Mac OS X)
(ifindex, test_interface) = socket.if_nameindex()[0]
Expand Down Expand Up @@ -1911,6 +1915,7 @@ def test_getaddrinfo_ipv6_scopeid_numeric(self):
@unittest.skipIf(sys.platform == 'win32', 'does not work on Windows')
@unittest.skipIf(AIX, 'Symbolic scope id does not work')
@unittest.skipUnless(hasattr(socket, 'if_nameindex'), "test needs socket.if_nameindex()")
@support.skip_android_selinux('if_nameindex')
def test_getnameinfo_ipv6_scopeid_symbolic(self):
# Just pick up any network interface.
(ifindex, test_interface) = socket.if_nameindex()[0]
Expand Down
Loading

0 comments on commit d98f13c

Please sign in to comment.