From 4d2921e742b34c848343a8372f51295ff74a182f Mon Sep 17 00:00:00 2001 From: LoveSy Date: Sun, 3 Mar 2024 10:33:04 +0800 Subject: [PATCH] Rewrite sepolicy statement parsing in Rust --- native/src/Android.mk | 1 - native/src/sepolicy/api.cpp | 280 +++++++++----- native/src/sepolicy/include/sepolicy.hpp | 40 +- native/src/sepolicy/lib.rs | 94 ++++- native/src/sepolicy/policy.hpp | 4 +- native/src/sepolicy/rules.cpp | 136 ------- native/src/sepolicy/rules.rs | 145 +++++++ native/src/sepolicy/sepolicy.cpp | 111 ++---- native/src/sepolicy/statement.cpp | 468 ----------------------- native/src/sepolicy/statement.rs | 349 +++++++++++++++++ 10 files changed, 834 insertions(+), 794 deletions(-) delete mode 100644 native/src/sepolicy/rules.cpp create mode 100644 native/src/sepolicy/rules.rs create mode 100644 native/src/sepolicy/statement.rs diff --git a/native/src/Android.mk b/native/src/Android.mk index 50dc583d3..e3130d0b5 100644 --- a/native/src/Android.mk +++ b/native/src/Android.mk @@ -167,7 +167,6 @@ LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES) LOCAL_SRC_FILES := \ sepolicy/api.cpp \ sepolicy/sepolicy.cpp \ - sepolicy/rules.cpp \ sepolicy/policydb.cpp \ sepolicy/statement.cpp \ sepolicy/policy-rs.cpp diff --git a/native/src/sepolicy/api.cpp b/native/src/sepolicy/api.cpp index 6db88cc9f..1de741a87 100644 --- a/native/src/sepolicy/api.cpp +++ b/native/src/sepolicy/api.cpp @@ -1,104 +1,27 @@ #include - +#include "flags.h" #include "policy.hpp" -#if 0 -// Print out all rules going through public API for debugging -template -static void dprint(const char *action, Args ...args) { - std::string s(action); - for (int i = 0; i < sizeof...(args); ++i) s += " %s"; - s += "\n"; - LOGD(s.data(), as_str(args)...); -} -#else -#define dprint(...) -#endif - -bool sepolicy::allow(const char *s, const char *t, const char *c, const char *p) { - dprint(__FUNCTION__, s, t, c, p); - return impl->add_rule(s, t, c, p, AVTAB_ALLOWED, false); -} - -bool sepolicy::deny(const char *s, const char *t, const char *c, const char *p) { - dprint(__FUNCTION__, s, t, c, p); - return impl->add_rule(s, t, c, p, AVTAB_ALLOWED, true); -} - -bool sepolicy::auditallow(const char *s, const char *t, const char *c, const char *p) { - dprint(__FUNCTION__, s, t, c, p); - return impl->add_rule(s, t, c, p, AVTAB_AUDITALLOW, false); -} - -bool sepolicy::dontaudit(const char *s, const char *t, const char *c, const char *p) { - dprint(__FUNCTION__, s, t, c, p); - return impl->add_rule(s, t, c, p, AVTAB_AUDITDENY, true); -} - -bool sepolicy::allowxperm(const char *s, const char *t, const char *c, const argument &xperm) { - dprint(__FUNCTION__, s, t, c, "ioctl", xperm); - return impl->add_xperm_rule(s, t, c, xperm, AVTAB_XPERMS_ALLOWED); -} - -bool sepolicy::auditallowxperm(const char *s, const char *t, const char *c, const argument &xperm) { - dprint(__FUNCTION__, s, t, c, "ioctl", xperm); - return impl->add_xperm_rule(s, t, c, xperm, AVTAB_XPERMS_AUDITALLOW); -} - -bool sepolicy::dontauditxperm(const char *s, const char *t, const char *c, const argument &xperm) { - dprint(__FUNCTION__, s, t, c, "ioctl", xperm); - return impl->add_xperm_rule(s, t, c, xperm, AVTAB_XPERMS_DONTAUDIT); -} - -bool sepolicy::type_change(const char *s, const char *t, const char *c, const char *d) { - dprint(__FUNCTION__, s, t, c, d); - return impl->add_type_rule(s, t, c, d, AVTAB_CHANGE); -} - -bool sepolicy::type_member(const char *s, const char *t, const char *c, const char *d) { - dprint(__FUNCTION__, s, t, c, d); - return impl->add_type_rule(s, t, c, d, AVTAB_MEMBER); -} - -bool sepolicy::type_transition(const char *s, const char *t, const char *c, const char *d, const char *o) { - if (o) { - dprint(__FUNCTION__, s, t, c, d, o); - return impl->add_filename_trans(s, t, c, d, o); +#if MAGISK_DEBUG +template +auto as_str(Arg arg) { + if constexpr (std::is_same_v || std::is_same_v) { + return arg == nullptr ? std::string("*") : std::string(arg); } else { - dprint(__FUNCTION__, s, t, c, d); - return impl->add_type_rule(s, t, c, d, AVTAB_TRANSITION); + return std::to_string(arg); } } -bool sepolicy::permissive(const char *s) { - dprint(__FUNCTION__, s); - return impl->set_type_state(s, true); -} - -bool sepolicy::enforce(const char *s) { - dprint(__FUNCTION__, s); - return impl->set_type_state(s, false); -} - -bool sepolicy::type(const char *name, const char *attr) { - dprint(__FUNCTION__, name, attr); - return impl->add_type(name, TYPE_TYPE) && impl->add_typeattribute(name, attr); -} - -bool sepolicy::attribute(const char *name) { - dprint(__FUNCTION__, name); - return impl->add_type(name, TYPE_ATTRIB); -} - -bool sepolicy::typeattribute(const char *type, const char *attr) { - dprint(__FUNCTION__, type, attr); - return impl->add_typeattribute(type, attr); -} - -bool sepolicy::genfscon(const char *fs_name, const char *path, const char *ctx) { - dprint(__FUNCTION__, fs_name, path, ctx); - return impl->add_genfscon(fs_name, path, ctx); +// Print out all rules going through public API for debugging +template +static void dprint(const char *action, Args ...args) { + std::string s; + s = (... + (" " + as_str(args))); + LOGD("%s%s", action, s.data()); } +#else +#define dprint(...) ((void) 0) +#endif bool sepolicy::exists(const char *type) { return hashtab_search(impl->db->p_types.table, type) != nullptr; @@ -108,6 +31,177 @@ void sepolicy::load_rule_file(const char *file) { rust::load_rule_file(*this, file); } +void sepolicy::parse_statement(const char *data) { + rust::parse_statement(*this, data); +} + +void sepolicy::magisk_rules() { + rust::magisk_rules(*this); +} + void sepolicy::load_rules(const std::string &rules) { rust::load_rules(*this, byte_view(rules, false)); } + +template +requires(std::invocable) +inline void expand(F &&f, T &&...args) { + f(std::forward(args)...); +} + +template +inline void expand(const rust::Vec &vec, T &&...args) { + for (auto i = vec.begin(); i != vec.end() || vec.empty(); ++i) { + expand(std::forward(args)..., vec.empty() ? nullptr : std::string(*i).data()); + if (vec.empty()) break; + } +} + +template +inline void expand(const rust::Vec &vec, T &&...args) { + for (auto &p : vec) { + expand(std::forward(args)..., p.low, p.high, p.reset); + } +} + +template +inline void expand(const rust::Str &s, T &&...args) { + expand(std::forward(args)..., std::string(s).data()); +} + +void +sepolicy::allow(rust::Vec src, rust::Vec tgt, rust::Vec cls, + rust::Vec perm) { + expand(src, tgt, cls, perm, [this](auto ...args) { + dprint("allow", args...); + impl->add_rule(args..., AVTAB_ALLOWED, false); + }); +} + +void +sepolicy::deny(rust::Vec src, rust::Vec tgt, rust::Vec cls, + rust::Vec perm) { + expand(src, tgt, cls, perm, [this](auto ...args) { + dprint("deny", args...); + impl->add_rule(args..., AVTAB_ALLOWED, true); + }); +} + +void sepolicy::auditallow(rust::Vec src, rust::Vec tgt, + rust::Vec cls, + rust::Vec perm) { + expand(src, tgt, cls, perm, [this](auto ...args) { + dprint("auditallow", args...); + impl->add_rule(args..., AVTAB_AUDITALLOW, false); + }); +} + +void sepolicy::dontaudit(rust::Vec src, rust::Vec tgt, + rust::Vec cls, + rust::Vec perm) { + expand(src, tgt, cls, perm, [this](auto ...args) { + dprint("dontaudit", args...); + impl->add_rule(args..., AVTAB_AUDITDENY, true); + }); +} + +void sepolicy::permissive(rust::Vec types) { + expand(types, [this](auto ...args) { + dprint("permissive", args...); + impl->set_type_state(args..., true); + }); +} + +void sepolicy::enforce(rust::Vec types) { + expand(types, [this](auto ...args) { + dprint("enforce", args...); + impl->set_type_state(args..., false); + }); +} + +void sepolicy::typeattribute(rust::Vec types, rust::Vec attrs) { + expand(types, attrs, [this](auto ...args) { + dprint("typeattribute", args...); + impl->add_typeattribute(args...); + }); +} + +void sepolicy::type(rust::Str type, rust::Vec attrs) { + expand(type, attrs, [this](auto name, auto attr) { + dprint("type", name, attr); + impl->add_type(name, TYPE_TYPE) && impl->add_typeattribute(name, attr); + }); +} + +void sepolicy::attribute(rust::Str name) { + expand(name, [this](auto ...args) { + dprint("name", args...); + impl->add_type(args..., TYPE_ATTRIB); + }); +} + +void sepolicy::type_transition(rust::Str src, rust::Str tgt, rust::Str cls, rust::Str def, + rust::Vec obj) { + auto obj_str = obj.empty() ? std::string() : std::string(obj[0]); + auto o = obj.empty() ? nullptr : obj_str.data(); + expand(src, tgt, cls, def, [this, &o](auto ...args) { + dprint("type_transition", args..., o); + if (o) { + impl->add_filename_trans(args..., o); + } else { + impl->add_type_rule(args..., AVTAB_TRANSITION); + } + }); +} + +void sepolicy::type_change(rust::Str src, rust::Str tgt, rust::Str cls, rust::Str def) { + expand(src, tgt, cls, def, [this](auto ...args) { + dprint("type_change", args...); + impl->add_type_rule(args..., AVTAB_CHANGE); + }); +} + +void sepolicy::type_member(rust::Str src, rust::Str tgt, rust::Str cls, rust::Str def) { + expand(src, tgt, cls, def, [this](auto ...args) { + dprint("type_member", args...); + impl->add_type_rule(args..., AVTAB_MEMBER); + }); +} + +void sepolicy::genfscon(rust::Str fs_name, rust::Str path, rust::Str ctx) { + expand(fs_name, path, ctx, [this](auto ...args) { + dprint("genfscon", args...); + impl->add_genfscon(args...); + }); +} + +void +sepolicy::allowxperm(rust::Vec src, rust::Vec tgt, rust::Vec cls, + rust::Vec xperm) { + expand(src, tgt, cls, xperm, [this](auto ...args) { + dprint("allowxperm", args...); + impl->add_xperm_rule(args..., AVTAB_XPERMS_ALLOWED); + }); +} + +void sepolicy::auditallowxperm(rust::Vec src, rust::Vec tgt, + rust::Vec cls, + rust::Vec xperm) { + expand(src, tgt, cls, xperm, [this](auto ...args) { + dprint("auditallowxperm", args...); + impl->add_xperm_rule(args..., AVTAB_XPERMS_AUDITALLOW); + }); +} + +void sepolicy::dontauditxperm(rust::Vec src, rust::Vec tgt, + rust::Vec cls, + rust::Vec xperm) { + expand(src, tgt, cls, xperm, [this](auto ...args) { + dprint("dontauditxperm", args...); + impl->add_xperm_rule(args..., AVTAB_XPERMS_DONTAUDIT); + }); +} + +void sepolicy::strip_dontaudit() { + impl->strip_dontaudit(); +} diff --git a/native/src/sepolicy/include/sepolicy.hpp b/native/src/sepolicy/include/sepolicy.hpp index 69d6f048f..75d955510 100644 --- a/native/src/sepolicy/include/sepolicy.hpp +++ b/native/src/sepolicy/include/sepolicy.hpp @@ -24,6 +24,8 @@ using token_list = std::vector; using argument = std::pair; using argument_list = std::vector; +struct Xperm; + #define ALL nullptr struct sepolicy { @@ -34,46 +36,44 @@ struct sepolicy { static sepolicy *from_file(c_str file); static sepolicy *from_split(); static sepolicy *compile_split(); - // External APIs bool to_file(c_str file); - void parse_statement(rust::Str stmt); void load_rules(const std::string &rules); void load_rule_file(c_str file); void print_rules(); + void parse_statement(c_str statement); // Operation on types - bool type(c_str name, c_str attr); - bool attribute(c_str name); - bool permissive(c_str type); - bool enforce(c_str type); - bool typeattribute(c_str type, c_str attr); + void type(rust::Str type, rust::Vec attrs); + void attribute(rust::Str names); + void permissive(rust::Vec types); + void enforce(rust::Vec types); + void typeattribute(rust::Vec types, rust::Vec attrs); bool exists(c_str type); // Access vector rules - bool allow(c_str src, c_str tgt, c_str cls, c_str perm); - bool deny(c_str src, c_str tgt, c_str cls, c_str perm); - bool auditallow(c_str src, c_str tgt, c_str cls, c_str perm); - bool dontaudit(c_str src, c_str tgt, c_str cls, c_str perm); + void allow(rust::Vec src, rust::Vec tgt, rust::Vec cls, rust::Vec perm); + void deny(rust::Vec src, rust::Vec tgt, rust::Vec cls, rust::Vec perm); + void auditallow(rust::Vec src, rust::Vec tgt, rust::Vec cls, rust::Vec perm); + void dontaudit(rust::Vec src, rust::Vec tgt, rust::Vec cls, rust::Vec perm); // Extended permissions access vector rules - bool allowxperm(c_str src, c_str tgt, c_str cls, const argument &xperm); - bool auditallowxperm(c_str src, c_str tgt, c_str cls, const argument &xperm); - bool dontauditxperm(c_str src, c_str tgt, c_str cls, const argument &xperm); + void allowxperm(rust::Vec src, rust::Vec tgt, rust::Vec cls, rust::Vec xperm); + void auditallowxperm(rust::Vec src, rust::Vec tgt, rust::Vec cls, rust::Vec xperm); + void dontauditxperm(rust::Vec src, rust::Vec tgt, rust::Vec cls, rust::Vec xperm); // Type rules - bool type_transition(c_str src, c_str tgt, c_str cls, c_str def, c_str obj = nullptr); - bool type_change(c_str src, c_str tgt, c_str cls, c_str def); - bool type_member(c_str src, c_str tgt, c_str cls, c_str def); + void type_transition(rust::Str src, rust::Str tgt, rust::Str cls, rust::Str def, rust::Vec obj); + void type_change(rust::Str src, rust::Str tgt, rust::Str cls, rust::Str def); + void type_member(rust::Str src, rust::Str tgt, rust::Str cls, rust::Str def); // File system labeling - bool genfscon(c_str fs_name, c_str path, c_str ctx); + void genfscon(rust::Str fs_name, rust::Str path, rust::Str ctx); // Magisk void magisk_rules(); - // Deprecate - bool create(c_str name) { return type(name, "domain"); } + void strip_dontaudit(); protected: // Prevent anyone from accidentally creating an instance diff --git a/native/src/sepolicy/lib.rs b/native/src/sepolicy/lib.rs index 524c47b68..be7e8197f 100644 --- a/native/src/sepolicy/lib.rs +++ b/native/src/sepolicy/lib.rs @@ -1,3 +1,5 @@ +#![feature(format_args_nl)] + use io::Cursor; use std::io; use std::io::{BufRead, BufReader}; @@ -5,12 +7,22 @@ use std::pin::Pin; pub use base; use base::libc::{O_CLOEXEC, O_RDONLY}; -use base::{BufReadExt, FsPath, LoggedResult, Utf8CStr}; +use base::{error, BufReadExt, FsPath, LoggedResult, Utf8CStr}; use crate::ffi::sepolicy; +mod statement; + +mod rules; + #[cxx::bridge] mod ffi { + struct Xperm { + low: u16, + high: u16, + reset: bool, + } + unsafe extern "C++" { #[namespace = "rust"] #[cxx_name = "Utf8CStr"] @@ -19,13 +31,69 @@ mod ffi { include!("include/sepolicy.hpp"); type sepolicy; - fn parse_statement(self: Pin<&mut sepolicy>, stmt: &str); + fn allow(self: Pin<&mut sepolicy>, s: Vec<&str>, t: Vec<&str>, c: Vec<&str>, p: Vec<&str>); + fn deny(self: Pin<&mut sepolicy>, s: Vec<&str>, t: Vec<&str>, c: Vec<&str>, p: Vec<&str>); + fn auditallow( + self: Pin<&mut sepolicy>, + s: Vec<&str>, + t: Vec<&str>, + c: Vec<&str>, + p: Vec<&str>, + ); + fn dontaudit( + self: Pin<&mut sepolicy>, + s: Vec<&str>, + t: Vec<&str>, + c: Vec<&str>, + p: Vec<&str>, + ); + fn allowxperm( + self: Pin<&mut sepolicy>, + s: Vec<&str>, + t: Vec<&str>, + c: Vec<&str>, + p: Vec, + ); + fn auditallowxperm( + self: Pin<&mut sepolicy>, + s: Vec<&str>, + t: Vec<&str>, + c: Vec<&str>, + p: Vec, + ); + fn dontauditxperm( + self: Pin<&mut sepolicy>, + s: Vec<&str>, + t: Vec<&str>, + c: Vec<&str>, + p: Vec, + ); + fn permissive(self: Pin<&mut sepolicy>, t: Vec<&str>); + fn enforce(self: Pin<&mut sepolicy>, t: Vec<&str>); + fn typeattribute(self: Pin<&mut sepolicy>, t: Vec<&str>, a: Vec<&str>); + #[cxx_name = "type"] + fn type_(self: Pin<&mut sepolicy>, t: &str, a: Vec<&str>); + fn attribute(self: Pin<&mut sepolicy>, t: &str); + fn type_transition( + self: Pin<&mut sepolicy>, + s: &str, + t: &str, + c: &str, + d: &str, + o: Vec<&str>, + ); + fn type_change(self: Pin<&mut sepolicy>, s: &str, t: &str, c: &str, d: &str); + fn type_member(self: Pin<&mut sepolicy>, s: &str, t: &str, c: &str, d: &str); + fn genfscon(self: Pin<&mut sepolicy>, s: &str, t: &str, c: &str); + fn strip_dontaudit(self: Pin<&mut sepolicy>); } #[namespace = "rust"] extern "Rust" { fn load_rules(sepol: Pin<&mut sepolicy>, rules: &[u8]); fn load_rule_file(sepol: Pin<&mut sepolicy>, filename: Utf8CStrRef); + fn parse_statement(sepol: Pin<&mut sepolicy>, statement: Utf8CStrRef); + fn magisk_rules(sepol: Pin<&mut sepolicy>); } } @@ -33,6 +101,11 @@ trait SepolicyExt { fn load_rules(self: Pin<&mut Self>, rules: &[u8]); fn load_rule_file(self: Pin<&mut Self>, filename: &Utf8CStr); fn load_rules_from_reader(self: Pin<&mut Self>, reader: &mut T); + fn parse_statement(self: Pin<&mut Self>, statement: &str); +} + +trait SepolicyMagisk { + fn magisk_rules(self: Pin<&mut Self>); } impl SepolicyExt for sepolicy { @@ -54,10 +127,19 @@ impl SepolicyExt for sepolicy { fn load_rules_from_reader(mut self: Pin<&mut sepolicy>, reader: &mut T) { reader.foreach_lines(|line| { let line = line.trim(); + if line.is_empty() { + return true; + } self.as_mut().parse_statement(line); true }); } + + fn parse_statement(self: Pin<&mut Self>, statement: &str) { + if let Err(_) = statement::parse_statement(self, statement) { + error!("sepolicy rule syntax error: {statement}"); + } + } } pub fn load_rule_file(sepol: Pin<&mut sepolicy>, filename: &Utf8CStr) { @@ -67,3 +149,11 @@ pub fn load_rule_file(sepol: Pin<&mut sepolicy>, filename: &Utf8CStr) { pub fn load_rules(sepol: Pin<&mut sepolicy>, rules: &[u8]) { sepol.load_rules(rules); } + +pub fn parse_statement(sepol: Pin<&mut sepolicy>, statement: &Utf8CStr) { + sepol.parse_statement(statement.as_str()); +} + +pub fn magisk_rules(sepol: Pin<&mut sepolicy>) { + sepol.magisk_rules(); +} diff --git a/native/src/sepolicy/policy.hpp b/native/src/sepolicy/policy.hpp index 84a8b1d25..b97e0bb4e 100644 --- a/native/src/sepolicy/policy.hpp +++ b/native/src/sepolicy/policy.hpp @@ -20,8 +20,8 @@ struct sepol_impl : public sepolicy { bool add_rule(const char *s, const char *t, const char *c, const char *p, int effect, bool invert); void add_rule(type_datum_t *src, type_datum_t *tgt, class_datum_t *cls, perm_datum_t *perm, int effect, bool invert); - void add_xperm_rule(type_datum_t *src, type_datum_t *tgt, class_datum_t *cls, const argument &xperm, int effect); - bool add_xperm_rule(const char *s, const char *t, const char *c, const argument &xperm, int effect); + void add_xperm_rule(type_datum_t *src, type_datum_t *tgt, class_datum_t *cls, uint16_t low, uint16_t high, bool reset, int effect); + bool add_xperm_rule(const char *s, const char *t, const char *c, uint16_t low, uint16_t high, bool reset, int effect); bool add_type_rule(const char *s, const char *t, const char *c, const char *d, int effect); bool add_filename_trans(const char *s, const char *t, const char *c, const char *d, const char *o); bool add_genfscon(const char *fs_name, const char *path, const char *context); diff --git a/native/src/sepolicy/rules.cpp b/native/src/sepolicy/rules.cpp deleted file mode 100644 index 68031f0fd..000000000 --- a/native/src/sepolicy/rules.cpp +++ /dev/null @@ -1,136 +0,0 @@ -#include -#include - -#include "policy.hpp" - -using namespace std; - -void sepolicy::magisk_rules() { - // Temp suppress warnings - set_log_level_state(LogLevel::Warn, false); - - // Prevent anything to change sepolicy except ourselves - deny(ALL, "kernel", "security", "load_policy"); - - type(SEPOL_PROC_DOMAIN, "domain"); - permissive(SEPOL_PROC_DOMAIN); /* Just in case something is missing */ - typeattribute(SEPOL_PROC_DOMAIN, "mlstrustedsubject"); - typeattribute(SEPOL_PROC_DOMAIN, "netdomain"); - typeattribute(SEPOL_PROC_DOMAIN, "bluetoothdomain"); - type(SEPOL_FILE_TYPE, "file_type"); - typeattribute(SEPOL_FILE_TYPE, "mlstrustedobject"); - type(SEPOL_LOG_TYPE, "file_type"); - typeattribute(SEPOL_LOG_TYPE, "mlstrustedobject"); - - // Make our root domain unconstrained - allow(SEPOL_PROC_DOMAIN, ALL, ALL, ALL); - // Allow us to do any ioctl - if (impl->db->policyvers >= POLICYDB_VERSION_XPERMS_IOCTL) { - argument all; - all.first.push_back(nullptr); - allowxperm(SEPOL_PROC_DOMAIN, ALL, "blk_file", all); - allowxperm(SEPOL_PROC_DOMAIN, ALL, "fifo_file", all); - allowxperm(SEPOL_PROC_DOMAIN, ALL, "chr_file", all); - } - - // Create unconstrained file type - allow(ALL, SEPOL_FILE_TYPE, "file", ALL); - allow(ALL, SEPOL_FILE_TYPE, "dir", ALL); - allow(ALL, SEPOL_FILE_TYPE, "fifo_file", ALL); - allow(ALL, SEPOL_FILE_TYPE, "chr_file", ALL); - allow(ALL, SEPOL_FILE_TYPE, "lnk_file", ALL); - allow(ALL, SEPOL_FILE_TYPE, "sock_file", ALL); - - // Only allow zygote to open log pipe - allow("zygote", SEPOL_LOG_TYPE, "fifo_file", "open"); - allow("zygote", SEPOL_LOG_TYPE, "fifo_file", "read"); - // Allow all processes to output logs - allow("domain", SEPOL_LOG_TYPE, "fifo_file", "write"); - - // Allow these processes to access MagiskSU and output logs - const char *clients[] { - "zygote", "shell", "system_app", "platform_app", - "priv_app", "untrusted_app", "untrusted_app_all" - }; - for (auto type: clients) { - if (!exists(type)) - continue; - allow(type, SEPOL_PROC_DOMAIN, "unix_stream_socket", "connectto"); - allow(type, SEPOL_PROC_DOMAIN, "unix_stream_socket", "getopt"); - } - - // Let everyone access tmpfs files (for SAR sbin overlay) - allow(ALL, "tmpfs", "file", ALL); - - // Allow magiskinit daemon to handle mock selinuxfs - allow("kernel", "tmpfs", "fifo_file", "write"); - - // For relabelling files - allow("rootfs", "labeledfs", "filesystem", "associate"); - allow(SEPOL_FILE_TYPE, "pipefs", "filesystem", "associate"); - allow(SEPOL_FILE_TYPE, "devpts", "filesystem", "associate"); - - // Let init transit to SEPOL_PROC_DOMAIN - allow("kernel", "kernel", "process", "setcurrent"); - allow("kernel", SEPOL_PROC_DOMAIN, "process", "dyntransition"); - - // Let init run stuffs - allow("kernel", SEPOL_PROC_DOMAIN, "fd", "use"); - allow("init", SEPOL_PROC_DOMAIN, "process", ALL); - - // suRights - allow("servicemanager", SEPOL_PROC_DOMAIN, "dir", "search"); - allow("servicemanager", SEPOL_PROC_DOMAIN, "dir", "read"); - allow("servicemanager", SEPOL_PROC_DOMAIN, "file", "open"); - allow("servicemanager", SEPOL_PROC_DOMAIN, "file", "read"); - allow("servicemanager", SEPOL_PROC_DOMAIN, "process", "getattr"); - allow(ALL, SEPOL_PROC_DOMAIN, "process", "sigchld"); - - // allowLog - allow("logd", SEPOL_PROC_DOMAIN, "dir", "search"); - allow("logd", SEPOL_PROC_DOMAIN, "file", "read"); - allow("logd", SEPOL_PROC_DOMAIN, "file", "open"); - allow("logd", SEPOL_PROC_DOMAIN, "file", "getattr"); - - // dumpsys - allow(ALL, SEPOL_PROC_DOMAIN, "fd", "use"); - allow(ALL, SEPOL_PROC_DOMAIN, "fifo_file", "write"); - allow(ALL, SEPOL_PROC_DOMAIN, "fifo_file", "read"); - allow(ALL, SEPOL_PROC_DOMAIN, "fifo_file", "open"); - allow(ALL, SEPOL_PROC_DOMAIN, "fifo_file", "getattr"); - - // bootctl - allow("hwservicemanager", SEPOL_PROC_DOMAIN, "dir", "search"); - allow("hwservicemanager", SEPOL_PROC_DOMAIN, "file", "read"); - allow("hwservicemanager", SEPOL_PROC_DOMAIN, "file", "open"); - allow("hwservicemanager", SEPOL_PROC_DOMAIN, "process", "getattr"); - - // For mounting loop devices, mirrors, tmpfs - allow("kernel", ALL, "file", "read"); - allow("kernel", ALL, "file", "write"); - - // Allow all binder transactions - allow(ALL, SEPOL_PROC_DOMAIN, "binder", ALL); - - // For changing file context - allow("rootfs", "tmpfs", "filesystem", "associate"); - - // Zygisk rules - allow("zygote", "zygote", "process", "execmem"); - allow("zygote", "fs_type", "filesystem", "unmount"); - allow("system_server", "system_server", "process", "execmem"); - - // Shut llkd up - dontaudit("llkd", SEPOL_PROC_DOMAIN, "process", "ptrace"); - - // Keep /data/adb/* context - deny("init", "adb_data_file", "dir", "search"); - deny("vendor_init", "adb_data_file", "dir", "search"); - -#if 0 - // Remove all dontaudit in debug mode - impl->strip_dontaudit(); -#endif - - set_log_level_state(LogLevel::Warn, true); -} diff --git a/native/src/sepolicy/rules.rs b/native/src/sepolicy/rules.rs new file mode 100644 index 000000000..ac06af8a6 --- /dev/null +++ b/native/src/sepolicy/rules.rs @@ -0,0 +1,145 @@ +use crate::{ffi::Xperm, sepolicy, SepolicyMagisk}; +use base::{set_log_level_state, LogLevel}; +use std::pin::Pin; + +macro_rules! rules { + (@args all) => { + vec![] + }; + (@args xall) => { + vec![Xperm { low: 0x0000, high: 0xFFFF, reset: false }] + }; + (@args [proc]) => { + vec!["magisk"] + }; + (@args [file]) => { + vec!["magisk_file"] + }; + (@args [log]) => { + vec!["magisk_log_file"] + }; + (@args proc) => { + "magisk" + }; + (@args file) => { + "magisk_file" + }; + (@args log) => { + "magisk_log_file" + }; + (@args [$($arg:tt)*]) => { + vec![$($arg)*] + }; + (@args $arg:expr) => { + $arg + }; + (@stmt $self:ident) => {}; + (@stmt $self:ident $action:ident($($args:tt),*); $($res:tt)*) => { + $self.as_mut().$action($(rules!(@args $args)),*); + rules!{@stmt $self $($res)* } + }; + (use $self:ident; $($res:tt)*) => {{ + rules!{@stmt $self $($res)* } + }}; +} + +impl SepolicyMagisk for sepolicy { + fn magisk_rules(mut self: Pin<&mut Self>) { + // Temp suppress warnings + set_log_level_state(LogLevel::Warn, false); + rules! { + use self; + allow(all, ["kernel"], ["security"], ["load_policy"]); + type_(proc, ["domain"]); + permissive([proc]); + typeattribute([proc], ["mlstrustedsubject", "netdomain", "bluetoothdomain"]); + type_(file, ["file_type"]); + typeattribute([file], ["mlstrustedobject"]); + type_(log, ["file_type"]); + typeattribute([log], ["mlstrustedobject"]); + + // Make our root domain unconstrained + allow([proc], all, all, all); + + // Allow us to do any ioctl + allowxperm(["magisk"], all, ["blk_file", "fifo_file", "chr_file"], xall); + + // Create unconstrained file type + allow(all, [file], ["file", "dir", "fifo_file", "chr_file", "lnk_file", "sock_file"], all); + + // Only allow zygote to open log pipe + allow(["zygote"], [log], ["fifo_file"], ["open", "read"]); + + // Allow all processes to output logs + + allow(["domain"], [log], ["fifo_file"], ["write"]); + + // Allow these processes to access MagiskSU and output logs + allow(["zygote", "shell", "system_app", "priv_app", "untrusted_app", "untrusted_app_all"], + [proc], ["unix_stream_socket"], ["connectto", "getopt"]); + + // Let everyone access tmpfs files (for SAR sbin overlay) + allow(all, ["tmpfs"], ["file"], all); + + // Allow magiskinit daemon to handle mock selinuxfs + allow(["kernel"], ["tmpfs"], ["fifo_file"], ["write"]); + + // For relabelling files + allow(["rootfs"], ["labeledfs"], ["filesystem"], ["associate"]); + allow([file], ["pipefs", "devpts"], ["filesystem"], ["associate"]); + + // Let init transit to SEPOL_PROC_DOMAIN + allow(["kernel"], ["kernel"], ["process"], ["setcurrent"]); + allow(["kernel"], [proc], ["process"], ["dyntransition"]); + + // Let init run stuffs + allow(["kernel"], [proc], ["fd"], ["use"]); + allow(["init"], [proc], ["process"], all); + + // suRights + allow(["servicemanager"], [proc], ["dir"], ["search", "read"]); + allow(["servicemanager"], [proc], ["file"], ["open", "read"]); + allow(["servicemanager"], [proc], ["process"], ["getattr"]); + allow(all, [proc], ["process"], ["sigchld"]); + + // allowLog + allow(["logd"], [proc], ["dir"], ["search"]); + allow(["logd"], [proc], ["file"], ["read", "open", "getattr"]); + + // dumpsys + allow(all, [proc], ["fd"], ["use"]); + allow(all, [proc], ["fifo_file"], ["write", "read", "open", "getattr"]); + + // bootctl + allow(["hwservicemanager"], [proc], ["dir"], ["search"]); + allow(["hwservicemanager"], [proc], ["file"], ["read", "open"]); + allow(["hwservicemanager"], [proc], ["process"], ["getattr"]); + + // For mounting loop devices, mirrors, tmpfs + allow(["kernel"], all, ["file"], ["read", "write"]); + + // Allow all binder transactions + allow(all, [proc], ["binder"], all); + + // For changing file context + allow(["rootfs"], ["tmpfs"], ["filesystem"], ["associate"]); + + // Zygisk rules + allow(["zygote"], ["zygote"], ["process"], ["execmem"]); + allow(["zygote"], ["fs_type"], ["filesystem"], ["unmount"]); + allow(["system_server"], ["system_server"], ["process"], ["execmem"]); + + // Shut llkd up + dontaudit(["llkd"], [proc], ["process"], ["ptrace"]); + + // Keep /data/adb/* context + deny(["init"], ["adb_data_file"], ["dir"], ["search"]); + deny(["vendor_init"], ["adb_data_file"], ["dir"], ["search"]); + } + // Remove all dontaudit in debug mode + #[cfg(debug_assertions)] + self.as_mut().strip_dontaudit(); + + set_log_level_state(LogLevel::Warn, true); + } +} diff --git a/native/src/sepolicy/sepolicy.cpp b/native/src/sepolicy/sepolicy.cpp index a33b117bd..89e161b25 100644 --- a/native/src/sepolicy/sepolicy.cpp +++ b/native/src/sepolicy/sepolicy.cpp @@ -270,18 +270,18 @@ bool sepol_impl::add_rule(const char *s, const char *t, const char *c, const cha #define ioctl_driver(x) (x>>8 & 0xFF) #define ioctl_func(x) (x & 0xFF) -void sepol_impl::add_xperm_rule(type_datum_t *src, type_datum_t *tgt, class_datum_t *cls, const argument &xperm, int effect) { +void sepol_impl::add_xperm_rule(type_datum_t *src, type_datum_t *tgt, class_datum_t *cls, uint16_t low, uint16_t high, bool reset, int effect) { if (src == nullptr) { for_each_attr(db->p_types.table, [&](type_datum_t *type) { - add_xperm_rule(type, tgt, cls, xperm, effect); + add_xperm_rule(type, tgt, cls, low, high, reset, effect); }); } else if (tgt == nullptr) { for_each_attr(db->p_types.table, [&](type_datum_t *type) { - add_xperm_rule(src, type, cls, xperm, effect); + add_xperm_rule(src, type, cls, low, high, reset, effect); }); } else if (cls == nullptr) { hashtab_for_each(db->p_classes.table, [&](hashtab_ptr_t node) { - add_xperm_rule(src, tgt, auto_cast(node->datum), xperm, effect); + add_xperm_rule(src, tgt, auto_cast(node->datum), low, high, reset, effect); }); } else { avtab_key_t key; @@ -304,35 +304,6 @@ void sepol_impl::add_xperm_rule(type_datum_t *src, type_datum_t *tgt, class_datu node = avtab_search_node_next(node, key.specified); } - bool reset = xperm.second; - vector> ranges; - - for (const char *tok : xperm.first) { - uint16_t low = 0; - uint16_t high = 0; - if (tok == nullptr) { - low = 0x0000; - high = 0xFF00; - reset = true; - } else if (strchr(tok, '-')) { - if (sscanf(tok, "%hx-%hx", &low, &high) != 2) { - // Invalid token, skip - continue; - } - } else { - if (sscanf(tok, "%hx", &low) != 1) { - // Invalid token, skip - continue; - } - high = low; - } - if (high == 0) { - reset = true; - } else { - ranges.emplace_back(make_pair(low, high)); - } - } - if (reset) { for (int i = 0; i <= 0xFF; ++i) { if (node_list[i]) { @@ -361,25 +332,23 @@ void sepol_impl::add_xperm_rule(type_datum_t *src, type_datum_t *tgt, class_datu return node; }; - if (!xperm.second) { - for (auto [low, high] : ranges) { - if (ioctl_driver(low) != ioctl_driver(high)) { - if (driver_node == nullptr) { - driver_node = new_driver_node(); - } - for (int i = ioctl_driver(low); i <= ioctl_driver(high); ++i) { - xperm_set(i, driver_node->datum.xperms->perms); - } - } else { - uint8_t driver = ioctl_driver(low); - auto node = node_list[driver]; - if (node == nullptr) { - node = new_func_node(driver); - node_list[driver] = node; - } - for (int i = ioctl_func(low); i <= ioctl_func(high); ++i) { - xperm_set(i, node->datum.xperms->perms); - } + if (!reset) { + if (ioctl_driver(low) != ioctl_driver(high)) { + if (driver_node == nullptr) { + driver_node = new_driver_node(); + } + for (int i = ioctl_driver(low); i <= ioctl_driver(high); ++i) { + xperm_set(i, driver_node->datum.xperms->perms); + } + } else { + uint8_t driver = ioctl_driver(low); + auto node = node_list[driver]; + if (node == nullptr) { + node = new_func_node(driver); + node_list[driver] = node; + } + for (int i = ioctl_func(low); i <= ioctl_func(high); ++i) { + xperm_set(i, node->datum.xperms->perms); } } } else { @@ -389,31 +358,29 @@ void sepol_impl::add_xperm_rule(type_datum_t *src, type_datum_t *tgt, class_datu // Fill the driver perms memset(driver_node->datum.xperms->perms, ~0, sizeof(avtab_extended_perms_t::perms)); - for (auto [low, high] : ranges) { - if (ioctl_driver(low) != ioctl_driver(high)) { - for (int i = ioctl_driver(low); i <= ioctl_driver(high); ++i) { - xperm_clear(i, driver_node->datum.xperms->perms); - } - } else { - uint8_t driver = ioctl_driver(low); - auto node = node_list[driver]; - if (node == nullptr) { - node = new_func_node(driver); - // Fill the func perms - memset(node->datum.xperms->perms, ~0, sizeof(avtab_extended_perms_t::perms)); - node_list[driver] = node; - } - xperm_clear(driver, driver_node->datum.xperms->perms); - for (int i = ioctl_func(low); i <= ioctl_func(high); ++i) { - xperm_clear(i, node->datum.xperms->perms); - } + if (ioctl_driver(low) != ioctl_driver(high)) { + for (int i = ioctl_driver(low); i <= ioctl_driver(high); ++i) { + xperm_clear(i, driver_node->datum.xperms->perms); + } + } else { + uint8_t driver = ioctl_driver(low); + auto node = node_list[driver]; + if (node == nullptr) { + node = new_func_node(driver); + // Fill the func perms + memset(node->datum.xperms->perms, ~0, sizeof(avtab_extended_perms_t::perms)); + node_list[driver] = node; + } + xperm_clear(driver, driver_node->datum.xperms->perms); + for (int i = ioctl_func(low); i <= ioctl_func(high); ++i) { + xperm_clear(i, node->datum.xperms->perms); } } } } } -bool sepol_impl::add_xperm_rule(const char *s, const char *t, const char *c, const argument &xperm, int effect) { +bool sepol_impl::add_xperm_rule(const char *s, const char *t, const char *c, uint16_t low, uint16_t high, bool reset, int effect) { type_datum_t *src = nullptr, *tgt = nullptr; class_datum_t *cls = nullptr; @@ -441,7 +408,7 @@ bool sepol_impl::add_xperm_rule(const char *s, const char *t, const char *c, con } } - add_xperm_rule(src, tgt, cls, xperm, effect); + add_xperm_rule(src, tgt, cls, low, high, reset, effect); return true; } diff --git a/native/src/sepolicy/statement.cpp b/native/src/sepolicy/statement.cpp index ca282d3a5..c4fac2583 100644 --- a/native/src/sepolicy/statement.cpp +++ b/native/src/sepolicy/statement.cpp @@ -100,471 +100,3 @@ Supported policy statements: type_msg_5, type_msg_6, type_msg_7, type_msg_8, type_msg_9); exit(0); } - -// Return value: -// 0: success -// -1: unclosed bracket -// -2: nested brackets -// -3: double complement -static int tokenize_string(char *stmt, argument_list &args) { - char *cur = stmt; - bool complement = false; - - auto add_new_arg = [&] (char *str) { - argument arg; - for (char *tok; (tok = strtok_r(nullptr, " ", &str)) != nullptr;) { - if (tok == "*"sv) { - // If any of the tokens is "*", the result is a single nullptr - arg.first.clear(); - arg.first.push_back(nullptr); - break; - } else { - arg.first.push_back(tok); - } - } - arg.second = complement; - complement = false; - args.push_back(std::move(arg)); - }; - - char *tok = strtok_r(nullptr, " ", &cur); - while (tok) { - if (tok[0] == '~') { - if (complement) { - // Double complement is not supported - return -3; - } - complement = true; - if (tok[1] == '\0') { - // If '~' is followed by a space, find the next token - tok = strtok_r(nullptr, " ", &cur); - } else { - // Reparse the rest of the string - ++tok; - } - continue; - } - - // Find brackets - char *begin = strchr(tok, '{'); - char *end = strchr(tok, '}'); - - if (begin == nullptr && end) { - // Bracket not closed, syntax error - return -1; - } - - if (begin) { - if (end && (begin > end)) { - // Bracket not closed, syntax error - return -1; - } - - // Restore the string so we can properly find the closing bracket - if (cur) - cur[-1] = ' '; - - if (end == nullptr) { - // Find again - end = strchr(begin + 1, '}'); - if (end == nullptr) { - // Bracket not closed, syntax error - return -1; - } - } - - // Close bracket and start the next parsing iteration after that - *end = '\0'; - cur = end + 1; - - if (strchr(begin + 1, '{')) { - // We don't support nested brackets - return -2; - } - - if (begin != tok) { - // There is an argument before the opening bracket - *begin = '\0'; - add_new_arg(tok); - } - - // Parse the arguments enclosed in the brackets - add_new_arg(begin + 1); - } else { - add_new_arg(tok); - } - - tok = strtok_r(nullptr, " ", &cur); - } - return 0; -} - -// Check all args listed have size = 1 (no multiple entries) -template -static bool enforce_single(const argument_list &args) { - initializer_list list{indices...}; - for (int i : list) - if (args[i].first.size() != 1) - return false; - return true; -} - -// Check all args listed are not null (no match all operator) -template -static bool enforce_non_null(const argument_list &args) { - initializer_list list{indices...}; - for (int i : list) - if (args[i].first.size() == 1 && args[i].first[0] == nullptr) - return false; - return true; -} - -template -static bool enforce_size(const argument_list &args) { - return args.size() == size; -} - -// Check all args are not complements except those listed -template -static bool check_complements(const argument_list &args) { - initializer_list list{except...}; - for (int i = 0; i < args.size(); ++i) { - bool disallow = true; - for (int e : list) { - if (i == e) { - disallow = false; - break; - } - } - if (disallow && args[i].second) - return false; - } - return true; -} - -// Tokenize and check argument count -template -static bool tokenize_and_check(char *stmt, argument_list &args) { - return tokenize_string(stmt, args) == 0 && - enforce_size(args) && - check_complements<>(args); -} - -#define sprint(...) off += ssprintf(buf + off, sizeof(buf) - off, __VA_ARGS__) - -const char *as_str(const argument &arg) { - size_t off = 0; - static char buf[4096]; - - if (arg.second) { - sprint("~"); - } - sprint("{ "); - for (const char *tok : arg.first) { - if (tok == nullptr) { - sprint("0x0000-0xFF00 "); - } else { - sprint("%s ", tok); - } - } - sprint("}"); - - return buf; -} - -const char *as_str(const char *arg) { return arg ?: "*"; } - -template -static void run_and_check(const Func &fn, const char *action, Args ...args) { - if (!fn(args...)) { - string s = "Error in: %s"; - for (int i = 0; i < sizeof...(args); ++i) s += " %s"; - s += "\n"; - LOGW(s.data(), action, as_str(args)...); - } -} - -#define run_fn(...) run_and_check(fn, action, __VA_ARGS__) - -// Pattern 1: allow *{ source } *{ target } *{ class } *{ permission } -template -static bool parse_pattern_1(const Func &fn, const char *action, char *stmt) { - argument_list args; - if (!tokenize_and_check<4>(stmt, args)) - return false; - for (auto src : args[0].first) - for (auto tgt : args[1].first) - for (auto cls : args[2].first) - for (auto perm : args[3].first) - run_fn(src, tgt, cls, perm); - return true; -} - -// Pattern 2: allowxperm *{ source } *{ target } *{ class } ioctl xperm_set -template -static bool parse_pattern_2(const Func &fn, const char *action, char *stmt) { - argument_list args; - if (tokenize_string(stmt, args) != 0 || - !enforce_size<5>(args) || - !check_complements<4>(args) || - !enforce_single<3>(args) || - !enforce_non_null<3>(args) || - args[3].first[0] != "ioctl"sv) - return false; - for (auto src : args[0].first) - for (auto tgt : args[1].first) - for (auto cls : args[2].first) - run_fn(src, tgt, cls, args[4]); - return true; -} - -// Pattern 3: permissive *{ type } -template -static bool parse_pattern_3(const Func &fn, const char *action, char *stmt) { - argument_list args; - if (!tokenize_and_check<1>(stmt, args)) - return false; - for (auto type : args[0].first) - run_fn(type); - return true; -} - -// Pattern 4: typeattribute { type } { attribute } -template -static bool parse_pattern_4(const Func &fn, const char *action, char *stmt) { - argument_list args; - if (!tokenize_and_check<2>(stmt, args) || !enforce_non_null<0, 1>(args)) - return false; - for (auto type : args[0].first) - for (auto attr : args[1].first) - run_fn(type, attr); - return true; -} - -// Pattern 5: type name { attribute } -template -static bool parse_pattern_5(const Func &fn, const char *action, char *stmt) { - argument_list args; - string tmp_str; - if (tokenize_string(stmt, args) != 0 || !enforce_single<0>(args)) - return false; - if (args.size() == 1) { - args.emplace_back(make_pair(initializer_list{ "domain" }, false)); - } - if (!enforce_size<2>(args) || !enforce_non_null<1>(args) || !check_complements<>(args)) - return false; - for (auto attr : args[1].first) - run_fn(args[0].first[0], attr); - return true; -} - -// Pattern 6: attribute name -template -static bool parse_pattern_6(const Func &fn, const char *action, char *stmt) { - argument_list args; - if (!tokenize_and_check<1>(stmt, args) || !enforce_single<0>(args)) - return false; - run_fn(args[0].first[0]); - return true; -} - -// Pattern 7: type_transition source target class default (filename) -template -static bool parse_pattern_7(const Func &fn, const char *action, char *stmt) { - argument_list args; - if (tokenize_string(stmt, args) != 0) - return false; - if (args.size() == 4) - args.emplace_back(make_pair(initializer_list{ nullptr }, false)); - if (!enforce_size<5>(args) || - !enforce_non_null<0, 1, 2, 3>(args) || - !enforce_single<0, 1, 2, 3, 4>(args) || - !check_complements<>(args)) - return false; - run_fn(args[0].first[0], args[1].first[0], args[2].first[0], - args[3].first[0], args[4].first[0]); - return true; -} - -// Pattern 8: type_change source target class default -template -static bool parse_pattern_8(const Func &fn, const char *action, char *stmt) { - argument_list args; - if (!tokenize_and_check<4>(stmt, args) || !enforce_single<0, 1, 2, 3>(args)) - return false; - run_fn(args[0].first[0], args[1].first[0], args[2].first[0], args[3].first[0]); - return true; -} - -// Pattern 9: genfscon name path context -template -static bool parse_pattern_9(const Func &fn, const char *action, char *stmt) { - argument_list args; - if (!tokenize_and_check<3>(stmt, args) || !enforce_single<0, 1, 2>(args)) - return false; - run_fn(args[0].first[0], args[1].first[0], args[2].first[0]); - return true; -} - -#define add_action_func(name, type, fn) \ -else if (strcmp(name, action) == 0) { \ - auto __fn = [&](auto && ...args){ return (fn)(args...); }; \ - if (!parse_pattern_##type(__fn, name, remain)) \ - LOGW("Syntax error in '%.*s'\n\n%s\n", (int) stmt.length(), stmt.data(), type_msg_##type); \ -} - -#define add_action(act, type) add_action_func(#act, type, act) - -void sepolicy::parse_statement(rust::Str stmt) { - if (stmt.empty()) return; - // strtok modify strings, create a copy - string cpy(stmt.data(), stmt.length()); - - char *remain; - char *action = strtok_r(cpy.data(), " ", &remain); - if (remain == nullptr) { - LOGW("Syntax error in '%.*s'\n\n", (int) stmt.length(), stmt.data()); - return; - } - - if (0) {} - add_action(allow, 1) - add_action(deny, 1) - add_action(auditallow, 1) - add_action(dontaudit, 1) - add_action(allowxperm, 2) - add_action(auditallowxperm, 2) - add_action(dontauditxperm, 2) - add_action(permissive, 3) - add_action(enforce, 3) - add_action(typeattribute, 4) - add_action(type, 5) - add_action(attribute, 6) - add_action(type_transition, 7) - add_action(type_change, 8) - add_action(type_member, 8) - add_action(genfscon, 9) - - // Backwards compatible syntax - add_action(create, 3) - add_action_func("attradd", 4, typeattribute) - add_action_func("name_transition", 7, type_transition) - - else { LOGW("Unknown action: '%s'\n\n", action); } -} - -// Parsing is hard, the following is a test suite to ensure correctness - -static void test_parse_stmt(const char *stmt, const char *expect, int code = 0) { - auto print_error = [](int code) { - switch (code) { - case -1: - fprintf(stderr, "unclosed bracket\n"); - break; - case -2: - fprintf(stderr, "nested brackets\n"); - break; - case -3: - fprintf(stderr, "double complement\n"); - break; - } - }; - - char cpy[4096]; - strncpy(cpy, stmt, sizeof(cpy)); - argument_list args; - int result = tokenize_string(cpy, args); - if (result != 0) { - if (expect != nullptr) { - fprintf(stderr, "Parsing: '%s' with unexpected error: ", stmt); - print_error(result); - exit(1); - } else { - if (code != result) { - fprintf(stderr, "Parsing: '%s'\n", stmt); - fprintf(stderr, "Expect error: "); - print_error(code); - fprintf(stderr, "Result error: "); - print_error(result); - exit(1); - } - return; - } - } - - char buf[4096]; - size_t off = 0; - for (const auto &arg : args) { - sprint("(%d,[", arg.second); - bool first = true; - for (const char *tok : arg.first) { - if (first) { - first = false; - } else { - sprint(","); - } - sprint("'%s'", tok ?: "(null)"); - } - sprint("])\n"); - } - if (strncmp(buf, expect, sizeof(buf)) != 0) { - fprintf(stderr, "Parsing: '%s'\n", stmt); - fprintf(stderr, "Expect:\n%s", expect); - fprintf(stderr, "-------------------\n"); - fprintf(stderr, "Result:\n%s", buf); - fprintf(stderr, "-------------------\n"); - exit(1); - } -} - -static void assert_msg(bool b, const char *msg) { - if (!b) { - fprintf(stderr, "Assertion failed: %s\n", msg); - exit(1); - } -} - -[[maybe_unused]] -void test_parse_statements() { - // Test parsing correctness - test_parse_stmt("a b c", - "(0,['a'])\n(0,['b'])\n(0,['c'])\n"); - test_parse_stmt(" a b {c}", - "(0,['a'])\n(0,['b'])\n(0,['c'])\n"); - test_parse_stmt("a b{c }", - "(0,['a'])\n(0,['b'])\n(0,['c'])\n"); - test_parse_stmt("a b{c}d", - "(0,['a'])\n(0,['b'])\n(0,['c'])\n(0,['d'])\n"); - test_parse_stmt("a b { c d }", - "(0,['a'])\n(0,['b'])\n(0,['c','d'])\n"); - test_parse_stmt("a {b} ~c", - "(0,['a'])\n(0,['b'])\n(1,['c'])\n"); - test_parse_stmt("a b ~ { c d }", - "(0,['a'])\n(0,['b'])\n(1,['c','d'])\n"); - test_parse_stmt("a b *", - "(0,['a'])\n(0,['b'])\n(0,['(null)'])\n"); - test_parse_stmt("a b { c * }", - "(0,['a'])\n(0,['b'])\n(0,['(null)'])\n"); - - // Invalid syntax tests - test_parse_stmt("a b { c", nullptr, -1); - test_parse_stmt("a {b}}c}", nullptr, -1); - test_parse_stmt("a }b{c}", nullptr, -1); - test_parse_stmt("{a} b } {c}", nullptr, -1); - test_parse_stmt("a { b } { c", nullptr, -1); - test_parse_stmt("a b {{ c }}", nullptr, -2); - test_parse_stmt("a b ~ ~c", nullptr, -3); - test_parse_stmt("a b ~~c", nullptr, -3); - - // Test enforcement functions - string s = "a * { b c } { d e } ~f *"; - argument_list args; - assert_msg(tokenize_string(s.data(), args) == 0, "parse failure"); - assert_msg(enforce_size<6>(args), "size != 6"); - assert_msg(enforce_non_null<0, 2, 3, 4>(args), "non-null enforcement failed"); - assert_msg(!enforce_non_null<0, 1, 2, 3, 4>(args), "non-null enforcement should fail"); - assert_msg(enforce_single<0, 1, 4, 5>(args), "single enforcement check failed"); - assert_msg(!enforce_single<0, 1, 2>(args), "single enforcement check should fail"); - assert_msg(check_complements<4>(args), "check complements failed"); - assert_msg(!check_complements<>(args), "check complements should fail"); -} diff --git a/native/src/sepolicy/statement.rs b/native/src/sepolicy/statement.rs new file mode 100644 index 000000000..e691d5a50 --- /dev/null +++ b/native/src/sepolicy/statement.rs @@ -0,0 +1,349 @@ +use crate::ffi::Xperm; +use crate::sepolicy; +use base::{LoggedError, LoggedResult}; +use std::{iter::Peekable, pin::Pin, vec::IntoIter}; + +pub enum Token<'a> { + AL, + DN, + AA, + DA, + AX, + AY, + DX, + PM, + EF, + TA, + TY, + AT, + TT, + TC, + TM, + GF, + IO, + LB, + RB, + CM, + ST, + TL, + HP, + HX(u16), + ID(&'a str), +} + +type Tokens<'a> = Peekable>>; + +macro_rules! throw { + () => { + Err(LoggedError::default())? + }; +} + +fn parse_id<'a>(tokens: &mut Tokens<'a>) -> LoggedResult<&'a str> { + match tokens.next() { + Some(Token::ID(name)) => Ok(name), + _ => throw!(), + } +} + +// names ::= ID(n) { vec![n] }; +// names ::= names(mut v) CM ID(n) { v.push(n); v }; +// term ::= ID(n) { vec![n] } +// term ::= LB names(n) RB { n }; +fn parse_term<'a>(tokens: &mut Tokens<'a>) -> LoggedResult> { + match tokens.next() { + Some(Token::ID(name)) => return Ok(vec![name]), + Some(Token::LB) => { + let mut names = Vec::new(); + loop { + names.push(parse_id(tokens)?); + match tokens.peek() { + Some(Token::CM) => { + tokens.next(); + } + _ => break, + } + } + if matches!(tokens.next(), Some(Token::RB)) { + return Ok(names); + } + throw!() + } + _ => throw!(), + } +} + +// terms ::= ST { vec![] } +// terms ::= term(n) { n } +fn parse_terms<'a>(tokens: &mut Tokens<'a>) -> LoggedResult> { + match tokens.peek() { + Some(Token::ST) => { + tokens.next(); + Ok(Vec::new()) + } + _ => parse_term(tokens), + } +} + +// xperm ::= HX(low) { Xperm{low, high: 0, reset: false} }; +// xperm ::= HX(low) HP HX(high) { Xperm{low, high, reset: false} }; +fn parse_xperm<'a>(tokens: &mut Tokens<'a>) -> LoggedResult { + let low = match tokens.next() { + Some(Token::HX(low)) => low, + _ => throw!(), + }; + let high = match tokens.peek() { + Some(Token::HP) => { + tokens.next(); + match tokens.next() { + Some(Token::HX(high)) => high, + _ => throw!(), + } + } + _ => 0, + }; + Ok(Xperm { + low, + high, + reset: false, + }) +} + +// xperms ::= HX(low) { if low > 0 { vec![Xperm{low, high: 0, reset: false}] } else { vec![Xperm{low: 0x0000, high: 0xFFFF, reset: true}] }}; +// xperms ::= LB xperm_list(l) RB { l }; +// xperms ::= TL LB xperm_list(mut l) RB { l.iter_mut().for_each(|x| { x.reset = true; }); l }; +// xperms ::= ST { vec![Xperm{low: 0x0000, high: 0xFFFF, reset: false}] }; +// +// xperm_list ::= xperm(p) { vec![p] } +// xperm_list ::= xperm_list(mut l) xperm(p) { l.push(p); l } +fn parse_xperms<'a>(tokens: &mut Tokens<'a>) -> LoggedResult> { + let mut xperms = Vec::new(); + let reset = match tokens.peek() { + Some(Token::TL) => { + tokens.next(); + if !matches!(tokens.peek(), Some(Token::LB)) { + throw!(); + } + true + } + _ => false, + }; + match tokens.next() { + Some(Token::LB) => { + // parse xperm_list + loop { + let mut xperm = parse_xperm(tokens)?; + xperm.reset = reset; + xperms.push(xperm); + if matches!(tokens.peek(), Some(Token::RB)) { + tokens.next(); + break; + } + } + } + Some(Token::ST) => { + xperms.push(Xperm { + low: 0x0000, + high: 0xFFFF, + reset, + }); + } + Some(Token::HX(low)) => { + if low > 0 { + xperms.push(Xperm { + low, + high: 0, + reset, + }); + } else { + xperms.push(Xperm { + low: 0x0000, + high: 0xFFFF, + reset, + }); + } + } + _ => throw!(), + } + Ok(xperms) +} + +// statement ::= AL sterm(s) sterm(t) sterm(c) sterm(p) { extra.as_mut().allow(s, t, c, p); }; +// statement ::= DN sterm(s) sterm(t) sterm(c) sterm(p) { extra.as_mut().deny(s, t, c, p); }; +// statement ::= AA sterm(s) sterm(t) sterm(c) sterm(p) { extra.as_mut().auditallow(s, t, c, p); }; +// statement ::= DA sterm(s) sterm(t) sterm(c) sterm(p) { extra.as_mut().dontaudit(s, t, c, p); }; +// statement ::= AX sterm(s) sterm(t) sterm(c) IO xperms(p) { extra.as_mut().allowxperm(s, t, c, p); }; +// statement ::= AY sterm(s) sterm(t) sterm(c) IO xperms(p) { extra.as_mut().auditallowxperm(s, t, c, p); }; +// statement ::= DX sterm(s) sterm(t) sterm(c) IO xperms(p) { extra.as_mut().dontauditxperm(s, t, c, p); }; +// statement ::= PM sterm(t) { extra.as_mut().permissive(t); }; +// statement ::= EF sterm(t) { extra.as_mut().enforce(t); }; +// statement ::= TA term(t) term(a) { extra.as_mut().typeattribute(t, a); }; +// statement ::= TY ID(t) { extra.as_mut().type_(t, vec![]);}; +// statement ::= TY ID(t) term(a) { extra.as_mut().type_(t, a);}; +// statement ::= AT ID(t) { extra.as_mut().attribute(t); }; +// statement ::= TT ID(s) ID(t) ID(c) ID(d) { extra.as_mut().type_transition(s, t, c, d, vec![]); }; +// statement ::= TT ID(s) ID(t) ID(c) ID(d) ID(o) { extra.as_mut().type_transition(s, t, c, d, vec![o]); }; +// statement ::= TC ID(s) ID(t) ID(c) ID(d) { extra.as_mut().type_change(s, t, c, d); }; +// statement ::= TM ID(s) ID(t) ID(c) ID(d) { extra.as_mut().type_member(s, t, c, d);}; +// statement ::= GF ID(s) ID(t) ID(c) { extra.as_mut().genfscon(s, t, c); }; +fn exec_statement<'a>(sepolicy: Pin<&mut sepolicy>, tokens: &mut Tokens<'a>) -> LoggedResult<()> { + let action = match tokens.next() { + Some(token) => token, + _ => throw!(), + }; + match action { + Token::AL | Token::DN | Token::AA | Token::DA => { + let s = parse_terms(tokens)?; + let t = parse_terms(tokens)?; + let c = parse_terms(tokens)?; + let p = parse_terms(tokens)?; + match action { + Token::AL => sepolicy.allow(s, t, c, p), + Token::DN => sepolicy.deny(s, t, c, p), + Token::AA => sepolicy.auditallow(s, t, c, p), + Token::DA => sepolicy.dontaudit(s, t, c, p), + _ => unreachable!(), + } + } + Token::AX | Token::AY | Token::DX => { + let s = parse_terms(tokens)?; + let t = parse_terms(tokens)?; + let c = parse_terms(tokens)?; + let p = if matches!(tokens.next(), Some(Token::IO)) { + parse_xperms(tokens)? + } else { + throw!() + }; + match action { + Token::AX => sepolicy.allowxperm(s, t, c, p), + Token::AY => sepolicy.auditallowxperm(s, t, c, p), + Token::DX => sepolicy.dontauditxperm(s, t, c, p), + _ => unreachable!(), + } + } + Token::PM | Token::EF => { + let t = parse_terms(tokens)?; + match action { + Token::PM => sepolicy.permissive(t), + Token::EF => sepolicy.enforce(t), + _ => unreachable!(), + } + } + Token::TA => { + let t = parse_term(tokens)?; + let a = parse_term(tokens)?; + sepolicy.typeattribute(t, a) + } + Token::TY => { + let t = parse_id(tokens)?; + let a = if matches!(tokens.peek(), None) { + vec![] + } else { + parse_term(tokens)? + }; + sepolicy.type_(t, a) + } + Token::AT => { + let t = parse_id(tokens)?; + sepolicy.attribute(t) + } + Token::TT | Token::TC | Token::TM => { + let s = parse_id(tokens)?; + let t = parse_id(tokens)?; + let c = parse_id(tokens)?; + let d = parse_id(tokens)?; + match action { + Token::TT => { + let o = if matches!(tokens.peek(), None) { + vec![] + } else { + vec![parse_id(tokens)?] + }; + sepolicy.type_transition(s, t, c, d, o) + } + Token::TC => sepolicy.type_change(s, t, c, d), + Token::TM => sepolicy.type_member(s, t, c, d), + _ => unreachable!(), + } + } + Token::GF => { + let s = parse_id(tokens)?; + let t = parse_id(tokens)?; + let c = parse_id(tokens)?; + sepolicy.genfscon(s, t, c) + } + _ => throw!(), + } + if matches!(tokens.peek(), None) { + Ok(()) + } else { + throw!() + } +} + +fn tokenize_statement<'a>(statement: &'a str) -> Vec> { + let mut tokens = Vec::new(); + for s in statement.trim().split_whitespace() { + let mut res = Some(s); + while let Some(s) = res { + match s { + "allow" => tokens.push(Token::AL), + "deny" => tokens.push(Token::DN), + "auditallow" => tokens.push(Token::AA), + "dontaudit" => tokens.push(Token::DA), + "allowxperm" => tokens.push(Token::AX), + "auditallowxperm" => tokens.push(Token::AY), + "dontauditxperm" => tokens.push(Token::DX), + "permissive" => tokens.push(Token::PM), + "enforce" => tokens.push(Token::EF), + "typeattribute" => tokens.push(Token::TA), + "type" => tokens.push(Token::TY), + "attribute" => tokens.push(Token::AT), + "type_transition" => tokens.push(Token::TT), + "type_change" => tokens.push(Token::TC), + "type_member" => tokens.push(Token::TM), + "genfscon" => tokens.push(Token::GF), + "ioctl" => tokens.push(Token::IO), + "" => {} + _ => { + if let Some(s) = s.strip_prefix("{") { + tokens.push(Token::LB); + res = Some(s); + continue; + } else if let Some(s) = s.strip_prefix("}") { + tokens.push(Token::RB); + res = Some(s); + continue; + } else if let Some(s) = s.strip_prefix(",") { + tokens.push(Token::CM); + res = Some(s); + continue; + } else if let Some(s) = s.strip_prefix("*") { + tokens.push(Token::ST); + res = Some(s); + continue; + } else if let Some(s) = s.strip_prefix("~") { + res = Some(s); + tokens.push(Token::TL); + continue; + } else if let Some(s) = s.strip_prefix("-") { + res = Some(s); + tokens.push(Token::HP); + continue; + } else if let Some(s) = s.strip_prefix("0x") { + tokens.push(Token::HX(s.parse().unwrap_or(0))); + } else { + tokens.push(Token::ID(s)); + } + } + } + break; + } + } + tokens +} + +pub fn parse_statement<'a>(sepolicy: Pin<&mut sepolicy>, statement: &'a str) -> LoggedResult<()> { + let mut tokens = tokenize_statement(statement).into_iter().peekable(); + exec_statement(sepolicy, &mut tokens) +}