[apparmor] [PATCH] fixup! parser: Add functions for features support tests
Tyler Hicks
tyhicks at canonical.com
Thu Mar 19 15:59:06 UTC 2015
Adjust the aa_features_supports() function to accept a const string and
add unit tests for the function that replaces strtok() in order to allow
for a const string.
Also, add unit tests for walk_one() and fix a few bugs that were found
by the unit tests.
Finally, use the correct name, which is "braces", for '{' and '}'.
Signed-off-by: Tyler Hicks <tyhicks at canonical.com>
---
parser/Makefile | 4 +-
parser/features.c | 406 +++++++++++++++++++++++++++++++++++++++++++++------
parser/features.h | 2 +-
parser/parser_main.c | 39 ++---
4 files changed, 380 insertions(+), 71 deletions(-)
diff --git a/parser/Makefile b/parser/Makefile
index 77be708..5e8616e 100644
--- a/parser/Makefile
+++ b/parser/Makefile
@@ -109,7 +109,7 @@ EXTRA_CFLAGS += $(INCLUDE_APPARMOR)
LEX_C_FILES = parser_lex.c
YACC_C_FILES = parser_yacc.c parser_yacc.h
-TESTS = tst_regex tst_misc tst_symtab tst_variable tst_lib
+TESTS = tst_regex tst_misc tst_symtab tst_variable tst_lib tst_features
TEST_CFLAGS = $(EXTRA_CFLAGS) -DUNIT_TEST -Wno-unused-result
TEST_OBJECTS = $(filter-out \
parser_lex.o \
@@ -294,6 +294,8 @@ tst_lib: lib.c parser.h $(filter-out lib.o, ${TEST_OBJECTS})
$(CXX) $(TEST_CFLAGS) -o $@ $< $(filter-out $(<:.c=.o), ${TEST_OBJECTS}) $(TEST_LDFLAGS) $(TEST_LDLIBS)
tst_%: parser_%.c parser.h $(filter-out parser_%.o, ${TEST_OBJECTS})
$(CXX) $(TEST_CFLAGS) -o $@ $< $(filter-out $(<:.c=.o), ${TEST_OBJECTS}) $(TEST_LDFLAGS) $(TEST_LDLIBS)
+tst_features: features.c features.h parser.h
+ $(CXX) $(TEST_CFLAGS) -o $@ $< $(filter-out $(<:.c=.o), ${TEST_OBJECTS}) $(TEST_LDFLAGS) $(TEST_LDLIBS)
.SILENT: check
.PHONY: check
diff --git a/parser/features.c b/parser/features.c
index 50e0024..a757b62 100644
--- a/parser/features.c
+++ b/parser/features.c
@@ -49,6 +49,11 @@ struct features_struct {
char *pos;
};
+struct component {
+ const char *str;
+ size_t len;
+};
+
static int features_snprintf(struct features_struct *fst, const char *fmt, ...)
{
va_list args;
@@ -164,22 +169,111 @@ static int load_features_file(const char *name, char *buffer, size_t size)
return 0;
}
-static bool walk_one(const char **str, const char *component, bool is_top_level)
+static bool islbrace(int c)
+{
+ return c == '{';
+}
+
+static bool isrbrace(int c)
+{
+ return c == '}';
+}
+
+static bool isnul(int c)
+{
+ return c == '\0';
+}
+
+static bool isbrace(int c)
+{
+ return islbrace(c) || isrbrace(c);
+}
+
+static bool isbrace_or_nul(int c)
+{
+ return isbrace(c) || isnul(c);
+}
+
+static bool isbrace_space_or_nul(int c)
+{
+ return isbrace(c) || isspace(c) || isnul(c);
+}
+
+static size_t tokenize_path_components(const char *str,
+ struct component *components,
+ size_t max_components)
+{
+ size_t i = 0;
+
+ memset(components, 0, sizeof(*components) * max_components);
+
+ if (!str)
+ return 0;
+
+ while (*str && i < max_components) {
+ const char *fwdslash = strchrnul(str, '/');
+
+ /* Save the token if it is not "/" */
+ if (fwdslash != str) {
+ components[i].str = str;
+ components[i].len = fwdslash - str;
+ i++;
+ }
+
+ if (isnul(*fwdslash))
+ break;
+
+ str = fwdslash + 1;
+ }
+
+ return i;
+}
+
+/**
+ * walk_one - walk one component of a features string
+ * @str: a pointer to the current position in a features string
+ * @component: the component to walk
+ * @is_top_level: true if component is a top-level component
+ *
+ * Imagine a features string such as:
+ *
+ * "feat1 { subfeat1.1 subfeat1.2 } feat2 { subfeat2.1 { subfeat2.1.1 } }"
+ *
+ * You want to know if "feat2/subfeat2.1/subfeat2.8" is valid. It will take 3
+ * invocations of this function to determine if that string is valid. On the
+ * first call, *@str will point to the beginning of the features string,
+ * component->str will be "feat2", and is_top_level will be true since feat2 is
+ * a top level feature. The function will return true and *@str will now point
+ * at the the left brace after "feat2" in the features string. You can call
+ * this function again with component->str being equal to "subfeat2.1" and it
+ * will return true and *@str will now point at the left brace after
+ * "subfeat2.1" in the features string. A third call to the function, with
+ * component->str equal to "subfeat2.8", will return false and *@str will not
+ * be changed.
+ *
+ * Returns true if the walk was successful and false otherwise. If the walk was
+ * successful, *@str will point to the first encountered brace after the walk.
+ * If the walk was unsuccessful, *@str is not updated.
+ */
+static bool walk_one(const char **str, const struct component *component,
+ bool is_top_level)
{
- const char *cur = *str;
- uint32_t bracket_count = 0;
- int i = 0;
+ const char *cur;
+ uint32_t brace_count = 0;
+ size_t i = 0;
- /* Empty strings are not accepted */
- if (!*cur || !component[0])
+ /* NULL pointers and empty strings are not accepted */
+ if (!str || !*str || !component || !component->str || !component->len)
return false;
+ cur = *str;
+
/**
* If @component is not top-level, the first character in the string to
* search MUST be '{'
*/
if (!is_top_level) {
- if (*cur != '{')
+ if (!islbrace(*cur))
return false;
cur++;
@@ -190,31 +284,29 @@ static bool walk_one(const char **str, const char *component, bool is_top_level)
* completes, cur will either point one character past the end of the
* matched @component or to the NUL terminator of *@str.
*/
- while(*cur && component[i]) {
+ while(!isnul(*cur) && i < component->len) {
if (!isascii(*cur)) {
/* Only ASCII is expected */
return false;
- } else if (*cur == '{') {
- /* There's a limit to the number of opening brackets */
- if (bracket_count == UINT32_MAX)
+ } else if (islbrace(*cur)) {
+ /* There's a limit to the number of left braces */
+ if (brace_count == UINT32_MAX)
return false;
- bracket_count++;
- } else if (*cur == '}') {
- /* Check for unexpected closing brackets */
- if (bracket_count == 0)
+ brace_count++;
+ } else if (isrbrace(*cur)) {
+ /* Check for unexpected right braces */
+ if (brace_count == 0)
return false;
- bracket_count--;
+ brace_count--;
}
/**
- * Move to the next character in @component if we have a match
- * and either @component is not top-level or, if @component is
- * top-level, we're not inside of brackets
+ * Move to the next character in @component if we're not inside
+ * of braces and we have a character match
*/
- if (*cur == component[i] &&
- (!is_top_level || bracket_count == 0))
+ if (brace_count == 0 && *cur == component->str[i])
i++;
else
i = 0;
@@ -222,20 +314,20 @@ static bool walk_one(const char **str, const char *component, bool is_top_level)
cur++;
}
- /* A full match was not found if component[i] is non-NUL */
- if (component[i])
+ /* Return false if a full match was not found */
+ if (i != component->len) {
+ return false;
+ } else if (!isbrace_space_or_nul(*cur))
return false;
/**
- * This loop eats up valid (ASCII) characters until a non-bracket or
- * non-space character is found so that *@str is properly set to call
- * back into this function, if necessary
+ * This loop eats up valid (ASCII) characters until a brace or NUL
+ * character is found so that *@str is properly set to call back into
+ * this function
*/
- while (*cur) {
+ while (!isbrace_or_nul(*cur)) {
if (!isascii(*cur))
return false;
- else if (*cur == '{' || *cur == '}' || !isspace(*cur))
- break;
cur++;
}
@@ -391,12 +483,11 @@ bool aa_features_is_equal(aa_features *features1, aa_features *features2)
*
* Returns: a bool specifying the support status of @str feature
*/
-bool aa_features_supports(aa_features *features, char *str)
+bool aa_features_supports(aa_features *features, const char *str)
{
const char *features_string = features->string;
- char *components[32];
- char *saveptr = NULL;
- size_t i;
+ struct component components[32];
+ size_t i, num_components;
/* Empty strings are not accepted. Neither are leading '/' chars. */
if (!str || str[0] == '/')
@@ -404,26 +495,251 @@ bool aa_features_supports(aa_features *features, char *str)
/**
* Break @str into an array of components. For example,
- * "mount/mask/mount" would turn into "mount" as the first component,
- * "mask" as the second, and "mount" as the third
+ * "mount/mask/mount" would turn into { "mount", 5 } as the first
+ * component, { "mask", 4 } as the second, and { "mount", 5 } as the
+ * third
*/
- for (i = 0; i < sizeof(components); i++) {
- components[i] = strtok_r(str, "/", &saveptr);
- if (!components[i])
- break;
-
- str = NULL;
- }
+ num_components = tokenize_path_components(str, components,
+ sizeof(components) / sizeof(*components));
/* At least one valid token is required */
- if (!components[0])
+ if (!num_components)
return false;
/* Ensure that all components are valid and found */
- for (i = 0; i < sizeof(components) && components[i]; i++) {
- if (!walk_one(&features_string, components[i], i == 0))
+ for (i = 0; i < num_components; i++) {
+ if (!walk_one(&features_string, &components[i], i == 0))
return false;
}
return true;
}
+
+#ifdef UNIT_TEST
+
+#include "unit_test.h"
+
+static int test_tokenize_path_components(void)
+{
+ struct component components[32];
+ size_t max = sizeof(components) / sizeof(*components);
+ size_t num;
+ int rc = 0;
+
+ num = tokenize_path_components(NULL, components, max);
+ MY_TEST(num == 0, "basic NULL test");
+
+ num = tokenize_path_components("", components, max);
+ MY_TEST(num == 0, "basic empty string test");
+
+ num = tokenize_path_components("a", components, 0);
+ MY_TEST(num == 0, "basic empty array test");
+
+ num = tokenize_path_components("a", components, 1);
+ MY_TEST(num == 1, "one component full test (num)");
+ MY_TEST(!strncmp(components[0].str, "a", components[0].len),
+ "one component full test (components[0])");
+
+ num = tokenize_path_components("a/b", components, 2);
+ MY_TEST(num == 2, "two component full test (num)");
+ MY_TEST(!strncmp(components[0].str, "a", components[0].len),
+ "two component full test (components[0])");
+ MY_TEST(!strncmp(components[1].str, "b", components[0].len),
+ "two component full test (components[1])");
+
+ num = tokenize_path_components("a/b/c", components, 1);
+ MY_TEST(num == 1, "not enough components full test (num)");
+ MY_TEST(!strncmp(components[0].str, "a/b/c", components[0].len),
+ "not enough components full test (components[0])");
+
+ num = tokenize_path_components("/", components, max);
+ MY_TEST(num == 0, "no valid components #1 (num)");
+
+ num = tokenize_path_components("////////", components, max);
+ MY_TEST(num == 0, "no valid components #2 (num)");
+
+ num = tokenize_path_components("////////////foo////", components, max);
+ MY_TEST(num == 1, "many invalid components (num)");
+ MY_TEST(!strncmp(components[0].str, "foo", components[0].len),
+ "many invalid components (components[0])");
+
+ num = tokenize_path_components("file", components, max);
+ MY_TEST(num == 1, "file (num)");
+ MY_TEST(!strncmp(components[0].str, "file", components[0].len),
+ "file (components[0])");
+
+ num = tokenize_path_components("/policy///versions//v7/", components, max);
+ MY_TEST(num == 3, "v7 (num)");
+ MY_TEST(!strncmp(components[0].str, "policy", components[0].len),
+ "v7 (components[0])");
+ MY_TEST(!strncmp(components[1].str, "versions", components[1].len),
+ "v7 (components[1])");
+ MY_TEST(!strncmp(components[2].str, "v7", components[2].len),
+ "v7 (components[2])");
+
+ num = tokenize_path_components("dbus/mask/send", components, max);
+ MY_TEST(num == 3, "dbus send (num)");
+ MY_TEST(!strncmp(components[0].str, "dbus", components[0].len),
+ "dbus send (components[0])");
+ MY_TEST(!strncmp(components[1].str, "mask", components[1].len),
+ "dbus send (components[1])");
+ MY_TEST(!strncmp(components[2].str, "send", components[2].len),
+ "dbus send (components[2])");
+
+
+ return rc;
+}
+
+static int do_test_walk_one(const char **str, const struct component *component,
+ bool is_top_level, bool expect_walk, const char *e1,
+ const char *e2, const char *e3)
+{
+ const char *save = str ? *str : NULL;
+ bool walked = walk_one(str, component, is_top_level);
+ int rc = 0;
+
+ /* Check if the result of the walk matches the expected result*/
+ MY_TEST(expect_walk == walked, e1);
+ if (save) {
+ /**
+ * If a walk was expected, @*str should have changed. It
+ * shouldn't change if a walk was unexpected.
+ */
+ if (expect_walk) {
+ MY_TEST(*str != save, e2);
+ } else {
+ MY_TEST(*str == save, e3);
+ }
+ }
+
+ return rc;
+}
+
+#define MY_WALK_TEST(str, component, is_top_level, expect_walk, error) \
+ if (do_test_walk_one(str, component, is_top_level, \
+ expect_walk, \
+ error " (walk check)", \
+ error " (str didn't change)", \
+ error " (str changed)")) { \
+ rc = 1; \
+ }
+
+#define MY_GOOD_WALK_TEST(str, component, is_top_level, error) \
+ MY_WALK_TEST(str, component, is_top_level, true, error)
+#define MY_BAD_WALK_TEST(str, component, is_top_level, error) \
+ MY_WALK_TEST(str, component, is_top_level, false, error)
+
+static int test_walk_one(void)
+{
+ struct component c;
+ const char *str;
+ int rc = 0;
+
+ MY_BAD_WALK_TEST(NULL, &c, true, "basic NULL str test");
+
+ str = NULL;
+ MY_BAD_WALK_TEST(&str, &c, true, "basic NULL *str test");
+
+ str = "test { a b }";
+ MY_BAD_WALK_TEST(&str, NULL, true, "basic NULL component test");
+
+ str = "test { a b }";
+ c = { NULL, 8 };
+ MY_BAD_WALK_TEST(&str, &c, true, "basic NULL c.str test");
+
+ str = "test { a b }";
+ c = { "", 0 };
+ MY_BAD_WALK_TEST(&str, &c, true, "basic empty c.str test");
+
+ str = "test";
+ c = { "test", 4 };
+ MY_GOOD_WALK_TEST(&str, &c, true, "single component");
+
+ str = "testX";
+ c = { "test", 4 };
+ MY_BAD_WALK_TEST(&str, &c, true, "single component bad str");
+
+ str = "test";
+ c = { "testX", 5 };
+ MY_BAD_WALK_TEST(&str, &c, true, "single component bad c.str");
+
+ str = "test { }";
+ c = { "test", 4 };
+ MY_GOOD_WALK_TEST(&str, &c, true, "single component empty braces #1");
+
+ str = "test {\n\t}";
+ c = { "test", 4 };
+ MY_GOOD_WALK_TEST(&str, &c, true, "single component empty braces #2");
+
+ str = "test{}";
+ c = { "test", 4 };
+ MY_GOOD_WALK_TEST(&str, &c, true, "single component empty braces #3");
+
+ str = "test\t{}\n ";
+ c = { "test", 4 };
+ MY_GOOD_WALK_TEST(&str, &c, true, "single component empty braces #4");
+
+ str = "test {}";
+ c = { "test", 4 };
+ MY_BAD_WALK_TEST(&str, &c, false, "single component bad is_top_level");
+
+ str = "front{back";
+ c = { "frontback", 9};
+ MY_BAD_WALK_TEST(&str, &c, true, "brace in the middle #1");
+ MY_BAD_WALK_TEST(&str, &c, false, "brace in the middle #2");
+
+ str = "ardvark { bear cat { deer } }";
+ c = { "ardvark", 7 };
+ MY_GOOD_WALK_TEST(&str, &c, true, "animal walk good ardvark");
+ c = { "deer", 4 };
+ MY_BAD_WALK_TEST(&str, &c, false, "animal walk bad deer");
+ MY_BAD_WALK_TEST(&str, &c, true, "animal walk bad top-level deer");
+ c = { "bear", 4 };
+ MY_BAD_WALK_TEST(&str, &c, true, "animal walk bad bear");
+ c = { "cat", 3 };
+ MY_GOOD_WALK_TEST(&str, &c, false, "animal walk good cat");
+ c = { "ardvark", 7 };
+ MY_BAD_WALK_TEST(&str, &c, true, "animal walk bad ardvark");
+ c = { "deer", 4 };
+ MY_GOOD_WALK_TEST(&str, &c, false, "animal walk good deer");
+
+ str = "dbus {mask {acquire send receive\n}\n}\nsignal {mask {hup int\n}\n}";
+ c = { "hup", 3 };
+ MY_BAD_WALK_TEST(&str, &c, true, "dbus/signal bad hup #1");
+ MY_BAD_WALK_TEST(&str, &c, false, "dbus/signal bad hup #2");
+ c = { "signal", 6 };
+ MY_BAD_WALK_TEST(&str, &c, false, "dbus/signal bad signal");
+ MY_GOOD_WALK_TEST(&str, &c, true, "dbus/signal good signal");
+ c = { "mask", 4 };
+ MY_BAD_WALK_TEST(&str, &c, true, "dbus/signal bad mask");
+ MY_GOOD_WALK_TEST(&str, &c, false, "dbus/signal good mask");
+ c = { "hup", 3 };
+ MY_GOOD_WALK_TEST(&str, &c, false, "dbus/signal good hup");
+
+ str = "policy {set_load {yes\n}\nversions {v7 {yes\n}\nv6 {yes\n}";
+ c = { "policy", 6 };
+ MY_GOOD_WALK_TEST(&str, &c, true, "policy good");
+ c = { "versions", 8 };
+ MY_GOOD_WALK_TEST(&str, &c, false, "versions good");
+ c = { "v7", 2 };
+ MY_GOOD_WALK_TEST(&str, &c, false, "v7 good");
+
+ return rc;
+}
+
+int main(void)
+{
+ int retval, rc = 0;
+
+ retval = test_tokenize_path_components();
+ if (retval)
+ rc = retval;
+
+ retval = test_walk_one();
+ if (retval)
+ rc = retval;
+
+ return rc;
+}
+
+#endif
diff --git a/parser/features.h b/parser/features.h
index 96d0ee9..26e2c50 100644
--- a/parser/features.h
+++ b/parser/features.h
@@ -29,6 +29,6 @@ aa_features *aa_features_ref(aa_features *features);
void aa_features_unref(aa_features *features);
const char *aa_features_get_string(aa_features *features);
bool aa_features_is_equal(aa_features *features1, aa_features *features2);
-bool aa_features_supports(aa_features *features, char *str);
+bool aa_features_supports(aa_features *features, const char *str);
#endif /* __AA_FEATURES_H */
diff --git a/parser/parser_main.c b/parser/parser_main.c
index 2d220a7..96eb308 100644
--- a/parser/parser_main.c
+++ b/parser/parser_main.c
@@ -574,18 +574,6 @@ no_match:
static void set_supported_features(void)
{
- char file[] = "file";
- char network[] = "network";
- char af_unix[] = "network/af_unix";
- char mount[] = "mount";
- char dbus[] = "dbus";
- char signal[] = "signal";
- char ptrace[] = "ptrace";
- char set_load[] = "policy/set_load";
- char diff_encode[] = "policy/diff_encode";
- char v7[] = "policy/versions/v7";
- char v6[] = "policy/versions/v6";
-
/* has process_args() already assigned a match string? */
if (!features && aa_features_new_from_kernel(&features) == -1) {
set_features_by_match_file();
@@ -593,19 +581,22 @@ static void set_supported_features(void)
}
perms_create = 1;
- kernel_supports_policydb = aa_features_supports(features, file);
- kernel_supports_network = aa_features_supports(features, network);
- kernel_supports_unix = aa_features_supports(features, af_unix);
- kernel_supports_mount = aa_features_supports(features, mount);
- kernel_supports_dbus = aa_features_supports(features, dbus);
- kernel_supports_signal = aa_features_supports(features, signal);
- kernel_supports_ptrace = aa_features_supports(features, ptrace);
- kernel_supports_setload = aa_features_supports(features, set_load);
- kernel_supports_diff_encode = aa_features_supports(features, diff_encode);
-
- if (aa_features_supports(features, v7))
+ kernel_supports_policydb = aa_features_supports(features, "file");
+ kernel_supports_network = aa_features_supports(features, "network");
+ kernel_supports_unix = aa_features_supports(features,
+ "network/af_unix");
+ kernel_supports_mount = aa_features_supports(features, "mount");
+ kernel_supports_dbus = aa_features_supports(features, "dbus");
+ kernel_supports_signal = aa_features_supports(features, "signal");
+ kernel_supports_ptrace = aa_features_supports(features, "ptrace");
+ kernel_supports_setload = aa_features_supports(features,
+ "policy/set_load");
+ kernel_supports_diff_encode = aa_features_supports(features,
+ "policy/diff_encode");
+
+ if (aa_features_supports(features, "policy/versions/v7"))
kernel_abi_version = 7;
- else if (aa_features_supports(features, v6))
+ else if (aa_features_supports(features, "policy/versions/v6"))
kernel_abi_version = 6;
if (!kernel_supports_diff_encode)
--
2.1.4
More information about the AppArmor
mailing list