Skip to content

Commit

Permalink
Move colorization into named disposable
Browse files Browse the repository at this point in the history
  • Loading branch information
NobodySpecial256 committed May 1, 2024
1 parent e86e7fa commit 2a3a76b
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 22 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
78 changes: 59 additions & 19 deletions color.py
Original file line number Diff line number Diff line change
@@ -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: "<span data-mx-color='%s' style='color: %s;'>" %(color, color)
TAG_END = lambda: "</span>"

if len(argv) not in [1, 2]:
print("Usage: %s <color>" %(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
Expand Down Expand Up @@ -131,7 +140,7 @@ def colorify_nb_words(char, ix, length, text):

colorify = colors["default"]

def main():
def dvm_agent():
global colorify

color = ""
Expand All @@ -144,26 +153,57 @@ 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 <color>" %(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)
gtk_app = NotificationApp(wm, qubes_app, dispatcher)
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()

0 comments on commit 2a3a76b

Please sign in to comment.