gh-118908: Limit exposed globals from internal imports and definitions on new REPL startup (#119547)

This commit is contained in:
Eugene Triguba 2024-06-11 13:40:31 -04:00 committed by GitHub
parent 0335662fe1
commit 86a8a1c57a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 83 additions and 8 deletions

View File

@ -27,6 +27,7 @@ from __future__ import annotations
import _sitebuiltins
import linecache
import builtins
import sys
import code
from types import ModuleType
@ -34,6 +35,12 @@ from types import ModuleType
from .console import InteractiveColoredConsole
from .readline import _get_reader, multiline_input
TYPE_CHECKING = False
if TYPE_CHECKING:
from typing import Any
_error: tuple[type[Exception], ...] | type[Exception]
try:
from .unix_console import _error
@ -73,20 +80,28 @@ REPL_COMMANDS = {
"clear": _clear_screen,
}
DEFAULT_NAMESPACE: dict[str, Any] = {
'__name__': '__main__',
'__doc__': None,
'__package__': None,
'__loader__': None,
'__spec__': None,
'__annotations__': {},
'__builtins__': builtins,
}
def run_multiline_interactive_console(
mainmodule: ModuleType | None = None,
future_flags: int = 0,
console: code.InteractiveConsole | None = None,
) -> None:
import __main__
from .readline import _setup
_setup()
mainmodule = mainmodule or __main__
namespace = mainmodule.__dict__ if mainmodule else DEFAULT_NAMESPACE
if console is None:
console = InteractiveColoredConsole(
mainmodule.__dict__, filename="<stdin>"
namespace, filename="<stdin>"
)
if future_flags:
console.compile.compiler.flags |= future_flags

View File

@ -1,9 +1,13 @@
import itertools
import io
import itertools
import os
import rlcompleter
from unittest import TestCase
import select
import subprocess
import sys
from unittest import TestCase, skipUnless
from unittest.mock import patch
from test.support import force_not_colorized
from .support import (
FakeConsole,
@ -17,6 +21,10 @@ from _pyrepl.console import Event
from _pyrepl.readline import ReadlineAlikeReader, ReadlineConfig
from _pyrepl.readline import multiline_input as readline_multiline_input
try:
import pty
except ImportError:
pty = None
class TestCursorPosition(TestCase):
def prepare_reader(self, events):
@ -828,3 +836,54 @@ class TestPasteEvent(TestCase):
reader = self.prepare_reader(events)
output = multiline_input(reader)
self.assertEqual(output, input_code)
@skipUnless(pty, "requires pty")
class TestMain(TestCase):
@force_not_colorized
def test_exposed_globals_in_repl(self):
expected_output = (
"[\'__annotations__\', \'__builtins__\', \'__doc__\', \'__loader__\', "
"\'__name__\', \'__package__\', \'__spec__\']"
)
output, exit_code = self.run_repl(["sorted(dir())", "exit"])
if "can\'t use pyrepl" in output:
self.skipTest("pyrepl not available")
self.assertEqual(exit_code, 0)
self.assertIn(expected_output, output)
def test_dumb_terminal_exits_cleanly(self):
env = os.environ.copy()
env.update({"TERM": "dumb"})
output, exit_code = self.run_repl("exit()\n", env=env)
self.assertEqual(exit_code, 0)
self.assertIn("warning: can\'t use pyrepl", output)
self.assertNotIn("Exception", output)
self.assertNotIn("Traceback", output)
def run_repl(self, repl_input: str | list[str], env: dict | None = None) -> tuple[str, int]:
master_fd, slave_fd = pty.openpty()
process = subprocess.Popen(
[sys.executable, "-i", "-u"],
stdin=slave_fd,
stdout=slave_fd,
stderr=slave_fd,
text=True,
close_fds=True,
env=env if env else os.environ,
)
if isinstance(repl_input, list):
repl_input = "\n".join(repl_input) + "\n"
os.write(master_fd, repl_input.encode("utf-8"))
output = []
while select.select([master_fd], [], [], 0.5)[0]:
data = os.read(master_fd, 1024).decode("utf-8")
if not data:
break
output.append(data)
os.close(master_fd)
os.close(slave_fd)
exit_code = process.wait()
return "\n".join(output), exit_code

View File

@ -1,9 +1,9 @@
"""Test the interactive interpreter."""
import sys
import os
import unittest
import subprocess
import sys
import unittest
from textwrap import dedent
from test import support
from test.support import cpython_only, has_subprocess_support, SuppressCrashReport
@ -199,7 +199,6 @@ class TestInteractiveInterpreter(unittest.TestCase):
assert_python_ok("-m", "asyncio")
class TestInteractiveModeSyntaxErrors(unittest.TestCase):
def test_interactive_syntax_error_correct_line(self):

View File

@ -0,0 +1,2 @@
Limit exposed globals from internal imports and definitions on new REPL
startup. Patch by Eugene Triguba and Pablo Galindo.