Merge remote-tracking branch 'origin/master' into core3-devel-mm

This commit is contained in:
Marek Marczykowski-Górecki 2016-04-11 13:03:12 +02:00
commit 8c6fe7ed90
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
25 changed files with 1177 additions and 175 deletions

View File

@ -74,8 +74,10 @@ endif
cp qubes-rpc-policy/qubes.NotifyUpdates.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.NotifyUpdates
cp qubes-rpc-policy/qubes.NotifyTools.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.NotifyTools
cp qubes-rpc-policy/qubes.GetImageRGBA.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.GetImageRGBA
cp qubes-rpc-policy/qubes.GetRandomizedTime.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.GetRandomizedTime
cp qubes-rpc/qubes.NotifyUpdates $(DESTDIR)/etc/qubes-rpc/
cp qubes-rpc/qubes.NotifyTools $(DESTDIR)/etc/qubes-rpc/
cp qubes-rpc/qubes.GetRandomizedTime $(DESTDIR)/etc/qubes-rpc/
cp qubes-rpc/qubes-notify-updates $(DESTDIR)/usr/libexec/qubes/
cp qubes-rpc/qubes-notify-tools $(DESTDIR)/usr/libexec/qubes/

View File

@ -591,9 +591,18 @@ class QubesVm(object):
if self.installed_by_rpm:
raise QubesException("Cannot rename VM installed by RPM -- first clone VM and then use yum to remove package.")
assert self._collection is not None
if self._collection.get_vm_by_name(name):
raise QubesException("VM with this name already exists")
self.pre_rename(name)
if self.libvirt_domain:
try:
self.libvirt_domain.undefine()
except libvirt.libvirtError as e:
if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN:
pass
else:
raise
if self._qdb_connection:
self._qdb_connection.close()
self._qdb_connection = None
@ -779,6 +788,8 @@ class QubesVm(object):
# libxl_domain_info failed - domain no longer exists
elif e.get_error_code() == libvirt.VIR_ERR_INTERNAL_ERROR:
return 0
elif e.get_error_code() is None: # unknown...
return 0
else:
print >>sys.stderr, "libvirt error code: {!r}".format(
e.get_error_code())
@ -796,7 +807,9 @@ class QubesVm(object):
if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN:
return 0
# libxl_domain_info failed - domain no longer exists
elif e.get_error_code() == libvirt.VIR_INTERNAL_ERROR:
elif e.get_error_code() == libvirt.VIR_ERR_INTERNAL_ERROR:
return 0
elif e.get_error_code() is None: # unknown...
return 0
else:
print >>sys.stderr, "libvirt error code: {!r}".format(
@ -918,6 +931,11 @@ class QubesVm(object):
except libvirt.libvirtError as e:
if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN:
return False
# libxl_domain_info failed - domain no longer exists
elif e.get_error_code() == libvirt.VIR_ERR_INTERNAL_ERROR:
return False
elif e.get_error_code() is None: # unknown...
return False
else:
print >>sys.stderr, "libvirt error code: {!r}".format(
e.get_error_code())
@ -932,6 +950,11 @@ class QubesVm(object):
except libvirt.libvirtError as e:
if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN:
return False
# libxl_domain_info failed - domain no longer exists
elif e.get_error_code() == libvirt.VIR_ERR_INTERNAL_ERROR:
return False
elif e.get_error_code() is None: # unknown...
return False
else:
print >>sys.stderr, "libvirt error code: {!r}".format(
e.get_error_code())
@ -1082,6 +1105,7 @@ class QubesVm(object):
if self.is_netvm():
self.qdb.write("/qubes-netvm-gateway", self.gateway)
self.qdb.write("/qubes-netvm-primary-dns", self.gateway)
self.qdb.write("/qubes-netvm-secondary-dns", self.secondary_dns)
self.qdb.write("/qubes-netvm-netmask", self.netmask)
self.qdb.write("/qubes-netvm-network", self.network)
@ -1090,6 +1114,7 @@ class QubesVm(object):
self.qdb.write("/qubes-ip", self.ip)
self.qdb.write("/qubes-netmask", self.netvm.netmask)
self.qdb.write("/qubes-gateway", self.netvm.gateway)
self.qdb.write("/qubes-primary-dns", self.netvm.gateway)
self.qdb.write("/qubes-secondary-dns", self.netvm.secondary_dns)
tzname = self.get_timezone()
@ -1651,13 +1676,17 @@ class QubesVm(object):
if bool(input) + bool(passio_popen) + bool(localcmd) > 1:
raise ValueError("'input', 'passio_popen', 'localcmd' cannot be "
"used together")
if not wait and (localcmd or input):
raise ValueError("Cannot use wait=False with input or "
"localcmd specified")
if localcmd:
return self.run("QUBESRPC %s %s" % (service, source),
localcmd=localcmd, user=user, wait=wait, gui=gui)
elif input:
return self.run("QUBESRPC %s %s" % (service, source),
localcmd="echo %s" % input, user=user, wait=wait,
gui=gui)
p = self.run("QUBESRPC %s %s" % (service, source),
user=user, wait=wait, gui=gui, passio_popen=True)
p.communicate(input)
return p.returncode
else:
return self.run("QUBESRPC %s %s" % (service, source),
passio_popen=passio_popen, user=user, wait=wait,

View File

@ -51,6 +51,7 @@ class QfileDaemonDvm:
qvm_collection = QubesVmCollection()
qvm_collection.lock_db_for_writing()
try:
tar_process = subprocess.Popen(
['bsdtar', '-C', current_savefile_vmdir,
@ -62,7 +63,6 @@ class QfileDaemonDvm:
vm = qvm_collection.get_vm_by_name(self.name)
if vm is None:
sys.stderr.write('Domain ' + self.name + ' does not exist ?')
qvm_collection.unlock_db()
return None
label = vm.label
if len(sys.argv) > 4 and len(sys.argv[4]) > 0:
@ -72,7 +72,6 @@ class QfileDaemonDvm:
vm_disptempl = qvm_collection.get_vm_by_name(disp_templ)
if vm_disptempl is None:
sys.stderr.write('Domain ' + disp_templ + ' does not exist ?')
qvm_collection.unlock_db()
return None
dispvm = qvm_collection.add_new_vm('QubesDisposableVm',
disp_template=vm_disptempl,
@ -98,7 +97,6 @@ class QfileDaemonDvm:
# Wait for tar to finish
if tar_process.wait() != 0:
sys.stderr.write('Failed to unpack saved-cows.tar')
qvm_collection.unlock_db()
return None
print >>sys.stderr, "time=%s, VM starting" % (str(time.time()))
try:
@ -112,6 +110,7 @@ class QfileDaemonDvm:
dispvm.netvm = vm.dispvm_netvm
print >>sys.stderr, "time=%s, VM started" % (str(time.time()))
qvm_collection.save()
finally:
qvm_collection.unlock_db()
# Reload firewall rules
print >>sys.stderr, "time=%s, reloading firewall" % (str(time.time()))

View File

@ -15,6 +15,10 @@ Options
Show this help message and exit
.. option:: --verify-only
Do not restore the data, only verify backup integrity
.. option:: --skip-broken
Do not restore VMs that have missing templates or netvms
@ -48,6 +52,22 @@ Options
Ignore dom0 username mismatch while restoring homedir
.. option:: --dest-vm=APPVM, -d APPVM
Restore from a backup located in a specific AppVM
.. option:: --encrypted, -e
The backup is encrypted
.. option:: --compressed. -z
The backup is compressed
.. option:: --debug
Enable (a lot of) debug output
Authors
=======
| Joanna Rutkowska <joanna at invisiblethingslab dot com>

View File

@ -156,7 +156,7 @@ mac
Can be used to force specific of virtual ethernet card in the VM. Setting
to ``auto`` will use automatic-generated MAC - based on VM id. Especially
useful when some licencing depending on static MAC address.
useful when licensing requires a static MAC address.
For template-based HVM ``auto`` mode means to clone template MAC.
default_user
@ -169,7 +169,7 @@ debug
Accepted values: ``on``, ``off``
Enables debug mode for VM. This can be used to turn on/off verbose logging
in many qubes components at once (gui virtualization, VM kernel, some other
in many Qubes components at once (gui virtualization, VM kernel, some other
services).
For template-based HVM, enabling debug mode also disables automatic reset
@ -196,7 +196,7 @@ guiagent_installed
This HVM have gui agent installed. This option disables full screen GUI
virtualization and enables per-window seemless GUI mode. This option will
be automatically turned on during Qubes Windows Tools installation, but if
you install qubes gui agent in some other OS, you need to turn this option
you install Qubes gui agent in some other OS, you need to turn this option
on manually. You can turn this option off to troubleshoot some early HVM OS
boot problems (enter safe mode etc), but the option will be automatically
enabled at first VM normal startup (and will take effect from the next

View File

@ -0,0 +1,6 @@
## Note that policy parsing stops at the first match,
## so adding anything below "$anyvm $anyvm action" line will have no effect
## Please use a single # to start your custom comments
$anyvm dom0 allow

View File

@ -0,0 +1,77 @@
#!/bin/bash
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2016 Patrick Schleizer <adrelanos@riseup.net>
#
# 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.
## Similar code as Boot Clock Randomization.
## https://www.whonix.org/wiki/Boot_Clock_Randomization
set -e
## Get a random 0 or 1.
## Will use this to decide to use plus or minus.
ZERO_OR_ONE="$(shuf -i0-1 -n1 --random-source=/dev/random)"
## Create a random number between 0 and 180.
DELAY="$(shuf -i0-180 -n1 --random-source=/dev/random)"
## Create a random number between 0 and 999999999.
##
## Thanks to
## https://stackoverflow.com/questions/22887891/how-can-i-get-a-random-dev-random-number-between-0-and-999999999-in-bash
NANOSECONDS="$(shuf -i0-999999999 -n1 --random-source=/dev/random)"
## Examples NANOSECONDS:
## 117752805
## 38653957
## Add leading zeros, because `date` expects 9 digits.
NANOSECONDS="$(printf '%0*d\n' 9 "$NANOSECONDS")"
## Using
## printf '%0*d\n' 9 "38653957"
## 38653957
## becomes
## 038653957
## Examples NANOSECONDS:
## 117752805
## 038653957
if [ "$ZERO_OR_ONE" = "0" ]; then
PLUS_OR_MINUS="-"
elif [ "$ZERO_OR_ONE" = "1" ]; then
PLUS_OR_MINUS="+"
else
exit 2
fi
#OLD_TIME="$(date)"
#OLD_TIME_NANOSECONDS="$(date +%s.%N)"
OLD_UNIXTIME="$(date +%s)"
NEW_TIME="$(( $OLD_UNIXTIME $PLUS_OR_MINUS $DELAY ))"
NEW_TIME_NANOSECONDS="$NEW_TIME.$NANOSECONDS"
echo "$NEW_TIME_NANOSECONDS"
## Testing the `date` syntax:
## date --date @1396733199.112834496
## date --date "@$NEW_TIME_NANOSECONDS"

View File

@ -451,7 +451,7 @@ class Backup(object):
vms_not_for_backup = [vm.name for vm in self.app.domains
if vm not in self.vms_for_backup]
summary += "VMs not selected for backup:\n - " + "\n - ".join(
vms_not_for_backup)
sorted(vms_not_for_backup))
return summary

View File

@ -25,13 +25,24 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
"""
.. warning::
The test suite hereby claims any domain whose name starts with
:py:data:`VMPREFIX` as fair game. This is needed to enforce sane
test executing environment. If you have domains named ``test-*``,
don't run the tests.
"""
import collections
from distutils import spawn
import functools
import multiprocessing
import logging
import os
import shutil
import subprocess
import sys
import tempfile
import traceback
import unittest
@ -129,6 +140,32 @@ class TestEmitter(qubes.events.Emitter):
super(TestEmitter, self).fire_event_pre(event, *args, **kwargs)
self.fired_events[(event, args, tuple(sorted(kwargs.items())))] += 1
def expectedFailureIfTemplate(templates):
"""
Decorator for marking specific test as expected to fail only for some
templates. Template name is compared as substring, so 'whonix' will
handle both 'whonix-ws' and 'whonix-gw'.
templates can be either a single string, or an iterable
"""
def decorator(func):
@functools.wraps(func)
def wrapper(self, *args, **kwargs):
template = self.template
if isinstance(templates, basestring):
should_expect_fail = template in templates
else:
should_expect_fail = any([template in x for x in templates])
if should_expect_fail:
try:
func(self, *args, **kwargs)
except Exception:
raise unittest.case._ExpectedFailure(sys.exc_info())
raise unittest.case._UnexpectedSuccess()
else:
# Call directly:
func(self, *args, **kwargs)
return wrapper
return decorator
class _AssertNotRaisesContext(object):
"""A context manager used to implement TestCase.assertNotRaises methods.
@ -537,12 +574,6 @@ class SystemTestsMixin(object):
@classmethod
def remove_test_vms(cls, xmlpath=XMLPATH, prefix=VMPREFIX):
'''Aggresively remove any domain that has name in testing namespace.
.. warning::
The test suite hereby claims any domain whose name starts with
:py:data:`VMPREFIX` as fair game. This is needed to enforce sane
test executing environment. If you have domains named ``test-*``,
don't run the tests.
'''
# first, remove them Qubes-way
@ -578,6 +609,30 @@ class SystemTestsMixin(object):
for vmname in vmnames:
cls._remove_vm_disk(vmname)
def qrexec_policy(self, service, source, destination, allow=True):
"""
Allow qrexec calls for duration of the test
:param service: service name
:param source: source VM name
:param destination: destination VM name
:return:
"""
def add_remove_rule(add=True):
with open('/etc/qubes-rpc/policy/{}'.format(service), 'r+') as policy:
policy_rules = policy.readlines()
rule = "{} {} {}\n".format(source, destination,
'allow' if allow else 'deny')
if add:
policy_rules.insert(0, rule)
else:
policy_rules.remove(rule)
policy.truncate(0)
policy.seek(0)
policy.write(''.join(policy_rules))
add_remove_rule(add=True)
self.addCleanup(add_remove_rule, add=False)
def wait_for_window(self, title, timeout=30, show=True):
"""
Wait for a window with a given title. Depending on show parameter,
@ -628,6 +683,76 @@ class SystemTestsMixin(object):
timeout -= 1
self.fail("Timeout while waiting for VM {} shutdown".format(vm.name))
def prepare_hvm_system_linux(self, vm, init_script, extra_files=None):
if not os.path.exists('/usr/lib/grub/i386-pc'):
self.skipTest('grub2 not installed')
if not spawn.find_executable('grub2-install'):
self.skipTest('grub2-tools not installed')
if not spawn.find_executable('dracut'):
self.skipTest('dracut not installed')
# create a single partition
p = subprocess.Popen(['sfdisk', '-q', '-L', vm.storage.root_img],
stdin=subprocess.PIPE,
stdout=open(os.devnull, 'w'),
stderr=subprocess.STDOUT)
p.communicate('2048,\n')
assert p.returncode == 0, 'sfdisk failed'
# TODO: check if root_img is really file, not already block device
p = subprocess.Popen(['sudo', 'losetup', '-f', '-P', '--show',
vm.storage.root_img], stdout=subprocess.PIPE)
(loopdev, _) = p.communicate()
loopdev = loopdev.strip()
looppart = loopdev + 'p1'
assert p.returncode == 0, 'losetup failed'
subprocess.check_call(['sudo', 'mkfs.ext2', '-q', '-F', looppart])
mountpoint = tempfile.mkdtemp()
subprocess.check_call(['sudo', 'mount', looppart, mountpoint])
try:
subprocess.check_call(['sudo', 'grub2-install',
'--target', 'i386-pc',
'--modules', 'part_msdos ext2',
'--boot-directory', mountpoint, loopdev],
stderr=open(os.devnull, 'w')
)
grub_cfg = '{}/grub2/grub.cfg'.format(mountpoint)
subprocess.check_call(
['sudo', 'chown', '-R', os.getlogin(), mountpoint])
with open(grub_cfg, 'w') as f:
f.write(
"set timeout=1\n"
"menuentry 'Default' {\n"
" linux /vmlinuz root=/dev/xvda1 "
"rd.driver.blacklist=bochs_drm "
"rd.driver.blacklist=uhci_hcd\n"
" initrd /initrd\n"
"}"
)
p = subprocess.Popen(['uname', '-r'], stdout=subprocess.PIPE)
(kernel_version, _) = p.communicate()
kernel_version = kernel_version.strip()
kernel = '/boot/vmlinuz-{}'.format(kernel_version)
shutil.copy(kernel, os.path.join(mountpoint, 'vmlinuz'))
init_path = os.path.join(mountpoint, 'init')
with open(init_path, 'w') as f:
f.write(init_script)
os.chmod(init_path, 0755)
dracut_args = [
'--kver', kernel_version,
'--include', init_path,
'/usr/lib/dracut/hooks/pre-pivot/initscript.sh',
'--no-hostonly', '--nolvmconf', '--nomdadmconf',
]
if extra_files:
dracut_args += ['--install', ' '.join(extra_files)]
subprocess.check_call(
['dracut'] + dracut_args + [os.path.join(mountpoint,
'initrd')],
stderr=open(os.devnull, 'w')
)
finally:
subprocess.check_call(['sudo', 'umount', mountpoint])
shutil.rmtree(mountpoint)
subprocess.check_call(['sudo', 'losetup', '-d', loopdev])
# noinspection PyAttributeOutsideInit
class BackupTestsMixin(SystemTestsMixin):

View File

@ -187,6 +187,8 @@ class TC_10_BackupVMMixin(qubes.tests.BackupTestsMixin):
vms = self.create_backup_vms()
self.backupvm.start()
retcode = self.backupvm.run(
# Debian 7 has too old losetup to handle loop-control device
"mknod /dev/loop0 b 7 0;"
"truncate -s 50M /home/user/backup.img && "
"mkfs.ext4 -F /home/user/backup.img && "
"mkdir /home/user/backup && "

View File

@ -40,6 +40,7 @@ class TC_00_Dom0UpgradeMixin(qubes.tests.SystemTestsMixin):
pkg_name = 'qubes-test-pkg'
dom0_update_common_opts = ['--disablerepo=*', '--enablerepo=test',
'--setopt=test.copy_local=1']
update_flag_path = '/var/lib/qubes/updates/dom0-updates-available'
@classmethod
def generate_key(cls, keydir):
@ -181,10 +182,18 @@ Test package
"test".format(retcode))
def test_000_update(self):
"""Dom0 update tests
Check if package update is:
- detected
- installed
- "updates pending" flag is cleared
"""
filename = self.create_pkg(self.tmpdir, self.pkg_name, '1.0')
subprocess.check_call(['sudo', 'rpm', '-i', filename])
filename = self.create_pkg(self.tmpdir, self.pkg_name, '2.0')
self.send_pkg(filename)
open(self.update_flag_path, 'a').close()
logpath = os.path.join(self.tmpdir, 'dom0-update-output.txt')
try:
@ -204,6 +213,67 @@ Test package
self.pkg_name)], stdout=open(os.devnull, 'w'))
self.assertEqual(retcode, 0, 'Package {}-2.0 not installed after '
'update'.format(self.pkg_name))
self.assertFalse(os.path.exists(self.update_flag_path),
"'updates pending' flag not cleared")
def test_005_update_flag_clear(self):
"""Check if 'updates pending' flag is creared"""
# create any pkg (but not install it) to initialize repo in the VM
filename = self.create_pkg(self.tmpdir, self.pkg_name, '1.0')
self.send_pkg(filename)
open(self.update_flag_path, 'a').close()
logpath = os.path.join(self.tmpdir, 'dom0-update-output.txt')
try:
subprocess.check_call(['sudo', 'qubes-dom0-update', '-y'] +
self.dom0_update_common_opts,
stdout=open(logpath, 'w'),
stderr=subprocess.STDOUT)
except subprocess.CalledProcessError:
self.fail("qubes-dom0-update failed: " + open(
logpath).read())
with open(logpath) as f:
dom0_update_output = f.read()
self.assertFalse('Errno' in dom0_update_output or
'Couldn\'t' in dom0_update_output,
"qubes-dom0-update reported an error: {}".
format(dom0_update_output))
self.assertFalse(os.path.exists(self.update_flag_path),
"'updates pending' flag not cleared")
def test_006_update_flag_clear(self):
"""Check if 'updates pending' flag is creared, using --clean"""
# create any pkg (but not install it) to initialize repo in the VM
filename = self.create_pkg(self.tmpdir, self.pkg_name, '1.0')
self.send_pkg(filename)
open(self.update_flag_path, 'a').close()
# remove also repodata to test #1685
shutil.rmtree('/var/lib/qubes/updates/repodata')
logpath = os.path.join(self.tmpdir, 'dom0-update-output.txt')
try:
subprocess.check_call(['sudo', 'qubes-dom0-update', '-y',
'--clean'] +
self.dom0_update_common_opts,
stdout=open(logpath, 'w'),
stderr=subprocess.STDOUT)
except subprocess.CalledProcessError:
self.fail("qubes-dom0-update failed: " + open(
logpath).read())
with open(logpath) as f:
dom0_update_output = f.read()
self.assertFalse('Errno' in dom0_update_output or
'Couldn\'t' in dom0_update_output,
"qubes-dom0-update reported an error: {}".
format(dom0_update_output))
self.assertFalse(os.path.exists(self.update_flag_path),
"'updates pending' flag not cleared")
def test_010_instal(self):
filename = self.create_pkg(self.tmpdir, self.pkg_name, '1.0')

View File

@ -126,6 +126,7 @@ class VmNetworkingMixin(qubes.tests.SystemTestsMixin):
"Ping by IP from AppVM failed")
@qubes.tests.expectedFailureIfTemplate('debian-7')
@unittest.skipUnless(spawn.find_executable('xdotool'),
"xdotool not installed")
def test_020_simple_proxyvm_nm(self):

View File

@ -176,6 +176,7 @@ class QMemmanReqHandler(SocketServer.BaseRequestHandler):
self.log = logging.getLogger('qmemman.daemon.reqhandler')
got_lock = False
try:
# self.request is the TCP socket connected to the client
while True:
self.data = self.request.recv(1024).strip()
@ -185,8 +186,6 @@ class QMemmanReqHandler(SocketServer.BaseRequestHandler):
if got_lock:
global force_refresh_domain_list
force_refresh_domain_list = True
global_lock.release()
self.log.debug('global_lock released')
return
# XXX something is wrong here: return without release?
@ -205,8 +204,13 @@ class QMemmanReqHandler(SocketServer.BaseRequestHandler):
resp = "FAIL\n"
self.log.debug('resp={!r}'.format(resp))
self.request.send(resp)
# XXX no release of lock?
except BaseException as e:
self.log.exception(
"exception while handling request: {!r}".format(e))
finally:
if got_lock:
global_lock.release()
self.log.debug('global_lock released')
parser = qubes.tools.QubesArgumentParser(want_app=False)

View File

@ -86,6 +86,8 @@ def main(args=None):
if args.vm is qubes.tools.VM_ALL and args.passio:
parser.error('--all and --passio are mutually exclusive')
if args.localcmd and not passio.passio:
parser.error('--localcmd have no effect without --pass-io')
if args.color_output and not args.filter_esc:
parser.error('--color-output must be used with --filter-escape-chars')

View File

@ -511,6 +511,13 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
subprocess.check_call(['sudo', 'systemctl', '-q', 'disable',
'qubes-vm@{}.service'.format(oldvalue)])
try:
self.app.domains[newvalue]
except KeyError:
pass
else:
raise qubes.exc.QubesValueError(
'VM named {!r} already exists'.format(newvalue))
@qubes.events.handler('property-set:name')
def on_property_set_name(self, event, name, new_name, old_name=None):
@ -896,18 +903,28 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
raise TypeError(
'input, passio_popen and localcmd cannot be used together')
if not wait and (localcmd or input):
raise ValueError("Cannot use wait=False with input or "
"localcmd specified")
if passio_stderr and not passio_popen:
raise TypeError('passio_stderr can be used only with passio_popen')
if input:
localcmd = 'printf %s {}'.format(pipes.quote(input))
# Internally use passio_popen, but do not return POpen object to
# the user - use internally for p.communicate()
passio_popen = True
source = 'dom0' if source is None else self.app.domains[source].name
return self.run('QUBESRPC {} {}'.format(service, source),
p = self.run('QUBESRPC {} {}'.format(service, source),
localcmd=localcmd, passio_popen=passio_popen, user=user, wait=wait,
gui=gui, passio_stderr=passio_stderr)
if input:
p.communicate(input)
return p.returncode
else:
return p
def request_memory(self, mem_required=None):
# overhead of per-qube/per-vcpu Xen structures,

View File

@ -18,7 +18,7 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
VERSION=2.4
VERSION=2.5
COPY2VM="dom0"
SUPPORT_FILES=0
@ -103,7 +103,7 @@ XL_VTX=`cat $TEMP_DIR/xl-info |grep xen_caps | grep hvm`
XL_VTD=`cat $TEMP_DIR/xl-info |grep virt_caps |grep hvm_directio`
PCRS=`find /sys/devices/ -name pcrs`
FILENAME="Qubes-HCL-${BRAND// /_}-${PRODUCT// /_}-$DATE"
FILENAME="Qubes-HCL-${BRAND//+([^[:alnum:]])/_}-${PRODUCT//+([^[:alnum:]])/_}-$DATE"
if [[ $XL_VTX ]]
then

View File

@ -30,6 +30,7 @@ import qubes.backup
import os
import sys
import getpass
from locale import getpreferredencoding
def print_progress(progress):
print >> sys.stderr, "\r-> Backing up files: {0}%...".format (progress),
@ -51,6 +52,10 @@ def main():
parser.add_option ("--no-encrypt", action="store_true",
dest="no_encrypt", default=False,
help="Skip encryption even if sending the backup to VM")
parser.add_option ("-p", "--passphrase-file", action="store",
dest="pass_file", default=None,
help="File containing the pass phrase to use, or '-' "
"to read it from stdin")
parser.add_option ("-E", "--enc-algo", action="store",
dest="crypto_algorithm", default=None,
help="Specify non-default encryption algorithm. For "
@ -156,24 +161,26 @@ def main():
if not options.encrypt:
print >>sys.stderr, "WARNING: encryption will not be used"
prompt = raw_input ("Do you want to proceed? [y/N] ")
if not (prompt == "y" or prompt == "Y"):
if options.pass_file is not None:
f = open(options.pass_file) if options.pass_file != "-" else sys.stdin
passphrase = f.readline().rstrip()
if f is not sys.stdin:
f.close()
else:
if raw_input("Do you want to proceed? [y/N] ").upper() != "Y":
exit(0)
if options.encrypt:
passphrase = getpass.getpass("Please enter the pass phrase that will "
"be used to encrypt and verify the "
"backup: ")
else:
passphrase = getpass.getpass("Please enter the pass phrase that will "
"be used to verify the backup: ")
s = ("Please enter the pass phrase that will be used to {}verify "
"the backup: ").format('encrypt and ' if options.encrypt else '')
passphrase = getpass.getpass(s)
passphrase2 = getpass.getpass("Enter again for verification: ")
if passphrase != passphrase2:
if getpass.getpass("Enter again for verification: ") != passphrase:
print >>sys.stderr, "ERROR: Password mismatch"
exit(1)
passphrase = passphrase.decode(sys.stdin.encoding)
encoding = sys.stdin.encoding or getpreferredencoding()
passphrase = passphrase.decode(encoding)
kwargs = {}
if options.hmac_algorithm:

View File

@ -31,6 +31,7 @@ from qubes.backup import backup_restore_do
import qubes.backup
import sys
from optparse import OptionParser
from locale import getpreferredencoding
import os
import sys
@ -81,6 +82,10 @@ def main():
parser.add_option ("-e", "--encrypted", action="store_true", dest="decrypt", default=False,
help="The backup is encrypted")
parser.add_option ("-p", "--passphrase-file", action="store",
dest="pass_file", default=None,
help="File containing the pass phrase to use, or '-' to read it from stdin")
parser.add_option ("-z", "--compressed", action="store_true", dest="compressed", default=False,
help="The backup is compressed")
@ -110,6 +115,8 @@ def main():
restore_options['use-default-netvm'] = True
if options.replace_template:
restore_options['replace-template'] = options.replace_template
if options.rename_conflicting:
restore_options['rename-conflicting'] = True
if not options.dom0_home:
restore_options['dom0-home'] = False
if options.ignore_username_mismatch:
@ -128,8 +135,16 @@ def main():
print >>sys.stderr, "ERROR: VM {0} does not exist".format(options.appvm)
exit(1)
passphrase = getpass.getpass("Please enter the pass phrase that will be used to decrypt/verify the backup: ")
passphrase = passphrase.decode(sys.stdin.encoding)
if options.pass_file is not None:
f = open(options.pass_file) if options.pass_file != "-" else sys.stdin
passphrase = f.readline().rstrip()
if f is not sys.stdin:
f.close()
else:
passphrase = getpass.getpass("Please enter the pass phrase to decrypt/verify the backup: ")
encoding = sys.stdin.encoding or getpreferredencoding()
passphrase = passphrase.decode(encoding)
print >> sys.stderr, "Checking backup content..."
@ -244,11 +259,10 @@ def main():
print >> sys.stderr, "Continuing as directed"
print >> sys.stderr, "While restoring user homedir, existing files/dirs will be backed up in 'home-pre-restore-<current-time>' dir"
prompt = raw_input ("Do you want to proceed? [y/N] ")
if not (prompt == "y" or prompt == "Y"):
if options.pass_file is None:
if raw_input("Do you want to proceed? [y/N] ").upper() != "Y":
exit(0)
try:
backup_restore_do(restore_info,
host_collection=host_collection,

View File

@ -22,6 +22,7 @@
#
import fcntl
from optparse import OptionParser
from qubes.qubes import QubesVmCollection
import os.path
import os
@ -41,9 +42,11 @@ def get_netvm_of_vm(vm):
return netvm
def main():
verbose = False
if len(sys.argv) > 1 and sys.argv[1] in [ '--verbose', '-v' ]:
verbose = True
parser = OptionParser()
parser.add_option ("-v", "--verbose", action="store_true", dest="verbose", default=False)
parser.add_option ("-f", "--force", action="store_true", dest="force", default=False)
(options, args) = parser.parse_args ()
lockfile_name = "/var/run/qubes/qvm-sync-clock.lock"
if os.path.exists(lockfile_name):
@ -74,23 +77,27 @@ def main():
sys.exit(1)
net_vm = get_netvm_of_vm(clock_vm)
if verbose:
if options.verbose:
print >> sys.stderr, '--> Waiting for network for ClockVM.'
# Ignore retcode, try even if nm-online failed - user can setup network manually
# on-online has timeout 30sec by default
net_vm.run('nm-online -x', verbose=verbose, gui=False, wait=True,
net_vm.run('nm-online -x', verbose=options.verbose, gui=False, wait=True,
ignore_stderr=True)
# Sync clock
if clock_vm.run('QUBESRPC qubes.SyncNtpClock dom0', user="root",
verbose=verbose, gui=False, wait=True, ignore_stderr=True) \
verbose=options.verbose, gui=False, wait=True, ignore_stderr=True) \
!= 0:
print >> sys.stderr, 'Time sync failed, aborting!'
if options.force:
print >> sys.stderr, 'Time sync failed! - Syncing with dom0 ' \
'anyway as requested'
else:
print >> sys.stderr, 'Time sync failed! - Exiting'
sys.exit(1)
else:
# Use the date format based on RFC2822 to avoid localisation issues
p = clock_vm.run('date -u -Iseconds', verbose=verbose,
p = clock_vm.run('date -u -Iseconds', verbose=options.verbose,
gui=False, passio_popen=True, ignore_stderr=True)
date_out = p.stdout.read(100)
date_out = date_out.strip()
@ -99,18 +106,18 @@ def main():
sys.exit(1)
# Sync dom0 time
if verbose:
if options.verbose:
print >> sys.stderr, '--> Syncing dom0 clock.'
subprocess.check_call(['sudo', 'date', '-u', '-Iseconds', '-s', date_out],
stdout=None if verbose else open(os.devnull, 'w'))
stdout=None if options.verbose else open(os.devnull, 'w'))
subprocess.check_call(['sudo', 'hwclock', '--systohc'],
stdout=None if verbose else open(os.devnull, 'w'))
stdout=None if options.verbose else open(os.devnull, 'w'))
# Sync other VMs clock
for vm in qvm_collection.values():
if vm.is_running() and vm.qid != 0 and vm.qid != clock_vm.qid:
if verbose:
if options.verbose:
print >> sys.stderr, '--> Syncing \'%s\' clock.' % vm.name
try:
vm.run_service("qubes.SetDateTime", user="root",

View File

@ -27,6 +27,9 @@
%{!?version: %define version %(cat version)}
# debug_package hack should be removed when BuildArch:noarch is enabled below
%define debug_package %{nil}
%define _dracutmoddir /usr/lib/dracut/modules.d
%if %{fedora} < 17
%define _dracutmoddir /usr/share/dracut/modules.d
@ -48,6 +51,9 @@ URL: http://www.qubes-os.org
# /bin -> usr/bin symlink). python*.rpm provides only /usr/bin/python.
AutoReq: no
# FIXME: Enable this and disable debug_package
#BuildArch: noarch
BuildRequires: ImageMagick
BuildRequires: systemd-units
@ -61,7 +67,7 @@ Requires(preun): systemd-units
Requires(postun): systemd-units
Requires: python, pciutils, python-inotify, python-daemon
Requires: python-setuptools
Requires: qubes-core-dom0-linux >= 2.0.24
Requires: qubes-core-dom0-linux >= 3.1.8
Requires: qubes-db-dom0
Requires: python-lxml
# TODO: R: qubes-gui-dom0 >= 2.1.11
@ -333,8 +339,10 @@ fi
%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/qubes.NotifyTools
%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/qubes.NotifyUpdates
%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/qubes.VMShell
%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/qubes.GetRandomizedTime
/etc/qubes-rpc/qubes.NotifyTools
/etc/qubes-rpc/qubes.NotifyUpdates
/etc/qubes-rpc/qubes.GetRandomizedTime
%attr(2770,root,qubes) %dir /var/log/qubes
%attr(0770,root,qubes) %dir /var/run/qubes
/etc/xdg/autostart/qubes-guid.desktop

View File

@ -31,3 +31,7 @@ endif
cp storage.py[co] $(DESTDIR)$(PYTHON_TESTSPATH)
cp storage_xen.py $(DESTDIR)$(PYTHON_TESTSPATH)
cp storage_xen.py[co] $(DESTDIR)$(PYTHON_TESTSPATH)
cp hardware.py $(DESTDIR)$(PYTHON_TESTSPATH)
cp hardware.py[co] $(DESTDIR)$(PYTHON_TESTSPATH)
cp extra.py $(DESTDIR)$(PYTHON_TESTSPATH)
cp extra.py[co] $(DESTDIR)$(PYTHON_TESTSPATH)

109
tests/extra.py Normal file
View File

@ -0,0 +1,109 @@
#!/usr/bin/python2 -O
# vim: fileencoding=utf-8
#
# The Qubes OS Project, https://www.qubes-os.org/
#
# Copyright (C) 2016
# Marek Marczykowski-Górecki <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 pkg_resources
import qubes.tests
import qubes.qubes
class ExtraTestMixin(qubes.tests.SystemTestsMixin):
template = None
def setUp(self):
super(ExtraTestMixin, self).setUp()
self.qc.unlock_db()
def create_vms(self, names):
"""
Create AppVMs for the duration of the test. Will be automatically
removed after completing the test.
:param names: list of VM names to create (each of them will be
prefixed with some test specific string)
:return: list of created VM objects
"""
self.qc.lock_db_for_writing()
self.qc.load()
if self.template:
template = self.qc.get_vm_by_name(self.template)
else:
template = self.qc.get_default_template()
for vmname in names:
vm = self.qc.add_new_vm("QubesAppVm",
name=self.make_vm_name(vmname),
template=template)
vm.create_on_disk(verbose=False)
self.save_and_reload_db()
self.qc.unlock_db()
# get objects after reload
vms = []
for vmname in names:
vms.append(self.qc.get_vm_by_name(self.make_vm_name(vmname)))
return vms
def enable_network(self):
"""
Enable access to the network. Must be called before creating VMs.
"""
# nothing to do in core2
pass
def load_tests(loader, tests, pattern):
for entry in pkg_resources.iter_entry_points('qubes.tests.extra'):
for test_case in entry():
tests.addTests(loader.loadTestsFromTestCase(
type(
entry.name + '_' + test_case.__name__,
(test_case, ExtraTestMixin, qubes.tests.QubesTestCase),
{}
)
))
try:
qc = qubes.qubes.QubesVmCollection()
qc.lock_db_for_reading()
qc.load()
qc.unlock_db()
templates = [vm.name for vm in qc.values() if
isinstance(vm, qubes.qubes.QubesTemplateVm)]
except OSError:
templates = []
for entry in pkg_resources.iter_entry_points(
'qubes.tests.extra.for_template'):
for test_case in entry.load()():
for template in templates:
tests.addTests(loader.loadTestsFromTestCase(
type(
'{}_{}_{}'.format(
entry.name, test_case.__name__, template),
(test_case, ExtraTestMixin,
qubes.tests.QubesTestCase),
{'template': template}
)
))
return tests

75
tests/hardware.py Normal file
View File

@ -0,0 +1,75 @@
#!/usr/bin/python2
# -*- coding: utf-8 -*-
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2016 Marek Marczykowski-Górecki
# <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 qubes.tests
import time
import subprocess
from unittest import expectedFailure
class TC_00_HVM(qubes.tests.SystemTestsMixin, qubes.tests.QubesTestCase):
def setUp(self):
super(TC_00_HVM, self).setUp()
self.vm = self.qc.add_new_vm("QubesHVm",
name=self.make_vm_name('vm1'))
self.vm.create_on_disk(verbose=False)
@expectedFailure
def test_000_pci_passthrough_presence(self):
pcidev = os.environ.get('QUBES_TEST_PCIDEV', None)
if pcidev is None:
self.skipTest('Specify PCI device with QUBES_TEST_PCIDEV '
'environment variable')
self.vm.pcidevs = [pcidev]
self.vm.pci_strictreset = False
self.qc.save()
self.qc.unlock_db()
init_script = (
"#!/bin/sh\n"
"set -e\n"
"lspci -n > /dev/xvdb\n"
"poweroff\n"
)
self.prepare_hvm_system_linux(self.vm, init_script,
['/usr/sbin/lspci'])
self.vm.start()
timeout = 60
while timeout > 0:
if not self.vm.is_running():
break
time.sleep(1)
timeout -= 1
if self.vm.is_running():
self.fail("Timeout while waiting for VM shutdown")
with open(self.vm.storage.private_img, 'r') as f:
lspci_vm = f.read(512).strip('\0')
p = subprocess.Popen(['lspci', '-ns', pcidev], stdout=subprocess.PIPE)
(lspci_host, _) = p.communicate()
# strip BDF, as it is different in VM
pcidev_desc = ' '.join(lspci_host.strip().split(' ')[1:])
self.assertIn(pcidev_desc, lspci_vm)

View File

@ -33,6 +33,7 @@ import time
from qubes.qubes import QubesVmCollection, defaults, QubesException
import qubes.tests
import re
TEST_DATA = "0123456789" * 1024
@ -528,16 +529,123 @@ class TC_00_AppVMMixin(qubes.tests.SystemTestsMixin):
self.fail("Timeout, probably deadlock")
self.assertEqual(result.value, 0, "Service call failed")
@unittest.skipUnless(spawn.find_executable('xdotool'),
"xdotool not installed")
def test_080_qrexec_service_argument_allow_default(self):
"""Qrexec service call with argument"""
self.testvm1.start()
self.testvm2.start()
p = self.testvm2.run("cat > /etc/qubes-rpc/test.Argument", user="root",
passio_popen=True)
p.communicate("/bin/echo $1")
with open("/etc/qubes-rpc/policy/test.Argument", "w") as policy:
policy.write("%s %s allow" % (self.testvm1.name, self.testvm2.name))
self.addCleanup(os.unlink, "/etc/qubes-rpc/policy/test.Argument")
p = self.testvm1.run("/usr/lib/qubes/qrexec-client-vm {} "
"test.Argument+argument".format(self.testvm2.name),
passio_popen=True)
(stdout, stderr) = p.communicate()
self.assertEqual(stdout, "argument\n")
def test_081_qrexec_service_argument_allow_specific(self):
"""Qrexec service call with argument - allow only specific value"""
self.testvm1.start()
self.testvm2.start()
p = self.testvm2.run("cat > /etc/qubes-rpc/test.Argument", user="root",
passio_popen=True)
p.communicate("/bin/echo $1")
with open("/etc/qubes-rpc/policy/test.Argument", "w") as policy:
policy.write("$anyvm $anyvm deny")
self.addCleanup(os.unlink, "/etc/qubes-rpc/policy/test.Argument")
with open("/etc/qubes-rpc/policy/test.Argument+argument", "w") as \
policy:
policy.write("%s %s allow" % (self.testvm1.name, self.testvm2.name))
self.addCleanup(os.unlink,
"/etc/qubes-rpc/policy/test.Argument+argument")
p = self.testvm1.run("/usr/lib/qubes/qrexec-client-vm {} "
"test.Argument+argument".format(self.testvm2.name),
passio_popen=True)
(stdout, stderr) = p.communicate()
self.assertEqual(stdout, "argument\n")
def test_082_qrexec_service_argument_deny_specific(self):
"""Qrexec service call with argument - deny specific value"""
self.testvm1.start()
self.testvm2.start()
p = self.testvm2.run("cat > /etc/qubes-rpc/test.Argument", user="root",
passio_popen=True)
p.communicate("/bin/echo $1")
with open("/etc/qubes-rpc/policy/test.Argument", "w") as policy:
policy.write("$anyvm $anyvm allow")
self.addCleanup(os.unlink, "/etc/qubes-rpc/policy/test.Argument")
with open("/etc/qubes-rpc/policy/test.Argument+argument", "w") as \
policy:
policy.write("%s %s deny" % (self.testvm1.name, self.testvm2.name))
self.addCleanup(os.unlink,
"/etc/qubes-rpc/policy/test.Argument+argument")
p = self.testvm1.run("/usr/lib/qubes/qrexec-client-vm {} "
"test.Argument+argument".format(self.testvm2.name),
passio_popen=True)
(stdout, stderr) = p.communicate()
self.assertEqual(stdout, "")
self.assertEqual(p.returncode, 1, "Service request should be denied")
def test_083_qrexec_service_argument_specific_implementation(self):
"""Qrexec service call with argument - argument specific
implementatation"""
self.testvm1.start()
self.testvm2.start()
p = self.testvm2.run("cat > /etc/qubes-rpc/test.Argument", user="root",
passio_popen=True)
p.communicate("/bin/echo $1")
p = self.testvm2.run("cat > /etc/qubes-rpc/test.Argument+argument",
user="root", passio_popen=True)
p.communicate("/bin/echo specific: $1")
with open("/etc/qubes-rpc/policy/test.Argument", "w") as policy:
policy.write("%s %s allow" % (self.testvm1.name, self.testvm2.name))
self.addCleanup(os.unlink, "/etc/qubes-rpc/policy/test.Argument")
p = self.testvm1.run("/usr/lib/qubes/qrexec-client-vm {} "
"test.Argument+argument".format(self.testvm2.name),
passio_popen=True)
(stdout, stderr) = p.communicate()
self.assertEqual(stdout, "specific: argument\n")
def test_084_qrexec_service_argument_extra_env(self):
"""Qrexec service call with argument - extra env variables"""
self.testvm1.start()
self.testvm2.start()
p = self.testvm2.run("cat > /etc/qubes-rpc/test.Argument", user="root",
passio_popen=True)
p.communicate("/bin/echo $QREXEC_SERVICE_FULL_NAME "
"$QREXEC_SERVICE_ARGUMENT")
with open("/etc/qubes-rpc/policy/test.Argument", "w") as policy:
policy.write("%s %s allow" % (self.testvm1.name, self.testvm2.name))
self.addCleanup(os.unlink, "/etc/qubes-rpc/policy/test.Argument")
p = self.testvm1.run("/usr/lib/qubes/qrexec-client-vm {} "
"test.Argument+argument".format(self.testvm2.name),
passio_popen=True)
(stdout, stderr) = p.communicate()
self.assertEqual(stdout, "test.Argument+argument argument\n")
def test_100_qrexec_filecopy(self):
self.testvm1.start()
self.testvm2.start()
self.qrexec_policy('qubes.Filecopy', self.testvm1.name,
self.testvm2.name)
p = self.testvm1.run("qvm-copy-to-vm %s /etc/passwd" %
self.testvm2.name, passio_popen=True,
passio_stderr=True)
# Confirm transfer
self.enter_keys_in_window('Question', ['y'])
p.wait()
self.assertEqual(p.returncode, 0, "qvm-copy-to-vm failed: %s" %
p.stderr.read())
@ -547,15 +655,34 @@ class TC_00_AppVMMixin(qubes.tests.SystemTestsMixin):
wait=True)
self.assertEqual(retcode, 0, "file differs")
@unittest.skipUnless(spawn.find_executable('xdotool'),
"xdotool not installed")
def test_105_qrexec_filemove(self):
self.testvm1.start()
self.testvm2.start()
self.qrexec_policy('qubes.Filecopy', self.testvm1.name,
self.testvm2.name)
retcode = self.testvm1.run("cp /etc/passwd passwd", wait=True)
assert retcode == 0, "Failed to prepare source file"
p = self.testvm1.run("qvm-move-to-vm %s passwd" %
self.testvm2.name, passio_popen=True,
passio_stderr=True)
p.wait()
self.assertEqual(p.returncode, 0, "qvm-move-to-vm failed: %s" %
p.stderr.read())
retcode = self.testvm2.run("diff /etc/passwd "
"/home/user/QubesIncoming/{}/passwd".format(
self.testvm1.name),
wait=True)
self.assertEqual(retcode, 0, "file differs")
retcode = self.testvm1.run("test -f passwd", wait=True)
self.assertEqual(retcode, 1, "source file not removed")
def test_101_qrexec_filecopy_with_autostart(self):
self.testvm1.start()
self.qrexec_policy('qubes.Filecopy', self.testvm1.name,
self.testvm2.name)
p = self.testvm1.run("qvm-copy-to-vm %s /etc/passwd" %
self.testvm2.name, passio_popen=True,
passio_stderr=True)
# Confirm transfer
self.enter_keys_in_window('Question', ['y'])
p.wait()
self.assertEqual(p.returncode, 0, "qvm-copy-to-vm failed: %s" %
p.stderr.read())
@ -570,15 +697,13 @@ class TC_00_AppVMMixin(qubes.tests.SystemTestsMixin):
wait=True)
self.assertEqual(retcode, 0, "file differs")
@unittest.skipUnless(spawn.find_executable('xdotool'),
"xdotool not installed")
def test_110_qrexec_filecopy_deny(self):
self.testvm1.start()
self.testvm2.start()
self.qrexec_policy('qubes.Filecopy', self.testvm1.name,
self.testvm2.name, allow=False)
p = self.testvm1.run("qvm-copy-to-vm %s /etc/passwd" %
self.testvm2.name, passio_popen=True)
# Deny transfer
self.enter_keys_in_window('Question', ['n'])
p.wait()
self.assertNotEqual(p.returncode, 0, "qvm-copy-to-vm unexpectedly "
"succeeded")
@ -590,15 +715,13 @@ class TC_00_AppVMMixin(qubes.tests.SystemTestsMixin):
@unittest.skip("Xen gntalloc driver crashes when page is mapped in the "
"same domain")
@unittest.skipUnless(spawn.find_executable('xdotool'),
"xdotool not installed")
def test_120_qrexec_filecopy_self(self):
self.testvm1.start()
self.qrexec_policy('qubes.Filecopy', self.testvm1.name,
self.testvm1.name)
p = self.testvm1.run("qvm-copy-to-vm %s /etc/passwd" %
self.testvm1.name, passio_popen=True,
passio_stderr=True)
# Confirm transfer
self.enter_keys_in_window('Question', ['y'])
p.wait()
self.assertEqual(p.returncode, 0, "qvm-copy-to-vm failed: %s" %
p.stderr.read())
@ -613,6 +736,8 @@ class TC_00_AppVMMixin(qubes.tests.SystemTestsMixin):
def test_130_qrexec_filemove_disk_full(self):
self.testvm1.start()
self.testvm2.start()
self.qrexec_policy('qubes.Filecopy', self.testvm1.name,
self.testvm2.name)
# Prepare test file
prepare_cmd = ("yes teststring | dd of=testfile bs=1M "
"count=50 iflag=fullblock")
@ -633,8 +758,6 @@ class TC_00_AppVMMixin(qubes.tests.SystemTestsMixin):
p = self.testvm1.run("qvm-move-to-vm %s testfile" %
self.testvm2.name, passio_popen=True,
passio_stderr=True)
# Confirm transfer
self.enter_keys_in_window('Question', ['y'])
# Close GUI error message
self.enter_keys_in_window('Error', ['Return'])
p.wait()
@ -689,6 +812,10 @@ class TC_00_AppVMMixin(qubes.tests.SystemTestsMixin):
self.assertEquals(retcode, 0,
"qvm-sync-clock failed with code {}".
format(retcode))
# qvm-sync-clock is asynchronous - it spawns qubes.SetDateTime
# service, send it timestamp value and exists without waiting for
# actual time set
time.sleep(1)
(vm_time, _) = self.testvm1.run("date -u +%s",
passio_popen=True).communicate()
self.assertAlmostEquals(int(vm_time), int(start_time), delta=30)
@ -733,7 +860,6 @@ class TC_00_AppVMMixin(qubes.tests.SystemTestsMixin):
# some safety margin for FS metadata
self.assertGreater(int(new_size.strip()), 5.8*1024**2)
class TC_05_StandaloneVM(qubes.tests.SystemTestsMixin, qubes.tests.QubesTestCase):
def test_000_create_start(self):
testvm1 = self.qc.add_new_vm("QubesAppVm",
@ -1204,6 +1330,298 @@ class TC_40_PVGrub(qubes.tests.SystemTestsMixin):
(actual_kver, _) = p.communicate()
self.assertEquals(actual_kver.strip(), kver)
@unittest.skipUnless(
spawn.find_executable('xprop') and
spawn.find_executable('xdotool') and
spawn.find_executable('wmctrl'),
"xprop or xdotool or wmctrl not installed")
class TC_50_MimeHandlers(qubes.tests.SystemTestsMixin):
@classmethod
def setUpClass(cls):
if cls.template == 'whonix-gw' or 'minimal' in cls.template:
raise unittest.SkipTest(
'Template {} not supported by this test'.format(cls.template))
if cls.template == 'whonix-ws':
# TODO remove when Whonix-based DispVMs will work (Whonix 13?)
raise unittest.SkipTest(
'Template {} not supported by this test'.format(cls.template))
qc = QubesVmCollection()
cls._kill_test_vms(qc, prefix=qubes.tests.CLSVMPREFIX)
qc.lock_db_for_writing()
qc.load()
cls._remove_test_vms(qc, qubes.qubes.vmm.libvirt_conn,
prefix=qubes.tests.CLSVMPREFIX)
cls.source_vmname = cls.make_vm_name('source', True)
source_vm = qc.add_new_vm("QubesAppVm",
template=qc.get_vm_by_name(cls.template),
name=cls.source_vmname)
source_vm.create_on_disk(verbose=False)
cls.target_vmname = cls.make_vm_name('target', True)
target_vm = qc.add_new_vm("QubesAppVm",
template=qc.get_vm_by_name(cls.template),
name=cls.target_vmname)
target_vm.create_on_disk(verbose=False)
qc.save()
qc.unlock_db()
source_vm.start()
target_vm.start()
# make sure that DispVMs will be started of the same template
retcode = subprocess.call(['/usr/bin/qvm-create-default-dvm',
cls.template],
stderr=open(os.devnull, 'w'))
assert retcode == 0, "Error preparing DispVM"
def setUp(self):
super(TC_50_MimeHandlers, self).setUp()
self.source_vm = self.qc.get_vm_by_name(self.source_vmname)
self.target_vm = self.qc.get_vm_by_name(self.target_vmname)
def get_window_class(self, winid, dispvm=False):
(vm_winid, _) = subprocess.Popen(
['xprop', '-id', winid, '_QUBES_VMWINDOWID'],
stdout=subprocess.PIPE
).communicate()
vm_winid = vm_winid.split("#")[1].strip('\n" ')
if dispvm:
(vmname, _) = subprocess.Popen(
['xprop', '-id', winid, '_QUBES_VMNAME'],
stdout=subprocess.PIPE
).communicate()
vmname = vmname.split("=")[1].strip('\n" ')
window_class = None
while window_class is None:
# XXX to use self.qc.get_vm_by_name would require reloading
# qubes.xml, so use qvm-run instead
xprop = subprocess.Popen(
['qvm-run', '-p', vmname, 'xprop -id {} WM_CLASS'.format(
vm_winid)], stdout=subprocess.PIPE)
(window_class, _) = xprop.communicate()
if xprop.returncode != 0:
self.skipTest("xprop failed, not installed?")
if 'not found' in window_class:
# WM_CLASS not set yet, wait a little
time.sleep(0.1)
window_class = None
else:
window_class = None
while window_class is None:
xprop = self.target_vm.run(
'xprop -id {} WM_CLASS'.format(vm_winid),
passio_popen=True)
(window_class, _) = xprop.communicate()
if xprop.returncode != 0:
self.skipTest("xprop failed, not installed?")
if 'not found' in window_class:
# WM_CLASS not set yet, wait a little
time.sleep(0.1)
window_class = None
# output: WM_CLASS(STRING) = "gnome-terminal-server", "Gnome-terminal"
try:
window_class = window_class.split("=")[1].split(",")[0].strip('\n" ')
except IndexError:
raise Exception(
"Unexpected output from xprop: '{}'".format(window_class))
return window_class
def open_file_and_check_viewer(self, filename, expected_app_titles,
expected_app_classes, dispvm=False):
self.qc.unlock_db()
if dispvm:
p = self.source_vm.run("qvm-open-in-dvm {}".format(filename),
passio_popen=True)
vmpattern = "disp*"
else:
self.qrexec_policy('qubes.Filecopy', self.source_vm.name,
self.target_vmname)
p = self.source_vm.run("qvm-open-in-vm {} {}".format(
self.target_vmname, filename), passio_popen=True)
vmpattern = self.target_vmname
wait_count = 0
winid = None
window_title = None
while True:
search = subprocess.Popen(['xdotool', 'search',
'--onlyvisible', '--class', vmpattern],
stdout=subprocess.PIPE,
stderr=open(os.path.devnull, 'w'))
retcode = search.wait()
if retcode == 0:
winid = search.stdout.read().strip()
# get window title
(window_title, _) = subprocess.Popen(
['xdotool', 'getwindowname', winid], stdout=subprocess.PIPE). \
communicate()
window_title = window_title.strip()
# ignore LibreOffice splash screen and window with no title
# set yet
if window_title and not window_title.startswith("LibreOffice")\
and not window_title == 'VMapp command':
break
wait_count += 1
if wait_count > 100:
self.fail("Timeout while waiting for editor window")
time.sleep(0.3)
# get window class
window_class = self.get_window_class(winid, dispvm)
# close the window - we've got the window class, it is no longer needed
subprocess.check_call(['wmctrl', '-i', '-c', winid])
p.wait()
self.wait_for_window(window_title, show=False)
def check_matches(obj, patterns):
return any((pat.search(obj) if isinstance(pat, type(re.compile('')))
else pat in obj) for pat in patterns)
if not check_matches(window_title, expected_app_titles) and \
not check_matches(window_class, expected_app_classes):
self.fail("Opening file {} resulted in window '{} ({})', which is "
"none of {!r} ({!r})".format(
filename, window_title, window_class,
expected_app_titles, expected_app_classes))
def prepare_txt(self, filename):
p = self.source_vm.run("cat > {}".format(filename), passio_popen=True)
p.stdin.write("This is test\n")
p.stdin.close()
retcode = p.wait()
assert retcode == 0, "Failed to write {} file".format(filename)
def prepare_pdf(self, filename):
self.prepare_txt("/tmp/source.txt")
cmd = "convert /tmp/source.txt {}".format(filename)
retcode = self.source_vm.run(cmd, wait=True)
assert retcode == 0, "Failed to run '{}'".format(cmd)
def prepare_doc(self, filename):
self.prepare_txt("/tmp/source.txt")
cmd = "unoconv -f doc -o {} /tmp/source.txt".format(filename)
retcode = self.source_vm.run(cmd, wait=True)
if retcode != 0:
self.skipTest("Failed to run '{}', not installed?".format(cmd))
def prepare_pptx(self, filename):
self.prepare_txt("/tmp/source.txt")
cmd = "unoconv -f pptx -o {} /tmp/source.txt".format(filename)
retcode = self.source_vm.run(cmd, wait=True)
if retcode != 0:
self.skipTest("Failed to run '{}', not installed?".format(cmd))
def prepare_png(self, filename):
self.prepare_txt("/tmp/source.txt")
cmd = "convert /tmp/source.txt {}".format(filename)
retcode = self.source_vm.run(cmd, wait=True)
if retcode != 0:
self.skipTest("Failed to run '{}', not installed?".format(cmd))
def prepare_jpg(self, filename):
self.prepare_txt("/tmp/source.txt")
cmd = "convert /tmp/source.txt {}".format(filename)
retcode = self.source_vm.run(cmd, wait=True)
if retcode != 0:
self.skipTest("Failed to run '{}', not installed?".format(cmd))
def test_000_txt(self):
filename = "/home/user/test_file.txt"
self.prepare_txt(filename)
self.open_file_and_check_viewer(filename, ["vim"],
["gedit", "emacs"])
def test_001_pdf(self):
filename = "/home/user/test_file.pdf"
self.prepare_pdf(filename)
self.open_file_and_check_viewer(filename, [],
["evince"])
def test_002_doc(self):
filename = "/home/user/test_file.doc"
self.prepare_doc(filename)
self.open_file_and_check_viewer(filename, [],
["libreoffice", "abiword"])
def test_003_pptx(self):
filename = "/home/user/test_file.pptx"
self.prepare_pptx(filename)
self.open_file_and_check_viewer(filename, [],
["libreoffice"])
def test_004_png(self):
filename = "/home/user/test_file.png"
self.prepare_png(filename)
self.open_file_and_check_viewer(filename, [],
["shotwell", "eog", "display"])
def test_005_jpg(self):
filename = "/home/user/test_file.jpg"
self.prepare_jpg(filename)
self.open_file_and_check_viewer(filename, [],
["shotwell", "eog", "display"])
def test_006_jpeg(self):
filename = "/home/user/test_file.jpeg"
self.prepare_jpg(filename)
self.open_file_and_check_viewer(filename, [],
["shotwell", "eog", "display"])
def test_100_txt_dispvm(self):
filename = "/home/user/test_file.txt"
self.prepare_txt(filename)
self.open_file_and_check_viewer(filename, ["vim"],
["gedit", "emacs"],
dispvm=True)
def test_101_pdf_dispvm(self):
filename = "/home/user/test_file.pdf"
self.prepare_pdf(filename)
self.open_file_and_check_viewer(filename, [],
["evince"],
dispvm=True)
def test_102_doc_dispvm(self):
filename = "/home/user/test_file.doc"
self.prepare_doc(filename)
self.open_file_and_check_viewer(filename, [],
["libreoffice", "abiword"],
dispvm=True)
def test_103_pptx_dispvm(self):
filename = "/home/user/test_file.pptx"
self.prepare_pptx(filename)
self.open_file_and_check_viewer(filename, [],
["libreoffice"],
dispvm=True)
def test_104_png_dispvm(self):
filename = "/home/user/test_file.png"
self.prepare_png(filename)
self.open_file_and_check_viewer(filename, [],
["shotwell", "eog", "display"],
dispvm=True)
def test_105_jpg_dispvm(self):
filename = "/home/user/test_file.jpg"
self.prepare_jpg(filename)
self.open_file_and_check_viewer(filename, [],
["shotwell", "eog", "display"],
dispvm=True)
def test_106_jpeg_dispvm(self):
filename = "/home/user/test_file.jpeg"
self.prepare_jpg(filename)
self.open_file_and_check_viewer(filename, [],
["shotwell", "eog", "display"],
dispvm=True)
def load_tests(loader, tests, pattern):
try:
@ -1233,4 +1651,10 @@ def load_tests(loader, tests, pattern):
(TC_40_PVGrub, qubes.tests.QubesTestCase),
{'template': template})))
tests.addTests(loader.loadTestsFromTestCase(
type(
'TC_50_MimeHandlers_' + template,
(TC_50_MimeHandlers, qubes.tests.QubesTestCase),
{'template': template})))
return tests

View File

@ -1 +1 @@
3.1.11
3.2.0