/* * (C) 2006, 2007 Andreas Gruenbacher * Copyright (c) 2003-2008 Novell, Inc. (All rights reserved) * Copyright 2009-2012 Canonical Ltd. * * The libapparmor library is licensed under the terms of the GNU * Lesser General Public License, version 2.1. Please see the file * COPYING.LGPL. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * * Base of implementation based on the Lexical Analysis chapter of: * Alfred V. Aho, Ravi Sethi, Jeffrey D. Ullman: * Compilers: Principles, Techniques, and Tools (The "Dragon Book"), * Addison-Wesley, 1986. */ #ifndef __LIBAA_RE_HFA_H #define __LIBAA_RE_HFA_H #include #include #include #include #include "expr-tree.h" class State; typedef map StateTrans; typedef list Partition; #include "../immunix.h" class perms_t { public: perms_t(void) throw(int): allow(0), deny(0), audit(0), quiet(0), exact(0) { }; bool is_null(void) { return !(allow | deny | audit | quiet); } void clear(void) { allow = deny = audit = quiet = 0; } void add(perms_t &rhs) { deny |= rhs.deny; if (!is_merged_x_consistent(allow & ALL_USER_EXEC, rhs.allow & ALL_USER_EXEC)) { if ((exact & AA_USER_EXEC_TYPE) && !(rhs.exact & AA_USER_EXEC_TYPE)) { /* do nothing */ } else if ((rhs.exact & AA_USER_EXEC_TYPE) && !(exact & AA_USER_EXEC_TYPE)) { allow = (allow & ~AA_USER_EXEC_TYPE) | (rhs.allow & AA_USER_EXEC_TYPE); } else throw 1; } else allow |= rhs.allow & AA_USER_EXEC_TYPE; if (!is_merged_x_consistent(allow & ALL_OTHER_EXEC, rhs.allow & ALL_OTHER_EXEC)) { if ((exact & AA_OTHER_EXEC_TYPE) && !(rhs.exact & AA_OTHER_EXEC_TYPE)) { /* do nothing */ } else if ((rhs.exact & AA_OTHER_EXEC_TYPE) && !(exact & AA_OTHER_EXEC_TYPE)) { allow = (allow & ~AA_OTHER_EXEC_TYPE) | (rhs.allow & AA_OTHER_EXEC_TYPE); } else throw 1; } else allow |= rhs.allow & AA_OTHER_EXEC_TYPE; allow = (allow | (rhs.allow & ~ALL_AA_EXEC_TYPE)) & ~deny; audit |= rhs.audit; quiet = (quiet | rhs.quiet) & deny; /* if (exec & AA_USER_EXEC_TYPE && (exec & AA_USER_EXEC_TYPE) != (allow & AA_USER_EXEC_TYPE)) throw 1; if (exec & AA_OTHER_EXEC_TYPE && (exec & AA_OTHER_EXEC_TYPE) != (allow & AA_OTHER_EXEC_TYPE)) throw 1; */ } int apply_and_clear_deny(void) { if (deny) { allow &= ~deny; quiet &= deny; deny = 0; return is_null(); } return 0; } bool operator<(perms_t const &rhs)const { if (allow < rhs.allow) return allow < rhs.allow; if (deny < rhs.deny) return deny < rhs.deny; if (audit < rhs.audit) return audit < rhs.audit; return quiet < rhs.quiet; } uint32_t allow, deny, audit, quiet, exact; }; int accept_perms(NodeSet *state, perms_t &perms); /* * hashedNodes - for efficient set comparison */ class hashedNodeSet { public: unsigned long hash; NodeSet *nodes; hashedNodeSet(NodeSet *n): nodes(n) { hash = hash_NodeSet(n); } bool operator<(hashedNodeSet const &rhs)const { if (hash == rhs.hash) { if (nodes->size() == rhs.nodes->size()) return *nodes < *(rhs.nodes); else return nodes->size() < rhs.nodes->size(); } else { return hash < rhs.hash; } } }; class hashedNodeVec { public: typedef ImportantNode ** iterator; iterator begin() { return nodes; } iterator end() { iterator t = nodes ? &nodes[len] : NULL; return t; } unsigned long hash; unsigned long len; ImportantNode **nodes; hashedNodeVec(NodeSet *n) { hash = hash_NodeSet(n); len = n->size(); nodes = new ImportantNode *[n->size()]; unsigned int j = 0; for (NodeSet::iterator i = n->begin(); i != n->end(); i++, j++) { nodes[j] = *i; } } hashedNodeVec(NodeSet *n, unsigned long h): hash(h) { len = n->size(); nodes = new ImportantNode *[n->size()]; ImportantNode **j = nodes; for (NodeSet::iterator i = n->begin(); i != n->end(); i++) { *(j++) = *i; } } ~hashedNodeVec() { delete nodes; } unsigned long size()const { return len; } bool operator<(hashedNodeVec const &rhs)const { if (hash == rhs.hash) { if (len == rhs.size()) { for (unsigned int i = 0; i < len; i++) { if (nodes[i] != rhs.nodes[i]) return nodes[i] < rhs.nodes[i]; } return false; } return len < rhs.size(); } return hash < rhs.hash; } }; class CacheStats { public: unsigned long dup, sum, max; CacheStats(void): dup(0), sum(0), max(0) { }; void clear(void) { dup = sum = max = 0; } virtual unsigned long size(void) const = 0; }; class NodeCache: public CacheStats { public: set cache; NodeCache(void): cache() { }; ~NodeCache() { clear(); }; virtual unsigned long size(void) const { return cache.size(); } void clear() { for (set::iterator i = cache.begin(); i != cache.end(); i++) { delete i->nodes; } cache.clear(); CacheStats::clear(); } NodeSet *insert(NodeSet *nodes) { if (!nodes) return NULL; pair::iterator,bool> uniq; uniq = cache.insert(hashedNodeSet(nodes)); if (uniq.second == false) { delete(nodes); dup++; } else { sum += nodes->size(); if (nodes->size() > max) max = nodes->size(); } return uniq.first->nodes; } }; struct deref_less_than { bool operator()(hashedNodeVec * const &lhs, hashedNodeVec * const &rhs)const { return *lhs < *rhs; } }; class NodeVecCache: public CacheStats { public: set cache; NodeVecCache(void): cache() { }; ~NodeVecCache() { clear(); }; virtual unsigned long size(void) const { return cache.size(); } void clear() { for (set::iterator i = cache.begin(); i != cache.end(); i++) { delete *i; } cache.clear(); CacheStats::clear(); } hashedNodeVec *insert(NodeSet *nodes) { if (!nodes) return NULL; pair::iterator,bool> uniq; hashedNodeVec *nv = new hashedNodeVec(nodes); uniq = cache.insert(nv); if (uniq.second == false) { delete nv; dup++; } else { sum += nodes->size(); if (nodes->size() > max) max = nodes->size(); } delete(nodes); return (*uniq.first); } }; /* * ProtoState - NodeSet and ancillery information used to create a state */ class ProtoState { public: hashedNodeVec *nnodes; NodeSet *anodes; ProtoState(hashedNodeVec *n, NodeSet *a = NULL): nnodes(n), anodes(a) { }; bool operator<(ProtoState const &rhs)const { if (nnodes == rhs.nnodes) return anodes < rhs.anodes; return nnodes < rhs.nnodes; } unsigned long size(void) { if (anodes) return nnodes->size() + anodes->size(); return nnodes->size(); } }; /* * State - DFA individual state information * label: a unique label to identify the state used for pretty printing * the non-matching state is setup to have label == 0 and * the start state is setup to have label == 1 * audit: the audit permission mask for the state * accept: the accept permissions for the state * trans: set of transitions from this state * otherwise: the default state for transitions not in @trans * parition: Is a temporary work variable used during dfa minimization. * it can be replaced with a map, but that is slower and uses more * memory. * nodes: Is a temporary work variable used during dfa creation. It can * be replaced by using the nodemap, but that is slower */ class State { public: State(int l, ProtoState &n, State *other) throw(int): label(l), perms(), trans() { int error; if (other) otherwise = other; else otherwise = this; proto = n; /* Compute permissions associated with the State. */ error = accept_perms(n.anodes, perms); if (error) { //cerr << "Failing on accept perms " << error << "\n"; throw error; } }; State *next(uchar c) { StateTrans::iterator i = trans.find(c); if (i != trans.end()) return i->second; return otherwise; }; int apply_and_clear_deny(void) { return perms.apply_and_clear_deny(); } int label; perms_t perms; StateTrans trans; State *otherwise; /* temp storage for State construction */ union { Partition *partition; ProtoState proto; }; }; ostream &operator<<(ostream &os, const State &state); class NodeMap: public CacheStats { public: typedef map::iterator iterator; iterator begin() { return cache.begin(); } iterator end() { return cache.end(); } map cache; NodeMap(void): cache() { }; ~NodeMap() { clear(); }; virtual unsigned long size(void) const { return cache.size(); } void clear() { cache.clear(); CacheStats::clear(); } pair insert(ProtoState &proto, State *state) { pair uniq; uniq = cache.insert(make_pair(proto, state)); if (uniq.second == false) { dup++; } else { sum += proto.size(); if (proto.size() > max) max = proto.size(); } return uniq; } }; /* Transitions in the DFA. */ class DFA { void dump_node_to_dfa(void); State *add_new_state(NodeSet *nodes, State *other); void update_state_transitions(State *state); /* temporary values used during computations */ NodeCache anodes_cache; NodeVecCache nnodes_cache; NodeMap node_map; list work_queue; public: DFA(Node *root, dfaflags_t flags); virtual ~DFA(); State *match_len(State *state, const char *str, size_t len); State *match_until(State *state, const char *str, const char term); State *match(const char *str); void remove_unreachable(dfaflags_t flags); bool same_mappings(State *s1, State *s2); size_t hash_trans(State *s); void minimize(dfaflags_t flags); int apply_and_clear_deny(void); void dump(ostream &os); void dump_dot_graph(ostream &os); void dump_uniq_perms(const char *s); map equivalence_classes(dfaflags_t flags); void apply_equivalence_classes(map &eq); Node *root; State *nonmatching, *start; Partition states; }; void dump_equivalence_classes(ostream &os, map &eq); #endif /* __LIBAA_RE_HFA_H */