From a2d9b1541330f6a423f1ed6e15e1e79a83cd6f2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Wed, 7 Sep 2016 03:43:46 +0200 Subject: [PATCH] qmemman: support simple VM meminfo format Instead of excerpt from /proc/meminfo, use just one integer. This make qmemman handling much easier and ease implementation for non-Linux OSes (where /proc/meminfo doesn't exist). For now keep also support for old format. Fixes QubesOS/qubes-issues#1312 --- qubes/qmemman/__init__.py | 5 +-- qubes/qmemman/algo.py | 88 ++++++++++++++++++++++----------------- qubes/vm/qubesvm.py | 3 ++ 3 files changed, 55 insertions(+), 41 deletions(-) diff --git a/qubes/qmemman/__init__.py b/qubes/qmemman/__init__.py index 3168534b..94f00d55 100644 --- a/qubes/qmemman/__init__.py +++ b/qubes/qmemman/__init__.py @@ -41,7 +41,6 @@ slow_memset_react_msg="VM didn't give back all requested memory" class DomainState: def __init__(self, id): - self.meminfo = None #dictionary of memory info read from client self.memory_current = 0 # the current memory size self.memory_actual = None # the current memory allocation (what VM # is using or can use at any time) @@ -286,7 +285,7 @@ class SystemState(object): def print_stats(self, xenfree, memset_reqs): for i in self.domdict.keys(): - if self.domdict[i].meminfo is not None: + if self.domdict[i].mem_used is not None: self.log.info('stat: dom {!r} act={} pref={}'.format(i, self.domdict[i].memory_actual, qubes.qmemman.algo.prefmem(self.domdict[i]))) @@ -375,6 +374,6 @@ class SystemState(object): self.mem_set(dom, mem) # for i in self.domdict.keys(): -# print 'domain ', i, ' meminfo=', self.domdict[i].meminfo, 'actual mem', self.domdict[i].memory_actual +# print 'domain ', i, ' meminfo=', self.domdict[i].mem_used, 'actual mem', self.domdict[i].memory_actual # print 'domain ', i, 'actual mem', self.domdict[i].memory_actual # print 'xen free mem', self.get_free_xen_memory() diff --git a/qubes/qmemman/algo.py b/qubes/qmemman/algo.py index b2eb8617..1552eb04 100644 --- a/qubes/qmemman/algo.py +++ b/qubes/qmemman/algo.py @@ -37,64 +37,76 @@ log = logging.getLogger('qmemman.daemon.algo') #untrusted meminfo size is taken from xenstore key, thus its size is limited #so splits do not require excessive memory -def parse_meminfo(untrusted_meminfo): +def sanitize_and_parse_meminfo(untrusted_meminfo): + if not untrusted_meminfo: + return None + + # new syntax - just one int + try: + if int(untrusted_meminfo) >= 0: + return int(untrusted_meminfo) + except ValueError: + pass + + # not new syntax - try the old one untrusted_dict = {} -#split meminfo contents into lines - untrusted_lines = string.split(untrusted_meminfo,"\n") + # split meminfo contents into lines + untrusted_lines = string.split(untrusted_meminfo, "\n") for untrusted_lines_iterator in untrusted_lines: -#split a single meminfo line into words + # split a single meminfo line into words untrusted_words = string.split(untrusted_lines_iterator) if len(untrusted_words) >= 2: - untrusted_dict[string.rstrip(untrusted_words[0], ":")] = untrusted_words[1] + untrusted_dict[string.rstrip(untrusted_words[0], ":")] = \ + untrusted_words[1] - return untrusted_dict + # sanitize start + if not is_meminfo_suspicious(untrusted_meminfo): + # sanitize end + meminfo = untrusted_meminfo + return meminfo['MemTotal'] - \ + meminfo['MemFree'] - meminfo['Cached'] - meminfo['Buffers'] + \ + meminfo['SwapTotal'] - meminfo['SwapFree'] -def is_meminfo_suspicious(domain, untrusted_meminfo): + + return None + + +def is_meminfo_suspicious(untrusted_meminfo): log.debug('is_meminfo_suspicious(' - 'domain={!r}, untrusted_meminfo={!r})'.format( - domain, untrusted_meminfo)) + 'untrusted_meminfo={!r})'.format(untrusted_meminfo)) ret = False -#check whether the required keys exist and are not negative + # check whether the required keys exist and are not negative try: - for i in ('MemTotal', 'MemFree', 'Buffers', 'Cached', 'SwapTotal', 'SwapFree'): + for i in ('MemTotal', 'MemFree', 'Buffers', 'Cached', + 'SwapTotal', 'SwapFree'): val = int(untrusted_meminfo[i])*1024 - if (val < 0): + if val < 0: ret = True untrusted_meminfo[i] = val except: ret = True - if not ret and untrusted_meminfo['SwapTotal'] < untrusted_meminfo['SwapFree']: + if untrusted_meminfo['SwapTotal'] < untrusted_meminfo['SwapFree']: ret = True - if not ret and untrusted_meminfo['MemTotal'] < untrusted_meminfo['MemFree'] + untrusted_meminfo['Cached'] + untrusted_meminfo['Buffers']: + if untrusted_meminfo['MemTotal'] < \ + untrusted_meminfo['MemFree'] + \ + untrusted_meminfo['Cached'] + untrusted_meminfo['Buffers']: ret = True -#we could also impose some limits on all the above values -#but it has little purpose - all the domain can gain by passing e.g. -#very large SwapTotal is that it will be assigned all free Xen memory -#it can be achieved with legal values, too, and it will not allow to -#starve existing domains, by design + # we could also impose some limits on all the above values + # but it has little purpose - all the domain can gain by passing e.g. + # very large SwapTotal is that it will be assigned all free Xen memory + # it can be achieved with legal values, too, and it will not allow to + # starve existing domains, by design if ret: - log.warning('suspicious meminfo for domain {!r}' - ' memory_actual={!r} untrusted_meminfo={!r}'.format(domain.id, - domain.memory_actual, untrusted_meminfo)) + log.warning('suspicious meminfo untrusted_meminfo={!r}'.format(untrusted_meminfo)) return ret -#called when a domain updates its 'meminfo' xenstore key + +# called when a domain updates its 'meminfo' xenstore key def refresh_meminfo_for_domain(domain, untrusted_xenstore_key): - untrusted_meminfo = parse_meminfo(untrusted_xenstore_key) - if untrusted_meminfo is None: - domain.meminfo = None - return -#sanitize start - if is_meminfo_suspicious(domain, untrusted_meminfo): -#sanitize end - domain.meminfo = None - domain.mem_used = None - else: -#sanitized, can assign - domain.meminfo = untrusted_meminfo - domain.mem_used = domain.meminfo['MemTotal'] - domain.meminfo['MemFree'] - domain.meminfo['Cached'] - domain.meminfo['Buffers'] + domain.meminfo['SwapTotal'] - domain.meminfo['SwapFree'] + domain.mem_used = sanitize_and_parse_meminfo(untrusted_xenstore_key) + def prefmem(domain): #dom0 is special, as it must have large cache, for vbds. Thus, give it a special boost @@ -158,7 +170,7 @@ def balance_when_enough_memory(domain_dictionary, left_memory = 0 acceptors_count = 0 for i in domain_dictionary.keys(): - if domain_dictionary[i].meminfo is None: + if domain_dictionary[i].mem_used is None: continue if domain_dictionary[i].no_progress: continue @@ -264,7 +276,7 @@ def balance(xen_free_memory, domain_dictionary): acceptors = list() # domains that require more memory #pass 1: compute the above "total" values for i in domain_dictionary.keys(): - if domain_dictionary[i].meminfo is None: + if domain_dictionary[i].mem_used is None: continue if domain_dictionary[i].no_progress: continue diff --git a/qubes/vm/qubesvm.py b/qubes/vm/qubesvm.py index f20fe59d..51db03fd 100644 --- a/qubes/vm/qubesvm.py +++ b/qubes/vm/qubesvm.py @@ -1537,6 +1537,9 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): domain = qubes.qmemman.DomainState(self.xid) qubes.qmemman.algo.refresh_meminfo_for_domain( domain, untrusted_meminfo_key) + if domain.mem_used is None: + # apparently invalid xenstore content + return 0 domain.memory_maximum = self.get_mem_static_max() * 1024 return qubes.qmemman.algo.prefmem(domain) / 1024