From b2e036c70dfee99275765f86f7ecd22337d2055a Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Mon, 5 Jun 2023 16:25:15 -0400 Subject: [PATCH] Escape application names for GMarkup GLib provides a parser called GMarkup, which implements a subset of XML. Application names may contain XML metacharacters, such as "<" and "&". These must be escaped to prevent XML injection, but the app menu didn't do that. The GMarkup documentation explicitly states that GMarkup must not be used to parse untrusted input [1]. Therefore, parsing malicious markup may have undefined results. Fortunately, there is no security problem because the only allowed character with special meaning in XML is "&" and ";" is not allowed. Therefore, there is no way to create a valid XML entity or inject tags. The worst that can happen is the creation of ill-formed markup that that GLib rejects. This patch also addresses a URL construction bug: filenames need to be URL-encoded in file:// URLs. [1]: https://github.com/GNOME/glib/blob/3304a517d9a7bdbb52d60394fdae6f9903f0f4f3/glib/gmarkup.c#L50-L51 --- qubes_menu/app_widgets.py | 4 +++- qubes_menu/utils.py | 24 ++++++++++++++---------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/qubes_menu/app_widgets.py b/qubes_menu/app_widgets.py index f060205..e0383aa 100644 --- a/qubes_menu/app_widgets.py +++ b/qubes_menu/app_widgets.py @@ -22,6 +22,7 @@ """ import subprocess import logging +import urllib.parse from typing import Optional, List from functools import reduce @@ -74,7 +75,8 @@ def __init__(self, app_info: ApplicationInfo, **properties): self.connect("drag-data-get", self._on_drag_data_get) def _on_drag_data_get(self, _widget, _drag_context, data, _info, _time): - data.set_uris(['file://' + str(self.app_info.file_path)]) + data.set_uris(['file://' + + urllib.parse.quote(str(self.app_info.file_path))]) def show_menu(self, _widget, event): """ diff --git a/qubes_menu/utils.py b/qubes_menu/utils.py index 67c7acc..99f4cf4 100644 --- a/qubes_menu/utils.py +++ b/qubes_menu/utils.py @@ -55,7 +55,6 @@ def load_icon(icon_name, pixbuf.fill(0x000) return pixbuf - def show_error(title, text): """ Helper function to display error messages. @@ -63,7 +62,7 @@ def show_error(title, text): dialog = Gtk.MessageDialog( None, 0, Gtk.MessageType.ERROR, Gtk.ButtonsType.OK) dialog.set_title(title) - dialog.set_markup(text) + dialog.set_markup(GLib.markup_escape_text(text)) dialog.connect("response", lambda *x: dialog.destroy()) dialog.show() @@ -91,7 +90,7 @@ def text_search(search_word: str, text_words: List[str]): def highlight_words(labels: List[Gtk.Label], search_words: List[str], - hl_tag: Optional[str] = None): + hl_tag: Optional[str] = None) -> None: """Highlight provided search_words in the provided labels.""" if not labels: return @@ -110,7 +109,7 @@ def highlight_words(labels: List[Gtk.Label], search_words: List[str], for label in labels: text = label.get_text() # remove existing highlighting - label.set_markup(text) + label.set_markup(GLib.markup_escape_text(text)) search_text = text.lower() found_intervals = [] for word in search_words: @@ -131,12 +130,17 @@ def highlight_words(labels: List[Gtk.Label], search_words: List[str], else: result_intervals.append(interval) - for interval in reversed(result_intervals): - start, end = interval - text = text[:start] + hl_tag + \ - text[start:end] + '' + text[end:] - - label.set_markup(text) + markup_list = [] + last_start = 0 + for start, end in reversed(result_intervals): + markup_list.append(GLib.markup_escape_text(text[last_start:start])) + markup_list.append(hl_tag) + markup_list.append(GLib.markup_escape_text(text[start:end])) + markup_list.append('') + last_start = end + markup_list.append(GLib.markup_escape_text(text[last_start:])) + + label.set_markup("".join(markup_list)) def get_visible_child(widget: Gtk.Container, reverse=False):