vm_qrexec_gui.py 66 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605
  1. #
  2. # The Qubes OS Project, https://www.qubes-os.org/
  3. #
  4. # Copyright (C) 2014-2015
  5. # Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>
  6. # Copyright (C) 2015 Wojtek Porczyk <woju@invisiblethingslab.com>
  7. #
  8. # This library is free software; you can redistribute it and/or
  9. # modify it under the terms of the GNU Lesser General Public
  10. # License as published by the Free Software Foundation; either
  11. # version 2.1 of the License, or (at your option) any later version.
  12. #
  13. # This library is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  16. # Lesser General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU Lesser General Public
  19. # License along with this library; if not, see <https://www.gnu.org/licenses/>.
  20. #
  21. import asyncio
  22. import multiprocessing
  23. import os
  24. import subprocess
  25. import sys
  26. import tempfile
  27. import unittest
  28. from distutils import spawn
  29. import grp
  30. import qubes.config
  31. import qubes.devices
  32. import qubes.tests
  33. import qubes.vm.appvm
  34. import qubes.vm.templatevm
  35. TEST_DATA = b"0123456789" * 1024
  36. class TC_00_AppVMMixin(object):
  37. def setUp(self):
  38. super(TC_00_AppVMMixin, self).setUp()
  39. self.init_default_template(self.template)
  40. if self._testMethodName == 'test_210_time_sync':
  41. self.init_networking()
  42. self.testvm1 = self.app.add_new_vm(
  43. qubes.vm.appvm.AppVM,
  44. label='red',
  45. name=self.make_vm_name('vm1'),
  46. template=self.app.domains[self.template])
  47. self.loop.run_until_complete(self.testvm1.create_on_disk())
  48. self.testvm2 = self.app.add_new_vm(
  49. qubes.vm.appvm.AppVM,
  50. label='red',
  51. name=self.make_vm_name('vm2'),
  52. template=self.app.domains[self.template])
  53. self.loop.run_until_complete(self.testvm2.create_on_disk())
  54. self.app.save()
  55. def tearDown(self):
  56. # socket-based qrexec tests:
  57. if os.path.exists('/etc/qubes-rpc/test.Socket'):
  58. os.unlink('/etc/qubes-rpc/test.Socket')
  59. if hasattr(self, 'service_proc'):
  60. try:
  61. self.service_proc.terminate()
  62. self.loop.run_until_complete(self.service_proc.communicate())
  63. except ProcessLookupError:
  64. pass
  65. super(TC_00_AppVMMixin, self).tearDown()
  66. def test_000_start_shutdown(self):
  67. # TODO: wait_for, timeout
  68. self.loop.run_until_complete(self.testvm1.start())
  69. self.assertEqual(self.testvm1.get_power_state(), "Running")
  70. self.loop.run_until_complete(self.wait_for_session(self.testvm1))
  71. self.loop.run_until_complete(self.testvm1.shutdown(wait=True))
  72. self.assertEqual(self.testvm1.get_power_state(), "Halted")
  73. @unittest.skipUnless(spawn.find_executable('xdotool'),
  74. "xdotool not installed")
  75. def test_010_run_xterm(self):
  76. self.loop.run_until_complete(self.testvm1.start())
  77. self.assertEqual(self.testvm1.get_power_state(), "Running")
  78. self.loop.run_until_complete(self.wait_for_session(self.testvm1))
  79. p = self.loop.run_until_complete(self.testvm1.run('xterm'))
  80. try:
  81. title = 'user@{}'.format(self.testvm1.name)
  82. if self.template.count("whonix"):
  83. title = 'user@host'
  84. self.wait_for_window(title)
  85. self.loop.run_until_complete(asyncio.sleep(0.5))
  86. subprocess.check_call(
  87. ['xdotool', 'search', '--name', title,
  88. 'windowactivate', 'type', 'exit\n'])
  89. self.wait_for_window(title, show=False)
  90. finally:
  91. try:
  92. p.terminate()
  93. self.loop.run_until_complete(p.wait())
  94. except ProcessLookupError: # already dead
  95. pass
  96. @unittest.skipUnless(spawn.find_executable('xdotool'),
  97. "xdotool not installed")
  98. def test_011_run_gnome_terminal(self):
  99. if "minimal" in self.template:
  100. self.skipTest("Minimal template doesn't have 'gnome-terminal'")
  101. if 'whonix' in self.template:
  102. self.skipTest("Whonix template doesn't have 'gnome-terminal'")
  103. self.loop.run_until_complete(self.testvm1.start())
  104. self.assertEqual(self.testvm1.get_power_state(), "Running")
  105. self.loop.run_until_complete(self.wait_for_session(self.testvm1))
  106. p = self.loop.run_until_complete(self.testvm1.run('gnome-terminal'))
  107. try:
  108. title = 'user@{}'.format(self.testvm1.name)
  109. if self.template.count("whonix"):
  110. title = 'user@host'
  111. self.wait_for_window(title)
  112. self.loop.run_until_complete(asyncio.sleep(0.5))
  113. subprocess.check_call(
  114. ['xdotool', 'search', '--name', title,
  115. 'windowactivate', '--sync', 'type', 'exit\n'])
  116. wait_count = 0
  117. while subprocess.call(['xdotool', 'search', '--name', title],
  118. stdout=open(os.path.devnull, 'w'),
  119. stderr=subprocess.STDOUT) == 0:
  120. wait_count += 1
  121. if wait_count > 100:
  122. self.fail("Timeout while waiting for gnome-terminal "
  123. "termination")
  124. self.loop.run_until_complete(asyncio.sleep(0.1))
  125. finally:
  126. try:
  127. p.terminate()
  128. self.loop.run_until_complete(p.wait())
  129. except ProcessLookupError: # already dead
  130. pass
  131. @unittest.skipUnless(spawn.find_executable('xdotool'),
  132. "xdotool not installed")
  133. def test_012_qubes_desktop_run(self):
  134. self.loop.run_until_complete(self.testvm1.start())
  135. self.assertEqual(self.testvm1.get_power_state(), "Running")
  136. xterm_desktop_path = "/usr/share/applications/xterm.desktop"
  137. # Debian has it different...
  138. xterm_desktop_path_debian = \
  139. "/usr/share/applications/debian-xterm.desktop"
  140. try:
  141. self.loop.run_until_complete(self.testvm1.run_for_stdio(
  142. 'test -r {}'.format(xterm_desktop_path_debian)))
  143. except subprocess.CalledProcessError:
  144. pass
  145. else:
  146. xterm_desktop_path = xterm_desktop_path_debian
  147. self.loop.run_until_complete(self.wait_for_session(self.testvm1))
  148. self.loop.run_until_complete(
  149. self.testvm1.run('qubes-desktop-run {}'.format(xterm_desktop_path)))
  150. title = 'user@{}'.format(self.testvm1.name)
  151. if self.template.count("whonix"):
  152. title = 'user@host'
  153. self.wait_for_window(title)
  154. self.loop.run_until_complete(asyncio.sleep(0.5))
  155. subprocess.check_call(
  156. ['xdotool', 'search', '--name', title,
  157. 'windowactivate', '--sync', 'type', 'exit\n'])
  158. self.wait_for_window(title, show=False)
  159. def test_050_qrexec_simple_eof(self):
  160. """Test for data and EOF transmission dom0->VM"""
  161. # XXX is this still correct? this is no longer simple qrexec,
  162. # but qubes.VMShell
  163. self.loop.run_until_complete(self.testvm1.start())
  164. try:
  165. (stdout, stderr) = self.loop.run_until_complete(asyncio.wait_for(
  166. self.testvm1.run_for_stdio('cat', input=TEST_DATA),
  167. timeout=10))
  168. except asyncio.TimeoutError:
  169. self.fail(
  170. "Timeout, probably EOF wasn't transferred to the VM process")
  171. self.assertEqual(stdout, TEST_DATA,
  172. 'Received data differs from what was sent')
  173. self.assertFalse(stderr,
  174. 'Some data was printed to stderr')
  175. def test_051_qrexec_simple_eof_reverse(self):
  176. """Test for EOF transmission VM->dom0"""
  177. @asyncio.coroutine
  178. def run(self):
  179. p = yield from self.testvm1.run(
  180. 'echo test; exec >&-; cat > /dev/null',
  181. stdin=subprocess.PIPE,
  182. stdout=subprocess.PIPE,
  183. stderr=subprocess.PIPE)
  184. # this will hang on test failure
  185. stdout = yield from asyncio.wait_for(p.stdout.read(), timeout=10)
  186. p.stdin.write(TEST_DATA)
  187. yield from p.stdin.drain()
  188. p.stdin.close()
  189. self.assertEqual(stdout.strip(), b'test',
  190. 'Received data differs from what was expected')
  191. # this may hang in some buggy cases
  192. self.assertFalse((yield from p.stderr.read()),
  193. 'Some data was printed to stderr')
  194. try:
  195. yield from asyncio.wait_for(p.wait(), timeout=1)
  196. except asyncio.TimeoutError:
  197. self.fail("Timeout, "
  198. "probably EOF wasn't transferred from the VM process")
  199. self.loop.run_until_complete(self.testvm1.start())
  200. self.loop.run_until_complete(self.wait_for_session(self.testvm1))
  201. self.loop.run_until_complete(run(self))
  202. def test_052_qrexec_vm_service_eof(self):
  203. """Test for EOF transmission VM(src)->VM(dst)"""
  204. self.loop.run_until_complete(asyncio.wait([
  205. self.testvm1.start(),
  206. self.testvm2.start()]))
  207. self.loop.run_until_complete(asyncio.wait([
  208. self.wait_for_session(self.testvm1),
  209. self.wait_for_session(self.testvm2)]))
  210. self.create_remote_file(self.testvm2,
  211. '/etc/qubes-rpc/test.EOF',
  212. '#!/bin/sh\n/bin/cat\n')
  213. with self.qrexec_policy('test.EOF', self.testvm1, self.testvm2):
  214. try:
  215. stdout, _ = self.loop.run_until_complete(asyncio.wait_for(
  216. self.testvm1.run_for_stdio('''\
  217. /usr/lib/qubes/qrexec-client-vm {} test.EOF \
  218. /bin/sh -c 'echo test; exec >&-; cat >&$SAVED_FD_1'
  219. '''.format(self.testvm2.name)),
  220. timeout=10))
  221. except subprocess.CalledProcessError as e:
  222. self.fail('{} exited with non-zero code {}; stderr: {}'.format(
  223. e.cmd, e.returncode, e.stderr))
  224. except asyncio.TimeoutError:
  225. self.fail("Timeout, probably EOF wasn't transferred")
  226. self.assertEqual(stdout, b'test\n',
  227. 'Received data differs from what was expected')
  228. def test_053_qrexec_vm_service_eof_reverse(self):
  229. """Test for EOF transmission VM(src)<-VM(dst)"""
  230. self.loop.run_until_complete(asyncio.wait([
  231. self.testvm1.start(),
  232. self.testvm2.start()]))
  233. self.create_remote_file(self.testvm2, '/etc/qubes-rpc/test.EOF',
  234. '#!/bin/sh\n'
  235. 'echo test; exec >&-; cat >/dev/null')
  236. with self.qrexec_policy('test.EOF', self.testvm1, self.testvm2):
  237. try:
  238. stdout, _ = self.loop.run_until_complete(asyncio.wait_for(
  239. self.testvm1.run_for_stdio('''\
  240. /usr/lib/qubes/qrexec-client-vm {} test.EOF \
  241. /bin/sh -c 'cat >&$SAVED_FD_1'
  242. '''.format(self.testvm2.name)),
  243. timeout=10))
  244. except subprocess.CalledProcessError as e:
  245. self.fail('{} exited with non-zero code {}; stderr: {}'.format(
  246. e.cmd, e.returncode, e.stderr))
  247. except asyncio.TimeoutError:
  248. self.fail("Timeout, probably EOF wasn't transferred")
  249. self.assertEqual(stdout, b'test\n',
  250. 'Received data differs from what was expected')
  251. def test_055_qrexec_dom0_service_abort(self):
  252. """
  253. Test if service abort (by dom0) is properly handled by source VM.
  254. If "remote" part of the service terminates, the source part should
  255. properly be notified. This includes closing its stdin (which is
  256. already checked by test_053_qrexec_vm_service_eof_reverse), but also
  257. its stdout - otherwise such service might hang on write(2) call.
  258. """
  259. self.loop.run_until_complete(self.testvm1.start())
  260. self.create_local_file('/etc/qubes-rpc/test.Abort',
  261. 'sleep 1')
  262. with self.qrexec_policy('test.Abort', self.testvm1, 'dom0'):
  263. try:
  264. # two possible exit codes, depending on when exactly dom0
  265. # service terminates:
  266. # exit code 141: EPIPE (no buffered data)
  267. # exit code 1: ECONNRESET (some buffered data remains)
  268. stdout, _ = self.loop.run_until_complete(asyncio.wait_for(
  269. self.testvm1.run_for_stdio('''\
  270. /usr/lib/qubes/qrexec-client-vm dom0 test.Abort \
  271. /bin/sh -c 'cat /dev/zero; echo $? >/tmp/exit-code';
  272. e=$(cat /tmp/exit-code);
  273. test $e -eq 141 -o $e -eq 1'''),
  274. timeout=10))
  275. except subprocess.CalledProcessError as e:
  276. self.fail('{} exited with non-zero code {}; stderr: {}'.format(
  277. e.cmd, e.returncode, e.stderr))
  278. except asyncio.TimeoutError:
  279. self.fail("Timeout, probably stdout wasn't closed")
  280. def test_060_qrexec_exit_code_dom0(self):
  281. self.loop.run_until_complete(self.testvm1.start())
  282. self.loop.run_until_complete(self.testvm1.run_for_stdio('exit 0'))
  283. with self.assertRaises(subprocess.CalledProcessError) as e:
  284. self.loop.run_until_complete(self.testvm1.run_for_stdio('exit 3'))
  285. self.assertEqual(e.exception.returncode, 3)
  286. def test_065_qrexec_exit_code_vm(self):
  287. self.loop.run_until_complete(asyncio.wait([
  288. self.testvm1.start(),
  289. self.testvm2.start()]))
  290. with self.qrexec_policy('test.Retcode', self.testvm1, self.testvm2):
  291. self.create_remote_file(self.testvm2, '/etc/qubes-rpc/test.Retcode',
  292. 'exit 0')
  293. (stdout, stderr) = self.loop.run_until_complete(
  294. self.testvm1.run_for_stdio('''\
  295. /usr/lib/qubes/qrexec-client-vm {} test.Retcode;
  296. echo $?'''.format(self.testvm2.name),
  297. stderr=None))
  298. self.assertEqual(stdout, b'0\n')
  299. self.create_remote_file(self.testvm2, '/etc/qubes-rpc/test.Retcode',
  300. 'exit 3')
  301. (stdout, stderr) = self.loop.run_until_complete(
  302. self.testvm1.run_for_stdio('''\
  303. /usr/lib/qubes/qrexec-client-vm {} test.Retcode;
  304. echo $?'''.format(self.testvm2.name),
  305. stderr=None))
  306. self.assertEqual(stdout, b'3\n')
  307. def test_070_qrexec_vm_simultaneous_write(self):
  308. """Test for simultaneous write in VM(src)->VM(dst) connection
  309. This is regression test for #1347
  310. Check for deadlock when initially both sides writes a lot of data
  311. (and not read anything). When one side starts reading, it should
  312. get the data and the remote side should be possible to write then more.
  313. There was a bug where remote side was waiting on write(2) and not
  314. handling anything else.
  315. """
  316. self.loop.run_until_complete(asyncio.wait([
  317. self.testvm1.start(),
  318. self.testvm2.start()]))
  319. self.create_remote_file(self.testvm2, '/etc/qubes-rpc/test.write', '''\
  320. # first write a lot of data
  321. dd if=/dev/zero bs=993 count=10000 iflag=fullblock
  322. # and only then read something
  323. dd of=/dev/null bs=993 count=10000 iflag=fullblock
  324. ''')
  325. with self.qrexec_policy('test.write', self.testvm1, self.testvm2):
  326. try:
  327. self.loop.run_until_complete(asyncio.wait_for(
  328. # first write a lot of data to fill all the buffers
  329. # then after some time start reading
  330. self.testvm1.run_for_stdio('''\
  331. /usr/lib/qubes/qrexec-client-vm {} test.write \
  332. /bin/sh -c '
  333. dd if=/dev/zero bs=993 count=10000 iflag=fullblock &
  334. sleep 1;
  335. dd of=/dev/null bs=993 count=10000 iflag=fullblock;
  336. wait'
  337. '''.format(self.testvm2.name)), timeout=10))
  338. except subprocess.CalledProcessError as e:
  339. self.fail('{} exited with non-zero code {}; stderr: {}'.format(
  340. e.cmd, e.returncode, e.stderr))
  341. except asyncio.TimeoutError:
  342. self.fail('Timeout, probably deadlock')
  343. def test_071_qrexec_dom0_simultaneous_write(self):
  344. """Test for simultaneous write in dom0(src)->VM(dst) connection
  345. Similar to test_070_qrexec_vm_simultaneous_write, but with dom0
  346. as a source.
  347. """
  348. self.loop.run_until_complete(self.testvm2.start())
  349. self.create_remote_file(self.testvm2, '/etc/qubes-rpc/test.write', '''\
  350. # first write a lot of data
  351. dd if=/dev/zero bs=993 count=10000 iflag=fullblock
  352. # and only then read something
  353. dd of=/dev/null bs=993 count=10000 iflag=fullblock
  354. ''')
  355. # can't use subprocess.PIPE, because asyncio will claim those FDs
  356. pipe1_r, pipe1_w = os.pipe()
  357. pipe2_r, pipe2_w = os.pipe()
  358. try:
  359. local_proc = self.loop.run_until_complete(
  360. asyncio.create_subprocess_shell(
  361. # first write a lot of data to fill all the buffers
  362. "dd if=/dev/zero bs=993 count=10000 iflag=fullblock & "
  363. # then after some time start reading
  364. "sleep 1; "
  365. "dd of=/dev/null bs=993 count=10000 iflag=fullblock; "
  366. "wait", stdin=pipe1_r, stdout=pipe2_w))
  367. self.service_proc = self.loop.run_until_complete(self.testvm2.run_service(
  368. "test.write", stdin=pipe2_r, stdout=pipe1_w))
  369. finally:
  370. os.close(pipe1_r)
  371. os.close(pipe1_w)
  372. os.close(pipe2_r)
  373. os.close(pipe2_w)
  374. try:
  375. self.loop.run_until_complete(
  376. asyncio.wait_for(self.service_proc.wait(), timeout=10))
  377. except asyncio.TimeoutError:
  378. self.fail("Timeout, probably deadlock")
  379. else:
  380. self.assertEqual(self.service_proc.returncode, 0,
  381. "Service call failed")
  382. def test_072_qrexec_to_dom0_simultaneous_write(self):
  383. """Test for simultaneous write in dom0(src)<-VM(dst) connection
  384. Similar to test_071_qrexec_dom0_simultaneous_write, but with dom0
  385. as a "hanging" side.
  386. """
  387. self.loop.run_until_complete(self.testvm2.start())
  388. self.create_remote_file(self.testvm2, '/etc/qubes-rpc/test.write', '''\
  389. # first write a lot of data
  390. dd if=/dev/zero bs=993 count=10000 iflag=fullblock &
  391. # and only then read something
  392. dd of=/dev/null bs=993 count=10000 iflag=fullblock
  393. sleep 1;
  394. wait
  395. ''')
  396. # can't use subprocess.PIPE, because asyncio will claim those FDs
  397. pipe1_r, pipe1_w = os.pipe()
  398. pipe2_r, pipe2_w = os.pipe()
  399. try:
  400. local_proc = self.loop.run_until_complete(
  401. asyncio.create_subprocess_shell(
  402. # first write a lot of data to fill all the buffers
  403. "dd if=/dev/zero bs=993 count=10000 iflag=fullblock & "
  404. # then, only when all written, read something
  405. "dd of=/dev/null bs=993 count=10000 iflag=fullblock; ",
  406. stdin=pipe1_r, stdout=pipe2_w))
  407. self.service_proc = self.loop.run_until_complete(
  408. self.testvm2.run_service(
  409. "test.write", stdin=pipe2_r, stdout=pipe1_w))
  410. finally:
  411. os.close(pipe1_r)
  412. os.close(pipe1_w)
  413. os.close(pipe2_r)
  414. os.close(pipe2_w)
  415. try:
  416. self.loop.run_until_complete(
  417. asyncio.wait_for(self.service_proc.wait(), timeout=10))
  418. except asyncio.TimeoutError:
  419. self.fail("Timeout, probably deadlock")
  420. else:
  421. self.assertEqual(self.service_proc.returncode, 0,
  422. "Service call failed")
  423. def test_080_qrexec_service_argument_allow_default(self):
  424. """Qrexec service call with argument"""
  425. self.loop.run_until_complete(asyncio.wait([
  426. self.testvm1.start(),
  427. self.testvm2.start()]))
  428. self.create_remote_file(self.testvm2, '/etc/qubes-rpc/test.Argument',
  429. '/usr/bin/printf %s "$1"')
  430. with self.qrexec_policy('test.Argument', self.testvm1, self.testvm2):
  431. stdout, stderr = self.loop.run_until_complete(
  432. self.testvm1.run_for_stdio('/usr/lib/qubes/qrexec-client-vm '
  433. '{} test.Argument+argument'.format(self.testvm2.name),
  434. stderr=None))
  435. self.assertEqual(stdout, b'argument')
  436. def test_081_qrexec_service_argument_allow_specific(self):
  437. """Qrexec service call with argument - allow only specific value"""
  438. self.loop.run_until_complete(asyncio.wait([
  439. self.testvm1.start(),
  440. self.testvm2.start()]))
  441. self.create_remote_file(self.testvm2, '/etc/qubes-rpc/test.Argument',
  442. '/usr/bin/printf %s "$1"')
  443. with self.qrexec_policy('test.Argument', '$anyvm', '$anyvm', False):
  444. with self.qrexec_policy('test.Argument+argument',
  445. self.testvm1.name, self.testvm2.name):
  446. stdout, stderr = self.loop.run_until_complete(
  447. self.testvm1.run_for_stdio(
  448. '/usr/lib/qubes/qrexec-client-vm '
  449. '{} test.Argument+argument'.format(self.testvm2.name),
  450. stderr=None))
  451. self.assertEqual(stdout, b'argument')
  452. def test_082_qrexec_service_argument_deny_specific(self):
  453. """Qrexec service call with argument - deny specific value"""
  454. self.loop.run_until_complete(asyncio.wait([
  455. self.testvm1.start(),
  456. self.testvm2.start()]))
  457. self.create_remote_file(self.testvm2, '/etc/qubes-rpc/test.Argument',
  458. '/usr/bin/printf %s "$1"')
  459. with self.qrexec_policy('test.Argument', '$anyvm', '$anyvm'):
  460. with self.qrexec_policy('test.Argument+argument',
  461. self.testvm1, self.testvm2, allow=False):
  462. with self.assertRaises(subprocess.CalledProcessError,
  463. msg='Service request should be denied'):
  464. self.loop.run_until_complete(
  465. self.testvm1.run_for_stdio(
  466. '/usr/lib/qubes/qrexec-client-vm {} '
  467. 'test.Argument+argument'.format(self.testvm2.name),
  468. stderr=None))
  469. def test_083_qrexec_service_argument_specific_implementation(self):
  470. """Qrexec service call with argument - argument specific
  471. implementatation"""
  472. self.loop.run_until_complete(asyncio.wait([
  473. self.testvm1.start(),
  474. self.testvm2.start()]))
  475. self.create_remote_file(self.testvm2,
  476. '/etc/qubes-rpc/test.Argument',
  477. '/usr/bin/printf %s "$1"')
  478. self.create_remote_file(self.testvm2,
  479. '/etc/qubes-rpc/test.Argument+argument',
  480. '/usr/bin/printf "specific: %s" "$1"')
  481. with self.qrexec_policy('test.Argument', self.testvm1, self.testvm2):
  482. stdout, stderr = self.loop.run_until_complete(
  483. self.testvm1.run_for_stdio('/usr/lib/qubes/qrexec-client-vm '
  484. '{} test.Argument+argument'.format(self.testvm2.name),
  485. stderr=None))
  486. self.assertEqual(stdout, b'specific: argument')
  487. def test_084_qrexec_service_argument_extra_env(self):
  488. """Qrexec service call with argument - extra env variables"""
  489. self.loop.run_until_complete(asyncio.wait([
  490. self.testvm1.start(),
  491. self.testvm2.start()]))
  492. self.create_remote_file(self.testvm2, '/etc/qubes-rpc/test.Argument',
  493. '/usr/bin/printf "%s %s" '
  494. '"$QREXEC_SERVICE_FULL_NAME" "$QREXEC_SERVICE_ARGUMENT"')
  495. with self.qrexec_policy('test.Argument', self.testvm1, self.testvm2):
  496. stdout, stderr = self.loop.run_until_complete(
  497. self.testvm1.run_for_stdio('/usr/lib/qubes/qrexec-client-vm '
  498. '{} test.Argument+argument'.format(self.testvm2.name),
  499. stderr=None))
  500. self.assertEqual(stdout, b'test.Argument+argument argument')
  501. def test_090_qrexec_service_socket_dom0(self):
  502. """Basic test socket services (dom0) - data receive"""
  503. self.loop.run_until_complete(self.testvm1.start())
  504. self.service_proc = self.loop.run_until_complete(
  505. asyncio.create_subprocess_shell(
  506. 'socat -u UNIX-LISTEN:/etc/qubes-rpc/test.Socket,mode=666 -',
  507. stdout=subprocess.PIPE, stdin=subprocess.PIPE))
  508. try:
  509. with self.qrexec_policy('test.Socket', self.testvm1, '@adminvm'):
  510. (stdout, stderr) = self.loop.run_until_complete(asyncio.wait_for(
  511. self.testvm1.run_for_stdio(
  512. 'qrexec-client-vm @adminvm test.Socket', input=TEST_DATA),
  513. timeout=10))
  514. except subprocess.CalledProcessError as e:
  515. self.fail('{} exited with non-zero code {}; stderr: {}'.format(
  516. e.cmd, e.returncode, e.stderr))
  517. except asyncio.TimeoutError:
  518. self.fail(
  519. "service timeout, probably EOF wasn't transferred to the VM process")
  520. try:
  521. (service_stdout, service_stderr) = self.loop.run_until_complete(
  522. asyncio.wait_for(
  523. self.service_proc.communicate(),
  524. timeout=10))
  525. except asyncio.TimeoutError:
  526. self.fail(
  527. "socat timeout, probably EOF wasn't transferred to the VM process")
  528. service_descriptor = b'test.Socket+ test-inst-vm1 keyword adminvm\0'
  529. self.assertEqual(service_stdout, service_descriptor + TEST_DATA,
  530. 'Received data differs from what was sent')
  531. self.assertFalse(stderr,
  532. 'Some data was printed to stderr')
  533. self.assertFalse(service_stderr,
  534. 'Some data was printed to stderr')
  535. def test_091_qrexec_service_socket_dom0_send(self):
  536. """Basic test socket services (dom0) - data send"""
  537. self.loop.run_until_complete(self.testvm1.start())
  538. self.create_local_file('/tmp/service-input', TEST_DATA.decode())
  539. self.service_proc = self.loop.run_until_complete(
  540. asyncio.create_subprocess_shell(
  541. 'socat -u OPEN:/tmp/service-input UNIX-LISTEN:/etc/qubes-rpc/test.Socket,mode=666'))
  542. try:
  543. with self.qrexec_policy('test.Socket', self.testvm1, '@adminvm'):
  544. stdout, stderr = self.loop.run_until_complete(asyncio.wait_for(
  545. self.testvm1.run_for_stdio(
  546. 'qrexec-client-vm @adminvm test.Socket'),
  547. timeout=10))
  548. except subprocess.CalledProcessError as e:
  549. self.fail('{} exited with non-zero code {}; stderr: {}'.format(
  550. e.cmd, e.returncode, e.stderr))
  551. except asyncio.TimeoutError:
  552. self.fail(
  553. "service timeout, probably EOF wasn't transferred to the VM process")
  554. try:
  555. (service_stdout, service_stderr) = self.loop.run_until_complete(
  556. asyncio.wait_for(
  557. self.service_proc.communicate(),
  558. timeout=10))
  559. except asyncio.TimeoutError:
  560. self.fail(
  561. "socat timeout, probably EOF wasn't transferred to the VM process")
  562. self.assertEqual(stdout, TEST_DATA,
  563. 'Received data differs from what was sent')
  564. self.assertFalse(stderr,
  565. 'Some data was printed to stderr')
  566. self.assertFalse(service_stderr,
  567. 'Some data was printed to stderr')
  568. def test_092_qrexec_service_socket_dom0_eof_reverse(self):
  569. """Test for EOF transmission dom0(socket)->VM"""
  570. self.loop.run_until_complete(self.testvm1.start())
  571. self.create_local_file(
  572. '/tmp/service_script',
  573. '#!/usr/bin/python3\n'
  574. 'import socket, os, sys, time\n'
  575. 's = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\n'
  576. 'os.umask(0)\n'
  577. 's.bind("/etc/qubes-rpc/test.Socket")\n'
  578. 's.listen(1)\n'
  579. 'conn, addr = s.accept()\n'
  580. 'conn.send(b"test\\n")\n'
  581. 'conn.shutdown(socket.SHUT_WR)\n'
  582. # wait longer than the timeout below
  583. 'time.sleep(15)\n'
  584. )
  585. self.service_proc = self.loop.run_until_complete(
  586. asyncio.create_subprocess_shell('python3 /tmp/service_script',
  587. stdout=subprocess.PIPE, stdin=subprocess.PIPE))
  588. try:
  589. with self.qrexec_policy('test.Socket', self.testvm1, '@adminvm'):
  590. p = self.loop.run_until_complete(self.testvm1.run(
  591. 'qrexec-client-vm @adminvm test.Socket',
  592. stdout=subprocess.PIPE, stdin=subprocess.PIPE))
  593. stdout = self.loop.run_until_complete(asyncio.wait_for(
  594. p.stdout.read(),
  595. timeout=10))
  596. except asyncio.TimeoutError:
  597. self.fail(
  598. "service timeout, probably EOF wasn't transferred from the VM process")
  599. self.assertEqual(stdout, b'test\n',
  600. 'Received data differs from what was expected')
  601. def test_093_qrexec_service_socket_dom0_eof(self):
  602. """Test for EOF transmission VM->dom0(socket)"""
  603. self.loop.run_until_complete(self.testvm1.start())
  604. self.create_local_file(
  605. '/tmp/service_script',
  606. '#!/usr/bin/python3\n'
  607. 'import socket, os, sys, time\n'
  608. 's = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\n'
  609. 'os.umask(0)\n'
  610. 's.bind("/etc/qubes-rpc/test.Socket")\n'
  611. 's.listen(1)\n'
  612. 'conn, addr = s.accept()\n'
  613. 'buf = conn.recv(100)\n'
  614. 'sys.stdout.buffer.write(buf)\n'
  615. 'buf = conn.recv(10)\n'
  616. 'sys.stdout.buffer.write(buf)\n'
  617. 'sys.stdout.buffer.flush()\n'
  618. 'os.close(1)\n'
  619. # wait longer than the timeout below
  620. 'time.sleep(15)\n'
  621. )
  622. self.service_proc = self.loop.run_until_complete(
  623. asyncio.create_subprocess_shell('python3 /tmp/service_script',
  624. stdout=subprocess.PIPE, stdin=subprocess.PIPE))
  625. try:
  626. with self.qrexec_policy('test.Socket', self.testvm1, '@adminvm'):
  627. p = self.loop.run_until_complete(self.testvm1.run(
  628. 'qrexec-client-vm @adminvm test.Socket',
  629. stdin=subprocess.PIPE))
  630. p.stdin.write(b'test1test2')
  631. p.stdin.write_eof()
  632. service_stdout = self.loop.run_until_complete(asyncio.wait_for(
  633. self.service_proc.stdout.read(),
  634. timeout=10))
  635. except asyncio.TimeoutError:
  636. self.fail(
  637. "service timeout, probably EOF wasn't transferred from the VM process")
  638. service_descriptor = b'test.Socket+ test-inst-vm1 keyword adminvm\0'
  639. self.assertEqual(service_stdout, service_descriptor + b'test1test2',
  640. 'Received data differs from what was expected')
  641. def _wait_for_socket_setup(self):
  642. try:
  643. self.loop.run_until_complete(asyncio.wait_for(
  644. self.testvm1.run_for_stdio(
  645. 'while ! test -e /etc/qubes-rpc/test.Socket; do sleep 0.1; done'),
  646. timeout=10))
  647. except asyncio.TimeoutError:
  648. self.fail(
  649. "waiting for /etc/qubes-rpc/test.Socket in VM timed out")
  650. def test_095_qrexec_service_socket_vm(self):
  651. """Basic test socket services (VM) - receive"""
  652. self.loop.run_until_complete(self.testvm1.start())
  653. self.service_proc = self.loop.run_until_complete(self.testvm1.run(
  654. 'socat -u UNIX-LISTEN:/etc/qubes-rpc/test.Socket,mode=666 -',
  655. stdout=subprocess.PIPE, stdin=subprocess.PIPE,
  656. user='root'))
  657. self._wait_for_socket_setup()
  658. try:
  659. (stdout, stderr) = self.loop.run_until_complete(asyncio.wait_for(
  660. self.testvm1.run_service_for_stdio('test.Socket+', input=TEST_DATA),
  661. timeout=10))
  662. except subprocess.CalledProcessError as e:
  663. self.fail('{} exited with non-zero code {}; stderr: {}'.format(
  664. e.cmd, e.returncode, e.stderr))
  665. except asyncio.TimeoutError:
  666. self.fail(
  667. "service timeout, probably EOF wasn't transferred to the VM process")
  668. try:
  669. (service_stdout, service_stderr) = self.loop.run_until_complete(
  670. asyncio.wait_for(
  671. self.service_proc.communicate(),
  672. timeout=10))
  673. except asyncio.TimeoutError:
  674. self.fail(
  675. "socat timeout, probably EOF wasn't transferred to the VM process")
  676. service_descriptor = b'test.Socket+ dom0\0'
  677. self.assertEqual(service_stdout, service_descriptor + TEST_DATA,
  678. 'Received data differs from what was sent')
  679. self.assertFalse(stderr,
  680. 'Some data was printed to stderr')
  681. self.assertFalse(service_stderr,
  682. 'Some data was printed to stderr')
  683. def test_096_qrexec_service_socket_vm_send(self):
  684. """Basic test socket services (VM) - send"""
  685. self.loop.run_until_complete(self.testvm1.start())
  686. self.create_remote_file(self.testvm1,
  687. '/tmp/service-input',
  688. TEST_DATA.decode())
  689. self.service_proc = self.loop.run_until_complete(self.testvm1.run(
  690. 'socat -u OPEN:/tmp/service-input UNIX-LISTEN:/etc/qubes-rpc/test.Socket,mode=666',
  691. user='root'))
  692. self._wait_for_socket_setup()
  693. try:
  694. (stdout, stderr) = self.loop.run_until_complete(asyncio.wait_for(
  695. self.testvm1.run_service_for_stdio('test.Socket+'),
  696. timeout=10))
  697. except subprocess.CalledProcessError as e:
  698. self.fail('{} exited with non-zero code {}; stderr: {}'.format(
  699. e.cmd, e.returncode, e.stderr))
  700. except asyncio.TimeoutError:
  701. self.fail(
  702. "service timeout, probably EOF wasn't transferred to the VM process")
  703. try:
  704. (service_stdout, service_stderr) = self.loop.run_until_complete(
  705. asyncio.wait_for(
  706. self.service_proc.communicate(),
  707. timeout=10))
  708. except asyncio.TimeoutError:
  709. self.fail(
  710. "socat timeout, probably EOF wasn't transferred to the VM process")
  711. self.assertEqual(stdout, TEST_DATA,
  712. 'Received data differs from what was sent')
  713. self.assertFalse(stderr,
  714. 'Some data was printed to stderr')
  715. self.assertFalse(service_stderr,
  716. 'Some data was printed to stderr')
  717. def test_097_qrexec_service_socket_vm_eof_reverse(self):
  718. """Test for EOF transmission VM(socket)->dom0"""
  719. self.loop.run_until_complete(self.testvm1.start())
  720. self.create_remote_file(self.testvm1,
  721. '/tmp/service_script',
  722. '#!/usr/bin/python3\n'
  723. 'import socket, os, sys, time\n'
  724. 's = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\n'
  725. 'os.umask(0)\n'
  726. 's.bind("/etc/qubes-rpc/test.Socket")\n'
  727. 's.listen(1)\n'
  728. 'conn, addr = s.accept()\n'
  729. 'conn.send(b"test\\n")\n'
  730. 'conn.shutdown(socket.SHUT_WR)\n'
  731. # wait longer than the timeout below
  732. 'time.sleep(15)\n'
  733. )
  734. self.service_proc = self.loop.run_until_complete(self.testvm1.run(
  735. 'python3 /tmp/service_script',
  736. stdout=subprocess.PIPE, stdin=subprocess.PIPE,
  737. user='root'))
  738. self._wait_for_socket_setup()
  739. try:
  740. p = self.loop.run_until_complete(
  741. self.testvm1.run_service('test.Socket+',
  742. stdin=subprocess.PIPE, stdout=subprocess.PIPE))
  743. stdout = self.loop.run_until_complete(asyncio.wait_for(p.stdout.read(),
  744. timeout=10))
  745. except asyncio.TimeoutError:
  746. p.terminate()
  747. self.fail(
  748. "service timeout, probably EOF wasn't transferred from the VM process")
  749. finally:
  750. self.loop.run_until_complete(p.wait())
  751. self.assertEqual(stdout,
  752. b'test\n',
  753. 'Received data differs from what was expected')
  754. def test_098_qrexec_service_socket_vm_eof(self):
  755. """Test for EOF transmission dom0->VM(socket)"""
  756. self.loop.run_until_complete(self.testvm1.start())
  757. self.create_remote_file(
  758. self.testvm1,
  759. '/tmp/service_script',
  760. '#!/usr/bin/python3\n'
  761. 'import socket, os, sys, time\n'
  762. 's = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\n'
  763. 'os.umask(0)\n'
  764. 's.bind("/etc/qubes-rpc/test.Socket")\n'
  765. 's.listen(1)\n'
  766. 'conn, addr = s.accept()\n'
  767. 'buf = conn.recv(100)\n'
  768. 'sys.stdout.buffer.write(buf)\n'
  769. 'buf = conn.recv(10)\n'
  770. 'sys.stdout.buffer.write(buf)\n'
  771. 'sys.stdout.buffer.flush()\n'
  772. 'os.close(1)\n'
  773. # wait longer than the timeout below
  774. 'time.sleep(15)\n'
  775. )
  776. self.service_proc = self.loop.run_until_complete(self.testvm1.run(
  777. 'python3 /tmp/service_script',
  778. stdout=subprocess.PIPE, stdin=subprocess.PIPE,
  779. user='root'))
  780. self._wait_for_socket_setup()
  781. try:
  782. p = self.loop.run_until_complete(
  783. self.testvm1.run_service('test.Socket+',
  784. stdin=subprocess.PIPE, stdout=subprocess.PIPE))
  785. p.stdin.write(b'test1test2')
  786. self.loop.run_until_complete(
  787. asyncio.wait_for(p.stdin.drain(), timeout=10))
  788. p.stdin.close()
  789. service_stdout = self.loop.run_until_complete(asyncio.wait_for(
  790. self.service_proc.stdout.read(),
  791. timeout=10))
  792. except asyncio.TimeoutError:
  793. p.terminate()
  794. self.fail(
  795. "service timeout, probably EOF wasn't transferred to the VM process")
  796. finally:
  797. self.loop.run_until_complete(p.wait())
  798. service_descriptor = b'test.Socket+ dom0\0'
  799. self.assertEqual(service_stdout, service_descriptor + b'test1test2',
  800. 'Received data differs from what was expected')
  801. def test_100_qrexec_filecopy(self):
  802. self.loop.run_until_complete(asyncio.wait([
  803. self.testvm1.start(),
  804. self.testvm2.start()]))
  805. self.loop.run_until_complete(self.testvm1.run_for_stdio(
  806. 'cp /etc/passwd /tmp/passwd'))
  807. with self.qrexec_policy('qubes.Filecopy', self.testvm1, self.testvm2):
  808. try:
  809. self.loop.run_until_complete(
  810. self.testvm1.run_for_stdio(
  811. 'qvm-copy-to-vm {} /tmp/passwd'.format(
  812. self.testvm2.name)))
  813. except subprocess.CalledProcessError as e:
  814. self.fail('qvm-copy-to-vm failed: {}'.format(e.stderr))
  815. try:
  816. self.loop.run_until_complete(self.testvm2.run_for_stdio(
  817. 'diff /etc/passwd /home/user/QubesIncoming/{}/passwd'.format(
  818. self.testvm1.name)))
  819. except subprocess.CalledProcessError:
  820. self.fail('file differs')
  821. try:
  822. self.loop.run_until_complete(self.testvm1.run_for_stdio(
  823. 'test -f /tmp/passwd'))
  824. except subprocess.CalledProcessError:
  825. self.fail('source file got removed')
  826. def test_105_qrexec_filemove(self):
  827. self.loop.run_until_complete(asyncio.wait([
  828. self.testvm1.start(),
  829. self.testvm2.start()]))
  830. self.loop.run_until_complete(self.testvm1.run_for_stdio(
  831. 'cp /etc/passwd /tmp/passwd'))
  832. with self.qrexec_policy('qubes.Filecopy', self.testvm1, self.testvm2):
  833. try:
  834. self.loop.run_until_complete(
  835. self.testvm1.run_for_stdio(
  836. 'qvm-move-to-vm {} /tmp/passwd'.format(
  837. self.testvm2.name)))
  838. except subprocess.CalledProcessError as e:
  839. self.fail('qvm-move-to-vm failed: {}'.format(e.stderr))
  840. try:
  841. self.loop.run_until_complete(self.testvm2.run_for_stdio(
  842. 'diff /etc/passwd /home/user/QubesIncoming/{}/passwd'.format(
  843. self.testvm1.name)))
  844. except subprocess.CalledProcessError:
  845. self.fail('file differs')
  846. with self.assertRaises(subprocess.CalledProcessError):
  847. self.loop.run_until_complete(self.testvm1.run_for_stdio(
  848. 'test -f /tmp/passwd'))
  849. def test_101_qrexec_filecopy_with_autostart(self):
  850. self.loop.run_until_complete(self.testvm1.start())
  851. with self.qrexec_policy('qubes.Filecopy', self.testvm1, self.testvm2):
  852. try:
  853. self.loop.run_until_complete(
  854. self.testvm1.run_for_stdio(
  855. 'qvm-copy-to-vm {} /etc/passwd'.format(
  856. self.testvm2.name)))
  857. except subprocess.CalledProcessError as e:
  858. self.fail('qvm-copy-to-vm failed: {}'.format(e.stderr))
  859. # workaround for libvirt bug (domain ID isn't updated when is started
  860. # from other application) - details in
  861. # QubesOS/qubes-core-libvirt@63ede4dfb4485c4161dd6a2cc809e8fb45ca664f
  862. # XXX is it still true with qubesd? --woju 20170523
  863. self.testvm2._libvirt_domain = None
  864. self.assertTrue(self.testvm2.is_running())
  865. try:
  866. self.loop.run_until_complete(self.testvm2.run_for_stdio(
  867. 'diff /etc/passwd /home/user/QubesIncoming/{}/passwd'.format(
  868. self.testvm1.name)))
  869. except subprocess.CalledProcessError:
  870. self.fail('file differs')
  871. try:
  872. self.loop.run_until_complete(self.testvm1.run_for_stdio(
  873. 'test -f /etc/passwd'))
  874. except subprocess.CalledProcessError:
  875. self.fail('source file got removed')
  876. def test_110_qrexec_filecopy_deny(self):
  877. self.loop.run_until_complete(asyncio.wait([
  878. self.testvm1.start(),
  879. self.testvm2.start()]))
  880. with self.qrexec_policy('qubes.Filecopy', self.testvm1, self.testvm2,
  881. allow=False):
  882. with self.assertRaises(subprocess.CalledProcessError):
  883. self.loop.run_until_complete(
  884. self.testvm1.run_for_stdio(
  885. 'qvm-copy-to-vm {} /etc/passwd'.format(
  886. self.testvm2.name)))
  887. with self.assertRaises(subprocess.CalledProcessError):
  888. self.loop.run_until_complete(self.testvm1.run_for_stdio(
  889. 'test -d /home/user/QubesIncoming/{}'.format(
  890. self.testvm1.name)))
  891. def test_115_qrexec_filecopy_no_agent(self):
  892. # The operation should not hang when qrexec-agent is down on target
  893. # machine, see QubesOS/qubes-issues#5347.
  894. self.loop.run_until_complete(asyncio.wait([
  895. self.testvm1.start(),
  896. self.testvm2.start()]))
  897. with self.qrexec_policy('qubes.Filecopy', self.testvm1, self.testvm2):
  898. try:
  899. self.loop.run_until_complete(
  900. self.testvm2.run_for_stdio(
  901. 'systemctl stop qubes-qrexec-agent.service', user='root'))
  902. except subprocess.CalledProcessError:
  903. # A failure is normal here, because we're killing the qrexec
  904. # process that is handling the command.
  905. pass
  906. with self.assertRaises(subprocess.CalledProcessError):
  907. self.loop.run_until_complete(
  908. asyncio.wait_for(
  909. self.testvm1.run_for_stdio(
  910. 'qvm-copy-to-vm {} /etc/passwd'.format(
  911. self.testvm2.name)),
  912. timeout=30))
  913. @unittest.skip("Xen gntalloc driver crashes when page is mapped in the "
  914. "same domain")
  915. def test_120_qrexec_filecopy_self(self):
  916. self.testvm1.start()
  917. self.qrexec_policy('qubes.Filecopy', self.testvm1.name,
  918. self.testvm1.name)
  919. p = self.testvm1.run("qvm-copy-to-vm %s /etc/passwd" %
  920. self.testvm1.name, passio_popen=True,
  921. passio_stderr=True)
  922. p.wait()
  923. self.assertEqual(p.returncode, 0, "qvm-copy-to-vm failed: %s" %
  924. p.stderr.read())
  925. retcode = self.testvm1.run(
  926. "diff /etc/passwd /home/user/QubesIncoming/{}/passwd".format(
  927. self.testvm1.name),
  928. wait=True)
  929. self.assertEqual(retcode, 0, "file differs")
  930. @unittest.skipUnless(spawn.find_executable('xdotool'),
  931. "xdotool not installed")
  932. def test_130_qrexec_filemove_disk_full(self):
  933. self.loop.run_until_complete(asyncio.wait([
  934. self.testvm1.start(),
  935. self.testvm2.start()]))
  936. self.loop.run_until_complete(self.wait_for_session(self.testvm1))
  937. # Prepare test file
  938. self.loop.run_until_complete(self.testvm1.run_for_stdio(
  939. 'yes teststring | dd of=/tmp/testfile bs=1M count=50 '
  940. 'iflag=fullblock'))
  941. # Prepare target directory with limited size
  942. self.loop.run_until_complete(self.testvm2.run_for_stdio(
  943. 'mkdir -p /home/user/QubesIncoming && '
  944. 'chown user /home/user/QubesIncoming && '
  945. 'mount -t tmpfs none /home/user/QubesIncoming -o size=48M',
  946. user='root'))
  947. with self.qrexec_policy('qubes.Filecopy', self.testvm1, self.testvm2):
  948. p = self.loop.run_until_complete(self.testvm1.run(
  949. 'qvm-move-to-vm {} /tmp/testfile'.format(
  950. self.testvm2.name)))
  951. # Close GUI error message
  952. try:
  953. self.enter_keys_in_window('Error', ['Return'])
  954. except subprocess.CalledProcessError:
  955. pass
  956. self.loop.run_until_complete(p.wait())
  957. self.assertNotEqual(p.returncode, 0)
  958. # the file shouldn't be removed in source vm
  959. self.loop.run_until_complete(self.testvm1.run_for_stdio(
  960. 'test -f /tmp/testfile'))
  961. def test_200_timezone(self):
  962. """Test whether timezone setting is properly propagated to the VM"""
  963. if "whonix" in self.template:
  964. self.skipTest("Timezone propagation disabled on Whonix templates")
  965. self.loop.run_until_complete(self.testvm1.start())
  966. vm_tz, _ = self.loop.run_until_complete(self.testvm1.run_for_stdio(
  967. 'date +%Z'))
  968. dom0_tz = subprocess.check_output(['date', '+%Z'])
  969. self.assertEqual(vm_tz.strip(), dom0_tz.strip())
  970. # Check if reverting back to UTC works
  971. vm_tz, _ = self.loop.run_until_complete(self.testvm1.run_for_stdio(
  972. 'TZ=UTC date +%Z'))
  973. self.assertEqual(vm_tz.strip(), b'UTC')
  974. def test_210_time_sync(self):
  975. """Test time synchronization mechanism"""
  976. if self.template.startswith('whonix-'):
  977. self.skipTest('qvm-sync-clock disabled for Whonix VMs')
  978. self.loop.run_until_complete(asyncio.wait([
  979. self.testvm1.start(),
  980. self.testvm2.start(),]))
  981. start_time = subprocess.check_output(['date', '-u', '+%s'])
  982. try:
  983. self.app.clockvm = self.testvm1
  984. self.app.save()
  985. # break vm and dom0 time, to check if qvm-sync-clock would fix it
  986. subprocess.check_call(['sudo', 'date', '-s', '2001-01-01T12:34:56'],
  987. stdout=subprocess.DEVNULL)
  988. self.loop.run_until_complete(
  989. self.testvm2.run_for_stdio('date -s 2001-01-01T12:34:56',
  990. user='root'))
  991. self.loop.run_until_complete(
  992. self.testvm2.run_for_stdio('qvm-sync-clock',
  993. user='root'))
  994. p = self.loop.run_until_complete(
  995. asyncio.create_subprocess_exec('sudo', 'qvm-sync-clock',
  996. stdout=asyncio.subprocess.DEVNULL))
  997. self.loop.run_until_complete(p.wait())
  998. self.assertEqual(p.returncode, 0)
  999. vm_time, _ = self.loop.run_until_complete(
  1000. self.testvm2.run_for_stdio('date -u +%s'))
  1001. self.assertAlmostEquals(int(vm_time), int(start_time), delta=30)
  1002. dom0_time = subprocess.check_output(['date', '-u', '+%s'])
  1003. self.assertAlmostEquals(int(dom0_time), int(start_time), delta=30)
  1004. except:
  1005. # reset time to some approximation of the real time
  1006. subprocess.Popen(
  1007. ["sudo", "date", "-u", "-s", "@" + start_time.decode()])
  1008. raise
  1009. finally:
  1010. self.app.clockvm = None
  1011. def wait_for_pulseaudio_startup(self, vm):
  1012. self.loop.run_until_complete(
  1013. self.wait_for_session(self.testvm1))
  1014. try:
  1015. self.loop.run_until_complete(vm.run_for_stdio(
  1016. "timeout 30s sh -c 'while ! pactl info; do sleep 1; done'"
  1017. ))
  1018. except subprocess.CalledProcessError as e:
  1019. self.fail('Timeout waiting for pulseaudio start in {}: {}{}'.format(
  1020. vm.name, e.stdout, e.stderr))
  1021. # then wait for the stream to appear in dom0
  1022. local_user = grp.getgrnam('qubes').gr_mem[0]
  1023. p = self.loop.run_until_complete(asyncio.create_subprocess_shell(
  1024. "sudo -E -u {} timeout 30s sh -c '"
  1025. "while ! pactl list sink-inputs | grep -q :{}; do sleep 1; done'".format(
  1026. local_user, vm.name)))
  1027. self.loop.run_until_complete(p.wait())
  1028. # and some more...
  1029. self.loop.run_until_complete(asyncio.sleep(1))
  1030. @unittest.skipUnless(spawn.find_executable('parecord'),
  1031. "pulseaudio-utils not installed in dom0")
  1032. def test_220_audio_playback(self):
  1033. if 'whonix-gw' in self.template:
  1034. self.skipTest('whonix-gw have no audio')
  1035. self.loop.run_until_complete(self.testvm1.start())
  1036. try:
  1037. self.loop.run_until_complete(
  1038. self.testvm1.run_for_stdio('which parecord'))
  1039. except subprocess.CalledProcessError:
  1040. self.skipTest('pulseaudio-utils not installed in VM')
  1041. self.wait_for_pulseaudio_startup(self.testvm1)
  1042. # generate some "audio" data
  1043. audio_in = b'\x20' * 44100
  1044. self.loop.run_until_complete(
  1045. self.testvm1.run_for_stdio('cat > audio_in.raw', input=audio_in))
  1046. local_user = grp.getgrnam('qubes').gr_mem[0]
  1047. with tempfile.NamedTemporaryFile() as recorded_audio:
  1048. os.chmod(recorded_audio.name, 0o666)
  1049. # FIXME: -d 0 assumes only one audio device
  1050. p = subprocess.Popen(['sudo', '-E', '-u', local_user,
  1051. 'parecord', '-d', '0', '--raw', recorded_audio.name],
  1052. stdout=subprocess.PIPE)
  1053. try:
  1054. self.loop.run_until_complete(
  1055. self.testvm1.run_for_stdio('paplay --raw audio_in.raw'))
  1056. except subprocess.CalledProcessError as err:
  1057. self.fail('{} stderr: {}'.format(str(err), err.stderr))
  1058. # wait for possible parecord buffering
  1059. self.loop.run_until_complete(asyncio.sleep(1))
  1060. p.terminate()
  1061. # for some reason sudo do not relay SIGTERM sent above
  1062. subprocess.check_call(['pkill', 'parecord'])
  1063. p.wait()
  1064. # allow up to 20ms missing, don't use assertIn, to avoid printing
  1065. # the whole data in error message
  1066. recorded_audio = recorded_audio.file.read()
  1067. if audio_in[:-3528] not in recorded_audio:
  1068. found_bytes = recorded_audio.count(audio_in[0])
  1069. all_bytes = len(audio_in)
  1070. self.fail('played sound not found in dom0, '
  1071. 'missing {} bytes out of {}'.format(
  1072. all_bytes-found_bytes, all_bytes))
  1073. def _configure_audio_recording(self, vm):
  1074. '''Connect VM's output-source to sink monitor instead of mic'''
  1075. local_user = grp.getgrnam('qubes').gr_mem[0]
  1076. sudo = ['sudo', '-E', '-u', local_user]
  1077. source_outputs = subprocess.check_output(
  1078. sudo + ['pacmd', 'list-source-outputs']).decode()
  1079. last_index = None
  1080. found = False
  1081. for line in source_outputs.splitlines():
  1082. if line.startswith(' index: '):
  1083. last_index = line.split(':')[1].strip()
  1084. elif line.startswith('\t\tapplication.name = '):
  1085. app_name = line.split('=')[1].strip('" ')
  1086. if vm.name == app_name:
  1087. found = True
  1088. break
  1089. if not found:
  1090. self.fail('source-output for VM {} not found'.format(vm.name))
  1091. subprocess.check_call(sudo +
  1092. ['pacmd', 'move-source-output', last_index, '0'])
  1093. @unittest.skipUnless(spawn.find_executable('parecord'),
  1094. "pulseaudio-utils not installed in dom0")
  1095. def test_221_audio_record_muted(self):
  1096. if 'whonix-gw' in self.template:
  1097. self.skipTest('whonix-gw have no audio')
  1098. self.loop.run_until_complete(self.testvm1.start())
  1099. try:
  1100. self.loop.run_until_complete(
  1101. self.testvm1.run_for_stdio('which parecord'))
  1102. except subprocess.CalledProcessError:
  1103. self.skipTest('pulseaudio-utils not installed in VM')
  1104. self.wait_for_pulseaudio_startup(self.testvm1)
  1105. # connect VM's recording source output monitor (instead of mic)
  1106. self._configure_audio_recording(self.testvm1)
  1107. # generate some "audio" data
  1108. audio_in = b'\x20' * 44100
  1109. local_user = grp.getgrnam('qubes').gr_mem[0]
  1110. record = self.loop.run_until_complete(
  1111. self.testvm1.run('parecord --raw audio_rec.raw'))
  1112. # give it time to start recording
  1113. self.loop.run_until_complete(asyncio.sleep(0.5))
  1114. p = subprocess.Popen(['sudo', '-E', '-u', local_user,
  1115. 'paplay', '--raw'],
  1116. stdin=subprocess.PIPE)
  1117. p.communicate(audio_in)
  1118. # wait for possible parecord buffering
  1119. self.loop.run_until_complete(asyncio.sleep(1))
  1120. self.loop.run_until_complete(
  1121. self.testvm1.run_for_stdio('pkill parecord'))
  1122. self.loop.run_until_complete(record.wait())
  1123. recorded_audio, _ = self.loop.run_until_complete(
  1124. self.testvm1.run_for_stdio('cat audio_rec.raw'))
  1125. # should be empty or silence, so check just a little fragment
  1126. if audio_in[:32] in recorded_audio:
  1127. self.fail('VM recorded something, even though mic disabled')
  1128. @unittest.skipUnless(spawn.find_executable('parecord'),
  1129. "pulseaudio-utils not installed in dom0")
  1130. def test_222_audio_record_unmuted(self):
  1131. if 'whonix-gw' in self.template:
  1132. self.skipTest('whonix-gw have no audio')
  1133. self.loop.run_until_complete(self.testvm1.start())
  1134. try:
  1135. self.loop.run_until_complete(
  1136. self.testvm1.run_for_stdio('which parecord'))
  1137. except subprocess.CalledProcessError:
  1138. self.skipTest('pulseaudio-utils not installed in VM')
  1139. self.wait_for_pulseaudio_startup(self.testvm1)
  1140. da = qubes.devices.DeviceAssignment(self.app.domains[0], 'mic')
  1141. self.loop.run_until_complete(
  1142. self.testvm1.devices['mic'].attach(da))
  1143. # connect VM's recording source output monitor (instead of mic)
  1144. self._configure_audio_recording(self.testvm1)
  1145. # generate some "audio" data
  1146. audio_in = b'\x20' * 44100
  1147. local_user = grp.getgrnam('qubes').gr_mem[0]
  1148. record = self.loop.run_until_complete(
  1149. self.testvm1.run('parecord --raw audio_rec.raw'))
  1150. # give it time to start recording
  1151. self.loop.run_until_complete(asyncio.sleep(0.5))
  1152. p = subprocess.Popen(['sudo', '-E', '-u', local_user,
  1153. 'paplay', '--raw'],
  1154. stdin=subprocess.PIPE)
  1155. p.communicate(audio_in)
  1156. # wait for possible parecord buffering
  1157. self.loop.run_until_complete(asyncio.sleep(1))
  1158. self.loop.run_until_complete(
  1159. self.testvm1.run_for_stdio('pkill parecord || :'))
  1160. _, record_stderr = self.loop.run_until_complete(record.communicate())
  1161. if record_stderr:
  1162. self.fail('parecord printed something on stderr: {}'.format(
  1163. record_stderr))
  1164. recorded_audio, _ = self.loop.run_until_complete(
  1165. self.testvm1.run_for_stdio('cat audio_rec.raw'))
  1166. # allow up to 20ms to be missing
  1167. if audio_in[:-3528] not in recorded_audio:
  1168. found_bytes = recorded_audio.count(audio_in[0])
  1169. all_bytes = len(audio_in)
  1170. self.fail('VM not recorded expected data, '
  1171. 'missing {} bytes out of {}'.format(
  1172. all_bytes-found_bytes, all_bytes))
  1173. def test_250_resize_private_img(self):
  1174. """
  1175. Test private.img resize, both offline and online
  1176. :return:
  1177. """
  1178. # First offline test
  1179. self.loop.run_until_complete(
  1180. self.testvm1.storage.resize('private', 4*1024**3))
  1181. self.loop.run_until_complete(self.testvm1.start())
  1182. df_cmd = '( df --output=size /rw || df /rw | awk \'{print $2}\' )|' \
  1183. 'tail -n 1'
  1184. # new_size in 1k-blocks
  1185. new_size, _ = self.loop.run_until_complete(
  1186. self.testvm1.run_for_stdio(df_cmd))
  1187. # some safety margin for FS metadata
  1188. self.assertGreater(int(new_size.strip()), 3.8*1024**2)
  1189. # Then online test
  1190. self.loop.run_until_complete(
  1191. self.testvm1.storage.resize('private', 6*1024**3))
  1192. # new_size in 1k-blocks
  1193. new_size, _ = self.loop.run_until_complete(
  1194. self.testvm1.run_for_stdio(df_cmd))
  1195. # some safety margin for FS metadata
  1196. self.assertGreater(int(new_size.strip()), 5.7*1024**2)
  1197. @unittest.skipUnless(spawn.find_executable('xdotool'),
  1198. "xdotool not installed")
  1199. def test_300_bug_1028_gui_memory_pinning(self):
  1200. """
  1201. If VM window composition buffers are relocated in memory, GUI will
  1202. still use old pointers and will display old pages
  1203. :return:
  1204. """
  1205. # this test does too much asynchronous operations,
  1206. # so let's rewrite it as a coroutine and call it as such
  1207. return self.loop.run_until_complete(
  1208. self._test_300_bug_1028_gui_memory_pinning())
  1209. @asyncio.coroutine
  1210. def _test_300_bug_1028_gui_memory_pinning(self):
  1211. self.testvm1.memory = 800
  1212. self.testvm1.maxmem = 800
  1213. # exclude from memory balancing
  1214. self.testvm1.features['service.meminfo-writer'] = False
  1215. yield from self.testvm1.start()
  1216. yield from self.wait_for_session(self.testvm1)
  1217. # and allow large map count
  1218. yield from self.testvm1.run('echo 256000 > /proc/sys/vm/max_map_count',
  1219. user="root")
  1220. allocator_c = '''
  1221. #include <sys/mman.h>
  1222. #include <stdlib.h>
  1223. #include <stdio.h>
  1224. int main(int argc, char **argv) {
  1225. int total_pages;
  1226. char *addr, *iter;
  1227. total_pages = atoi(argv[1]);
  1228. addr = mmap(NULL, total_pages * 0x1000, PROT_READ | PROT_WRITE,
  1229. MAP_ANONYMOUS | MAP_PRIVATE | MAP_POPULATE, -1, 0);
  1230. if (addr == MAP_FAILED) {
  1231. perror("mmap");
  1232. exit(1);
  1233. }
  1234. printf("Stage1\\n");
  1235. fflush(stdout);
  1236. getchar();
  1237. for (iter = addr; iter < addr + total_pages*0x1000; iter += 0x2000) {
  1238. if (mlock(iter, 0x1000) == -1) {
  1239. perror("mlock");
  1240. fprintf(stderr, "%d of %d\\n", (iter-addr)/0x1000, total_pages);
  1241. exit(1);
  1242. }
  1243. }
  1244. printf("Stage2\\n");
  1245. fflush(stdout);
  1246. for (iter = addr+0x1000; iter < addr + total_pages*0x1000; iter += 0x2000) {
  1247. if (munmap(iter, 0x1000) == -1) {
  1248. perror(\"munmap\");
  1249. exit(1);
  1250. }
  1251. }
  1252. printf("Stage3\\n");
  1253. fflush(stdout);
  1254. fclose(stdout);
  1255. getchar();
  1256. return 0;
  1257. }
  1258. '''
  1259. yield from self.testvm1.run_for_stdio('cat > allocator.c',
  1260. input=allocator_c.encode())
  1261. try:
  1262. yield from self.testvm1.run_for_stdio(
  1263. 'gcc allocator.c -o allocator')
  1264. except subprocess.CalledProcessError as e:
  1265. self.skipTest('allocator compile failed: {}'.format(e.stderr))
  1266. # drop caches to have even more memory pressure
  1267. yield from self.testvm1.run_for_stdio(
  1268. 'echo 3 > /proc/sys/vm/drop_caches', user='root')
  1269. # now fragment all free memory
  1270. stdout, _ = yield from self.testvm1.run_for_stdio(
  1271. "grep ^MemFree: /proc/meminfo|awk '{print $2}'")
  1272. memory_pages = int(stdout) // 4 # 4k pages
  1273. alloc1 = yield from self.testvm1.run(
  1274. 'ulimit -l unlimited; exec /home/user/allocator {}'.format(
  1275. memory_pages),
  1276. user="root",
  1277. stdin=subprocess.PIPE, stdout=subprocess.PIPE,
  1278. stderr=subprocess.PIPE)
  1279. # wait for memory being allocated; can't use just .read(), because EOF
  1280. # passing is unreliable while the process is still running
  1281. alloc1.stdin.write(b'\n')
  1282. yield from alloc1.stdin.drain()
  1283. try:
  1284. alloc_out = yield from alloc1.stdout.readexactly(
  1285. len('Stage1\nStage2\nStage3\n'))
  1286. except asyncio.IncompleteReadError as e:
  1287. alloc_out = e.partial
  1288. if b'Stage3' not in alloc_out:
  1289. # read stderr only in case of failed assert (), but still have nice
  1290. # failure message (don't use self.fail() directly)
  1291. #
  1292. # stderr isn't always read, because on not-failed run, the process
  1293. # is still running, so stderr.read() will wait (indefinitely).
  1294. self.assertIn(b'Stage3', alloc_out,
  1295. (yield from alloc1.stderr.read()))
  1296. # now, launch some window - it should get fragmented composition buffer
  1297. # it is important to have some changing content there, to generate
  1298. # content update events (aka damage notify)
  1299. proc = yield from self.testvm1.run(
  1300. 'xterm -maximized -e top')
  1301. if proc.returncode is not None:
  1302. self.fail('xterm failed to start')
  1303. # get window ID
  1304. winid = yield from self.wait_for_window_coro(
  1305. self.testvm1.name + ':xterm',
  1306. search_class=True)
  1307. xprop = yield from asyncio.get_event_loop().run_in_executor(None,
  1308. subprocess.check_output,
  1309. ['xprop', '-notype', '-id', winid, '_QUBES_VMWINDOWID'])
  1310. vm_winid = xprop.decode().strip().split(' ')[4]
  1311. # now free the fragmented memory and trigger compaction
  1312. alloc1.stdin.write(b'\n')
  1313. yield from alloc1.stdin.drain()
  1314. yield from alloc1.wait()
  1315. yield from self.testvm1.run_for_stdio(
  1316. 'echo 1 > /proc/sys/vm/compact_memory', user='root')
  1317. # now window may be already "broken"; to be sure, allocate (=zero)
  1318. # some memory
  1319. alloc2 = yield from self.testvm1.run(
  1320. 'ulimit -l unlimited; /home/user/allocator {}'.format(memory_pages),
  1321. user='root', stdout=subprocess.PIPE)
  1322. yield from alloc2.stdout.read(len('Stage1\n'))
  1323. # wait for damage notify - top updates every 3 sec by default
  1324. yield from asyncio.sleep(6)
  1325. # stop changing the window content
  1326. subprocess.check_call(['xdotool', 'key', '--window', winid, 'd'])
  1327. # now take screenshot of the window, from dom0 and VM
  1328. # choose pnm format, as it doesn't have any useless metadata - easy
  1329. # to compare
  1330. vm_image, _ = yield from self.testvm1.run_for_stdio(
  1331. 'import -window {} pnm:-'.format(vm_winid))
  1332. dom0_image = yield from asyncio.get_event_loop().run_in_executor(None,
  1333. subprocess.check_output, ['import', '-window', winid, 'pnm:-'])
  1334. if vm_image != dom0_image:
  1335. self.fail("Dom0 window doesn't match VM window content")
  1336. class TC_10_Generic(qubes.tests.SystemTestCase):
  1337. def setUp(self):
  1338. super(TC_10_Generic, self).setUp()
  1339. self.init_default_template()
  1340. self.vm = self.app.add_new_vm(
  1341. qubes.vm.appvm.AppVM,
  1342. name=self.make_vm_name('vm'),
  1343. label='red',
  1344. template=self.app.default_template)
  1345. self.loop.run_until_complete(self.vm.create_on_disk())
  1346. self.app.save()
  1347. self.vm = self.app.domains[self.vm.qid]
  1348. def test_000_anyvm_deny_dom0(self):
  1349. '''$anyvm in policy should not match dom0'''
  1350. policy = open("/etc/qubes-rpc/policy/test.AnyvmDeny", "w")
  1351. policy.write("%s $anyvm allow" % (self.vm.name,))
  1352. policy.close()
  1353. self.addCleanup(os.unlink, "/etc/qubes-rpc/policy/test.AnyvmDeny")
  1354. flagfile = '/tmp/test-anyvmdeny-flag'
  1355. if os.path.exists(flagfile):
  1356. os.remove(flagfile)
  1357. self.create_local_file('/etc/qubes-rpc/test.AnyvmDeny',
  1358. 'touch {}\necho service output\n'.format(flagfile))
  1359. self.loop.run_until_complete(self.vm.start())
  1360. with self.qrexec_policy('test.AnyvmDeny', self.vm, '$anyvm'):
  1361. with self.assertRaises(subprocess.CalledProcessError,
  1362. msg='$anyvm matched dom0') as e:
  1363. self.loop.run_until_complete(
  1364. self.vm.run_for_stdio(
  1365. '/usr/lib/qubes/qrexec-client-vm dom0 test.AnyvmDeny'))
  1366. stdout = e.exception.output
  1367. stderr = e.exception.stderr
  1368. self.assertFalse(os.path.exists(flagfile),
  1369. 'Flag file created (service was run) even though should be denied,'
  1370. ' qrexec-client-vm output: {} {}'.format(stdout, stderr))
  1371. def create_testcases_for_templates():
  1372. return qubes.tests.create_testcases_for_templates('TC_00_AppVM',
  1373. TC_00_AppVMMixin, qubes.tests.SystemTestCase,
  1374. module=sys.modules[__name__])
  1375. def load_tests(loader, tests, pattern):
  1376. tests.addTests(loader.loadTestsFromNames(
  1377. create_testcases_for_templates()))
  1378. return tests
  1379. qubes.tests.maybe_create_testcases_on_import(create_testcases_for_templates)