mirror of
https://gitlab.com/apparmor/apparmor.git
synced 2025-03-04 08:24:42 +01:00

speedup and reduce memory usage of dfa generation A variety of changes to improve dfa generation - By switching to Nodevec instead of Node sets we can reduce memory usage slightly and reduce code - By using charsets for chars we reduce code and increase chances of node merging/reduction which reduces memory usage slightly - By merging charsets we reduce the number of nodes Signed-off-by: John Johansen <john.johansen@canonical.com> MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1066 Approved-by: John Johansen <john@jjmx.net> Merged-by: John Johansen <john@jjmx.net>
357 lines
8.9 KiB
C++
357 lines
8.9 KiB
C++
/*
|
|
* (C) 2006, 2007 Andreas Gruenbacher <agruen@suse.de>
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
*
|
|
* 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 <list>
|
|
#include <map>
|
|
#include <vector>
|
|
|
|
#include <assert.h>
|
|
#include <stdint.h>
|
|
|
|
#include "expr-tree.h"
|
|
|
|
#define DiffEncodeFlag 1
|
|
|
|
class State;
|
|
|
|
typedef map<transchar, State *> StateTrans;
|
|
typedef list<State *> Partition;
|
|
|
|
#include "../immunix.h"
|
|
|
|
ostream &operator<<(ostream &os, const State &state);
|
|
ostream &operator<<(ostream &os, State &state);
|
|
|
|
class perms_t {
|
|
public:
|
|
perms_t(void): allow(0), deny(0), audit(0), quiet(0), exact(0) { };
|
|
|
|
bool is_accept(void) { return (allow | audit | quiet); }
|
|
|
|
void dump(ostream &os)
|
|
{
|
|
os << " (0x " << hex
|
|
<< allow << "/" << deny << "/" << audit << "/" << quiet
|
|
<< ')' << dec;
|
|
}
|
|
|
|
void clear(void) { allow = deny = audit = quiet = 0; }
|
|
void add(perms_t &rhs, bool filedfa)
|
|
{
|
|
deny |= rhs.deny;
|
|
|
|
if (filedfa && !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 if (filedfa)
|
|
allow |= rhs.allow & AA_USER_EXEC_TYPE;
|
|
|
|
if (filedfa && !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 if (filedfa)
|
|
allow |= rhs.allow & AA_OTHER_EXEC_TYPE;
|
|
|
|
if (filedfa)
|
|
allow = (allow | (rhs.allow & ~ALL_AA_EXEC_TYPE));
|
|
else
|
|
allow |= rhs.allow;
|
|
audit |= rhs.audit;
|
|
quiet = (quiet | rhs.quiet);
|
|
|
|
/*
|
|
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_accept();
|
|
}
|
|
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(NodeVec *state, perms_t &perms, bool filedfa);
|
|
|
|
/*
|
|
* ProtoState - NodeSet and ancillery information used to create a state
|
|
*/
|
|
class ProtoState {
|
|
public:
|
|
NodeVec *nnodes;
|
|
NodeVec *anodes;
|
|
|
|
/* init is used instead of a constructor because ProtoState is used
|
|
* in a union
|
|
*/
|
|
void init(NodeVec *n, NodeVec *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();
|
|
}
|
|
};
|
|
|
|
/* Temporary state structure used when building differential encoding
|
|
* @parents - set of states that have transitions to this state
|
|
* @depth - level in the DAG
|
|
* @state - back reference to state this DAG entry belongs
|
|
* @rel - state that this state is relative to for differential encoding
|
|
*/
|
|
struct DiffDag {
|
|
Partition parents;
|
|
int depth;
|
|
State *state;
|
|
State *rel;
|
|
};
|
|
|
|
/*
|
|
* 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
|
|
* partition: Is a temporary work variable used during dfa minimization.
|
|
* it can be replaced with a map, but that is slower and uses more
|
|
* memory.
|
|
* proto: 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, bool filedfa):
|
|
label(l), flags(0), 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, filedfa);
|
|
if (error) {
|
|
//cerr << "Failing on accept perms " << error << "\n";
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
State *next(transchar c) {
|
|
State *state = this;
|
|
do {
|
|
StateTrans::iterator i = state->trans.find(c);
|
|
if (i != state->trans.end())
|
|
return i->second;
|
|
|
|
if (!(state->flags & DiffEncodeFlag))
|
|
return state->otherwise;
|
|
state = state->otherwise;
|
|
} while (state);
|
|
|
|
/* never reached */
|
|
assert(0);
|
|
return NULL;
|
|
}
|
|
|
|
ostream &dump(ostream &os)
|
|
{
|
|
cerr << *this << "\n";
|
|
for (StateTrans::iterator i = trans.begin(); i != trans.end(); i++) {
|
|
os << " " << i->first.c << " -> " << *i->second << "\n";
|
|
}
|
|
return os;
|
|
}
|
|
|
|
int diff_weight(State *rel, int max_range, int upper_bound);
|
|
int make_relative(State *rel, int upper_bound);
|
|
void flatten_relative(State *, int upper_bound);
|
|
|
|
int apply_and_clear_deny(void) { return perms.apply_and_clear_deny(); }
|
|
|
|
int label;
|
|
int flags;
|
|
perms_t perms;
|
|
StateTrans trans;
|
|
State *otherwise;
|
|
|
|
/* temp storage for State construction */
|
|
union {
|
|
Partition *partition; /* used during minimization */
|
|
ProtoState proto; /* used during creation */
|
|
DiffDag *diff; /* used during diff encoding */
|
|
};
|
|
};
|
|
|
|
class NodeMap: public CacheStats
|
|
{
|
|
public:
|
|
typedef map<ProtoState, State *>::iterator iterator;
|
|
iterator begin() { return cache.begin(); }
|
|
iterator end() { return cache.end(); }
|
|
|
|
map<ProtoState, State *> cache;
|
|
|
|
NodeMap(void): cache() { };
|
|
~NodeMap() { clear(); };
|
|
|
|
virtual unsigned long size(void) const { return cache.size(); }
|
|
|
|
void clear()
|
|
{
|
|
cache.clear();
|
|
CacheStats::clear();
|
|
}
|
|
|
|
pair<iterator,bool> insert(ProtoState &proto, State *state)
|
|
{
|
|
pair<iterator,bool> 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);
|
|
State *add_new_state(NodeSet *anodes, NodeSet *nnodes, State *other);
|
|
void update_state_transitions(State *state);
|
|
void process_work_queue(const char *header, optflags const &);
|
|
void dump_diff_chain(ostream &os, map<State *, Partition> &relmap,
|
|
Partition &chain, State *state,
|
|
unsigned int &count, unsigned int &total,
|
|
unsigned int &max);
|
|
|
|
/* temporary values used during computations */
|
|
NodeVecCache anodes_cache;
|
|
NodeVecCache nnodes_cache;
|
|
NodeMap node_map;
|
|
list<State *> work_queue;
|
|
|
|
public:
|
|
DFA(Node *root, optflags const &flags, bool filedfa);
|
|
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(optflags const &flags);
|
|
bool same_mappings(State *s1, State *s2);
|
|
void minimize(optflags const &flags);
|
|
int apply_and_clear_deny(void);
|
|
|
|
void diff_encode(optflags const &flags);
|
|
void undiff_encode(void);
|
|
void dump_diff_encode(ostream &os);
|
|
|
|
void dump(ostream &os);
|
|
void dump_dot_graph(ostream &os);
|
|
void dump_uniq_perms(const char *s);
|
|
|
|
map<transchar, transchar> equivalence_classes(optflags const &flags);
|
|
void apply_equivalence_classes(map<transchar, transchar> &eq);
|
|
|
|
unsigned int diffcount;
|
|
int oob_range;
|
|
int max_range;
|
|
int ord_range;
|
|
int upper_bound;
|
|
Node *root;
|
|
State *nonmatching, *start;
|
|
Partition states;
|
|
bool filedfa;
|
|
};
|
|
|
|
void dump_equivalence_classes(ostream &os, map<transchar, transchar> &eq);
|
|
|
|
#endif /* __LIBAA_RE_HFA_H */
|