Rewrite sepolicy statement parsing in Rust

This commit is contained in:
LoveSy 2024-03-03 10:33:04 +08:00 committed by topjohnwu
parent ecc74d45d1
commit 4d2921e742
10 changed files with 834 additions and 794 deletions

View File

@ -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

View File

@ -1,104 +1,27 @@
#include <base.hpp>
#include "flags.h"
#include "policy.hpp"
#if 0
// Print out all rules going through public API for debugging
template <typename ...Args>
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<typename Arg>
auto as_str(Arg arg) {
if constexpr (std::is_same_v<Arg, const char *> || std::is_same_v<Arg, char *>) {
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<typename ...Args>
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<typename F, typename ...T>
requires(std::invocable<F, T...>)
inline void expand(F &&f, T &&...args) {
f(std::forward<T>(args)...);
}
template<typename ...T>
inline void expand(const rust::Vec<rust::Str> &vec, T &&...args) {
for (auto i = vec.begin(); i != vec.end() || vec.empty(); ++i) {
expand(std::forward<T>(args)..., vec.empty() ? nullptr : std::string(*i).data());
if (vec.empty()) break;
}
}
template<typename ...T>
inline void expand(const rust::Vec<Xperm> &vec, T &&...args) {
for (auto &p : vec) {
expand(std::forward<T>(args)..., p.low, p.high, p.reset);
}
}
template<typename ...T>
inline void expand(const rust::Str &s, T &&...args) {
expand(std::forward<T>(args)..., std::string(s).data());
}
void
sepolicy::allow(rust::Vec<rust::Str> src, rust::Vec<rust::Str> tgt, rust::Vec<rust::Str> cls,
rust::Vec<rust::Str> perm) {
expand(src, tgt, cls, perm, [this](auto ...args) {
dprint("allow", args...);
impl->add_rule(args..., AVTAB_ALLOWED, false);
});
}
void
sepolicy::deny(rust::Vec<rust::Str> src, rust::Vec<rust::Str> tgt, rust::Vec<rust::Str> cls,
rust::Vec<rust::Str> perm) {
expand(src, tgt, cls, perm, [this](auto ...args) {
dprint("deny", args...);
impl->add_rule(args..., AVTAB_ALLOWED, true);
});
}
void sepolicy::auditallow(rust::Vec<rust::Str> src, rust::Vec<rust::Str> tgt,
rust::Vec<rust::Str> cls,
rust::Vec<rust::Str> perm) {
expand(src, tgt, cls, perm, [this](auto ...args) {
dprint("auditallow", args...);
impl->add_rule(args..., AVTAB_AUDITALLOW, false);
});
}
void sepolicy::dontaudit(rust::Vec<rust::Str> src, rust::Vec<rust::Str> tgt,
rust::Vec<rust::Str> cls,
rust::Vec<rust::Str> perm) {
expand(src, tgt, cls, perm, [this](auto ...args) {
dprint("dontaudit", args...);
impl->add_rule(args..., AVTAB_AUDITDENY, true);
});
}
void sepolicy::permissive(rust::Vec<rust::Str> types) {
expand(types, [this](auto ...args) {
dprint("permissive", args...);
impl->set_type_state(args..., true);
});
}
void sepolicy::enforce(rust::Vec<rust::Str> types) {
expand(types, [this](auto ...args) {
dprint("enforce", args...);
impl->set_type_state(args..., false);
});
}
void sepolicy::typeattribute(rust::Vec<rust::Str> types, rust::Vec<rust::Str> attrs) {
expand(types, attrs, [this](auto ...args) {
dprint("typeattribute", args...);
impl->add_typeattribute(args...);
});
}
void sepolicy::type(rust::Str type, rust::Vec<rust::Str> 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<rust::Str> 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<rust::Str> src, rust::Vec<rust::Str> tgt, rust::Vec<rust::Str> cls,
rust::Vec<Xperm> 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<rust::Str> src, rust::Vec<rust::Str> tgt,
rust::Vec<rust::Str> cls,
rust::Vec<Xperm> 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<rust::Str> src, rust::Vec<rust::Str> tgt,
rust::Vec<rust::Str> cls,
rust::Vec<Xperm> 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();
}

View File

@ -24,6 +24,8 @@ using token_list = std::vector<const char *>;
using argument = std::pair<token_list, bool>;
using argument_list = std::vector<argument>;
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<rust::Str> attrs);
void attribute(rust::Str names);
void permissive(rust::Vec<rust::Str> types);
void enforce(rust::Vec<rust::Str> types);
void typeattribute(rust::Vec<rust::Str> types, rust::Vec<rust::Str> 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<rust::Str> src, rust::Vec<rust::Str> tgt, rust::Vec<rust::Str> cls, rust::Vec<rust::Str> perm);
void deny(rust::Vec<rust::Str> src, rust::Vec<rust::Str> tgt, rust::Vec<rust::Str> cls, rust::Vec<rust::Str> perm);
void auditallow(rust::Vec<rust::Str> src, rust::Vec<rust::Str> tgt, rust::Vec<rust::Str> cls, rust::Vec<rust::Str> perm);
void dontaudit(rust::Vec<rust::Str> src, rust::Vec<rust::Str> tgt, rust::Vec<rust::Str> cls, rust::Vec<rust::Str> 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<rust::Str> src, rust::Vec<rust::Str> tgt, rust::Vec<rust::Str> cls, rust::Vec<Xperm> xperm);
void auditallowxperm(rust::Vec<rust::Str> src, rust::Vec<rust::Str> tgt, rust::Vec<rust::Str> cls, rust::Vec<Xperm> xperm);
void dontauditxperm(rust::Vec<rust::Str> src, rust::Vec<rust::Str> tgt, rust::Vec<rust::Str> cls, rust::Vec<Xperm> 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<rust::Str> 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

View File

@ -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<Xperm>,
);
fn auditallowxperm(
self: Pin<&mut sepolicy>,
s: Vec<&str>,
t: Vec<&str>,
c: Vec<&str>,
p: Vec<Xperm>,
);
fn dontauditxperm(
self: Pin<&mut sepolicy>,
s: Vec<&str>,
t: Vec<&str>,
c: Vec<&str>,
p: Vec<Xperm>,
);
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<T: BufRead>(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<T: BufRead>(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();
}

View File

@ -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);

View File

@ -1,136 +0,0 @@
#include <consts.hpp>
#include <base.hpp>
#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);
}

View File

@ -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);
}
}

View File

@ -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<pair<uint16_t, uint16_t>> 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;
}

View File

@ -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 <int ...indices>
static bool enforce_single(const argument_list &args) {
initializer_list<int> 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 <int ...indices>
static bool enforce_non_null(const argument_list &args) {
initializer_list<int> list{indices...};
for (int i : list)
if (args[i].first.size() == 1 && args[i].first[0] == nullptr)
return false;
return true;
}
template <int size>
static bool enforce_size(const argument_list &args) {
return args.size() == size;
}
// Check all args are not complements except those listed
template <int ...except>
static bool check_complements(const argument_list &args) {
initializer_list<int> 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 <int size>
static bool tokenize_and_check(char *stmt, argument_list &args) {
return tokenize_string(stmt, args) == 0 &&
enforce_size<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 <typename Func, typename ...Args>
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 <typename Func>
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 <typename Func>
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 <typename Func>
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 <typename Func>
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 <typename Func>
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<const char*>{ "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 <typename Func>
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 <typename Func>
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<const char*>{ 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 <typename Func>
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 <typename Func>
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");
}

View File

@ -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<IntoIter<Token<'a>>>;
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<Vec<&'a str>> {
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<Vec<&'a str>> {
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<Xperm> {
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<Vec<Xperm>> {
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<Token<'a>> {
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)
}