core-admin/tests/vm_qrexec_gui.py

992 lines
40 KiB
Python
Raw Normal View History

#!/usr/bin/python
2015-02-05 15:46:40 +01:00
# vim: fileencoding=utf-8
#
2015-02-05 15:46:40 +01:00
# The Qubes OS Project, https://www.qubes-os.org/
#
2015-02-05 15:46:40 +01:00
# Copyright (C) 2014-2015
# Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>
# Copyright (C) 2015 Wojtek Porczyk <woju@invisiblethingslab.com>
#
2015-02-05 15:46:40 +01:00
# 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.
#
2015-02-05 15:46:40 +01:00
# 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.
#
from distutils import spawn
2015-02-05 15:46:40 +01:00
import multiprocessing
import os
import subprocess
import unittest
import time
2015-02-09 06:18:57 +01:00
from qubes.qubes import QubesVmCollection, defaults, QubesException
2015-02-05 15:46:40 +01:00
import qubes.tests
TEST_DATA = "0123456789" * 1024
class TC_00_AppVMMixin(qubes.tests.SystemTestsMixin):
def setUp(self):
super(TC_00_AppVMMixin, self).setUp()
self.testvm1 = self.qc.add_new_vm(
"QubesAppVm",
2015-02-05 15:46:40 +01:00
name=self.make_vm_name('vm1'),
template=self.qc.get_vm_by_name(self.template))
self.testvm1.create_on_disk(verbose=False)
self.testvm2 = self.qc.add_new_vm(
"QubesAppVm",
2015-02-05 15:46:40 +01:00
name=self.make_vm_name('vm2'),
template=self.qc.get_vm_by_name(self.template))
self.testvm2.create_on_disk(verbose=False)
self.save_and_reload_db()
self.qc.unlock_db()
self.testvm1 = self.qc[self.testvm1.qid]
self.testvm2 = self.qc[self.testvm2.qid]
def test_000_start_shutdown(self):
self.testvm1.start()
self.assertEquals(self.testvm1.get_power_state(), "Running")
self.testvm1.shutdown()
shutdown_counter = 0
while self.testvm1.is_running():
if shutdown_counter > defaults["shutdown_counter_max"]:
self.fail("VM hanged during shutdown")
shutdown_counter += 1
time.sleep(1)
time.sleep(1)
self.assertEquals(self.testvm1.get_power_state(), "Halted")
@unittest.skipUnless(spawn.find_executable('xdotool'),
"xdotool not installed")
def test_010_run_xterm(self):
self.testvm1.start()
self.assertEquals(self.testvm1.get_power_state(), "Running")
self.testvm1.run("xterm")
wait_count = 0
title = 'user@{}'.format(self.testvm1.name)
if self.template.count("whonix"):
title = 'user@host'
while subprocess.call(
['xdotool', 'search', '--name', title],
stdout=open(os.path.devnull, 'w'),
stderr=subprocess.STDOUT) > 0:
wait_count += 1
if wait_count > 100:
self.fail("Timeout while waiting for xterm window")
time.sleep(0.1)
time.sleep(0.5)
subprocess.check_call(
['xdotool', 'search', '--name', title,
'windowactivate', 'type', 'exit\n'])
wait_count = 0
while subprocess.call(['xdotool', 'search', '--name', title],
stdout=open(os.path.devnull, 'w'),
stderr=subprocess.STDOUT) == 0:
wait_count += 1
if wait_count > 100:
self.fail("Timeout while waiting for xterm "
"termination")
time.sleep(0.1)
@unittest.skipUnless(spawn.find_executable('xdotool'),
"xdotool not installed")
def test_011_run_gnome_terminal(self):
if "minimal" in self.template:
self.skipTest("Minimal template doesn't have 'gnome-terminal'")
self.testvm1.start()
self.assertEquals(self.testvm1.get_power_state(), "Running")
self.testvm1.run("gnome-terminal")
title = 'user@{}'.format(self.testvm1.name)
if self.template.count("whonix"):
title = 'user@host'
wait_count = 0
while subprocess.call(
['xdotool', 'search', '--name', title],
stdout=open(os.path.devnull, 'w'),
stderr=subprocess.STDOUT) > 0:
wait_count += 1
if wait_count > 100:
self.fail("Timeout while waiting for gnome-terminal window")
time.sleep(0.1)
time.sleep(0.5)
subprocess.check_call(
['xdotool', 'search', '--name', title,
'windowactivate', 'type', 'exit\n'])
wait_count = 0
while subprocess.call(['xdotool', 'search', '--name', title],
stdout=open(os.path.devnull, 'w'),
stderr=subprocess.STDOUT) == 0:
wait_count += 1
if wait_count > 100:
self.fail("Timeout while waiting for gnome-terminal "
"termination")
time.sleep(0.1)
@unittest.skipUnless(spawn.find_executable('xdotool'),
"xdotool not installed")
def test_012_qubes_desktop_run(self):
self.testvm1.start()
self.assertEquals(self.testvm1.get_power_state(), "Running")
xterm_desktop_path = "/usr/share/applications/xterm.desktop"
# Debian has it different...
xterm_desktop_path_debian = \
"/usr/share/applications/debian-xterm.desktop"
if self.testvm1.run("test -r {}".format(xterm_desktop_path_debian),
wait=True) == 0:
xterm_desktop_path = xterm_desktop_path_debian
self.testvm1.run("qubes-desktop-run {}".format(xterm_desktop_path))
title = 'user@{}'.format(self.testvm1.name)
if self.template.count("whonix"):
title = 'user@host'
wait_count = 0
while subprocess.call(
['xdotool', 'search', '--name', title],
stdout=open(os.path.devnull, 'w'),
stderr=subprocess.STDOUT) > 0:
wait_count += 1
if wait_count > 100:
self.fail("Timeout while waiting for xterm window")
time.sleep(0.1)
time.sleep(0.5)
subprocess.check_call(
['xdotool', 'search', '--name', title,
'windowactivate', 'type', 'exit\n'])
wait_count = 0
while subprocess.call(['xdotool', 'search', '--name', title],
stdout=open(os.path.devnull, 'w'),
stderr=subprocess.STDOUT) == 0:
wait_count += 1
if wait_count > 100:
self.fail("Timeout while waiting for xterm "
"termination")
time.sleep(0.1)
def test_050_qrexec_simple_eof(self):
"""Test for data and EOF transmission dom0->VM"""
result = multiprocessing.Value('i', 0)
def run(self, result):
p = self.testvm1.run("cat", passio_popen=True,
passio_stderr=True)
(stdout, stderr) = p.communicate(TEST_DATA)
if stdout != TEST_DATA:
result.value = 1
if len(stderr) > 0:
result.value = 2
self.testvm1.start()
t = multiprocessing.Process(target=run, args=(self, result))
t.start()
t.join(timeout=10)
if t.is_alive():
t.terminate()
self.fail("Timeout, probably EOF wasn't transferred to the VM "
"process")
if result.value == 1:
self.fail("Received data differs from what was sent")
elif result.value == 2:
self.fail("Some data was printed to stderr")
def test_051_qrexec_simple_eof_reverse(self):
"""Test for EOF transmission VM->dom0"""
result = multiprocessing.Value('i', 0)
def run(self, result):
p = self.testvm1.run("echo test; exec >&-; cat > /dev/null",
passio_popen=True, passio_stderr=True)
# this will hang on test failure
stdout = p.stdout.read()
p.stdin.write(TEST_DATA)
p.stdin.close()
if stdout.strip() != "test":
result.value = 1
# this may hang in some buggy cases
elif len(p.stderr.read()) > 0:
result.value = 2
elif p.poll() is None:
time.sleep(1)
if p.poll() is None:
result.value = 3
self.testvm1.start()
t = multiprocessing.Process(target=run, args=(self, result))
t.start()
t.join(timeout=10)
if t.is_alive():
t.terminate()
self.fail("Timeout, probably EOF wasn't transferred from the VM "
"process")
if result.value == 1:
self.fail("Received data differs from what was expected")
elif result.value == 2:
self.fail("Some data was printed to stderr")
elif result.value == 3:
self.fail("VM proceess didn't terminated on EOF")
def test_052_qrexec_vm_service_eof(self):
"""Test for EOF transmission VM(src)->VM(dst)"""
result = multiprocessing.Value('i', 0)
def run(self, result):
p = self.testvm1.run("/usr/lib/qubes/qrexec-client-vm %s test.EOF "
"/bin/sh -c 'echo test; exec >&-; cat "
">&$SAVED_FD_1'" % self.testvm2.name,
passio_popen=True)
(stdout, stderr) = p.communicate()
if stdout != "test\n":
result.value = 1
self.testvm1.start()
self.testvm2.start()
p = self.testvm2.run("cat > /etc/qubes-rpc/test.EOF", user="root",
passio_popen=True)
p.stdin.write("/bin/cat")
p.stdin.close()
p.wait()
policy = open("/etc/qubes-rpc/policy/test.EOF", "w")
policy.write("%s %s allow" % (self.testvm1.name, self.testvm2.name))
policy.close()
self.addCleanup(os.unlink, "/etc/qubes-rpc/policy/test.EOF")
t = multiprocessing.Process(target=run, args=(self, result))
t.start()
t.join(timeout=10)
if t.is_alive():
t.terminate()
self.fail("Timeout, probably EOF wasn't transferred")
if result.value == 1:
self.fail("Received data differs from what was expected")
@unittest.expectedFailure
def test_053_qrexec_vm_service_eof_reverse(self):
"""Test for EOF transmission VM(src)<-VM(dst)"""
result = multiprocessing.Value('i', 0)
def run(self, result):
p = self.testvm1.run("/usr/lib/qubes/qrexec-client-vm %s test.EOF "
"/bin/sh -c 'cat >&$SAVED_FD_1'"
2015-08-08 02:05:52 +02:00
% self.testvm2.name,
passio_popen=True)
(stdout, stderr) = p.communicate()
if stdout != "test\n":
result.value = 1
self.testvm1.start()
self.testvm2.start()
p = self.testvm2.run("cat > /etc/qubes-rpc/test.EOF", user="root",
passio_popen=True)
p.stdin.write("echo test; exec >&-; cat >/dev/null")
p.stdin.close()
p.wait()
policy = open("/etc/qubes-rpc/policy/test.EOF", "w")
policy.write("%s %s allow" % (self.testvm1.name, self.testvm2.name))
policy.close()
self.addCleanup(os.unlink, "/etc/qubes-rpc/policy/test.EOF")
t = multiprocessing.Process(target=run, args=(self, result))
t.start()
t.join(timeout=10)
if t.is_alive():
t.terminate()
self.fail("Timeout, probably EOF wasn't transferred")
if result.value == 1:
self.fail("Received data differs from what was expected")
def test_055_qrexec_dom0_service_abort(self):
"""
Test if service abort (by dom0) is properly handled by source VM.
If "remote" part of the service terminates, the source part should
properly be notified. This includes closing its stdin (which is
already checked by test_053_qrexec_vm_service_eof_reverse), but also
its stdout - otherwise such service might hang on write(2) call.
"""
def run (src):
p = src.run("/usr/lib/qubes/qrexec-client-vm dom0 "
"test.Abort /bin/cat /dev/zero",
passio_popen=True)
p.communicate()
p.wait()
self.testvm1.start()
service = open("/etc/qubes-rpc/test.Abort", "w")
service.write("sleep 1")
service.close()
self.addCleanup(os.unlink, "/etc/qubes-rpc/test.Abort")
policy = open("/etc/qubes-rpc/policy/test.Abort", "w")
policy.write("%s dom0 allow" % (self.testvm1.name))
policy.close()
self.addCleanup(os.unlink, "/etc/qubes-rpc/policy/test.Abort")
t = multiprocessing.Process(target=run, args=(self.testvm1,))
t.start()
t.join(timeout=10)
if t.is_alive():
t.terminate()
self.fail("Timeout, probably stdout wasn't closed")
2015-02-09 06:08:48 +01:00
def test_060_qrexec_exit_code_dom0(self):
self.testvm1.start()
p = self.testvm1.run("exit 0", passio_popen=True)
p.wait()
self.assertEqual(0, p.returncode)
p = self.testvm1.run("exit 3", passio_popen=True)
p.wait()
self.assertEqual(3, p.returncode)
@unittest.expectedFailure
def test_065_qrexec_exit_code_vm(self):
self.testvm1.start()
self.testvm2.start()
policy = open("/etc/qubes-rpc/policy/test.Retcode", "w")
policy.write("%s %s allow" % (self.testvm1.name, self.testvm2.name))
policy.close()
self.addCleanup(os.unlink, "/etc/qubes-rpc/policy/test.Retcode")
p = self.testvm2.run("cat > /etc/qubes-rpc/test.Retcode", user="root",
passio_popen=True)
p.stdin.write("exit 0")
p.stdin.close()
p.wait()
p = self.testvm1.run("/usr/lib/qubes/qrexec-client-vm %s test.Retcode "
"/bin/sh -c 'cat >/dev/null'; echo $?"
% self.testvm1.name,
passio_popen=True)
(stdout, stderr) = p.communicate()
self.assertEqual(stdout, "0\n")
p = self.testvm2.run("cat > /etc/qubes-rpc/test.Retcode", user="root",
passio_popen=True)
p.stdin.write("exit 3")
p.stdin.close()
p.wait()
p = self.testvm1.run("/usr/lib/qubes/qrexec-client-vm %s test.Retcode "
"/bin/sh -c 'cat >/dev/null'; echo $?"
% self.testvm1.name,
passio_popen=True)
(stdout, stderr) = p.communicate()
self.assertEqual(stdout, "3\n")
def test_070_qrexec_vm_simultaneous_write(self):
"""Test for simultaneous write in VM(src)->VM(dst) connection
This is regression test for #1347
Check for deadlock when initially both sides writes a lot of data
(and not read anything). When one side starts reading, it should
get the data and the remote side should be possible to write then more.
There was a bug where remote side was waiting on write(2) and not
handling anything else.
"""
result = multiprocessing.Value('i', -1)
def run(self):
p = self.testvm1.run(
"/usr/lib/qubes/qrexec-client-vm %s test.write "
"/bin/sh -c '"
# first write a lot of data to fill all the buffers
"dd if=/dev/zero bs=993 count=10000 iflag=fullblock & "
# then after some time start reading
"sleep 1; "
"dd of=/dev/null bs=993 count=10000 iflag=fullblock; "
"wait"
"'" % self.testvm2.name, passio_popen=True)
p.communicate()
result.value = p.returncode
self.testvm1.start()
self.testvm2.start()
p = self.testvm2.run("cat > /etc/qubes-rpc/test.write", user="root",
passio_popen=True)
# first write a lot of data
p.stdin.write("dd if=/dev/zero bs=993 count=10000 iflag=fullblock\n")
# and only then read something
p.stdin.write("dd of=/dev/null bs=993 count=10000 iflag=fullblock\n")
p.stdin.close()
p.wait()
policy = open("/etc/qubes-rpc/policy/test.write", "w")
policy.write("%s %s allow" % (self.testvm1.name, self.testvm2.name))
policy.close()
self.addCleanup(os.unlink, "/etc/qubes-rpc/policy/test.write")
t = multiprocessing.Process(target=run, args=(self,))
t.start()
t.join(timeout=10)
if t.is_alive():
t.terminate()
self.fail("Timeout, probably deadlock")
self.assertEqual(result.value, 0, "Service call failed")
def test_071_qrexec_dom0_simultaneous_write(self):
"""Test for simultaneous write in dom0(src)->VM(dst) connection
Similar to test_070_qrexec_vm_simultaneous_write, but with dom0
as a source.
"""
result = multiprocessing.Value('i', -1)
def run(self):
result.value = self.testvm2.run_service(
"test.write", localcmd="/bin/sh -c '"
# first write a lot of data to fill all the buffers
"dd if=/dev/zero bs=993 count=10000 iflag=fullblock & "
# then after some time start reading
"sleep 1; "
"dd of=/dev/null bs=993 count=10000 iflag=fullblock; "
"wait"
"'")
self.testvm2.start()
p = self.testvm2.run("cat > /etc/qubes-rpc/test.write", user="root",
passio_popen=True)
# first write a lot of data
p.stdin.write("dd if=/dev/zero bs=993 count=10000 iflag=fullblock\n")
# and only then read something
p.stdin.write("dd of=/dev/null bs=993 count=10000 iflag=fullblock\n")
p.stdin.close()
p.wait()
policy = open("/etc/qubes-rpc/policy/test.write", "w")
policy.write("%s %s allow" % (self.testvm1.name, self.testvm2.name))
policy.close()
self.addCleanup(os.unlink, "/etc/qubes-rpc/policy/test.write")
t = multiprocessing.Process(target=run, args=(self,))
t.start()
t.join(timeout=10)
if t.is_alive():
t.terminate()
self.fail("Timeout, probably deadlock")
self.assertEqual(result.value, 0, "Service call failed")
def test_072_qrexec_to_dom0_simultaneous_write(self):
"""Test for simultaneous write in dom0(src)<-VM(dst) connection
Similar to test_071_qrexec_dom0_simultaneous_write, but with dom0
as a "hanging" side.
"""
result = multiprocessing.Value('i', -1)
def run(self):
result.value = self.testvm2.run_service(
"test.write", localcmd="/bin/sh -c '"
# first write a lot of data to fill all the buffers
"dd if=/dev/zero bs=993 count=10000 iflag=fullblock "
# then, only when all written, read something
"dd of=/dev/null bs=993 count=10000 iflag=fullblock; "
"'")
self.testvm2.start()
p = self.testvm2.run("cat > /etc/qubes-rpc/test.write", user="root",
passio_popen=True)
# first write a lot of data
p.stdin.write("dd if=/dev/zero bs=993 count=10000 iflag=fullblock &\n")
# and only then read something
p.stdin.write("dd of=/dev/null bs=993 count=10000 iflag=fullblock\n")
p.stdin.write("sleep 1; \n")
p.stdin.write("wait\n")
p.stdin.close()
p.wait()
policy = open("/etc/qubes-rpc/policy/test.write", "w")
policy.write("%s %s allow" % (self.testvm1.name, self.testvm2.name))
policy.close()
self.addCleanup(os.unlink, "/etc/qubes-rpc/policy/test.write")
t = multiprocessing.Process(target=run, args=(self,))
t.start()
t.join(timeout=10)
if t.is_alive():
t.terminate()
self.fail("Timeout, probably deadlock")
self.assertEqual(result.value, 0, "Service call failed")
@unittest.skipUnless(spawn.find_executable('xdotool'),
"xdotool not installed")
def test_100_qrexec_filecopy(self):
self.testvm1.start()
self.testvm2.start()
p = self.testvm1.run("qvm-copy-to-vm %s /etc/passwd" %
self.testvm2.name, passio_popen=True,
passio_stderr=True)
# Confirm transfer
subprocess.check_call(
['xdotool', 'search', '--sync', '--name', 'Question', 'key', 'y'])
p.wait()
self.assertEqual(p.returncode, 0, "qvm-copy-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")
@unittest.skipUnless(spawn.find_executable('xdotool'),
"xdotool not installed")
def test_110_qrexec_filecopy_deny(self):
self.testvm1.start()
self.testvm2.start()
p = self.testvm1.run("qvm-copy-to-vm %s /etc/passwd" %
self.testvm2.name, passio_popen=True)
# Deny transfer
subprocess.check_call(['xdotool', 'search', '--sync', '--name', 'Question',
'key', 'n'])
p.wait()
2015-02-09 06:08:48 +01:00
self.assertNotEqual(p.returncode, 0, "qvm-copy-to-vm unexpectedly "
"succeeded")
retcode = self.testvm1.run("ls /home/user/QubesIncoming/%s" %
self.testvm1.name, wait=True,
ignore_stderr=True)
2015-02-09 06:08:48 +01:00
self.assertNotEqual(retcode, 0, "QubesIncoming exists although file "
"copy was denied")
tests: disable qrexec_filecopy_self test When vchan connection is established back to the source domain, gntalloc crashes with this message: [ 9.937990] BUG: Bad page map in process qrexec-agent pte:80000000f9d41167 pmd:131c3067 [ 9.938010] page:ffffea00036a6638 count:1 mapcount:-1 mapping: (null) index:0xffffffffffffffff [ 9.938018] page flags: 0x4000000000000c14(referenced|dirty|reserved|private) [ 9.938033] addr:00007fa856d47000 vm_flags:140400fb anon_vma: (null) mapping:ffff880011efe940 index:11 [ 9.938042] vma->vm_ops->fault: (null) [ 9.938057] vma->vm_file->f_op->mmap: gntalloc_mmap+0x0/0x1c0 [xen_gntalloc] [ 9.938066] CPU: 0 PID: 1108 Comm: qrexec-agent Tainted: G O 3.12.23-1.pvops.qubes.x86_64 #1 [ 9.938074] ffff8800131f3818 ffff88001316fc78 ffffffff814db550 00007fa856d47000 [ 9.938085] ffff88001316fcb8 ffffffff81139413 ffff880011efe940 ffff8800131c3a38 [ 9.938096] ffffea00036a6638 00007fa856d47000 00007fa856d57000 ffff88001316fe18 [ 9.938107] Call Trace: [ 9.938117] [<ffffffff814db550>] dump_stack+0x45/0x56 [ 9.938126] [<ffffffff81139413>] print_bad_pte+0x1a3/0x240 [ 9.938133] [<ffffffff8113ac9e>] unmap_page_range+0x6ee/0x7d0 [ 9.938142] [<ffffffff8113adf6>] unmap_single_vma+0x76/0xa0 [ 9.938149] [<ffffffff8113be09>] unmap_vmas+0x49/0x90 [ 9.938157] [<ffffffff8114443c>] exit_mmap+0x9c/0x170 [ 9.938166] [<ffffffff8105950c>] mmput+0x5c/0x110 [ 9.938175] [<ffffffff8105d74c>] do_exit+0x27c/0xa20 [ 9.938184] [<ffffffff810908ef>] ? vtime_account_user+0x4f/0x60 [ 9.938194] [<ffffffff81116502>] ? context_tracking_user_exit+0x52/0xc0 [ 9.938203] [<ffffffff8105ed2a>] do_group_exit+0x3a/0xa0 [ 9.938211] [<ffffffff8105ed9f>] SyS_exit_group+0xf/0x10 [ 9.938220] [<ffffffff814ea907>] tracesys+0xdd/0xe2
2015-02-09 22:09:15 +01:00
@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()
p = self.testvm1.run("qvm-copy-to-vm %s /etc/passwd" %
self.testvm1.name, passio_popen=True,
passio_stderr=True)
# Confirm transfer
subprocess.check_call(['xdotool', 'search', '--sync', '--name', 'Question',
'key', 'y'])
p.wait()
self.assertEqual(p.returncode, 0, "qvm-copy-to-vm failed: %s" %
p.stderr.read())
retcode = self.testvm1.run(
"diff /etc/passwd /home/user/QubesIncoming/{}/passwd".format(
self.testvm1.name),
wait=True)
self.assertEqual(retcode, 0, "file differs")
def test_200_timezone(self):
"""Test whether timezone setting is properly propagated to the VM"""
if "whonix" in self.template:
self.skipTest("Timezone propagation disabled on Whonix templates")
self.testvm1.start()
(vm_tz, _) = self.testvm1.run("date +%Z",
passio_popen=True).communicate()
(dom0_tz, _) = subprocess.Popen(["date", "+%Z"],
stdout=subprocess.PIPE).communicate()
self.assertEqual(vm_tz.strip(), dom0_tz.strip())
# Check if reverting back to UTC works
(vm_tz, _) = self.testvm1.run("TZ=UTC date +%Z",
passio_popen=True).communicate()
self.assertEqual(vm_tz.strip(), "UTC")
2015-07-08 01:21:13 +02:00
def test_210_time_sync(self):
"""Test time synchronization mechanism"""
self.testvm1.start()
self.testvm2.start()
(start_time, _) = subprocess.Popen(["date", "-u", "+%s"],
stdout=subprocess.PIPE).communicate()
original_clockvm = self.qc.get_clockvm_vm()
if original_clockvm:
original_clockvm_name = original_clockvm.name
else:
original_clockvm_name = "none"
2015-07-08 01:21:13 +02:00
try:
# use qubes-prefs to not hassle with qubes.xml locking
subprocess.check_call(["qubes-prefs", "-s", "clockvm",
self.testvm1.name])
# break vm and dom0 time, to check if qvm-sync-clock would fix it
subprocess.check_call(["sudo", "date", "-s",
"2001-01-01T12:34:56"],
stdout=open(os.devnull, 'w'))
retcode = self.testvm1.run("date -s 2001-01-01T12:34:56",
user="root", wait=True)
self.assertEquals(retcode, 0, "Failed to break the VM(1) time")
retcode = self.testvm2.run("date -s 2001-01-01T12:34:56",
user="root", wait=True)
self.assertEquals(retcode, 0, "Failed to break the VM(2) time")
retcode = subprocess.call(["qvm-sync-clock"])
self.assertEquals(retcode, 0,
"qvm-sync-clock failed with code {}".
format(retcode))
(vm_time, _) = self.testvm1.run("date -u +%s",
passio_popen=True).communicate()
self.assertAlmostEquals(int(vm_time), int(start_time), delta=10)
(vm_time, _) = self.testvm2.run("date -u +%s",
passio_popen=True).communicate()
self.assertAlmostEquals(int(vm_time), int(start_time), delta=10)
(dom0_time, _) = subprocess.Popen(["date", "-u", "+%s"],
stdout=subprocess.PIPE
).communicate()
self.assertAlmostEquals(int(dom0_time), int(start_time), delta=30)
2015-07-08 01:21:13 +02:00
except:
# reset time to some approximation of the real time
subprocess.Popen(["sudo", "date", "-u", "-s", "@" + start_time])
raise
finally:
subprocess.call(["qubes-prefs", "-s", "clockvm",
original_clockvm_name])
2015-02-05 15:46:40 +01:00
class TC_10_HVM(qubes.tests.SystemTestsMixin, qubes.tests.QubesTestCase):
2015-02-09 06:18:57 +01:00
# TODO: test with some OS inside
# TODO: windows tools tests
def test_000_create_start(self):
testvm1 = self.qc.add_new_vm("QubesHVm",
name=self.make_vm_name('vm1'))
testvm1.create_on_disk(verbose=False)
2015-02-05 15:46:40 +01:00
self.qc.save()
self.qc.unlock_db()
testvm1.start()
self.assertEquals(testvm1.get_power_state(), "Running")
2015-02-09 06:18:57 +01:00
def test_010_create_start_template(self):
templatevm = self.qc.add_new_vm("QubesTemplateHVm",
name=self.make_vm_name('template'))
templatevm.create_on_disk(verbose=False)
self.qc.save()
self.qc.unlock_db()
2015-02-09 06:18:57 +01:00
templatevm.start()
self.assertEquals(templatevm.get_power_state(), "Running")
2015-02-09 06:18:57 +01:00
def test_020_create_start_template_vm(self):
templatevm = self.qc.add_new_vm("QubesTemplateHVm",
name=self.make_vm_name('template'))
templatevm.create_on_disk(verbose=False)
testvm2 = self.qc.add_new_vm("QubesHVm",
name=self.make_vm_name('vm2'),
template=templatevm)
testvm2.create_on_disk(verbose=False)
2015-02-05 15:46:40 +01:00
self.qc.save()
self.qc.unlock_db()
2015-02-09 06:18:57 +01:00
testvm2.start()
self.assertEquals(testvm2.get_power_state(), "Running")
2015-02-09 06:18:57 +01:00
def test_030_prevent_simultaneus_start(self):
templatevm = self.qc.add_new_vm("QubesTemplateHVm",
name=self.make_vm_name('template'))
templatevm.create_on_disk(verbose=False)
testvm2 = self.qc.add_new_vm("QubesHVm",
name=self.make_vm_name('vm2'),
template=templatevm)
testvm2.create_on_disk(verbose=False)
2015-02-05 15:46:40 +01:00
self.qc.save()
self.qc.unlock_db()
2015-02-09 06:18:57 +01:00
templatevm.start()
self.assertEquals(templatevm.get_power_state(), "Running")
self.assertRaises(QubesException, testvm2.start)
templatevm.force_shutdown()
testvm2.start()
self.assertEquals(testvm2.get_power_state(), "Running")
self.assertRaises(QubesException, templatevm.start)
2015-02-09 06:18:57 +01:00
class TC_20_DispVMMixin(qubes.tests.SystemTestsMixin):
2014-11-10 11:42:30 +01:00
def test_000_prepare_dvm(self):
2015-02-05 15:46:40 +01:00
self.qc.unlock_db()
2014-11-10 11:42:30 +01:00
retcode = subprocess.call(['/usr/bin/qvm-create-default-dvm',
self.template],
2014-11-10 11:42:30 +01:00
stderr=open(os.devnull, 'w'))
self.assertEqual(retcode, 0)
2015-02-05 15:46:40 +01:00
self.qc.lock_db_for_writing()
2014-11-10 11:42:30 +01:00
self.qc.load()
self.assertIsNotNone(self.qc.get_vm_by_name(
self.template + "-dvm"))
2014-11-10 11:42:30 +01:00
# TODO: check mtime of snapshot file
def test_010_simple_dvm_run(self):
2015-02-05 15:46:40 +01:00
self.qc.unlock_db()
2014-11-10 11:42:30 +01:00
p = subprocess.Popen(['/usr/lib/qubes/qfile-daemon-dvm',
'qubes.VMShell', 'dom0', 'DEFAULT'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=open(os.devnull, 'w'))
(stdout, _) = p.communicate(input="echo test")
self.assertEqual(stdout, "test\n")
# TODO: check if DispVM is destroyed
@unittest.skipUnless(spawn.find_executable('xdotool'),
"xdotool not installed")
2014-11-10 11:42:30 +01:00
def test_020_gui_app(self):
2015-02-05 15:46:40 +01:00
self.qc.unlock_db()
2014-11-10 11:42:30 +01:00
p = subprocess.Popen(['/usr/lib/qubes/qfile-daemon-dvm',
'qubes.VMShell', 'dom0', 'DEFAULT'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=open(os.devnull, 'w'))
2015-02-05 15:46:40 +01:00
2014-11-10 11:42:30 +01:00
# wait for DispVM startup:
p.stdin.write("echo test\n")
p.stdin.flush()
l = p.stdout.readline()
self.assertEqual(l, "test\n")
2015-02-05 15:46:40 +01:00
2014-11-10 11:42:30 +01:00
# potential race condition, but our tests are supposed to be
# running on dedicated machine, so should not be a problem
self.qc.lock_db_for_reading()
self.qc.load()
self.qc.unlock_db()
2015-02-05 15:46:40 +01:00
2014-11-10 11:42:30 +01:00
max_qid = 0
for vm in self.qc.values():
if not vm.is_disposablevm():
continue
if vm.qid > max_qid:
max_qid = vm.qid
dispvm = self.qc[max_qid]
self.assertNotEqual(dispvm.qid, 0, "DispVM not found in qubes.xml")
self.assertTrue(dispvm.is_running())
2015-02-05 15:46:40 +01:00
2014-11-10 11:42:30 +01:00
window_title = 'user@%s' % (dispvm.template.name + "-dvm")
p.stdin.write("gnome-terminal -e "
"\"sh -s -c 'echo \\\"\033]0;{}\007\\\"'\"\n".
format(window_title))
wait_count = 0
2014-11-10 11:42:30 +01:00
while subprocess.call(['xdotool', 'search', '--name', window_title],
stdout=open(os.path.devnull, 'w'),
stderr=subprocess.STDOUT) > 0:
wait_count += 1
if wait_count > 100:
self.fail("Timeout while waiting for gnome-terminal window")
time.sleep(0.1)
time.sleep(0.5)
subprocess.check_call(['xdotool', 'search', '--name', window_title,
'windowactivate', 'type', 'exit\n'])
wait_count = 0
while subprocess.call(['xdotool', 'search', '--name', window_title],
stdout=open(os.path.devnull, 'w'),
stderr=subprocess.STDOUT) == 0:
wait_count += 1
if wait_count > 100:
self.fail("Timeout while waiting for gnome-terminal "
"termination")
time.sleep(0.1)
p.stdin.close()
wait_count = 0
while dispvm.is_running():
wait_count += 1
if wait_count > 100:
self.fail("Timeout while waiting for DispVM destruction")
time.sleep(0.1)
wait_count = 0
while p.poll() is None:
wait_count += 1
if wait_count > 100:
self.fail("Timeout while waiting for qfile-daemon-dvm "
"termination")
time.sleep(0.1)
self.assertEqual(p.returncode, 0)
self.qc.lock_db_for_reading()
self.qc.load()
self.qc.unlock_db()
self.assertIsNone(self.qc.get_vm_by_name(dispvm.name),
"DispVM not removed from qubes.xml")
def _handle_editor(self, winid):
(window_title, _) = subprocess.Popen(
['xdotool', 'getwindowname', winid], stdout=subprocess.PIPE).\
communicate()
window_title = window_title.strip().\
replace('(', '\(').replace(')', '\)')
time.sleep(1)
if "gedit" in window_title:
subprocess.check_call(['xdotool', 'search', '--name', window_title,
'windowactivate', 'type', 'test test 2\n'])
time.sleep(0.5)
subprocess.check_call(['xdotool', 'search', '--name', window_title,
'key', 'ctrl+s', 'ctrl+q'])
elif "emacs" in window_title:
subprocess.check_call(['xdotool', 'search', '--name', window_title,
'windowactivate', 'type', 'test test 2\n'])
time.sleep(0.5)
subprocess.check_call(['xdotool', 'search', '--name', window_title,
'key', 'ctrl+x', 'ctrl+s'])
subprocess.check_call(['xdotool', 'search', '--name', window_title,
'key', 'ctrl+x', 'ctrl+c'])
elif "vim" in window_title:
subprocess.check_call(['xdotool', 'search', '--name', window_title,
'windowactivate', 'key', 'i',
'type', 'test test 2\n'])
subprocess.check_call(
['xdotool', 'search', '--name', window_title,
'key', 'Escape', 'colon', 'w', 'q', 'Return'])
else:
self.fail("Unknown editor window: {}".format(window_title))
@unittest.skipUnless(spawn.find_executable('xdotool'),
"xdotool not installed")
2014-11-10 11:42:30 +01:00
def test_030_edit_file(self):
testvm1 = self.qc.add_new_vm("QubesAppVm",
name=self.make_vm_name('vm1'),
template=self.qc.get_vm_by_name(
self.template))
testvm1.create_on_disk(verbose=False)
2014-11-10 11:42:30 +01:00
self.qc.save()
testvm1.start()
testvm1.run("echo test1 > /home/user/test.txt", wait=True)
2015-02-05 15:46:40 +01:00
self.qc.unlock_db()
p = testvm1.run("qvm-open-in-dvm /home/user/test.txt",
passio_popen=True)
2014-11-10 11:42:30 +01:00
wait_count = 0
winid = None
while True:
search = subprocess.Popen(['xdotool', 'search',
'--onlyvisible', '--class', 'disp*'],
stdout=subprocess.PIPE,
stderr=open(os.path.devnull, 'w'))
retcode = search.wait()
if retcode == 0:
winid = search.stdout.read().strip()
break
2014-11-10 11:42:30 +01:00
wait_count += 1
if wait_count > 100:
self.fail("Timeout while waiting for editor window")
time.sleep(0.3)
self._handle_editor(winid)
2014-11-10 11:42:30 +01:00
p.wait()
p = testvm1.run("cat /home/user/test.txt",
passio_popen=True)
2014-11-10 11:42:30 +01:00
(test_txt_content, _) = p.communicate()
self.assertEqual(test_txt_content, "test test 2\ntest1\n")
2014-11-10 11:42:30 +01:00
2015-03-31 05:12:40 +02:00
class TC_30_Gui_daemon(qubes.tests.SystemTestsMixin, qubes.tests.QubesTestCase):
@unittest.skipUnless(spawn.find_executable('xdotool'),
"xdotool not installed")
def test_000_clipboard(self):
testvm1 = self.qc.add_new_vm("QubesAppVm",
name=self.make_vm_name('vm1'),
template=self.qc.get_default_template())
testvm1.create_on_disk(verbose=False)
testvm2 = self.qc.add_new_vm("QubesAppVm",
name=self.make_vm_name('vm2'),
template=self.qc.get_default_template())
testvm2.create_on_disk(verbose=False)
2015-03-31 05:12:40 +02:00
self.qc.save()
self.qc.unlock_db()
testvm1.start()
testvm2.start()
2015-03-31 05:12:40 +02:00
window_title = 'user@{}'.format(testvm1.name)
testvm1.run('zenity --text-info --editable --title={}'.format(
2015-03-31 05:12:40 +02:00
window_title))
self.wait_for_window(window_title)
2015-03-31 05:12:40 +02:00
time.sleep(0.5)
test_string = "test{}".format(testvm1.xid)
2015-03-31 05:12:40 +02:00
# Type and copy some text
subprocess.check_call(['xdotool', 'search', '--name', window_title,
'windowactivate',
'type', '{}'.format(test_string)])
2015-03-31 05:12:40 +02:00
# second xdotool call because type --terminator do not work (SEGV)
# additionally do not use search here, so window stack will be empty
# and xdotool will use XTEST instead of generating events manually -
# this will be much better - at least because events will have
# correct timestamp (so gui-daemon would not drop the copy request)
subprocess.check_call(['xdotool',
'key', 'ctrl+a', 'ctrl+c', 'ctrl+shift+c',
'Escape'])
2015-03-31 05:12:40 +02:00
clipboard_content = \
open('/var/run/qubes/qubes-clipboard.bin', 'r').read().strip()
self.assertEquals(clipboard_content, test_string,
"Clipboard copy operation failed - content")
clipboard_source = \
open('/var/run/qubes/qubes-clipboard.bin.source',
'r').read().strip()
self.assertEquals(clipboard_source, testvm1.name,
2015-03-31 05:12:40 +02:00
"Clipboard copy operation failed - owner")
# Then paste it to the other window
window_title = 'user@{}'.format(testvm2.name)
testvm2.run('zenity --entry --title={} > test.txt'.format(
window_title))
self.wait_for_window(window_title)
2015-03-31 05:12:40 +02:00
subprocess.check_call(['xdotool', 'key', '--delay', '100',
'ctrl+shift+v', 'ctrl+v', 'Return'])
time.sleep(0.5)
2015-03-31 05:12:40 +02:00
# And compare the result
(test_output, _) = testvm2.run('cat test.txt',
passio_popen=True).communicate()
2015-03-31 05:12:40 +02:00
self.assertEquals(test_string, test_output.strip())
clipboard_content = \
open('/var/run/qubes/qubes-clipboard.bin', 'r').read().strip()
self.assertEquals(clipboard_content, "",
"Clipboard not wiped after paste - content")
clipboard_source = \
open('/var/run/qubes/qubes-clipboard.bin.source', 'r').read(
).strip()
self.assertEquals(clipboard_source, "",
"Clipboard not wiped after paste - owner")
def load_tests(loader, tests, pattern):
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 template in templates:
tests.addTests(loader.loadTestsFromTestCase(
type(
'TC_00_AppVM_' + template,
(TC_00_AppVMMixin, qubes.tests.QubesTestCase),
{'template': template})))
tests.addTests(loader.loadTestsFromTestCase(
type(
'TC_20_DispVM_' + template,
(TC_20_DispVMMixin, qubes.tests.QubesTestCase),
{'template': template})))
2015-06-21 01:12:47 +02:00
return tests