Skip to content
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
2 changes: 2 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ include:
project: QubesOS/qubes-continuous-integration
- file: /r4.3/gitlab-host.yml
project: QubesOS/qubes-continuous-integration
- file: /r4.3/gitlab-host-openqa.yml
project: QubesOS/qubes-continuous-integration

lint:
extends: .lint
Expand Down
10 changes: 5 additions & 5 deletions .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -58,19 +58,19 @@ const-rgx=(([A-Za-z_][A-Za-z0-9_]*)|(__.*__))$
class-rgx=([A-Z_][a-zA-Z0-9]+|TC_\d\d_[a-zA-Z0-9_]+)$

# Regular expression which should only match correct function names
function-rgx=[a-z_][a-z0-9_]{2,30}$
function-rgx=(test_[0-9]{3}_[a-z0-9_]{2,50}|[a-z_][a-z0-9_]{2,40})$

# Regular expression which should only match correct method names
method-rgx=[a-z_][a-z0-9_]{2,30}$
method-rgx=(test_[0-9]{3}_[a-z0-9_]{2,50}|[a-z_][a-z0-9_]{2,40})$

# Regular expression which should only match correct instance attribute names
attr-rgx=[a-z_][a-z0-9_]{2,30}$
attr-rgx=[a-z_][a-z0-9_]{2,40}$

# Regular expression which should only match correct argument names
argument-rgx=[a-z_][a-z0-9_]{2,30}$
argument-rgx=[a-z_][a-z0-9_]{2,40}$

# Regular expression which should only match correct variable names
variable-rgx=[a-z_][a-z0-9_]{2,30}$
variable-rgx=[a-z_][a-z0-9_]{2,40}$

# Regular expression which should only match correct list comprehension /
# generator expression variable names
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ all:

install:
ifeq ($(OS),Linux)
$(MAKE) install -C linux/autostart
$(MAKE) install -C linux/systemd
$(MAKE) install -C linux/aux-tools
$(MAKE) install -C linux/system-config
Expand Down
6 changes: 6 additions & 0 deletions linux/autostart/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
all:
true

install:
mkdir -p $(DESTDIR)/etc/xdg/autostart
cp qubes-preload-dispvm.desktop $(DESTDIR)/etc/xdg/autostart
9 changes: 9 additions & 0 deletions linux/autostart/qubes-preload-dispvm.desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[Desktop Entry]
Icon=qubes
Name=Qubes Preload Disposables
Comment=Workaround for session monitoring with qubes.WaitForSession
Categories=System;Monitor;
Exec=systemctl start qubes-preload-dispvm.service
Terminal=false
NoDisplay=true
Type=Application
5 changes: 3 additions & 2 deletions linux/aux-tools/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ all:

install:
mkdir -p $(DESTDIR)/usr/lib/qubes
cp cleanup-dispvms $(DESTDIR)/usr/lib/qubes
cp startup-misc.sh $(DESTDIR)/usr/lib/qubes
cp preload-dispvm $(DESTDIR)/usr/lib/qubes/
cp cleanup-dispvms $(DESTDIR)/usr/lib/qubes/
cp startup-misc.sh $(DESTDIR)/usr/lib/qubes/
cp fix-dir-perms.sh $(DESTDIR)/usr/lib/qubes/
35 changes: 35 additions & 0 deletions linux/aux-tools/preload-dispvm
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/env python3

import asyncio
import concurrent.futures
import qubesadmin


def get_max(qube):
return int(qube.features.get("preload-dispvm-max", 0) or 0)


async def main():
domains = qubesadmin.Qubes().domains
appvms = [
qube
for qube in domains
if get_max(qube) > 0
and qube.klass == "AppVM"
and getattr(qube, "template_for_dispvms", False)
]
method = "admin.vm.CreateDisposable"
loop = asyncio.get_running_loop()
tasks = []
with concurrent.futures.ThreadPoolExecutor() as executor:
for qube in appvms:
maximum = get_max(qube)
print(f"{qube}:{maximum}")
exec_args = qube.qubesd_call, qube.name, method, "preload-autostart"
future = loop.run_in_executor(executor, *exec_args)
tasks.append(future)
await asyncio.gather(*tasks)


if __name__ == "__main__":
asyncio.run(main())
1 change: 1 addition & 0 deletions linux/systemd/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ install:
cp [email protected] $(DESTDIR)$(UNITDIR)
cp qubes-qmemman.service $(DESTDIR)$(UNITDIR)
cp qubesd.service $(DESTDIR)$(UNITDIR)
cp qubes-preload-dispvm.service $(DESTDIR)$(UNITDIR)
install -d $(DESTDIR)$(UNITDIR)/[email protected]
install -m 0644 [email protected]_30_qubes.conf \
$(DESTDIR)$(UNITDIR)/[email protected]/30_qubes.conf
14 changes: 14 additions & 0 deletions linux/systemd/qubes-preload-dispvm.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[Unit]
Description=Preload Qubes DispVMs
ConditionKernelCommandLine=!qubes.skip_autostart
# After qmemman so the daemon can create the file containing available memory.
After=qubesd.service qubes-meminfo-writer-dom0.service

[Service]
Type=oneshot
ExecStart=/usr/lib/qubes/preload-dispvm
Group=qubes
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
4 changes: 2 additions & 2 deletions linux/systemd/[email protected]
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
[Unit]
Description=Start Qubes VM %i
After=qubesd.service qubes-meminfo-writer-dom0.service
Before=qubes-preload-dispvm.service
ConditionKernelCommandLine=!qubes.skip_autostart

[Service]
Type=oneshot
Environment=DISPLAY=:0
ExecStart=/usr/bin/qvm-start --skip-if-running %i
ExecStart=/usr/bin/qvm-start --skip-if-running -- %i
Group=qubes
RemainAfterExit=yes

Expand Down
48 changes: 36 additions & 12 deletions qubes/api/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@
import asyncio
import functools
import os
import pathlib
import re
import string
import subprocess
import pathlib

from ctypes import CDLL

Expand Down Expand Up @@ -1159,9 +1160,18 @@

@qubes.api.method("admin.vm.feature.Set", scope="local", write=True)
async def vm_feature_set(self, untrusted_payload):
# validation of self.arg done by qrexec-policy is enough
value = untrusted_payload.decode("ascii", errors="strict")
untrusted_value = untrusted_payload.decode("ascii", errors="strict")
del untrusted_payload
if re.match(r"\A[a-zA-Z0-9_.-]+\Z", self.arg) is None:
raise qubes.exc.QubesValueError(

Check warning on line 1166 in qubes/api/admin.py

View check run for this annotation

Codecov / codecov/patch

qubes/api/admin.py#L1166

Added line #L1166 was not covered by tests
"feature name contains illegal characters"
)
if re.match(r"\A[\x20-\x7E]*\Z", untrusted_value) is None:
raise qubes.exc.QubesValueError(

Check warning on line 1170 in qubes/api/admin.py

View check run for this annotation

Codecov / codecov/patch

qubes/api/admin.py#L1170

Added line #L1170 was not covered by tests
f"{self.arg} value contains illegal characters"
)
value = untrusted_value
del untrusted_value

self.fire_event_for_permission(value=value)
self.dest.features[self.arg] = value
Expand Down Expand Up @@ -1296,25 +1306,33 @@

@qubes.api.method("admin.vm.CreateDisposable", scope="global", write=True)
async def create_disposable(self, untrusted_payload):
self.enforce(not self.arg)
"""
Create a disposable. If the RPC argument is ``preload-autostart``,
cleanse the preload list and start preloading fresh disposables.
"""
self.enforce(self.arg in [None, "", "preload-autostart"])
preload_autostart = False
if self.arg == "preload-autostart":
preload_autostart = True
if untrusted_payload not in (b"", b"uuid"):
raise qubes.exc.QubesValueError(
"Invalid payload for admin.vm.CreateDisposable: "
"expected the empty string or 'uuid'"
)

if self.dest.name == "dom0":
dispvm_template = self.src.default_dispvm
appvm = self.src.default_dispvm
else:
dispvm_template = self.dest
appvm = self.dest

self.fire_event_for_permission(dispvm_template=dispvm_template)

dispvm = await qubes.vm.dispvm.DispVM.from_appvm(dispvm_template)
self.fire_event_for_permission(dispvm_template=appvm)
if preload_autostart:
await appvm.fire_event_async("domain-preload-dispvm-autostart")
return
dispvm = await qubes.vm.dispvm.DispVM.from_appvm(appvm)
# TODO: move this to extension (in race-free fashion, better than here)
dispvm.tags.add("created-by-" + str(self.src))
dispvm.tags.add("disp-created-by-" + str(self.src))

return (
("uuid:" + str(dispvm.uuid)) if untrusted_payload else dispvm.name
)
Expand Down Expand Up @@ -1659,7 +1677,10 @@
self.app.save()

@qubes.api.method(
"admin.vm.device.denied.List", no_payload=True, scope="local", read=True
"admin.vm.device.denied.List",
no_payload=True,
scope="local",
read=True,
)
async def vm_device_denied_list(self):
"""
Expand Down Expand Up @@ -1767,7 +1788,10 @@
self.dest.firewall.save()

@qubes.api.method(
"admin.vm.firewall.Reload", no_payload=True, scope="local", execute=True
"admin.vm.firewall.Reload",
no_payload=True,
scope="local",
execute=True,
)
async def vm_firewall_reload(self):
self.enforce(not self.arg)
Expand Down
19 changes: 18 additions & 1 deletion qubes/api/internal.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# 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/>.

""" Internal interface for dom0 components to communicate with qubesd. """
"""Internal interface for dom0 components to communicate with qubesd."""

import asyncio
import json
Expand All @@ -45,6 +45,8 @@
"domain-shutdown",
"domain-tag-add:*",
"domain-tag-delete:*",
"domain-feature-set:internal",
"domain-feature-delete:internal",
"property-set:template_for_dispvms",
"property-reset:template_for_dispvms",
"property-set:default_dispvm",
Expand Down Expand Up @@ -117,6 +119,7 @@
system_info = {
"domains": {
domain.name: {
"internal": domain.features.get("internal", None),
"tags": list(domain.tags),
"type": domain.__class__.__name__,
"template_for_dispvms": getattr(
Expand Down Expand Up @@ -262,6 +265,12 @@
:return:
"""

preload_templates = qubes.vm.dispvm.get_preload_templates(
self.app.domains
)
for qube in preload_templates:
qube.remove_preload_excess(0)

Check warning on line 272 in qubes/api/internal.py

View check run for this annotation

Codecov / codecov/patch

qubes/api/internal.py#L272

Added line #L272 was not covered by tests

# first keep track of VMs which were paused before suspending
previously_paused = [
vm.name
Expand Down Expand Up @@ -406,3 +415,11 @@
qubes.config.suspend_timeout,
"qubes.SuspendPostAll",
)

preload_templates = qubes.vm.dispvm.get_preload_templates(
self.app.domains
)
for qube in preload_templates:
asyncio.ensure_future(

Check warning on line 423 in qubes/api/internal.py

View check run for this annotation

Codecov / codecov/patch

qubes/api/internal.py#L423

Added line #L423 was not covered by tests
qube.fire_event_async("domain-preload-dispvm-autostart")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

When I change the other places to call autostart to no call future, do you think it matters here? I think that awaiting would make post suspend slower to finish? I don't know the impacts of this.

)
4 changes: 4 additions & 0 deletions qubes/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,7 @@ class Defaults(TypedDict):
qubes_ipv6_prefix = "fd09:24ef:4179:0000"

suspend_timeout = 60

#: amount of available memory on the system. Beware that the use of a file is
# subject to change.
qmemman_avail_mem_file = "/var/run/qubes/qmemman-avail-mem"
12 changes: 12 additions & 0 deletions qubes/ext/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@
except KeyError:
pass

vm.untrusted_qdb.write(
"/qubes-gui-enabled",
str(
bool(
getattr(vm, "guivm", None) and vm.features.get("gui", True)
)
),
)
# Add GuiVM Xen ID for gui-daemon
if getattr(vm, "guivm", None):
if vm != vm.guivm:
Expand Down Expand Up @@ -129,6 +137,10 @@
domain for domain in self.attached_vms(vm) if domain.is_running()
]
for attached_vm in attached_vms:
attached_vm.untrusted_qdb.write(

Check warning on line 140 in qubes/ext/gui.py

View check run for this annotation

Codecov / codecov/patch

qubes/ext/gui.py#L140

Added line #L140 was not covered by tests
"/qubes-gui-enabled",
str(bool(attached_vm.features.get("gui", True))),
)
attached_vm.untrusted_qdb.write(
"/qubes-gui-domain-xid", str(vm.xid)
)
Expand Down
2 changes: 1 addition & 1 deletion qubes/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
self.debug = debug

def formatMessage(self, record):
fmt = ""
fmt = "%(levelname)s: "

Check warning on line 36 in qubes/log.py

View check run for this annotation

Codecov / codecov/patch

qubes/log.py#L36

Added line #L36 was not covered by tests
if self.debug:
fmt += "[%(processName)s %(module)s.%(funcName)s:%(lineno)d] "
if self.debug or record.name.startswith("vm."):
Expand Down
Loading