Sanitize notifications ids as they are retransmitted over the TTY

This commit is contained in:
Kovid Goyal 2022-09-05 10:41:19 +05:30
parent c455fea729
commit f05783e64d
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
4 changed files with 19 additions and 3 deletions

View File

@ -56,6 +56,10 @@ Detailed list of changes
- Wayland: Fix remembering window size not accurate when client side decorations are present - Wayland: Fix remembering window size not accurate when client side decorations are present
- Fix an issue where notification identifiers were not sanitized leading to
code execution if the user clicked on a notification popup from a malicious
source. Thanks to Carter Sande for discovering this vulnerability.
0.26.1 [2022-08-30] 0.26.1 [2022-08-30]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -1,13 +1,15 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# License: GPLv3 Copyright: 2019, Kovid Goyal <kovid at kovidgoyal.net> # License: GPLv3 Copyright: 2019, Kovid Goyal <kovid at kovidgoyal.net>
import re
from base64 import standard_b64decode from base64 import standard_b64decode
from collections import OrderedDict from collections import OrderedDict
from itertools import count from itertools import count
from typing import Dict, Optional, Callable from typing import Callable, Dict, Optional
from .constants import is_macos, logo_png_file from .constants import is_macos, logo_png_file
from .fast_data_types import get_boss from .fast_data_types import get_boss
from .types import run_once
from .utils import log_error from .utils import log_error
NotifyImplementation = Callable[[str, str, str], None] NotifyImplementation = Callable[[str, str, str], None]
@ -92,6 +94,11 @@ def parse_osc_777(raw: str) -> NotificationCommand:
return ans return ans
@run_once
def sanitize_identifier_pat() -> 're.Pattern[str]':
return re.compile(r'[^a-zA-Z0-9-_+.]+')
def parse_osc_99(raw: str) -> NotificationCommand: def parse_osc_99(raw: str) -> NotificationCommand:
cmd = NotificationCommand() cmd = NotificationCommand()
metadata, payload = raw.partition(';')[::2] metadata, payload = raw.partition(';')[::2]
@ -107,7 +114,7 @@ def parse_osc_99(raw: str) -> NotificationCommand:
if k == 'p': if k == 'p':
payload_type = v payload_type = v
elif k == 'i': elif k == 'i':
cmd.identifier = v cmd.identifier = sanitize_identifier_pat().sub('', v)
elif k == 'e': elif k == 'e':
payload_is_encoded = v == '1' payload_is_encoded = v == '1'
elif k == 'd': elif k == 'd':

View File

@ -41,7 +41,7 @@ from .fast_data_types import (
update_window_title, update_window_visibility, wakeup_main_loop update_window_title, update_window_visibility, wakeup_main_loop
) )
from .keys import keyboard_mode_name, mod_mask from .keys import keyboard_mode_name, mod_mask
from .notify import NotificationCommand, handle_notification_cmd from .notify import NotificationCommand, handle_notification_cmd, sanitize_identifier_pat
from .options.types import Options from .options.types import Options
from .rgb import to_color from .rgb import to_color
from .terminfo import get_capabilities from .terminfo import get_capabilities
@ -1001,6 +1001,7 @@ class Window:
self.screen.send_escape_code_to_child(OSC, f'{code};rgb:{r:04x}/{g:04x}/{b:04x}') self.screen.send_escape_code_to_child(OSC, f'{code};rgb:{r:04x}/{g:04x}/{b:04x}')
def report_notification_activated(self, identifier: str) -> None: def report_notification_activated(self, identifier: str) -> None:
identifier = sanitize_identifier_pat().sub('', identifier)
self.screen.send_escape_code_to_child(OSC, f'99;i={identifier};') self.screen.send_escape_code_to_child(OSC, f'99;i={identifier};')
def set_dynamic_color(self, code: int, value: Union[str, bytes]) -> None: def set_dynamic_color(self, code: int, value: Union[str, bytes]) -> None:

View File

@ -555,3 +555,7 @@ class TestDataTypes(BaseTest):
self.assertEqual(hash(SingleKey(key=1)), hash(SingleKey(key=1))) self.assertEqual(hash(SingleKey(key=1)), hash(SingleKey(key=1)))
self.assertNotEqual(hash(SingleKey(key=1, mods=2)), hash(SingleKey(key=1))) self.assertNotEqual(hash(SingleKey(key=1, mods=2)), hash(SingleKey(key=1)))
self.assertNotEqual(SingleKey(key=1, mods=2), SingleKey(key=1)) self.assertNotEqual(SingleKey(key=1, mods=2), SingleKey(key=1))
def test_notify_identifier_sanitization(self):
from kitty.notify import sanitize_identifier_pat
self.ae(sanitize_identifier_pat().sub('', '\x1b\nabc\n[*'), 'abc')