Skip to content

Commit 4d62b9e

Browse files
committed
Don't translate dom0 to @adminvm
The previous behavior of handling "dom0" and "@adminvm" in policies was inconsistent and let to the problem that you could not specify target=dom0 default_target=dom0 (or both @adminvm) since one was translated to @adminvm and the other was alsways expanded to dom0. Based on the discussion in QubesOS/qubes-issues#9870 we now stop to translate dom0 (or it's UUID) to @adminvm. The later works as keyword that is expanded to dom0. Note this also uncovered some wrong tests (like that "@type:AdminVM" should not match "dom0"). Closes QubesOS/qubes-issues#9870
1 parent 7b7e4fe commit 4d62b9e

File tree

4 files changed

+135
-49
lines changed

4 files changed

+135
-49
lines changed

qrexec/policy/parser.py

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,14 @@ def match_strings(info: SystemInfo, self: str, other: str) -> bool:
236236
return self == other
237237

238238

239+
def is_dom0(token) -> bool:
240+
return token in (
241+
"@adminvm",
242+
"dom0",
243+
"uuid:00000000-0000-0000-0000-000000000000",
244+
)
245+
246+
239247
class VMToken(str, metaclass=VMTokenMeta):
240248
"""A domain specification
241249
@@ -273,11 +281,6 @@ def __new__(
273281
"invalid empty {} token".format(cls.__name__.lower()),
274282
)
275283

276-
# first, adjust some aliases
277-
if token in {"dom0", "uuid:00000000-0000-0000-0000-000000000000"}:
278-
# TODO: log a warning in Qubes 4.3
279-
token = "@adminvm"
280-
281284
# if user specified just qube name or UUID, use it directly
282285
if not (token.startswith("@") or token == "*"):
283286
return super().__new__(cls, token)
@@ -351,9 +354,10 @@ def match(
351354
source: Optional["VMToken"] = None,
352355
) -> bool:
353356
"""Check if this token matches opposite token"""
354-
# pylint: disable=unused-argument,too-many-return-statements
355-
if self == "@adminvm":
356-
return other == "@adminvm"
357+
# pylint: disable=unused-argument
358+
if is_dom0(self):
359+
# see note in AdminVM.match
360+
return is_dom0(other)
357361
return match_strings(system_info["domains"], self, other)
358362

359363
def is_special_value(self) -> bool:
@@ -486,6 +490,20 @@ def expand(self, *, system_info: FullSystemInfo) -> Iterable["AdminVM"]:
486490
def verify(self, *, system_info: FullSystemInfo) -> "AdminVM":
487491
return self
488492

493+
# Currently there's only one valid AdminVM nameley "@adminvm". It refers to
494+
# (the local) dom0. We match dom0 and @adminvm commutatively for backward
495+
# compatibility. If you are going to change that, you need to check all
496+
# code that tests for dom0, uuid:0, @adminvm or AdminVM and adapt it, if
497+
# necessary.
498+
def match(
499+
self,
500+
other: Optional[str],
501+
*,
502+
system_info: FullSystemInfo,
503+
source: Optional[VMToken] = None,
504+
) -> bool:
505+
return is_dom0(other)
506+
489507

490508
class AnyVM(Source, Target):
491509
# pylint: disable=missing-docstring,unused-argument
@@ -498,13 +516,13 @@ def match(
498516
system_info: FullSystemInfo,
499517
source: Optional[VMToken] = None,
500518
) -> bool:
501-
return other != "@adminvm"
519+
return not is_dom0(other)
502520

503521
def expand(self, *, system_info: FullSystemInfo) -> Iterable[VMToken]:
504522
for name, domain in system_info["domains"].items():
505523
if name.startswith("uuid:"):
506524
continue
507-
if domain["type"] != "AdminVM":
525+
if not is_dom0(name):
508526
yield IntendedTarget(name)
509527
if domain["template_for_dispvms"]:
510528
yield DispVMTemplate("@dispvm:" + name)
@@ -767,16 +785,12 @@ async def execute(self) -> str:
767785
lines = []
768786

769787
# Adminvm/dom0 special case
770-
if target in (
771-
"@adminvm",
772-
"dom0",
773-
"uuid:00000000-0000-0000-0000-000000000000",
774-
):
788+
if is_dom0(target):
775789
lines.extend(
776790
[
777791
f"user={self.user or 'DEFAULT'}",
778792
"result=allow",
779-
"target=@adminvm",
793+
"target=dom0",
780794
f"autostart={self.autostart}",
781795
f"requested_target={request.target}",
782796
]
@@ -1079,7 +1093,7 @@ def allow_no_autostart(target: str, system_info: FullSystemInfo) -> bool:
10791093
"""
10801094
Should we allow this target when autostart is disabled
10811095
"""
1082-
if target == "@adminvm":
1096+
if is_dom0(target):
10831097
return True
10841098
if target.startswith("@dispvm"):
10851099
return False
@@ -1189,7 +1203,7 @@ def evaluate(self, request: Request) -> AllowResolution:
11891203
)
11901204
target = target_
11911205
del target_
1192-
# expand default AdminVM
1206+
# expand @adminvm but keep uuid:0
11931207
elif target == "@adminvm":
11941208
target = "dom0"
11951209

@@ -1262,7 +1276,10 @@ def evaluate(self, request: Request) -> AskResolution:
12621276

12631277
targets_for_ask: Iterable[str]
12641278
if self.target is not None:
1265-
targets_for_ask = [self.target]
1279+
if is_dom0(self.target):
1280+
targets_for_ask = ["dom0"]
1281+
else:
1282+
targets_for_ask = [self.target]
12661283
else:
12671284
targets_for_ask = list(
12681285
self.rule.policy.collect_targets_for_ask(request)
@@ -1293,8 +1310,8 @@ def evaluate(self, request: Request) -> AskResolution:
12931310
default_target = default_target.get_dispvm_template(
12941311
request.source, system_info=request.system_info
12951312
)
1296-
# expand default AdminVM
1297-
elif isinstance(default_target, AdminVM):
1313+
# expand @adminvm and uuid:0 to dom0
1314+
elif is_dom0(default_target):
12981315
default_target = "dom0"
12991316

13001317
return request.ask_resolution_type(

qrexec/tests/policy_graph.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ def test_simple_redirect():
277277
)
278278
content = output.read().decode()
279279
expected = """digraph g {
280-
"work" -> "@adminvm" [label="test.Service" color=red];
280+
"work" -> "dom0" [label="test.Service" color=red];
281281
}
282282
"""
283283
assert content == expected

qrexec/tests/policy_parser.py

Lines changed: 83 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -301,15 +301,15 @@ def test_021_Target_expand(self):
301301
)
302302
self.assertEqual(
303303
list(parser.Target("dom0").expand(system_info=self.system_info)),
304-
["@adminvm"],
304+
["dom0"],
305305
)
306306
self.assertEqual(
307307
list(
308308
parser.Target(
309309
"uuid:00000000-0000-0000-0000-000000000000"
310310
).expand(system_info=self.system_info)
311311
),
312-
["@adminvm"],
312+
["dom0"],
313313
)
314314
self.assertEqual(
315315
list(
@@ -354,12 +354,12 @@ def test_021_Target_expand(self):
354354
set(parser.Target("*").expand(system_info=self.system_info))
355355
),
356356
[
357-
"@adminvm",
358357
"@dispvm",
359358
"@dispvm:default-dvm",
360359
"@dispvm:test-vm3",
361360
"@dispvm:test-vm4",
362361
"default-dvm",
362+
"dom0",
363363
"test-invalid-dvm",
364364
"test-no-dvm",
365365
"test-relayvm1",
@@ -564,7 +564,14 @@ def test_100_match_single(self):
564564
("@adminvm", "@adminvm", True),
565565
("@adminvm", "dom0", True),
566566
("dom0", "@adminvm", True),
567+
("@adminvm", "uuid:00000000-0000-0000-0000-000000000000", True),
567568
("dom0", "dom0", True),
569+
("test-vm3", "dom0", False),
570+
("dom0", "test-vm3", False),
571+
("test-vm3", "@adminvm", False),
572+
("@adminvm", "test-vm3", False),
573+
("test-vm3", "uuid:00000000-0000-0000-0000-000000000000", False),
574+
("uuid:00000000-0000-0000-0000-000000000000", "test-vm3", False),
568575
("@dispvm:default-dvm", "@dispvm:default-dvm", True),
569576
("@anyvm", "@dispvm", True),
570577
("*", "test-vm1", True),
@@ -583,8 +590,8 @@ def test_100_match_single(self):
583590
("@anyvm", "@adminvm", False),
584591
("@tag:dom0-tag", "@adminvm", False),
585592
("@type:AdminVM", "@adminvm", False),
586-
("@tag:dom0-tag", "dom0", False),
587-
("@type:AdminVM", "dom0", False),
593+
("@tag:dom0-tag", "dom0", True),
594+
("@type:AdminVM", "dom0", True),
588595
("@tag:tag1", "dom0", False),
589596
("@dispvm", "test-vm1", False),
590597
("@dispvm", "default-dvm", False),
@@ -1869,7 +1876,7 @@ def test_060_eval_to_dom0(self):
18691876
self.assertIsInstance(resolution, parser.AllowResolution)
18701877
self.assertEqual(resolution.rule, policy.rules[0])
18711878
self.assertEqual(resolution.target, "dom0")
1872-
self.assertEqual(resolution.request.target, "@adminvm")
1879+
self.assertEqual(resolution.request.target, "dom0")
18731880

18741881
def test_061_eval_to_dom0_keyword(self):
18751882
policy = parser.StringPolicy(
@@ -1883,6 +1890,45 @@ def test_061_eval_to_dom0_keyword(self):
18831890
self.assertEqual(resolution.target, "dom0")
18841891
self.assertEqual(resolution.request.target, "@adminvm")
18851892

1893+
def test_062_eval_to_dom0_literal(self):
1894+
policy = parser.StringPolicy(
1895+
policy="""\
1896+
* * test-vm3 dom0 allow"""
1897+
)
1898+
resolution = policy.evaluate(self.gen_req("test-vm3", "dom0"))
1899+
1900+
self.assertIsInstance(resolution, parser.AllowResolution)
1901+
self.assertEqual(resolution.rule, policy.rules[0])
1902+
self.assertEqual(resolution.target, "dom0")
1903+
self.assertEqual(resolution.request.target, "dom0")
1904+
1905+
def test_063_eval_to_dom0_literal_policy(self):
1906+
policy = parser.StringPolicy(
1907+
policy="""\
1908+
* * test-vm3 dom0 allow"""
1909+
)
1910+
resolution = policy.evaluate(self.gen_req("test-vm3", "@adminvm"))
1911+
1912+
self.assertIsInstance(resolution, parser.AllowResolution)
1913+
self.assertEqual(resolution.rule, policy.rules[0])
1914+
self.assertEqual(resolution.target, "dom0")
1915+
self.assertEqual(resolution.request.target, "@adminvm")
1916+
1917+
def test_064_eval_to_dom0_deny(self):
1918+
names = (
1919+
"dom0",
1920+
"@adminvm",
1921+
"uuid:00000000-0000-0000-0000-000000000000",
1922+
)
1923+
for target in names:
1924+
policy = parser.StringPolicy(policy=f"* * test-vm3 test-vm2 allow")
1925+
with self.assertRaises(exc.AccessDenied):
1926+
policy.evaluate(self.gen_req("test-vm3", target))
1927+
1928+
policy = parser.StringPolicy(policy=f"* * test-vm3 {target} allow")
1929+
with self.assertRaises(exc.AccessDenied):
1930+
policy.evaluate(self.gen_req("test-vm3", "test-vm2"))
1931+
18861932
def test_070_eval_to_dom0_ask_default_target(self):
18871933
policy = parser.StringPolicy(
18881934
policy="""\
@@ -1893,7 +1939,7 @@ def test_070_eval_to_dom0_ask_default_target(self):
18931939
self.assertIsInstance(resolution, parser.AskResolution)
18941940
self.assertEqual(resolution.rule, policy.rules[0])
18951941
self.assertEqual(resolution.default_target, "dom0")
1896-
self.assertEqual(resolution.request.target, "@adminvm")
1942+
self.assertEqual(resolution.request.target, "dom0")
18971943
self.assertEqual(resolution.targets_for_ask, ["dom0"])
18981944

18991945
def test_071_eval_to_dom0_ask_default_target(self):
@@ -1906,7 +1952,7 @@ def test_071_eval_to_dom0_ask_default_target(self):
19061952
self.assertIsInstance(resolution, parser.AskResolution)
19071953
self.assertEqual(resolution.rule, policy.rules[0])
19081954
self.assertEqual(resolution.default_target, "dom0")
1909-
self.assertEqual(resolution.request.target, "@adminvm")
1955+
self.assertEqual(resolution.request.target, "dom0")
19101956
self.assertEqual(resolution.targets_for_ask, ["dom0"])
19111957

19121958
def test_072_eval_to_dom0_ask_default_target(self):
@@ -1919,7 +1965,7 @@ def test_072_eval_to_dom0_ask_default_target(self):
19191965
self.assertIsInstance(resolution, parser.AskResolution)
19201966
self.assertEqual(resolution.rule, policy.rules[0])
19211967
self.assertEqual(resolution.default_target, "dom0")
1922-
self.assertEqual(resolution.request.target, "@adminvm")
1968+
self.assertEqual(resolution.request.target, "dom0")
19231969
self.assertEqual(resolution.targets_for_ask, ["dom0"])
19241970

19251971
def test_073_eval_to_dom0_ask_default_target(self):
@@ -1932,9 +1978,30 @@ def test_073_eval_to_dom0_ask_default_target(self):
19321978
self.assertIsInstance(resolution, parser.AskResolution)
19331979
self.assertEqual(resolution.rule, policy.rules[0])
19341980
self.assertEqual(resolution.default_target, "dom0")
1935-
self.assertEqual(resolution.request.target, "@adminvm")
1981+
self.assertEqual(resolution.request.target, "dom0")
19361982
self.assertEqual(resolution.targets_for_ask, ["dom0"])
19371983

1984+
def test_074_eval_to_default_dom0(self):
1985+
names = (
1986+
"dom0",
1987+
"@adminvm",
1988+
"uuid:00000000-0000-0000-0000-000000000000",
1989+
)
1990+
for target in names:
1991+
for default_target in names:
1992+
policy = parser.StringPolicy(
1993+
policy=f"* * test-vm3 @default ask target={target} default_target={default_target}"
1994+
)
1995+
resolution = policy.evaluate(
1996+
self.gen_req("test-vm3", "@default")
1997+
)
1998+
1999+
self.assertIsInstance(resolution, parser.AskResolution)
2000+
self.assertEqual(resolution.rule, policy.rules[0])
2001+
self.assertEqual(resolution.default_target, "dom0")
2002+
self.assertEqual(resolution.request.target, "@default")
2003+
self.assertEqual(resolution.targets_for_ask, ["dom0"])
2004+
19382005
def test_080_eval_override_target(self):
19392006
policy = parser.StringPolicy(
19402007
policy="""\
@@ -2034,7 +2101,9 @@ def test_088_eval_override_target_uuid_dom0(self):
20342101

20352102
self.assertIsInstance(resolution, parser.AllowResolution)
20362103
self.assertEqual(resolution.rule, policy.rules[0])
2037-
self.assertEqual(resolution.target, "dom0")
2104+
self.assertEqual(
2105+
resolution.target, "uuid:00000000-0000-0000-0000-000000000000"
2106+
)
20382107
self.assertEqual(resolution.request.target, "test-vm1")
20392108

20402109
def test_089_eval_override_target_dispvm_uuid(self):
@@ -2231,9 +2300,9 @@ async def _test_121_execute_dom0(self):
22312300
"""\
22322301
user=DEFAULT
22332302
result=allow
2234-
target=@adminvm
2303+
target=dom0
22352304
autostart=True
2236-
requested_target=@adminvm\
2305+
requested_target=dom0\
22372306
""",
22382307
)
22392308

@@ -2258,7 +2327,7 @@ async def _test_121_execute_dom0_keyword(self):
22582327
"""\
22592328
user=DEFAULT
22602329
result=allow
2261-
target=@adminvm
2330+
target=dom0
22622331
autostart=True
22632332
requested_target=@adminvm\
22642333
""",

0 commit comments

Comments
 (0)