gh-112997: Don't log arguments in asyncio unless debugging (#115667)

Nothing else in Python generally logs the contents of variables, so this
can be very unexpected for developers and could leak sensitive
information in to terminals and log files.
This commit is contained in:
Pierre Ossman (ThinLinc team) 2024-02-28 02:39:08 +01:00 committed by GitHub
parent a355f60b03
commit 5a1559d949
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 42 additions and 12 deletions

View File

@ -54,7 +54,8 @@ class Handle:
info.append('cancelled')
if self._callback is not None:
info.append(format_helpers._format_callback_source(
self._callback, self._args))
self._callback, self._args,
debug=self._loop.get_debug()))
if self._source_traceback:
frame = self._source_traceback[-1]
info.append(f'created at {frame[0]}:{frame[1]}')
@ -90,7 +91,8 @@ class Handle:
raise
except BaseException as exc:
cb = format_helpers._format_callback_source(
self._callback, self._args)
self._callback, self._args,
debug=self._loop.get_debug())
msg = f'Exception in callback {cb}'
context = {
'message': msg,

View File

@ -19,19 +19,26 @@ def _get_function_source(func):
return None
def _format_callback_source(func, args):
func_repr = _format_callback(func, args, None)
def _format_callback_source(func, args, *, debug=False):
func_repr = _format_callback(func, args, None, debug=debug)
source = _get_function_source(func)
if source:
func_repr += f' at {source[0]}:{source[1]}'
return func_repr
def _format_args_and_kwargs(args, kwargs):
def _format_args_and_kwargs(args, kwargs, *, debug=False):
"""Format function arguments and keyword arguments.
Special case for a single parameter: ('hello',) is formatted as ('hello').
Note that this function only returns argument details when
debug=True is specified, as arguments may contain sensitive
information.
"""
if not debug:
return '()'
# use reprlib to limit the length of the output
items = []
if args:
@ -41,10 +48,11 @@ def _format_args_and_kwargs(args, kwargs):
return '({})'.format(', '.join(items))
def _format_callback(func, args, kwargs, suffix=''):
def _format_callback(func, args, kwargs, *, debug=False, suffix=''):
if isinstance(func, functools.partial):
suffix = _format_args_and_kwargs(args, kwargs) + suffix
return _format_callback(func.func, func.args, func.keywords, suffix)
suffix = _format_args_and_kwargs(args, kwargs, debug=debug) + suffix
return _format_callback(func.func, func.args, func.keywords,
debug=debug, suffix=suffix)
if hasattr(func, '__qualname__') and func.__qualname__:
func_repr = func.__qualname__
@ -53,7 +61,7 @@ def _format_callback(func, args, kwargs, suffix=''):
else:
func_repr = repr(func)
func_repr += _format_args_and_kwargs(args, kwargs)
func_repr += _format_args_and_kwargs(args, kwargs, debug=debug)
if suffix:
func_repr += suffix
return func_repr

View File

@ -2250,7 +2250,7 @@ class HandleTests(test_utils.TestCase):
h = asyncio.Handle(noop, (1, 2), self.loop)
filename, lineno = test_utils.get_function_source(noop)
self.assertEqual(repr(h),
'<Handle noop(1, 2) at %s:%s>'
'<Handle noop() at %s:%s>'
% (filename, lineno))
# cancelled handle
@ -2268,14 +2268,14 @@ class HandleTests(test_utils.TestCase):
# partial function
cb = functools.partial(noop, 1, 2)
h = asyncio.Handle(cb, (3,), self.loop)
regex = (r'^<Handle noop\(1, 2\)\(3\) at %s:%s>$'
regex = (r'^<Handle noop\(\)\(\) at %s:%s>$'
% (re.escape(filename), lineno))
self.assertRegex(repr(h), regex)
# partial function with keyword args
cb = functools.partial(noop, x=1)
h = asyncio.Handle(cb, (2, 3), self.loop)
regex = (r'^<Handle noop\(x=1\)\(2, 3\) at %s:%s>$'
regex = (r'^<Handle noop\(\)\(\) at %s:%s>$'
% (re.escape(filename), lineno))
self.assertRegex(repr(h), regex)
@ -2316,6 +2316,24 @@ class HandleTests(test_utils.TestCase):
'<Handle cancelled noop(1, 2) at %s:%s created at %s:%s>'
% (filename, lineno, create_filename, create_lineno))
# partial function
cb = functools.partial(noop, 1, 2)
create_lineno = sys._getframe().f_lineno + 1
h = asyncio.Handle(cb, (3,), self.loop)
regex = (r'^<Handle noop\(1, 2\)\(3\) at %s:%s created at %s:%s>$'
% (re.escape(filename), lineno,
re.escape(create_filename), create_lineno))
self.assertRegex(repr(h), regex)
# partial function with keyword args
cb = functools.partial(noop, x=1)
create_lineno = sys._getframe().f_lineno + 1
h = asyncio.Handle(cb, (2, 3), self.loop)
regex = (r'^<Handle noop\(x=1\)\(2, 3\) at %s:%s created at %s:%s>$'
% (re.escape(filename), lineno,
re.escape(create_filename), create_lineno))
self.assertRegex(repr(h), regex)
def test_handle_source_traceback(self):
loop = asyncio.get_event_loop_policy().new_event_loop()
loop.set_debug(True)

View File

@ -0,0 +1,2 @@
Stop logging potentially sensitive callback arguments in :mod:`asyncio`
unless debug mode is active.