Skip to content

Commit 06cf964

Browse files
committed
Allow VM boot modes to change a VM's default user
1 parent e7d49e3 commit 06cf964

File tree

4 files changed

+131
-20
lines changed

4 files changed

+131
-20
lines changed

qubes/ext/core_features.py

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,11 @@ async def qubes_features_request(self, vm, event, untrusted_features):
108108
# handle boot mode advertisement
109109
old_bootmode_info = {}
110110
for feature_key, feature_val in vm.features.items():
111-
if feature_key.startswith(
112-
"boot-mode.kernelopts."
113-
) or feature_key.startswith("boot-mode.name."):
111+
if (
112+
feature_key.startswith("boot-mode.kernelopts.")
113+
or feature_key.startswith("boot-mode.name.")
114+
or feature_key.startswith("boot-mode.default-user.")
115+
):
114116
old_bootmode_info[feature_key] = feature_val
115117
new_bootmode_info = {}
116118
new_bootmode_names = []
@@ -136,19 +138,22 @@ async def qubes_features_request(self, vm, event, untrusted_features):
136138
untrusted_feature_key,
137139
untrusted_feature_value,
138140
) in untrusted_features.items():
139-
if untrusted_feature_key.startswith("boot-mode.name."):
140-
bootmode_key_parts = untrusted_feature_key.split(".")
141-
if len(bootmode_key_parts) != 3:
142-
# Boot mode key contains unexpected data, reject it
143-
continue
144-
bootmode_name = bootmode_key_parts[2]
145-
if bootmode_name == "":
146-
continue
147-
if (
148-
f"boot-mode.kernelopts.{bootmode_name}"
149-
not in new_bootmode_info
150-
) and bootmode_name != "default":
151-
continue
141+
if not untrusted_feature_key.startswith("boot-mode."):
142+
continue
143+
bootmode_key_parts = untrusted_feature_key.split(".")
144+
if len(bootmode_key_parts) != 3:
145+
# Boot mode key contains unexpected data, reject it
146+
continue
147+
bootmode_name = bootmode_key_parts[2]
148+
if bootmode_name == "":
149+
continue
150+
if (
151+
f"boot-mode.kernelopts.{bootmode_name}" not in new_bootmode_info
152+
) and bootmode_name != "default":
153+
continue
154+
if untrusted_feature_key.startswith(
155+
"boot-mode.name."
156+
) or untrusted_feature_key.startswith("boot-mode.default-user."):
152157
bootmode_feature = untrusted_feature_key
153158
bootmode_value = untrusted_feature_value
154159
new_bootmode_info[bootmode_feature] = bootmode_value

qubes/tests/ext.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1587,6 +1587,75 @@ def test_055_bootmode_preserve_oldvals(self):
15871587
],
15881588
)
15891589

1590+
def test_056_bootmode_default_user(self):
1591+
del self.vm.template
1592+
self.loop.run_until_complete(
1593+
self.ext.qubes_features_request(
1594+
self.vm,
1595+
"features-request",
1596+
untrusted_features={
1597+
"boot-mode.name.vmreq": "VMReq",
1598+
"boot-mode.kernelopts.vmreq": "vmreq1 vmreq2",
1599+
"boot-mode.default-user.vmreq": "altuser",
1600+
},
1601+
)
1602+
)
1603+
self.assertListEqual(
1604+
self.vm.mock_calls,
1605+
[
1606+
("features.items", (), {}),
1607+
(
1608+
"features.__setitem__",
1609+
("boot-mode.kernelopts.vmreq", "vmreq1 vmreq2"),
1610+
{},
1611+
),
1612+
(
1613+
"features.__setitem__",
1614+
("boot-mode.name.vmreq", "VMReq"),
1615+
{},
1616+
),
1617+
(
1618+
"features.__setitem__",
1619+
("boot-mode.default-user.vmreq", "altuser"),
1620+
{},
1621+
),
1622+
("features.get", ("qrexec", False), {}),
1623+
("features.get", ("qrexec", False), {}),
1624+
],
1625+
)
1626+
1627+
def test_056_bootmode_default_user_mismatch(self):
1628+
del self.vm.template
1629+
self.loop.run_until_complete(
1630+
self.ext.qubes_features_request(
1631+
self.vm,
1632+
"features-request",
1633+
untrusted_features={
1634+
"boot-mode.name.vmreq": "VMReq",
1635+
"boot-mode.kernelopts.vmreq": "vmreq1 vmreq2",
1636+
"boot-mode.default-user.nope": "altuser",
1637+
},
1638+
)
1639+
)
1640+
self.assertListEqual(
1641+
self.vm.mock_calls,
1642+
[
1643+
("features.items", (), {}),
1644+
(
1645+
"features.__setitem__",
1646+
("boot-mode.kernelopts.vmreq", "vmreq1 vmreq2"),
1647+
{},
1648+
),
1649+
(
1650+
"features.__setitem__",
1651+
("boot-mode.name.vmreq", "VMReq"),
1652+
{},
1653+
),
1654+
("features.get", ("qrexec", False), {}),
1655+
("features.get", ("qrexec", False), {}),
1656+
],
1657+
)
1658+
15901659
def test_100_servicevm_feature(self):
15911660
self.vm.provides_network = True
15921661
self.ext.set_servicevm_feature(self.vm)

qubes/tests/vm/qubesvm.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3232,3 +3232,25 @@ def test_811_default_bootmode(self):
32323232
self.assertEqual(vm.bootmode, "testmode3")
32333233
del vm.template.features["boot-mode.kernelopts.testmode3"]
32343234
self.assertEqual(vm.bootmode, "default")
3235+
3236+
def test_812_bootmode_default_user(self):
3237+
vm = self.get_vm(cls=qubes.vm.appvm.AppVM)
3238+
vm.template = self.get_vm(cls=qubes.vm.templatevm.TemplateVM)
3239+
vm.bootmode = qubes.property.DEFAULT
3240+
self.assertEqual(vm.get_default_user(), "user")
3241+
vm.features["boot-mode.kernelopts.testmode1"] = "abc def"
3242+
vm.features["boot-mode.default-user.testmode1"] = "altuser"
3243+
vm.features["boot-mode.active"] = "testmode1"
3244+
self.assertEqual(vm.get_default_user(), "altuser")
3245+
del vm.features["boot-mode.default-user.testmode1"]
3246+
self.assertEqual(vm.get_default_user(), "user")
3247+
vm.features["boot-mode.default-user.testmode1"] = "altuser"
3248+
del vm.features["boot-mode.kernelopts.testmode1"]
3249+
self.assertEqual(vm.get_default_user(), "user")
3250+
del vm.features["boot-mode.default-user.testmode1"]
3251+
vm.template.features["boot-mode.kernelopts.testmode2"] = "ghi jkl"
3252+
vm.template.features["boot-mode.default-user.testmode2"] = "altuser2"
3253+
vm.features["boot-mode.active"] = "testmode2"
3254+
self.assertEqual(vm.get_default_user(), "altuser2")
3255+
del vm.features["boot-mode.active"]
3256+
self.assertEqual(vm.get_default_user(), "user")

qubes/vm/qubesvm.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1807,7 +1807,7 @@ async def run_service(
18071807
name = self.name + "-dm" if stubdom else self.name
18081808

18091809
if user is None:
1810-
user = self.default_user
1810+
user = self.get_default_user()
18111811

18121812
if self.is_paused():
18131813
# XXX what about autostart?
@@ -1876,7 +1876,7 @@ async def run(self, command, user=None, **kwargs):
18761876
""" # pylint: disable=redefined-builtin
18771877

18781878
if user is None:
1879-
user = self.default_user
1879+
user = self.get_default_user()
18801880

18811881
return await asyncio.create_subprocess_exec(
18821882
qubes.config.system_path["qrexec_client_path"],
@@ -2106,7 +2106,7 @@ async def start_qrexec_daemon(self, stubdom=False):
21062106
"--",
21072107
str(self.xid),
21082108
self.name,
2109-
self.default_user,
2109+
self.get_default_user(),
21102110
]
21112111

21122112
if not self.debug:
@@ -2274,6 +2274,21 @@ def libvirt_undefine(self):
22742274

22752275
# state of the machine
22762276

2277+
def get_default_user(self):
2278+
"""Return a user account name suitable for use as a default user.
2279+
2280+
Usually returns the value of the default_user property, but also
2281+
allows boot modes to override the default user.
2282+
"""
2283+
if self.bootmode == "default":
2284+
return self.default_user
2285+
bootmode_default_user = self.features.check_with_template(
2286+
f"boot-mode.default-user.{self.bootmode}", None
2287+
)
2288+
if bootmode_default_user is None:
2289+
return self.default_user
2290+
return bootmode_default_user
2291+
22772292
def get_power_state(self):
22782293
"""Return power state description string.
22792294
@@ -2726,7 +2741,7 @@ def create_qdb_entries(self):
27262741

27272742
self.untrusted_qdb.write("/name", self.name)
27282743
self.untrusted_qdb.write("/type", self.__class__.__name__)
2729-
self.untrusted_qdb.write("/default-user", self.default_user)
2744+
self.untrusted_qdb.write("/default-user", self.get_default_user())
27302745
self.untrusted_qdb.write("/qubes-vm-updateable", str(self.updateable))
27312746
self.untrusted_qdb.write(
27322747
"/qubes-vm-persistence", "full" if self.updateable else "rw-only"

0 commit comments

Comments
 (0)