|
2 | 2 | Disposable implementation |
3 | 3 | ========================= |
4 | 4 |
|
| 5 | +.. warning:: |
5 | 6 |
|
6 | | -**Note: The content below applies to Qubes R3.2.** |
| 7 | + This page is intended for advanced users. |
7 | 8 |
|
8 | | -DisposableVM image preparation |
9 | | ------------------------------- |
| 9 | +Disposable behavior |
| 10 | +------------------- |
10 | 11 |
|
11 | 12 |
|
12 | | -DisposableVM is not started like other VMs, by executing equivalent of ``xl create`` - it would be too slow. Instead, DisposableVM are started by restore from a savefile. |
| 13 | +A :term:`disposable template` is an :term:`app qube` with the property ``template_for_dispvms`` enabled. |
13 | 14 |
|
14 | | -Preparing a savefile is done by ``/usr/lib/qubes/qubes_prepare_saved_domain.sh`` script. It takes two mandatory arguments, appvm name (APPVM) and the savefile name, and optional path to “prerun” script. The script executes the following steps: |
| 15 | +A :term:`disposable` qube is a qube with the ``DispVM`` class based on a disposable template. Every disposable type has all of its volumes configured to disable ``save_on_stop``, therefore no changes are saved on shutdown. Unnamed disposables enables the property ``auto_cleanup`` by default, thus automatically removes the qube upon shutdown. |
15 | 16 |
|
16 | | -1. APPVM is started by ``qvm-start`` |
| 17 | +Named disposables are useful for service qubes, as referencing static names is easier when the qube name is mentioned on Qrexec policies (:file:`qubes.UpdatesProxy` target) or as a property of another qube, such as a disposable :term:`net qube` which is referenced by downstream clients in the ``netvm`` property. |
17 | 18 |
|
18 | | -2. xenstore key ``/local/domain/appvm_domain_id/qubes_save_request`` is created |
| 19 | +Unnamed disposables have their names in the format :samp:`disp{1234}`, where :samp:`{1234}` is derived from the ``dispid`` property, a random integer ranging from 0 to 9999 with a fail-safe mechanism to avoid reusing the same value in a short period. |
19 | 20 |
|
20 | | -3. if prerun script was specified, copy it to ``qubes_save_script`` xenstore key |
| 21 | +The system and every qube can have the ``default_dispvm`` property. If the qube property is set to the default value, it will use the system's property. This property can only have disposable template as value or an empty value. Qubes which have this property set are allowed to request the creation of a disposable from this property. There are some Qrexec services that which allows execution to this newly created disposable when the destination qube (Qrexec field) uses the ``@dispvm`` tag, most commonly used to open files and URLs, (:file:`qubes.OpenInVM` and :file:`qubes.OpenURL`, respectively). |
21 | 22 |
|
22 | | -4. wait for the ``qubes_used_mem`` key to appear |
| 23 | +Preloaded disposables |
| 24 | +--------------------- |
23 | 25 |
|
24 | | -5. (in APPVM) APPVM boots normally, up to the point in ``/etc/init.d/qubes_core`` script when the presence of ``qubes_save_request`` key is tested. If it exists, then |
25 | 26 |
|
26 | | - 1. (in APPVM) if exists, prerun script is retrieved from the respective xenstore key and executed. This preloads filesystem cache with useful applications, so that they will start faster. |
| 27 | +Preloaded disposables are started in the background and kept hidden from the user when not in use. They are interrupted (paused or suspended, as appropriate) and resumed (transparently) when a disposable qube is requested by the user. |
27 | 28 |
|
28 | | - 2. (in APPVM) the amount of used memory is stored to ``qubes_used_mem`` xenstore key |
| 29 | +Every process that is slow the user will want to circumvent, preloaded disposables enables fast retrieval of fresh disposables, so users don't have to get away from the computer or switch tasks when requesting disposables (or not requesting one at all because it was slow). |
29 | 30 |
|
30 | | - 3. (in APPVM) busy-waiting for ``qubes_restore_complete`` xenstore key to appear |
| 31 | +Preloaded disposable stages |
| 32 | +^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
31 | 33 |
|
| 34 | +There are several stages a disposable goes through while preloading and being used. In short: |
32 | 35 |
|
| 36 | +- **Preload**: The qube is created and marked as preloaded. Qube is not visible in GUI applications. |
33 | 37 |
|
34 | | -6. when ``qubes_used_mem`` key appears, the domain memory is reduced to this amount, to make the savefile smaller. |
| 38 | + - **Startup**: Begins qube startup, start basic services in it and attempt to interrupt (suspend/pause). |
35 | 39 |
|
36 | | -7. APPVM private image is detached |
| 40 | + - **Request**: The qube is removed from the preload list. If startup has not yet reached interrupt, the latter is skipped. |
37 | 41 |
|
38 | | -8. the domain is saved via ``xl save`` |
| 42 | +- **Used**: The qube is marked as used and may be unpaused/resumed (if applicable). Only in this phase, GUI applications treat the qube as any other unnamed disposable and the qube object is returned to the caller if requested. |
39 | 43 |
|
40 | | -9. the COW file volatile.img (cow for root fs and swap) is packed to ``saved_cows.tar`` archive |
| 44 | +Preloaded disposable's worry-free life-cycle |
| 45 | +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
41 | 46 |
|
42 | 47 |
|
| 48 | +There are several events that may trigger the creation or deletion of preloaded disposables. These events happen for different reasons, mostly because: |
43 | 49 |
|
44 | | -The ``qubes_prepare_saved_domain.sh`` script is lowlevel. It is usually called by ``qvm-create-default-dvm`` script, that takes care of creating a special AppVM (named template_name-dvm) to be passed to ``qubes_prepare_saved_domain.sh``, as well as copying the savefile to /dev/shm (the latter action is not done if the ``/var/lib/qubes/dvmdata/dont_use_shm`` file exists). |
| 50 | +- There is a gap between the current number of preloaded disposables and the max; or |
| 51 | +- The current number of preloaded disposable is over the max. |
45 | 52 |
|
46 | | -Restoring a DisposableVM from the savefile |
47 | | ------------------------------------------- |
| 53 | +The preload creation can also fail for different reasons: |
48 | 54 |
|
| 55 | +- Qubesd was interrupted mid preload creation, on the next service restart, ``domain-load`` of the disposable template will refresh the incomplete disposables; |
| 56 | +- Service to check if the system is fully operation has failed; and |
| 57 | +- There is not enough memory to preload at the moment. |
49 | 58 |
|
50 | | -Normally, disposable VM is created when qubes rpc request with target *$dispvm* is received. Then, as a part of rpc connection setup, the ``qfile-daemon-dvm`` program is executed; it executes ``/usr/lib/qubes/qubes_restore`` program. It is crucial that this program executes quickly, to make DisposableVM creation overhead bearable for the user. Its main steps are: |
| 59 | +If there is a gap, it will be capped *event*\ ually, if it is over the max, it will be deleted as soon as possible. If the volumes are outdated, they will be refreshed. These are common events that trigger changes in preloaded disposables quantity: |
51 | 60 |
|
52 | | -1. modify the savefile so that the VM name, VM UUID, MAC address and IP address are unique |
| 61 | +- Setting or deleting the ``preload-dispvm-max`` feature will refill or remove; |
| 62 | +- (Re)starting :file:`qubes-preload-dispvm.service` will refresh; |
| 63 | +- Using a preloaded disposable will refill; |
| 64 | +- Requesting a disposable will refill; |
| 65 | +- Updating the volumes of a template or disposable template will refresh; |
| 66 | +- Changing system's ``default_dispvm`` while system's feature is set to a different value than the disposable template setting will refill or remove; |
53 | 67 |
|
54 | | -2. restore the COW files from the ``saved_cows.tar`` |
| 68 | +Preloaded disposables memory management |
| 69 | +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
55 | 70 |
|
56 | | -3. create the ``/var/run/qubes/fast_block_attach`` file, whose presence tells the ``/etc/xen/scripts/block`` script to bypass some redundant checks and execute as fast as possible. |
57 | 71 |
|
58 | | -4. execute ``xl restore`` in order to restore a domain. |
| 72 | +At the end of preloading, the qube is paused if it has not been requested yet, but before pausing, on ``domain-pre-paused``, it attempts to retrieve memory from the qube by setting it to use its preferred memory value, which in :doc:`qmemman parlance </developer/services/qmemman>`, is just enough to have the qube running. The memory must be managed before the qube is paused, cause once on the paused state, it is not possible to negotiate with the domain. |
59 | 73 |
|
60 | | -5. create the same xenstore keys as normally created when AppVM boots (e.g. ``qubes_ip``) |
| 74 | +This process can take a bit of time because it depends on how fast the qube can free up memory. There is a timeout and a threshold in transfer speed. When any of these exit conditions are met, the memory management seizes and the preloaded disposable is paused. Although this process takes some time, we do not worry much about it because it economizes memory on the long run, the biggest problems is that qmemman is synchronous, so only one request can be made at a time, anything that takes too much time on qmemman could prevent ballooning/balancing of other qubes on the system. |
61 | 75 |
|
62 | | -6. create the ``qubes_restore_complete`` xenstore key. This allows the boot process in DisposableVM to continue. |
| 76 | +Preloaded disposables security |
| 77 | +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
63 | 78 |
|
64 | 79 |
|
| 80 | +As preloaded disposables are started before being used, methods to prevent accidental tampering have been put in place as well as guarantees to prevent reuse: |
65 | 81 |
|
66 | | -The actual passing of files between AppVM and a DisposableVM is implemented via qubes rpc. |
| 82 | +- The qube has the ``internal`` feature enabled, Qubes GUI applications were patched to hide internal qubes, handling events for ``domain-feature-(set|delete):internal``; |
| 83 | +- When requesting an unnamed disposable, the qube object is only returned to the user once it has finished preloading; |
| 84 | +- The qube is paused as the last stage of preloading, this permits receiving ``domain-unpaused`` events and be notified that the qube was used, marked as such and removed from the preload list to avoid reuse, even without the qube being requested with ``DispVM.from_appvm``; |
| 85 | +- The GUID only connects to the GUI agent on the qube after the preloaded disposable is marked as used, this prevents that an autostarted application such as a terminal appears on the screen before preloading has finished. Enabling a GUI is is controlled by the ``is_preload`` property, that when disabled, allows the GUI connection to initiate. This method delays GUI calls considerably as establishing the connection can take ~2 seconds, research is being done to prevent this delay. |
67 | 86 |
|
68 | | -Validating the DisposableVM savefile |
69 | | ------------------------------------- |
| 87 | +Another point of security is reliability: |
70 | 88 |
|
| 89 | +- The feature ``preload-dispvm-threshold`` controls how much free memory must be present on the system before attempting to create a new preloaded disposable. Used to ensure preloaded disposables do not consume all available memory, which would prevent starting other qubes. |
71 | 90 |
|
72 | | -DisposableVM savefile contains references to template rootfs and to COW files. The COW files are restored before each DisposableVM start, so they cannot change. On the other hand, if templateVM is started, the template rootfs will change, and it may not be coherent with the COW files. |
| 91 | +Alternatives considered |
| 92 | +^^^^^^^^^^^^^^^^^^^^^^^ |
73 | 93 |
|
74 | | -Therefore, the check for template rootfs modification time being older than DisposableVM savefile modification time is required. It is done in ``qfilexchgd`` daemon, just before restoring DisposableVM. If necessary, an attempt is made to recreate the DisposableVM savefile, using the last template used (or default template, if run for the first time) and the default prerun script, residing at ``/var/lib/qubes/vm-templates/templatename/dispvm_prerun.sh``. Unfortunately, the prerun script takes a lot of time to execute - therefore, after template rootfs modification, the next DisposableVM creation can be longer by about 2.5 minutes. |
| 94 | + |
| 95 | +For an alternative to be considered for implementation, it must meet the following requirements: |
| 96 | + |
| 97 | +- No memory or ``vcpus`` restrictions such as limiting to a few number of ``vcpus`` or assigns memory on request (can be slow). |
| 98 | +- Performant as much as a normal disposable even on long running sessions; |
| 99 | +- Caller transparency, no change necessary for callers, the request must be transparent and the server must find the fastest option. This is to avoid transition burden (API breakage). |
| 100 | + |
| 101 | +From the evaluated options, only :ref:`preload queue <developer/services/disposablevm-implementation:preload queue>` meets all requirements. |
| 102 | + |
| 103 | + |
| 104 | +Restoration from savefile |
| 105 | +""""""""""""""""""""""""" |
| 106 | + |
| 107 | + |
| 108 | +**Description**: Disposable template booted, its image was dumped (suspend to disk), newly disposables would restore this image to become their own. |
| 109 | + |
| 110 | +**Evaluation**: |
| 111 | + |
| 112 | +- Used in R3.2, worked at that time, when there was only one disposable template available, see next points of why it can't be used anymore. |
| 113 | +- Incompatible with multiple ``vcpus``. |
| 114 | +- Some memory issues. |
| 115 | +- Savefile creation takes a long time. The disposable qube savefile contains references to template rootfs and :abbr:`CoW (Copy-on-Write)` files, if there is a modification on the template or disposable template, it took longer than 2.5 minutes to generate the next disposable. |
| 116 | + |
| 117 | +Xen domain fork |
| 118 | +""""""""""""""" |
| 119 | + |
| 120 | + |
| 121 | +**Description**: domain forking is the process of creating a domain with an empty memory space and a parent domain specified from which to populate the memory when necessary. For the new domain to be functional the domain state is copied over as part of the fork operation (HVM params, heap allocation etc). This description was sourced from `[Xen-devel] [RFC PATCH for-next 17/18] xen/mem_sharing: VM forking, Tamas K Lengyel <https://lists.xenproject.org/archives/html/xen-devel/2019-09/msg02497.html>`__. |
| 122 | + |
| 123 | +**Evaluation**: |
| 124 | + |
| 125 | +- Shares too much information from the trunk to the forks. This appears to have improved if not totally fixed on Linux 6.14, as mentioned by Andrew Cooper on Qubes OS Summit 2025; |
| 126 | +- Requires changing properties after the fork is done, this includes, but not limited to, ``xid`` of connected qubes, network uplink; |
| 127 | +- Not designed for long running sessions, the initial intention was fuzzing. As fast as the creation can be, the usage may be slower as memory is mapped on request. Xen doesn't have a poper :abbr:`CoW (Copy-on-Write)` support for domain memory, so making a full copy of a domain on fork also has some overhead; |
| 128 | +- Tamas K Lengyiel `VM Forking & Hypervisor-Based Fuzzing with Xen <https://www.youtube.com/watch?v=3MYo8ctD_aU>`__ presentation during the Open Source Summit Europe in 2022, showed impressive results on CPU i5-8350U, an average of time of 0.745 ms per fork (created 1300 VM/s). These fast results were later explained that was due to not initializing the whole VM memory on the fork unless it was requested, as explained on the point above. Still impressive results but current usage is limited to fuzzing. |
| 129 | + |
| 130 | +Preload queue |
| 131 | +""""""""""""" |
| 132 | + |
| 133 | + |
| 134 | +**Description**: Start disposables and queue them in a disposable template feature, unnamed disposables requested will prefer to retrieve disposables from this list. |
| 135 | + |
| 136 | +**Evaluation**: |
| 137 | + |
| 138 | +- Because the qube is running prior to being requested, multiple components have to be patched to support it to various levels off difficulty. Excluding from backups to allowing removal of disposable templates that only have preloaded disposables to even stranger issues such as deferring net qube change from a preloaded disposable where the old net qube has already been purged from the system. |
| 139 | +- The biggest difference between the queue and the other alternatives is that this solution works, is reliable and fulfills all requirements. A proper solution would be patching upstream Xen to implement :abbr:`CoW (Copy-on-Write)`, but that would involve a lot more work than what the Qubes Team can provide with current resources. |
0 commit comments