diff --git a/Makefile b/Makefile index 78197e83..adfa3152 100644 --- a/Makefile +++ b/Makefile @@ -178,6 +178,7 @@ endif cp qubes-rpc-policy/qubes.OpenURL.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.OpenURL cp qubes-rpc-policy/qubes.VMShell.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.VMShell cp qubes-rpc-policy/qubes.VMRootShell.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.VMRootShell + cp qubes-rpc-policy/qubes.VMExec.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.VMExec cp qubes-rpc-policy/qubes.NotifyUpdates.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.NotifyUpdates cp qubes-rpc-policy/qubes.NotifyTools.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.NotifyTools cp qubes-rpc-policy/qubes.GetImageRGBA.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.GetImageRGBA @@ -186,7 +187,6 @@ endif cp qubes-rpc-policy/qubes.NotifyUpdates.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.NotifyUpdates cp qubes-rpc-policy/qubes.OpenInVM.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.OpenInVM cp qubes-rpc-policy/qubes.StartApp.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.StartApp - cp qubes-rpc-policy/qubes.VMShell.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.VMShell cp qubes-rpc-policy/qubes.UpdatesProxy.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.UpdatesProxy cp qubes-rpc-policy/qubes.GetDate.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.GetDate cp qubes-rpc-policy/qubes.ConnectTCP.policy $(DESTDIR)/etc/qubes-rpc/policy/qubes.ConnectTCP diff --git a/qubes-rpc-policy/qubes.VMExec.policy b/qubes-rpc-policy/qubes.VMExec.policy new file mode 100644 index 00000000..9863df8d --- /dev/null +++ b/qubes-rpc-policy/qubes.VMExec.policy @@ -0,0 +1,31 @@ +## Note that policy parsing stops at the first match, +## so adding anything below "$anyvm $anyvm action" line will have no effect + +## Please use a single # to start your custom comments + +$anyvm $dispvm allow +$anyvm $anyvm deny + +# WARNING: The qubes.VMExec service is dangerous and there are really few +# cases when it could be safely used. Especially when policy set to "ask" you +# have no way to know for sure what command(s) will be called. Compromissed +# source VM can substitute the command. Allowing one VM to execute +# qubes.VMExec over the other VM allows the former to TAKE FULL CONTROL over +# the later. In most cases this is not what we want! +# +# Instead we should be using task-specific qrexec services which provide +# assurance as to what program will be responding to the (untrusted) VM +# requests. +# +# It is, however, safe, in most cases, to allow ultimate control of the +# creating AppVM over the DisposableVM it creates as part of the qrexec service +# invocation. That's why by default we have "$anyvm $dispvm allow" rule. Note +# that it does _not_ allow any AppVM to execute qubes.VMExec service over any +# DispVM created in the system -- that would obviously be wrong. It only allows +# qubes.VMExec service access to the AppVM which creates the DispVM as part of +# this very service invocation. +# +# See e.g. this thread for some discussion: +# https://groups.google.com/d/msg/qubes-users/xnAByaL_bjI/3PjYdiTDW-0J +# +# diff --git a/qubes/ext/core_features.py b/qubes/ext/core_features.py index 153677db..b24dc9e7 100644 --- a/qubes/ext/core_features.py +++ b/qubes/ext/core_features.py @@ -1,4 +1,4 @@ -# -*- encoding: utf8 -*- +# -*- encoding: utf-8 -*- # # The Qubes OS Project, http://www.qubes-os.org # @@ -34,7 +34,8 @@ class CoreFeatures(qubes.ext.Extension): return requested_features = {} - for feature in ('qrexec', 'gui', 'gui-emulated', 'qubes-firewall'): + for feature in ( + 'qrexec', 'gui', 'gui-emulated', 'qubes-firewall', 'vmexec'): untrusted_value = untrusted_features.get(feature, None) if untrusted_value in ('1', '0'): requested_features[feature] = bool(int(untrusted_value)) @@ -53,7 +54,7 @@ class CoreFeatures(qubes.ext.Extension): vm.features[feature] = requested_features[feature] # those features can be freely enabled or disabled by template - for feature in ('qubes-firewall',): + for feature in ('qubes-firewall', 'vmexec'): if feature in requested_features: vm.features[feature] = requested_features[feature] diff --git a/qubes/tests/ext.py b/qubes/tests/ext.py index 99fbb0ce..582adece 100644 --- a/qubes/tests/ext.py +++ b/qubes/tests/ext.py @@ -1,4 +1,4 @@ -# -*- encoding: utf8 -*- +# -*- encoding: utf-8 -*- # # The Qubes OS Project, http://www.qubes-os.org # @@ -46,13 +46,15 @@ class TC_00_CoreFeatures(qubes.tests.QubesTestCase): 'gui': '1', 'version': '1', 'default-user': 'user', - 'qrexec': '1'})) - self.assertEqual(self.vm.mock_calls, [ + 'qrexec': '1', + 'vmexec': '1'})) + self.assertListEqual(self.vm.mock_calls, [ ('features.get', ('qrexec', False), {}), ('features.__contains__', ('qrexec',), {}), ('features.__setitem__', ('qrexec', True), {}), ('features.__contains__', ('gui',), {}), ('features.__setitem__', ('gui', True), {}), + ('features.__setitem__', ('vmexec', True), {}), ('features.get', ('qrexec', False), {}), ('fire_event_async', ('template-postinstall',), {}), ('fire_event_async().__iter__', (), {}), @@ -66,13 +68,15 @@ class TC_00_CoreFeatures(qubes.tests.QubesTestCase): 'gui': '0', 'version': '1', 'default-user': 'user', - 'qrexec': '0'})) - self.assertEqual(self.vm.mock_calls, [ + 'qrexec': '0', + 'vmexec': '0'})) + self.assertListEqual(self.vm.mock_calls, [ ('features.get', ('qrexec', False), {}), ('features.__contains__', ('qrexec',), {}), ('features.__setitem__', ('qrexec', False), {}), ('features.__contains__', ('gui',), {}), ('features.__setitem__', ('gui', False), {}), + ('features.__setitem__', ('vmexec', False), {}), ('features.get', ('qrexec', False), {}), ]) @@ -84,7 +88,7 @@ class TC_00_CoreFeatures(qubes.tests.QubesTestCase): 'version': '1', 'default-user': 'user', })) - self.assertEqual(self.vm.mock_calls, [ + self.assertListEqual(self.vm.mock_calls, [ ('features.get', ('qrexec', False), {}), ('features.get', ('qrexec', False), {}), ]) @@ -98,7 +102,7 @@ class TC_00_CoreFeatures(qubes.tests.QubesTestCase): 'gui': '1', 'default-user': 'user', })) - self.assertEqual(self.vm.mock_calls, [ + self.assertListEqual(self.vm.mock_calls, [ ('features.get', ('qrexec', False), {}), ('features.__contains__', ('qrexec',), {}), ('features.__setitem__', ('qrexec', True), {}), @@ -136,7 +140,7 @@ class TC_00_CoreFeatures(qubes.tests.QubesTestCase): 'gui': 'invalid', 'default-user': 'user', })) - self.assertEqual(self.vm.mock_calls, [ + self.assertListEqual(self.vm.mock_calls, [ ('features.get', ('qrexec', False), {}), ('features.__contains__', ('qrexec',), {}), ('features.__setitem__', ('qrexec', True), {}), @@ -171,7 +175,7 @@ class TC_00_CoreFeatures(qubes.tests.QubesTestCase): 'version': '1', 'default-user': 'user', 'qrexec': '1'})) - self.assertEqual(self.vm.mock_calls, [ + self.assertListEqual(self.vm.mock_calls, [ ('features.get', ('qrexec', False), {}), ('features.__contains__', ('qrexec',), {}), ('features.__contains__', ('gui',), {}), diff --git a/rpm_spec/core-dom0.spec.in b/rpm_spec/core-dom0.spec.in index 76339017..6cbeb47d 100644 --- a/rpm_spec/core-dom0.spec.in +++ b/rpm_spec/core-dom0.spec.in @@ -402,6 +402,7 @@ fi %attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/qubes.StartApp %attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/qubes.VMShell %attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/qubes.VMRootShell +%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/qubes.VMExec %attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/qubes.UpdatesProxy %attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/qubes.GetDate %attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/policy.RegisterArgument