From f05783e64d5fa62e1aed603e8d69aced5e49824f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 5 Sep 2022 10:41:19 +0530 Subject: [PATCH] Sanitize notifications ids as they are retransmitted over the TTY --- docs/changelog.rst | 4 ++++ kitty/notify.py | 11 +++++++++-- kitty/window.py | 3 ++- kitty_tests/datatypes.py | 4 ++++ 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 6c9851f2a..926770e19 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -56,6 +56,10 @@ Detailed list of changes - 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] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/kitty/notify.py b/kitty/notify.py index 00dfff5a6..ad316c509 100644 --- a/kitty/notify.py +++ b/kitty/notify.py @@ -1,13 +1,15 @@ #!/usr/bin/env python3 # License: GPLv3 Copyright: 2019, Kovid Goyal +import re from base64 import standard_b64decode from collections import OrderedDict 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 .fast_data_types import get_boss +from .types import run_once from .utils import log_error NotifyImplementation = Callable[[str, str, str], None] @@ -92,6 +94,11 @@ def parse_osc_777(raw: str) -> NotificationCommand: 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: cmd = NotificationCommand() metadata, payload = raw.partition(';')[::2] @@ -107,7 +114,7 @@ def parse_osc_99(raw: str) -> NotificationCommand: if k == 'p': payload_type = v elif k == 'i': - cmd.identifier = v + cmd.identifier = sanitize_identifier_pat().sub('', v) elif k == 'e': payload_is_encoded = v == '1' elif k == 'd': diff --git a/kitty/window.py b/kitty/window.py index 544aae739..db92dc9cf 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -41,7 +41,7 @@ from .fast_data_types import ( update_window_title, update_window_visibility, wakeup_main_loop ) 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 .rgb import to_color 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}') 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};') def set_dynamic_color(self, code: int, value: Union[str, bytes]) -> None: diff --git a/kitty_tests/datatypes.py b/kitty_tests/datatypes.py index 9369a115f..f4622a134 100644 --- a/kitty_tests/datatypes.py +++ b/kitty_tests/datatypes.py @@ -555,3 +555,7 @@ class TestDataTypes(BaseTest): self.assertEqual(hash(SingleKey(key=1)), 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)) + + def test_notify_identifier_sanitization(self): + from kitty.notify import sanitize_identifier_pat + self.ae(sanitize_identifier_pat().sub('', '\x1b\nabc\n[*'), 'abc')