Qubes: detect qube failing to start (missing RAM)

In Qubes OS it's often the case that the user doesn't have enough
RAM to start the conversion. In this case it raises BrokenPipeException
and exits with code 126.

It didn't seem possible to distinguish this kind of failure to one
where the user has misconfigured qrexec policies.

NOTE: this approach is not ideal UX-wise. After the first doc failing
the next one will also try and fail. Upon first failure we should
inform the user that they need to close some programs or qubes.
This commit is contained in:
deeplow 2023-09-21 10:09:52 +01:00
parent 63f03d5bcd
commit 0a6b33ebed
No known key found for this signature in database
GPG key ID: 577982871529A52A
3 changed files with 70 additions and 24 deletions

View file

@ -24,6 +24,14 @@ class ConversionException(Exception):
return subclasses
class QubesNotEnoughRAMError(ConversionException):
error_code = 126 # No ERROR_SHIFT since this is a qrexec error
error_message = (
"Your system does not have enough RAM available to start the conversion. "
"Please close some qubes or programs and try again."
)
class DocFormatUnsupported(ConversionException):
error_code = ERROR_SHIFT + 10
error_message = "The document format is not supported"

View file

@ -88,33 +88,13 @@ class Qubes(IsolationProvider):
percentage = 0.0
with open(document.input_filename, "rb") as f:
# TODO handle lack of memory to start qube
if getattr(sys, "dangerzone_dev", False):
# Use dz.ConvertDev RPC call instead, if we are in development mode.
# Basically, the change is that we also transfer the necessary Python
# code as a zipfile, before sending the doc that the user requested.
self.proc = subprocess.Popen(
["/usr/bin/qrexec-client-vm", "@dispvm:dz-dvm", "dz.ConvertDev"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
assert self.proc is not None
self.proc = self.qrexec_subprocess()
try:
assert self.proc.stdin is not None
# Send the dangerzone module first.
self.teleport_dz_module(self.proc.stdin)
# Finally, send the document, as in the normal case.
self.proc.stdin.write(f.read())
self.proc.stdin.close()
else:
self.proc = subprocess.Popen(
["/usr/bin/qrexec-client-vm", "@dispvm:dz-dvm", "dz.Convert"],
stdin=f,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
)
except BrokenPipeError as e:
raise errors.InterruptedConversion()
# Get file size (in MiB)
size = os.path.getsize(document.input_filename) / 1024**2
@ -207,6 +187,32 @@ class Qubes(IsolationProvider):
def get_max_parallel_conversions(self) -> int:
return 1
def qrexec_subprocess(self) -> subprocess.Popen:
dev_mode = getattr(sys, "dangerzone_dev", False) == True
if dev_mode:
# Use dz.ConvertDev RPC call instead, if we are in development mode.
# Basically, the change is that we also transfer the necessary Python
# code as a zipfile, before sending the doc that the user requested.
qrexec_policy = "dz.ConvertDev"
stderr = subprocess.PIPE
else:
qrexec_policy = "dz.Convert"
stderr = subprocess.DEVNULL
p = subprocess.Popen(
["/usr/bin/qrexec-client-vm", "@dispvm:dz-dvm", qrexec_policy],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=stderr,
)
if dev_mode:
assert p.stdin is not None
# Send the dangerzone module first.
self.teleport_dz_module(p.stdin)
return p
def teleport_dz_module(self, wpipe: IO[bytes]) -> None:
"""Send the dangerzone module to another qube, as a zipfile."""
# Grab the absolute file path of the dangerzone module.

View file

@ -1,4 +1,9 @@
import signal
import subprocess
import time
import pytest
from pytest import MonkeyPatch
from pytest_mock import MockerFixture
from dangerzone.conversion import errors
@ -54,3 +59,30 @@ class TestQubes(IsolationProviderTest):
with pytest.raises(errors.MaxPageHeightException):
success = provider._convert(Document(sample_bad_height), ocr_lang=None)
assert not success
def test_out_of_ram(
self,
provider: Qubes,
mocker: MockerFixture,
monkeypatch: MonkeyPatch,
sample_doc: str,
) -> None:
provider.progress_callback = mocker.MagicMock()
def qrexec_subprocess() -> subprocess.Popen:
p = subprocess.Popen(
# XXX error 126 simulates a qrexec-policy failure. Source:
# https://github.com/QubesOS/qubes-core-qrexec/blob/fdcbfd7/daemon/qrexec-daemon.c#L1022
["exit 126"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=True,
)
return p
monkeypatch.setattr(provider, "qrexec_subprocess", qrexec_subprocess)
with pytest.raises(errors.QubesNotEnoughRAMError) as e:
doc = Document(sample_doc)
provider._convert(doc, ocr_lang=None)