From 2a3a76ba5a50fc244233b178274a96dde670d032 Mon Sep 17 00:00:00 2001 From: NobodySpecial Date: Wed, 1 May 2024 07:39:41 +0000 Subject: [PATCH] Move colorization into named disposable --- README.md | 6 ++--- color.py | 78 +++++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 62 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 2ae17e2..0862fc8 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # qubes-color Colorify text in Qubes' global clipboard -#### Warning: +#### Setup -This script parses the Qubes global clipboard (which is encoded via UTF-8). It's possible for the Python environment to be buggy, and as a result, the security of dom0 cannot be completely guaranteed when using this script, if the clipboard is maliciously modified +Before using this script, you need to create a named disposable `sys-colorify`, which will do the actual processing. This way, dom0 is entirely isolated from any potentially-malicious inputs -The script is a very small security risk, but this doesn't mean it's not a risk. There's a non-zero chance that it gets exploited to compromise dom0 +`sys-colorify` can be based on any template, including minimal templates. It is recommended to deny network access, since it really has no need to connect to the internet ### How to copy files to dom0 diff --git a/color.py b/color.py index 50118dd..1275859 100755 --- a/color.py +++ b/color.py @@ -1,19 +1,28 @@ #!/usr/bin/env python3 -#import re # For using RegEx parsing (may increase attack surface if the RegEx implementation has bugs) - from sys import argv, stderr -# Refer to /usr/lib/python3.11/site-packages/qui/clipboard.py -from qui.clipboard import pyinotify, qubesadmin, NotificationApp, DATA, Gtk, Gdk -from html import escape +# Modules which increase attack surface are only imported if running in a colorification agent +if len(argv) > 2 and argv[1] == "--dvm-agent": + import re + from html import escape + +from subprocess import run # For spawning disposables +from string import printable # For sanitizing strings TAG_START = lambda color: "" %(color, color) TAG_END = lambda: "" -if len(argv) not in [1, 2]: - print("Usage: %s " %(argv[0]), file=stderr) - raise SystemExit +# The name of the VM to use for processing. This should be a disposable VM, but technically it can be any VM you want, except dom0 +# It is not recommended to do processing in a trusted VM, since the global clipboard is an untrusted input +AGENT_QUBE = "sys-colorify" + +def clean_str(string): + ret = "" + for char in string: + if char in printable: + ret += char + return ret re_defined = True try: re @@ -131,7 +140,7 @@ def colorify_nb_words(char, ix, length, text): colorify = colors["default"] -def main(): +def dvm_agent(): global colorify color = "" @@ -144,6 +153,35 @@ def main(): else: colorify = colors[color] + with open(DATA, 'r', encoding='utf-8') as contents: + global_text = contents.read() + global_text_length = len(global_text) + + colored = ColoredString() + for ix, char in enumerate(global_text): + colored += colorify(char, ix, global_text_length, global_text) + + return colored + +def main(): + global argv + + if len(argv) > 2 and argv[1] == "--dvm-agent": # Tells the script that it's running in a disposable + global DATA + DATA = argv[2] + argv = argv[2:] + print(dvm_agent(), end="") + return + elif len(argv) not in [1, 2]: + print("Usage: %s " %(argv[0]), file=stderr) + raise SystemExit + + if AGENT_QUBE == "dom0": + raise ValueError("For security reasons, dom0 cannot be used as a colorification agent") + + # Refer to /usr/lib/python3.11/site-packages/qui/clipboard.py + from qui.clipboard import pyinotify, qubesadmin, NotificationApp, DATA, Gtk, Gdk + wm = pyinotify.WatchManager() qubes_app = qubesadmin.Qubes() dispatcher = qubesadmin.events.EventsDispatcher(qubes_app) @@ -151,19 +189,21 @@ def main(): clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) text = clipboard.wait_for_text() - with open(DATA, 'r', encoding='utf-8') as contents: - global_text = contents.read() - global_text_length = len(global_text) + with open(argv[0], 'rb') as prgm: + prgm_bin = prgm.read() + with open(DATA, 'rb') as contents: + global_bin = contents.read() - colored = ColoredString() - for ix, char in enumerate(global_text): - colored += colorify(char, ix, global_text_length, global_text) + run(["/usr/bin/qvm-run", "-u", "root", "--pass-io", AGENT_QUBE, "tee", argv[0]], input=prgm_bin, capture_output=True) + run(["/usr/bin/qvm-run", "-u", "root", "--pass-io", AGENT_QUBE, "tee", DATA], input=global_bin, capture_output=True) + + colored = clean_str(run(["/usr/bin/qvm-run", "--pass-io", AGENT_QUBE, "python3", argv[0], "--dvm-agent", DATA] + argv[1:], capture_output=True).stdout.decode(encoding="ascii", errors="replace")) - clipboard.set_text(str(colored), -1) - gtk_app.copy_dom0_clipboard() + clipboard.set_text(colored, -1) + gtk_app.copy_dom0_clipboard() - if text != None: - clipboard.set_text(text, -1) + if text != None: + clipboard.set_text(text, -1) if __name__ == "__main__": main()