Skip to content

Commit 9f2e7c3

Browse files
committed
Merge remote-tracking branch 'origin/pr/724'
* origin/pr/724: Automatically write /hash-app-dispvm/ entries for app-dispvm services
2 parents ab3f759 + 2ee3cea commit 9f2e7c3

2 files changed

Lines changed: 158 additions & 8 deletions

File tree

qubes/ext/services.py

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
# License along with this library; if not, see <https://www.gnu.org/licenses/>.
2020

2121
"""Extension responsible for qvm-service framework"""
22-
22+
import base64
23+
import hashlib
2324
import os
2425
import qubes.ext
2526
import qubes.config
@@ -63,6 +64,21 @@ def remove_dom0_service(vm, service):
6364
)
6465
)
6566

67+
def update_app_dispvm_entry(self, vm, app_name, value=None):
68+
"""Update /hash-app-dispvm/ entry for service.app-dispvm. feature"""
69+
70+
if value is None:
71+
value = vm.features.get(f"service.app-dispvm.{app_name}", False)
72+
app_hash = (
73+
base64.urlsafe_b64encode(hashlib.sha256(app_name.encode()).digest())
74+
.decode()
75+
.rstrip("=")
76+
)
77+
if value:
78+
vm.untrusted_qdb.write(f"/hash-app-dispvm/{app_hash}", app_name)
79+
else:
80+
vm.untrusted_qdb.rm(f"/hash-app-dispvm/{app_hash}")
81+
6682
@qubes.ext.handler("domain-qdb-create")
6783
def on_domain_qdb_create(self, vm, event):
6884
"""Actually export features"""
@@ -74,8 +90,15 @@ def on_domain_qdb_create(self, vm, event):
7490
if not service:
7591
vm.log.warning("Empty service name, ignoring: " + service)
7692
continue
93+
if service.startswith("app-dispvm."):
94+
self.update_app_dispvm_entry(vm, service[len("app-dispvm.") :])
7795
if len(service) > 48:
78-
vm.log.warning("Too long service name, ignoring: " + service)
96+
# don't log warning for long app-dispvm. names - those are
97+
# handled specially above
98+
if not service.startswith("app-dispvm."):
99+
vm.log.warning(
100+
"Too long service name, ignoring: " + service
101+
)
79102
continue
80103
# forcefully convert to '0' or '1'
81104
vm.untrusted_qdb.write(
@@ -109,7 +132,8 @@ def on_domain_feature_pre_set(
109132
'Service name cannot be "." or ".."'
110133
)
111134

112-
if len(service) > 48:
135+
# app-dispvm. have hash-based handling for long names
136+
if len(service) > 48 and not service.startswith("app-dispvm."):
113137
raise qubes.exc.QubesValueError(
114138
"Service name must not exceed 48 bytes"
115139
)
@@ -137,10 +161,15 @@ def on_domain_feature_set(self, vm, event, feature, value, oldvalue=None):
137161
if not feature.startswith("service."):
138162
return
139163
service = feature[len("service.") :]
140-
# forcefully convert to '0' or '1'
141-
vm.untrusted_qdb.write(
142-
"/qubes-service/{}".format(service), str(int(bool(value)))
143-
)
164+
if service.startswith("app-dispvm."):
165+
self.update_app_dispvm_entry(
166+
vm, service[len("app-dispvm.") :], value
167+
)
168+
if len(service) <= 48:
169+
# forcefully convert to '0' or '1'
170+
vm.untrusted_qdb.write(
171+
"/qubes-service/{}".format(service), str(int(bool(value)))
172+
)
144173

145174
if vm.name == "dom0":
146175
if str(int(bool(value))) == "1":
@@ -160,7 +189,12 @@ def on_domain_feature_delete(self, vm, event, feature):
160189
# this one is excluded from user control
161190
if service == "meminfo-writer":
162191
return
163-
vm.untrusted_qdb.rm("/qubes-service/{}".format(service))
192+
if service.startswith("app-dispvm."):
193+
self.update_app_dispvm_entry(
194+
vm, service[len("app-dispvm.") :], False
195+
)
196+
if len(service) <= 48:
197+
vm.untrusted_qdb.rm("/qubes-service/{}".format(service))
164198

165199
if vm.name == "dom0":
166200
self.remove_dom0_service(vm, service)

qubes/tests/ext.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2022,6 +2022,122 @@ def test_014_feature_set_empty_value_dom0(self):
20222022

20232023
self.assertEqual(os.path.exists(service_path), False)
20242024

2025+
def test_020_app_dispvm_init(self):
2026+
self.features["service.app-dispvm.short-name"] = "1"
2027+
short_name_hash = "uRc_3Q51DYlZXclkZX_BN2iYvUkKh8qXRxkqvvGAwEI"
2028+
long_name = "long-nameeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
2029+
long_name_hash = "Szm7u1YaqyWBAi2tG6eCWfXYIE7YHxQU7hpcCYZ1qbc"
2030+
self.features[(f"service.app-dispvm.{long_name}")] = "1"
2031+
2032+
self.ext.on_domain_qdb_create(self.vm, "domain-qdb-create")
2033+
self.assertEqual(
2034+
sorted(self.vm.untrusted_qdb.mock_calls),
2035+
[
2036+
(
2037+
"write",
2038+
(f"/hash-app-dispvm/{long_name_hash}", long_name),
2039+
{},
2040+
),
2041+
(
2042+
"write",
2043+
(f"/hash-app-dispvm/{short_name_hash}", "short-name"),
2044+
{},
2045+
),
2046+
("write", ("/qubes-service/app-dispvm.short-name", "1"), {}),
2047+
("write", ("/qubes-service/meminfo-writer", "1"), {}),
2048+
],
2049+
)
2050+
2051+
def test_021_app_dispvm_add(self):
2052+
short_name_hash = "uRc_3Q51DYlZXclkZX_BN2iYvUkKh8qXRxkqvvGAwEI"
2053+
long_name = "long-nameeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
2054+
long_name_hash = "Szm7u1YaqyWBAi2tG6eCWfXYIE7YHxQU7hpcCYZ1qbc"
2055+
self.ext.on_domain_feature_pre_set(
2056+
self.vm,
2057+
"feature-set:service.app-dispvm.short-name",
2058+
"service.app-dispvm.short-name",
2059+
"1",
2060+
)
2061+
self.ext.on_domain_feature_set(
2062+
self.vm,
2063+
"feature-set:service.app-dispvm.short-name",
2064+
"service.app-dispvm.short-name",
2065+
"1",
2066+
)
2067+
self.assertEqual(
2068+
sorted(self.vm.untrusted_qdb.mock_calls),
2069+
[
2070+
(
2071+
"write",
2072+
(f"/hash-app-dispvm/{short_name_hash}", "short-name"),
2073+
{},
2074+
),
2075+
("write", ("/qubes-service/app-dispvm.short-name", "1"), {}),
2076+
],
2077+
)
2078+
2079+
self.ext.on_domain_feature_pre_set(
2080+
self.vm,
2081+
f"feature-set:service.app-dispvm.{long_name}",
2082+
f"service.app-dispvm.{long_name}",
2083+
"1",
2084+
)
2085+
self.ext.on_domain_feature_set(
2086+
self.vm,
2087+
f"feature-set:service.app-dispvm.{long_name}",
2088+
f"service.app-dispvm.{long_name}",
2089+
"1",
2090+
)
2091+
2092+
self.assertEqual(
2093+
sorted(self.vm.untrusted_qdb.mock_calls),
2094+
[
2095+
(
2096+
"write",
2097+
(f"/hash-app-dispvm/{long_name_hash}", long_name),
2098+
{},
2099+
),
2100+
(
2101+
"write",
2102+
(f"/hash-app-dispvm/{short_name_hash}", "short-name"),
2103+
{},
2104+
),
2105+
("write", ("/qubes-service/app-dispvm.short-name", "1"), {}),
2106+
],
2107+
)
2108+
2109+
def test_022_app_dispvm_del(self):
2110+
short_name_hash = "uRc_3Q51DYlZXclkZX_BN2iYvUkKh8qXRxkqvvGAwEI"
2111+
long_name = "long-nameeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
2112+
long_name_hash = "Szm7u1YaqyWBAi2tG6eCWfXYIE7YHxQU7hpcCYZ1qbc"
2113+
self.ext.on_domain_feature_delete(
2114+
self.vm,
2115+
"feature-set:service.app-dispvm.short-name",
2116+
"service.app-dispvm.short-name",
2117+
)
2118+
self.assertEqual(
2119+
sorted(self.vm.untrusted_qdb.mock_calls),
2120+
[
2121+
("rm", (f"/hash-app-dispvm/{short_name_hash}",), {}),
2122+
("rm", ("/qubes-service/app-dispvm.short-name",), {}),
2123+
],
2124+
)
2125+
2126+
self.ext.on_domain_feature_delete(
2127+
self.vm,
2128+
f"feature-set:service.app-dispvm.{long_name}",
2129+
f"service.app-dispvm.{long_name}",
2130+
)
2131+
2132+
self.assertEqual(
2133+
sorted(self.vm.untrusted_qdb.mock_calls),
2134+
[
2135+
("rm", (f"/hash-app-dispvm/{long_name_hash}",), {}),
2136+
("rm", (f"/hash-app-dispvm/{short_name_hash}",), {}),
2137+
("rm", ("/qubes-service/app-dispvm.short-name",), {}),
2138+
],
2139+
)
2140+
20252141

20262142
class TC_20_VmConfig(qubes.tests.QubesTestCase):
20272143
def setUp(self):

0 commit comments

Comments
 (0)