apparmor/parser/libapparmor_re
John Johansen 2e77129e15 Merge parser: fix expr MatchFlag dump
Match Flags convert output to hex but don't restore after outputting
the flag resulting in following numbers being hex encoded. This
results in dumps that can be confusing eg.

rule: \d2  ->  \x2 priority=1001 (0x4/0)< 0x4>

rule: \d7  ->  \a priority=3e9 (0x4/0)< 0x4>

rule: \d10  ->  \n priority=3e9 (0x4/0)< 0x4>

rule: \d9  ->  \t priority=3e9 (0x4/0)< 0x4>

rule: \d14  ->  \xe priority=1001 (0x4/0)< 0x4>

where priority=3e9 is the hex encoded priority 1001.

Signed-off-by: John Johansen <john.johansen@canonical.com>

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1419
Approved-by: Maxime Bélair <maxime.belair@canonical.com>
Approved-by: John Johansen <john@jjmx.net>
Merged-by: John Johansen <john@jjmx.net>
2024-11-15 17:41:13 +00:00
..
aare_rules.cc Merge parser: improve libapparmor_re build and dump info 2024-11-15 00:32:30 +00:00
aare_rules.h Fix memory leak in aare_rules UniquePermsCache 2024-10-28 12:23:17 +01:00
apparmor_re.h parser: fix 16 bit state limitation 2024-08-14 17:01:30 -07:00
chfa.cc Merge parser: improve libapparmor_re build and dump info 2024-11-15 00:32:30 +00:00
chfa.h parser: fix 16 bit state limitation 2024-08-14 17:01:30 -07:00
expr-tree.cc parser: fix Normalizatin infinite loop 2024-06-14 07:00:47 -07:00
expr-tree.h parser: fix expr MatchFlag dump 2024-11-11 14:39:40 -08:00
flex-tables.h add ability to use out of band transitions 2019-11-26 21:32:08 -08:00
hfa.cc parser: add the abilitiy to dump the permissions table 2024-11-06 11:44:30 -08:00
hfa.h Merge parser: bug fix do not change auditing information when applying deny 2024-11-11 20:52:19 +00:00
Makefile parser: fix and cleanup libapparmor_re/Makefile 2024-11-06 09:20:51 -08:00
parse.h Split out parsing and expression trees from regexp.y 2011-03-13 05:46:29 -07:00
parse.y parser: fix regex parser leak on parsing failure 2023-11-23 17:37:46 -03:00
policy_compat.cc parser: fix mapping of AA_CONT_MATCH for policydb compat entries 2024-11-06 12:33:36 -08:00
policy_compat.h parser: pass rule mode prompt through to backend 2024-08-14 15:47:13 -07:00
README parser: fix 16 bit state limitation 2024-08-14 17:01:30 -07:00

apparmor_re.h - control flags for hfa generation
expr-tree.{h,cc} - abstract syntax tree (ast) built from a regex parse
parse.{h,y} - code to parse a regex into an ast
hfc.{h,cc} - code to build and manipulate a hybrid finite automata (state
             machine).
flex-tables.h - basic defines used by chfa
chfa.{h,cc} - code to build a highly compressed runtime readonly version
              of an hfa.
aare_rules.{h,cc} - code to that binds parse -> expr-tree -> hfa generation
                    -> chfa generation into a basic interface for converting
		    rules to a runtime ready state machine.

Notes on the compress hfa file format (chfa)
==============================================

The file format used is based on the GNU flex table file format
(--tables-file option; see Table File Format in the flex info pages and
the flex sources for documentation). The magic number used in the header
is set to 0x1B5E783D instead of 0xF13C57B1 though, which is meant to
indicate that the file format logically is not the same: the YY_ID_CHK
(check) and YY_ID_DEF (default), YY_ID_BASE tables are used differently.

The YY_ID_ACCEPTX tables either encode permissions directly, or are an
index, into an external tables.

There are two DFA table formats to support different size state machines
DFA16
  default/next/check - are 16 bit tables
DFA32
  default/next/check - are 32 bit tables

  DFA32 is limited to 2^24 states, due to the upper 8 bits being used
  as flags in the base table, unless the flags table is defined. When
  the flags table is defined, DFA32 can have a full 2^32 states.

In both DFA16 and DFA32
   base and accept are 32 bit tables.

State 0 is always used as the trap state. Its accept, base and default
fields should be 0.

State 1 is the default start state. Alternate start states are stored
external to the state machine.

If the flags table is not defined, the base table uses the lower 24
bits as index into the next/check tables, and the upper 8 bits are used
as flags.

The currently defined flags are
#define MATCH_FLAG_DIFF_ENCODE 0x80000000
#define MARK_DIFF_ENCODE 0x40000000
#define MATCH_FLAG_OOB_TRANSITION 0x20000000

Note the default[state] is used in two different ways.

1. When diff_encode is set, the state stores the difference to another
   state defined by default. The next field will only store the
   transitions that are unique to this state. Those transition may mask
   transitions in the state that the current state is relative to, also
   note the state that this state is relative might also be relative to
   another state. Cycles are forbidden and checked for by the verifier.
   The exact algorithm used to build these state difference will be
   discussed in another section.


States and transitions on specific characters to next states
------------------------------------------------------------
 1: ('a' => 2, 'b' => 3, 'c' => 4)
 2: ('a' => 2, 'b' => 3, 'd' => 5)

Table format - where D in base represnts Diff encode flag
----------------------
index: (default, base)
    0: (      0,    0)  <== dummy state (nonmatching)
    1: (      0,    0)
    2: (      1, D  256)

  index: (next, check)
      0: (   0,     0)  <== unused entry
	 (   0,     1)  <== ord('a') identical entries
  0+'a': (   2,     1)
  0+'b': (   3,     1)
  0+'c': (   4,     1)
	 (   0,     1)  <== (255 - ord('c')) identical entries
256+'c': (   0,     2)
256+'d': (   5,     2)

Here, state 2 is described as ('c' => 0, 'd' => 5), and everything else
as in state 1. The matching algorithm is as follows.

Scanner algorithm
---------------------------
  /* current state is in <state>, input character <c> */

  while (check[base[state] + c] != state) {
      diff = (FLAGS(base) & diff_encode);
      state = default[state];
      if (!diff)
         goto done;
  }
  state = next[base[state] + c];
  done:

  /* continue with the next input character */

2. When diff_encode is NOT set, the default state is used to represent
   all none matching transitions (ie. check[base[state] + c] != state).
   The dfa build will compute the transition with the most transitions
   and use that for the default state. ie.

   if we have
       1: ('a' => 2)
          ("[^a]" => 0)
   then 0 will be used as the default state

   if we have
       1: ("[^a]" => 2)
          ('a' => 0)
   then 2 will be used as the default state, and the only state encoded
   in the next/check tables will be for 'a'

The combination of the diff-encoded and non-diff encoded states performs
well even when there are many inverted or wildcard matches ("[^x]", ".").


Simplified Regexp scanner algorithm for non-diff encoded state (note
diff encode algorithm above works as well)

------------------------
  /* current state is in <state>, matching character <c> */
  if (check[base[state] + c] == state)
    state = next[base[state] + c];
  else
    state = default[state];
  /* continue with the next input character */


Each input character may cause several iterations in the while loop,
but due to guarantees in the build at most 2n states will be
transitioned for n input characters.  The expected number of states
walked is much closer to n and in practice due to cache locality the
diff encoded state machine is usually faster than a non-diff encoded
state machine with a strict n state for n input walk.


Comb Compression
-----------------

The next/check tables of states are only used to encode transitions
not covered by the default transition. The input byte is indexed off
the base value, covering 256 positions within the next/check
tables. However a state may only encode a few transitions within that
range, leaving holes.  These holes are filled by other states
transitions whose range will overlap.

   1: ('a' => 2, 'b' => 3, 'c' => 4)
   2: ('a' => 2, 'b' => 3, 'd' => 5)
   3: ('a' => 0, everything else => 5)

Regexp tables
-------------
index: (default, base)
    0: (      0,    0)  <== dummy state (nonmatching)
    1: (      0,    0)
    2: (      1,    3)
    3: (      5,    7)

  index: (next, check)
      0: (   0,     0)  <== unused entry
	 (   0,     0)  <== ord('a') identical, unused entries
  0+'a': (   2,     1)
  0+'b': (   3,     1)
  0+'c': (   4,     1)
  3+'a': (   2,     2)
  3+'b': (   3,     2)
  3+'c': (   0,     0)  <== entry is unused, hole that could be filled
  3+'d': (   5,     2)
  7+'a': (   0,     3)
	 (   0,     0)  <== (255 - ord('a')) identical, unused entries


Regexp tables comb compressed
-------------
index: (default, base)
    0: (      0,    0)
    1: (      0,    0)
    2: (      1,    3)
    3: (      5,    5)

  index: (next, check)
      0: (   0,     0)
	 (   0,     0)
  0+'a': (   2,     1)
  0+'b': (   3,     1)
  0+'c': (   4,     1)
  3+'a': (   2,     2)
  3+'b': (   3,     2)
  5+'a': (   0,     3)  <== entry was previously at 7+'a'
  3+'d': (   5,     2)
	 (   0,     0)  <== (255 - ord('a')) identical, unused entries


Out of Band Transitions (oobs)
---------------------------------

Out of band transitions (oobs) allow for a state to have transitions
that can not be triggered by input. Any state that has oobs must have
the OOB flag set on the state. An oob is triggered by subtracting the
oob number from the the base index value, to find the next and check
value. Current only single oob is supported. And all states using
an oob must have the oob flag set.

  if ((FLAG(base) & OOB) && check[base[state] - oob] == state)
    state = next[base[state]] - oob]

oobs might be expressed as a negative number eg. -1 for the first
oob. In which case the oob transition above uses a + oob instead.

If more oobs are needed a second oob flag can be allocated, and if
used in combination with the original, would allow a state to have
up to 3 oobs

  00 - none
  01 - 1
  10 - 2
  11 - 3


Diff Encode Spanning Tree
============================================
To build the state machine with diff encoded states and to still meet
run time guaratees about traversing no more than 2n states for n input
a spanning tree is use.

* TODO *