From a8d7f740b2e32d3823acc8c0d76452c48c2fe8bd Mon Sep 17 00:00:00 2001 From: Chris PeBenito Date: Sun, 22 Feb 2015 09:24:39 -0500 Subject: [PATCH] Change range constructor to use regular levels instead of semantic ones. This affords better error reporting since levels can be invalid for several reasons. * Fix memory leak in qpol_semantic_level * Add unit tests --- libqpol/include/qpol/mls_query.h | 3 +- libqpol/mls_query.c | 44 +++++----- setools/policyrep/mls.py | 51 ++++++------ setools/policyrep/qpol.i | 16 +++- tests/policyrep/__init__.py | 18 ++++ tests/policyrep/mls.conf | 136 +++++++++++++++++++++++++++++++ tests/policyrep/mls.py | 97 ++++++++++++++++++++++ 7 files changed, 310 insertions(+), 55 deletions(-) create mode 100644 tests/policyrep/__init__.py create mode 100644 tests/policyrep/mls.conf create mode 100644 tests/policyrep/mls.py diff --git a/libqpol/include/qpol/mls_query.h b/libqpol/include/qpol/mls_query.h index e365d0b..e723dc3 100644 --- a/libqpol/include/qpol/mls_query.h +++ b/libqpol/include/qpol/mls_query.h @@ -258,9 +258,10 @@ extern "C" extern int qpol_policy_get_semantic_level_by_name(const qpol_policy_t * policy, const char *name, const qpol_semantic_level_t ** datum); extern int qpol_semantic_level_add_cats_by_name(const qpol_policy_t * policy, const qpol_semantic_level_t * level, const char *low, const char *high); extern int qpol_mls_level_from_semantic_level(const qpol_policy_t * policy, const qpol_semantic_level_t * src, qpol_mls_level_t **dest); + extern void qpol_semantic_level_destroy(qpol_semantic_level_t * level); /* semantic ranges */ - extern int qpol_policy_get_mls_range_from_semantic_levels(const qpol_policy_t * policy, const qpol_semantic_level_t * low, const qpol_semantic_level_t *high, qpol_mls_range_t **dest); + extern int qpol_policy_get_mls_range_from_mls_levels(const qpol_policy_t * policy, const qpol_mls_level_t * low, const qpol_mls_level_t *high, qpol_mls_range_t **dest); #ifdef __cplusplus } diff --git a/libqpol/mls_query.c b/libqpol/mls_query.c index 36a8436..9c27c98 100644 --- a/libqpol/mls_query.c +++ b/libqpol/mls_query.c @@ -541,12 +541,11 @@ int qpol_cat_get_alias_iter(const qpol_policy_t * policy, const qpol_cat_t * dat } /* mls range */ -int qpol_policy_get_mls_range_from_semantic_levels(const qpol_policy_t * policy, const qpol_semantic_level_t *low, const qpol_semantic_level_t *high, qpol_mls_range_t **dest) +int qpol_policy_get_mls_range_from_mls_levels(const qpol_policy_t * policy, const qpol_mls_level_t *low, const qpol_mls_level_t *high, qpol_mls_range_t **dest) { policydb_t *db = NULL; mls_range_t *internal_range = NULL; - mls_semantic_range_t *internal_semantic = NULL; - mls_semantic_level_t *internal_low = NULL, *internal_high = NULL; + mls_level_t *internal_low = NULL, *internal_high = NULL; if (policy == NULL || low == NULL || high == NULL || dest == NULL) { if (dest != NULL) @@ -556,50 +555,40 @@ int qpol_policy_get_mls_range_from_semantic_levels(const qpol_policy_t * policy, return STATUS_ERR; } - internal_low = (mls_semantic_level_t*)low; - internal_high = (mls_semantic_level_t*)high; + db = &policy->p->p; *dest = NULL; + internal_low = (mls_level_t*)low; + internal_high = (mls_level_t*)high; - internal_semantic = malloc(sizeof(mls_semantic_range_t)); - if (!internal_semantic) { + if (!mls_level_dom(internal_high, internal_low)) { ERR(policy, "%s", strerror(EINVAL)); + errno = EINVAL; return STATUS_ERR; } - mls_semantic_range_init(internal_semantic); internal_range = malloc(sizeof(mls_range_t)); if (!internal_range) { - mls_semantic_range_destroy(internal_semantic); - ERR(policy, "%s", strerror(EINVAL)); + ERR(policy, "%s", strerror(errno)); return STATUS_ERR; } mls_range_init(internal_range); - db = &policy->p->p; - if(mls_semantic_level_cpy(&internal_semantic->level[0], internal_low) < 0) { - goto err; - } - if (mls_semantic_level_cpy(&internal_semantic->level[1], internal_high) < 0) { + if (mls_level_cpy(&internal_range->level[0], internal_low) < 0) { goto err; } - if(mls_semantic_range_expand(internal_semantic, internal_range, db, policy->sh) < 0) { - ERR(policy, "%s", strerror(EINVAL)); + if (mls_level_cpy(&internal_range->level[1], internal_high) < 0) { goto err; } *dest = (qpol_mls_range_t*) internal_range; - mls_semantic_range_destroy(internal_semantic); - free(internal_semantic); return STATUS_SUCCESS; err: mls_range_destroy(internal_range); - mls_semantic_range_destroy(internal_semantic); free(internal_range); - free(internal_semantic); - errno = EINVAL; + errno = ENOMEM; return STATUS_ERR; } @@ -835,3 +824,14 @@ int qpol_semantic_level_add_cats_by_name(const qpol_policy_t * policy, const qpo errno = ENOENT; return STATUS_ERR; } + +void qpol_semantic_level_destroy(qpol_semantic_level_t * level) +{ + + if (level == NULL) { + return; + } + + mls_semantic_level_destroy((mls_semantic_level_t*) level); + free(level); +} diff --git a/setools/policyrep/mls.py b/setools/policyrep/mls.py index 59d70ad..6d3eeb2 100644 --- a/setools/policyrep/mls.py +++ b/setools/policyrep/mls.py @@ -87,15 +87,21 @@ def sensitivity_factory(policy, symbol): raise InvalidSensitivity("{0} is not a valid sensitivity".format(symbol)) -def _build_semantic_level(policy, level): - """Parse the level string and construct a qpol semantic representation.""" - sens_split = level.split(":") +def level_factory(policy, symbol): + """ + Factory function for creating MLS level objects (e.g. levels used + in contexts of labeling statements) + """ + if isinstance(symbol, qpol.qpol_mls_level_t): + return MLSLevel(policy, symbol) + + sens_split = symbol.split(":") sens = sens_split[0] try: semantic_level = qpol.qpol_semantic_level_t(policy, sens) except ValueError: - raise InvalidLevel("{0} is not a valid sensitivity".format(sens)) + raise InvalidLevel("{0} is invalid ({1} is not a valid sensitivity)".format(symbol, sens)) try: cats = sens_split[1] @@ -109,34 +115,24 @@ def _build_semantic_level(policy, level): try: semantic_level.add_cats(policy, catrange[0], catrange[1]) except ValueError: - raise InvalidLevel("{0} is not a valid category range".format(group)) + raise InvalidLevel("{0} is invalid ({1} is not a valid category range)". + format(symbol, group)) elif len(catrange) == 1: try: semantic_level.add_cats(policy, catrange[0], catrange[0]) except ValueError: - raise InvalidLevel("{0} is not a valid category".format(group)) + raise InvalidLevel("{0} is invalid ({1} is not a valid category)". + format(symbol, group)) else: - # may not be possible to get here - raise InvalidLevel("{0} is not a valid category range".format(group)) - - return semantic_level - - -def level_factory(policy, symbol): - """ - Factory function for creating MLS level objects (e.g. levels used - in contexts of labeling statements) - """ - if isinstance(symbol, qpol.qpol_mls_level_t): - return MLSLevel(policy, symbol) - - semantic_level = _build_semantic_level(policy, symbol) + raise InvalidLevel("{0} is invalid (level parsing error)".format(symbol)) # convert to level object try: policy_level = qpol.qpol_mls_level_t(policy, semantic_level) except ValueError: - raise InvalidLevel("{0} is not a valid level".format(symbol)) + raise InvalidLevel( + "{0} is invalid (one or more categories are not associated with the sensitivity)". + format(symbol)) return MLSLevel(policy, policy_level) @@ -165,12 +161,12 @@ def range_factory(policy, symbol): levels = symbol.split("-") try: - low = _build_semantic_level(policy, levels[0]) + low = level_factory(policy, levels[0]) except InvalidLevel as e: raise InvalidRange("{0} is not a valid range ({1}).".format(symbol, e)) try: - high = _build_semantic_level(policy, levels[1]) + high = level_factory(policy, levels[1]) except InvalidLevel as e: raise InvalidRange("{0} is not a valid range ({1}).".format(symbol, e)) except IndexError: @@ -178,11 +174,10 @@ def range_factory(policy, symbol): # convert to range object try: - policy_range = qpol.qpol_mls_range_t(policy, low, high) + policy_range = qpol.qpol_mls_range_t(policy, low.qpol_symbol, high.qpol_symbol) except ValueError: - # this can be due to one of the semantic levels being invalid (category not allowed in - # level) or the high level does not dominate the low level. Can't tell which one. - raise InvalidLevel("{0} is not a valid range".format(symbol)) + raise InvalidRange("{0} is not a valid range ({1} is not dominated by {2})". + format(symbol, levels[0], levels[1])) return MLSRange(policy, policy_range) diff --git a/setools/policyrep/qpol.i b/setools/policyrep/qpol.i index 6c6c0c9..f140ae9 100644 --- a/setools/policyrep/qpol.i +++ b/setools/policyrep/qpol.i @@ -1523,17 +1523,25 @@ typedef struct qpol_cat {} qpol_cat_t; /* qpol mls range */ typedef struct qpol_mls_range {} qpol_mls_range_t; %extend qpol_mls_range { + # TODO: determine how to conditionally destroy this range. + # It should only be destroyed if it was looked up (user-entered) + # Otherwise qpol will destroy the others when the policy closes. %exception qpol_mls_range { $action if (!result) { - PyErr_SetString(PyExc_ValueError, "Invalid range."); + if (errno == EINVAL) { + PyErr_SetString(PyExc_ValueError, "Invalid range."); + } else { + PyErr_SetFromErrno(PyExc_OSError); + } + return NULL; } } - qpol_mls_range(qpol_policy_t *p, qpol_semantic_level_t *l, qpol_semantic_level_t *h) { + qpol_mls_range(qpol_policy_t *p, qpol_mls_level_t *l, qpol_mls_level_t *h) { qpol_mls_range_t *range; - qpol_policy_get_mls_range_from_semantic_levels(p, l, h, &range); + qpol_policy_get_mls_range_from_mls_levels(p, l, h, &range); return range; } @@ -1586,7 +1594,7 @@ typedef struct qpol_semantic_level {} qpol_semantic_level_t; }; ~qpol_semantic_level() { - /* mls_semantic_level_destroy(self); */ + qpol_semantic_level_destroy(self); return; }; diff --git a/tests/policyrep/__init__.py b/tests/policyrep/__init__.py new file mode 100644 index 0000000..359be6a --- /dev/null +++ b/tests/policyrep/__init__.py @@ -0,0 +1,18 @@ +# Copyright 2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# SETools is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SETools. If not, see . +# +from . import mls diff --git a/tests/policyrep/mls.conf b/tests/policyrep/mls.conf new file mode 100644 index 0000000..1b2db89 --- /dev/null +++ b/tests/policyrep/mls.conf @@ -0,0 +1,136 @@ +class infoflow +class infoflow2 +class infoflow3 +class infoflow4 +class infoflow5 +class infoflow6 +class infoflow7 + +sid kernel +sid security + +common infoflow +{ + low_w + med_w + hi_w + low_r + med_r + hi_r +} + +class infoflow +inherits infoflow + +class infoflow2 +inherits infoflow +{ + super_w + super_r +} + +class infoflow3 +{ + null +} + +class infoflow4 +inherits infoflow + +class infoflow5 +inherits infoflow + +class infoflow6 +inherits infoflow + +class infoflow7 +inherits infoflow +{ + super_w + super_r + super_none + super_both + super_unmapped +} + +sensitivity s0; +sensitivity s1; +sensitivity s2; + +dominance { s0 s1 s2 } + +category c0; +category c1; +category c2; +category c3; +category c4; +category c5; +category c6; +category c7; +category c8; +category c9; +category c10; +category c11; +category c12; +category c13; + +#level decl +level s0:c0.c2; +level s1:c0.c13; +level s2:c0.c13; + +#some constraints +mlsconstrain infoflow hi_r ((l1 dom l2) or (t1 == mls_exempt)); + +attribute mls_exempt; + +type system; +role system; +role system types system; + +role role20_r; +role role21a_r; +role role21b_r; +role role21c_r; + +role role20_r types system; +role role21a_r types system; +role role21b_r types system; +role role21c_r types system; + +type type30; +type type31a; +type type31b; +type type31c; +role system types { type30 type31a type31b type31c }; + +allow system self:infoflow hi_w; + +#users +user system roles { system role20_r role21a_r role21b_r role21c_r } level s0 range s0 - s2:c0.c4; +user user10 roles system level s0 range s0 - s2:c0.c4; +user user11a roles system level s0 range s0 - s2:c0.c4; +user user11b roles system level s0 range s0 - s2:c0.c4; +user user11c roles system level s0 range s0 - s2:c0.c4; + +#normal constraints +constrain infoflow hi_w (u1 == u2); + +#isids +sid kernel system:system:system:s0 +sid security system:system:system:s0 + +#fs_use +fs_use_trans devpts system:object_r:system:s0; +fs_use_xattr ext3 system:object_r:system:s0; +fs_use_task pipefs system:object_r:system:s0; + +#genfscon +genfscon proc / system:object_r:system:s1 +genfscon proc /sys system:object_r:system:s0 +genfscon selinuxfs / system:object_r:system:s2:c0.c4 +portcon tcp 1 system:system:system:s0:c0.c1 +netifcon eth0 system:object_r:system:s0 system:object_r:system:s0 +nodecon 127.0.0.1 255.255.255.255 system:object_r:system:s0 +nodecon ::1 ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff system:object_r:system:s0 + diff --git a/tests/policyrep/mls.py b/tests/policyrep/mls.py new file mode 100644 index 0000000..cdbf40a --- /dev/null +++ b/tests/policyrep/mls.py @@ -0,0 +1,97 @@ +# Copyright 2015, Tresys Technology, LLC +# +# This file is part of SETools. +# +# SETools is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# SETools is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SETools. If not, see . +# +import unittest + +from setools import SELinuxPolicy +from setools.policyrep.mls import level_factory, InvalidLevel, range_factory, InvalidRange + + +class BoolQueryTest(unittest.TestCase): + + def setUp(self): + self.p = SELinuxPolicy("tests/policyrep/mls.conf") + + def test_300_level_lookup_no_cats(self): + """Level lookup with no categories.""" + levelobj = level_factory(self.p.policy, "s2") + self.assertEqual(str(levelobj), "s2") + + def test_301_level_lookup_cat_range(self): + """Level lookup with category range.""" + levelobj = level_factory(self.p.policy, "s1:c0.c13") + self.assertEqual(str(levelobj), "s1:c0.c13") + + def test_302_level_lookup_complex_cats(self): + """Level lookup with complex category set.""" + levelobj = level_factory(self.p.policy, "s2:c0.c5,c7,c9.c11,c13") + self.assertEqual(str(levelobj), "s2:c0.c5,c7,c9.c11,c13") + + def test_303_level_lookup_bad1(self): + """Level lookup with garbage.""" + self.assertRaises(InvalidLevel, level_factory, self.p.policy, "FAIL") + + def test_304_level_lookup_bad2(self): + """Level lookup with : in garbage.""" + self.assertRaises(InvalidLevel, level_factory, self.p.policy, "FAIL:BAD") + + def test_305_level_lookup_bad_cat(self): + """Level lookup with invalid category.""" + self.assertRaises(InvalidLevel, level_factory, self.p.policy, "s0:FAIL") + + def test_306_level_lookup_bad_cat_range(self): + """Level lookup with backwards category range.""" + self.assertRaises(InvalidLevel, level_factory, self.p.policy, "s0:c4.c0") + + def test_306_level_lookup_cat_not_assoc(self): + """Level lookup with category not associated with sensitivity.""" + # c4 is not associated with s0. + self.assertRaises(InvalidLevel, level_factory, self.p.policy, "s0:c0,c4") + + def test_400_range_lookup_single_level(self): + """Range lookup with single-level range.""" + rangeobj = range_factory(self.p.policy, "s0") + self.assertEqual(str(rangeobj), "s0") + + def test_401_range_lookup_single_level_redundant(self): + """Range lookup with single-level range (same range listed twice).""" + rangeobj = range_factory(self.p.policy, "s1-s1") + self.assertEqual(str(rangeobj), "s1") + + def test_402_range_lookup_simple(self): + """Range lookup with simple range.""" + rangeobj = range_factory(self.p.policy, "s0-s1:c0.c10") + self.assertEqual(str(rangeobj), "s0 - s1:c0.c10") + + def test_403_range_lookup_no_cats(self): + """Range lookup with no categories.""" + rangeobj = range_factory(self.p.policy, "s0-s1") + self.assertEqual(str(rangeobj), "s0 - s1") + + def test_404_range_lookup_complex(self): + """Range lookup with complex category set.""" + rangeobj = range_factory(self.p.policy, "s0:c0.c2-s2:c0.c5,c7,c9.c11,c13") + self.assertEqual(str(rangeobj), "s0:c0.c2 - s2:c0.c5,c7,c9.c11,c13") + + def test_405_range_lookup_non_dom(self): + """Range lookup with non-dominating high level.""" + self.assertRaises(InvalidRange, range_factory, self.p.policy, "s1-s0") + + def test_406_range_lookup_invalid_range(self): + """Range lookup with an invalid range (low).""" + # c13 is not associated with s0. + self.assertRaises(InvalidRange, range_factory, self.p.policy, "s0:c13-s2:c13")