Rename vm.qdb to vm.untrusted_qdb

QubesDB can be freely modified by a VM, so one should take care when
reading any data retrieved from it.

Fixes QubesOS/qubes-issues#2934
This commit is contained in:
Marek Marczykowski-Górecki 2017-07-21 23:11:24 +02:00
parent c17e63588e
commit 1759bca00f
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
9 changed files with 74 additions and 68 deletions

View File

@ -52,9 +52,9 @@ class QubesMiscAPI(qubes.api.AbstractQubesAPI):
prefix = '/features-request/'
keys = [key.decode('ascii', errors='strict')
for key in self.src.qdb.list(prefix)]
for key in self.src.untrusted_qdb.list(prefix)]
untrusted_features = {key[len(prefix):]:
self.src.qdb.read(key).decode('ascii', errors='strict')
self.src.untrusted_qdb.read(key).decode('ascii', errors='strict')
for key in keys}
safe_set = string.ascii_letters + string.digits
@ -79,7 +79,8 @@ class QubesMiscAPI(qubes.api.AbstractQubesAPI):
safe_set = string.ascii_letters + string.digits
expected_features = ('qrexec', 'gui', 'default-user')
for feature in expected_features:
untrusted_value = self.src.qdb.read('/qubes-tools/' + feature)
untrusted_value = self.src.untrusted_qdb.read(
'/qubes-tools/' + feature)
if untrusted_value:
untrusted_value = untrusted_value.decode('ascii',
errors='strict')

View File

@ -56,7 +56,7 @@ class BlockDevice(qubes.devices.DeviceInfo):
return self.ident
safe_set = {ord(c) for c in
string.ascii_letters + string.digits + '()+,-.:=_/ '}
untrusted_desc = self.backend_domain.qdb.read(
untrusted_desc = self.backend_domain.untrusted_qdb.read(
'/qubes-block-devices/{}/desc'.format(self.ident))
desc = ''.join((chr(c) if c in safe_set else '_')
for c in untrusted_desc)
@ -69,7 +69,7 @@ class BlockDevice(qubes.devices.DeviceInfo):
if self._mode is None:
if not self.backend_domain.is_running():
return 'w'
untrusted_mode = self.backend_domain.qdb.read(
untrusted_mode = self.backend_domain.untrusted_qdb.read(
'/qubes-block-devices/{}/mode'.format(self.ident))
if untrusted_mode is None:
self._mode = 'w'
@ -87,7 +87,7 @@ class BlockDevice(qubes.devices.DeviceInfo):
if self._size is None:
if not self.backend_domain.is_running():
return None
untrusted_size = self.backend_domain.qdb.read(
untrusted_size = self.backend_domain.untrusted_qdb.read(
'/qubes-block-devices/{}/size'.format(self.ident))
if untrusted_size is None:
self._size = 0
@ -114,7 +114,7 @@ class BlockDeviceExtension(qubes.ext.Extension):
:param ident: device identifier
:returns BlockDevice'''
untrusted_qubes_device_attrs = vm.qdb.list(
untrusted_qubes_device_attrs = vm.untrusted_qdb.list(
'/qubes-block-devices/{}/'.format(ident))
if not untrusted_qubes_device_attrs:
return None
@ -128,7 +128,7 @@ class BlockDeviceExtension(qubes.ext.Extension):
string.ascii_letters + string.digits}
if not vm.is_running():
return
untrusted_qubes_devices = vm.qdb.list('/qubes-block-devices/')
untrusted_qubes_devices = vm.untrusted_qdb.list('/qubes-block-devices/')
untrusted_idents = set(untrusted_path.split(b'/', 3)[2]
for untrusted_path in untrusted_qubes_devices)
for untrusted_ident in untrusted_idents:

View File

@ -60,9 +60,9 @@ class R3Compatibility(qubes.ext.Extension):
vmtype = 'NetVM'
else:
vmtype = 'AppVM'
vm.qdb.write('/qubes-vm-type', vmtype)
vm.untrusted_qdb.write('/qubes-vm-type', vmtype)
vm.qdb.write("/qubes-iptables-error", '')
vm.untrusted_qdb.write("/qubes-iptables-error", '')
self.write_iptables_qubesdb_entry(vm)
self.write_services(vm)
@ -81,7 +81,7 @@ class R3Compatibility(qubes.ext.Extension):
def write_iptables_qubesdb_entry(self, firewallvm):
# pylint: disable=no-self-use
firewallvm.qdb.rm("/qubes-iptables-domainrules/")
firewallvm.untrusted_qdb.rm("/qubes-iptables-domainrules/")
iptables = "# Generated by Qubes Core on {0}\n".format(
datetime.datetime.now().ctime())
iptables += "*filter\n"
@ -102,7 +102,7 @@ class R3Compatibility(qubes.ext.Extension):
# Deny inter-VMs networking
iptables += "-A FORWARD -i vif+ -o vif+ -j DROP\n"
iptables += "COMMIT\n"
firewallvm.qdb.write("/qubes-iptables-header", iptables)
firewallvm.untrusted_qdb.write("/qubes-iptables-header", iptables)
for vm in firewallvm.connected_vms:
iptables = "*filter\n"
@ -154,11 +154,12 @@ class R3Compatibility(qubes.ext.Extension):
iptables += '-A FORWARD -s {0} -j {1}\n'.format(ip,
str(conf.policy).upper())
iptables += 'COMMIT\n'
firewallvm.qdb.write('/qubes-iptables-domainrules/' + str(xid),
firewallvm.untrusted_qdb.write(
'/qubes-iptables-domainrules/' + str(xid),
iptables)
# no need for ending -A FORWARD -j DROP, cause default action is DROP
firewallvm.qdb.write('/qubes-iptables', 'reload')
firewallvm.untrusted_qdb.write('/qubes-iptables', 'reload')
def write_services(self, vm):
for feature, value in vm.features.items():
@ -166,8 +167,9 @@ class R3Compatibility(qubes.ext.Extension):
if service is None:
continue
# forcefully convert to '0' or '1'
vm.qdb.write('/qubes-service/{}'.format(service),
vm.untrusted_qdb.write('/qubes-service/{}'.format(service),
str(int(bool(value))))
if 'updates-proxy-setup' in vm.features.keys():
vm.qdb.write('/qubes-service/{}'.format('yum-proxy-setup'),
vm.untrusted_qdb.write(
'/qubes-service/{}'.format('yum-proxy-setup'),
str(int(bool(vm.features['updates-proxy-setup']))))

View File

@ -419,7 +419,8 @@ class Storage(object):
# trigger watches to update device status
# FIXME: this should be removed once libvirt will report such
# events itself
# self.vm.qdb.write('/qubes-block-devices', '') ← do we need this?
# self.vm.untrusted_qdb.write('/qubes-block-devices', '')
# ← do we need this?
def _is_already_attached(self, volume):
''' Checks if the given volume is already attached '''

View File

@ -37,9 +37,10 @@ class TC_00_API_Misc(qubes.tests.QubesTestCase):
def configure_qdb(self, entries):
self.src.configure_mock(**{
'qdb.read.side_effect': (lambda path: entries.get(path, None)),
'qdb.list.side_effect': (lambda path:
sorted(map(str.encode, entries.keys()))),
'untrusted_qdb.read.side_effect': (
lambda path: entries.get(path, None)),
'untrusted_qdb.list.side_effect': (
lambda path: sorted(map(str.encode, entries.keys()))),
})
def call_mgmt_func(self, method, arg=b'', payload=b''):
@ -64,10 +65,10 @@ class TC_00_API_Misc(qubes.tests.QubesTestCase):
mock.call.save()
])
self.assertEqual(self.src.mock_calls, [
mock.call.qdb.list('/features-request/'),
mock.call.qdb.read('/features-request/feature1'),
mock.call.qdb.read('/features-request/feature2'),
mock.call.qdb.read('/features-request/feature3'),
mock.call.untrusted_qdb.list('/features-request/'),
mock.call.untrusted_qdb.read('/features-request/feature1'),
mock.call.untrusted_qdb.read('/features-request/feature2'),
mock.call.untrusted_qdb.read('/features-request/feature3'),
mock.call.fire_event('features-request', untrusted_features={
'feature1': '1', 'feature2': '', 'feature3': 'other'})
])
@ -80,7 +81,7 @@ class TC_00_API_Misc(qubes.tests.QubesTestCase):
mock.call.save()
])
self.assertEqual(self.src.mock_calls, [
mock.call.qdb.list('/features-request/'),
mock.call.untrusted_qdb.list('/features-request/'),
mock.call.fire_event('features-request', untrusted_features={})
])
@ -93,8 +94,8 @@ class TC_00_API_Misc(qubes.tests.QubesTestCase):
self.call_mgmt_func(b'qubes.FeaturesRequest')
self.assertEqual(self.app.mock_calls, [])
self.assertEqual(self.src.mock_calls, [
mock.call.qdb.list('/features-request/'),
mock.call.qdb.read('/features-request/feature1'),
mock.call.untrusted_qdb.list('/features-request/'),
mock.call.untrusted_qdb.read('/features-request/feature1'),
])
def test_003_features_request_invalid2(self):
@ -106,8 +107,8 @@ class TC_00_API_Misc(qubes.tests.QubesTestCase):
self.call_mgmt_func(b'qubes.FeaturesRequest')
self.assertEqual(self.app.mock_calls, [])
self.assertEqual(self.src.mock_calls, [
mock.call.qdb.list('/features-request/'),
mock.call.qdb.read('/features-request/feature1'),
mock.call.untrusted_qdb.list('/features-request/'),
mock.call.untrusted_qdb.read('/features-request/feature1'),
])
def test_010_notify_tools(self):
@ -125,9 +126,9 @@ class TC_00_API_Misc(qubes.tests.QubesTestCase):
mock.call.save()
])
self.assertEqual(self.src.mock_calls, [
mock.call.qdb.read('/qubes-tools/qrexec'),
mock.call.qdb.read('/qubes-tools/gui'),
mock.call.qdb.read('/qubes-tools/default-user'),
mock.call.untrusted_qdb.read('/qubes-tools/qrexec'),
mock.call.untrusted_qdb.read('/qubes-tools/gui'),
mock.call.untrusted_qdb.read('/qubes-tools/default-user'),
mock.call.fire_event('features-request', untrusted_features={
'gui': '1',
'default-user': 'user',
@ -146,9 +147,9 @@ class TC_00_API_Misc(qubes.tests.QubesTestCase):
response = self.call_mgmt_func(b'qubes.NotifyTools')
self.assertIsNone(response)
self.assertEqual(self.src.mock_calls, [
mock.call.qdb.read('/qubes-tools/qrexec'),
mock.call.qdb.read('/qubes-tools/gui'),
mock.call.qdb.read('/qubes-tools/default-user'),
mock.call.untrusted_qdb.read('/qubes-tools/qrexec'),
mock.call.untrusted_qdb.read('/qubes-tools/gui'),
mock.call.untrusted_qdb.read('/qubes-tools/default-user'),
mock.call.fire_event('features-request', untrusted_features={
'gui': '1',
'default-user': 'user',
@ -169,7 +170,7 @@ class TC_00_API_Misc(qubes.tests.QubesTestCase):
self.call_mgmt_func(b'qubes.NotifyTools')
self.assertEqual(self.app.mock_calls, [])
self.assertEqual(self.src.mock_calls, [
mock.call.qdb.read('/qubes-tools/qrexec'),
mock.call.untrusted_qdb.read('/qubes-tools/qrexec'),
])
def test_016_notify_tools_invalid_value_gui(self):
@ -185,8 +186,8 @@ class TC_00_API_Misc(qubes.tests.QubesTestCase):
self.call_mgmt_func(b'qubes.NotifyTools')
self.assertEqual(self.app.mock_calls, [])
self.assertEqual(self.src.mock_calls, [
mock.call.qdb.read('/qubes-tools/qrexec'),
mock.call.qdb.read('/qubes-tools/gui'),
mock.call.untrusted_qdb.read('/qubes-tools/qrexec'),
mock.call.untrusted_qdb.read('/qubes-tools/gui'),
])
def test_020_notify_updates_standalone(self):

View File

@ -120,7 +120,7 @@ class TestApp(object):
class TestVM(object):
def __init__(self, qdb, domain_xml=None, running=True, name='test-vm'):
self.name = name
self.qdb = TestQubesDB(qdb)
self.untrusted_qdb = TestQubesDB(qdb)
self.libvirt_domain = mock.Mock()
self.is_running = lambda: running
self.log = mock.Mock()

View File

@ -167,7 +167,7 @@ class AdminVM(qubes.vm.BaseVM):
return None
@property
def qdb(self):
def untrusted_qdb(self):
'''QubesDB handle for this domain.'''
if self._qdb_connection is None:
import qubesdb # pylint: disable=import-error

View File

@ -291,12 +291,12 @@ class NetVMMixin(qubes.events.Emitter):
base_dir = '/qubes-firewall/' + vm.ip + '/'
# remove old entries if any (but don't touch base empty entry - it
# would trigger reload right away
self.qdb.rm(base_dir)
self.untrusted_qdb.rm(base_dir)
# write new rules
for key, value in vm.firewall.qdb_entries(addr_family=4).items():
self.qdb.write(base_dir + key, value)
self.untrusted_qdb.write(base_dir + key, value)
# signal its done
self.qdb.write(base_dir[:-1], '')
self.untrusted_qdb.write(base_dir[:-1], '')
def set_mapped_ip_info_for_vm(self, vm):
'''
@ -307,14 +307,15 @@ class NetVMMixin(qubes.events.Emitter):
# add info about remapped IPs (VM IP hidden from the VM itself)
mapped_ip_base = '/mapped-ip/{}'.format(vm.ip)
if vm.visible_ip:
self.qdb.write(mapped_ip_base + '/visible-ip', vm.visible_ip)
self.untrusted_qdb.write(mapped_ip_base + '/visible-ip',
vm.visible_ip)
else:
self.qdb.rm(mapped_ip_base + '/visible-ip')
self.untrusted_qdb.rm(mapped_ip_base + '/visible-ip')
if vm.visible_gateway:
self.qdb.write(mapped_ip_base + '/visible-gateway',
self.untrusted_qdb.write(mapped_ip_base + '/visible-gateway',
vm.visible_gateway)
else:
self.qdb.rm(mapped_ip_base + '/visible-gateway')
self.untrusted_qdb.rm(mapped_ip_base + '/visible-gateway')
@qubes.events.handler('property-del:netvm')

View File

@ -590,7 +590,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
yield block_dev
@property
def qdb(self):
def untrusted_qdb(self):
'''QubesDB handle for this domain.'''
if self._qdb_connection is None:
if self.is_running():
@ -1714,53 +1714,53 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
'''
# pylint: disable=no-member
self.qdb.write('/name', self.name)
self.qdb.write('/type', self.__class__.__name__)
self.qdb.write('/qubes-vm-updateable', str(self.updateable))
self.qdb.write('/qubes-vm-persistence',
self.untrusted_qdb.write('/name', self.name)
self.untrusted_qdb.write('/type', self.__class__.__name__)
self.untrusted_qdb.write('/qubes-vm-updateable', str(self.updateable))
self.untrusted_qdb.write('/qubes-vm-persistence',
'full' if self.updateable else 'rw-only')
self.qdb.write('/qubes-debug-mode', str(int(self.debug)))
self.untrusted_qdb.write('/qubes-debug-mode', str(int(self.debug)))
try:
self.qdb.write('/qubes-base-template', self.template.name)
self.untrusted_qdb.write('/qubes-base-template', self.template.name)
except AttributeError:
self.qdb.write('/qubes-base-template', '')
self.untrusted_qdb.write('/qubes-base-template', '')
self.qdb.write('/qubes-random-seed',
self.untrusted_qdb.write('/qubes-random-seed',
base64.b64encode(qubes.utils.urandom(64)))
if self.provides_network:
# '/qubes-netvm-network' value is only checked for being non empty
self.qdb.write('/qubes-netvm-network', self.gateway)
self.qdb.write('/qubes-netvm-gateway', self.gateway)
self.qdb.write('/qubes-netvm-netmask', self.netmask)
self.untrusted_qdb.write('/qubes-netvm-network', self.gateway)
self.untrusted_qdb.write('/qubes-netvm-gateway', self.gateway)
self.untrusted_qdb.write('/qubes-netvm-netmask', self.netmask)
for i, addr in zip(('primary', 'secondary'), self.dns):
self.qdb.write('/qubes-netvm-{}-dns'.format(i), addr)
self.untrusted_qdb.write('/qubes-netvm-{}-dns'.format(i), addr)
if self.netvm is not None:
self.qdb.write('/qubes-ip', self.visible_ip)
self.qdb.write('/qubes-netmask', self.visible_netmask)
self.qdb.write('/qubes-gateway', self.visible_gateway)
self.untrusted_qdb.write('/qubes-ip', self.visible_ip)
self.untrusted_qdb.write('/qubes-netmask', self.visible_netmask)
self.untrusted_qdb.write('/qubes-gateway', self.visible_gateway)
for i, addr in zip(('primary', 'secondary'), self.dns):
self.qdb.write('/qubes-{}-dns'.format(i), addr)
self.untrusted_qdb.write('/qubes-{}-dns'.format(i), addr)
tzname = qubes.utils.get_timezone()
if tzname:
self.qdb.write('/qubes-timezone', tzname)
self.untrusted_qdb.write('/qubes-timezone', tzname)
for feature, value in self.features.items():
if not feature.startswith('service.'):
continue
service = feature[len('service.'):]
# forcefully convert to '0' or '1'
self.qdb.write('/qubes-service/{}'.format(service),
self.untrusted_qdb.write('/qubes-service/{}'.format(service),
str(int(bool(value))))
self.qdb.write('/qubes-block-devices', '')
self.untrusted_qdb.write('/qubes-block-devices', '')
self.qdb.write('/qubes-usb-devices', '')
self.untrusted_qdb.write('/qubes-usb-devices', '')
# TODO: Currently the whole qmemman is quite Xen-specific, so stay with
# xenstore for it until decided otherwise