diff --git a/qubes/app.py b/qubes/app.py index d5012664..e5e75d07 100644 --- a/qubes/app.py +++ b/qubes/app.py @@ -702,7 +702,9 @@ class Qubes(qubes.PropertyHolder): pools_xml = lxml.etree.Element('pools') for pool in self.pools.values(): - pools_xml.append(pool.__xml__()) + xml = pool.__xml__() + if xml is not None: + pools_xml.append(xml) element.append(pools_xml) diff --git a/qubes/storage/__init__.py b/qubes/storage/__init__.py index 0aaf0790..d9efd275 100644 --- a/qubes/storage/__init__.py +++ b/qubes/storage/__init__.py @@ -57,13 +57,15 @@ class Volume(object): script = None usage = 0 - def __init__(self, name, pool, volume_type, vid=None, size=0, **kwargs): + def __init__(self, name, pool, volume_type, vid=None, size=0, + removable=False, **kwargs): super(Volume, self).__init__(**kwargs) self.name = str(name) self.pool = str(pool) self.vid = vid self.size = size self.volume_type = volume_type + self.removable = removable def __xml__(self): return lxml.etree.Element('volume', **self.config) diff --git a/qubes/storage/domain.py b/qubes/storage/domain.py new file mode 100644 index 00000000..e2ab388c --- /dev/null +++ b/qubes/storage/domain.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python2 +# -*- encoding: utf8 -*- +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2016 Bahtiar `kalkin-` Gadimov +# +# 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. +# +''' Manages block devices in a domain ''' + +import string + +from qubes.storage import Pool, Volume + + +class DomainPool(Pool): + ''' This pool manages all the block devices of a domain. + + The devices are queried through :py:module:`qubesdb` + ''' + + driver = 'domain' + + def __init__(self, vm): + self.vm = vm + super(DomainPool, self).__init__(name='p_' + vm.name) + + @property + def volumes(self): + ''' Queries qubesdb and returns volumes for `self.vm` ''' + + qdb = self.vm.qdb + safe_set = set(string.letters + string.digits + string.punctuation) + allowed_attributes = {'desc': string.printable, + 'mode': string.letters, + 'size': string.digits} + if not self.vm.is_running(): + return [] + untrusted_qubes_devices = qdb.list('/qubes-block-devices/') + # because we get each path 3 x times as + # /qubes-block-devices/foo/{desc,mode,size} we need to merge this + devices = {} + for untrusted_device_path in untrusted_qubes_devices: + if not all(c in safe_set for c in untrusted_device_path): + msg = ("%s vm's device path name contains unsafe characters. " + "Skipping it.") + self.vm.log.warning(msg % self.vm.name) + continue + + # name can be trusted because it was checked as a part of + # untrusted_device_path check above + _, _, name, untrusted_atr = untrusted_device_path.split('/', 4) + + if untrusted_atr in allowed_attributes.keys(): + atr = untrusted_atr + else: + msg = ('{!s} has an unknown qubes-block-device atr {!s} ' + 'Skipping it') + self.vm.log.error(msg.format(self.vm.name, untrusted_atr)) + continue + + untrusted_value = qdb.read(untrusted_device_path) + allowed_characters = allowed_attributes[atr] + if all(c in allowed_characters for c in untrusted_value): + value = untrusted_value + else: + msg = ("{!s} vm's device path {!s} contains unsafe characters") + self.vm.log.error(msg.format(self.vm.name, atr)) + continue + + if name not in devices.keys(): + devices[name] = {} + + devices[name][atr] = value + + return [DomainVolume(n, self.name, **atrs) + for n, atrs in devices.items()] + + def clone(self, source, target): + raise NotImplementedError + + def __xml__(self): + return None + + +class DomainVolume(Volume): + ''' A volume provided by a block device in an domain ''' + + def __init__(self, name, pool, desc, mode, size): + if mode == 'w': + volume_type = 'read-write' + else: + volume_type = 'read-only' + + super(DomainVolume, self).__init__(desc, + pool, + volume_type, + vid=name, + size=size, + removable=True) diff --git a/qubes/vm/qubesvm.py b/qubes/vm/qubesvm.py index 203331f1..ae97d8bf 100644 --- a/qubes/vm/qubesvm.py +++ b/qubes/vm/qubesvm.py @@ -51,6 +51,8 @@ import qubes.vm import qubes.vm.mix.net import qubes.tools.qvm_ls +from qubes.storage.domain import DomainPool + qmemman_present = False try: import qubes.qmemman.client @@ -530,6 +532,8 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): # Initialize VM image storage class self.storage = qubes.storage.Storage(self) + vm_pool = DomainPool(self) + self.app.pools[vm_pool.name] = vm_pool @qubes.events.handler('property-set:label') diff --git a/rpm_spec/core-dom0.spec b/rpm_spec/core-dom0.spec index ccd0ec89..8b351e56 100644 --- a/rpm_spec/core-dom0.spec +++ b/rpm_spec/core-dom0.spec @@ -234,6 +234,7 @@ fi %dir %{python_sitelib}/qubes/storage %{python_sitelib}/qubes/storage/__init__.py* %{python_sitelib}/qubes/storage/file.py* +%{python_sitelib}/qubes/storage/domain.py* %{python_sitelib}/qubes/storage/kernels.py* %dir %{python_sitelib}/qubes/tools