Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ignore invalid GUI related features and options #315

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ include:
project: QubesOS/qubes-continuous-integration
checks:pylint:
before_script:
- sudo dnf install -y python3-rpm
- sudo dnf install -y python3-rpm python3-xlib
- pip3 install --quiet -r ci/requirements.txt
script:
- export PATH="$HOME/.local/bin:$PATH"
Expand All @@ -19,15 +19,15 @@ checks:tests:
after_script:
- ci/codecov-wrapper
before_script:
- sudo dnf install -y openssl python3-rpm sequoia-sqv
- sudo dnf install -y openssl python3-rpm sequoia-sqv python3-xlib libnotify
xorg-x11-server-Xvfb
- pip3 install --quiet -r ci/requirements.txt
- git config --global --add safe.directory "$PWD"
script:
- python3 setup.py build
# simulate "normal" qubes env for most tests
- export DISPLAY=:0
- ./run-tests
- python3 -m coverage xml
- xvfb-run ./run-tests
- xvfb-run python3 -m coverage xml
stage: checks
tags:
- docker
Expand Down
1 change: 1 addition & 0 deletions debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Depends:
qubes-repo-templates,
scrypt,
qubes-rpm-oxide (>= 0.2.3),
python3-xlib,
${python:Depends},
${python3:Depends},
${misc:Depends},
Expand Down
36 changes: 34 additions & 2 deletions qubesadmin/tests/tools/qvm_start_daemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@

import qubesadmin.tests
import qubesadmin.tools.qvm_start_daemon
from qubesadmin.tools.qvm_start_daemon import GUI_DAEMON_OPTIONS
from qubesadmin.tools.qvm_start_daemon import GUI_DAEMON_OPTIONS
from qubesadmin.tools.qvm_start_daemon import validator_key_sequence, \
validator_trayicon_mode, validator_color
import qubesadmin.vm


Expand Down Expand Up @@ -93,7 +95,7 @@ def setup_common_args(self):
('test-vm', 'admin.vm.property.Get', 'xid', None)] = \
b'0\x00default=99 type=int 99'

for name, _kind in GUI_DAEMON_OPTIONS:
for name, _kind, _validator in GUI_DAEMON_OPTIONS:
self.app.expected_calls[
('test-vm', 'admin.vm.feature.Get',
'gui-' + name.replace('_', '-'), None)] = \
Expand Down Expand Up @@ -754,3 +756,33 @@ def test_070_send_monitor_layout_all(self):
unittest.mock.call(vm2, monitor_layout)])
mock_get_monior_layout.assert_called_once_with()
self.assertAllCalled()

def test_071_validators(self):
self.assertFalse(validator_key_sequence(123))
self.assertFalse(validator_key_sequence('Super-X'))
self.assertTrue(validator_key_sequence('Ctrl-shift-F12'))
self.assertFalse(validator_key_sequence('F13'))
self.assertFalse(validator_trayicon_mode(True))
self.assertTrue(validator_trayicon_mode('bg'))
self.assertTrue(validator_trayicon_mode('tint+border1'))
self.assertFalse(validator_trayicon_mode('shadow'))
self.assertFalse(validator_color(321))
self.assertTrue(validator_color('0x123456'))
self.assertTrue(validator_color('0XFFF'))
self.assertTrue(validator_color('lime'))
self.assertFalse(validator_color('scarlet'))
self.assertFalse(validator_color('octarine'))

def test_072_feature_validation(self):
self.setup_common_args()

self.app.expected_calls[
('gui-vm', 'admin.vm.feature.Get',
'gui-default-override-redirect', None)] = \
b'0\x00ask'

_args, config = self.run_common_args()
self.assertEqual(config, '''\
global: {
}
''')
106 changes: 89 additions & 17 deletions qubesadmin/tools/qvm_start_daemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,22 +39,77 @@
import qubesadmin.vm
from qubesadmin.tools import xcffibhelpers

# pylint: disable=wrong-import-position,wrong-import-order
from Xlib import X, XK
from Xlib.display import Display
from Xlib.error import DisplayConnectionError
Comment on lines +42 to +45
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two issues here:

  1. I believe nowadays it should be "xcffib" - "xcb" is preferred over "xlib" directly, and its python bindings are in python3-xcffib
  2. You forgot to add dependency to the debian package, so qvm-start-daemon fails on all Debian debian tests.

Copy link
Contributor Author

@alimirjamali alimirjamali Nov 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I believe nowadays it should be "xcffib" - "xcb" is preferred over "xlib" directly, and its python bindings are in python3-xcffib

I have to study xcffib documentation as it appears to be a drop-in replacement for xcb rather than xlib. It does not provide alloc_named_color and string_to_keysym functions (or I can not find it).

2. You forgot to add dependency to the debian package, so qvm-start-daemon fails on all Debian debian tests.

OK. I will do it

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there are no easy equivalents, then I guess it may stay with python3-xlib, but check first.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://stackoverflow.com/a/50539803

(Except that it only shows keysym values, not keysym names, which you can find in /usr/include/X11/keysymdef.h or using Xlib's XKeysymToString(), and I don't think there exists an XCB port of that function, but you could write your own based on keysymdef.h.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://stackoverflow.com/a/50539803

As Geoffrey comments in the Stackoverflow, his suggested code does not show keysym names (only syms & values). And suggests XKeysymToString (which I am using it's python-xlib equivalent here).

For the keyboard, It might be possible to revert to python3-xkbcommon which is Python bindings for libxkbcommon using cffi. And what is used nowadays with Wayland as well.

For the color, I have to look for alternatives

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marmarek there are positive and negative things about python3-xkbcommon. I tested it and it works well. While it is the new modern approach and what is currently used with both X & Wayland, It is only available for Fedora 38 onward. Not Fedora 37. So it is time to decide if this patch is going to come only to Qubes R4.3 or R4.2 as well.

Copy link
Contributor Author

@alimirjamali alimirjamali Nov 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For color, using xcffib.xproto.xprotoExtension().AllocNamedColor() works. But not as good as the Xlib's alloc_named_color. Since Xlib supports #RGB, #RRGGBB, rgb:RRRRGGGGBBBB, etc (as well as the named colors).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is only available for Fedora 38 onward.

It also isn't available in Debian... So, I guess python3-xlib it is.

Copy link
Contributor Author

@alimirjamali alimirjamali Nov 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also isn't available in Debian... So, I guess python3-xlib it is.

During the early stages of writing this PR, I used higher level GTK 4.0 for Keysym name validation (reference). It is still possible. And the ugly approach of try import xcffib except import xlib is also possible. Still your decision.


GUI_DAEMON_PATH = '/usr/bin/qubes-guid'
PACAT_DAEMON_PATH = '/usr/bin/pacat-simple-vchan'
QUBES_ICON_DIR = '/usr/share/icons/hicolor/128x128/devices'

def validator_key_sequence(sequence: str) -> bool:
""" xside.c key sequence validation is not case sensitive and supports more
choices than Global Config's limited choices, so we replicate it here """
if not isinstance(sequence, str):
return False
if sequence.lower() in ['none', 'disable']:
return True

Check warning on line 57 in qubesadmin/tools/qvm_start_daemon.py

View check run for this annotation

Codecov / codecov/patch

qubesadmin/tools/qvm_start_daemon.py#L57

Added line #L57 was not covered by tests
while '-' in sequence:
modifier, sequence = sequence.split('-', 1)
if not modifier.lower() in \
['shift', 'ctrl', 'alt', 'mod1', 'mod2', 'mod3', 'mod4']:
return False
try:
display = Display()
result = display.keysym_to_keycode(XK.string_to_keysym(sequence))
display.close()
return result != X.NoSymbol
except DisplayConnectionError:
return False

Check warning on line 69 in qubesadmin/tools/qvm_start_daemon.py

View check run for this annotation

Codecov / codecov/patch

qubesadmin/tools/qvm_start_daemon.py#L68-L69

Added lines #L68 - L69 were not covered by tests

def validator_trayicon_mode(mode: str) -> bool:
""" xside.c tray mode validation is replicated here """
if not isinstance(mode, str):
return False
if mode in ['bg', 'border1', 'border2', 'tint']:
return True
if mode.startswith('tint'):
if mode[4:] in ['+border1', '+border2', '+saturation50', '+whitehack']:
return True
return False

def validator_color(color: str) -> bool:
""" xside.c `parse_color` validation code is replicated here """
if not isinstance(color, str):
return False
if re.match(r"^0[xX](?:[0-9a-fA-F]{3}){1,2}$", color.strip()):
# Technically `parse_color` could parse values such as `0x0`
# Xlib.xobject.colormap conventions & standards are different.
return True
try:
display = Display()
result = display.screen().default_colormap.alloc_named_color(color)
display.close()
if result is not None:
return True
except DisplayConnectionError:
return False

Check warning on line 97 in qubesadmin/tools/qvm_start_daemon.py

View check run for this annotation

Codecov / codecov/patch

qubesadmin/tools/qvm_start_daemon.py#L96-L97

Added lines #L96 - L97 were not covered by tests
return False

GUI_DAEMON_OPTIONS = [
('allow_fullscreen', 'bool'),
('override_redirect_protection', 'bool'),
('override_redirect', 'str'),
('allow_utf8_titles', 'bool'),
('secure_copy_sequence', 'str'),
('secure_paste_sequence', 'str'),
('windows_count_limit', 'int'),
('trayicon_mode', 'str'),
('window_background_color', 'str'),
('startup_timeout', 'int'),
('max_clipboard_size', 'int'),
('allow_fullscreen', 'bool', (lambda x: isinstance(x, bool))),
('override_redirect_protection', 'bool', (lambda x: isinstance(x, bool))),
('override_redirect', 'str', (lambda x: x in ['allow', 'disable'])),
('allow_utf8_titles', 'bool', (lambda x: isinstance(x, bool))),
('secure_copy_sequence', 'str', validator_key_sequence),
('secure_paste_sequence', 'str', validator_key_sequence),
('windows_count_limit', 'int', (lambda x: isinstance(x, int) and x > 0)),
('trayicon_mode', 'str', validator_trayicon_mode),
('window_background_color', 'str', validator_color),
('startup_timeout', 'int', (lambda x: isinstance(x, int) and x >= 0)),
('max_clipboard_size', 'int', \
(lambda x: isinstance(x, int) and 256 <= x <= 256000)),
]

formatter = logging.Formatter(
Expand Down Expand Up @@ -108,12 +163,12 @@

options = {}

for name, kind in GUI_DAEMON_OPTIONS:
feature_value = vm.features.get(
'gui-' + name.replace('_', '-'), None)
for name, kind, validator in GUI_DAEMON_OPTIONS:
feature = 'gui-' + name.replace('_', '-')
feature_value = vm.features.get(feature, None)
if feature_value is None:
feature_value = guivm.features.get(
'gui-default-' + name.replace('_', '-'), None)
feature = 'gui-default-' + name.replace('_', '-')
feature_value = guivm.features.get(feature, None)
if feature_value is None:
continue

Expand All @@ -126,6 +181,15 @@
else:
assert False, kind

if not validator(value):
message = f"{vm.name}: Ignoring invalid feature:\n" \
f"{feature}={feature_value}"
log.error(message)
if not sys.stdout.isatty():
subprocess.run(['notify-send', '-a', 'Qubes GUI Daemon', \
'--icon', 'dialog-warning', message], check=False)
continue

options[name] = value
return options

Expand All @@ -141,7 +205,7 @@
'',
'global: {',
]
for name, kind in GUI_DAEMON_OPTIONS:
for name, kind, validator in GUI_DAEMON_OPTIONS:
if name in options:
value = options[name]
if kind == 'bool':
Expand All @@ -153,6 +217,14 @@
else:
assert False, kind

if not validator(value):
message = f"Ignoring invalid GUI property:\n{name}={value}"
log.error(message)
if not sys.stdout.isatty():
subprocess.run(['notify-send', '-a', 'Qubes GUI Daemon', \

Check warning on line 224 in qubesadmin/tools/qvm_start_daemon.py

View check run for this annotation

Codecov / codecov/patch

qubesadmin/tools/qvm_start_daemon.py#L221-L224

Added lines #L221 - L224 were not covered by tests
'--icon', 'dialog-warning', message], check=False)
continue

Check warning on line 226 in qubesadmin/tools/qvm_start_daemon.py

View check run for this annotation

Codecov / codecov/patch

qubesadmin/tools/qvm_start_daemon.py#L226

Added line #L226 was not covered by tests

lines.append(' {} = {};'.format(name, serialized))
lines.append('}')
lines.append('')
Expand Down
1 change: 1 addition & 0 deletions rpm_spec/qubes-core-admin-client.spec.in
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Requires: python%{python3_pkgversion}-qubesdb
Requires: python%{python3_pkgversion}-rpm
Requires: python%{python3_pkgversion}-tqdm
Requires: python%{python3_pkgversion}-pyxdg
Requires: python%{python3_pkgversion}-xlib
Conflicts: qubes-core-dom0 < 4.3.0
Conflicts: qubes-manager < 4.0.6

Expand Down