mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-12-22 16:07:39 +00:00
Update xperm parsing
This commit is contained in:
parent
9638dc0a66
commit
6089cc36de
@ -9,7 +9,7 @@ static void dprint(const char *action, Args ...args) {
|
|||||||
std::string s(action);
|
std::string s(action);
|
||||||
for (int i = 0; i < sizeof...(args); ++i) s += " %s";
|
for (int i = 0; i < sizeof...(args); ++i) s += " %s";
|
||||||
s += "\n";
|
s += "\n";
|
||||||
LOGD(s.data(), (args ? args : "*")...);
|
LOGD(s.data(), as_str(args)...);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
#define dprint(...)
|
#define dprint(...)
|
||||||
@ -35,19 +35,19 @@ bool sepolicy::dontaudit(const char *s, const char *t, const char *c, const char
|
|||||||
return impl->add_rule(s, t, c, p, AVTAB_AUDITDENY, true);
|
return impl->add_rule(s, t, c, p, AVTAB_AUDITDENY, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool sepolicy::allowxperm(const char *s, const char *t, const char *c, const char *range) {
|
bool sepolicy::allowxperm(const char *s, const char *t, const char *c, const argument &xperm) {
|
||||||
dprint(__FUNCTION__, s, t, c, "ioctl", range);
|
dprint(__FUNCTION__, s, t, c, "ioctl", xperm);
|
||||||
return impl->add_xperm_rule(s, t, c, range, AVTAB_XPERMS_ALLOWED, false);
|
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 char *range) {
|
bool sepolicy::auditallowxperm(const char *s, const char *t, const char *c, const argument &xperm) {
|
||||||
dprint(__FUNCTION__, s, t, c, "ioctl", range);
|
dprint(__FUNCTION__, s, t, c, "ioctl", xperm);
|
||||||
return impl->add_xperm_rule(s, t, c, range, AVTAB_XPERMS_AUDITALLOW, false);
|
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 char *range) {
|
bool sepolicy::dontauditxperm(const char *s, const char *t, const char *c, const argument &xperm) {
|
||||||
dprint(__FUNCTION__, s, t, c, "ioctl", range);
|
dprint(__FUNCTION__, s, t, c, "ioctl", xperm);
|
||||||
return impl->add_xperm_rule(s, t, c, range, AVTAB_XPERMS_DONTAUDIT, false);
|
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) {
|
bool sepolicy::type_change(const char *s, const char *t, const char *c, const char *d) {
|
||||||
|
@ -5,7 +5,14 @@
|
|||||||
|
|
||||||
#include <selinux.hpp>
|
#include <selinux.hpp>
|
||||||
|
|
||||||
|
using token_list = std::vector<const char *>;
|
||||||
|
using argument = std::pair<token_list, bool>;
|
||||||
|
using argument_list = std::vector<argument>;
|
||||||
|
|
||||||
|
const argument &all_xperm();
|
||||||
|
|
||||||
#define ALL nullptr
|
#define ALL nullptr
|
||||||
|
#define ALL_XPERM all_xperm()
|
||||||
|
|
||||||
struct sepolicy {
|
struct sepolicy {
|
||||||
using c_str = const char *;
|
using c_str = const char *;
|
||||||
@ -38,9 +45,9 @@ struct sepolicy {
|
|||||||
bool dontaudit(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);
|
||||||
|
|
||||||
// Extended permissions access vector rules
|
// Extended permissions access vector rules
|
||||||
bool allowxperm(c_str src, c_str tgt, c_str cls, c_str range);
|
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, c_str range);
|
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, c_str range);
|
bool dontauditxperm(c_str src, c_str tgt, c_str cls, const argument &xperm);
|
||||||
|
|
||||||
// Type rules
|
// Type rules
|
||||||
bool type_transition(c_str src, c_str tgt, c_str cls, c_str def, c_str obj = nullptr);
|
bool type_transition(c_str src, c_str tgt, c_str cls, c_str def, c_str obj = nullptr);
|
||||||
|
@ -8,12 +8,14 @@
|
|||||||
#include "policy-rs.hpp"
|
#include "policy-rs.hpp"
|
||||||
|
|
||||||
struct sepol_impl : public sepolicy {
|
struct sepol_impl : public sepolicy {
|
||||||
|
avtab_ptr_t find_avtab_node(avtab_key_t *key, avtab_extended_perms_t *xperms);
|
||||||
|
avtab_ptr_t insert_avtab_node(avtab_key_t *key);
|
||||||
avtab_ptr_t get_avtab_node(avtab_key_t *key, avtab_extended_perms_t *xperms);
|
avtab_ptr_t get_avtab_node(avtab_key_t *key, avtab_extended_perms_t *xperms);
|
||||||
|
|
||||||
bool add_rule(const char *s, const char *t, const char *c, const char *p, int effect, bool invert);
|
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_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,
|
void add_xperm_rule(type_datum_t *src, type_datum_t *tgt, class_datum_t *cls, const argument &xperm, int effect);
|
||||||
class_datum_t *cls, uint16_t low, uint16_t high, int effect, bool invert);
|
bool add_xperm_rule(const char *s, const char *t, const char *c, const argument &xperm, int effect);
|
||||||
bool add_xperm_rule(const char *s, const char *t, const char *c, const char *range, int effect, bool invert);
|
|
||||||
bool add_type_rule(const char *s, const char *t, const char *c, const char *d, 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_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);
|
bool add_genfscon(const char *fs_name, const char *path, const char *context);
|
||||||
@ -31,4 +33,8 @@ struct sepol_impl : public sepolicy {
|
|||||||
|
|
||||||
#define impl reinterpret_cast<sepol_impl *>(this)
|
#define impl reinterpret_cast<sepol_impl *>(this)
|
||||||
|
|
||||||
|
const char *as_str(const argument &arg);
|
||||||
|
const char *as_str(const char *arg);
|
||||||
|
|
||||||
void statement_help();
|
void statement_help();
|
||||||
|
void test_parse_statements();
|
||||||
|
@ -23,9 +23,9 @@ void sepolicy::magisk_rules() {
|
|||||||
allow(SEPOL_PROC_DOMAIN, ALL, ALL, ALL);
|
allow(SEPOL_PROC_DOMAIN, ALL, ALL, ALL);
|
||||||
// Allow us to do any ioctl
|
// Allow us to do any ioctl
|
||||||
if (impl->db->policyvers >= POLICYDB_VERSION_XPERMS_IOCTL) {
|
if (impl->db->policyvers >= POLICYDB_VERSION_XPERMS_IOCTL) {
|
||||||
allowxperm(SEPOL_PROC_DOMAIN, ALL, "blk_file", ALL);
|
allowxperm(SEPOL_PROC_DOMAIN, ALL, "blk_file", ALL_XPERM);
|
||||||
allowxperm(SEPOL_PROC_DOMAIN, ALL, "fifo_file", ALL);
|
allowxperm(SEPOL_PROC_DOMAIN, ALL, "fifo_file", ALL_XPERM);
|
||||||
allowxperm(SEPOL_PROC_DOMAIN, ALL, "chr_file", ALL);
|
allowxperm(SEPOL_PROC_DOMAIN, ALL, "chr_file", ALL_XPERM);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create unconstrained file type
|
// Create unconstrained file type
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
#include "policy.hpp"
|
#include "policy.hpp"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
// Invert is adding rules for auditdeny; in other cases, invert is removing rules
|
// Invert is adding rules for auditdeny; in other cases, invert is removing rules
|
||||||
#define strip_av(effect, invert) ((effect == AVTAB_AUDITDENY) == !invert)
|
#define strip_av(effect, invert) ((effect == AVTAB_AUDITDENY) == !invert)
|
||||||
|
|
||||||
@ -101,38 +103,42 @@ static bool is_redundant(avtab_ptr_t node) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
avtab_ptr_t sepol_impl::get_avtab_node(avtab_key_t *key, avtab_extended_perms_t *xperms) {
|
avtab_ptr_t sepol_impl::find_avtab_node(avtab_key_t *key, avtab_extended_perms_t *xperms) {
|
||||||
avtab_ptr_t node;
|
avtab_ptr_t node;
|
||||||
|
|
||||||
/* AVTAB_XPERMS entries are not necessarily unique */
|
// AVTAB_XPERMS entries are not necessarily unique
|
||||||
if (key->specified & AVTAB_XPERMS) {
|
if (key->specified & AVTAB_XPERMS) {
|
||||||
bool match = false;
|
if (xperms == nullptr)
|
||||||
|
return nullptr;
|
||||||
node = avtab_search_node(&db->te_avtab, key);
|
node = avtab_search_node(&db->te_avtab, key);
|
||||||
while (node) {
|
while (node) {
|
||||||
if ((node->datum.xperms->specified == xperms->specified) &&
|
if ((node->datum.xperms->specified == xperms->specified) &&
|
||||||
(node->datum.xperms->driver == xperms->driver)) {
|
(node->datum.xperms->driver == xperms->driver)) {
|
||||||
match = true;
|
node = nullptr;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
node = avtab_search_node_next(node, key->specified);
|
node = avtab_search_node_next(node, key->specified);
|
||||||
}
|
}
|
||||||
if (!match)
|
|
||||||
node = nullptr;
|
|
||||||
} else {
|
} else {
|
||||||
node = avtab_search_node(&db->te_avtab, key);
|
node = avtab_search_node(&db->te_avtab, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!node) {
|
return node;
|
||||||
avtab_datum_t avdatum{};
|
|
||||||
/*
|
|
||||||
* AUDITDENY, aka DONTAUDIT, are &= assigned, versus |= for
|
|
||||||
* others. Initialize the data accordingly.
|
|
||||||
*/
|
|
||||||
avdatum.data = key->specified == AVTAB_AUDITDENY ? ~0U : 0U;
|
|
||||||
/* this is used to get the node - insertion is actually unique */
|
|
||||||
node = avtab_insert_nonunique(&db->te_avtab, key, &avdatum);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
avtab_ptr_t sepol_impl::insert_avtab_node(avtab_key_t *key) {
|
||||||
|
avtab_datum_t avdatum{};
|
||||||
|
// AUDITDENY, aka DONTAUDIT, are &= assigned, versus |= for others.
|
||||||
|
// Initialize the data accordingly.
|
||||||
|
avdatum.data = key->specified == AVTAB_AUDITDENY ? ~0U : 0U;
|
||||||
|
return avtab_insert_nonunique(&db->te_avtab, key, &avdatum);
|
||||||
|
}
|
||||||
|
|
||||||
|
avtab_ptr_t sepol_impl::get_avtab_node(avtab_key_t *key, avtab_extended_perms_t *xperms) {
|
||||||
|
avtab_ptr_t node = find_avtab_node(key, xperms);
|
||||||
|
if (!node) {
|
||||||
|
node = insert_avtab_node(key);
|
||||||
|
}
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,19 +245,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_driver(x) (x>>8 & 0xFF)
|
||||||
#define ioctl_func(x) (x & 0xFF)
|
#define ioctl_func(x) (x & 0xFF)
|
||||||
|
|
||||||
void sepol_impl::add_xperm_rule(type_datum_t *src, type_datum_t *tgt,
|
void sepol_impl::add_xperm_rule(type_datum_t *src, type_datum_t *tgt, class_datum_t *cls, const argument &xperm, int effect) {
|
||||||
class_datum_t *cls, uint16_t low, uint16_t high, int effect, bool invert) {
|
|
||||||
if (src == nullptr) {
|
if (src == nullptr) {
|
||||||
for_each_attr(db->p_types.table, [&](type_datum_t *type) {
|
for_each_attr(db->p_types.table, [&](type_datum_t *type) {
|
||||||
add_xperm_rule(type, tgt, cls, low, high, effect, invert);
|
add_xperm_rule(type, tgt, cls, xperm, effect);
|
||||||
});
|
});
|
||||||
} else if (tgt == nullptr) {
|
} else if (tgt == nullptr) {
|
||||||
for_each_attr(db->p_types.table, [&](type_datum_t *type) {
|
for_each_attr(db->p_types.table, [&](type_datum_t *type) {
|
||||||
add_xperm_rule(src, type, cls, low, high, effect, invert);
|
add_xperm_rule(src, type, cls, xperm, effect);
|
||||||
});
|
});
|
||||||
} else if (cls == nullptr) {
|
} else if (cls == nullptr) {
|
||||||
hashtab_for_each(db->p_classes.table, [&](hashtab_ptr_t node) {
|
hashtab_for_each(db->p_classes.table, [&](hashtab_ptr_t node) {
|
||||||
add_xperm_rule(src, tgt, auto_cast(node->datum), low, high, effect, invert);
|
add_xperm_rule(src, tgt, auto_cast(node->datum), xperm, effect);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
avtab_key_t key;
|
avtab_key_t key;
|
||||||
@ -260,46 +265,130 @@ void sepol_impl::add_xperm_rule(type_datum_t *src, type_datum_t *tgt,
|
|||||||
key.target_class = cls->s.value;
|
key.target_class = cls->s.value;
|
||||||
key.specified = effect;
|
key.specified = effect;
|
||||||
|
|
||||||
avtab_datum_t *datum;
|
// Each key may contain 1 driver node and 256 function nodes
|
||||||
avtab_extended_perms_t xperms;
|
avtab_ptr_t node_list[257] = { nullptr };
|
||||||
|
#define driver_node (node_list[256])
|
||||||
|
|
||||||
memset(&xperms, 0, sizeof(xperms));
|
// Find all rules with key
|
||||||
|
for (avtab_ptr_t node = avtab_search_node(&db->te_avtab, &key); node;) {
|
||||||
|
if (node->datum.xperms->specified == AVTAB_XPERMS_IOCTLDRIVER) {
|
||||||
|
driver_node = node;
|
||||||
|
} else if (node->datum.xperms->specified == AVTAB_XPERMS_IOCTLFUNCTION) {
|
||||||
|
node_list[node->datum.xperms->driver] = node;
|
||||||
|
}
|
||||||
|
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]) {
|
||||||
|
avtab_remove_node(&db->te_avtab, node_list[i]);
|
||||||
|
node_list[i] = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (driver_node) {
|
||||||
|
memset(driver_node->datum.xperms->perms, 0, sizeof(avtab_extended_perms_t::perms));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto new_driver_node = [&]() -> avtab_ptr_t {
|
||||||
|
auto node = insert_avtab_node(&key);
|
||||||
|
node->datum.xperms = auto_cast(calloc(1, sizeof(avtab_extended_perms_t)));
|
||||||
|
node->datum.xperms->specified = AVTAB_XPERMS_IOCTLDRIVER;
|
||||||
|
node->datum.xperms->driver = 0;
|
||||||
|
return node;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto new_func_node = [&](uint8_t driver) -> avtab_ptr_t {
|
||||||
|
auto node = insert_avtab_node(&key);
|
||||||
|
node->datum.xperms = auto_cast(calloc(1, sizeof(avtab_extended_perms_t)));
|
||||||
|
node->datum.xperms->specified = AVTAB_XPERMS_IOCTLFUNCTION;
|
||||||
|
node->datum.xperms->driver = driver;
|
||||||
|
return node;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!xperm.second) {
|
||||||
|
for (auto [low, high] : ranges) {
|
||||||
if (ioctl_driver(low) != ioctl_driver(high)) {
|
if (ioctl_driver(low) != ioctl_driver(high)) {
|
||||||
xperms.specified = AVTAB_XPERMS_IOCTLDRIVER;
|
if (driver_node == nullptr) {
|
||||||
xperms.driver = 0;
|
driver_node = new_driver_node();
|
||||||
} else {
|
|
||||||
xperms.specified = AVTAB_XPERMS_IOCTLFUNCTION;
|
|
||||||
xperms.driver = ioctl_driver(low);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
datum = &get_avtab_node(&key, &xperms)->datum;
|
|
||||||
if (datum->xperms != nullptr)
|
|
||||||
memcpy(xperms.perms, datum->xperms->perms, sizeof(xperms.perms));
|
|
||||||
|
|
||||||
if (xperms.specified == AVTAB_XPERMS_IOCTLDRIVER) {
|
|
||||||
for (int i = ioctl_driver(low); i <= ioctl_driver(high); ++i) {
|
for (int i = ioctl_driver(low); i <= ioctl_driver(high); ++i) {
|
||||||
if (invert)
|
xperm_set(i, driver_node->datum.xperms->perms);
|
||||||
xperm_clear(i, xperms.perms);
|
|
||||||
else
|
|
||||||
xperm_set(i, xperms.perms);
|
|
||||||
}
|
}
|
||||||
} else {
|
} 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) {
|
for (int i = ioctl_func(low); i <= ioctl_func(high); ++i) {
|
||||||
if (invert)
|
xperm_set(i, node->datum.xperms->perms);
|
||||||
xperm_clear(i, xperms.perms);
|
}
|
||||||
else
|
}
|
||||||
xperm_set(i, xperms.perms);
|
}
|
||||||
|
} else {
|
||||||
|
if (driver_node == nullptr) {
|
||||||
|
driver_node = new_driver_node();
|
||||||
|
}
|
||||||
|
// 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 (datum->xperms == nullptr)
|
bool sepol_impl::add_xperm_rule(const char *s, const char *t, const char *c, const argument &xperm, int effect) {
|
||||||
datum->xperms = auto_cast(malloc(sizeof(xperms)));
|
|
||||||
|
|
||||||
memcpy(datum->xperms, &xperms, sizeof(xperms));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool sepol_impl::add_xperm_rule(const char *s, const char *t, const char *c, const char *range, int effect, bool invert) {
|
|
||||||
type_datum_t *src = nullptr, *tgt = nullptr;
|
type_datum_t *src = nullptr, *tgt = nullptr;
|
||||||
class_datum_t *cls = nullptr;
|
class_datum_t *cls = nullptr;
|
||||||
|
|
||||||
@ -327,21 +416,7 @@ bool sepol_impl::add_xperm_rule(const char *s, const char *t, const char *c, con
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t low, high;
|
add_xperm_rule(src, tgt, cls, xperm, effect);
|
||||||
|
|
||||||
if (range) {
|
|
||||||
if (strchr(range, '-')){
|
|
||||||
sscanf(range, "%hx-%hx", &low, &high);
|
|
||||||
} else {
|
|
||||||
sscanf(range, "%hx", &low);
|
|
||||||
high = low;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
low = 0;
|
|
||||||
high = 0xFFFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
add_xperm_rule(src, tgt, cls, low, high, effect, invert);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,13 @@
|
|||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
const argument &all_xperm() {
|
||||||
|
static argument arg;
|
||||||
|
if (arg.first.empty())
|
||||||
|
arg.first.push_back(nullptr);
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
|
||||||
static const char *type_msg_1 =
|
static const char *type_msg_1 =
|
||||||
R"EOF("allow *source_type *target_type *class *perm_set"
|
R"EOF("allow *source_type *target_type *class *perm_set"
|
||||||
"deny *source_type *target_type *class *perm_set"
|
"deny *source_type *target_type *class *perm_set"
|
||||||
@ -19,15 +26,23 @@ static const char *type_msg_2 =
|
|||||||
R"EOF("allowxperm *source_type *target_type *class operation xperm_set"
|
R"EOF("allowxperm *source_type *target_type *class operation xperm_set"
|
||||||
"auditallowxperm *source_type *target_type *class operation xperm_set"
|
"auditallowxperm *source_type *target_type *class operation xperm_set"
|
||||||
"dontauditxperm *source_type *target_type *class operation xperm_set"
|
"dontauditxperm *source_type *target_type *class operation xperm_set"
|
||||||
- The only supported operation is 'ioctl'
|
- The only supported operation right now is 'ioctl'
|
||||||
- xperm_set format is either 'low-high', 'value', or '*'.
|
- xperm_set is one or multiple hexadecimal numeric values ranging from 0x0000 to 0xFFFF.
|
||||||
'*' will be treated as '0x0000-0xFFFF'.
|
Multiple values consist of a space separated list enclosed in braces ({}).
|
||||||
All values should be written in hexadecimal.
|
Use the complement operator (~) to specify all permissions except those explicitly listed.
|
||||||
|
Use the range operator (-) to specify all permissions within the low – high range.
|
||||||
|
Use the match all operator (*) to match all ioctl commands.
|
||||||
|
The special value 0 is used to clear all rules.
|
||||||
|
Some examples:
|
||||||
|
allowxperm source target class ioctl 0x8910
|
||||||
|
allowxperm source target class ioctl { 0x8910-0x8926 0x892A-0x8935 }
|
||||||
|
allowxperm source target class ioctl ~{ 0x8910 0x892A }
|
||||||
|
allowxperm source target class ioctl *
|
||||||
)EOF";
|
)EOF";
|
||||||
|
|
||||||
static const char *type_msg_3 =
|
static const char *type_msg_3 =
|
||||||
R"EOF("permissive ^type"
|
R"EOF("permissive *type"
|
||||||
"enforce ^type"
|
"enforce *type"
|
||||||
)EOF";
|
)EOF";
|
||||||
|
|
||||||
static const char *type_msg_4 =
|
static const char *type_msg_4 =
|
||||||
@ -64,8 +79,8 @@ this means each policy statement should be enclosed in quotes.
|
|||||||
Multiple policy statements can be provided in a single command.
|
Multiple policy statements can be provided in a single command.
|
||||||
|
|
||||||
Statements has a format of "<rule_name> [args...]".
|
Statements has a format of "<rule_name> [args...]".
|
||||||
Arguments labeled with (^) can accept one or more entries. Multiple
|
Arguments labeled with (^) can accept one or more entries.
|
||||||
entries consist of a space separated list enclosed in braces ({}).
|
Multiple entries consist of a space separated list enclosed in braces ({}).
|
||||||
Arguments labeled with (*) are the same as (^), but additionally
|
Arguments labeled with (*) are the same as (^), but additionally
|
||||||
support the match-all operator (*).
|
support the match-all operator (*).
|
||||||
|
|
||||||
@ -93,101 +108,230 @@ type_msg_5, type_msg_6, type_msg_7, type_msg_8, type_msg_9);
|
|||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
using parsed_tokens = vector<vector<const char *>>;
|
// Return value:
|
||||||
|
// 0: success
|
||||||
static bool tokenize_string(char *stmt, parsed_tokens &arr) {
|
// -1: unclosed bracket
|
||||||
// cur is the pointer to where the top level is parsing
|
// -2: nested brackets
|
||||||
|
// -3: double complement
|
||||||
|
static int tokenize_string(char *stmt, argument_list &args) {
|
||||||
char *cur = stmt;
|
char *cur = stmt;
|
||||||
for (char *tok; (tok = strtok_r(nullptr, " ", &cur)) != nullptr;) {
|
bool complement = false;
|
||||||
vector<const char *> token;
|
|
||||||
if (tok[0] == '{') {
|
auto add_new_arg = [&] (char *str) {
|
||||||
// cur could point to somewhere in the braces, restore the string
|
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)
|
if (cur)
|
||||||
cur[-1] = ' ';
|
cur[-1] = ' ';
|
||||||
++tok;
|
|
||||||
char *end = strchr(tok, '}');
|
if (end == nullptr) {
|
||||||
|
// Find again
|
||||||
|
end = strchr(begin + 1, '}');
|
||||||
if (end == nullptr) {
|
if (end == nullptr) {
|
||||||
// Bracket not closed, syntax error
|
// Bracket not closed, syntax error
|
||||||
return false;
|
return -1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close bracket and start the next parsing iteration after that
|
||||||
*end = '\0';
|
*end = '\0';
|
||||||
for (char *sub_tok; (sub_tok = strtok_r(nullptr, " ", &tok)) != nullptr;)
|
|
||||||
token.push_back(sub_tok);
|
|
||||||
cur = end + 1;
|
cur = end + 1;
|
||||||
} else if (tok[0] == '*') {
|
|
||||||
token.push_back(nullptr);
|
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 {
|
} else {
|
||||||
token.push_back(tok);
|
add_new_arg(tok);
|
||||||
}
|
|
||||||
arr.push_back(std::move(token));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check array size and all args listed in 'ones' have size = 1 (no multiple entries)
|
tok = strtok_r(nullptr, " ", &cur);
|
||||||
template <int size, int ...ones>
|
}
|
||||||
static bool check_tokens(parsed_tokens &arr) {
|
return 0;
|
||||||
if (arr.size() != size)
|
}
|
||||||
return false;
|
|
||||||
initializer_list<int> list{ones...};
|
// 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)
|
for (int i : list)
|
||||||
if (arr[i].size() != 1)
|
if (args[i].first.size() != 1)
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <int size, int ...ones>
|
// Check all args listed are not null (no match all operator)
|
||||||
static bool tokenize_and_check(char *stmt, parsed_tokens &arr) {
|
template <int ...indices>
|
||||||
return tokenize_string(stmt, arr) && check_tokens<size, ones...>(arr);
|
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>
|
template <typename Func, typename ...Args>
|
||||||
static void run_and_check(const Func &fn, const char *action, Args ...args) {
|
static void run_and_check(const Func &fn, const char *action, Args ...args) {
|
||||||
if (!fn(args...)) {
|
if (!fn(args...)) {
|
||||||
string s = "Error in: %s";
|
string s = "Error in: %s";
|
||||||
for (int i = 0; i < sizeof...(args); ++i) s += " %s";
|
for (int i = 0; i < sizeof...(args); ++i) s += " %s";
|
||||||
s += "\n";
|
s += "\n";
|
||||||
LOGW(s.data(), action, (args ? args : "*")...);
|
LOGW(s.data(), action, as_str(args)...);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#define run_fn(...) run_and_check(fn, action, __VA_ARGS__)
|
#define run_fn(...) run_and_check(fn, action, __VA_ARGS__)
|
||||||
|
|
||||||
// Pattern 1: allow { source } { target } { class } { permission }
|
// Pattern 1: allow *{ source } *{ target } *{ class } *{ permission }
|
||||||
template <typename Func>
|
template <typename Func>
|
||||||
static bool parse_pattern_1(const Func &fn, const char *action, char *stmt) {
|
static bool parse_pattern_1(const Func &fn, const char *action, char *stmt) {
|
||||||
parsed_tokens arr;
|
argument_list args;
|
||||||
if (!tokenize_and_check<4>(stmt, arr))
|
if (!tokenize_and_check<4>(stmt, args))
|
||||||
return false;
|
return false;
|
||||||
for (auto src : arr[0])
|
for (auto src : args[0].first)
|
||||||
for (auto tgt : arr[1])
|
for (auto tgt : args[1].first)
|
||||||
for (auto cls : arr[2])
|
for (auto cls : args[2].first)
|
||||||
for (auto perm : arr[3])
|
for (auto perm : args[3].first)
|
||||||
run_fn(src, tgt, cls, perm);
|
run_fn(src, tgt, cls, perm);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pattern 2: allowxperm { source } { target } { class } ioctl range
|
// Pattern 2: allowxperm *{ source } *{ target } *{ class } ioctl xperm_set
|
||||||
template <typename Func>
|
template <typename Func>
|
||||||
static bool parse_pattern_2(const Func &fn, const char *action, char *stmt) {
|
static bool parse_pattern_2(const Func &fn, const char *action, char *stmt) {
|
||||||
parsed_tokens arr;
|
argument_list args;
|
||||||
if (!tokenize_and_check<5, 3, 4>(stmt, arr) || arr[3][0] != "ioctl"sv)
|
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;
|
return false;
|
||||||
auto range = arr[4][0];
|
for (auto src : args[0].first)
|
||||||
for (auto src : arr[0])
|
for (auto tgt : args[1].first)
|
||||||
for (auto tgt : arr[1])
|
for (auto cls : args[2].first)
|
||||||
for (auto cls : arr[2])
|
run_fn(src, tgt, cls, args[4]);
|
||||||
run_fn(src, tgt, cls, range);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pattern 3: permissive { type }
|
// Pattern 3: permissive *{ type }
|
||||||
template <typename Func>
|
template <typename Func>
|
||||||
static bool parse_pattern_3(const Func &fn, const char *action, char *stmt) {
|
static bool parse_pattern_3(const Func &fn, const char *action, char *stmt) {
|
||||||
parsed_tokens arr;
|
argument_list args;
|
||||||
if (!tokenize_and_check<1>(stmt, arr))
|
if (!tokenize_and_check<1>(stmt, args))
|
||||||
return false;
|
return false;
|
||||||
for (auto type : arr[0])
|
for (auto type : args[0].first)
|
||||||
run_fn(type);
|
run_fn(type);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -195,11 +339,11 @@ static bool parse_pattern_3(const Func &fn, const char *action, char *stmt) {
|
|||||||
// Pattern 4: typeattribute { type } { attribute }
|
// Pattern 4: typeattribute { type } { attribute }
|
||||||
template <typename Func>
|
template <typename Func>
|
||||||
static bool parse_pattern_4(const Func &fn, const char *action, char *stmt) {
|
static bool parse_pattern_4(const Func &fn, const char *action, char *stmt) {
|
||||||
parsed_tokens arr;
|
argument_list args;
|
||||||
if (!tokenize_and_check<2>(stmt, arr))
|
if (!tokenize_and_check<2>(stmt, args) || !enforce_non_null<0, 1>(args))
|
||||||
return false;
|
return false;
|
||||||
for (auto type : arr[0])
|
for (auto type : args[0].first)
|
||||||
for (auto attr : arr[1])
|
for (auto attr : args[1].first)
|
||||||
run_fn(type, attr);
|
run_fn(type, attr);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -207,61 +351,65 @@ static bool parse_pattern_4(const Func &fn, const char *action, char *stmt) {
|
|||||||
// Pattern 5: type name { attribute }
|
// Pattern 5: type name { attribute }
|
||||||
template <typename Func>
|
template <typename Func>
|
||||||
static bool parse_pattern_5(const Func &fn, const char *action, char *stmt) {
|
static bool parse_pattern_5(const Func &fn, const char *action, char *stmt) {
|
||||||
parsed_tokens arr;
|
argument_list args;
|
||||||
string tmp_str;
|
string tmp_str;
|
||||||
if (!tokenize_string(stmt, arr))
|
if (tokenize_string(stmt, args) != 0 || !enforce_single<0>(args))
|
||||||
return false;
|
return false;
|
||||||
if (arr.size() == 1) {
|
if (args.size() == 1) {
|
||||||
arr.emplace_back(initializer_list<const char*>{ "domain" });
|
args.emplace_back(make_pair(initializer_list<const char*>{ "domain" }, false));
|
||||||
}
|
}
|
||||||
if (!check_tokens<2, 0>(arr))
|
if (!enforce_size<2>(args) || !enforce_non_null<1>(args) || !check_complements<>(args))
|
||||||
return false;
|
return false;
|
||||||
for (auto attr : arr[1])
|
for (auto attr : args[1].first)
|
||||||
run_fn(arr[0][0], attr);
|
run_fn(args[0].first[0], attr);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pattern 6: attribute name
|
// Pattern 6: attribute name
|
||||||
template <typename Func>
|
template <typename Func>
|
||||||
static bool parse_pattern_6(const Func &fn, const char *action, char *stmt) {
|
static bool parse_pattern_6(const Func &fn, const char *action, char *stmt) {
|
||||||
parsed_tokens arr;
|
argument_list args;
|
||||||
if (!tokenize_and_check<1, 0>(stmt, arr))
|
if (!tokenize_and_check<1>(stmt, args) || !enforce_single<0>(args))
|
||||||
return false;
|
return false;
|
||||||
run_fn(arr[0][0]);
|
run_fn(args[0].first[0]);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pattern 7: type_transition source target class default (filename)
|
// Pattern 7: type_transition source target class default (filename)
|
||||||
template <typename Func>
|
template <typename Func>
|
||||||
static bool parse_pattern_7(const Func &fn, const char *action, char *stmt) {
|
static bool parse_pattern_7(const Func &fn, const char *action, char *stmt) {
|
||||||
parsed_tokens arr;
|
argument_list args;
|
||||||
if (!tokenize_string(stmt, arr))
|
if (tokenize_string(stmt, args) != 0)
|
||||||
return false;
|
return false;
|
||||||
if (arr.size() == 4)
|
if (args.size() == 4)
|
||||||
arr.emplace_back(initializer_list<const char*>{nullptr});
|
args.emplace_back(make_pair(initializer_list<const char*>{ nullptr }, false));
|
||||||
if (!check_tokens<5, 0, 1, 2, 3, 4>(arr))
|
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;
|
return false;
|
||||||
run_fn(arr[0][0], arr[1][0], arr[2][0], arr[3][0], arr[4][0]);
|
run_fn(args[0].first[0], args[1].first[0], args[2].first[0],
|
||||||
|
args[3].first[0], args[4].first[0]);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pattern 8: type_change source target class default
|
// Pattern 8: type_change source target class default
|
||||||
template <typename Func>
|
template <typename Func>
|
||||||
static bool parse_pattern_8(const Func &fn, const char *action, char *stmt) {
|
static bool parse_pattern_8(const Func &fn, const char *action, char *stmt) {
|
||||||
parsed_tokens arr;
|
argument_list args;
|
||||||
if (!tokenize_and_check<4, 0, 1, 2, 3>(stmt, arr))
|
if (!tokenize_and_check<4>(stmt, args) || !enforce_single<0, 1, 2, 3>(args))
|
||||||
return false;
|
return false;
|
||||||
run_fn(arr[0][0], arr[1][0], arr[2][0], arr[3][0]);
|
run_fn(args[0].first[0], args[1].first[0], args[2].first[0], args[3].first[0]);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pattern 9: genfscon name path context
|
// Pattern 9: genfscon name path context
|
||||||
template <typename Func>
|
template <typename Func>
|
||||||
static bool parse_pattern_9(const Func &fn, const char *action, char *stmt) {
|
static bool parse_pattern_9(const Func &fn, const char *action, char *stmt) {
|
||||||
parsed_tokens arr;
|
argument_list args;
|
||||||
if (!tokenize_and_check<3, 0, 1, 2>(stmt, arr))
|
if (!tokenize_and_check<3>(stmt, args) || !enforce_single<0, 1, 2>(args))
|
||||||
return false;
|
return false;
|
||||||
run_fn(arr[0][0], arr[1][0], arr[2][0]);
|
run_fn(args[0].first[0], args[1].first[0], args[2].first[0]);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,3 +458,119 @@ void sepolicy::parse_statement(const char *stmt, int len) {
|
|||||||
|
|
||||||
else { LOGW("Unknown action: '%s'\n\n", action); }
|
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");
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user