From 490e6a6f236ba31b3b195a159d57b010702d0405 Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Mon, 9 Dec 2019 04:14:30 -0500 Subject: [PATCH] Add new API to load sepolicy rule file --- native/jni/Android.mk | 3 +- .../{magiskpolicy => include}/magiskpolicy.h | 8 +- native/jni/magiskpolicy/api.cpp | 3 +- native/jni/magiskpolicy/magiskpolicy.cpp | 486 ++---------------- native/jni/magiskpolicy/policydb.cpp | 2 +- native/jni/magiskpolicy/rules.cpp | 2 +- native/jni/magiskpolicy/statement.cpp | 425 +++++++++++++++ 7 files changed, 483 insertions(+), 446 deletions(-) rename native/jni/{magiskpolicy => include}/magiskpolicy.h (91%) create mode 100644 native/jni/magiskpolicy/statement.cpp diff --git a/native/jni/Android.mk b/native/jni/Android.mk index 266a11def..0362f30af 100644 --- a/native/jni/Android.mk +++ b/native/jni/Android.mk @@ -73,6 +73,7 @@ LOCAL_SRC_FILES := \ magiskpolicy/magiskpolicy.cpp \ magiskpolicy/rules.cpp \ magiskpolicy/policydb.cpp \ + magiskpolicy/statement.cpp \ magiskpolicy/sepolicy.c LOCAL_CFLAGS := -DAPPLET_STUB_MAIN=magiskpolicy_main @@ -97,7 +98,6 @@ ifdef BB_INIT LOCAL_STATIC_LIBRARIES := libsepol libxz libutils LOCAL_C_INCLUDES := \ jni/include \ - jni/magiskpolicy \ $(EXT_PATH)/include \ out \ out/$(TARGET_ARCH_ABI) \ @@ -113,6 +113,7 @@ LOCAL_SRC_FILES := \ magiskpolicy/magiskpolicy.cpp \ magiskpolicy/rules.cpp \ magiskpolicy/policydb.cpp \ + magiskpolicy/statement.cpp \ magiskpolicy/sepolicy.c LOCAL_LDFLAGS := -static diff --git a/native/jni/magiskpolicy/magiskpolicy.h b/native/jni/include/magiskpolicy.h similarity index 91% rename from native/jni/magiskpolicy/magiskpolicy.h rename to native/jni/include/magiskpolicy.h index 0dea4734f..c3927bc40 100644 --- a/native/jni/magiskpolicy/magiskpolicy.h +++ b/native/jni/include/magiskpolicy.h @@ -1,6 +1,3 @@ -/* magiskpolicy.h - Public API for policy patching - */ - #pragma once #include @@ -35,3 +32,8 @@ int sepol_exists(const char *source); // Built in rules void sepol_magisk_rules(); + +// Statement parsing +void parse_statement(const char *statement); +void load_rule_file(const char *file); +void statement_help(); diff --git a/native/jni/magiskpolicy/api.cpp b/native/jni/magiskpolicy/api.cpp index 8fa498ae2..e65a238dd 100644 --- a/native/jni/magiskpolicy/api.cpp +++ b/native/jni/magiskpolicy/api.cpp @@ -1,4 +1,5 @@ -#include "magiskpolicy.h" +#include + #include "sepolicy.h" //#define vprint(fmt, ...) printf(fmt, __VA_ARGS__) diff --git a/native/jni/magiskpolicy/magiskpolicy.cpp b/native/jni/magiskpolicy/magiskpolicy.cpp index d612ee3fb..6a7640f0d 100644 --- a/native/jni/magiskpolicy/magiskpolicy.cpp +++ b/native/jni/magiskpolicy/magiskpolicy.cpp @@ -1,459 +1,59 @@ -/* magiskpolicy.cpp - Main function for policy patching - * - * Includes all the parsing logic for the policy statements - */ - #include #include -#include -#include #include #include #include +#include #include "sepolicy.h" -#include "magiskpolicy.h" -using namespace std; - -static const char *type_msg_1 = -"Type 1:\n" -"\" source_type target_type class perm_set\"\n" -"Rules: allow, deny, auditallow, dontaudit\n"; - -static const char *type_msg_2 = -"Type 2:\n" -"\" source_type target_type class operation xperm_set\"\n" -"Rules: allowxperm, auditallowxperm, dontauditxperm\n" -"* The only supported operation is ioctl\n" -"* The only supported xperm_set format is range ([low-high])\n"; - -static const char *type_msg_3 = -"Type 3:\n" -"\" class\"\n" -"Rules: create, permissive, enforcing\n"; - -static const char *type_msg_4 = -"Type 4:\n" -"\"attradd class attribute\"\n"; - -static const char *type_msg_5 = -"Type 5:\n" -"\" source_type target_type class default_type\"\n" -"Rules: type_transition, type_change, type_member\n"; - -static const char *type_msg_6 = -"Type 6:\n" -"\"name_transition source_type target_type class default_type object_name\"\n"; - - -[[noreturn]] static void statements() { - fprintf(stderr, - "One policy statement should be treated as one parameter;\n" - "this means a full policy statement should be enclosed in quotes;\n" - "multiple policy statements can be provided in a single command\n" - "\n" - "The statements has a format of \" [args...]\"\n" - "Multiple types and permissions can be grouped into collections\n" - "wrapped in curly brackets.\n" - "'*' represents a collection containing all valid matches.\n" - "\n" - "Supported policy statements:\n" - "\n" - "%s\n" - "%s\n" - "%s\n" - "%s\n" - "%s\n" - "%s\n" - "Notes:\n" - "* Type 4 - 6 does not support collections\n" - "* Object classes cannot be collections\n" - "* source_type and target_type can also be attributes\n" - "\n" - "Example: allow { s1 s2 } { t1 t2 } class *\n" - "Will be expanded to:\n" - "\n" - "allow s1 t1 class { all permissions }\n" - "allow s1 t2 class { all permissions }\n" - "allow s2 t1 class { all permissions }\n" - "allow s2 t2 class { all permissions }\n" - "\n", - type_msg_1, type_msg_2, type_msg_3, type_msg_4, type_msg_5, type_msg_6); - exit(0); -} +using namespace std::literals; [[noreturn]] static void usage(char *arg0) { fprintf(stderr, - FULL_VER(MagiskPolicy) "\n\n" - "Usage: %s [--options...] [policy statements...]\n" - "\n" - "Options:\n" - " --help show help message for policy statements\n" - " --load FILE load policies from FILE\n" - " --load-split load from preloaded sepolicy or compile\n" - " split policies\n" - " --compile-split compile split cil policies\n" - " --save FILE save policies to FILE\n" - " --live directly apply sepolicy live\n" - " --magisk inject built-in rules for a minimal\n" - " Magisk selinux environment\n" - "\n" - "If neither --load or --compile-split is specified, it will load\n" - "from current live policies (" SELINUX_POLICY ")\n" - "\n", - arg0); +FULL_VER(MagiskPolicy) R"EOF( + +Usage: %s [--options...] [policy statements...] + +Options: + --help show help message for policy statements + --load FILE load policies from FILE + --load-split load from precompiled sepolicy or compile + split policies + --compile-split compile split cil policies + --save FILE save policies to FILE + --live directly apply sepolicy live + --magisk inject built-in rules for a minimal + Magisk selinux environment + --apply FILE apply rules from FILE, read and parsed + line by line as policy statements + +If neither --load or --compile-split is specified, it will load +from current live policies (/sys/fs/selinux/policy) + +)EOF", arg0); exit(1); } -static int parse_bracket(char *tok, char *&stmt, vector *vec) { - if (tok == nullptr || tok[0] != '{') { - // Not in a bracket - vec->push_back(tok); - } else { - if (stmt) - stmt[-1] = ' '; - tok = strchr(tok, '{') + 1; - char *end = strchr(tok, '}'); - if (end == nullptr) // Bracket not closed - return 1; - *end = '\0'; - char *cur; - while ((cur = strtok_r(nullptr, " ", &tok)) != nullptr) - vec->push_back(cur); - stmt = end + 1; - } - return 0; -} - -// Pattern 1: action { source } { target } class { permission } -static int parse_pattern_1(int action, const char *action_str, char *stmt) { - int (*action_func)(const char*, const char*, const char*, const char*); - switch (action) { - case 0: - action_func = sepol_allow; - break; - case 1: - action_func = sepol_deny; - break; - case 2: - action_func = sepol_auditallow; - break; - case 3: - action_func = sepol_dontaudit; - break; - default: - return 1; - } - - int state = 0; - char *cur, *cls; - vector source, target, permission; - while ((cur = strtok_r(nullptr, " ", &stmt)) != nullptr) { - if (cur[0] == '*') cur = ALL; - vector *vec; - switch (state) { - case 0: - vec = &source; - break; - case 1: - vec = ⌖ - break; - case 2: - vec = nullptr; - cls = cur; - break; - case 3: - vec = &permission; - break; - default: - return 1; - } - - if (vec && parse_bracket(cur, stmt, vec)) - return 1; - ++state; - } - if (state != 4 || source.empty() || target.empty() || permission.empty()) - return 1; - - for (auto src : source) - for (auto tgt : target) - for (auto perm : permission) - if (action_func(src, tgt, cls, perm)) - fprintf(stderr, "Error in: %s %s %s %s %s\n", action_str, src, tgt, cls, perm); - - return 0; -} - -// Pattern 2: action { source } { target } { class } ioctl range -static int parse_pattern_2(int action, const char *action_str, char *stmt) { - int (*action_func)(const char*, const char*, const char*, const char*); - switch (action) { - case 0: - action_func = sepol_allowxperm; - break; - case 1: - action_func = sepol_auditallowxperm; - break; - case 2: - action_func = sepol_dontauditxperm; - break; - default: - return 1; - } - - int state = 0; - char *cur, *range; - vector source, target, classes; - while ((cur = strtok_r(nullptr, " ", &stmt)) != nullptr) { - if (cur[0] == '*') cur = ALL; - vector *vec; - switch (state) { - case 0: - vec = &source; - break; - case 1: - vec = ⌖ - break; - case 2: - vec = &classes; - break; - case 3: - // Currently only support ioctl - if (strcmp(cur, "ioctl") != 0) - return 1; - vec = nullptr; - break; - case 4: - vec = nullptr; - range = cur; - break; - default: - return 1; - } - - if (vec && parse_bracket(cur, stmt, vec)) - return 1; - ++state; - } - if (state != 5 || source.empty() || target.empty() || classes.empty()) - return 1; - - for (auto src : source) - for (auto tgt : target) - for (auto cls : classes) - if (action_func(src, tgt, cls, range)) - fprintf(stderr, "Error in: %s %s %s %s %s\n", action_str, src, tgt, cls, range); - - return 0; -} - -// Pattern 3: action { type } -static int parse_pattern_3(int action, const char *action_str, char* stmt) { - int (*action_func)(const char*); - switch (action) { - case 0: - action_func = sepol_create; - break; - case 1: - action_func = sepol_permissive; - break; - case 2: - action_func = sepol_enforce; - break; - default: - return 1; - } - - char *cur; - vector domains; - while ((cur = strtok_r(nullptr, " {}", &stmt)) != nullptr) { - if (cur[0] == '*') cur = ALL; - domains.push_back(cur); - } - - if (domains.empty()) - return 1; - - for (auto dom : domains) - if (action_func(dom)) - fprintf(stderr, "Error in: %s %s\n", action_str, dom); - - return 0; -} - -// Pattern 4: action { class } { attribute } -static int parse_pattern_4(int action, const char *action_str, char *stmt) { - int (*action_func)(const char*, const char*); - switch (action) { - case 0: - action_func = sepol_attradd; - break; - default: - return 1; - } - - int state = 0; - char *cur; - vector classes, attribute; - while ((cur = strtok_r(nullptr, " ", &stmt)) != nullptr) { - if (cur[0] == '*') cur = ALL; - vector *vec; - switch (state) { - case 0: - vec = &classes; - break; - case 1: - vec = &attribute; - break; - default: - return 1; - } - - if (parse_bracket(cur, stmt, vec)) - return 1; - ++state; - } - if (state != 2 || classes.empty() || attribute.empty()) - return 1; - - for (auto cls : classes) - for (auto attr : attribute) - if (action_func(cls, attr)) - fprintf(stderr, "Error in: %s %s %s\n", action_str, cls, attr); - - return 0; -} - -// Pattern 5: action source target class default -static int parse_pattern_5(int action, const char *action_str, char *stmt) { - int (*action_func)(const char*, const char*, const char*, const char*); - switch (action) { - case 0: - action_func = sepol_typetrans; - break; - case 1: - action_func = sepol_typechange; - break; - case 2: - action_func = sepol_typemember; - break; - default: - return 1; - } - int state = 0; - char *cur; - char *source, *target, *cls, *def; - while ((cur = strtok_r(nullptr, " ", &stmt)) != nullptr) { - switch(state) { - case 0: - source = cur; - break; - case 1: - target = cur; - break; - case 2: - cls = cur; - break; - case 3: - def = cur; - break; - default: - return 1; - } - ++state; - } - if (state < 4) return 1; - if (action_func(source, target, cls, def)) - fprintf(stderr, "Error in: %s %s %s %s %s\n", action_str, source, target, cls, def); - return 0; -} - -// Pattern 6: action source target class default filename -static int parse_pattern_6(int action, const char *action_str, char *stmt) { - int state = 0; - char *cur; - char *source, *target, *cls, *def, *filename; - while ((cur = strtok_r(nullptr, " ", &stmt)) != nullptr) { - switch(state) { - case 0: - source = cur; - break; - case 1: - target = cur; - break; - case 2: - cls = cur; - break; - case 3: - def = cur; - break; - case 4: - filename = cur; - break; - default: - return 1; - } - ++state; - } - if (state < 4) return 1; - if (sepol_nametrans(source, target, cls, def, filename)) - fprintf(stderr, "Error in: %s %s %s %s %s %s\n", - action_str, source, target, cls, def, filename); - return 0; -} - -#define add_action(name, type, num) \ -else if (strcmp(name, action) == 0) { \ - if (parse_pattern_##type(num, name, remain)) \ - fprintf(stderr, "Syntax error in '%s'\n\n%s\n", orig.c_str(), type_msg_##type); \ -} - -static void parse_statement(char *statement) { - char *action, *remain; - - // strtok will modify the origin string, duplicate the statement for error messages - string orig(statement); - - action = strtok_r(statement, " ", &remain); - if (remain == nullptr) remain = &action[strlen(action)]; - - if (0) {} - add_action("allow", 1, 0) - add_action("deny", 1, 1) - add_action("auditallow", 1, 2) - add_action("dontaudit", 1, 3) - add_action("allowxperm", 2, 0) - add_action("auditallowxperm", 2, 1) - add_action("dontauditxperm", 2, 2) - add_action("create", 3, 0) - add_action("permissive", 3, 1) - add_action("enforce", 3, 2) - add_action("attradd", 4, 0) - add_action("type_transition", 5, 0) - add_action("type_change", 5, 1) - add_action("type_member", 5, 2) - add_action("name_transition", 6, 0) - else { fprintf(stderr, "Unknown statement: '%s'\n\n", orig.c_str()); } -} - int magiskpolicy_main(int argc, char *argv[]) { cmdline_logging(); - const char *outfile = nullptr; - bool magisk = false, live = false; + const char *out_file = nullptr; + const char *rule_file = nullptr; + bool magisk = false; + bool live = false; if (argc < 2) usage(argv[0]); int i = 1; for (; i < argc; ++i) { // Parse options if (argv[i][0] == '-' && argv[i][1] == '-') { - if (strcmp(argv[i] + 2, "live") == 0) + auto option = argv[i] + 2; + if (option == "live"sv) live = true; - else if (strcmp(argv[i] + 2, "magisk") == 0) + else if (option == "magisk"sv) magisk = true; - else if (strcmp(argv[i] + 2, "load") == 0) { + else if (option == "load"sv) { if (argv[i + 1] == nullptr) usage(argv[0]); if (load_policydb(argv[i + 1])) { @@ -461,23 +61,28 @@ int magiskpolicy_main(int argc, char *argv[]) { return 1; } ++i; - } else if (strcmp(argv[i] + 2, "load-split") == 0) { + } else if (option == "load-split"sv) { if (load_split_cil()) { fprintf(stderr, "Cannot load split cil\n"); return 1; } - } else if (strcmp(argv[i] + 2, "compile-split") == 0) { + } else if (option == "compile-split"sv) { if (compile_split_cil()) { fprintf(stderr, "Cannot compile split cil\n"); return 1; } - } else if (strcmp(argv[i] + 2, "save") == 0) { + } else if (option == "save"sv) { if (argv[i + 1] == nullptr) usage(argv[0]); - outfile = argv[i + 1]; + out_file = argv[i + 1]; ++i; - } else if (strcmp(argv[i] + 2, "help") == 0) { - statements(); + } else if (option == "apply"sv) { + if (argv[i + 1] == nullptr) + usage(argv[0]); + rule_file = argv[i + 1]; + ++i; + } else if (option == "help"sv) { + statement_help(); } else { usage(argv[0]); } @@ -495,6 +100,9 @@ int magiskpolicy_main(int argc, char *argv[]) { if (magisk) sepol_magisk_rules(); + if (rule_file) + load_rule_file(rule_file); + for (; i < argc; ++i) parse_statement(argv[i]); @@ -503,8 +111,8 @@ int magiskpolicy_main(int argc, char *argv[]) { return 1; } - if (outfile && dump_policydb(outfile)) { - fprintf(stderr, "Cannot dump policy to %s\n", outfile); + if (out_file && dump_policydb(out_file)) { + fprintf(stderr, "Cannot dump policy to %s\n", out_file); return 1; } diff --git a/native/jni/magiskpolicy/policydb.cpp b/native/jni/magiskpolicy/policydb.cpp index 723fd3876..2ffaacfad 100644 --- a/native/jni/magiskpolicy/policydb.cpp +++ b/native/jni/magiskpolicy/policydb.cpp @@ -8,8 +8,8 @@ #include #include #include +#include -#include "magiskpolicy.h" #include "sepolicy.h" int load_policydb(const char *file) { diff --git a/native/jni/magiskpolicy/rules.cpp b/native/jni/magiskpolicy/rules.cpp index ffbc83bfc..d930ae69f 100644 --- a/native/jni/magiskpolicy/rules.cpp +++ b/native/jni/magiskpolicy/rules.cpp @@ -1,7 +1,7 @@ #include #include +#include -#include "magiskpolicy.h" #include "sepolicy.h" static void allowSuClient(const char *target) { diff --git a/native/jni/magiskpolicy/statement.cpp b/native/jni/magiskpolicy/statement.cpp new file mode 100644 index 000000000..bf97ae5e4 --- /dev/null +++ b/native/jni/magiskpolicy/statement.cpp @@ -0,0 +1,425 @@ +#include +#include +#include + +#include +#include +#include + +using namespace std; + +static const char *type_msg_1 = +R"EOF(Type 1: +" source_type target_type class perm_set" +Rules: allow, deny, auditallow, dontaudit +)EOF"; + +static const char *type_msg_2 = +R"EOF(Type 2: +" source_type target_type class operation xperm_set" +Rules: allowxperm, auditallowxperm, dontauditxperm +* The only supported operation is ioctl +* The only supported xperm_set format is range ([low-high]) +)EOF"; + +static const char *type_msg_3 = +R"EOF(Type 3: +" class" +Rules: create, permissive, enforcing +)EOF"; + +static const char *type_msg_4 = +R"EOF(Type 4: +"attradd class attribute" +)EOF"; + +static const char *type_msg_5 = +R"EOF(Type 5: +" source_type target_type class default_type" +Rules: type_transition, type_change, type_member +)EOF"; + +static const char *type_msg_6 = +R"EOF(Type 6: +"name_transition source_type target_type class default_type object_name" +)EOF"; + +void statement_help() { + fprintf(stderr, +R"EOF(One policy statement should be treated as one parameter; +this means a full policy statement should be enclosed in quotes. +Multiple policy statements can be provided in a single command. + +The statements has a format of " [args...]" +Multiple types and permissions can be grouped into collections +wrapped in curly brackets. +'*' represents a collection containing all valid matches. + +Supported policy statements: + +%s +%s +%s +%s +%s +%s +Notes: +* Type 4 - 6 does not support collections +* Object classes cannot be collections +* source_type and target_type can also be attributes + +Example: allow { s1 s2 } { t1 t2 } class * +Will be expanded to: + +allow s1 t1 class { all-permissions } +allow s1 t2 class { all-permissions } +allow s2 t1 class { all-permissions } +allow s2 t2 class { all-permissions } + +)EOF", type_msg_1, type_msg_2, type_msg_3, type_msg_4, type_msg_5, type_msg_6); + exit(0); +} + +static int parse_bracket(char *tok, char *&stmt, vector &vec) { + if (tok == nullptr || tok[0] != '{') { + // Not in a bracket + vec.push_back(tok); + } else { + if (stmt) + stmt[-1] = ' '; + tok = strchr(tok, '{') + 1; + char *end = strchr(tok, '}'); + if (end == nullptr) // Bracket not closed + return 1; + *end = '\0'; + char *cur; + while ((cur = strtok_r(nullptr, " ", &tok)) != nullptr) + vec.push_back(cur); + stmt = end + 1; + } + return 0; +} + +// Pattern 1: action { source } { target } class { permission } +static int parse_pattern_1(int action, const char *action_str, char *stmt) { + int (*action_func)(const char*, const char*, const char*, const char*); + switch (action) { + case 0: + action_func = sepol_allow; + break; + case 1: + action_func = sepol_deny; + break; + case 2: + action_func = sepol_auditallow; + break; + case 3: + action_func = sepol_dontaudit; + break; + default: + return 1; + } + + int state = 0; + char *cur, *cls; + vector source, target, permission; + while ((cur = strtok_r(nullptr, " ", &stmt)) != nullptr) { + if (cur[0] == '*') cur = ALL; + vector *vec; + switch (state) { + case 0: + vec = &source; + break; + case 1: + vec = ⌖ + break; + case 2: + vec = nullptr; + cls = cur; + break; + case 3: + vec = &permission; + break; + default: + return 1; + } + + if (vec && parse_bracket(cur, stmt, *vec)) + return 1; + ++state; + } + if (state != 4 || source.empty() || target.empty() || permission.empty()) + return 1; + + for (auto src : source) + for (auto tgt : target) + for (auto perm : permission) + if (action_func(src, tgt, cls, perm)) + LOGW("Error in: %s %s %s %s %s\n", action_str, src, tgt, cls, perm); + + return 0; +} + +// Pattern 2: action { source } { target } { class } ioctl range +static int parse_pattern_2(int action, const char *action_str, char *stmt) { + int (*action_func)(const char*, const char*, const char*, const char*); + switch (action) { + case 0: + action_func = sepol_allowxperm; + break; + case 1: + action_func = sepol_auditallowxperm; + break; + case 2: + action_func = sepol_dontauditxperm; + break; + default: + return 1; + } + + int state = 0; + char *cur, *range; + vector source, target, classes; + while ((cur = strtok_r(nullptr, " ", &stmt)) != nullptr) { + if (cur[0] == '*') cur = ALL; + vector *vec; + switch (state) { + case 0: + vec = &source; + break; + case 1: + vec = ⌖ + break; + case 2: + vec = &classes; + break; + case 3: + // Currently only support ioctl + if (strcmp(cur, "ioctl") != 0) + return 1; + vec = nullptr; + break; + case 4: + vec = nullptr; + range = cur; + break; + default: + return 1; + } + + if (vec && parse_bracket(cur, stmt, *vec)) + return 1; + ++state; + } + if (state != 5 || source.empty() || target.empty() || classes.empty()) + return 1; + + for (auto src : source) + for (auto tgt : target) + for (auto cls : classes) + if (action_func(src, tgt, cls, range)) + LOGW("Error in: %s %s %s %s %s\n", action_str, src, tgt, cls, range); + + return 0; +} + +// Pattern 3: action { type } +static int parse_pattern_3(int action, const char *action_str, char* stmt) { + int (*action_func)(const char*); + switch (action) { + case 0: + action_func = sepol_create; + break; + case 1: + action_func = sepol_permissive; + break; + case 2: + action_func = sepol_enforce; + break; + default: + return 1; + } + + char *cur; + vector domains; + while ((cur = strtok_r(nullptr, " {}", &stmt)) != nullptr) { + if (cur[0] == '*') cur = ALL; + domains.push_back(cur); + } + + if (domains.empty()) + return 1; + + for (auto dom : domains) + if (action_func(dom)) + LOGW("Error in: %s %s\n", action_str, dom); + + return 0; +} + +// Pattern 4: action { class } { attribute } +static int parse_pattern_4(int action, const char *action_str, char *stmt) { + int (*action_func)(const char*, const char*); + switch (action) { + case 0: + action_func = sepol_attradd; + break; + default: + return 1; + } + + int state = 0; + char *cur; + vector classes, attribute; + while ((cur = strtok_r(nullptr, " ", &stmt)) != nullptr) { + if (cur[0] == '*') cur = ALL; + vector *vec; + switch (state) { + case 0: + vec = &classes; + break; + case 1: + vec = &attribute; + break; + default: + return 1; + } + + if (parse_bracket(cur, stmt, *vec)) + return 1; + ++state; + } + if (state != 2 || classes.empty() || attribute.empty()) + return 1; + + for (auto cls : classes) + for (auto attr : attribute) + if (action_func(cls, attr)) + LOGW("Error in: %s %s %s\n", action_str, cls, attr); + + return 0; +} + +// Pattern 5: action source target class default +static int parse_pattern_5(int action, const char *action_str, char *stmt) { + int (*action_func)(const char*, const char*, const char*, const char*); + switch (action) { + case 0: + action_func = sepol_typetrans; + break; + case 1: + action_func = sepol_typechange; + break; + case 2: + action_func = sepol_typemember; + break; + default: + return 1; + } + int state = 0; + char *cur; + char *source, *target, *cls, *def; + while ((cur = strtok_r(nullptr, " ", &stmt)) != nullptr) { + switch(state) { + case 0: + source = cur; + break; + case 1: + target = cur; + break; + case 2: + cls = cur; + break; + case 3: + def = cur; + break; + default: + return 1; + } + ++state; + } + if (state < 4) return 1; + if (action_func(source, target, cls, def)) + LOGW("Error in: %s %s %s %s %s\n", action_str, source, target, cls, def); + return 0; +} + +// Pattern 6: action source target class default filename +static int parse_pattern_6(int action, const char *action_str, char *stmt) { + int state = 0; + char *cur; + char *source, *target, *cls, *def, *filename; + while ((cur = strtok_r(nullptr, " ", &stmt)) != nullptr) { + switch(state) { + case 0: + source = cur; + break; + case 1: + target = cur; + break; + case 2: + cls = cur; + break; + case 3: + def = cur; + break; + case 4: + filename = cur; + break; + default: + return 1; + } + ++state; + } + if (state < 4) return 1; + if (sepol_nametrans(source, target, cls, def, filename)) + LOGW("Error in: %s %s %s %s %s %s\n", action_str, source, target, cls, def, filename); + return 0; +} + +#define add_action(name, type, num) \ +else if (strcmp(name, action) == 0) { \ + if (parse_pattern_##type(num, name, remain)) \ + LOGW("Syntax error in '%s'\n\n%s\n", statement, type_msg_##type); \ +} + +void parse_statement(const char *statement) { + char *action, *remain; + + // strtok will modify strings, duplicate the statement + string stmt(statement); + + action = strtok_r(stmt.data(), " ", &remain); + + if (remain == nullptr) { + LOGE("Syntax error in '%s'\n\n", statement); + return; + } + + if (0) {} + add_action("allow", 1, 0) + add_action("deny", 1, 1) + add_action("auditallow", 1, 2) + add_action("dontaudit", 1, 3) + add_action("allowxperm", 2, 0) + add_action("auditallowxperm", 2, 1) + add_action("dontauditxperm", 2, 2) + add_action("create", 3, 0) + add_action("permissive", 3, 1) + add_action("enforce", 3, 2) + add_action("attradd", 4, 0) + add_action("type_transition", 5, 0) + add_action("type_change", 5, 1) + add_action("type_member", 5, 2) + add_action("name_transition", 6, 0) + else { LOGW("Unknown statement: '%s'\n\n", statement); } +} + +void load_rule_file(const char *file) { + file_readline(file, [](string_view line) -> bool { + if (line.empty() || line[0] == '#') + return true; + parse_statement(line.data()); + return true; + }, true); +}