core-admin/core-modules/01QubesDisposableVm.py
Marek Marczykowski-Górecki 602155374a dispvm: restore DispVM naming independent of Qubes VM ID (#983)
Using QID for DispVM ID was a bad idea in terms of anonymity:
1. It gives some clue about VMs count in the system. In case of large
numbers, this can be quite unique.
2. If new DispVM is started just after closing previous one, it will get
the same ID, and in consequence the same IP. In case of using TorVM,
this leads to use the same circuit as just closed DispVM.

Fixes qubesos/qubes-issues#983
2015-05-04 00:41:33 +02:00

241 lines
8.3 KiB
Python

#!/usr/bin/python2
# -*- coding: utf-8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
# Copyright (C) 2013 Marek Marczykowski <marmarek@invisiblethingslab.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
#
import os
import sys
import libvirt
import time
from qubes.qubes import QubesVm,QubesVmLabel,register_qubes_vm_class, \
QubesException
from qubes.qubes import QubesDispVmLabels
from qubes.qubes import dry_run,vmm
import grp
qmemman_present = False
try:
from qubes.qmemman_client import QMemmanClient
qmemman_present = True
except ImportError:
pass
DISPID_STATE_FILE = '/var/run/qubes/dispid'
class QubesDisposableVm(QubesVm):
"""
A class that represents an DisposableVM. A child of QubesVm.
"""
# In which order load this VM type from qubes.xml
load_order = 120
def _assign_new_dispid(self):
# This method in called while lock on qubes.xml is held, so no need for
# additional lock
if os.path.exists(DISPID_STATE_FILE):
f = open(DISPID_STATE_FILE, 'r+')
dispid = int(f.read())
f.seek(0)
f.truncate(0)
f.write(str(dispid+1))
f.close()
else:
dispid = 1
f = open(DISPID_STATE_FILE, 'w')
f.write(str(dispid+1))
f.close()
os.chown(DISPID_STATE_FILE, -1, grp.getgrnam('qubes').gr_gid)
os.chmod(DISPID_STATE_FILE, 0664)
return dispid
def get_attrs_config(self):
attrs_config = super(QubesDisposableVm, self).get_attrs_config()
attrs_config['name']['func'] = \
lambda x: "disp%d" % self.dispid if x is None else x
# New attributes
attrs_config['dispid'] = {
'func': lambda x: (self._assign_new_dispid() if x is None
else int(x)),
'save': lambda: str(self.dispid),
# needs to be set before name
'order': 0
}
attrs_config['include_in_backups']['func'] = lambda x: False
attrs_config['disp_savefile'] = {
'default': '/var/run/qubes/current-savefile',
'save': lambda: str(self.disp_savefile) }
return attrs_config
def __init__(self, **kwargs):
disp_template = None
if 'disp_template' in kwargs.keys():
disp_template = kwargs['disp_template']
kwargs['template'] = disp_template.template
kwargs['dir_path'] = disp_template.dir_path
super(QubesDisposableVm, self).__init__(**kwargs)
assert self.template is not None, "Missing template for DisposableVM!"
if disp_template:
self.clone_attrs(disp_template)
# Use DispVM icon with the same color
if self._label:
self._label = QubesDispVmLabels[self._label.name]
self.icon_path = self._label.icon_path
@property
def type(self):
return "DisposableVM"
def is_disposablevm(self):
return True
@property
def ip(self):
if self.netvm is not None:
return self.netvm.get_ip_for_dispvm(self.dispid)
else:
return None
def get_clone_attrs(self):
attrs = super(QubesDisposableVm, self).get_clone_attrs()
attrs.remove('_label')
return attrs
def do_not_use_get_xml_attrs(self):
# Minimal set - do not inherit rest of attributes
attrs = {}
attrs["qid"] = str(self.qid)
attrs["name"] = self.name
attrs["dispid"] = str(self.dispid)
attrs["template_qid"] = str(self.template.qid)
attrs["label"] = self.label.name
attrs["firewall_conf"] = self.relative_path(self.firewall_conf)
attrs["netvm_qid"] = str(self.netvm.qid) if self.netvm is not None else "none"
return attrs
def verify_files(self):
return True
def get_config_params(self):
attrs = super(QubesDisposableVm, self).get_config_params()
attrs['privatedev'] = ''
return attrs
def create_qubesdb_entries(self):
super(QubesDisposableVm, self).create_qubesdb_entries()
self.qdb.write('/qubes-restore-complete', '1')
def start(self, verbose = False, **kwargs):
self.log.debug('start()')
if dry_run:
return
# Intentionally not used is_running(): eliminate also "Paused", "Crashed", "Halting"
if self.get_power_state() != "Halted":
raise QubesException ("VM is already running!")
# skip netvm state checking - calling VM have the same netvm, so it
# must be already running
if verbose:
print >> sys.stderr, "--> Loading the VM (type = {0})...".format(self.type)
print >>sys.stderr, "time=%s, creating config file" % (str(time.time()))
# refresh config file
domain_config = self.create_config_file()
if qmemman_present:
mem_required = int(self.memory) * 1024 * 1024
print >>sys.stderr, "time=%s, getting %d memory" % (str(time.time()), mem_required)
qmemman_client = QMemmanClient()
try:
got_memory = qmemman_client.request_memory(mem_required)
except IOError as e:
raise IOError("ERROR: Failed to connect to qmemman: %s" % str(e))
if not got_memory:
qmemman_client.close()
raise MemoryError ("ERROR: insufficient memory to start VM '%s'" % self.name)
# dispvm cannot have PCI devices
assert (len(self.pcidevs) == 0), "DispVM cannot have PCI devices"
print >>sys.stderr, "time=%s, calling restore" % (str(time.time()))
vmm.libvirt_conn.restoreFlags(self.disp_savefile,
domain_config, libvirt.VIR_DOMAIN_SAVE_PAUSED)
print >>sys.stderr, "time=%s, done" % (str(time.time()))
self._libvirt_domain = None
if verbose:
print >> sys.stderr, "--> Starting Qubes DB..."
self.start_qubesdb()
self.services['qubes-dvm'] = True
if verbose:
print >> sys.stderr, "--> Setting Qubes DB info for the VM..."
self.create_qubesdb_entries()
print >>sys.stderr, "time=%s, done qubesdb" % (str(time.time()))
# fire hooks
for hook in self.hooks_start:
hook(self, verbose = verbose, **kwargs)
if verbose:
print >> sys.stderr, "--> Starting the VM..."
self.libvirt_domain.resume()
print >>sys.stderr, "time=%s, resumed" % (str(time.time()))
# close() is not really needed, because the descriptor is close-on-exec
# anyway, the reason to postpone close() is that possibly xl is not done
# constructing the domain after its main process exits
# so we close() when we know the domain is up
# the successful unpause is some indicator of it
if qmemman_present:
qmemman_client.close()
if self._start_guid_first and kwargs.get('start_guid', True) and os.path.exists('/var/run/shm.id'):
self.start_guid(verbose=verbose,
notify_function=kwargs.get('notify_function', None))
self.start_qrexec_daemon(verbose=verbose,
notify_function=kwargs.get('notify_function', None))
print >>sys.stderr, "time=%s, qrexec done" % (str(time.time()))
if not self._start_guid_first and kwargs.get('start_guid', True) and os.path.exists('/var/run/shm.id'):
self.start_guid(verbose=verbose,
notify_function=kwargs.get('notify_function', None))
print >>sys.stderr, "time=%s, guid done" % (str(time.time()))
return self.xid
# register classes
register_qubes_vm_class(QubesDisposableVm)