Do not use assert statement in security logic

This is because assert statement gets optimised out when Python is run
with -O flag. This was pointed out to me by audience at PyWaw 76.
This commit is contained in:
Wojtek Porczyk 2018-06-11 12:32:05 +02:00
parent 39a9e4e422
commit 4e49b951ce
10 changed files with 164 additions and 149 deletions

View File

@ -196,6 +196,11 @@ class AbstractQubesAPI(object):
return apply_filters(iterable, return apply_filters(iterable,
self.fire_event_for_permission(**kwargs)) self.fire_event_for_permission(**kwargs))
def enforce(self, predicate):
'''An assert replacement, but works even with optimisations.'''
if not predicate:
raise PermissionDenied()
class QubesDaemonProtocol(asyncio.Protocol): class QubesDaemonProtocol(asyncio.Protocol):
buffer_size = 65536 buffer_size = 65536

View File

@ -98,8 +98,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
@asyncio.coroutine @asyncio.coroutine
def vmclass_list(self): def vmclass_list(self):
'''List all VM classes''' '''List all VM classes'''
assert not self.arg self.enforce(not self.arg)
assert self.dest.name == 'dom0' self.enforce(self.dest.name == 'dom0')
entrypoints = self.fire_event_for_filter( entrypoints = self.fire_event_for_filter(
pkg_resources.iter_entry_points(qubes.vm.VM_ENTRY_POINT)) pkg_resources.iter_entry_points(qubes.vm.VM_ENTRY_POINT))
@ -112,7 +112,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
@asyncio.coroutine @asyncio.coroutine
def vm_list(self): def vm_list(self):
'''List all the domains''' '''List all the domains'''
assert not self.arg self.enforce(not self.arg)
if self.dest.name == 'dom0': if self.dest.name == 'dom0':
domains = self.fire_event_for_filter(self.app.domains) domains = self.fire_event_for_filter(self.app.domains)
@ -137,11 +137,11 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
@asyncio.coroutine @asyncio.coroutine
def property_list(self): def property_list(self):
'''List all global properties''' '''List all global properties'''
assert self.dest.name == 'dom0' self.enforce(self.dest.name == 'dom0')
return self._property_list(self.app) return self._property_list(self.app)
def _property_list(self, dest): def _property_list(self, dest):
assert not self.arg self.enforce(not self.arg)
properties = self.fire_event_for_filter(dest.property_list()) properties = self.fire_event_for_filter(dest.property_list())
@ -159,7 +159,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
@asyncio.coroutine @asyncio.coroutine
def property_get(self): def property_get(self):
'''Get a value of one global property''' '''Get a value of one global property'''
assert self.dest.name == 'dom0' self.enforce(self.dest.name == 'dom0')
return self._property_get(self.app) return self._property_get(self.app)
def _property_get(self, dest): def _property_get(self, dest):
@ -203,7 +203,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
@asyncio.coroutine @asyncio.coroutine
def property_get_default(self): def property_get_default(self):
'''Get a value of one global property''' '''Get a value of one global property'''
assert self.dest.name == 'dom0' self.enforce(self.dest.name == 'dom0')
return self._property_get_default(self.app) return self._property_get_default(self.app)
def _property_get_default(self, dest): def _property_get_default(self, dest):
@ -247,7 +247,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
@asyncio.coroutine @asyncio.coroutine
def property_set(self, untrusted_payload): def property_set(self, untrusted_payload):
'''Set property value''' '''Set property value'''
assert self.dest.name == 'dom0' self.enforce(self.dest.name == 'dom0')
return self._property_set(self.app, return self._property_set(self.app,
untrusted_payload=untrusted_payload) untrusted_payload=untrusted_payload)
@ -275,7 +275,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
@asyncio.coroutine @asyncio.coroutine
def property_help(self): def property_help(self):
'''Get help for one property''' '''Get help for one property'''
assert self.dest.name == 'dom0' self.enforce(self.dest.name == 'dom0')
return self._property_help(self.app) return self._property_help(self.app)
def _property_help(self, dest): def _property_help(self, dest):
@ -303,7 +303,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
@asyncio.coroutine @asyncio.coroutine
def property_reset(self): def property_reset(self):
'''Reset a property to a default value''' '''Reset a property to a default value'''
assert self.dest.name == 'dom0' self.enforce(self.dest.name == 'dom0')
return self._property_reset(self.app) return self._property_reset(self.app)
def _property_reset(self, dest): def _property_reset(self, dest):
@ -319,7 +319,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='local', read=True) scope='local', read=True)
@asyncio.coroutine @asyncio.coroutine
def vm_volume_list(self): def vm_volume_list(self):
assert not self.arg self.enforce(not self.arg)
volume_names = self.fire_event_for_filter(self.dest.volumes.keys()) volume_names = self.fire_event_for_filter(self.dest.volumes.keys())
return ''.join('{}\n'.format(name) for name in volume_names) return ''.join('{}\n'.format(name) for name in volume_names)
@ -328,7 +328,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='local', read=True) scope='local', read=True)
@asyncio.coroutine @asyncio.coroutine
def vm_volume_info(self): def vm_volume_info(self):
assert self.arg in self.dest.volumes.keys() self.enforce(self.arg in self.dest.volumes.keys())
self.fire_event_for_permission() self.fire_event_for_permission()
@ -356,7 +356,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='local', read=True) scope='local', read=True)
@asyncio.coroutine @asyncio.coroutine
def vm_volume_listsnapshots(self): def vm_volume_listsnapshots(self):
assert self.arg in self.dest.volumes.keys() self.enforce(self.arg in self.dest.volumes.keys())
volume = self.dest.volumes[self.arg] volume = self.dest.volumes[self.arg]
id_to_timestamp = volume.revisions id_to_timestamp = volume.revisions
@ -369,13 +369,13 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='local', write=True) scope='local', write=True)
@asyncio.coroutine @asyncio.coroutine
def vm_volume_revert(self, untrusted_payload): def vm_volume_revert(self, untrusted_payload):
assert self.arg in self.dest.volumes.keys() self.enforce(self.arg in self.dest.volumes.keys())
untrusted_revision = untrusted_payload.decode('ascii').strip() untrusted_revision = untrusted_payload.decode('ascii').strip()
del untrusted_payload del untrusted_payload
volume = self.dest.volumes[self.arg] volume = self.dest.volumes[self.arg]
snapshots = volume.revisions snapshots = volume.revisions
assert untrusted_revision in snapshots self.enforce(untrusted_revision in snapshots)
revision = untrusted_revision revision = untrusted_revision
self.fire_event_for_permission(volume=volume, revision=revision) self.fire_event_for_permission(volume=volume, revision=revision)
@ -391,7 +391,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='local', write=True) scope='local', write=True)
@asyncio.coroutine @asyncio.coroutine
def vm_volume_clone_from(self): def vm_volume_clone_from(self):
assert self.arg in self.dest.volumes.keys() self.enforce(self.arg in self.dest.volumes.keys())
volume = self.dest.volumes[self.arg] volume = self.dest.volumes[self.arg]
@ -403,7 +403,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
self.app.api_admin_pending_clone = {} self.app.api_admin_pending_clone = {}
# don't handle collisions any better - if someone is so much out of # don't handle collisions any better - if someone is so much out of
# luck, can try again anyway # luck, can try again anyway
assert token not in self.app.api_admin_pending_clone self.enforce(token not in self.app.api_admin_pending_clone)
self.app.api_admin_pending_clone[token] = volume self.app.api_admin_pending_clone[token] = volume
return token return token
@ -412,11 +412,11 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='local', write=True) scope='local', write=True)
@asyncio.coroutine @asyncio.coroutine
def vm_volume_clone_to(self, untrusted_payload): def vm_volume_clone_to(self, untrusted_payload):
assert self.arg in self.dest.volumes.keys() self.enforce(self.arg in self.dest.volumes.keys())
untrusted_token = untrusted_payload.decode('ascii').strip() untrusted_token = untrusted_payload.decode('ascii').strip()
del untrusted_payload del untrusted_payload
assert untrusted_token in getattr(self.app, self.enforce(
'api_admin_pending_clone', {}) untrusted_token in getattr(self.app, 'api_admin_pending_clone', {}))
token = untrusted_token token = untrusted_token
del untrusted_token del untrusted_token
@ -424,8 +424,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
del self.app.api_admin_pending_clone[token] del self.app.api_admin_pending_clone[token]
# make sure the volume still exists, but invalidate token anyway # make sure the volume still exists, but invalidate token anyway
assert str(src_volume.pool) in self.app.pools self.enforce(str(src_volume.pool) in self.app.pools)
assert src_volume in self.app.pools[str(src_volume.pool)].volumes self.enforce(src_volume in self.app.pools[str(src_volume.pool)].volumes)
dst_volume = self.dest.volumes[self.arg] dst_volume = self.dest.volumes[self.arg]
@ -446,11 +446,11 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='local', write=True) scope='local', write=True)
@asyncio.coroutine @asyncio.coroutine
def vm_volume_resize(self, untrusted_payload): def vm_volume_resize(self, untrusted_payload):
assert self.arg in self.dest.volumes.keys() self.enforce(self.arg in self.dest.volumes.keys())
untrusted_size = untrusted_payload.decode('ascii').strip() untrusted_size = untrusted_payload.decode('ascii').strip()
del untrusted_payload del untrusted_payload
assert untrusted_size.isdigit() # only digits, forbid '-' too self.enforce(untrusted_size.isdigit()) # only digits, forbid '-' too
assert len(untrusted_size) <= 20 # limit to about 2^64 self.enforce(len(untrusted_size) <= 20) # limit to about 2^64
size = int(untrusted_size) size = int(untrusted_size)
@ -472,7 +472,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
payload) and response from that call will be actually send to the payload) and response from that call will be actually send to the
caller. caller.
''' '''
assert self.arg in self.dest.volumes.keys() self.enforce(self.arg in self.dest.volumes.keys())
self.fire_event_for_permission() self.fire_event_for_permission()
@ -480,7 +480,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
raise qubes.exc.QubesVMNotHaltedError(self.dest) raise qubes.exc.QubesVMNotHaltedError(self.dest)
path = self.dest.storage.import_data(self.arg) path = self.dest.storage.import_data(self.arg)
assert ' ' not in path self.enforce(' ' not in path)
size = self.dest.volumes[self.arg].size size = self.dest.volumes[self.arg].size
# when we know the action is allowed, inform extensions that it will # when we know the action is allowed, inform extensions that it will
@ -493,13 +493,13 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='local', write=True) scope='local', write=True)
@asyncio.coroutine @asyncio.coroutine
def vm_volume_set_revisions_to_keep(self, untrusted_payload): def vm_volume_set_revisions_to_keep(self, untrusted_payload):
assert self.arg in self.dest.volumes.keys() self.enforce(self.arg in self.dest.volumes.keys())
try: try:
untrusted_value = int(untrusted_payload.decode('ascii')) untrusted_value = int(untrusted_payload.decode('ascii'))
except (UnicodeDecodeError, ValueError): except (UnicodeDecodeError, ValueError):
raise qubes.api.ProtocolError('Invalid value') raise qubes.api.ProtocolError('Invalid value')
del untrusted_payload del untrusted_payload
assert untrusted_value >= 0 self.enforce(untrusted_value >= 0)
newvalue = untrusted_value newvalue = untrusted_value
del untrusted_value del untrusted_value
@ -512,7 +512,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='local', write=True) scope='local', write=True)
@asyncio.coroutine @asyncio.coroutine
def vm_volume_set_rw(self, untrusted_payload): def vm_volume_set_rw(self, untrusted_payload):
assert self.arg in self.dest.volumes.keys() self.enforce(self.arg in self.dest.volumes.keys())
try: try:
newvalue = qubes.property.bool(None, None, newvalue = qubes.property.bool(None, None,
untrusted_payload.decode('ascii')) untrusted_payload.decode('ascii'))
@ -532,7 +532,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='local', read=True) scope='local', read=True)
@asyncio.coroutine @asyncio.coroutine
def vm_tag_list(self): def vm_tag_list(self):
assert not self.arg self.enforce(not self.arg)
tags = self.dest.tags tags = self.dest.tags
@ -579,8 +579,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='global', read=True) scope='global', read=True)
@asyncio.coroutine @asyncio.coroutine
def pool_list(self): def pool_list(self):
assert not self.arg self.enforce(not self.arg)
assert self.dest.name == 'dom0' self.enforce(self.dest.name == 'dom0')
pools = self.fire_event_for_filter(self.app.pools) pools = self.fire_event_for_filter(self.app.pools)
@ -590,8 +590,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='global', read=True) scope='global', read=True)
@asyncio.coroutine @asyncio.coroutine
def pool_listdrivers(self): def pool_listdrivers(self):
assert self.dest.name == 'dom0' self.enforce(self.dest.name == 'dom0')
assert not self.arg self.enforce(not self.arg)
drivers = self.fire_event_for_filter(qubes.storage.pool_drivers()) drivers = self.fire_event_for_filter(qubes.storage.pool_drivers())
@ -604,8 +604,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='global', read=True) scope='global', read=True)
@asyncio.coroutine @asyncio.coroutine
def pool_info(self): def pool_info(self):
assert self.dest.name == 'dom0' self.enforce(self.dest.name == 'dom0')
assert self.arg in self.app.pools.keys() self.enforce(self.arg in self.app.pools.keys())
pool = self.app.pools[self.arg] pool = self.app.pools[self.arg]
@ -635,30 +635,32 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='global', write=True) scope='global', write=True)
@asyncio.coroutine @asyncio.coroutine
def pool_add(self, untrusted_payload): def pool_add(self, untrusted_payload):
assert self.dest.name == 'dom0' self.enforce(self.dest.name == 'dom0')
drivers = qubes.storage.pool_drivers() drivers = qubes.storage.pool_drivers()
assert self.arg in drivers self.enforce(self.arg in drivers)
untrusted_pool_config = untrusted_payload.decode('ascii').splitlines() untrusted_pool_config = untrusted_payload.decode('ascii').splitlines()
del untrusted_payload del untrusted_payload
assert all(('=' in line) for line in untrusted_pool_config) self.enforce(all(('=' in line) for line in untrusted_pool_config))
# pairs of (option, value) # pairs of (option, value)
untrusted_pool_config = [line.split('=', 1) untrusted_pool_config = [line.split('=', 1)
for line in untrusted_pool_config] for line in untrusted_pool_config]
# reject duplicated options # reject duplicated options
assert len(set(x[0] for x in untrusted_pool_config)) == \ self.enforce(
len([x[0] for x in untrusted_pool_config]) len(set(x[0] for x in untrusted_pool_config)) ==
len([x[0] for x in untrusted_pool_config]))
# and convert to dict # and convert to dict
untrusted_pool_config = dict(untrusted_pool_config) untrusted_pool_config = dict(untrusted_pool_config)
assert 'name' in untrusted_pool_config self.enforce('name' in untrusted_pool_config)
untrusted_pool_name = untrusted_pool_config.pop('name') untrusted_pool_name = untrusted_pool_config.pop('name')
allowed_chars = string.ascii_letters + string.digits + '-_.' allowed_chars = string.ascii_letters + string.digits + '-_.'
assert all(c in allowed_chars for c in untrusted_pool_name) self.enforce(all(c in allowed_chars for c in untrusted_pool_name))
pool_name = untrusted_pool_name pool_name = untrusted_pool_name
assert pool_name not in self.app.pools self.enforce(pool_name not in self.app.pools)
driver_parameters = qubes.storage.driver_parameters(self.arg) driver_parameters = qubes.storage.driver_parameters(self.arg)
assert all(key in driver_parameters for key in untrusted_pool_config) self.enforce(
all(key in driver_parameters for key in untrusted_pool_config))
pool_config = untrusted_pool_config pool_config = untrusted_pool_config
self.fire_event_for_permission(name=pool_name, self.fire_event_for_permission(name=pool_name,
@ -671,8 +673,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='global', write=True) scope='global', write=True)
@asyncio.coroutine @asyncio.coroutine
def pool_remove(self): def pool_remove(self):
assert self.dest.name == 'dom0' self.enforce(self.dest.name == 'dom0')
assert self.arg in self.app.pools.keys() self.enforce(self.arg in self.app.pools.keys())
self.fire_event_for_permission() self.fire_event_for_permission()
@ -683,15 +685,15 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='global', write=True) scope='global', write=True)
@asyncio.coroutine @asyncio.coroutine
def pool_set_revisions_to_keep(self, untrusted_payload): def pool_set_revisions_to_keep(self, untrusted_payload):
assert self.dest.name == 'dom0' self.enforce(self.dest.name == 'dom0')
assert self.arg in self.app.pools.keys() self.enforce(self.arg in self.app.pools.keys())
pool = self.app.pools[self.arg] pool = self.app.pools[self.arg]
try: try:
untrusted_value = int(untrusted_payload.decode('ascii')) untrusted_value = int(untrusted_payload.decode('ascii'))
except (UnicodeDecodeError, ValueError): except (UnicodeDecodeError, ValueError):
raise qubes.api.ProtocolError('Invalid value') raise qubes.api.ProtocolError('Invalid value')
del untrusted_payload del untrusted_payload
assert untrusted_value >= 0 self.enforce(untrusted_value >= 0)
newvalue = untrusted_value newvalue = untrusted_value
del untrusted_value del untrusted_value
@ -704,8 +706,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='global', read=True) scope='global', read=True)
@asyncio.coroutine @asyncio.coroutine
def label_list(self): def label_list(self):
assert self.dest.name == 'dom0' self.enforce(self.dest.name == 'dom0')
assert not self.arg self.enforce(not self.arg)
labels = self.fire_event_for_filter(self.app.labels.values()) labels = self.fire_event_for_filter(self.app.labels.values())
@ -715,7 +717,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='global', read=True) scope='global', read=True)
@asyncio.coroutine @asyncio.coroutine
def label_get(self): def label_get(self):
assert self.dest.name == 'dom0' self.enforce(self.dest.name == 'dom0')
try: try:
label = self.app.get_label(self.arg) label = self.app.get_label(self.arg)
@ -730,7 +732,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='global', read=True) scope='global', read=True)
@asyncio.coroutine @asyncio.coroutine
def label_index(self): def label_index(self):
assert self.dest.name == 'dom0' self.enforce(self.dest.name == 'dom0')
try: try:
label = self.app.get_label(self.arg) label = self.app.get_label(self.arg)
@ -745,12 +747,12 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='global', write=True) scope='global', write=True)
@asyncio.coroutine @asyncio.coroutine
def label_create(self, untrusted_payload): def label_create(self, untrusted_payload):
assert self.dest.name == 'dom0' self.enforce(self.dest.name == 'dom0')
# don't confuse label name with label index # don't confuse label name with label index
assert not self.arg.isdigit() self.enforce(not self.arg.isdigit())
allowed_chars = string.ascii_letters + string.digits + '-_.' allowed_chars = string.ascii_letters + string.digits + '-_.'
assert all(c in allowed_chars for c in self.arg) self.enforce(all(c in allowed_chars for c in self.arg))
try: try:
self.app.get_label(self.arg) self.app.get_label(self.arg)
except KeyError: except KeyError:
@ -760,10 +762,10 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
raise qubes.exc.QubesValueError('label already exists') raise qubes.exc.QubesValueError('label already exists')
untrusted_payload = untrusted_payload.decode('ascii').strip() untrusted_payload = untrusted_payload.decode('ascii').strip()
assert len(untrusted_payload) == 8 self.enforce(len(untrusted_payload) == 8)
assert untrusted_payload.startswith('0x') self.enforce(untrusted_payload.startswith('0x'))
# besides prefix, only hex digits are allowed # besides prefix, only hex digits are allowed
assert all(x in string.hexdigits for x in untrusted_payload[2:]) self.enforce(all(x in string.hexdigits for x in untrusted_payload[2:]))
# SEE: #2732 # SEE: #2732
color = untrusted_payload color = untrusted_payload
@ -782,14 +784,14 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='global', write=True) scope='global', write=True)
@asyncio.coroutine @asyncio.coroutine
def label_remove(self): def label_remove(self):
assert self.dest.name == 'dom0' self.enforce(self.dest.name == 'dom0')
try: try:
label = self.app.get_label(self.arg) label = self.app.get_label(self.arg)
except KeyError: except KeyError:
raise qubes.exc.QubesValueError raise qubes.exc.QubesValueError
# don't allow removing default labels # don't allow removing default labels
assert label.index > qubes.config.max_default_label self.enforce(label.index > qubes.config.max_default_label)
# FIXME: this should be in app.add_label() # FIXME: this should be in app.add_label()
for vm in self.app.domains: for vm in self.app.domains:
@ -805,7 +807,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='local', execute=True) scope='local', execute=True)
@asyncio.coroutine @asyncio.coroutine
def vm_start(self): def vm_start(self):
assert not self.arg self.enforce(not self.arg)
self.fire_event_for_permission() self.fire_event_for_permission()
try: try:
yield from self.dest.start() yield from self.dest.start()
@ -819,7 +821,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='local', execute=True) scope='local', execute=True)
@asyncio.coroutine @asyncio.coroutine
def vm_shutdown(self): def vm_shutdown(self):
assert not self.arg self.enforce(not self.arg)
self.fire_event_for_permission() self.fire_event_for_permission()
yield from self.dest.shutdown() yield from self.dest.shutdown()
@ -827,7 +829,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='local', execute=True) scope='local', execute=True)
@asyncio.coroutine @asyncio.coroutine
def vm_pause(self): def vm_pause(self):
assert not self.arg self.enforce(not self.arg)
self.fire_event_for_permission() self.fire_event_for_permission()
yield from self.dest.pause() yield from self.dest.pause()
@ -835,7 +837,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='local', execute=True) scope='local', execute=True)
@asyncio.coroutine @asyncio.coroutine
def vm_unpause(self): def vm_unpause(self):
assert not self.arg self.enforce(not self.arg)
self.fire_event_for_permission() self.fire_event_for_permission()
yield from self.dest.unpause() yield from self.dest.unpause()
@ -843,7 +845,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='local', execute=True) scope='local', execute=True)
@asyncio.coroutine @asyncio.coroutine
def vm_kill(self): def vm_kill(self):
assert not self.arg self.enforce(not self.arg)
self.fire_event_for_permission() self.fire_event_for_permission()
yield from self.dest.kill() yield from self.dest.kill()
@ -851,7 +853,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='global', read=True) scope='global', read=True)
@asyncio.coroutine @asyncio.coroutine
def events(self): def events(self):
assert not self.arg self.enforce(not self.arg)
# run until client connection is terminated # run until client connection is terminated
self.cancellable = True self.cancellable = True
@ -893,7 +895,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='local', read=True) scope='local', read=True)
@asyncio.coroutine @asyncio.coroutine
def vm_feature_list(self): def vm_feature_list(self):
assert not self.arg self.enforce(not self.arg)
features = self.fire_event_for_filter(self.dest.features.keys()) features = self.fire_event_for_filter(self.dest.features.keys())
return ''.join('{}\n'.format(feature) for feature in features) return ''.join('{}\n'.format(feature) for feature in features)
@ -978,7 +980,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
untrusted_payload=untrusted_payload) untrusted_payload=untrusted_payload)
def _vm_create(self, vm_type, allow_pool=False, untrusted_payload=None): def _vm_create(self, vm_type, allow_pool=False, untrusted_payload=None):
assert self.dest.name == 'dom0' self.enforce(self.dest.name == 'dom0')
kwargs = {} kwargs = {}
pool = None pool = None
@ -992,10 +994,10 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
# when given VM class do need a template # when given VM class do need a template
if hasattr(vm_class, 'template'): if hasattr(vm_class, 'template'):
if self.arg: if self.arg:
assert self.arg in self.app.domains self.enforce(self.arg in self.app.domains)
kwargs['template'] = self.app.domains[self.arg] kwargs['template'] = self.app.domains[self.arg]
else: else:
assert not self.arg self.enforce(not self.arg)
for untrusted_param in untrusted_payload.decode('ascii', for untrusted_param in untrusted_payload.decode('ascii',
errors='strict').split(' '): errors='strict').split(' '):
@ -1009,9 +1011,9 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
elif untrusted_key == 'label': elif untrusted_key == 'label':
# don't confuse label name with label index # don't confuse label name with label index
assert not untrusted_value.isdigit() self.enforce(not untrusted_value.isdigit())
allowed_chars = string.ascii_letters + string.digits + '-_.' allowed_chars = string.ascii_letters + string.digits + '-_.'
assert all(c in allowed_chars for c in untrusted_value) self.enforce(all(c in allowed_chars for c in untrusted_value))
try: try:
kwargs['label'] = self.app.get_label(untrusted_value) kwargs['label'] = self.app.get_label(untrusted_value)
except KeyError: except KeyError:
@ -1025,8 +1027,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
untrusted_volume = untrusted_key.split(':', 1)[1] untrusted_volume = untrusted_key.split(':', 1)[1]
# kind of ugly, but actual list of volumes is available only # kind of ugly, but actual list of volumes is available only
# after creating a VM # after creating a VM
assert untrusted_volume in ['root', 'private', 'volatile', self.enforce(untrusted_volume in [
'kernel'] 'root', 'private', 'volatile', 'kernel'])
volume = untrusted_volume volume = untrusted_volume
if volume in pools: if volume in pools:
raise qubes.api.ProtocolError( raise qubes.api.ProtocolError(
@ -1065,7 +1067,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='global', write=True) scope='global', write=True)
@asyncio.coroutine @asyncio.coroutine
def create_disposable(self): def create_disposable(self):
assert not self.arg self.enforce(not self.arg)
if self.dest.name == 'dom0': if self.dest.name == 'dom0':
dispvm_template = self.src.default_dispvm dispvm_template = self.src.default_dispvm
@ -1084,7 +1086,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='global', write=True) scope='global', write=True)
@asyncio.coroutine @asyncio.coroutine
def vm_remove(self): def vm_remove(self):
assert not self.arg self.enforce(not self.arg)
self.fire_event_for_permission() self.fire_event_for_permission()
@ -1116,7 +1118,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
devices = [dev for dev in devices if dev.ident == self.arg] devices = [dev for dev in devices if dev.ident == self.arg]
# no duplicated devices, but device may not exists, in which case # no duplicated devices, but device may not exists, in which case
# the list is empty # the list is empty
assert len(devices) <= 1 self.enforce(len(devices) <= 1)
devices = self.fire_event_for_filter(devices, devclass=devclass) devices = self.fire_event_for_filter(devices, devclass=devclass)
dev_info = {} dev_info = {}
@ -1133,7 +1135,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
# specification # specification
(('description', dev.description),) (('description', dev.description),)
)) ))
assert '\n' not in properties_txt self.enforce('\n' not in properties_txt)
dev_info[dev.ident] = properties_txt dev_info[dev.ident] = properties_txt
return ''.join('{} {}\n'.format(ident, dev_info[ident]) return ''.join('{} {}\n'.format(ident, dev_info[ident])
@ -1154,7 +1156,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
== (select_backend, select_ident)] == (select_backend, select_ident)]
# no duplicated devices, but device may not exists, in which case # no duplicated devices, but device may not exists, in which case
# the list is empty # the list is empty
assert len(device_assignments) <= 1 self.enforce(len(device_assignments) <= 1)
device_assignments = self.fire_event_for_filter(device_assignments, device_assignments = self.fire_event_for_filter(device_assignments,
devclass=devclass) devclass=devclass)
@ -1166,7 +1168,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
dev.options.items(), dev.options.items(),
(('persistent', 'yes' if dev.persistent else 'no'),) (('persistent', 'yes' if dev.persistent else 'no'),)
)) ))
assert '\n' not in properties_txt self.enforce('\n' not in properties_txt)
ident = '{!s}+{!s}'.format(dev.backend_domain, dev.ident) ident = '{!s}+{!s}'.format(dev.backend_domain, dev.ident)
dev_info[ident] = properties_txt dev_info[ident] = properties_txt
@ -1254,7 +1256,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
def vm_device_set_persistent(self, endpoint, untrusted_payload): def vm_device_set_persistent(self, endpoint, untrusted_payload):
devclass = endpoint devclass = endpoint
assert untrusted_payload in (b'True', b'False') self.enforce(untrusted_payload in (b'True', b'False'))
persistent = untrusted_payload == b'True' persistent = untrusted_payload == b'True'
del untrusted_payload del untrusted_payload
@ -1264,7 +1266,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
matching_devices = [dev for dev matching_devices = [dev for dev
in self.dest.devices[devclass].attached() in self.dest.devices[devclass].attached()
if dev.backend_domain.name == backend_domain and dev.ident == ident] if dev.backend_domain.name == backend_domain and dev.ident == ident]
assert len(matching_devices) == 1 self.enforce(len(matching_devices) == 1)
dev = matching_devices[0] dev = matching_devices[0]
self.fire_event_for_permission(device=dev, self.fire_event_for_permission(device=dev,
@ -1277,7 +1279,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='local', read=True) scope='local', read=True)
@asyncio.coroutine @asyncio.coroutine
def vm_firewall_get(self): def vm_firewall_get(self):
assert not self.arg self.enforce(not self.arg)
self.fire_event_for_permission() self.fire_event_for_permission()
@ -1289,7 +1291,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='local', write=True) scope='local', write=True)
@asyncio.coroutine @asyncio.coroutine
def vm_firewall_set(self, untrusted_payload): def vm_firewall_set(self, untrusted_payload):
assert not self.arg self.enforce(not self.arg)
rules = [] rules = []
for untrusted_line in untrusted_payload.decode('ascii', for untrusted_line in untrusted_payload.decode('ascii',
errors='strict').splitlines(): errors='strict').splitlines():
@ -1306,7 +1308,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='local', execute=True) scope='local', execute=True)
@asyncio.coroutine @asyncio.coroutine
def vm_firewall_reload(self): def vm_firewall_reload(self):
assert not self.arg self.enforce(not self.arg)
self.fire_event_for_permission() self.fire_event_for_permission()
@ -1367,7 +1369,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
'qubes.BackupPassphrase+' + self.arg) 'qubes.BackupPassphrase+' + self.arg)
# make it foolproof against "echo passphrase" implementation # make it foolproof against "echo passphrase" implementation
passphrase = passphrase.strip() passphrase = passphrase.strip()
assert b'\n' not in passphrase self.enforce(b'\n' not in passphrase)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
raise qubes.exc.QubesException( raise qubes.exc.QubesException(
'Failed to retrieve passphrase from \'{}\' VM'.format( 'Failed to retrieve passphrase from \'{}\' VM'.format(
@ -1410,9 +1412,9 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='global', read=True, execute=True) scope='global', read=True, execute=True)
@asyncio.coroutine @asyncio.coroutine
def backup_execute(self): def backup_execute(self):
assert self.dest.name == 'dom0' self.enforce(self.dest.name == 'dom0')
assert self.arg self.enforce(self.arg)
assert '/' not in self.arg self.enforce('/' not in self.arg)
self.fire_event_for_permission() self.fire_event_for_permission()
@ -1430,7 +1432,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
self._backup_progress_callback, self.arg) self._backup_progress_callback, self.arg)
# forbid running the same backup operation twice at the time # forbid running the same backup operation twice at the time
assert self.arg not in self.app.api_admin_running_backups self.enforce(self.arg not in self.app.api_admin_running_backups)
backup_task = asyncio.ensure_future(backup.backup_do()) backup_task = asyncio.ensure_future(backup.backup_do())
self.app.api_admin_running_backups[self.arg] = backup_task self.app.api_admin_running_backups[self.arg] = backup_task
@ -1445,9 +1447,9 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='global', execute=True) scope='global', execute=True)
@asyncio.coroutine @asyncio.coroutine
def backup_cancel(self): def backup_cancel(self):
assert self.dest.name == 'dom0' self.enforce(self.dest.name == 'dom0')
assert self.arg self.enforce(self.arg)
assert '/' not in self.arg self.enforce('/' not in self.arg)
self.fire_event_for_permission() self.fire_event_for_permission()
@ -1463,9 +1465,9 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='local', read=True) scope='local', read=True)
@asyncio.coroutine @asyncio.coroutine
def backup_info(self): def backup_info(self):
assert self.dest.name == 'dom0' self.enforce(self.dest.name == 'dom0')
assert self.arg self.enforce(self.arg)
assert '/' not in self.arg self.enforce('/' not in self.arg)
self.fire_event_for_permission() self.fire_event_for_permission()
@ -1527,7 +1529,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
scope='global', read=True) scope='global', read=True)
@asyncio.coroutine @asyncio.coroutine
def vm_stats(self): def vm_stats(self):
assert not self.arg self.enforce(not self.arg)
# run until client connection is terminated # run until client connection is terminated
self.cancellable = True self.cancellable = True

View File

@ -39,8 +39,8 @@ class QubesInternalAPI(qubes.api.AbstractQubesAPI):
@qubes.api.method('internal.GetSystemInfo', no_payload=True) @qubes.api.method('internal.GetSystemInfo', no_payload=True)
@asyncio.coroutine @asyncio.coroutine
def getsysteminfo(self): def getsysteminfo(self):
assert self.dest.name == 'dom0' self.enforce(self.dest.name == 'dom0')
assert not self.arg self.enforce(not self.arg)
system_info = {'domains': { system_info = {'domains': {
domain.name: { domain.name: {
@ -64,7 +64,7 @@ class QubesInternalAPI(qubes.api.AbstractQubesAPI):
when actual import is finished. Response from this method is sent do when actual import is finished. Response from this method is sent do
the client (as a response for admin.vm.volume.Import call). the client (as a response for admin.vm.volume.Import call).
''' '''
assert self.arg in self.dest.volumes.keys() self.enforce(self.arg in self.dest.volumes.keys())
success = untrusted_payload == b'ok' success = untrusted_payload == b'ok'
try: try:

View File

@ -46,8 +46,8 @@ class QubesMiscAPI(qubes.api.AbstractQubesAPI):
appropriate extensions. Requests not explicitly handled by some appropriate extensions. Requests not explicitly handled by some
extension are ignored. extension are ignored.
''' '''
assert self.dest.name == 'dom0' self.enforce(self.dest.name == 'dom0')
assert not self.arg self.enforce(not self.arg)
prefix = '/features-request/' prefix = '/features-request/'
@ -59,7 +59,7 @@ class QubesMiscAPI(qubes.api.AbstractQubesAPI):
safe_set = string.ascii_letters + string.digits safe_set = string.ascii_letters + string.digits
for untrusted_key in untrusted_features: for untrusted_key in untrusted_features:
untrusted_value = untrusted_features[untrusted_key] untrusted_value = untrusted_features[untrusted_key]
assert all((c in safe_set) for c in untrusted_value) self.enforce(all((c in safe_set) for c in untrusted_value))
yield from self.src.fire_event_async('features-request', yield from self.src.fire_event_async('features-request',
untrusted_features=untrusted_features) untrusted_features=untrusted_features)
@ -71,8 +71,8 @@ class QubesMiscAPI(qubes.api.AbstractQubesAPI):
''' '''
Legacy version of qubes.FeaturesRequest, used by Qubes Windows Tools Legacy version of qubes.FeaturesRequest, used by Qubes Windows Tools
''' '''
assert self.dest.name == 'dom0' self.enforce(self.dest.name == 'dom0')
assert not self.arg self.enforce(not self.arg)
untrusted_features = {} untrusted_features = {}
safe_set = string.ascii_letters + string.digits safe_set = string.ascii_letters + string.digits
@ -83,7 +83,7 @@ class QubesMiscAPI(qubes.api.AbstractQubesAPI):
if untrusted_value: if untrusted_value:
untrusted_value = untrusted_value.decode('ascii', untrusted_value = untrusted_value.decode('ascii',
errors='strict') errors='strict')
assert all((c in safe_set) for c in untrusted_value) self.enforce(all((c in safe_set) for c in untrusted_value))
untrusted_features[feature] = untrusted_value untrusted_features[feature] = untrusted_value
del untrusted_value del untrusted_value
@ -102,7 +102,7 @@ class QubesMiscAPI(qubes.api.AbstractQubesAPI):
''' '''
untrusted_update_count = untrusted_payload.strip() untrusted_update_count = untrusted_payload.strip()
assert untrusted_update_count.isdigit() self.enforce(untrusted_update_count.isdigit())
# now sanitized # now sanitized
update_count = int(untrusted_update_count) update_count = int(untrusted_update_count)
del untrusted_update_count del untrusted_update_count

View File

@ -20,26 +20,27 @@
# #
# #
from __future__ import unicode_literals from __future__ import unicode_literals
import itertools
import logging
import functools
import string
import termios
import asyncio import asyncio
import datetime
from qubes.utils import size_to_human
import stat
import os
import fcntl import fcntl
import subprocess import functools
import grp
import itertools
import logging
import os
import pathlib
import pwd
import re import re
import shutil import shutil
import stat
import string
import subprocess
import tempfile import tempfile
import termios
import time import time
import grp
import pwd from .utils import size_to_human
import datetime
import qubes import qubes
import qubes.core2migration import qubes.core2migration
import qubes.storage import qubes.storage
@ -257,17 +258,14 @@ class Backup(object):
size = qubes.storage.file.get_disk_usage(file_path) size = qubes.storage.file.get_disk_usage(file_path)
if subdir is None: if subdir is None:
abs_file_path = os.path.abspath(file_path) abs_file_dir = pathlib.Path(file_path).resolve().parent
abs_base_dir = os.path.abspath( abs_base_dir = pathlib.Path(
qubes.config.system_path["qubes_base_dir"]) + '/' qubes.config.system_path["qubes_base_dir"]).resolve()
abs_file_dir = os.path.dirname(abs_file_path) + '/' # this raises ValueError if abs_file_dir is not in abs_base_dir
(nothing, directory, subdir) = \ subdir = str(abs_file_dir.relative_to(abs_base_dir))
abs_file_dir.partition(abs_base_dir)
assert nothing == "" if not subdir.endswith(os.path.sep):
assert directory == abs_base_dir subdir += os.path.sep
else:
if subdir and not subdir.endswith('/'):
subdir += '/'
#: real path to the file #: real path to the file
self.path = file_path self.path = file_path

View File

@ -238,9 +238,9 @@ class DeviceCollection(object):
if device_assignment.bus is None: if device_assignment.bus is None:
device_assignment.bus = self._bus device_assignment.bus = self._bus
else: elif device_assignment.bus != self._bus:
assert device_assignment.bus == self._bus, \ raise ValueError(
"Trying to attach DeviceAssignment of a different device class" 'Trying to attach DeviceAssignment of a different device class')
if not device_assignment.persistent and self._vm.is_halted(): if not device_assignment.persistent and self._vm.is_halted():
raise qubes.exc.QubesVMNotRunningError(self._vm, raise qubes.exc.QubesVMNotRunningError(self._vm,

View File

@ -39,9 +39,10 @@ class RuleOption(object):
# subset of string.punctuation # subset of string.punctuation
safe_set = string.ascii_letters + string.digits + \ safe_set = string.ascii_letters + string.digits + \
':;,./-_[]' ':;,./-_[]'
assert all(x in safe_set for x in str(untrusted_value)) untrusted_value = str(untrusted_value)
value = str(untrusted_value) if not all(x in safe_set for x in untrusted_value):
self._value = value raise ValueError('strange characters in rule')
self._value = untrusted_value
@property @property
def rule(self): def rule(self):
@ -226,9 +227,10 @@ class Comment(RuleOption):
# subset of string.punctuation # subset of string.punctuation
safe_set = string.ascii_letters + string.digits + \ safe_set = string.ascii_letters + string.digits + \
':;,./-_[] ' ':;,./-_[] '
assert all(x in safe_set for x in str(untrusted_value)) untrusted_value = str(untrusted_value)
value = str(untrusted_value) if not all(x in safe_set for x in untrusted_value):
self._value = value raise ValueError('strange characters comment')
self._value = untrusted_value
@property @property
def rule(self): def rule(self):

View File

@ -280,7 +280,8 @@ class Tags(set):
@staticmethod @staticmethod
def validate_tag(tag): def validate_tag(tag):
safe_set = string.ascii_letters + string.digits + '-_' safe_set = string.ascii_letters + string.digits + '-_'
assert all((x in safe_set) for x in tag) if not all((x in safe_set) for x in tag):
raise ValueError('disallowed characters')
class BaseVM(qubes.PropertyHolder): class BaseVM(qubes.PropertyHolder):

View File

@ -302,7 +302,9 @@ class NetVMMixin(qubes.events.Emitter):
if not self.is_running(): if not self.is_running():
raise qubes.exc.QubesVMNotRunningError(self) raise qubes.exc.QubesVMNotRunningError(self)
assert self.netvm is not None if self.netvm is None:
raise qubes.exc.QubesVMError(self,
'netvm should not be {}'.format(self.netvm))
if not self.netvm.is_running(): # pylint: disable=no-member if not self.netvm.is_running(): # pylint: disable=no-member
# pylint: disable=no-member # pylint: disable=no-member
@ -319,7 +321,9 @@ class NetVMMixin(qubes.events.Emitter):
if not self.is_running(): if not self.is_running():
raise qubes.exc.QubesVMNotRunningError(self) raise qubes.exc.QubesVMNotRunningError(self)
assert self.netvm is not None if self.netvm is None:
raise qubes.exc.QubesVMError(self,
'netvm should not be {}'.format(self.netvm))
self.libvirt_domain.detachDevice( self.libvirt_domain.detachDevice(
self.app.env.get_template('libvirt/devices/net.xml').render( self.app.env.get_template('libvirt/devices/net.xml').render(

View File

@ -416,10 +416,13 @@ class PolicyAction(object):
raise AccessDenied( raise AccessDenied(
'denied by policy {}:{}'.format(rule.filename, rule.lineno)) 'denied by policy {}:{}'.format(rule.filename, rule.lineno))
elif rule.action == Action.ask: elif rule.action == Action.ask:
assert targets_for_ask is not None if targets_for_ask is None:
raise AccessDenied(
'invalid policy {}:{}'.format(rule.filename, rule.lineno))
elif rule.action == Action.allow: elif rule.action == Action.allow:
assert targets_for_ask is None if targets_for_ask is not None or target is None:
assert target is not None raise AccessDenied(
'invalid policy {}:{}'.format(rule.filename, rule.lineno))
self.action = rule.action self.action = rule.action
def handle_user_response(self, response, target=None): def handle_user_response(self, response, target=None):