Skip to content

Commit

Permalink
Allow qube (template) sending basic OS metadata
Browse files Browse the repository at this point in the history
Save distribution, version and EOL date in features.

Fixes QubesOS/qubes-issues#8725
  • Loading branch information
marmarek committed Jan 31, 2024
1 parent 62f3c3d commit 8fbb02b
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 0 deletions.
35 changes: 35 additions & 0 deletions qubes/ext/core_features.py
Expand Up @@ -17,7 +17,9 @@
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, see <https://www.gnu.org/licenses/>.
import datetime
import re
import string

import qubes.ext

Expand All @@ -34,6 +36,39 @@ async def qubes_features_request(self, vm, event, untrusted_features):
'Ignoring qubes.NotifyTools for template-based VM')
return

if "os-distribution" in untrusted_features:
# entry point already validates values for safe characters
vm.features["os-distribution"] = \
untrusted_features["os-distribution"]
if "os-version" in untrusted_features:
# no letters in versions please
safe_set = string.digits + ".-"
untrusted_version = untrusted_features["os-version"]
if all(c in safe_set for c in untrusted_version) \
and untrusted_version[0].isdigit():
vm.features["os-version"] = untrusted_version
else:
# safe to log the value as passed preliminary filtering already
vm.log.warning(
"Invalid 'os-version' value '%s', must start "
"with a digit and only digits and _ or . are allowed",
untrusted_version)
if "os-eol" in untrusted_features:
untrusted_eol = untrusted_features["os-eol"]
valid = False
if re.match(r"\A\d{4}-\d{2}-\d{2}\Z", untrusted_eol):
try:
datetime.date.fromisoformat(untrusted_eol)
valid = True
except ValueError:
pass
if valid:
vm.features["os-eol"] = untrusted_eol
else:
vm.log.warning(
"Invalid 'os-eol' value '%s', expected YYYY-MM-DD",
untrusted_eol)

requested_features = {}
for feature in (
'qrexec', 'gui', 'gui-emulated', 'qubes-firewall', 'vmexec'):
Expand Down
74 changes: 74 additions & 0 deletions qubes/tests/ext.py
Expand Up @@ -19,6 +19,8 @@
# License along with this library; if not, see <https://www.gnu.org/licenses/>.

import os
import unittest.mock

import qubes.ext.core_features
import qubes.ext.services
import qubes.ext.windows
Expand Down Expand Up @@ -239,6 +241,78 @@ def test_21_version_invalid(self):
('features.get', ('qrexec', False), {}),
])

def test_30_distro_meta(self):
self.features['qrexec'] = True
del self.vm.template
self.loop.run_until_complete(
self.ext.qubes_features_request(self.vm, 'features-request',
untrusted_features={
'os': 'Linux',
'os-distribution': 'debian',
'os-version': '12',
'os-eol': '2026-06-10',
}))
self.assertListEqual(self.vm.mock_calls, [
('features.__setitem__', ('os-distribution', 'debian'), {}),
('features.__setitem__', ('os-version', '12'), {}),
('features.__setitem__', ('os-eol', '2026-06-10'), {}),
('features.get', ('qrexec', False), {}),
])

def test_031_distro_meta_ubuntu(self):
self.features['qrexec'] = True
del self.vm.template
self.loop.run_until_complete(
self.ext.qubes_features_request(self.vm, 'features-request',
untrusted_features={
'os': 'Linux',
'os-distribution': 'ubuntu',
'os-version': '22.04',
'os-eol': '2027-06-01',
}))
self.assertListEqual(self.vm.mock_calls, [
('features.__setitem__', ('os-distribution', 'ubuntu'), {}),
('features.__setitem__', ('os-version', '22.04'), {}),
('features.__setitem__', ('os-eol', '2027-06-01'), {}),
('features.get', ('qrexec', False), {}),
])

def test_032_distro_meta_invalid(self):
self.features['qrexec'] = True
del self.vm.template
self.loop.run_until_complete(
self.ext.qubes_features_request(self.vm, 'features-request',
untrusted_features={
'os': 'Linux',
'os-distribution': 'ubuntu',
'os-version': '123aaa',
'os-eol': '20270601',
}))
self.assertListEqual(self.vm.mock_calls, [
('features.__setitem__', ('os-distribution', 'ubuntu'), {}),
('log.warning', unittest.mock.ANY, {}),
('log.warning', unittest.mock.ANY, {}),
('features.get', ('qrexec', False), {}),
])

def test_033_distro_meta_invalid2(self):
self.features['qrexec'] = True
del self.vm.template
self.loop.run_until_complete(
self.ext.qubes_features_request(self.vm, 'features-request',
untrusted_features={
'os': 'Linux',
'os-distribution': 'ubuntu',
'os-version': 'a123',
'os-eol': '2027-06-40',
}))
self.assertListEqual(self.vm.mock_calls, [
('features.__setitem__', ('os-distribution', 'ubuntu'), {}),
('log.warning', unittest.mock.ANY, {}),
('log.warning', unittest.mock.ANY, {}),
('features.get', ('qrexec', False), {}),
])

def test_100_servicevm_feature(self):
self.vm.provides_network = True
self.ext.set_servicevm_feature(self.vm)
Expand Down

0 comments on commit 8fbb02b

Please sign in to comment.