diff --git a/NEWS b/NEWS index 6516412b8b7c2758de9b9f66df9a6ac2d00d802d..ec983769bee4482cc66cdf7c74a10ef5d3395cf1 100644 --- a/NEWS +++ b/NEWS @@ -101,6 +101,15 @@ Details - Improved burnin +Version 2.0.5 +------------- + +- Fix security issue due to missing validation of iallocator names; this + allows local and remote execution of arbitrary executables +- Fix failure of gnt-node list during instance removal +- Ship the RAPI documentation in the archive + + Version 2.0.4 ------------- diff --git a/lib/backend.py b/lib/backend.py index f5f258bd25c1cb6439b4d343015420beed28b716..a93397e5c0e0c6e4b4d65a2e12f39bb4523858f2 100644 --- a/lib/backend.py +++ b/lib/backend.py @@ -1731,10 +1731,11 @@ def _TryOSFromDisk(name, base_dir=None): """ if base_dir is None: os_dir = utils.FindFile(name, constants.OS_SEARCH_PATH, os.path.isdir) - if os_dir is None: - return False, "Directory for OS %s not found in search path" % name else: - os_dir = os.path.sep.join([base_dir, name]) + os_dir = utils.FindFile(name, [base_dir], os.path.isdir) + + if os_dir is None: + return False, "Directory for OS %s not found in search path" % name status, api_versions = _OSOndiskAPIVersion(name, os_dir) if not status: @@ -2612,8 +2613,6 @@ class HooksRunner(object): on the master side. """ - RE_MASK = re.compile("^[a-zA-Z0-9_-]+$") - def __init__(self, hooks_base_dir=None): """Constructor for hooks runner. @@ -2721,7 +2720,7 @@ class HooksRunner(object): for relname in dir_contents: fname = os.path.join(dir_name, relname) if not (os.path.isfile(fname) and os.access(fname, os.X_OK) and - self.RE_MASK.match(relname) is not None): + constants.EXT_PLUGIN_MASK.match(relname) is not None): rrval = constants.HKR_SKIP output = "" else: diff --git a/lib/cmdlib.py b/lib/cmdlib.py index c149a229d49f404db72a84c049eda77bffd32b7a..c837cd90cbf23ce93dffa99d7bb2e39d871858c7 100644 --- a/lib/cmdlib.py +++ b/lib/cmdlib.py @@ -2547,10 +2547,9 @@ class LUQueryNodes(NoHooksLU): inst_fields = frozenset(("pinst_cnt", "pinst_list", "sinst_cnt", "sinst_list")) if inst_fields & frozenset(self.op.output_fields): - instancelist = self.cfg.GetInstanceList() + inst_data = self.cfg.GetAllInstancesInfo() - for instance_name in instancelist: - inst = self.cfg.GetInstanceInfo(instance_name) + for instance_name, inst in inst_data.items(): if inst.primary_node in node_to_primary: node_to_primary[inst.primary_node].add(inst.name) for secnode in inst.secondary_nodes: diff --git a/lib/constants.py b/lib/constants.py index 0f035ef79eaf4d9564095637e44869ad0df340d0..7fb61a2ee8f96843202021b79d410e4e606d4cdc 100644 --- a/lib/constants.py +++ b/lib/constants.py @@ -21,6 +21,8 @@ """Module holding different constants.""" +import re + from ganeti import _autoconf # various versions @@ -174,6 +176,9 @@ VALUE_NONE = "none" VALUE_TRUE = "true" VALUE_FALSE = "false" +# External script validation mask +EXT_PLUGIN_MASK = re.compile("^[a-zA-Z0-9_-]+$") + # hooks-related constants HOOKS_BASE_DIR = CONF_DIR + "/hooks" HOOKS_PHASE_PRE = "pre" diff --git a/lib/utils.py b/lib/utils.py index 91baa8849da1d333e2333b98df3e48c868a80e73..8f7d5579ef3d5e30ec40a5d5e744244d38318b9e 100644 --- a/lib/utils.py +++ b/lib/utils.py @@ -1656,9 +1656,17 @@ def FindFile(name, search_path, test=os.path.exists): @return: full path to the object if found, None otherwise """ + # validate the filename mask + if constants.EXT_PLUGIN_MASK.match(name) is None: + logging.critical("Invalid value passed for external script name: '%s'", + name) + return None + for dir_name in search_path: item_name = os.path.sep.join([dir_name, name]) - if test(item_name): + # check the user test and that we're indeed resolving to the given + # basename + if test(item_name) and os.path.basename(item_name) == name: return item_name return None