Skip to content

gh-134873: Fix a DOS issue in idlelib #134874

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
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
34 changes: 23 additions & 11 deletions Lib/idlelib/editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -1203,10 +1203,7 @@ def load_extension(self, name):
self.apply_bindings(keydefs)
for vevent in keydefs:
methodname = vevent.replace("-", "_")
while methodname[:1] == '<':
methodname = methodname[1:]
while methodname[-1:] == '>':
methodname = methodname[:-1]
methodname = methodname.lstrip('<').rstrip('>')
methodname = methodname + "_event"
if hasattr(ins, methodname):
self.text.bind(vevent, getattr(ins, methodname))
Expand Down Expand Up @@ -1341,6 +1338,24 @@ def set_indentation_params(self, is_py_src, guess=True):
self.usetabs = False
self.set_tk_tabwidth(self.tabwidth)

def delete_trail_char_and_space(self, want, chars, tabwidth):
chars = chars[:-1] # remove last character unconditionally
current_pos = 0
ncharsretained = 0
for char in chars:
if char == '\t':
current_pos = (current_pos // tabwidth + 1) * tabwidth
else:
current_pos += 1
if current_pos > want:
break
ncharsretained += 1
for i in range(ncharsretained, len(chars)):
if chars[i] not in " \t":
ncharsretained = i + 1
chars = chars[:ncharsretained]
return chars

def smart_backspace_event(self, event):
text = self.text
first, last = self.get_selection_indices()
Expand Down Expand Up @@ -1369,13 +1384,10 @@ def smart_backspace_event(self, event):
assert have > 0
want = ((have - 1) // self.indentwidth) * self.indentwidth
# Debug prompt is multilined....
ncharsdeleted = 0
while True:
chars = chars[:-1]
ncharsdeleted = ncharsdeleted + 1
have = len(chars.expandtabs(tabwidth))
if have <= want or chars[-1] not in " \t":
break
oldchars = chars
chars = self.delete_trail_char_and_space(want, chars, tabwidth)
ncharsdeleted = len(oldchars) - len(chars)
have = len(chars.expandtabs(tabwidth))
text.undo_block_start()
text.delete("insert-%dc" % ncharsdeleted, "insert")
if have < want:
Expand Down
62 changes: 61 additions & 1 deletion Lib/idlelib/idle_test/test_editor.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
"Test editor, coverage 53%."
"Test editor, coverage approximately 53%."

from idlelib import editor
import unittest
from collections import namedtuple
from test.support import requires
from tkinter import Tk, Text
import time

Editor = editor.EditorWindow

Expand Down Expand Up @@ -237,5 +238,64 @@ def test_rclick(self):
pass


class DeleteWantTest(unittest.TestCase):

data = [
("abcde" + 10000 * "\t" + 10000 * " ", [
(30000, 4, "abcde" + 7499 * "\t"),
(41005, 4, "abcde" + 10000 * "\t" + 1001 * " "),
(3, 4, "abcde"),
(6, 4, "abcde"),
(30002, 4, "abcde" + 7499 * "\t"),
]),
("abcde\tabd\t\t", [
(7, 4, "abcde\tabd"),
(12, 4, "abcde\tabd\t"),
(13, 4, "abcde\tabd\t"),
(16, 4, "abcde\tabd\t"),
]),
("abcde\tabd\t \ta", [
(7, 4, "abcde\tabd"),
(12, 4, "abcde\tabd\t"),
(13, 4, "abcde\tabd\t "),
(16, 4, "abcde\tabd\t \t"),
]),
(" ", [
(2, 2, " "),
]),
]

def mock_delete_trail_char_and_space(self, want, chars, tabwidth):
ncharsdeleted = 0
while True:
chars = chars[:-1]
ncharsdeleted = ncharsdeleted + 1
have = len(chars.expandtabs(tabwidth))
if have <= want or chars[-1] not in " \t":
break
return chars

def do_tests(self):
ew = Editor()
for dat in self.data:
test_str = dat[0]
for da in dat[1]:
with self.subTest(want=da[0], tabwidth=da[1], input=test_str):
res = ew.delete_trail_char_and_space(da[0], test_str, da[1])
self.assertEqual(res, da[2])

def test_delete_trail_char_and_space(self):
with unittest.mock.patch.object(Editor, '__init__', return_value=None):
initial_time_new = time.time()
self.do_tests()
time_new = time.time() - initial_time_new

with unittest.mock.patch.object(Editor, 'delete_trail_char_and_space', self.mock_delete_trail_char_and_space):
initial_time_old = time.time()
self.do_tests()
time_old = time.time() - initial_time_old

self.assertGreaterEqual(time_old / time_new, 10)

if __name__ == '__main__':
unittest.main(verbosity=2)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix a DOS vulnerability in :mod:`idlelib` regarding string slicing.
Loading