2
0
mirror of https://gitlab.com/apparmor/apparmor synced 2025-08-31 14:25:52 +00:00

Compare commits

...

186 Commits

Author SHA1 Message Date
John Johansen
68297d9398 Fix change_profile to grant access to api
http://bugs.launchpad.net/bugs/979135

Currently a change_profile rule does not grant access to the
/proc/<pid>/attr/{current,exec} interfaces that are needed to perform
a change_profile or change_onexec, requiring that an explicit rule allowing
access to the interface be granted.

Make it so change_profile implies the necessary
  /proc/@{PID}/attr/{current,exec} w,

rule just like the presence of hats does for change_hat


Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-04-11 16:04:33 -07:00
John Johansen
6f27ba3abb Fix protocol error when loading policy to kernels without compat patches
http://bugs.launchpad.net/bugs/968956

The parser is incorrectly generating network rules for kernels that can
not support them.  This occurs on kernels with the new features directory
but not the compatibility patches applied.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-04-11 16:03:21 -07:00
John Johansen
7afa066be3 Fix change_onexec for profiles without attachment specification
This fix is needed for the userspace portion of both 
BugLink: http://bugs.launchpad.net/bugs/963756
BugLink: http://bugs.launchpad.net/bugs/978038

change_onexec fails for profiles that don't have an attachment specification
  eg. unconfined

This is because change_onexec goes through 2 permission checks.  The first
at the api call point, which is a straight match of the profile name

  eg.
    /bin/foo
    unconfined

and a second test at exec time, tying the profile to change to to the
exec.  This allows restricting the transition to specific execs.  This
is mapped as a two entry check

  /executable/name\x00profile_name

where the executable name must be marked with the change_onexec permission
and the subsequent profile name as well.

The previous "fix" only covered adding onexec to executable names and
also works for the initial change_onexec request when the profile is
an executable.

However it does not fix the case for when the profile being transitioned
to is not an executable.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-04-11 16:02:13 -07:00
John Johansen
562eb63964 expand automated profile generation to to allow profile generation from stdin
This extends the auto-profile generation so that it can take profiles formated
in standard profile language augemented by a few special variables for
the automatically generated rules.  This will all extended the regression
tests in ways that are not currently supported, because mkprofile format
does not match of the profile language.

the special apparmorish variables are
@{gen_elf name} - generate rules for elf binaries
@{gen_bin name} - generate rules for a binary
@{gen_def} - generate default rules
@{gen name} - do @{gen_def} @{gen_bin name}

To generate a profile you do

genprofile --stdin <<EOF
/profile/name {
@{gen /profile/name}
}
EOF

eg. to generate the equivalent of
  genprofile
you would do
  genprofile --stdin <<EOF
  $test {
  @{gen $test}
  }
EOF

and the equiv of
  genprofile $file:rw
would be
  genprofile --stdin <<EOF
  $test {
  @{gen $test}
  $file rw,
  }


while it takes a little more to generate a base profile than the old syntax, it
use the actual profile language (augmented with the special variables), it is a
lot more flexible, and a lot easier to expand when new rule types are added.

eg. of something not possible with the current auto generation
    Generate a profile with a child profile and hat and a trailing profile

genprofile --stdin <<EOF
$test {
@{gen $test}

  profile $bin/open {
@{gen $bin/open}
  }

  ^hatfoo {
     $file rw,
  }
}
profile $bin/exec {
@{gen $bin/exec}
}
EOF

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-04-11 15:55:54 -07:00
Jamie Strandboge
852907e1cc clarifications for mount rules
Acked-By: Jamie Strandboge <jamie@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
Acked-by: John Johansen <john.johansen@canonical.com>
2012-04-11 16:34:22 -05:00
Jamie Strandboge
50aa2335eb remove unintended comma from parser/apparmor.d.pod
Acked-By: Jamie Strandboge <jamie@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-04-11 11:53:16 -05:00
Jamie Strandboge
3ff29d2e4b Attached is a patch to add --stderr to the common rules for generating
manpages (and adjust it so that it's one rule instead of eight). It
also fixes the above problem and a similar problem in the aa-exec
manpage.

Acked-By: Steve Beattie <sbeattie@ubuntu.com>
Acked-By: Jamie Strandboge <jamie@canonical.com>
2012-04-11 11:16:47 -05:00
Jamie Strandboge
24e46508d5 parser/apparmor.d.pod: add mount rule syntax and usage. Refinements and
clarifications thanks to Steve Beattie.

Acked-By: Jamie Strandboge <jamie@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-04-11 11:10:29 -05:00
Steve Beattie
f7ce93b27c libapparmor: add support for ip addresses and ports
Bugs: https://bugs.launchpad.net/ubuntu/+source/apparmor/+bug/800826
  https://bugzilla.novell.com/show_bug.cgi?id=755923

This patch modifies the libapparmor log parsing code to add support
for the additional ip address and port keywords that can occur in
network rejection rules. The laddr and faddr keywords stand for local
address and foreign address respectively.

The regex used to match an ip address is not very strict, to hopefully
catch the formats that the kernel emits for ipv6 addresses; however,
because this is in a context triggered by the addr keywords, it should
not over-eagerly consume non-ip addresses. Said addresses are returned
as strings in the struct to be processed by the calling application.

Bug: https://launchpad.net/bugs/800826
2012-04-06 15:59:04 -07:00
Christian Boltz
f67168cf2d the usr.lib.dovecot.imap-login profile should allow inet6 in addition to inet
References: https://bugzilla.novell.com/show_bug.cgi?id=755923 

Acked-By: Jamie Strandboge <jamie@canonical.com>
2012-04-05 23:51:17 +02:00
Steve Beattie
c80254eb3f Restructure the apparmor.vim creation script a bit to do a bit of
re-use and to structure things to make understanding clearer.
2012-04-05 14:39:57 -07:00
Steve Beattie
01fe7f42a0 Subject: call autodep when creating a child profile
This patch calls autodep on the 'exec'ed binary when the user selects
to place that execution in a child profile. Previously, logprof would
create an entirely empty child profile in complain mode (this fix
still leaves the child profile in complain mode).
2012-03-27 17:21:22 -07:00
Steve Beattie
f37f59f47b Subject: fix autodep profile construction
This patch fixes a couple of issue with autodep:

  1) The initial profile construction had not been adjusted to include
     the 'allow' or 'deny' hash prefixing the path elements. This
     fixes it by eliminating the path portion entirely and pushing
     the path based accesses to the later analysis section of code.

  2) the mode of the original binary was accidentally getting reset
     to 0, when it was intended to initialize the audit field to 0.
2012-03-27 17:18:44 -07:00
Steve Beattie
521b237e8b Subject: autodep - add bash abstraction when using dash script
On Ubuntu and Debian, by default /bin/sh is a symlink to /bin/dash. When
autodep'ing a shell script, the bash abstraction was not being included.
2012-03-27 17:17:25 -07:00
Steve Beattie
daa5b9f496 Subject: aa-logprof - fix handling of 'exec' events (LP: #872446)
Bug: https://bugs.launchpad.net/ubuntu/+source/apparmor/+bug/872446

Due to logging changes for 'exec' events, 'exec' events in aa-logprof
were being skipped when a profile is in enforcing mode. This patch
addresses the issue.

Bug: https://launchpad.net/bugs/872446
2012-03-27 17:15:50 -07:00
John Johansen
18ddf78dbe Make mount operations aware of 'in' keyword so they can affect the flags build list
Bug #959560 - part 2/3 of fix

Signed-off-by: John Johansen <john.johansen@canonical.com>
2012-03-26 06:19:21 -07:00
John Johansen
3356dc4edd Update the parser to support the 'in' keyword for value lists
Bug #959560 Part 1/3 of fix

Signed-off-by: John Johansen <john.johansen@canonical.com>
2012-03-26 06:17:40 -07:00
John Johansen
c1722cdfdb Fix permission mapping for change_profile onexec
Bug #963756

The kernel has an extended test for change_profile when used with
onexec, that allows it to only work against set executables.

The parser is not correctly mapping change_profile for this test
update the mapping so change_onexec will work when confined.

Note: the parser does not currently support the extended syntax
that the kernel test allows for, this just enables it to work
for the generic case.

Signed-off-by: John Johansen <john.johansen@canonical.com>
2012-03-26 06:11:16 -07:00
John Johansen
5c09f44f8b Fix the changehat_wrapper regression test
The capabilities tests where failing in the changehat_wrapper test.  This was because
they could not the changehat_wrapper sub executable, which trying to exec a binary
in the tmpdir.

Specifically if the test was for syscall_ptrace.  It would generate a profile with
a hat for ^syscall_ptrace and attempt to execute ./syscall_ptrace.  However this
was failing in some situations, including when trying to debug from the tmpdir,
as the syscall_XXX binary is no longer local.

Instead use the fully qualified path for the hat name, and the exec path.

Signed-off-by: John Johansen <john.johansen@canonical.com>
2012-03-26 06:10:18 -07:00
John Johansen
40588d182a Modifify regression test infrastructure to stop on failure when retainingtmpdir
The retaining of the tmpdir is used during debugging of test failures, but currently
when a test fails, the next test is run overwritting the previous tmpdir value. This
is a problem even when manually running individual test shell scripts if the failure
is not the last test in the script.

Instead cause testing to about when retaintmpdir is true, which will cover the debugging
needs for the majority of failure cases.

Signed-off-by: John Johansen <john.johansen@canonical.com>
2012-03-26 06:09:04 -07:00
Jamie Strandboge
83ead1217f clean up utils/vim/common symlink on clean
Acked-By: Jamie Strandboge <jamie@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-03-23 11:02:20 -05:00
Jamie Strandboge
4a89f974f6 utils/aa-exec: update copyright year to be 2011-2012 since it was committed
in 2012
2012-03-22 18:07:07 -05:00
Jamie Strandboge
93308e4a29 Use linux/capability.h instead of sys/capability.h
Acked-By: Jamie Strandboge <jamie@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-03-22 16:41:08 -05:00
John Johansen
593cb59d38 bump revision and set tag for apparmor_2.8-beta4 2012-03-22 13:29:46 -07:00
Steve Beattie
1439d006cd Subject: add apparmor.vim install target to utils/ install
This patch adds a make install target for the generated apparmor.vim
file, installing by default into /usr/share/apparmor based on IRC
discussions; alternate suggestions welcome. (Installing directly
into the vim syntax tree is difficult as the system path by default
contains the vim version number.)
2012-03-22 13:27:29 -07:00
Steve Beattie
b4feb99841 Subject: rewrite apparmor.vim generation and integrate into build
This patch replaces the apparmor.vim generating script with a python
version that eliminates the need for using the replace tool from the
mysql-server package. It makes use of the automatically generated
lists of capabilities and network protocols provided by the build
infrastructure. I did not capture all the notes and TODOs that
Christian had in the shell script; I can do so if desired.

It also hooks the generation of the apparmor.vim file into the utils/
build and clean stages.
2012-03-22 13:26:20 -07:00
Steve Beattie
63c43ae9f5 Subject: add missing capabilities to severity.db
This patch adds several missing capabilities to the utils/
severity.db file as detected by the newly added make check target,
along with corresponding severity levels that I believe :re appropriate
(discussion welcome):

  CAP_MAC_ADMIN 10
  CAP_MAC_OVERRIDE 10
  CAP_SETFCAP 9
  CAP_SYSLOG 8
  CAP_WAKE_ALARM 8

The latter two are undocumented in the capabilities(7) man page
provided in Ubuntu 12.04; the syslog one is the separation out of
accessing the dmesg buffer from CAP_SYSADMIN, and the CAP_WAKE_ALARM
allows setting alarms that would wake a system from a suspended state,
if my reading is correct.

This also fixes a trailing whitespace on CAP_CHOWN, moves
CAP_DAC_READ_SEARCH to the end of the section of capabilities it's
in due to its lower priority level (7).
2012-03-22 13:24:12 -07:00
Steve Beattie
a31e1349ce Subject: utils/: add check to ensure severity.db contains all
capabilities

This patch adds a new make target, check_severity_db, to the
utils/Makefile. It greps the severity.db for the presence of each
capability, as computed by the newly abstracted out variable in
common/Make.rules, and issues a build time error if it finds any
missing.

It also silences the check targets, so that only the output from them
will be emitted.
2012-03-22 13:23:19 -07:00
John Johansen
f4240fcc74 Rename and invert logic of is_null to is_accept to better reflect its use
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-03-22 13:21:55 -07:00
Steve Beattie
8eaeb44f56 Subject: abstract out cap and net proto generation to common/Make.rules
This patch abstracts out the generation of the lists of capabilities
and network protocol names to the common Make.rules file that is
included in most locations in the build tree, to allow it to be
re-used in the utils/ tree and possibly elsewhere.

It provides the lists in both make variables and as make targets.

It also sorts the resulting lists, which causes it to output differently
than the before case. I did confirm that the results for the generated
files used in the parser build were the same after taking the sorting
into account.
2012-03-22 13:19:27 -07:00
Steve Beattie
bfc1032fc1 Subject: toplevel makefile: correct location of libapparmor
This patch fixes an issue with the toplevel make clean target that did
not take into account where the libapparmor tree had been moved to.
2012-03-22 13:17:48 -07:00
Jamie Strandboge
65f90c0942 fix distro-specific apparmor.vim man page
Acked-By: Jamie Strandboge <jamie@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2012-03-22 15:15:20 -05:00
John Johansen
4fcd1f33dc Fix aa-exec file mode to be 751 so that it can be exec'd 2012-03-22 12:52:58 -07:00
John Johansen
86527a2f4c Fix the return size of aa_getprocattr
aa_getprocattr is returning the size of the buffer not the size of the
data read that it is supposed to return.  Also update the man page to
reflect the return value as documented in the functions, and update
the test cases to check the return value.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Steve Beattie <sbeattie@ubuntu.com>
2012-03-22 07:58:18 -07:00
John Johansen
648166ecca Fix error case of aa_getprocattr to set buffers to NULL
While aa_getprocattr does return the documented error code on failure
the **buf and **mode parameters can point into the buffer that was
allocated and then discarded on failure.

Set them to null on failure so that even if the error code is ignored
they do not point to heap data.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-03-22 07:57:18 -07:00
John Johansen
2e3b5ff134 Fix mnt_flags passed for remount
Remount should not be screening off the set of flags it is.  They are
the set of flags that the kernel is masking out for make_type and
should not be used on remount. Instead just screen off the other cmds
that can have their own rules generated.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-03-22 07:55:58 -07:00
John Johansen
3c9cdfb841 rework the is_null test to not include deny
The deny information is not used as valid accept state information,
so remove it from the is_null test.  This does not change the dfa
generated but does result in the dumped information changing,
as states that don't have any accept information are no longer
reported as accepting. This is what changes the number of states
reported in the minimize tests.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-03-22 07:55:00 -07:00
John Johansen
e7f6e0f9f1 Fix dfa minimization around the nonmatching state
The same mappings routine had two bugs in it, that in practice haven't
manifested because of partition ordering during minimization.  The
result is that some states may fail comparison and split, resulting
in them not being eliminated when they could be.

The first is that direct comparison to the nonmatching state should
not be done as it is a candiate for elimination, instead its partion
should be compared against.  This simplifies the first test


The other error is the comparison
  if (rep->otherwise != nonmatching)

again this is wrong because nomatching should not be directly
compared against.  And again can result in the current rep->otherwise
not being eliminated/replaced by the partion.  Again resulting in
extra trap states.

These tests where original done the way they were because
 ->otherwise could be null, which was used to represent nonmatching.
The code was cleaned up a while ago to remove this, ->otherwise is
always a valid pointer now.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-03-22 07:50:35 -07:00
John Johansen
7fcbd543d7 Factor all the permissions dump code into a single perms method
Also make sure the perms method properly switches to hex and back to dec
as some of the previous perm dump code did not.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-03-22 07:49:43 -07:00
John Johansen
2f603cc73e Add the aa-exec command line utility
The aa-exec command can be used to launch an application under a specified
confinement, which may be different for what regular profile attachment
would apply.

Signed-off-by: John Johansen <john.johansen@canonical.com>
2012-03-20 11:45:13 -07:00
Steve Beattie
69dc13efdf This patch adds testcases that confirm that using a bare
file,

rule will allow access to both the '/' directory and other directories.
2012-03-15 16:46:50 -07:00
John Johansen
456220db56 Bump revision and tag for 2.8-beta3 2012-03-15 12:57:13 -07:00
John Johansen
c50858a877 Update permission mapping for changes made to the upstream kernel patch.
The changes are around how user data is handled.

1. permissions are mapped before data is matched
2. If data is to be mapped a AA_CONT_MATCH flag is set in the permissions
   which allows data matching to continue.
3. If data auditing is to occur the AA_AUDIT_MNT_DATA flag is set

This allows better control over matching and auditing of data which can
be binary and should not be matched or audited

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-03-15 12:54:34 -07:00
John Johansen
a11efe838a Fix the bare file rule so that it grants access to to root
file, should grant access to all files paths on the system but it does
not currently allow access to /

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-03-15 12:16:56 -07:00
John Johansen
d6dc04d737 Fix pivot_root to support named transitions correctly
Rename the pivotroot rule to pivot_root to match the command and the fn
and fix it to support named transition correctly leveraging the parsing
action used for exec transitions.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-03-15 12:14:15 -07:00
John Johansen
feeea88a58 Fix the case where no flags match
Currently the backend doesn't like it (blows up) when the a vector entry is
empty.  For the case where no flags match build_mnt_flags generates an
alternation of an impossible entry and nothing

  (impossible|)

This provides the effect of a null entry without having an empty vector
entry.  Unfortunately the impossible entry is not correct.

Note: how this is done needs to be changed and fixed in the next release
this is just a minimal patch to get it working for 2.8


Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-03-15 12:10:35 -07:00
John Johansen
36d44a3b25 Fix the mount flags set generated by the parser
When generating the flag set the parser was not generating the complete
set when flags where not consecutive.  This is because the len value
was not being reset for each flag considered, so once it was set for
a flag, then the next flag would have to be set to reset it else the
output string was still incremented by the old len value.

  Eg.
  echo "/t { mount options=rbind, }" | apparmor_parser -QT -D rule-exprs

  results in
  rule: \x07[^\000]*\x00[^\000]*\x00[^\000]*\x00\x0d  ->

  however \x0d only covers the bind and not the recursive flag

This is fixed by adding a continue to the flags generation loop for the
else case.

  resulting the dump from above generating

  rule: \x07[^\000]*\x00[^\000]*\x00[^\000]*\x00\x0d\x0f  ->

  \x0d\x0f covers both of the required flags

Also fix the flags output to allow for the allow any flags case.  This
was being screened out.  By masking the flags even when no flags where
specified.

  this results in a difference of

  echo "/t { mount, }" | apparmor_parser -QT -D rule-exprs

    rule: \x07[^\000]*\x00[^\000]*\x00[^\000]*\x00(\x01|)(\x02|)(\x03|)(\x04|)(\x05|)\x00[^\000]*

  becoming
    \x07[^\000]*\x00[^\000]*\x00[^\000]*\x00[^\000]*\x00[^\000]*

  which is simplified and covers all permissions vs. the first rule output

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-03-15 09:03:48 -07:00
John Johansen
fc5f4dc86f Revert commit: -r 1955 Default profiles to be chroot relative
This commit causes policy problems because we do not have chroot rules
and policy extension to support it.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-03-15 08:59:56 -07:00
John Johansen
59c0bb0f46 Fix minimize.sh test to screen out more parser error messages by grepping
closer to the expected -O dfa-states output
2012-03-09 06:48:03 -08:00
John Johansen
fae11e12cf Mark the minimize test as executable 2012-03-09 05:54:54 -08:00
John Johansen
e0a74881bf Bump version for 2.8-beta2 2012-03-09 04:44:37 -08:00
John Johansen
ce38857061 tag apparmor_2.8-beta2 2012-03-09 04:27:47 -08:00
John Johansen
c8e134930f Fix the "Kernel features are written to cache:" test
the cache test is failing because it assumes that kernel features are
stored in a file instead of a directory

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-03-09 04:25:03 -08:00
John Johansen
3876299fa0 Fix caching when used with a newer kernel with the feature directory
On newer kernels the features directory causes the creation of a
cache/.feature file that contains newline characters.  This causes the
feature comparison to fail, because get_flags_string() uses fgets
which stop reading in the feature file after the first newline.

This caches the features comparision to compare a single line of the
file against the full kernel feature directory resulting in caching
failure.

Worse this also means the cache won't get updated as the parser doesn't
change what set gets caches after the .feature file gets created.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-03-09 04:24:20 -08:00
John Johansen
b0b2bde160 Fix permissions attached to the bare file keyword
file,

was not given the correct permissions.  It was only being given the owner
set of permissions.  This would result in rejects when trying look at
files owned by other users

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-03-09 04:23:25 -08:00
John Johansen
3a1b7bb54c Fix infinite loop bug in normalization.
There are some rare occassions, when lots of alternations are used that
tree simplification can result in an expression of
  (E | (E | E)) or (E . (E . E))   where E is the epsnode

both of these expressions will lead to an inifinite loop in normalize_tree
as the epsnode test
       if ((&epsnode == t->child[dir]) &&
       	        (&epsnode != t->child[!dir]) &&
		      	         dynamic_cast<TwoChildNode *>(t)) {

and the tree node rotation test
    	} else if ((dynamic_cast<AltNode *>(t) &&
	           dynamic_cast<AltNode *>(t->child[dir])) ||
		   			   (dynamic_cast<CatNode *>(t) &&
					   			    dynamic_cast<CatNode *>(t->child[dir]))) {

end up undoing each others work, ie.

                eps flip                 rotate
  (E | (E | E)) --------> ((E | E) | E) -------> (E | (E | E))

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-03-09 04:22:42 -08:00
John Johansen
04ef92ca94 Fix a couple build warnings in mount.c
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-03-09 04:21:54 -08:00
John Johansen
d7a6860a23 Fix Make file for mount.c so that warnings are emitted during a build
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-03-09 04:21:06 -08:00
John Johansen
5e361a4a05 Fix dfa minimization to deal with exec conflicts
Minimization was failing because it was too agressive.  It was minimizing
as if there was only 1 accept condition.  This allowed it to remove more
states but at the cost of loosing unique permission sets, they where
being combined into single commulative perms.  This means that audit,
deny, xtrans, ... info on one path would be applied to all other paths
that it was combined with during minimization.

This means that we need to retain the unique accept states, not allowing
them to be combined into a single state.  To do this we put each unique
permission set into its own partition at the start of minimization.

The states within a partition have the  same permissions and can be combined
within the other states in the partition as the loss of unique path
information is will not result in a conflict.

This is similar to what perm hashing used to do but deny information is
still being correctly applied and carried.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-03-09 04:20:19 -08:00
John Johansen
cf5f7ef9c2 Fix the x intersection consistency test
The in x intersection consistency test for minimization was failing because
it was screening off the AA_MAY_EXEC permission before passing the exec
information to the consistency test fn.  This resulted in the consistency
test fn not testing the consistency because it treated the permission set
as not having x permissions.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-03-09 04:19:24 -08:00
John Johansen
811d8aefa3 Fix transition character reporting of dfa dumps
Make them report a hex value strings instead of the default C++
\vvvvv

Make them consistent,
- Dump to report the default transition and what isn't transitioned
  on it.


Signed-off-by: John Johansen <john.johansen@canonical.com>
2012-03-09 04:18:35 -08:00
John Johansen
37f446dd79 Fix/cleanup the permission reporting for the dfa dumps
The permission reporting was not reporting the full set of permission
flags and was inconsistent between the dump routines.

Report permissions as the quad (allow/deny/audit/quiet) in hex.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-03-09 04:17:47 -08:00
John Johansen
1a01b5c296 Fix/cleanup the dfa dump routines output to provide state label
Fix the transitions states output so that they output the state label
instead of the state address.  That is
  {1} -> 0x10831a0:  /
now becomes
  {1} -> {2}:  /

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-03-09 04:14:34 -08:00
Jamie Strandboge
b47197b881 allow read access to ~/.drirc
Bug-Ubuntu: https://launchpad.net/bugs/941506

Acked-By: Jamie Strandboge <jamie@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-03-02 15:09:21 -06:00
Jamie Strandboge
3e5ae57164 Description: allow read access to /usr/share/texmf/fonts
Bug-Ubuntu: https://launchpad.net/bugs/941503

Acked-By: Jamie Strandboge <jamie@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-03-02 15:08:03 -06:00
Jamie Strandboge
a0048ec064 ubuntu-browsers.d/java: update to fix LP: #945019 2012-03-02 13:03:04 -06:00
Jamie Strandboge
c35e10f875 fix path to java in ubuntu-browsers.d/java
Bug-Ubuntu: https://launchpad.net/bugs/943161

Acked-By: Jamie Strandboge <jamie@canonical.com>
2012-03-02 11:18:11 -06:00
Jamie Strandboge
46d9aae952 include /etc/drirc in the X abstraction
Bug-Ubuntu: https://launchpad.net/bugs/918879

Acked-By: Jamie Strandboge <jamie@canonical.com>
Signed-off-by: Kees Cook <kees@ubuntu.com>
2012-02-28 12:50:20 -06:00
John Johansen
bd67bb909a tag apparmor 2.8 beta1 release as 2.7.99 2012-02-24 04:38:24 -08:00
John Johansen
c454964e5b Update current mount tests to reflect how they should behave with mount rules
Signed-off-by: John Johansen <john.johansen@canonical.com>
2012-02-24 04:29:08 -08:00
John Johansen
e61b7b9241 Update the copyright dates for the apparmor_parser
Signed-off-by: John Johansen <john.johansen@canonical.com>
2012-02-24 04:21:59 -08:00
John Johansen
954dc6f694 Fix hexdigit conversion in the pcre parser
The pcre parser in the dfa backend is not correctly converting escaped
hex string like 
  \0x0d

This is the minimal patch to fix, and we should investigate just using
the C/C++ conversion routines here.

I also I nominated for the 2.7 series.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Seth Arnold <seth.arnold@gmail.com>
2012-02-24 04:20:46 -08:00
John Johansen
c9e31b7fbd Add mount rules
Add the ability to control mounting and unmounting

The basic form of the rules are.

  [audit] [deny] mount [conds]* [device] [ -> [conds] path],
  [audit] [deny] remount [conds]* [path],
  [audit] [deny] umount [conds]* [path],
  [audit] [deny] pivotroot [oldroot=<value>] <path> -> <profile>

  remount is just a short cut for mount options=remount

  where [conds] can be
    fstype=<expr>
    options=<expr>


  conds follow the extended conditional syntax of allowing either:

  * a single value after the equals, which has the same character range as
    regular IDS (ie most anything but it can't be terminated with a , (comma)
    and if spaces or other characters are needed it can be quoted

    eg.
       options=foo
       options = foo
       options="foo bar"

  * a list of values after the equals, the list of values is enclosed within
    parenthesis () and its has a slightly reduced character set but again
    elements can be quoted.

    the separation between elements is whitespace and commas.

    eg.
      options=(foo bar)
      options=(foo, bar)
      options=(foo , bar)
      options=(foo,bar)


The rules are flexible and follow a similar pattern as network, capability,
etc.

  mount,	# allow all mounts, but not umount or pivotroot

  mount fstype=procfs,  # allow mounting procfs anywhere

  mount options=(bind, ro) /foo -> /bar,  # readonly bind mount

  mount /dev/sda -> /mnt,

  mount /dev/sd** -> /mnt/**,

  mount fstype=overlayfs options=(rw,upperdir=/tmp/upper/,lowerdir=/) overlay -> /mnt/

  umount,

  umount /m*,


Currently variables and regexs are are supported on the device and mount
point. ie.
  mount <devince> -> <mount point>,

Regexes are supported in fstype and options.  The options have a further
caveat that regexs only work if the option is fs specific option.

  eg. options=(upperdir=/tmp/*,lowerdir=/)

regex's will not currently work against the standard options like ro, rw
nosuid


Conditionals (fstype) can only be applied to the device (source) at this
time and will be disregarded in situations where the mount is manipulating
an existing mount (bind, remount).

Options can be specified multiple times
  mount option=rw option=(nosuid,upperdir=/foo),

and will be combined together into a single set of values

The ordering of the standard mount options (rw,ro, ...) does not matter
but the ordering of fs specific options does.

Specifying that the value of a particular option does not matter can be
acheived by providing both the positive and negative forms of and option
  option=(rw,ro) options=(suid,nosuid)

For the fs specific options specifying that a particular value does not
matter is achieve using a regex with alternations.

Improvements to the syntax and order restrictions are planned for the
future.

Signed-off-by: John Johansen <john.johansen@canonical.com>
2012-02-24 04:19:38 -08:00
John Johansen
df46234c55 Generate the features list from the features directory
Newer versions of AppArmor use a features directory instead of a file
update the parser to use this to determine features and match string

This is just a first pass at this to get things up quickly.  A much
more comprehensive rework that can parse and use the full information
set is needed.

Signed-off-by: John Johansen <john.johansen@canonical.com>
2012-02-24 04:18:45 -08:00
John Johansen
662ad60cd7 Extend the information dumped by -D rule-exprs to include permissions
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2012-02-24 04:17:19 -08:00
John Johansen
a5640ec89c Default profiles to be chroot relative
Due to changes in path looks and the work going forward default profiles
to resolve relative to the chroot instead of the namespace.

This will only affect profiles that are used on tasks within a chroot.
For now it will be possible to get the old default namespace relative
behavior by passing the namespace_relative flag to the profile

eg.
  profile /example (namespace_relative) { .. }

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2012-02-24 04:14:00 -08:00
John Johansen
93cd01d7e6 Add const to yyerror parameter to get rid of compile warning
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2012-02-16 08:17:41 -08:00
John Johansen
ca9b813aea Add missing ifdefs in parser.h
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2012-02-16 08:16:18 -08:00
John Johansen
cbe3f33daf Add Basic infrastructure support for the policydb
policydb is the new matching format, that combines the matching portions
of different rules into a single dfa/hfa.  This patch only lays some ground
work it does not add encoding of any rules into the policydb

Signed-off-by: John Johansen <john.johansen@canonical.com>
2012-02-16 08:14:46 -08:00
John Johansen
b8f36df713 Make value_list generic so it can be reused.
value_list can be reused by conditionals and list values, so pull it out
and abstract it some more.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2012-02-16 08:07:28 -08:00
John Johansen
e087db57b2 Allow the 'file' keyword to be optionally used on file rules.
Add the optional 'file' keyword to the language/grammer.  The main reason
for doing this is to support false token injection.  Which is needed
to move towards the parser being broken out into an api that can be
used to parse individual rule types, separate from parsing the whole file.

Since we are adding the token to the grammar expose it to userspace with
the 'file' keyword.  While not needed it helps bring consistency, as all
the other rule types start with a keyword (capability, network, rlimit, ...).

Also allow the bare keyword to be used to represent allowing all file
operations, just as with network and capability.  Domain transitions are
defaulted to ix.  Thus

  file,

is equivalent to

  /** rwlkmix,

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2012-02-16 08:06:04 -08:00
John Johansen
dd7427d1eb Remove setting of capabilities from the syntax
The ability to set capabilities from a profile has been removed from the
kernel for several releases.  Remove it from the parser as well.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2012-02-16 08:04:04 -08:00
John Johansen
4a4ec1c54a Make expressing all capabilities easier
Allow the capability rule to be bare to represent all capabilities similar
to how network, and other rule types work.

  capability,

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2012-02-16 08:01:47 -08:00
John Johansen
18c87e98bf Enable the parser to pass the next token to be returned to the lexer
Currently the parser can not directly influence the lexer output.  This
limits the grammar and also how the parser can be invoked.  Allow the
parser to pass the next TOKEN that the lexer will return.

This is has two uses:  It allows us to trick the bison parser into having
multiple start symbols, allowing us to say invoke the parser on an
individual network or file rule.  It also allows the semantic analysis of
the parser to change the language recognized.  This can be leveraged to
overcome some of the limitation of bison's LALR parse generator.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2012-02-16 07:59:23 -08:00
John Johansen
8a3edd677c Require matched mode strings to terminate
mode strings overlap with other potential commands, or strings, and as
currently written can be match as a leading substring of an ID.  Eliminate
the leading substring case by requiring that for a mode string to be
recognized it must be terminated by whitespace, eol, eof, or
comma (end of rule).

The other cases where modes string overlap are ambiguous and the ID should
be quoted to remove the ambiguity.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2012-02-16 07:56:53 -08:00
John Johansen
def8c20168 Instead of using a special flags= token and keyword use TOK_CONDID
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2012-02-16 07:53:04 -08:00
John Johansen
613997fd7e Add generic support extended conditional syntax
extended conditionals use a syntax of
    cond=value
    cond=(value1 value2)
    cond=(value1,value2)
  where the comma is optional and supported as such because the
  flags syntax used them

  The mount patch extends, and improves on this patch, the changes probably
  should have been separated out but ...

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2012-02-16 07:51:33 -08:00
John Johansen
eabeb4f7b3 Convert FLAGS_MODE start condition to a generic list of values start cond
Signed-off-by: John Johansen <john.johansen@canonical.com>
jj@ortho:~/apparmor/aa-test/parser$ guilt header
Convert FLAGS_MODE start condition to a generic list of values start cond

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2012-02-16 07:49:12 -08:00
John Johansen
ac6c7dd37f Rework the definition of ID and POST_VAR_ID to use a define for the charset
ID and POST_VAR_ID define a set of characters that is reused, pull this
out to avoid making mistakes when updating the character set.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2012-02-16 07:46:43 -08:00
John Johansen
e7c550243c Make second minimization pass optional
The removal of deny information is a one way operation, that can result
in a smaller dfa, but also results in a dfa that should not be used in
future operations because the deny rules from the precomputed dfa would
not get applied.

For now default filtering out of deny information to off, as it takes
extra time and seldom results in further state reduction.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2012-02-16 07:43:02 -08:00
John Johansen
6f95ff5637 Track full permission set through all stages of DFA construction.
Previously permission information was thrown away early and permissions
where packed to their CHFA form at the start of DFA construction.  Because
of this permissions hashing to setup the initial DFA partitions was
required as x transition conflicts, etc. could not be resolved.

Move the mapping of permissions to CHFA construction, and track the full
permission set through DFA construction.  This allows removal of the
perm_hashing hack, which prevented a full minimization from happening
in some DFAs.  It also could result in x conflicts not being correctly
detected, and deny rules not being fully applied in some situations.

Eg.
 pre full minimization
   Created dfa: states 33451
   Minimized dfa: final partitions 17033

 with full minimization
   Created dfa: states 33451
   Minimized dfa: final partitions 9550
   Dfa minimization no states removed: partitions 9550

The tracking of deny rules through to the completed DFA construction creates
a new class of states.  That is states that are marked as being accepting
(carry permission information) but infact are non-accepting as they
only carry deny information.  We add a second minimization pass where such
states have their permission information cleared and are thus moved into the
non-accepting partion.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2012-02-16 07:41:40 -08:00
John Johansen
82a20d9bb8 Track deny and quiet perms during DFA construction
Delay the packing of audit and quiet permissions until chfa construction,
and track deny and quiet perms during DFA construction, so that we will
be able to do full minimization.  Also delay the packing of audit and

Signed-off-by: John Johansen <john.johansen@canonical.com>
2012-02-16 07:40:21 -08:00
Steve Beattie
fb55e9cddc Two patches in one:
From: Kees Cook <kees@ubuntu.com>
This is a trivial manpage fix that makes pod2man stop yelling at me.
Acked-By: Steve Beattie <sbeattie@ubuntu.com>

From: Steve Beattie <sbeattie@ubuntu.com>
This patch adds --stderr to pod2man to make it report errors, as well as
fixes a few other minor text issues I noticed.
Acked-by: Kees Cook <kees@ubuntu.com>
2012-02-15 16:34:03 -08:00
Christian Boltz
4d406621ee update apparmor.vim to support "capability," (which allows all
capabilities). The rule will be marked in the "dangerous capability"
color.

Additionally, the patch removes the (already commented out) code for 
"set capability".

Acked-by: Kees Cook <kees@ubuntu.com>
2012-02-15 23:44:39 +01:00
Christian Boltz
e074def743 Add the missing k permission for /etc/.pwd.lock to the userdel profile.
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-02-13 10:50:37 +01:00
Jamie Strandboge
13e959f8c0 Author: Jamie Strandboge <jamie@canonical.com>
Description: glibc's __get_nprocs() now checks /sys/devices/system/cpu/online
 in addition to /proc/stat for the number of processors. This is used in the
 _SC_NPROCESSORS_ONLN implementation, a part of sysconf. This was introduced in
 upstream glibc commit:
 84e2a551a7
Bug-Ubuntu: https://launchpad.net/bugs/929531

Acked-By: Jamie Strandboge <jamie@canonical.com>
Acked-By: Christian Boltz <apparmor@cboltz.de>
2012-02-09 21:06:24 -06:00
Jamie Strandboge
5151168071 abstractions/ubuntu-helpers: use Pixr instead of Pix in case we have an
interpreted file
2012-01-24 09:00:32 -06:00
Christian Boltz
77f37e84eb Update abstractios for KDE4
(At least) openSUSE uses ~/.kde4 to store KDE4 settings.
This patch changes ~/.kde/ to ~/.kde{,4} in all abstractions.

The patch is mostly from Velery Valery, I only fixed a merge conflict 
and added the kmail{,2} part in private-files-strict.

References: https://bugzilla.novell.com/show_bug.cgi?id=741592

Acked-By: Steve Beattie <sbeattie@ubuntu.com> for both trunk and 2.7.
2012-01-19 15:20:28 +01:00
Jamie Strandboge
8e3b75c40a update p11-kit to allow mmap of libraries in pkcs directories
Acked-by: Jamie Strandboge <jamie@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-01-18 16:22:08 -06:00
Jamie Strandboge
4037c3ae65 Adjust dnsmasq profile for read access to /{,var/}run/nm-dns-dnsmasq.conf
which is needed by NetworkManager integration in Ubuntu. (LP: #917628)

Acked-by: Jamie Strandboge <jamie@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-01-18 16:20:43 -06:00
Steve Beattie
c817e01774 Bug: https://bugs.launchpad.net/bugs/917859
A bug in Ubuntu reported that the aspell abstraction does
not allow write access to the user customizable dictionaries, the
personal dictionary (~/.aspell.$LANG.pws) and the personal replacement
dictionary (~/.aspell.$LANG.prepl). It also adjusts the abstraction
to add the owner modifier to the personal dictionaries.

Signed-off-by: Steve Beattie <sbeattie@ubuntu.com>
Acked-by: John Johansen <john.johansen@canonical.com>

Bug: https://launchpad.net/bugs/917859
2012-01-18 10:15:57 -08:00
Jamie Strandboge
8d1e8c9c2e ubuntu-browsers.d/plugins-common: include the ubuntu-helpers profile here as
it is needed by pretty much all of the browser abstractions. aa-update-browser
unconditionally adds the plugins-common abstraction, so this should be
sufficient.
2012-01-17 08:22:11 -06:00
Jamie Strandboge
b0ad1303ac update ubuntu-integration abstraction for multiarch with gst-plugin-scanner
(LP: #917641)

Acked-by: Jamie Strandboge <jamie@canonical.com>
2012-01-17 08:00:56 -06:00
Jamie Strandboge
e4e173233c update ubuntu-browsers.d/text-editors to work with emacs2[2-9] 2012-01-17 07:10:33 -06:00
Jamie Strandboge
dd1756ba9e profiles/apparmor.d/abstractions/ubuntu-helpers: add inet6 too 2012-01-13 11:19:55 +01:00
Steve Beattie
cec0d50cfd Regression testsuite: remove a bit more code that's no longer needed
in prologue.inc.

Signed-off-by: Steve Beattie <sbeattie@ubuntu.com>
Acked-by: John Johansen <john.johansen@canonical.com>
2012-01-12 12:37:24 +01:00
Steve Beattie
1ef2d7e757 Regression testsuite: the coredump checkcorefile() function did not
properly quote the _known variable (set when the tests are marked as
expected failures) when the expectation was that the testcase would
produce a corefile. This would result in a failed testcase reporting
XFAIL incorrectly.

Signed-off-by: Steve Beattie <sbeattie@ubuntu.com>
Acked-by: John Johansen <john.johansen@canonical.com>
2012-01-12 12:32:32 +01:00
Steve Beattie
03976a038e Regression testsuite: remove unneeded parsing of permissions on the
suffix of an image= flag, as it's no longer needed. It also eliminates
code that emitted the permissions based on the result of the parse.

Signed-off-by: Steve Beattie <sbeattie@ubuntu.com>
Acked-by: John Johansen@canonical.com
2012-01-12 12:31:25 +01:00
Steve Beattie
83a3de91f8 Regression testsuite: add a flag to mkprofile.pl to not automatically
add 'rix' permissions on executable images (but still auto-generate
ldd dependencies), for use when specifying alternate permissions
on executables.

Where appropriate, it also converts a few testcases to make use of
the option.

Signed-off-by: Steve Beattie <sbeattie@ubuntu.com>
Acked-by: John Johansen <john.johansen@canonical.com>
2012-01-12 12:29:10 +01:00
Steve Beattie
e7cc3e2094 Regression testsuite: add the ability to add multiple binaries to a
generated profile and have the ldd auto-generation of rules occur on
it. It also kills all testcase usage of $dynlibs, which had stopped
being generated by prologue.inc in a prior patch.

Signed-off-by: Steve Beattie <sbeattie@ubuntu.com>
Acked-by: John Johansen <john.johansen@canonical.com>
2012-01-12 12:23:51 +01:00
Steve Beattie
4fa434a3d0 Regression testsuite: move the ldd analysis of binaries to the
mkprofile.pl helper and take the convoluted code out of the bash
prologue.inc. It also detects if the binary is a script and performs
ldd analysis on the interpreter.

Signed-off-by: Steve Beattie <sbeattie@ubuntu.com>
Acked-by: John Johansen <john.johansen@canonical.com>
2012-01-12 12:20:22 +01:00
Steve Beattie
44ca1c0f11 Regression testsuite: rename the emit_ functions to gen_ which reflects
their purpose a little more accurately; renames the dump_flags to
emit_flags for the same reason, and also adds a modicum a function
prototype information to the function declarations.

Signed-off-by: Steve Beattie <sbeattie@ubuntu.com>
Acked-by: John Johansen <john.johansen@canonical.com>
2012-01-12 11:49:32 +01:00
Steve Beattie
a0fbc1f26c Regression testsuite: move the generation of the rules that grant
write access to /proc/*/attr/current to mkprofile.pl from prologue.inc.

Signed-Off-By: Steve Beattie <sbeattie@ubuntu.com>
Acked-by: John Johansen <john.johansen@canonical.com>
2012-01-12 11:39:57 +01:00
John Johansen
62a7934ea6 Disable caching when a namespace is specified
Profile loads when specifying namespaces currently conflict with caching.
If the profile (ignoring the specified namespace) is in the cache, then
the cached profile will be loaded, replacing the profile in the current
namespace instead of loading the profile to the new namespace.

Fix this by disabling caching when a namespace is specified, forcing the
profile to be compiled.

NOTE: this will not affect profiles loaded from within a namespace using
      either the same or a separate directory as the base to load a namespac
      from.  This only affects loading profiles directly into a child
      namespace.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2012-01-11 17:26:51 +01:00
John Johansen
5fdf33c689 Add an option to allow setting the cache's location.
Currently the cache location is fixed and links are needed to move it.
Add an option that can be set in the apparmor_parser.conf file so distros
can locate the cache where ever makes sense for them.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2012-01-11 17:25:18 +01:00
John Johansen
7031a91aec Disable revalidation/revocation tests
The behavior for revalidation/revocation of open files has changed
with the current kernel code, resulting in these tests being reported
as failing even though they are showing expected behavior.

Under the current kernel module this form of revalidation/revocation
can not be tested reliably, so just changing the expected result is
not enough, completely disable the tests for now.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-01-11 16:50:34 +01:00
Jamie Strandboge
39a3f5b08d Add profiles/apparmor.d/abstractions/ubuntu-helpers:
Lenient profile that is intended to be used when 'Ux' is desired but
does not provide enough environment sanitizing. This effectively is an
open profile that blacklists certain known dangerous files and also
does not allow any capabilities. For example, it will not allow 'm' on files
owned be the user invoking the program. While this provides some additional
protection, please use with care as applications running under this profile
are effectively running without any AppArmor protection. Use this profile
only if the process absolutely must be run (effectively) unconfined.

Limitations:
1. This does not work for root owned processes, because of the way we use
   owner matching in the sanitized helper. We could do a better job with
   this to support root, but it would make the policy harder to understand
   and going unconfined as root is not desirable anyway.

2. For this sanitized_helper to work, the program running in the sanitized
   environment must open symlinks directly in order for AppArmor to mediate
   it. This is confirmed to work with:
    - compiled code which can load shared libraries
    - python imports
   It is known not to work with:
    - perl includes

3. Going forward it might be useful to try sanitizing ruby and java

Use at your own risk. This profile was developed as an interim workaround for
LP: #851986 until AppArmor implements proper environment filtering.

Acked-by: Jamie Strandboge <jamie@canonical.com>



Adjust ubuntu abstractions to use sanitized_helper instead of (P)Ux.

Acked-by: Jamie Strandboge <jamie@canonical.com>



Update launchpad-integration to use a sanitized helper in a similar manner
as that in ubuntu-helpers.

Acked-by: Jamie Strandboge <jamie@canonical.com>
2012-01-11 15:20:42 +01:00
Jamie Strandboge
7eee94290b add xdg-desktop abstraction to gnome and kde. Acked via discussions 2012-01-11 14:17:32 +01:00
Jamie Strandboge
45e7265c82 Create an xdg-desktop abstraction based on the upstream documentation for
creating owner writes on things like ~/.cache and ~/.config

Acked-by: Jamie Strandboge <jamie@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-01-11 13:00:34 +01:00
Jamie Strandboge
3c2684b674 merge from trunk 2012-01-11 09:48:22 +01:00
Jamie Strandboge
b1a8e7cf44 fix up comments in launchpad-integration 2012-01-11 09:27:22 +01:00
Jamie Strandboge
7887d5906d don't #include ubuntu-helpers in the abstractions. This can only be included
once in policy, otherwise you will get an error regarding multiple definitions
for sanitized_helper
2012-01-11 09:00:35 +01:00
Jamie Strandboge
b0ef4d2b6a add note to ubuntu-helpers regarding needing to only include it once 2012-01-11 08:50:47 +01:00
Jamie Strandboge
c626e62da6 fix missing '}' in ubuntu-helpers 2012-01-11 08:48:07 +01:00
Jamie Strandboge
b93c49974c update ubuntu-browsers to also Cx the firefox.sh in /usr/lib 2012-01-11 00:59:38 +01:00
Jamie Strandboge
d045d609be update launchpad-integration to use a sanitized helper 2012-01-10 20:54:57 +01:00
Jamie Strandboge
333cd41d33 update ubuntu abstractions to use the sanitized helper 2012-01-10 20:54:38 +01:00
Jamie Strandboge
2526933747 ubuntu-helpers: account for lib32 and lib64 2012-01-10 19:57:58 +01:00
Christian Boltz
a33c5822a9 when using smbldap-useradd using this smb.conf entry
add machine script = /usr/sbin/smbldap-useradd -t 5 -w "%u"
smbd obviously needs x permissions for smbldap-useradd.

The commit also adds a new profile for usr.sbin.smbldap-useradd (based on 
the audit.log from Alexis Pellicier).

Additionally, I moved the "/etc/samba/* rwk" rule next to the other 
/etc-related rules in the smbd profile.

References: https://bugzilla.novell.com/show_bug.cgi?id=738041
2012-01-10 19:06:24 +01:00
Jamie Strandboge
d6c4f56da8 add profiles/apparmor.d/abstractions/ubuntu-helpers:
Lenient profile that is intended to be used when 'Ux' is desired but
does not provide enough environment sanitizing. This effectively is an
open profile that blacklists certain known dangerous files and also
does not allow any capabilities. For example, it will not allow 'm' on files
owned be the user invoking the program. While this provides some additional
protection, please use with care as applications running under this profile
are effectively running without any AppArmor protection. Use this profile
only if the process absolutely must be run (effectively) unconfined.

Limitations:
1. This does not work for root owned processes, because of the way we use
   owner matching in the sanitized helper. We could do a better job with
   this to support root, but it would make the policy harder to understand
   and going unconfined as root is not desirable any way.

2. For this sanitized_helper to work, the program running in the sanitized
   environment must open symlinks directly in order for AppArmor to mediate
   it. This is confirmed to work with:
    - compiled code which can load shared libraries
    - python imports
   It is known not to work with:
    - perl includes

3. Going forward it might be useful to try sanitizing ruby and java

Use at your own risk. This profile was developed as an interim workaround for
LP: #851986 until AppArmor implements proper environment filtering.
2012-01-10 18:36:14 +01:00
Steve Beattie
25f800ac7d Fix from Felix Geyer: block write access to ~/.kde/env because KDE
automatically sources scripts in that folder on startup.

Bug: https://launchpad.net/bugs/914190
2012-01-10 11:54:12 +01:00
Steve Beattie
ad0f942bb5 Fix from Felix Geyer: in the enchant abstraction, allow the creation of
enchant .config directory.

Bug: https://launchpad.net/bugs/914184
2012-01-10 11:37:54 +01:00
Steve Beattie
1ff5a08f60 utils/Immunix/AppArmor.pm: fixes the profile autogeneration code to
include read access to the script itself for interpreted scripts.
2012-01-09 17:42:55 +01:00
Steve Beattie
8a223ce3d5 utils/Immunix/AppArmor.pm: update the initial profile generation
for python and ruby scripts to include the respective abstractions.
2012-01-09 17:39:31 +01:00
Christian Boltz
2f85e0b7d2 according to Peter Czanik, the openSUSE syslog-ng maintainer, syslog-ng
needs capability dac_read_search.

References: https://bugzilla.novell.com/show_bug.cgi?id=731876

ACKed-by: Steve Beattie <steve@nxnw.org>
2012-01-09 13:28:25 +01:00
Jamie Strandboge
6d55882b4a add p11-kit abstraction (LP: #912754, LP: #912752)
From the README in the toplevel source:
"[P11-KIT] Provides a way to load and enumerate PKCS#11 modules. Provides a
standard configuration setup for installing PKCS#11 modules in such a way that
they're discoverable."

File locatations are described in [1]. There is a global configuration file in
/etc/pkcs11/pkcs11.conf. Per module configuration happens in
/etc/pkcs11/<module name>. There is also user configuration in ~/.pkcs11, but
IMO this should not be allowed in the abstraction. Example configuration can be
seen in the upstream documentation[2].

This will likely need to be refined as more applications use p11-kit.

[1]http://p11-glue.freedesktop.org/doc/p11-kit/config-locations.html
[2]http://p11-glue.freedesktop.org/doc/p11-kit/config-example.html

Acked-by: Jamie Strandboge <jamie@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>


Also add p11-kit to authentication abstraction

Acked-by: Jamie Strandboge <jamie@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-01-06 17:01:39 -06:00
Jamie Strandboge
c5ccbb50d2 add p11-kit to authentication abstraction
Acked-by: Jamie Strandboge <jamie@canonical.com>
2012-01-06 11:46:52 -06:00
Jamie Strandboge
572bab7e84 add p11-kit abstraction (LP: #912754, LP: #912752)
From the README in the toplevel source:
"[P11-KIT] Provides a way to load and enumerate PKCS#11 modules. Provides a
standard configuration setup for installing PKCS#11 modules in such a way that
they're discoverable."

File locatations are described in [1]. There is a global configuration file in
/etc/pkcs11/pkcs11.conf. Per module configuration happens in
/etc/pkcs11/<module name>. There is also user configuration in ~/.pkcs11, but
IMO this should not be allowed in the abstraction. Example configuration can be
seen in the upstream documentation[2].

This will likely need to be refined as more applications use p11-kit.

[1]http://p11-glue.freedesktop.org/doc/p11-kit/config-locations.html
[2]http://p11-glue.freedesktop.org/doc/p11-kit/config-example.html

Acked-by: Jamie Strandboge <jamie@canonical.com>
2012-01-06 11:45:44 -06:00
John Johansen
f561b8cdfe Make hfa::match not need to walk a string twice
Currently hfa::match calls hfa::match_len to do matching.  However this
requires walking the input string twice.  Instead provide a match routine
for input that is supposed to terminate at a given input character.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-01-06 09:04:36 -08:00
John Johansen
3ff8b4d19a Add basic string matching to the hfa
Add the ability to match strings directly from the hfa instead of needing
to build a cfha.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-01-06 09:03:20 -08:00
Jamie Strandboge
47280bb483 Description: allow read of @{HOME}/.cups/client.conf and
@{HOME}/.cups/lpoptions
Bug-Ubuntu: https://launchpad.net/bugs/887992

Added owner match per Steve Beattie and lpoptions per Steve and Christian Boltz
2012-01-06 10:45:34 -06:00
Jamie Strandboge
2b4e235ffd Description: allow read access of /etc/python{2,3}.[0-7]*/sitecustomize.py
in python abstraction. This script is used by apport aware python applications
Bug-Ubuntu: https://launchpad.net/bugs/860856

Acked-by: Jamie Strandboge <jamie@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2012-01-06 10:38:06 -06:00
Jamie Strandboge
27dacf2eca Description: update dovecot deliver profile to access various .conf files for
dovecot
Bug-Ubuntu: https://launchpad.net/bugs/458922

Acked-by: Jamie Strandboge <jamie@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2012-01-06 10:34:44 -06:00
Jamie Strandboge
15e4f8a05f Description: updates for usr.bin.sshd example profile to work with zsh4, dash
and systems where /var/run moved to /run. Also allows read of
 /etc/default/locale.
Bug-Ubuntu: https://launchpad.net/bugs/817956

Acked-by: Jamie Strandboge <jamie@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2012-01-06 10:33:10 -06:00
Jamie Strandboge
0cb4e48344 Description: Disallow writing and linking to @{HOME}/.pki/nssdb/ .so files
Bug-Ubuntu: https://launchpad.net/bugs/911847

Acked-by: Jamie Strandboge <jamie@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-01-06 10:29:32 -06:00
John Johansen
f29c0cc377 Remove asprintf warning and fix invalid test
asprintf is marked with warn_unused_result and its return value should
not be ignored, even casting to (void) will not remove this warning.

The current code ignored the result and used the value of newfmt to
make a decision.  This is however not correct in that according to the
asprintf man page newfmt is undefined if asprintf returns an error.

Fix the warning and error by using the return value of asprintf

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-01-06 07:09:12 -08:00
Christian Boltz
960d19b6cb according to Lars Müller (a samba developer) smbd needs access to some
more files in /usr/lib*/samba/ in some cases.

References: https://bugzilla.novell.com/show_bug.cgi?id=725967#c5

Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-01-05 23:47:03 +01:00
Christian Boltz
4b34c77a39 Split off various permissions from the httpd2-prefork profile to
abstractions/apache2-common. Additionally, add read permissions
for /**/.htaccess and /dev/urandom to apache2-common.

The patch is based on a profile abstraction from darix. I made some 
things more strict (compared to darix' profile), and OTOH added some 
things that are needed on my servers.

*** BACKWARDS-INCOMPATIBLE CHANGES ***

^HANDLING_UNTRUSTED_INPUT
- don't allow /.htaccess (.htaccess files in subdirectories are still allowed)
- don't allow *.htaccess files (the old /**.htaccess rule was too generous)
2012-01-05 23:28:17 +01:00
Jamie Strandboge
61b614543c add audacity to the ubuntu-media-players abstraction (LP: #899963) 2012-01-04 11:45:43 -06:00
Jamie Strandboge
30c8dfe12c allow software-center in the ubuntu-integration abstraction for
apt: URLs (LP: #662906)
2012-01-04 09:36:21 -06:00
Jamie Strandboge
3d42221ba8 allow fireclam plugin to work in Ubuntu multimedia abstraction
(LP: #562831)
2012-01-03 17:50:00 -06:00
Jamie Strandboge
bd56500d03 Author: James Troup
Description: fix typo when adding multiarch lines for gconv
Bug-Ubuntu: https://launchpad.net/bugs/904548

Acked-by: Jamie Strandboge <jamie@canonical.com>
2012-01-03 17:27:26 -06:00
Jamie Strandboge
401363854a Author: Felix Geyer
Description: allow avahi to do dbus introspection
Bug-Ubuntu: https://launchpad.net/bugs/769148

Acked-by: Jamie Strandboge <jamie@canonical.com>
2012-01-03 17:26:00 -06:00
Jamie Strandboge
a6d274dcb0 Author: Felix Geyer
Description: abstractions/fonts should allow access to ~/.fonts.conf.d
Bug-Ubuntu: https://launchpad.net/bugs/870992

Acked-by: Jamie Strandboge <jamie@canonical.com>
2012-01-03 17:25:10 -06:00
Jamie Strandboge
9d20afa95c Nvidia users need access to /dev/nvidia* files for various plugins
to work right. Since these are all focused around multimedia, add the
accesses to ubuntu-browsers.d/multimedia
2012-01-03 17:24:04 -06:00
Jamie Strandboge
32362d2f79 allow access to Thunar as well as thunar in ubuntu-integration abstraction
(LP: #890894)
2012-01-03 17:23:23 -06:00
Jamie Strandboge
3a201bf72b allow ixr access to exo-open in Ubuntu integration abstraction
(LP: #890894)
2012-01-03 17:22:27 -06:00
Jamie Strandboge
d15fcb69ab update binaries for for transmission in ubuntu-bittorrent-clients
(LP: #852062)
2012-01-03 17:21:40 -06:00
Jamie Strandboge
c1850f9855 add kate to Ubuntu text editors browser abstraction
fix for LP: #884748
2012-01-03 17:20:54 -06:00
Christian Boltz
b9bbcdc45c Create /etc/apparmor.d/tunables/multiarch.d directory in profiles/Makefile
(otherwise it's created as a file, which is wrong)

Acked-by: John Johansen <john.johansen@canonical.com>
2012-01-03 23:41:07 +01:00
John Johansen
ff98d79963 Rework how the lexer identifies hat names following a ^
Reworking this code is a step to getting rid of the SUB_NAME2 start
condition.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2012-01-02 16:49:31 -08:00
John Johansen
6ef6f605b0 Update parsing of the 'hat' and 'profile' keyword to use SUB_NAME
Change how we handle the parsing of the hat and profile keywords this allows
us to get rid of the SUB_NAME2 start condition because the the whitespace
that is allowed by these rules are now consumed by matching the keyword

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2012-01-02 16:48:53 -08:00
John Johansen
7f9c79e345 Add new processid fn that handles both quoted and unquoted ids
There is a lot of duplication of code calling processqunquoted and
processquoted.  Move all this code to use the new processid fn.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2012-01-02 16:48:44 -08:00
John Johansen
37e2975d4d Simple rename to reflect the ( ) are not always used by flags (in the future)
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2012-01-02 16:48:34 -08:00
John Johansen
49142c74a5 Update the flex scanner to use a stack for its start conditions
This is the first step in reducing the number of shared rules between the
different start conditions.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2012-01-02 16:48:24 -08:00
John Johansen
98f196506a Remove extaneous BEGIN(INITIAL) from comment rule.
The affected comment rule is already in the INITIAL start condition
so BEGIN(INITIAL) is extraneous and will cause problems when switching
to a stack of start conditions.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2012-01-02 16:48:14 -08:00
John Johansen
c52f417406 Move rlimit start condition and rules up to be with other start conditions.
The rlimit start condition was separating different rules of the base
set making the lexer grammer harder to read than necessary.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2012-01-02 16:47:32 -08:00
Christian Boltz
ec68828a30 Fix a syntax error in abstractions/python introduced in r1854.
According to https://launchpad.net/bugs/840734 pyconfig.h should have r 
permissions.

Acked-by: John Johansen <john.johansen@canonical.com>
2012-01-03 00:51:12 +01:00
John Johansen
c259deb5b2 Fix apparmor_parser when removing a profile from an alternate namespace
The module interface calls for names with namespaces to be in the format of

  :namespace:profile or :namespace://profile

but the parser was generating
  namespace:profile

causing profile lookup to fail, or removal of the wrong profile as it was
done against the current namespace, instead of the specified namespace

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2012-01-02 15:35:21 -08:00
John Johansen
f0f520eeff Update the documented error codes for aa_change_profile() and aa_change_onexe()
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2012-01-02 15:34:23 -08:00
John Johansen
a9697cec28 Fix apparmor_parser when removing a profile from an alternate namespace
The module interface calls for names with namespaces to be in the format of

  :namespace:profile or :namespace://profile

but the parser was generating
  namespace:profile

causing profile lookup to fail, or removal of the wrong profile as it was
done against the current namespace, instead of the specified namespace
    
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2012-01-02 15:32:36 -08:00
Christian Boltz
9e6a13fa78 smbd needs read access to /etc/netgroup.
References: https://bugzilla.novell.com/show_bug.cgi?id=738041
2011-12-29 17:34:01 +01:00
Jamie Strandboge
95015dae9f Merge from Simon Deziel for TFTP read-only access for dnsmasq. Fixes
LP: #905412

Acked-by: Jamie Strandboge <jamie@canonical.com>
2011-12-21 12:45:59 -06:00
Simon Deziel
cba77b26a5 dnsmasq's TFTP server provides read-only access. 2011-12-17 12:20:53 -05:00
Simon Deziel
0069bf82a6 Allow dnsmasq's profile to read and write to /var/tftp (configurable) 2011-12-16 12:15:12 -05:00
John Johansen
9e9a7ff572 update version number to indicate we are in a dev version, using the
proposed .99 versioning to fix the version problem we had with using
alpha, beta, etc. because apt and rpm have different version comparisons
2011-12-15 10:08:13 -08:00
John Johansen
18821b079b To reduce memory overhead of dfa creation convert to using a Node Vector
instead of a NodeSet.

We need to store sets of Nodes, to compute the dfa but the C++ set is
not the most efficient way to do this as, it has a has a lot of overhead
just to store a single pointer.

Instead we can use an array of tightly packed pointers + a some header
information.  We can do this because once the Set is finalized it will
not change, we just need to be able to reference and compare to it.

We don't use C++ Vectors as they have more overhead than a plain array
and we don't need their additional functionality.

We only replace the use of hashedNodeSets for non-accepting states as
these sets are only used in the dfa construction, and dominate the memory
usage.  The accepting states still may need to be modified during
minimization and there are only a small number of entries (20-30), so
it does not make sense to convert them.

Also introduce a NodeVec cache that serves the same purpose as the NodeSet
cache that was introduced earlier.

This is not abstracted this out as nicely as might be desired but avoiding
the use of a custom iterator and directly iterating on the Node array
allows for a small performance gain, on larger sets.

This patch reduces the amount of heap memory used by dfa creation by about
4x - overhead.  So for small dfas the savings is only 2-3x but on larger
dfas the savings become more and more pronounced.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2011-12-15 05:16:03 -08:00
John Johansen
2674a8b708 Split the nodeset used in computing the dfa into two sets, accepting and
non-accepting, and have the proto-state use them.

To reduce memory overhead each set gains its own "cache" that make sure
there is only a single instance of each NodeSet generated.  And since
we have a cache abstraction, move relavent stats into it.

Also refactor code slightly to make caches and work_queue etc, DFA member
variables instead of passing them as parameters.

The split + caching results in a small reduction in memory use as the
cost of ProtoState + Caching is less than the redundancy that is eliminated.
However this results in a small decrease in performance.

Sorry I know this really should have been split into multiple patches
but the patch evolved and I got lazy and decided to just not bother
splitting it.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2011-12-15 05:14:37 -08:00
John Johansen
8bc30c8851 Replace usage of NodeSet with ProtoState in dfa creation.
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2011-12-15 05:12:30 -08:00
John Johansen
bd10235397 Add a new class hashedNodeSet.
It is the functional equivalent of ProtoState.  We do this to provide a
new level of abstraction that ProtoState can leverage, when the node types
are split.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2011-12-15 05:11:09 -08:00
John Johansen
35b7ee91eb Now that we have a proper class we don't need a functor to do comparisons,
we can fold it into the classes operator<.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2011-12-15 05:09:47 -08:00
John Johansen
d452f53576 Begin preparing to split accept nodes and non-accept nodes.
Create a new ProtoState class that will encapsulate the split, but for
this patch it will just contain what was done previously with NodeSet

Signed-off-by: John Johansen <john.johansen@canonical.com>
2011-12-15 05:08:31 -08:00
John Johansen
9d374d4726 Rename compressed_hfa.{c,h} and TransitionTable within them to chfa. This
is done to be clear what TransitionTable is, as we will then add matching
capabilities.  Renaming the files is just to make them consistent with
the class in the file.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2011-12-15 05:06:32 -08:00
John Johansen
4beee46c52 Make sure that state always has otherwise set
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2011-12-15 05:01:35 -08:00
John Johansen
319cd6c038 Now that State Cases have been renamed, rename NodeCases back to Cases.
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2011-12-15 04:59:55 -08:00
John Johansen
bd66fba55f This helps make the meaning of things a little clearer and provides a clear
distinction betwen NodeCases, and State transitions

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2011-12-15 04:58:33 -08:00
196 changed files with 5924 additions and 1708 deletions

View File

@@ -7,7 +7,7 @@ include common/Make.rules
DIRS=parser \
profiles \
utils \
changehat/libapparmor \
libraries/libapparmor \
changehat/mod_apparmor \
changehat/pam_apparmor \
tests

View File

@@ -150,6 +150,40 @@ _clean:
-rm -f ${NAME}-${VERSION}-*.tar.gz
-rm -f ${MANPAGES} *.[0-9].gz ${HTMLMANPAGES} pod2htm*.tmp
# =====================
# generate list of capabilities based on
# /usr/include/linux/capabilities.h for use in multiple locations in
# the source tree
# =====================
# emits defined capabilities in a simple list, e.g. "CAP_NAME CAP_NAME2"
CAPABILITIES=$(shell echo "\#include <linux/capability.h>" | cpp -dM | LC_ALL=C sed -n -e '/CAP_EMPTY_SET/d' -e 's/^\#define[ \t]\+CAP_\([A-Z0-9_]\+\)[ \t]\+\([0-9xa-f]\+\)\(.*\)$$/CAP_\1/p' | sort)
.PHONY: list_capabilities
list_capabilities: /usr/include/linux/capability.h
@echo "$(CAPABILITIES)"
# =====================
# generate list of network protocols based on
# sys/socket.h for use in multiple locations in
# the source tree
# =====================
# These are the families that it doesn't make sense for apparmor
# to mediate. We use PF_ here since that is what is required in
# bits/socket.h, but we will rewrite these as AF_.
FILTER_FAMILIES=PF_UNSPEC PF_UNIX PF_LOCAL PF_NETLINK
__FILTER=$(shell echo $(strip $(FILTER_FAMILIES)) | sed -e 's/ /\\\|/g')
# emits the AF names in a "AF_NAME NUMBER," pattern
AF_NAMES=$(shell echo "\#include <sys/socket.h>" | cpp -dM | LC_ALL=C sed -n -e '/$(__FILTER)/d' -e 's/^\#define[ \t]\+PF_\([A-Z0-9_]\+\)[ \t]\+\([0-9]\+\).*$$/AF_\1 \2,/p' | sort -n -k2)
.PHONY: list_af_names
list_af_names:
@echo "$(AF_NAMES)"
# =====================
# manpages
# =====================
@@ -172,29 +206,8 @@ install_manpages: $(MANPAGES)
MAN_RELEASE="AppArmor ${VERSION}"
%.1: %.pod
$(POD2MAN) $< --release=$(MAN_RELEASE) --center=AppArmor --section=1 > $@
%.2: %.pod
$(POD2MAN) $< --release=$(MAN_RELEASE) --center=AppArmor --section=2 > $@
%.3: %.pod
$(POD2MAN) $< --release=$(MAN_RELEASE) --center=AppArmor --section=3 > $@
%.4: %.pod
$(POD2MAN) $< --release=$(MAN_RELEASE) --center=AppArmor --section=4 > $@
%.5: %.pod
$(POD2MAN) $< --release=$(MAN_RELEASE) --center=AppArmor --section=5 > $@
%.6: %.pod
$(POD2MAN) $< --release=$(MAN_RELEASE) --center=AppArmor --section=6 > $@
%.7: %.pod
$(POD2MAN) $< --release=$(MAN_RELEASE) --center=AppArmor --section=7 > $@
%.8: %.pod
$(POD2MAN) $< --release=$(MAN_RELEASE) --center=AppArmor --section=8 > $@
%.1 %.2 %.3 %.4 %.5 %.6 %.7 %.8: %.pod
$(POD2MAN) $< --release=$(MAN_RELEASE) --center=AppArmor --stderr --section=$(subst .,,$(suffix $@)) > $@
%.1.html: %.pod
$(POD2HTML) --header --css apparmor.css --infile=$< --outfile=$@

View File

@@ -1 +1 @@
2.7.0
2.7.102

View File

@@ -16,4 +16,5 @@ BUILT_SOURCES = $(man_MANS)
--section=2 \
--release="AppArmor $(VERSION)" \
--center="AppArmor" \
--stderr \
$< > $@

View File

@@ -85,14 +85,9 @@ Insufficient kernel memory was available.
The calling application is not confined by apparmor.
=item B<ECHILD>
The application's profile has no hats defined for it.
=item B<EACCES>
The specified I<profile> does not exist in this profile or the
process tried to change another process's domain.
The task does not have sufficient permissions to change its domain.
=back
@@ -175,6 +170,7 @@ The output when run:
If /tmp/change_p is to be confined as well, then the following profile can be
used (in addition to the one for 'i_cant_be_trusted_anymore', above):
# Confine change_p to be able to read /etc/passwd and aa_change_profile()
# to the 'i_cant_be_trusted_anymore' profile.
/tmp/change_p {

View File

@@ -38,8 +38,9 @@ Link with B<-lapparmor> when compiling.
=head1 DESCRIPTION
The aa_is_enabled function returns true (1) if apparmor is enabled. If it
isn't it sets the errno to reflect the reason it is not enabled and returns 0.
The aa_is_enabled function returns true (1) if apparmor is enabled.
If it isn't it sets the errno(3) to reflect the reason it is not
enabled and returns 0.
The aa_find_mountpoint function finds where the apparmor filesystem is mounted
on the system, and returns a string containing the mount path. It is the
@@ -57,10 +58,10 @@ appropriately.
=head1 ERRORS
=over 4
B<aa_is_enabled>
=over 4
=item B<ENOSYS>
AppArmor extensions to the system are not available.
@@ -84,18 +85,21 @@ Did not have sufficient permissions to determine if AppArmor is enabled.
=item B<EACCES>
+Did not have sufficient permissions to determine if AppArmor is enabled.
Did not have sufficient permissions to determine if AppArmor is enabled.
=back
B<aa_find_mountpoint>
=over 4
=item B<ENOMEM>
Insufficient memory was available.
=item B<EACCES>
Access to the the required paths was denied.
Access to the required paths was denied.
=item B<ENOENT>

View File

@@ -50,7 +50,7 @@ Link with B<-lapparmor> when compiling.
The aa_getcon function gets the current AppArmor confinement context for the
current task. The confinement context is usually just the name of the AppArmor
profile restricting the task, but it may include the profile namespace or in
some cases a set of profile names (known as a stack of profiles). The returned string *con should be freed using <free()>.
some cases a set of profile names (known as a stack of profiles). The returned string *con should be freed using free().
The aa_gettaskcon function is like the aa_getcon function except it will work
for any arbitrary task in the system.
@@ -69,7 +69,8 @@ does not handle buffer allocation.
=head1 RETURN VALUE
On success zero is returned. On error, -1 is returned, and
On success size of data placed in the buffer is returned, this includes the
mode if present and any terminating characters. On error, -1 is returned, and
errno(3) is set appropriately.
=head1 ERRORS

View File

@@ -141,6 +141,10 @@ typedef struct
char *net_family;
char *net_protocol;
char *net_sock_type;
char *net_local_addr;
unsigned long net_local_port;
char *net_foreign_addr;
unsigned long net_foreign_port;
} aa_log_record;
/**

View File

@@ -83,6 +83,7 @@ aa_record_event_type lookup_aa_event(unsigned int type)
%token <t_str> TOK_QUOTED_STRING TOK_ID TOK_MODE TOK_DMESG_STAMP
%token <t_str> TOK_AUDIT_DIGITS TOK_DATE_MONTH TOK_DATE_TIME
%token <t_str> TOK_HEXSTRING TOK_TYPE_OTHER TOK_MSG_REST
%token <t_str> TOK_IP_ADDR
%token TOK_EQUALS
%token TOK_COLON
@@ -133,6 +134,10 @@ aa_record_event_type lookup_aa_event(unsigned int type)
%token TOK_KEY_CAPNAME
%token TOK_KEY_OFFSET
%token TOK_KEY_TARGET
%token TOK_KEY_LADDR
%token TOK_KEY_FADDR
%token TOK_KEY_LPORT
%token TOK_KEY_FPORT
%token TOK_SYSLOG_KERNEL
@@ -268,6 +273,14 @@ key: TOK_KEY_OPERATION TOK_EQUALS TOK_QUOTED_STRING
{ /* target was always name2 in the past */
ret_record->name2 = $3;
}
| TOK_KEY_LADDR TOK_EQUALS TOK_IP_ADDR
{ ret_record->net_local_addr = $3;}
| TOK_KEY_FADDR TOK_EQUALS TOK_IP_ADDR
{ ret_record->net_foreign_addr = $3;}
| TOK_KEY_LPORT TOK_EQUALS TOK_DIGITS
{ ret_record->net_local_port = $3;}
| TOK_KEY_FPORT TOK_EQUALS TOK_DIGITS
{ ret_record->net_foreign_port = $3;}
| TOK_MSG_REST
{
ret_record->event = AA_RECORD_INVALID;

View File

@@ -278,11 +278,12 @@ int aa_getprocattr(pid_t tid, const char *attr, char **buf, char **mode)
if (rc == -1) {
free(buffer);
size = -1;
*buf = NULL;
*mode = NULL;
} else
*buf = buffer;
return size;
return rc;
}
static int setprocattr(pid_t tid, const char *attr, const char *buf, int len)
@@ -617,6 +618,7 @@ int aa_getpeercon(int fd, char **con)
if (rc == -1) {
free(buffer);
*con = NULL;
size = -1;
} else
*con = buffer;

View File

@@ -133,8 +133,15 @@ key_capability "capability"
key_capname "capname"
key_offset "offset"
key_target "target"
key_laddr "laddr"
key_faddr "faddr"
key_lport "lport"
key_fport "fport"
audit "audit"
/* network addrs */
ip_addr [a-f[:digit:].:]{3,}
/* syslog tokens */
syslog_kernel kernel{colon}
syslog_month Jan(uary)?|Feb(ruary)?|Mar(ch)?|Apr(il)?|May|Jun(e)?|Jul(y)?|Aug(ust)?|Sep(tember)?|Oct(ober)?|Nov(ember)?|Dec(ember)?
@@ -149,6 +156,7 @@ dmesg_timestamp \[[[:digit:] ]{5,}\.[[:digit:]]{6,}\]
%x dmesg_timestamp
%x safe_string
%x audit_types
%x ip_addr
%x other_audit
%x unknown_message
@@ -201,6 +209,12 @@ yy_flex_debug = 0;
. { /* eek, error! try another state */ BEGIN(INITIAL); yyless(0); }
}
<ip_addr>{
{ip_addr} { yylval->t_str = strdup(yytext); yy_pop_state(yyscanner); return(TOK_IP_ADDR); }
{equals} { return(TOK_EQUALS); }
. { /* eek, error! try another state */ BEGIN(INITIAL); yyless(0); }
}
<audit_types>{
{equals} { return(TOK_EQUALS); }
{digits} { yylval->t_long = atol(yytext); BEGIN(INITIAL); return(TOK_DIGITS); }
@@ -270,6 +284,10 @@ yy_flex_debug = 0;
{key_capname} { return(TOK_KEY_CAPNAME); }
{key_offset} { return(TOK_KEY_OFFSET); }
{key_target} { return(TOK_KEY_TARGET); }
{key_laddr} { yy_push_state(ip_addr, yyscanner); return(TOK_KEY_LADDR); }
{key_faddr} { yy_push_state(ip_addr, yyscanner); return(TOK_KEY_FADDR); }
{key_lport} { return(TOK_KEY_LPORT); }
{key_fport} { return(TOK_KEY_FPORT); }
{syslog_kernel} { BEGIN(dmesg_timestamp); return(TOK_SYSLOG_KERNEL); }
{syslog_month} { yylval->t_str = strdup(yytext); return(TOK_DATE_MONTH); }

View File

@@ -51,6 +51,18 @@ int main(int argc, char **argv)
return ret;
}
#define print_string(description, var) \
if ((var) != NULL) { \
printf("%s: %s\n", (description), (var)); \
}
/* unset is the value that the library sets to the var to indicate
that it is unset */
#define print_long(description, var, unset) \
if ((var) != (unsigned long) (unset)) { \
printf("%s: %ld\n", (description), (var)); \
}
int print_results(aa_log_record *record)
{
printf("Event type: ");
@@ -185,6 +197,11 @@ int print_results(aa_log_record *record)
{
printf("Protocol: %s\n", record->net_protocol);
}
print_string("Local addr", record->net_local_addr);
print_string("Foreign addr", record->net_foreign_addr);
print_long("Local port", record->net_local_port, 0);
print_long("Foreign port", record->net_foreign_port, 0);
printf("Epoch: %lu\n", record->epoch);
printf("Audit subid: %u\n", record->audit_sub_id);
return(0);

View File

@@ -0,0 +1 @@
Apr 5 19:30:56 precise-amd64 kernel: [153073.826757] type=1400 audit(1308766940.698:3704): apparmor="DENIED" operation="sendmsg" parent=24737 profile="/usr/bin/evince-thumbnailer" pid=24743 comm="evince-thumbnai" laddr=192.168.66.150 lport=765 faddr=192.168.66.200 fport=2049 family="inet" sock_type="stream" protocol=6

View File

@@ -0,0 +1,18 @@
START
File: test_multi/testcase_network_01.in
Event type: AA_RECORD_DENIED
Audit ID: 1308766940.698:3704
Operation: sendmsg
Profile: /usr/bin/evince-thumbnailer
Command: evince-thumbnai
Parent: 24737
PID: 24743
Network family: inet
Socket type: stream
Protocol: tcp
Local addr: 192.168.66.150
Foreign addr: 192.168.66.200
Local port: 765
Foreign port: 2049
Epoch: 1308766940
Audit subid: 3704

View File

@@ -0,0 +1 @@
Apr 5 19:31:04 precise-amd64 kernel: [153073.826757] type=1400 audit(1308766940.698:3704): apparmor="DENIED" operation="sendmsg" parent=24737 profile="/usr/bin/evince-thumbnailer" pid=24743 comm="evince-thumbnai" lport=765 fport=2049 family="inet" sock_type="stream" protocol=6

View File

@@ -0,0 +1,16 @@
START
File: test_multi/testcase_network_02.in
Event type: AA_RECORD_DENIED
Audit ID: 1308766940.698:3704
Operation: sendmsg
Profile: /usr/bin/evince-thumbnailer
Command: evince-thumbnai
Parent: 24737
PID: 24743
Network family: inet
Socket type: stream
Protocol: tcp
Local port: 765
Foreign port: 2049
Epoch: 1308766940
Audit subid: 3704

View File

@@ -0,0 +1 @@
type=AVC msg=audit(1333648169.009:11707146): apparmor="ALLOWED" operation="accept" parent=25932 profile="/usr/lib/dovecot/imap-login" pid=5049 comm="imap-login" lport=143 family="inet6" sock_type="stream" protocol=6

View File

@@ -0,0 +1,15 @@
START
File: test_multi/testcase_network_03.in
Event type: AA_RECORD_ALLOWED
Audit ID: 1333648169.009:11707146
Operation: accept
Profile: /usr/lib/dovecot/imap-login
Command: imap-login
Parent: 25932
PID: 5049
Network family: inet6
Socket type: stream
Protocol: tcp
Local port: 143
Epoch: 1333648169
Audit subid: 11707146

View File

@@ -0,0 +1 @@
type=AVC msg=audit(1333697181.284:273901): apparmor="DENIED" operation="recvmsg" parent=1596 profile="/home/ubuntu/tmp/nc" pid=1056 comm="nc" laddr=::1 lport=2048 faddr=::1 fport=33986 family="inet6" sock_type="stream" protocol=6

View File

@@ -0,0 +1,18 @@
START
File: test_multi/testcase_network_04.in
Event type: AA_RECORD_DENIED
Audit ID: 1333697181.284:273901
Operation: recvmsg
Profile: /home/ubuntu/tmp/nc
Command: nc
Parent: 1596
PID: 1056
Network family: inet6
Socket type: stream
Protocol: tcp
Local addr: ::1
Foreign addr: ::1
Local port: 2048
Foreign port: 33986
Epoch: 1333697181
Audit subid: 273901

View File

@@ -0,0 +1 @@
type=AVC msg=audit(1333698107.128:273917): apparmor="DENIED" operation="recvmsg" parent=1596 profile="/home/ubuntu/tmp/nc" pid=1875 comm="nc" laddr=::ffff:127.0.0.1 lport=2048 faddr=::ffff:127.0.0.1 fport=59180 family="inet6" sock_type="stream" protocol=6

View File

@@ -0,0 +1,18 @@
START
File: test_multi/testcase_network_05.in
Event type: AA_RECORD_DENIED
Audit ID: 1333698107.128:273917
Operation: recvmsg
Profile: /home/ubuntu/tmp/nc
Command: nc
Parent: 1596
PID: 1875
Network family: inet6
Socket type: stream
Protocol: tcp
Local addr: ::ffff:127.0.0.1
Foreign addr: ::ffff:127.0.0.1
Local port: 2048
Foreign port: 59180
Epoch: 1333698107
Audit subid: 273917

View File

@@ -76,8 +76,8 @@ EXTRA_CFLAGS+=-DSUBDOMAIN_CONFDIR=\"${CONFDIR}\"
SRCS = parser_common.c parser_include.c parser_interface.c parser_lex.c \
parser_main.c parser_misc.c parser_merge.c parser_symtab.c \
parser_yacc.c parser_regex.c parser_variable.c parser_policy.c \
parser_alias.c
HDRS = parser.h parser_include.h immunix.h
parser_alias.c mount.c
HDRS = parser.h parser_include.h immunix.h mount.h
TOOLS = apparmor_parser
OBJECTS = $(SRCS:.c=.o)
@@ -200,26 +200,25 @@ parser_alias.o: parser_alias.c parser.h
parser_common.o: parser_common.c parser.h
$(CC) $(EXTRA_CFLAGS) -c -o $@ $<
mount.o: mount.c mount.h parser.h immunix.h
$(CC) $(EXTRA_CFLAGS) -c -o $@ $<
parser_version.h: Makefile
@echo \#define PARSER_VERSION \"$(VERSION)\" > .ver
@mv -f .ver $@
# These are the families that it doesn't make sense for apparmor to mediate.
# We use PF_ here since that is what is required in bits/socket.h, but we will
# rewrite these as AF_.
FILTER_FAMILIES=PF_MAX PF_UNSPEC PF_UNIX PF_LOCAL PF_NETLINK
__FILTER=$(shell echo $(strip $(FILTER_FAMILIES)) | sed -e 's/ /\\\|/g')
# af_names and capabilities generation has moved to common/Make.rules,
# as well as the filtering that occurs for network protocols that
# apparmor should not mediate.
.PHONY: af_names.h
af_names.h:
echo "#include <sys/socket.h>" | cpp -dM | LC_ALL=C sed -n -e '/$(__FILTER)/d' -e "s/^\#define[ \\t]\\+PF_\\([A-Z0-9_]\\+\\)[ \\t]\\+\\([0-9]\\+\\)\\(.*\\)\$$/#ifndef AF_\\1\\n# define AF_\\1 \\2\\n#endif\\nAA_GEN_NET_ENT(\"\\L\\1\", \\UAF_\\1)\\n/p" > $@
echo "#include <sys/socket.h>" | cpp -dM | LC_ALL=C sed -n -e "s/^\#define[ \\t]\\+PF_MAX[ \\t]\\+\\([0-9]\\+\\)\\+.*/#define AA_AF_MAX \\1\n/p" >> $@
echo "$(AF_NAMES)" | LC_ALL=C sed -n -e 's/[ \t]\?AF_MAX[ \t]\+[0-9]\+,//g' -e 's/[ \t]\+\?AF_\([A-Z0-9_]\+\)[ \t]\+\([0-9]\+\),/#ifndef AF_\1\n# define AF_\1 \2\n#endif\nAA_GEN_NET_ENT("\L\1", \UAF_\1)\n\n/pg' > $@
echo "$(AF_NAMES)" | LC_ALL=C sed -n -e 's/.*,[ \t]\+AF_MAX[ \t]\+\([0-9]\+\),\?.*/#define AA_AF_MAX \1\n/p' >> $@
# cat $@
cap_names.h: /usr/include/linux/capability.h
LC_ALL=C sed -n -e "/CAP_EMPTY_SET/d" -e "s/^\#define[ \\t]\\+CAP_\\([A-Z0-9_]\\+\\)[ \\t]\\+\\([0-9xa-f]\\+\\)\\(.*\\)\$$/\{\"\\L\\1\", \\UCAP_\\1\},/p" $< > $@
echo "$(CAPABILITIES)" | LC_ALL=C sed -n -e "s/[ \\t]\\?CAP_\\([A-Z0-9_]\\+\\)/\{\"\\L\\1\", \\UCAP_\\1\},\\n/pg" > $@
tst_%: parser_%.c parser.h $(filter-out parser_%.o, ${TEST_OBJECTS})
$(CC) $(TEST_CFLAGS) -o $@ $< $(filter-out $(<:.c=.o), ${TEST_OBJECTS}) $(TEST_LDFLAGS)

View File

@@ -3,7 +3,7 @@
# 2008, 2009
# NOVELL (All rights reserved)
#
# Copyright (c) 2010
# Copyright (c) 2010 - 2012
# Canonical Ltd. (All rights reserved)
#
# This program is free software; you can redistribute it and/or
@@ -54,7 +54,7 @@ B<COMMENT> = '#' I<TEXT>
B<TEXT> = any characters
B<PROFILE> = [ I<COMMENT> ... ] [ I<VARIABLE ASSIGNMENT> ... ] ( '"' I<PROGRAM> '"' | I<PROGRAM> ) [ 'flags=(complain)' ]'{' [ ( I<RESOURCE RULE> | I<COMMENT> | I<INCLUDE> | I<SUBPROFILE> | 'capability ' I<CAPABILITY> | I<NETWORK RULE> | 'change_profile -> ' I<PROGRAMCHILD> ) ... ] '}'
B<PROFILE> = [ I<COMMENT> ... ] [ I<VARIABLE ASSIGNMENT> ... ] ( '"' I<PROGRAM> '"' | I<PROGRAM> ) [ 'flags=(complain)' ]'{' [ ( I<RESOURCE RULE> | I<COMMENT> | I<INCLUDE> | I<SUBPROFILE> | 'capability ' I<CAPABILITY> | I<NETWORK RULE> | I<MOUNT RULE> | I<FILE RULE> | 'change_profile -> ' I<PROGRAMCHILD> ) ... ] '}'
B<SUBPROFILE> = [ I<COMMENT> ... ] ( I<PROGRAMHAT> | 'profile ' I<PROGRAMCHILD> ) '{' [ ( I<FILE RULE> | I<COMMENT> | I<INCLUDE> ) ... ] '}'
@@ -75,11 +75,37 @@ B<PROGRAMHAT> = '^' (non-whitespace characters; see aa_change_hat(2) for a desc
B<PROGRAMCHILD> = I<SUBPROFILE> name
B<MOUNT RULE> = ( I<MOUNT> | I<REMOUNT> | I<UMOUNT> | I<PIVOT ROOT> )
B<MOUNT> = [ 'audit' ] [ 'deny' ] 'mount' [ I<MOUNT CONDITIONS> ] [ I<SOURCE FILEGLOB> ] [ -> [ I<MOUNTPOINT FILEGLOB> ]
B<REMOUNT> = [ 'audit' ] [ 'deny' ] 'remount' [ I<MOUNT CONDITIONS> ] I<MOUNTPOINT FILEGLOB>
B<UMOUNT> = [ 'audit' ] [ 'deny' ] 'umount' [ I<MOUNT CONDITIONS> ] I<MOUNTPOINT FILEGLOB>
B<PIVOT ROOT> = [ 'audit' ] [ 'deny' ] pivot_root [ I<OLD ABS PATH> ] [ I<MOUNTPOINT ABS PATH> ] [ -> I<PROGRAMCHILD> ]
B<MOUNT CONDITIONS> = [ ( 'fstype' | 'vfstype' ) ( '=' | 'in' ) I<MOUNT FSTYPE EXPRESSION> ] [ 'options' ( '=' | 'in' ) I<MOUNT FLAGS EXPRESSION> ]
B<MOUNT FSTYPE EXPRESSION> = ( I<MOUNT FSTYPE LIST> | I<MOUNT EXPRESSION> )
B<MOUNT FSTYPE LIST> = Comma separated list of valid filesystem and virtual filesystem types (eg ext4, debugfs, devfs, etc)
B<MOUNT FLAGS EXPRESSION> = ( I<MOUNT FLAGS LIST> | I<MOUNT EXPRESSION> )
B<MOUNT FLAGS LIST> = Comma separated list of I<MOUNT FLAGS>.
B<MOUNT FLAGS> = ( 'ro' | 'rw' | 'nosuid' | 'suid' | 'nodev' | 'dev' | 'noexec' | 'exec' | 'sync' | 'async' | 'remount' | 'mand' | 'nomand' | 'dirsync' | 'nodirsync' | 'noatime' | 'atime' | 'nodiratime' | 'diratime' | 'bind' | 'move' | 'rec' | 'verbose' | 'silent' | 'load' | 'acl' | 'noacl' | 'unbindable' | 'private' | 'slave' | 'shared' | 'relative' | 'norelative' | 'iversion' | 'noiversion' | 'strictatime' | 'nouser' | 'user' )
B<MOUNT EXPRESSION> = ( I<ALPHANUMERIC> | I<AARE> ) ...
B<AARE> = B<?*[]{}^> (see below for meanings)
B<FILE RULE> = I<RULE QUALIFIER> ( '"' I<FILEGLOB> '"' | I<FILEGLOB> ) I<ACCESS> ','
B<RULE QUALIFIER> = [ 'audit' ] [ 'deny' ] [ 'owner' ]
B<FILEGLOB> = (must start with '/' (after variable expansion), B<?*[]{}^> have special meanings; see below. May include I<VARIABLE>. Rules with embedded spaces or tabs must be quoted. Rules must end with '/' to apply to directories.)
B<FILEGLOB> = (must start with '/' (after variable expansion), B<AARE> have special meanings; see below. May include I<VARIABLE>. Rules with embedded spaces or tabs must be quoted. Rules must end with '/' to apply to directories.)
B<ACCESS> = ( 'r' | 'w' | 'l' | 'ix' | 'ux' | 'Ux' | 'px' | 'Px' | 'cx -> ' I<PROGRAMCHILD> | 'Cx -> ' I<PROGRAMCHILD> | 'm' ) [ I<ACCESS> ... ] (not all combinations are allowed; see below.)
@@ -303,10 +329,6 @@ access is not granted, some capabilities allow loading kernel modules,
arbitrary access to IPC, ability to bypass discretionary access controls,
and other operations that are typically reserved for the root user.
The only operations that cannot be controlled in this manner are mount(2),
umount(2), and loading new AppArmor policy into the kernel, which are
always denied to confined processes.
=head2 Network Rules
AppArmor supports simple coarse grained network mediation. The network
@@ -328,6 +350,281 @@ eg.
network inet tcp, #allow access to tcp only for inet4 addresses
network inet6 tcp, #allow access to tcp only for inet6 addresses
=head2 Mount Rules
AppArmor supports mount mediation and allows specifying filesystem types and
mount flags. The syntax of mount rules in AppArmor is based on the mount(8)
command syntax. Mount rules must contain one of the mount, remount, umount or
pivot_root keywords, but all mount conditions are optional. Unspecified
optional conditionals are assumed to match all entries (eg, not specifying
fstype means all fstypes are matched). Due to the complexity of the mount
command and how options may be specified, AppArmor allows specifying
conditionals three different ways:
=over 4
=item 1.
If a conditional is specified using '=', then the rule only grants permission
for mounts matching the exactly specified options. For example, an AppArmor
policy with the following rule:
=over 4
mount options=ro /dev/foo -> /mnt/,
=back
Would match:
=over 4
$ mount -o ro /dev/foo /mnt
=back
but not either of these:
=over 4
$ mount -o ro,atime /dev/foo /mnt
$ mount -o rw /dev/foo /mnt
=back
=item 2.
If a conditional is specified using 'in', then the rule grants permission for
mounts matching any combination of the specified options. For example, if an
AppArmor policy has the following rule:
=over 4
mount options in (ro,atime) /dev/foo -> /mnt/,
=back
all of these mount commands will match:
=over 4
$ mount -o ro /dev/foo /mnt
$ mount -o ro,atime /dev/foo /mnt
$ mount -o atime /dev/foo /mnt
=back
but none of these will:
=over 4
$ mount -o ro,sync /dev/foo /mnt
$ mount -o ro,atime,sync /dev/foo /mnt
$ mount -o rw /dev/foo /mnt
$ mount -o rw,noatime /dev/foo /mnt
$ mount /dev/foo /mnt
=back
=item 3.
If multiple conditionals are specified in a single mount rule, then the rule
grants permission for each set of options. This provides a shorthand when
writing mount rules which might help to logically break up a conditional. For
example, if an AppArmor policy has the following rule:
=over 4
mount options=ro options=atime
=back
both of these mount commands will match:
=over 4
$ mount -o ro /dev/foo /mnt
$ mount -o atime /dev/foo /mnt
=back
but this one will not:
=over 4
$ mount -o ro,atime /dev/foo /mnt
=back
=back
Note that separate mount rules are distinct and the options do not accumulate.
For example, these AppArmor mount rules:
=over 4
mount options=ro,
mount options=atime,
=back
are not equivalent to either of these mount rules:
=over 4
mount options=(ro,atime),
mount options in (ro,atime),
=back
To help clarify the flexibility and complexity of mount rules, here are some
example rules with accompanying matching commands:
=over 4
=item B<mount,>
the 'mount' rule without any conditionals is the most generic and allows any
mount. Equivalent to 'mount fstype=** options=** ** -> /**'.
=item B<mount /dev/foo,>
allow mounting of /dev/foo anywhere with any options. Some matching mount
commands:
=over 4
$ mount /dev/foo /mnt
$ mount -t ext3 /dev/foo /mnt
$ mount -t vfat /dev/foo /mnt
$ mount -o ro,atime,noexec,nodiratime /dev/foo /srv/some/mountpoint
=back
=item B<mount options=ro /dev/foo,>
allow mounting of /dev/foo anywhere, as read only. Some matching mount
commands:
=over 4
$ mount -o ro /dev/foo /mnt
$ mount -o ro /dev/foo /some/where/else
=back
=item B<mount options=(ro,atime) /dev/foo,>
allow mount of /dev/foo anywhere, as read only and using inode access times.
Some matching mount commands:
=over 4
$ mount -o ro,atime /dev/foo /mnt
$ mount -o ro,atime /dev/foo /some/where/else
=back
=item B<mount options in (ro,atime) /dev/foo,>
allow mount of /dev/foo anywhere using some combination of 'ro' and 'atime'
(see above). Some matching mount commands:
=over 4
$ mount -o ro /dev/foo /mnt
$ mount -o atime /dev/foo /some/where/else
$ mount -o ro,atime /dev/foo /some/other/place
=back
=item B<mount options=ro /dev/foo, mount options=atime /dev/foo,>
allow mount of /dev/foo anywhere as read only, and allow mount of /dev/foo
anywhere using inode access times. Note this is expressed as two different
rules. Matches:
=over 4
$ mount -o ro /dev/foo /mnt/1
$ mount -o atime /dev/foo /mnt/2
=back
=item B<< mount -> /mnt/**, >>
allow mounting anything under a directory in /mnt/**. Some matching mount
commands:
=over 4
$ mount /dev/foo1 /mnt/1
$ mount -o ro,atime,noexec,nodiratime /dev/foo2 /mnt/deep/path/foo2
=back
=item B<< mount options=ro -> /mnt/**, >>
allow mounting anything under /mnt/**, as read only. Some matching mount
commands:
=over 4
$ mount -o ro /dev/foo1 /mnt/1
$ mount -o ro /dev/foo2 /mnt/deep/path/foo2
=back
=item B<< mount fstype=ext3 options=(rw,atime) /dev/sdb1 -> /mnt/stick/, >>
allow mounting an ext3 filesystem in /dev/sdb1 on /mnt/stick as read/write and
using inode access times. Matches only:
=over 4
$ mount -o rw,atime /dev/sdb1 /mnt/stick
=back
=item B<< mount options=(ro, atime) options in (nodev, user) /dev/foo -> /mnt/, >>
allow mounting /dev/foo on /mmt/ read only and using inode access times or
allow mounting /dev/foo on /mnt/ with some combination of 'nodev' and 'user'.
Matches only:
=over 4
$ mount -o ro,atime /dev/foo /mnt
$ mount -o nodev /dev/foo /mnt
$ mount -o user /dev/foo /mnt
$ mount -o nodev,user /dev/foo /mnt
=back
=back
=head2 Variables
AppArmor's policy language allows embedding variables into file rules
@@ -605,6 +902,29 @@ An example AppArmor profile:
=back
=head1 KNOWN BUGS
=over 4
Mount options support the use of pattern matching but mount flags are not
correctly intersected against specified patterns. Eg, 'mount options=**,'
should be equivalent to 'mount,', but it is not. (LP: #965690)
The fstype may not be matched against when certain mount command flags are
used. Specifically fstype matching currently only works when creating a new
mount and not remount, bind, etc.
Mount rules with multiple 'options' conditionals are not applied as documented
but instead merged such that 'options in (ro,nodev) options in (atime)' is
equivalent to 'options in (ro,nodev,atime)'.
When specifying mount options with the 'in' conditional, both the positive and
negative values match when specifying one or the other. Eg, 'rw' matches when
'ro' is specified and 'dev' matches when 'nodev' is specified such that
'options in (ro,nodev)' is equivalent to 'options in (rw,dev)'.
=back
=head1 SEE ALSO
apparmor(7), apparmor_parser(8), aa-complain(1),

View File

@@ -3,7 +3,7 @@
# 2008, 2009
# NOVELL (All rights reserved)
#
# Copyright (c) 2010
# Copyright (c) 2010 - 2012
# Canonical Ltd. (All rights reserved)
#
# This program is free software; you can redistribute it and/or
@@ -16,7 +16,7 @@
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, contact Novell, Inc.
# along with this program; if not, contact Canonical, Ltd.
# ----------------------------------------------------------------------
@@ -28,13 +28,11 @@ apparmor.vim - vim syntax highlighting file for AppArmor profiles
=head1 SYNOPSIS
The SUSE vim package is configured to automatically use syntax
highlighting for AppArmor policies stored in /etc/apparmor.d/ and the
extra policies stored in /etc/apparmor/profiles/extras/. If you wish to
use the syntax highlighting in a specific vim session, you may run:
Your system may be configured to automatically use syntax highlighting
for installed AppArmor policies. If not, you can enable syntax highlighting in
a specific vim session by performing:
:syntax on
:setf apparmor
:set syntax=apparmor
=head1 DESCRIPTION

View File

@@ -3,7 +3,7 @@
# 2008, 2009
# NOVELL (All rights reserved)
#
# Copyright (c) 2010
# Copyright (c) 2010 - 2012
# Canonical Ltd. (All rights reserved)
#
# This program is free software; you can redistribute it and/or
@@ -127,16 +127,21 @@ Perform no caching at all: disables -W, implies -T.
=item -T, --skip-read-cache
By default, if a profile's cache is found in /etc/apparmor.d/cache/ and
the timestamp is newer than the profile, it will be loaded from the cache.
This option disables this cache loading behavior.
By default, if a profile's cache is found in the location specified by
--cache-loc and the timestamp is newer than the profile, it will be loaded
from the cache. This option disables this cache loading behavior.
=item -W, --write-cache
Write out cached profiles to /etc/apparmor.d/cache/. Off by default.
In cases where abstractions have been changed, and the parser is running
with "--replace", it may make sense to also use "--skip-read-cache" with
the "--write-cache" option.
Write out cached profiles to the location specified in --cache-loc. Off
by default. In cases where abstractions have been changed, and the parser
is running with "--replace", it may make sense to also use
"--skip-read-cache" with the "--write-cache" option.
=item -L, --cache-loc
Set the location of the cache directory. If not specified the cache location
defaults to /etc/apparmor.d/cache
=item -Q, --skip-kernel-load

View File

@@ -61,6 +61,7 @@
#define AA_PTRACE_PERMS (AA_USER_PTRACE | AA_OTHER_PTRACE)
#define AA_CHANGE_HAT (1 << 30)
#define AA_ONEXEC (1 << 30)
#define AA_CHANGE_PROFILE (1 << 31)
#define AA_SHARED_PERMS (AA_CHANGE_HAT | AA_CHANGE_PROFILE)
@@ -96,6 +97,9 @@
#define ALL_AA_EXEC_TYPE (AA_USER_EXEC_TYPE | AA_OTHER_EXEC_TYPE)
#define ALL_USER_EXEC (AA_USER_EXEC | AA_USER_EXEC_TYPE)
#define ALL_OTHER_EXEC (AA_OTHER_EXEC | AA_OTHER_EXEC_TYPE)
#define AA_LINK_BITS ((AA_MAY_LINK << AA_USER_SHIFT) | \
(AA_MAY_LINK << AA_OTHER_SHIFT))

View File

@@ -4,7 +4,7 @@
TARGET=libapparmor_re.a
CFLAGS ?= -g -Wall -O2 ${EXTRA_CFLAGS}
CXXFLAGS := ${CFLAGS}
CXXFLAGS := ${CFLAGS} -std=c++0x
ARFLAGS=-rcs
@@ -12,16 +12,16 @@ BISON := bison
all : ${TARGET}
libapparmor_re.a: parse.o expr-tree.o hfa.o compressed_hfa.o aare_rules.o
libapparmor_re.a: parse.o expr-tree.o hfa.o chfa.o aare_rules.o
ar ${ARFLAGS} $@ $^
expr-tree.o: expr-tree.cc expr-tree.h
hfa.o: hfa.cc apparmor_re.h hfa.h ../immunix.h
aare_rules.o: aare_rules.cc aare_rules.h apparmor_re.h expr-tree.h hfa.h compressed_hfa.h parse.h ../immunix.h
aare_rules.o: aare_rules.cc aare_rules.h apparmor_re.h expr-tree.h hfa.h chfa.h parse.h ../immunix.h
compressed_hfa.o: compressed_hfa.cc compressed_hfa.h ../immunix.h
chfa.o: chfa.cc chfa.h ../immunix.h
parse.o : parse.cc apparmor_re.h expr-tree.h

View File

@@ -1,7 +1,7 @@
/*
* (C) 2006, 2007 Andreas Gruenbacher <agruen@suse.de>
* Copyright (c) 2003-2008 Novell, Inc. (All rights reserved)
* Copyright 2009-2010 Canonical Ltd.
* 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
@@ -31,7 +31,7 @@
#include "expr-tree.h"
#include "parse.h"
#include "hfa.h"
#include "compressed_hfa.h"
#include "chfa.h"
#include "../immunix.h"
struct aare_ruleset {
@@ -98,6 +98,7 @@ extern "C" int aare_add_rule_vec(aare_ruleset_t *rules, int deny,
{
Node *tree = NULL, *accept;
int exact_match;
uint32_t allow = perms;
assert(perms != 0);
@@ -220,7 +221,11 @@ extern "C" int aare_add_rule_vec(aare_ruleset_t *rules, int deny,
}
cerr << " -> ";
tree->dump(cerr);
cerr << "\n\n";
if (deny)
cerr << " deny";
cerr << " (0x" << hex << allow <<"/" << audit << dec << ")";
accept->dump(cerr);
cerr << "\n\n";
}
if (rules->root)
@@ -270,6 +275,23 @@ extern "C" void *aare_create_dfa(aare_ruleset_t *rules, size_t *size,
if (flags & DFA_DUMP_MIN_UNIQ_PERMS)
dfa.dump_uniq_perms("minimized dfa");
}
if (flags & DFA_CONTROL_FILTER_DENY &&
flags & DFA_CONTROL_MINIMIZE &&
dfa.apply_and_clear_deny()) {
/* Do a second minimization pass as removal of deny
* information has moved some states from accepting
* to none accepting partitions
*
* TODO: add this as a tail pass to minimization
* so we don't need to do a full second pass
*/
dfa.minimize(flags);
if (flags & DFA_DUMP_MIN_UNIQ_PERMS)
dfa.dump_uniq_perms("minimized dfa");
}
if (flags & DFA_CONTROL_REMOVE_UNREACHABLE)
dfa.remove_unreachable(flags);
@@ -291,10 +313,10 @@ extern "C" void *aare_create_dfa(aare_ruleset_t *rules, size_t *size,
} else if (flags & DFA_DUMP_EQUIV)
cerr << "\nDFA did not generate an equivalence class\n";
TransitionTable transition_table(dfa, eq, flags);
CHFA chfa(dfa, eq, flags);
if (flags & DFA_DUMP_TRANS_TABLE)
transition_table.dump(cerr);
transition_table.flex_table(stream, "");
chfa.dump(cerr);
chfa.flex_table(stream, "");
}
catch(int error) {
*size = 0;

View File

@@ -1,7 +1,7 @@
/*
* (C) 2006, 2007 Andreas Gruenbacher <agruen@suse.de>
* Copyright (c) 2003-2008 Novell, Inc. (All rights reserved)
* Copyright 2009-2010 Canonical Ltd.
* 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

View File

@@ -1,7 +1,7 @@
/*
* (C) 2006, 2007 Andreas Gruenbacher <agruen@suse.de>
* Copyright (c) 2003-2008 Novell, Inc. (All rights reserved)
* Copyright 2009-2010 Canonical Ltd.
* 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
@@ -26,7 +26,7 @@ typedef enum dfaflags {
DFA_CONTROL_TREE_LEFT = 1 << 3,
DFA_CONTROL_MINIMIZE = 1 << 4,
DFA_CONTROL_MINIMIZE_HASH_TRANS = 1 << 5,
DFA_CONTROL_MINIMIZE_HASH_PERMS = 1 << 6,
DFA_CONTROL_FILTER_DENY = 1 << 6,
DFA_CONTROL_REMOVE_UNREACHABLE = 1 << 7,
DFA_CONTROL_TRANS_HIGH = 1 << 8,

View File

@@ -1,7 +1,7 @@
/*
* (C) 2006, 2007 Andreas Gruenbacher <agruen@suse.de>
* Copyright (c) 2003-2008 Novell, Inc. (All rights reserved)
* Copyright 2009-2010 Canonical Ltd.
* 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
@@ -30,9 +30,10 @@
#include <string.h>
#include "hfa.h"
#include "compressed_hfa.h"
#include "chfa.h"
#include "../immunix.h"
void TransitionTable::init_free_list(vector<pair<size_t, size_t> > &free_list,
void CHFA::init_free_list(vector<pair<size_t, size_t> > &free_list,
size_t prev, size_t start)
{
for (size_t i = start; i < free_list.size(); i++) {
@@ -47,11 +48,10 @@ void TransitionTable::init_free_list(vector<pair<size_t, size_t> > &free_list,
/**
* new Construct the transition table.
*/
TransitionTable::TransitionTable(DFA &dfa, map<uchar, uchar> &eq,
dfaflags_t flags): eq(eq)
CHFA::CHFA(DFA &dfa, map<uchar, uchar> &eq, dfaflags_t flags): eq(eq)
{
if (flags & DFA_DUMP_TRANS_PROGRESS)
fprintf(stderr, "Compressing trans table:\r");
fprintf(stderr, "Compressing HFA:\r");
if (eq.empty())
max_eq = 255;
@@ -74,14 +74,14 @@ TransitionTable::TransitionTable(DFA &dfa, map<uchar, uchar> &eq,
for (Partition::iterator i = dfa.states.begin(); i != dfa.states.end(); i++) {
if (*i == dfa.start || *i == dfa.nonmatching)
continue;
optimal += (*i)->cases.cases.size();
optimal += (*i)->trans.size();
if (flags & DFA_CONTROL_TRANS_HIGH) {
size_t range = 0;
if ((*i)->cases.cases.size())
if ((*i)->trans.size())
range =
(*i)->cases.cases.rbegin()->first -
(*i)->cases.begin()->first;
size_t ord = ((256 - (*i)->cases.cases.size()) << 8) | (256 - range);
(*i)->trans.rbegin()->first -
(*i)->trans.begin()->first;
size_t ord = ((256 - (*i)->trans.size()) << 8) | (256 - range);
/* reverse sort by entry count, most entries first */
order.insert(make_pair(ord, *i));
}
@@ -113,8 +113,8 @@ TransitionTable::TransitionTable(DFA &dfa, map<uchar, uchar> &eq,
for (Partition::iterator i = dfa.states.begin(); i != dfa.states.end(); i++) {
if (*i != dfa.nonmatching && *i != dfa.start) {
insert_state(free_list, *i, dfa);
accept[num.size()] = (*i)->accept;
accept2[num.size()] = (*i)->audit;
accept[num.size()] = (*i)->perms.allow;
accept2[num.size()] = PACK_AUDIT_CTL((*i)->perms.audit, (*i)->perms.quiet & (*i)->perms.deny);
num.insert(make_pair(*i, num.size()));
}
if (flags & (DFA_DUMP_TRANS_PROGRESS)) {
@@ -130,8 +130,8 @@ TransitionTable::TransitionTable(DFA &dfa, map<uchar, uchar> &eq,
if (i->second != dfa.nonmatching &&
i->second != dfa.start) {
insert_state(free_list, i->second, dfa);
accept[num.size()] = i->second->accept;
accept2[num.size()] = i->second->audit;
accept[num.size()] = i->second->perms.allow;
accept2[num.size()] = PACK_AUDIT_CTL(i->second->perms.audit, i->second->perms.quiet & i->second->perms.deny);
num.insert(make_pair(i->second, num.size()));
}
if (flags & (DFA_DUMP_TRANS_PROGRESS)) {
@@ -154,14 +154,14 @@ TransitionTable::TransitionTable(DFA &dfa, map<uchar, uchar> &eq,
}
/**
* Does <cases> fit into position <base> of the transition table?
* Does <trans> fit into position <base> of the transition table?
*/
bool TransitionTable::fits_in(vector<pair<size_t, size_t> > &free_list
bool CHFA::fits_in(vector<pair<size_t, size_t> > &free_list
__attribute__ ((unused)), size_t pos,
Cases &cases)
StateTrans &trans)
{
size_t c, base = pos - cases.begin()->first;
for (Cases::iterator i = cases.begin(); i != cases.end(); i++) {
size_t c, base = pos - trans.begin()->first;
for (StateTrans::iterator i = trans.begin(); i != trans.end(); i++) {
c = base + i->first;
/* if it overflows the next_check array it fits in as we will
* resize */
@@ -177,21 +177,21 @@ bool TransitionTable::fits_in(vector<pair<size_t, size_t> > &free_list
/**
* Insert <state> of <dfa> into the transition table.
*/
void TransitionTable::insert_state(vector<pair<size_t, size_t> > &free_list,
void CHFA::insert_state(vector<pair<size_t, size_t> > &free_list,
State *from, DFA &dfa)
{
State *default_state = dfa.nonmatching;
size_t base = 0;
int resize;
Cases &cases = from->cases;
size_t c = cases.begin()->first;
StateTrans &trans = from->trans;
size_t c = trans.begin()->first;
size_t prev = 0;
size_t x = first_free;
if (cases.otherwise)
default_state = cases.otherwise;
if (cases.cases.empty())
if (from->otherwise)
default_state = from->otherwise;
if (trans.empty())
goto do_insert;
repeat:
@@ -203,16 +203,16 @@ repeat:
}
/* try inserting until we succeed. */
while (x && !fits_in(free_list, x, cases)) {
while (x && !fits_in(free_list, x, trans)) {
prev = x;
x = free_list[x].second;
}
if (!x) {
resize = 256 - cases.begin()->first;
resize = 256 - trans.begin()->first;
x = free_list.size();
/* set prev to last free */
} else if (x + 255 - cases.begin()->first >= next_check.size()) {
resize = (255 - cases.begin()->first - (next_check.size() - 1 - x));
} else if (x + 255 - trans.begin()->first >= next_check.size()) {
resize = (255 - trans.begin()->first - (next_check.size() - 1 - x));
for (size_t y = x; y; y = free_list[y].second)
prev = y;
}
@@ -229,7 +229,7 @@ repeat:
}
base = x - c;
for (Cases::iterator j = cases.begin(); j != cases.end(); j++) {
for (StateTrans::iterator j = trans.begin(); j != trans.end(); j++) {
next_check[base + j->first] = make_pair(j->second, from);
size_t prev = free_list[base + j->first].first;
size_t next = free_list[base + j->first].second;
@@ -248,7 +248,7 @@ do_insert:
/**
* Text-dump the transition table (for debugging).
*/
void TransitionTable::dump(ostream &os)
void CHFA::dump(ostream &os)
{
map<size_t, const State *> st;
for (map<const State *, size_t>::iterator i = num.begin(); i != num.end(); i++) {
@@ -342,7 +342,7 @@ template<class Iter>
os << fill64(sizeof(td) + sizeof(*pos) * size);
}
void TransitionTable::flex_table(ostream &os, const char *name)
void CHFA::flex_table(ostream &os, const char *name)
{
const char th_version[] = "notflex";
struct table_set_header th = { 0, 0, 0, 0 };

View File

@@ -1,7 +1,7 @@
/*
* (C) 2006, 2007 Andreas Gruenbacher <agruen@suse.de>
* Copyright (c) 2003-2008 Novell, Inc. (All rights reserved)
* Copyright 2009-2010 Canonical Ltd.
* 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
@@ -16,10 +16,10 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
* Create a compressed hfa from and hfa
* Create a compressed hfa (chfa) from and hfa
*/
#ifndef __LIBAA_RE_COMPRESSED_HFA_H
#define __LIBAA_RE_COMPRESSED_HFA_H
#ifndef __LIBAA_RE_CHFA_H
#define __LIBAA_RE_CHFA_H
#include <map>
#include <vector>
@@ -28,17 +28,17 @@
using namespace std;
class TransitionTable {
class CHFA {
typedef vector<pair<const State *, size_t> > DefaultBase;
typedef vector<pair<const State *, const State *> > NextCheck;
public:
TransitionTable(DFA &dfa, map<uchar, uchar> &eq, dfaflags_t flags);
CHFA(DFA &dfa, map<uchar, uchar> &eq, dfaflags_t flags);
void dump(ostream & os);
void flex_table(ostream &os, const char *name);
void init_free_list(vector<pair<size_t, size_t> > &free_list,
size_t prev, size_t start);
bool fits_in(vector<pair<size_t, size_t> > &free_list, size_t base,
Cases &cases);
StateTrans &cases);
void insert_state(vector<pair<size_t, size_t> > &free_list,
State *state, DFA &dfa);
@@ -53,4 +53,4 @@ class TransitionTable {
size_t first_free;
};
#endif /* __LIBAA_RE_COMPRESSED_HFA_H */
#endif /* __LIBAA_RE_CHFA_H */

View File

@@ -1,7 +1,7 @@
/*
* (C) 2006, 2007 Andreas Gruenbacher <agruen@suse.de>
* Copyright (c) 2003-2008 Novell, Inc. (All rights reserved)
* Copyright 2009-2010 Canonical Ltd.
* 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
@@ -187,14 +187,22 @@ void normalize_tree(Node *t, int dir)
return;
for (;;) {
if ((&epsnode == t->child[dir]) &&
(&epsnode != t->child[!dir]) &&
dynamic_cast<TwoChildNode *>(t)) {
if (dynamic_cast<TwoChildNode *>(t) &&
(&epsnode == t->child[dir]) &&
(&epsnode != t->child[!dir])) {
// (E | a) -> (a | E)
// Ea -> aE
Node *c = t->child[dir];
t->child[dir] = t->child[!dir];
t->child[!dir] = c;
// Test for E | (E | E) and E . (E . E) which will
// result in an infinite loop
Node *c = t->child[!dir];
if (dynamic_cast<TwoChildNode *>(c) &&
&epsnode == c->child[dir] &&
&epsnode == c->child[!dir]) {
c->release();
c = &epsnode;
}
t->child[dir] = c;
t->child[!dir] = &epsnode;
// Don't break here as 'a' may be a tree that
// can be pulled up.
} else if ((dynamic_cast<AltNode *>(t) &&

View File

@@ -1,7 +1,7 @@
/*
* (C) 2006, 2007 Andreas Gruenbacher <agruen@suse.de>
* Copyright (c) 2003-2008 Novell, Inc. (All rights reserved)
* Copyright 2009-2010 Canonical Ltd.
* 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
@@ -81,17 +81,15 @@ ostream &operator<<(ostream &os, const NodeSet &state);
* (i.e., following an AnyCharNode or NotCharSetNode). This avoids
* enumerating all the explicit tranitions for default matches.
*/
typedef struct NodeCases {
typedef struct Cases {
typedef map<uchar, NodeSet *>::iterator iterator;
iterator begin() { return cases.begin(); }
iterator end() { return cases.end(); }
NodeCases(): otherwise(0) { }
Cases(): otherwise(0) { }
map<uchar, NodeSet *> cases;
NodeSet *otherwise;
}
NodeCases;
} Cases;
ostream &operator<<(ostream &os, Node &node);
@@ -205,7 +203,8 @@ public:
ImportantNode(): LeafNode() { }
void compute_firstpos() { firstpos.insert(this); }
void compute_lastpos() { lastpos.insert(this); }
virtual void follow(NodeCases &cases) = 0;
virtual void follow(Cases &cases) = 0;
virtual int is_accept(void) = 0;
};
/* common base class for all the different classes that contain
@@ -214,13 +213,14 @@ public:
class CNode: public ImportantNode {
public:
CNode(): ImportantNode() { }
int is_accept(void) { return false; }
};
/* Match one specific character (/c/). */
class CharNode: public CNode {
public:
CharNode(uchar c): c(c) { }
void follow(NodeCases &cases)
void follow(Cases &cases)
{
NodeSet **x = &cases.cases[c];
if (!*x) {
@@ -251,7 +251,7 @@ public:
class CharSetNode: public CNode {
public:
CharSetNode(Chars &chars): chars(chars) { }
void follow(NodeCases &cases)
void follow(Cases &cases)
{
for (Chars::iterator i = chars.begin(); i != chars.end(); i++) {
NodeSet **x = &cases.cases[*i];
@@ -292,7 +292,7 @@ public:
class NotCharSetNode: public CNode {
public:
NotCharSetNode(Chars &chars): chars(chars) { }
void follow(NodeCases & cases)
void follow(Cases &cases)
{
if (!cases.otherwise)
cases.otherwise = new NodeSet;
@@ -305,7 +305,7 @@ public:
* the old otherwise state for the matching characters.
*/
cases.otherwise->insert(followpos.begin(), followpos.end());
for (NodeCases::iterator i = cases.begin(); i != cases.end();
for (Cases::iterator i = cases.begin(); i != cases.end();
i++) {
if (chars.find(i->first) == chars.end())
i->second->insert(followpos.begin(),
@@ -340,12 +340,12 @@ public:
class AnyCharNode: public CNode {
public:
AnyCharNode() { }
void follow(NodeCases &cases)
void follow(Cases &cases)
{
if (!cases.otherwise)
cases.otherwise = new NodeSet;
cases.otherwise->insert(followpos.begin(), followpos.end());
for (NodeCases::iterator i = cases.begin(); i != cases.end();
for (Cases::iterator i = cases.begin(); i != cases.end();
i++)
i->second->insert(followpos.begin(), followpos.end());
}
@@ -365,6 +365,7 @@ public:
class AcceptNode: public ImportantNode {
public:
AcceptNode() { }
int is_accept(void) { return true; }
void release(void)
{
/* don't delete AcceptNode via release as they are shared, and
@@ -372,7 +373,7 @@ public:
*/
}
void follow(NodeCases &cases __attribute__ ((unused)))
void follow(Cases &cases __attribute__ ((unused)))
{
/* Nothing to follow. */
}
@@ -573,22 +574,6 @@ void label_nodes(Node *root);
unsigned long hash_NodeSet(NodeSet *ns);
void flip_tree(Node *node);
/* Comparison operator for sets of <NodeSet *>.
* Compare set hashes, and if the sets have the same hash
* do compare pointer comparison on set of <Node *>, the pointer comparison
* allows us to determine which Sets of <Node *> we have seen already from
* new ones when constructing the DFA.
*/
struct deref_less_than {
bool operator()(pair<unsigned long, NodeSet *>const &lhs,
pair<unsigned long, NodeSet *>const &rhs)const
{
if (lhs.first == rhs.first)
return *(lhs.second) < *(rhs.second);
else
return lhs.first < rhs.first;
}
};
class MatchFlag: public AcceptNode {
public:

View File

@@ -1,7 +1,7 @@
/*
* (C) 2006, 2007 Andreas Gruenbacher <agruen@suse.de>
* Copyright (c) 2003-2008 Novell, Inc. (All rights reserved)
* Copyright 2009-2010 Canonical Ltd.
* 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
@@ -30,11 +30,40 @@
#include <ostream>
#include <iostream>
#include <fstream>
#include <string.h>
#include "expr-tree.h"
#include "hfa.h"
#include "../immunix.h"
ostream &operator<<(ostream &os, const CacheStats &cache)
{
/* dump the state label */
os << "cache: size=";
os << cache.size();
os << " dups=";
os << cache.dup;
os << " longest=";
os << cache.max;
if (cache.size()) {
os << " avg=";
os << cache.sum / cache.size();
}
return os;
}
ostream &operator<<(ostream &os, const ProtoState &proto)
{
/* dump the state label */
os << '{';
os << proto.nnodes;
os << ',';
os << proto.anodes;
os << '}';
return os;
}
ostream &operator<<(ostream &os, const State &state)
{
/* dump the state label */
@@ -44,46 +73,48 @@ ostream &operator<<(ostream &os, const State &state)
return os;
}
State *DFA::add_new_state(NodeMap &nodemap,
pair<unsigned long, NodeSet *> index,
NodeSet *nodes, dfa_stats_t &stats)
static void split_node_types(NodeSet *nodes, NodeSet **anodes, NodeSet **nnodes
)
{
State *state = new State(nodemap.size(), nodes);
states.push_back(state);
nodemap.insert(make_pair(index, state));
stats.proto_sum += nodes->size();
if (nodes->size() > stats.proto_max)
stats.proto_max = nodes->size();
return state;
*anodes = *nnodes = NULL;
for (NodeSet::iterator i = nodes->begin(); i != nodes->end(); ) {
if ((*i)->is_accept()) {
if (!*anodes)
*anodes = new NodeSet;
(*anodes)->insert(*i);
NodeSet::iterator k = i++;
nodes->erase(k);
} else
i++;
}
*nnodes = nodes;
}
State *DFA::find_target_state(NodeMap &nodemap, list<State *> &work_queue,
NodeSet *nodes, dfa_stats_t &stats)
State *DFA::add_new_state(NodeSet *nodes, State *other)
{
State *target;
/* The splitting of nodes should probably get pushed down into
* follow(), ie. put in separate lists from the start
*/
NodeSet *anodes, *nnodes;
hashedNodeVec *nnodev;
split_node_types(nodes, &anodes, &nnodes);
nnodev = nnodes_cache.insert(nnodes);
anodes = anodes_cache.insert(anodes);
pair<unsigned long, NodeSet *> index = make_pair(hash_NodeSet(nodes), nodes);
map<pair<unsigned long, NodeSet *>, State *, deref_less_than>::iterator x = nodemap.find(index);
if (x == nodemap.end()) {
/* set of nodes isn't known so create new state, and nodes to
* state mapping
*/
target = add_new_state(nodemap, index, nodes, stats);
work_queue.push_back(target);
ProtoState proto(nnodev, anodes);
State *state = new State(node_map.size(), proto, other);
pair<NodeMap::iterator,bool> x = node_map.insert(proto, state);
if (x.second == false) {
delete state;
} else {
/* set of nodes already has a mapping so free this one */
stats.duplicates++;
delete(nodes);
target = x->second;
states.push_back(state);
work_queue.push_back(state);
}
return target;
return x.first->second;
}
void DFA::update_state_transitions(NodeMap &nodemap, list<State *> &work_queue,
State *state, dfa_stats_t &stats)
void DFA::update_state_transitions(State *state)
{
/* Compute possible transitions for state->nodes. This is done by
* iterating over all the nodes in state->nodes and combining the
@@ -91,9 +122,12 @@ void DFA::update_state_transitions(NodeMap &nodemap, list<State *> &work_queue,
*
* The resultant transition set is a mapping of characters to
* sets of nodes.
*
* Note: the follow set for accept nodes is always empty so we don't
* need to compute follow for the accept nodes in a protostate
*/
NodeCases cases;
for (NodeSet::iterator i = state->nodes->begin(); i != state->nodes->end(); i++)
Cases cases;
for (hashedNodeVec::iterator i = state->proto.nnodes->begin(); i != state->proto.nnodes->end(); i++)
(*i)->follow(cases);
/* Now for each set of nodes in the computed transitions, make
@@ -103,22 +137,22 @@ void DFA::update_state_transitions(NodeMap &nodemap, list<State *> &work_queue,
/* check the default transition first */
if (cases.otherwise)
state->cases.otherwise = find_target_state(nodemap, work_queue,
cases.otherwise,
stats);;
state->otherwise = add_new_state(cases.otherwise, nonmatching);
else
state->otherwise = nonmatching;
/* For each transition from *from, check if the set of nodes it
* transitions to already has been mapped to a state
*/
for (NodeCases::iterator j = cases.begin(); j != cases.end(); j++) {
for (Cases::iterator j = cases.begin(); j != cases.end(); j++) {
State *target;
target = find_target_state(nodemap, work_queue, j->second, stats);
target = add_new_state(j->second, nonmatching);
/* Don't insert transition that the default transition
/* Don't insert transition that the otherwise transition
* already covers
*/
if (target != state->cases.otherwise)
state->cases.cases[j->first] = target;
if (target != state->otherwise)
state->trans[j->first] = target;
}
}
@@ -131,7 +165,7 @@ void DFA::dump_node_to_dfa(void)
" State <= Nodes\n"
"-------------------\n";
for (Partition::iterator i = states.begin(); i != states.end(); i++)
cerr << " " << (*i)->label << " <= " << *(*i)->nodes << "\n";
cerr << " " << (*i)->label << " <= " << (*i)->proto << "\n";
}
/**
@@ -139,7 +173,6 @@ void DFA::dump_node_to_dfa(void)
*/
DFA::DFA(Node *root, dfaflags_t flags): root(root)
{
dfa_stats_t stats = { 0, 0, 0 };
int i = 0;
if (flags & DFA_DUMP_PROGRESS)
@@ -157,15 +190,8 @@ DFA::DFA(Node *root, dfaflags_t flags): root(root)
(*i)->compute_followpos();
}
NodeMap nodemap;
NodeSet *emptynode = new NodeSet;
nonmatching = add_new_state(nodemap,
make_pair(hash_NodeSet(emptynode), emptynode),
emptynode, stats);
NodeSet *first = new NodeSet(root->firstpos);
start = add_new_state(nodemap, make_pair(hash_NodeSet(first), first),
first, stats);
nonmatching = add_new_state(new NodeSet, NULL);
start = add_new_state(new NodeSet(root->firstpos), nonmatching);
/* the work_queue contains the states that need to have their
* transitions computed. This could be done with a recursive
@@ -177,14 +203,18 @@ DFA::DFA(Node *root, dfaflags_t flags): root(root)
* manner, this may help reduce the number of entries on the
* work_queue at any given time, thus reducing peak memory use.
*/
list<State *> work_queue;
work_queue.push_back(start);
while (!work_queue.empty()) {
if (i % 1000 == 0 && (flags & DFA_DUMP_PROGRESS))
fprintf(stderr, "\033[2KCreating dfa: queue %zd\tstates %zd\teliminated duplicates %d\r",
work_queue.size(), states.size(),
stats.duplicates);
if (i % 1000 == 0 && (flags & DFA_DUMP_PROGRESS)) {
cerr << "\033[2KCreating dfa: queue "
<< work_queue.size()
<< "\tstates "
<< states.size()
<< "\teliminated duplicates "
<< node_map.dup
<< "\r";
}
i++;
State *from = work_queue.front();
@@ -193,7 +223,7 @@ DFA::DFA(Node *root, dfaflags_t flags): root(root)
/* Update 'from's transitions, and if it transitions to any
* unknown State create it and add it to the work_queue
*/
update_state_transitions(nodemap, work_queue, from, stats);
update_state_transitions(from);
} /* while (!work_queue.empty()) */
@@ -209,34 +239,68 @@ DFA::DFA(Node *root, dfaflags_t flags): root(root)
if (flags & DFA_DUMP_NODE_TO_DFA)
dump_node_to_dfa();
for (NodeMap::iterator i = nodemap.begin(); i != nodemap.end(); i++)
delete i->first.second;
nodemap.clear();
if (flags & (DFA_DUMP_STATS))
fprintf(stderr, "\033[2KCreated dfa: states %zd,\teliminated duplicates %d,\tprotostate sets: longest %u, avg %u\n",
states.size(), stats.duplicates, stats.proto_max,
(unsigned int)(stats.proto_sum / states.size()));
if (flags & (DFA_DUMP_STATS)) {
cerr << "\033[2KCreated dfa: states "
<< states.size()
<< " proto { "
<< node_map
<< " }, nnodes { "
<< nnodes_cache
<< " }, anodes { "
<< anodes_cache
<< " }\n";
}
/* Clear out uniq_nnodes as they are no longer needed.
* Do not clear out uniq_anodes, as we need them for minimizations
* diffs, unions, ...
*/
nnodes_cache.clear();
node_map.clear();
}
DFA::~DFA()
{
anodes_cache.clear();
nnodes_cache.clear();
for (Partition::iterator i = states.begin(); i != states.end(); i++)
delete *i;
}
State *DFA::match_len(State *state, const char *str, size_t len)
{
for (; len > 0; ++str, --len)
state = state->next(*str);
return state;
}
State *DFA::match_until(State *state, const char *str, const char term)
{
while (*str != term)
state = state->next(*str++);
return state;
}
State *DFA::match(const char *str)
{
return match_until(start, str, 0);
}
void DFA::dump_uniq_perms(const char *s)
{
set<pair<uint32_t, uint32_t> > uniq;
set<perms_t> uniq;
for (Partition::iterator i = states.begin(); i != states.end(); i++)
uniq.insert(make_pair((*i)->accept, (*i)->audit));
uniq.insert((*i)->perms);
cerr << "Unique Permission sets: " << s << " (" << uniq.size() << ")\n";
cerr << "----------------------\n";
for (set<pair<uint32_t, uint32_t> >::iterator i = uniq.begin();
i != uniq.end(); i++) {
cerr << " " << hex << i->first << " " << i->second << dec << "\n";
for (set<perms_t >::iterator i = uniq.begin(); i != uniq.end(); i++) {
cerr << " allow:" << hex << i->allow << " deny:"
<< i->deny << " audit:" << i->audit
<< " quiet:" << i->quiet << dec << "\n";
}
}
@@ -244,7 +308,6 @@ void DFA::dump_uniq_perms(const char *s)
void DFA::remove_unreachable(dfaflags_t flags)
{
set<State *> reachable;
list<State *> work_queue;
/* find the set of reachable states */
reachable.insert(nonmatching);
@@ -254,11 +317,11 @@ void DFA::remove_unreachable(dfaflags_t flags)
work_queue.pop_front();
reachable.insert(from);
if (from->cases.otherwise &&
(reachable.find(from->cases.otherwise) == reachable.end()))
work_queue.push_back(from->cases.otherwise);
if (from->otherwise != nonmatching &&
reachable.find(from->otherwise) == reachable.end())
work_queue.push_back(from->otherwise);
for (Cases::iterator j = from->cases.begin(); j != from->cases.end(); j++) {
for (StateTrans::iterator j = from->trans.begin(); j != from->trans.end(); j++) {
if (reachable.find(j->second) == reachable.end())
work_queue.push_back(j->second);
}
@@ -277,12 +340,8 @@ void DFA::remove_unreachable(dfaflags_t flags)
cerr << "unreachable: " << **i;
if (*i == start)
cerr << " <==";
if ((*i)->accept) {
cerr << " (0x" << hex
<< (*i)->accept << " "
<< (*i)->audit << dec
<< ')';
}
if ((*i)->perms.is_accept())
(*i)->perms.dump(cerr);
cerr << "\n";
}
State *current = *i;
@@ -301,26 +360,17 @@ void DFA::remove_unreachable(dfaflags_t flags)
/* test if two states have the same transitions under partition_map */
bool DFA::same_mappings(State *s1, State *s2)
{
if (s1->cases.otherwise && s1->cases.otherwise != nonmatching) {
if (!s2->cases.otherwise || s2->cases.otherwise == nonmatching)
return false;
Partition *p1 = s1->cases.otherwise->partition;
Partition *p2 = s2->cases.otherwise->partition;
if (p1 != p2)
return false;
} else if (s2->cases.otherwise && s2->cases.otherwise != nonmatching) {
if (s1->otherwise->partition != s2->otherwise->partition)
return false;
}
if (s1->cases.cases.size() != s2->cases.cases.size())
if (s1->trans.size() != s2->trans.size())
return false;
for (Cases::iterator j1 = s1->cases.begin(); j1 != s1->cases.end(); j1++) {
Cases::iterator j2 = s2->cases.cases.find(j1->first);
if (j2 == s2->cases.end())
for (StateTrans::iterator j1 = s1->trans.begin(); j1 != s1->trans.end(); j1++) {
StateTrans::iterator j2 = s2->trans.find(j1->first);
if (j2 == s2->trans.end())
return false;
Partition *p1 = j1->second->partition;
Partition *p2 = j2->second->partition;
if (p1 != p2)
if (j1->second->partition != j2->second->partition)
return false;
}
@@ -330,7 +380,7 @@ bool DFA::same_mappings(State *s1, State *s2)
/* Do simple djb2 hashing against a States transition cases
* this provides a rough initial guess at state equivalence as if a state
* has a different number of transitions or has transitions on different
* cases they will never be equivalent.
* trans they will never be equivalent.
* Note: this only hashes based off of the alphabet (not destination)
* as different destinations could end up being equiv
*/
@@ -338,22 +388,31 @@ size_t DFA::hash_trans(State *s)
{
unsigned long hash = 5381;
for (Cases::iterator j = s->cases.begin(); j != s->cases.end(); j++) {
for (StateTrans::iterator j = s->trans.begin(); j != s->trans.end(); j++) {
hash = ((hash << 5) + hash) + j->first;
State *k = j->second;
hash = ((hash << 5) + hash) + k->cases.cases.size();
hash = ((hash << 5) + hash) + k->trans.size();
}
if (s->cases.otherwise && s->cases.otherwise != nonmatching) {
if (s->otherwise != nonmatching) {
hash = ((hash << 5) + hash) + 5381;
State *k = s->cases.otherwise;
hash = ((hash << 5) + hash) + k->cases.cases.size();
State *k = s->otherwise;
hash = ((hash << 5) + hash) + k->trans.size();
}
hash = (hash << 8) | s->cases.cases.size();
hash = (hash << 8) | s->trans.size();
return hash;
}
int DFA::apply_and_clear_deny(void)
{
int c = 0;
for (Partition::iterator i = states.begin(); i != states.end(); i++)
c += (*i)->apply_and_clear_deny();
return c;
}
/* minimize the number of dfa states */
void DFA::minimize(dfaflags_t flags)
{
@@ -373,19 +432,11 @@ void DFA::minimize(dfaflags_t flags)
int accept_count = 0;
int final_accept = 0;
for (Partition::iterator i = states.begin(); i != states.end(); i++) {
uint64_t perm_hash = 0;
if (flags & DFA_CONTROL_MINIMIZE_HASH_PERMS) {
/* make every unique perm create a new partition */
perm_hash = ((uint64_t) (*i)->audit) << 32 |
(uint64_t) (*i)->accept;
} else if ((*i)->audit || (*i)->accept) {
/* combine all perms together into a single parition */
perm_hash = 1;
} /* else not an accept state so 0 for perm_hash */
size_t trans_hash = 0;
size_t hash = 0;
uint64_t permtype = ((uint64_t) (PACK_AUDIT_CTL((*i)->perms.audit, (*i)->perms.quiet & (*i)->perms.deny)) << 32) | (uint64_t) (*i)->perms.allow;
if (flags & DFA_CONTROL_MINIMIZE_HASH_TRANS)
trans_hash = hash_trans(*i);
pair<uint64_t, size_t> group = make_pair(perm_hash, trans_hash);
hash |= hash_trans(*i);
pair<uint64_t, size_t> group = make_pair(permtype, hash);
map<pair<uint64_t, size_t>, Partition *>::iterator p = perm_map.find(group);
if (p == perm_map.end()) {
Partition *part = new Partition();
@@ -393,7 +444,7 @@ void DFA::minimize(dfaflags_t flags)
perm_map.insert(make_pair(group, part));
partitions.push_back(part);
(*i)->partition = part;
if (perm_hash)
if (permtype)
accept_count++;
} else {
(*i)->partition = p->second;
@@ -487,11 +538,9 @@ void DFA::minimize(dfaflags_t flags)
cerr << *rep << " : ";
/* update representative state's transitions */
if (rep->cases.otherwise) {
Partition *partition = rep->cases.otherwise->partition;
rep->cases.otherwise = *partition->begin();
}
for (Cases::iterator c = rep->cases.begin(); c != rep->cases.end(); c++) {
rep->otherwise = *rep->otherwise->partition->begin();
for (StateTrans::iterator c = rep->trans.begin(); c != rep->trans.end(); c++) {
Partition *partition = c->second->partition;
c->second = *partition->begin();
}
@@ -505,10 +554,9 @@ void DFA::minimize(dfaflags_t flags)
if (flags & DFA_DUMP_MIN_PARTS)
cerr << **i << ", ";
(*i)->label = -1;
rep->accept |= (*i)->accept;
rep->audit |= (*i)->audit;
rep->perms.add((*i)->perms);
}
if (rep->accept || rep->audit)
if (rep->perms.is_accept())
final_accept++;
//if ((*p)->size() > 1)
//cerr << "\n";
@@ -563,26 +611,52 @@ out:
void DFA::dump(ostream & os)
{
for (Partition::iterator i = states.begin(); i != states.end(); i++) {
if (*i == start || (*i)->accept) {
if (*i == start || (*i)->perms.is_accept()) {
os << **i;
if (*i == start)
os << " <==";
if ((*i)->accept) {
os << " (0x" << hex << (*i)->accept << " "
<< (*i)->audit << dec << ')';
}
os << " <== (allow/deny/audit/quiet)";
if ((*i)->perms.is_accept())
(*i)->perms.dump(os);
os << "\n";
}
}
os << "\n";
for (Partition::iterator i = states.begin(); i != states.end(); i++) {
if ((*i)->cases.otherwise)
os << **i << " -> " << (*i)->cases.otherwise << "\n";
for (Cases::iterator j = (*i)->cases.begin();
j != (*i)->cases.end(); j++) {
os << **i << " -> " << j->second << ": "
<< j->first << "\n";
Chars excluded;
for (StateTrans::iterator j = (*i)->trans.begin();
j != (*i)->trans.end(); j++) {
if (j->second == nonmatching) {
excluded.insert(j->first);
} else {
os << **i;
if ((*i)->perms.is_accept())
os << " ", (*i)->perms.dump(os);
os << " -> " << *(j)->second << ": 0x"
<< hex << (int) j->first;
if (isprint(j->first))
os << " " << j->first;
os << dec << "\n";
}
}
if ((*i)->otherwise != nonmatching) {
os << **i;
if ((*i)->perms.is_accept())
os << " ", (*i)->perms.dump(os);
os << " -> " << *(*i)->otherwise << ": [";
if (!excluded.empty()) {
os << "^";
for (Chars::iterator k = excluded.begin();
k != excluded.end(); k++) {
if (isprint(*k))
os << *k;
else
os << "\\0x" << hex << (int) *k << dec;
}
}
os << "]\n";
}
}
os << "\n";
@@ -603,35 +677,42 @@ void DFA::dump_dot_graph(ostream & os)
if (*i == start) {
os << "\t\tstyle=bold" << "\n";
}
uint32_t perms = (*i)->accept;
if (perms) {
os << "\t\tlabel=\"" << **i << "\\n("
<< perms << ")\"" << "\n";
if ((*i)->perms.is_accept()) {
os << "\t\tlabel=\"" << **i << "\\n";
(*i)->perms.dump(os);
os << "\"\n";
}
os << "\t]" << "\n";
}
for (Partition::iterator i = states.begin(); i != states.end(); i++) {
Cases &cases = (*i)->cases;
Chars excluded;
for (Cases::iterator j = cases.begin(); j != cases.end(); j++) {
for (StateTrans::iterator j = (*i)->trans.begin(); j != (*i)->trans.end(); j++) {
if (j->second == nonmatching)
excluded.insert(j->first);
else {
os << "\t\"" << **i << "\" -> \"" << *j->second
<< "\" [" << "\n";
os << "\t\tlabel=\"" << j->first << "\"\n";
os << "\t]" << "\n";
os << "\t\tlabel=\"";
if (isprint(j->first))
os << j->first;
else
os << "\\0x" << hex << (int) j->first << dec;
os << "\"\n\t]" << "\n";
}
}
if (cases.otherwise && cases.otherwise != nonmatching) {
os << "\t\"" << **i << "\" -> \"" << *cases.otherwise
if ((*i)->otherwise != nonmatching) {
os << "\t\"" << **i << "\" -> \"" << *(*i)->otherwise
<< "\" [" << "\n";
if (!excluded.empty()) {
os << "\t\tlabel=\"[^";
for (Chars::iterator i = excluded.begin();
i != excluded.end(); i++) {
os << *i;
if (isprint(*i))
os << *i;
else
os << "\\0x" << hex << (int) *i << dec;
}
os << "]\"" << "\n";
}
@@ -651,11 +732,9 @@ map<uchar, uchar> DFA::equivalence_classes(dfaflags_t flags)
uchar next_class = 1;
for (Partition::iterator i = states.begin(); i != states.end(); i++) {
Cases & cases = (*i)->cases;
/* Group edges to the same next state together */
map<const State *, Chars> node_sets;
for (Cases::iterator j = cases.begin(); j != cases.end(); j++)
for (StateTrans::iterator j = (*i)->trans.begin(); j != (*i)->trans.end(); j++)
node_sets[j->second].insert(j->first);
for (map<const State *, Chars>::iterator j = node_sets.begin();
@@ -742,10 +821,9 @@ void DFA::apply_equivalence_classes(map<uchar, uchar> &eq)
*/
for (Partition::iterator i = states.begin(); i != states.end(); i++) {
map<uchar, State *> tmp;
tmp.swap((*i)->cases.cases);
for (Cases::iterator j = tmp.begin(); j != tmp.end(); j++)
(*i)->cases.cases.
insert(make_pair(eq[j->first], j->second));
tmp.swap((*i)->trans);
for (StateTrans::iterator j = tmp.begin(); j != tmp.end(); j++)
(*i)->trans.insert(make_pair(eq[j->first], j->second));
}
}
@@ -789,81 +867,66 @@ static inline int diff_qualifiers(uint32_t perm1, uint32_t perm2)
* have any exact matches, then they override the execute and safe
* execute flags.
*/
uint32_t accept_perms(NodeSet *state, uint32_t *audit_ctl, int *error)
int accept_perms(NodeSet *state, perms_t &perms)
{
uint32_t perms = 0, exact_match_perms = 0;
uint32_t audit = 0, exact_audit = 0, quiet = 0, deny = 0;
int error = 0;
uint32_t exact_match_allow = 0;
uint32_t exact_audit = 0;
perms.clear();
if (!state)
return error;
if (error)
*error = 0;
for (NodeSet::iterator i = state->begin(); i != state->end(); i++) {
MatchFlag *match;
if (!(match = dynamic_cast<MatchFlag *>(*i)))
continue;
if (dynamic_cast<ExactMatchFlag *>(match)) {
/* exact match only ever happens with x */
if (!is_merged_x_consistent(exact_match_perms,
match->flag) && error)
*error = 1;;
exact_match_perms |= match->flag;
if (!is_merged_x_consistent(exact_match_allow,
match->flag))
error = 1;;
exact_match_allow |= match->flag;
exact_audit |= match->audit;
} else if (dynamic_cast<DenyMatchFlag *>(match)) {
deny |= match->flag;
quiet |= match->audit;
perms.deny |= match->flag;
perms.quiet |= match->audit;
} else {
if (!is_merged_x_consistent(perms, match->flag)
&& error)
*error = 1;
perms |= match->flag;
audit |= match->audit;
if (!is_merged_x_consistent(perms.allow, match->flag))
error = 1;
perms.allow |= match->flag;
perms.audit |= match->audit;
}
}
//if (audit || quiet)
//fprintf(stderr, "perms: 0x%x, audit: 0x%x exact: 0x%x eaud: 0x%x deny: 0x%x quiet: 0x%x\n", perms, audit, exact_match_perms, exact_audit, deny, quiet);
perms.allow |= exact_match_allow & ~(ALL_AA_EXEC_TYPE);
perms |= exact_match_perms & ~(AA_USER_EXEC_TYPE | AA_OTHER_EXEC_TYPE);
if (exact_match_perms & AA_USER_EXEC_TYPE) {
perms = (exact_match_perms & AA_USER_EXEC_TYPE) |
(perms & ~AA_USER_EXEC_TYPE);
audit = (exact_audit & AA_USER_EXEC_TYPE) |
(audit & ~AA_USER_EXEC_TYPE);
if (exact_match_allow & AA_USER_EXEC_TYPE) {
perms.allow = (exact_match_allow & AA_USER_EXEC_TYPE) |
(perms.allow & ~AA_USER_EXEC_TYPE);
perms.audit = (exact_audit & AA_USER_EXEC_TYPE) |
(perms.audit & ~AA_USER_EXEC_TYPE);
perms.exact = AA_USER_EXEC_TYPE;
}
if (exact_match_perms & AA_OTHER_EXEC_TYPE) {
perms = (exact_match_perms & AA_OTHER_EXEC_TYPE) |
(perms & ~AA_OTHER_EXEC_TYPE);
audit = (exact_audit & AA_OTHER_EXEC_TYPE) |
(audit & ~AA_OTHER_EXEC_TYPE);
if (exact_match_allow & AA_OTHER_EXEC_TYPE) {
perms.allow = (exact_match_allow & AA_OTHER_EXEC_TYPE) |
(perms.allow & ~AA_OTHER_EXEC_TYPE);
perms.audit = (exact_audit & AA_OTHER_EXEC_TYPE) |
(perms.audit & ~AA_OTHER_EXEC_TYPE);
perms.exact |= AA_OTHER_EXEC_TYPE;
}
if (perms & AA_USER_EXEC & deny)
perms &= ~AA_USER_EXEC_TYPE;
if (AA_USER_EXEC & perms.deny)
perms.deny |= AA_USER_EXEC_TYPE;
if (perms & AA_OTHER_EXEC & deny)
perms &= ~AA_OTHER_EXEC_TYPE;
if (AA_OTHER_EXEC & perms.deny)
perms.deny |= AA_OTHER_EXEC_TYPE;
perms &= ~deny;
perms.allow &= ~perms.deny;
perms.quiet &= perms.deny;
if (audit_ctl)
*audit_ctl = PACK_AUDIT_CTL(audit, quiet & deny);
// if (perms & AA_ERROR_BIT) {
// fprintf(stderr, "error bit 0x%x\n", perms);
// exit(255);
//}
//if (perms & AA_EXEC_BITS)
//fprintf(stderr, "accept perm: 0x%x\n", perms);
/*
if (perms & ~AA_VALID_PERMS)
yyerror(_("Internal error accumulated invalid perm 0x%llx\n"), perms);
*/
//if (perms & AA_CHANGE_HAT)
// fprintf(stderr, "change_hat 0x%x\n", perms);
if (*error)
if (error)
fprintf(stderr, "profile has merged rule with conflicting x modifiers\n");
return perms;
return error;
}

View File

@@ -1,7 +1,7 @@
/*
* (C) 2006, 2007 Andreas Gruenbacher <agruen@suse.de>
* Copyright (c) 2003-2008 Novell, Inc. (All rights reserved)
* Copyright 2009-2010 Canonical Ltd.
* 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
@@ -33,28 +33,298 @@
#include "expr-tree.h"
class State;
/**
* State cases are identical to NodesCases except they map to State *
* instead of NodeSet.
* Out-edges from a state to another: we store the follow State
* for each input character that is not a default match in cases and
* default matches in otherwise as well as in all matching explicit cases
* This avoids enumerating all the explicit tranitions for default matches.
*/
typedef struct Cases {
typedef map<uchar, State *>::iterator iterator;
iterator begin() { return cases.begin(); }
iterator end() { return cases.end(); }
Cases(): otherwise(0) { }
map<uchar, State *> cases;
State *otherwise;
} Cases;
typedef map<uchar, State *> StateTrans;
typedef list<State *> Partition;
uint32_t accept_perms(NodeSet *state, uint32_t *audit_ctl, int *error);
#include "../immunix.h"
class perms_t {
public:
perms_t(void) throw(int): 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)
{
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));
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(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<hashedNodeSet> cache;
NodeCache(void): cache() { };
~NodeCache() { clear(); };
virtual unsigned long size(void) const { return cache.size(); }
void clear()
{
for (set<hashedNodeSet>::iterator i = cache.begin();
i != cache.end(); i++) {
delete i->nodes;
}
cache.clear();
CacheStats::clear();
}
NodeSet *insert(NodeSet *nodes)
{
if (!nodes)
return NULL;
pair<set<hashedNodeSet>::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<hashedNodeVec *, deref_less_than> cache;
NodeVecCache(void): cache() { };
~NodeVecCache() { clear(); };
virtual unsigned long size(void) const { return cache.size(); }
void clear()
{
for (set<hashedNodeVec *>::iterator i = cache.begin();
i != cache.end(); i++) {
delete *i;
}
cache.clear();
CacheStats::clear();
}
hashedNodeVec *insert(NodeSet *nodes)
{
if (!nodes)
return NULL;
pair<set<hashedNodeVec *>::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
@@ -63,7 +333,8 @@ uint32_t accept_perms(NodeSet *state, uint32_t *audit_ctl, int *error);
* the start state is setup to have label == 1
* audit: the audit permission mask for the state
* accept: the accept permissions for the state
* cases: set of transitions from this 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.
@@ -72,62 +343,110 @@ uint32_t accept_perms(NodeSet *state, uint32_t *audit_ctl, int *error);
*/
class State {
public:
State(): label(0), audit(0), accept(0), cases(), nodes(NULL) { };
State(int l): label(l), audit(0), accept(0), cases(), nodes(NULL) { };
State(int l, NodeSet * n) throw(int):
label(l), audit(0), accept(0), cases(), nodes(n)
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. */
accept = accept_perms(nodes, &audit, &error);
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;
uint32_t audit, accept;
Cases cases;
perms_t perms;
StateTrans trans;
State *otherwise;
/* temp storage for State construction */
union {
Partition *partition;
NodeSet *nodes;
ProtoState proto;
};
};
ostream &operator<<(ostream &os, const State &state);
typedef map<pair<unsigned long, NodeSet *>, State *, deref_less_than> NodeMap;
/* Transitions in the DFA. */
class NodeMap: public CacheStats
{
public:
typedef map<ProtoState, State *>::iterator iterator;
iterator begin() { return cache.begin(); }
iterator end() { return cache.end(); }
/* dfa_stats - structure to group various stats about dfa creation
* duplicates - how many duplicate NodeSets where encountered and discarded
* proto_max - maximum length of a NodeSet encountered during dfa construction
* proto_sum - sum of NodeSet length during dfa construction. Used to find
* average length.
*/
typedef struct dfa_stats {
unsigned int duplicates, proto_max, proto_sum;
} dfa_stats_t;
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(NodeMap &nodemap,
pair<unsigned long, NodeSet *> index,
NodeSet *nodes, dfa_stats_t &stats);
void update_state_transitions(NodeMap &nodemap,
list<State *> &work_queue,
State *state, dfa_stats_t &stats);
State *find_target_state(NodeMap &nodemap, list<State *> &work_queue,
NodeSet *nodes, dfa_stats_t &stats);
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<State *> 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);

View File

@@ -169,7 +169,7 @@ int hexdigit(char c)
else if (c >= 'A' && c <= 'F')
return 10 + c - 'A';
else if (c >= 'a' && c <= 'f')
return 10 + c - 'A';
return 10 + c - 'a';
else
return -1;
}

526
parser/mount.c Normal file
View File

@@ -0,0 +1,526 @@
/*
* Copyright (c) 2010
* Canonical, Ltd. (All rights reserved)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License published by the Free Software Foundation.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, contact Novell, Inc. or Canonical
* Ltd.
*/
/**
* The mount command, its mix of options and flags, its permissions and
* mapping are a mess.
* mount [-lhV]
*
* mount -a [-fFnrsvw] [-t vfstype] [-O optlist]
*
* mount [-fnrsvw] [-o option[,option]...] device|dir
*
* mount [-fnrsvw] [-t vfstype] [-o options] device dir
*
*----------------------------------------------------------------------
* Mount flags of no interest for apparmor mediation
* -a, --all
* -F fork for simultaneous mount
* -f fake, do everything except that actual system call
* -h --help
* -i, --internal-only
* -n mount without writing in /etc/mtab
* -O <optlist> limits what is auto mounted
* -p, --pass-fd num
* -s Tolerate sloppy mount options
* -U uuid
* -V --version
* --no-canonicalize
*
*----------------------------------------------------------------------
* what do we do with these
* -l list?
* -L <label> label
* -v --verbose deprecated
*
*----------------------------------------------------------------------
* Filesystem type
* -t <vfstype>
* vfstype=<vfstype>
*
*----------------------------------------------------------------------
* Mount Flags/options (-o --options)
* -o option[,option]
*
* The Linux kernel has 32 fs - independent mount flags, that mount command
* is responsible for stripping out and mapping to a 32 bit flags field.
* The mount commands mapping is documented below.
*
* Unfortunately we can not directly use this mapping as we need to be able
* represent, whether none, 1 or both options of a flag can be present for
* example
* ro, and rw information is stored in a single bit. But we need 2 bits
* of information.
* ro - the mount can only be readonly
* rw - the mount can only be rw
* ro/rw - the mount can be either ro/rw
* the fourth state of neither ro/rw does not exist, but still we need
* >1 bit to represent the possible choices
*
* The fs specific mount options are passed into the kernel as a string
* to be interpreted by the filesystem.
*
*
* #define MS_RDONLY 1 Mount read-only
* ro -r --read-only [source] dest
* rw -w
* #define MS_NOSUID 2 Ignore suid and sgid bits
* nosuid
* suid
* #define MS_NODEV 4 Disallow access to device special files
* nodev
* dev
* #define MS_NOEXEC 8 Disallow program execution
* noexec
* exec
* #define MS_SYNCHRONOUS 16 Writes are synced at once
* sync
* async
* #define MS_REMOUNT 32 Alter flags of a mounted FS
* remount source dest
* #define MS_MANDLOCK 64 Allow mandatory locks on an FS
* mand
* nomand
* #define MS_DIRSYNC 128 Directory modifications are synchronous
* dirsync
* #define MS_NOATIME 1024 Do not update access times
* noatime
* atime
* #define MS_NODIRATIME 2048 Do not update directory access times
* nodiratime
* diratime
* #define MS_BIND 4096
* --bind -B source dest
* #define MS_MOVE 8192
* --move -M source dest
* #define MS_REC 16384
* --rbind -R source dest
* --make-rshared dest
* --make-rslave dest
* --make-rprivate dest
* --make-runbindable dest
* #define MS_VERBOSE 32768 MS_VERBOSE is deprecated
* #define MS_SILENT 32768
* silent
* load
* #define MS_POSIXACL (1<<16) VFS does not apply the umask
* acl
* noacl
* #define MS_UNBINDABLE (1<<17) change to unbindable
* --make-unbindable dest
* #define MS_PRIVATE (1<<18) change to private
* --make-private dest
* #define MS_SLAVE (1<<19) change to slave
* --make-slave dest
* #define MS_SHARED (1<<20) change to shared
* --make-shared dest
* #define MS_RELATIME (1<<21) Update atime relative to mtime/ctime
* relatime
* norelatime
* #define MS_KERNMOUNT (1<<22) this is a kern_mount call
* #define MS_I_VERSION (1<<23) Update inode I_version field
* iversion
* noiversion
* #define MS_STRICTATIME (1<<24) Always perform atime updates
* strictatime
* nostrictatime
* #define MS_NOSEC (1<<28)
* #define MS_BORN (1<<29)
* #define MS_ACTIVE (1<<30)
* #define MS_NOUSER (1<<31)
* nouser
* user
*
* other mount options of interest
*
* selinux
* context=<context>
* fscontext=<context>
* defcontext=<context>,
* rootcontext=<context>
*
* defaults -> rw, suid, dev, exec, auto, nouser, async
* owner -> implies nosuid and nodev
* users -> implies noexec, nosuid, and nodev
*
*----------------------------------------------------------------------
* AppArmor mount rules
*
* AppArmor mount rules try to leverage mount syntax within apparmor syntax
* this can not be done entirely but it is largely covered.
*
* The general mount syntax is
* [audit] [deny] [owner] mount [conds]* [source] [ -> [conds] path],
* [audit] [deny] remount [conds]* [path],
* [audit] [deny] umount [conds]* [path],
*
* Note: leading owner option applies owner condition to both sours and dest
* path.
*
* where [conds] can be
* fstype=<expr>
* options=<expr>
* owner[=<expr>]
*
* <expr> := <re> | '(' (<re>[,])+ ')'
*
* If a condition is not specified then it is assumed to match all possible
* entries for it. ie. a missing fstype means all fstypes are matched.
* However if a condition is specified then the rule only grants permission
* for mounts matching the specified pattern.
*
* Examples.
* mount, # allow any mount
* mount /dev/foo, # allow mounting of /dev/foo anywhere
* mount options=ro /dev/foo, #allow mounting /dev/foo as read only
* mount options=(ro,foo) /dev/foo,
* mount options=ro options=foo /dev/foo,
* mount fstype=overlayfs options=(rw,upperdir=/tmp/upper/,lowerdir=/) overlay -> /mnt/
*
*----------------------------------------------------------------------
* pivotroot
* pivotroot [oldroot=<value>] <path> -> <profile>
* pivotroot <path> -> { }
*
*----------------------------------------------------------------------
* chroot
* chroot <path> -> <profile>
* chroot <path> -> { }
*
*----------------------------------------------------------------------
* AppArmor mount rule encoding
*
* TODO:
* add semantic checking of options against specified filesytem types
* to catch mount options that can't be covered.
*
*
*/
#include <stdlib.h>
#include <string.h>
#include "parser.h"
#include "mount.h"
struct mnt_keyword_table {
char *keyword;
unsigned int set;
unsigned int clear;
};
static struct mnt_keyword_table mnt_opts_table[] = {
{"ro", MS_RDONLY, 0},
{"r", MS_RDONLY, 0},
{"read-only", MS_RDONLY, 0},
{"rw", 0, MS_RDONLY},
{"w", 0, MS_RDONLY},
{"suid", 0, MS_NOSUID},
{"nosuid", MS_NOSUID, 0},
{"dev", 0, MS_NODEV},
{"nodev", MS_NODEV, 0},
{"exec", 0, MS_NOEXEC},
{"noexec", MS_NOEXEC, 0},
{"sync", MS_SYNC, 0},
{"async", 0, MS_SYNC},
{"remount", MS_REMOUNT, 0},
{"mand", MS_MAND, 0},
{"nomand", 0, MS_MAND},
{"dirsync", MS_DIRSYNC, 0},
{"atime", 0, MS_NOATIME},
{"noatime", MS_NOATIME, 0},
{"diratime", 0, MS_NODIRATIME},
{"nodiratime", MS_NODIRATIME, 0},
{"bind", MS_BIND, 0},
{"B", MS_BIND, 0},
{"move", MS_MOVE, 0},
{"M", MS_MOVE, 0},
{"rbind", MS_RBIND, 0},
{"R", MS_RBIND, 0},
{"verbose", MS_VERBOSE, 0},
{"silent", MS_SILENT, 0},
{"load", 0, MS_SILENT},
{"acl", MS_ACL, 0},
{"noacl", 0, MS_ACL},
{"make-unbindable", MS_UNBINDABLE, 0},
{"make-runbindable", MS_RUNBINDABLE, 0},
{"make-private", MS_PRIVATE, 0},
{"make-rprivate", MS_RPRIVATE, 0},
{"make-slave", MS_SLAVE, 0},
{"make-rslave", MS_RSLAVE, 0},
{"make-shared", MS_SHARED, 0},
{"make-rshared", MS_RSHARED, 0},
{"relatime", MS_RELATIME, 0},
{"norelatime", 0, MS_NORELATIME},
{"iversion", MS_IVERSION, 0},
{"noiversion", 0, MS_IVERSION},
{"strictatime", MS_STRICTATIME, 0},
{"user", 0, MS_NOUSER},
{"nouser", MS_NOUSER, 0},
{NULL, 0, 0}
};
static struct mnt_keyword_table mnt_conds_table[] = {
{"options", MNT_SRC_OPT, MNT_COND_OPTIONS},
{"option", MNT_SRC_OPT, MNT_COND_OPTIONS},
{"fstype", MNT_SRC_OPT | MNT_DST_OPT, MNT_COND_FSTYPE},
{"vfstype", MNT_SRC_OPT | MNT_DST_OPT, MNT_COND_FSTYPE},
{NULL, 0, 0}
};
static int find_mnt_keyword(struct mnt_keyword_table *table, const char *name)
{
int i;
for (i = 0; table[i].keyword; i++) {
if (strcmp(name, table[i].keyword) == 0)
return i;
}
return -1;
}
int is_valid_mnt_cond(const char *name, int src)
{
int i;
i = find_mnt_keyword(mnt_conds_table, name);
if (i != -1)
return (mnt_conds_table[i].set & src);
return -1;
}
static unsigned int extract_flags(struct value_list **list, unsigned int *inv)
{
unsigned int flags = 0;
*inv = 0;
struct value_list *entry, *tmp, *prev = NULL;
list_for_each_safe(*list, entry, tmp) {
int i;
i = find_mnt_keyword(mnt_opts_table, entry->value);
if (i != -1) {
flags |= mnt_opts_table[i].set;
*inv |= mnt_opts_table[i].clear;
PDEBUG(" extracting mount flag %s req: 0x%x inv: 0x%x"
" => req: 0x%x inv: 0x%x\n",
entry->value, mnt_opts_table[i].set,
mnt_opts_table[i].clear, flags, *inv);
if (prev)
prev->next = tmp;
if (entry == *list)
*list = tmp;
entry->next = NULL;
free_value_list(entry);
} else
prev = entry;
}
return flags;
}
static struct value_list *extract_fstype(struct cond_entry **conds)
{
struct value_list *list = NULL;
struct cond_entry *entry, *tmp, *prev = NULL;
list_for_each_safe(*conds, entry, tmp) {
if (strcmp(entry->name, "fstype") == 0 ||
strcmp(entry->name, "vfstype") == 0) {
PDEBUG(" extracting fstype\n");
if (prev)
prev->next = tmp;
if (entry == *conds)
*conds = tmp;
entry->next = NULL;
list_append(entry->vals, list);
list = entry->vals;
entry->vals = NULL;
free_cond_entry(entry);
} else
prev = entry;
}
return list;
}
static struct value_list *extract_options(struct cond_entry **conds, int eq)
{
struct value_list *list = NULL;
struct cond_entry *entry, *tmp, *prev = NULL;
list_for_each_safe(*conds, entry, tmp) {
if ((strcmp(entry->name, "options") == 0 ||
strcmp(entry->name, "option") == 0) &&
entry->eq == eq) {
if (prev)
prev->next = tmp;
if (entry == *conds)
*conds = tmp;
entry->next = NULL;
PDEBUG(" extracting option %s\n", entry->name);
list_append(entry->vals, list);
list = entry->vals;
entry->vals = NULL;
free_cond_entry(entry);
} else
prev = entry;
}
return list;
}
struct mnt_entry *new_mnt_entry(struct cond_entry *src_conds, char *device,
struct cond_entry *dst_conds __unused, char *mnt_point,
int allow)
{
/* FIXME: dst_conds are ignored atm */
struct mnt_entry *ent;
ent = (struct mnt_entry *) calloc(1, sizeof(struct mnt_entry));
if (ent) {
ent->mnt_point = mnt_point;
ent->device = device;
ent->dev_type = extract_fstype(&src_conds);
ent->flags = 0;
ent->inv_flags = 0;
if (src_conds) {
unsigned int flags = 0, inv_flags = 0;
struct value_list *list = extract_options(&src_conds, 0);
ent->opts = extract_options(&src_conds, 1);
if (ent->opts)
ent->flags = extract_flags(&ent->opts,
&ent->inv_flags);
if (list) {
flags = extract_flags(&list, &inv_flags);
/* these flags are optional so set both */
flags |= inv_flags;
inv_flags |= flags;
ent->flags |= flags;
ent->inv_flags |= inv_flags;
if (ent->opts)
list_append(ent->opts, list);
else if (list)
ent->opts = list;
}
}
if (allow & AA_DUMMY_REMOUNT) {
allow = AA_MAY_MOUNT;
ent->flags |= MS_REMOUNT;
ent->inv_flags = 0;
} else if (!(ent->flags | ent->inv_flags)) {
/* no flag options, and not remount, allow everything */
ent->flags = MS_ALL_FLAGS;
ent->inv_flags = MS_ALL_FLAGS;
}
ent->allow = allow;
if (src_conds) {
PERROR(" unsupported mount conditions\n");
exit(1);
}
}
return ent;
}
void free_mnt_entry(struct mnt_entry *ent)
{
if (!ent)
return;
free_mnt_entry(ent->next);
free_value_list(ent->opts);
free_value_list(ent->dev_type);
free(ent->device);
free(ent->mnt_point);
free(ent->trans);
free(ent);
}
struct mnt_entry *dup_mnt_entry(struct mnt_entry *orig)
{
struct mnt_entry *entry = NULL;
entry = (struct mnt_entry *) calloc(1, sizeof(struct mnt_entry));
if (!entry)
return NULL;
entry->mnt_point = orig->mnt_point ? strdup(orig->mnt_point) : NULL;
entry->device = orig->device ? strdup(orig->device) : NULL;
entry->trans = orig->trans ? strdup(orig->trans) : NULL;
entry->dev_type = dup_value_list(orig->dev_type);
entry->opts = dup_value_list(orig->opts);
entry->flags = orig->flags;
entry->inv_flags = orig->inv_flags;
entry->allow = orig->allow;
entry->audit = orig->audit;
entry->deny = orig->deny;
entry->next = orig->next;
return entry;
}
void print_mnt_entry(struct mnt_entry *entry)
{
if (entry->allow & AA_MAY_MOUNT)
fprintf(stderr, "mount");
else if (entry->allow & AA_MAY_UMOUNT)
fprintf(stderr, "umount");
else if (entry->allow & AA_MAY_PIVOTROOT)
fprintf(stderr, "pivotroot");
else
fprintf(stderr, "error: unknonwn mount perm");
fprintf(stderr, " (0x%x - 0x%x) ", entry->flags, entry->inv_flags);
if (entry->dev_type) {
fprintf(stderr, " type=");
print_value_list(entry->dev_type);
}
if (entry->opts) {
fprintf(stderr, " options=");
print_value_list(entry->opts);
}
if (entry->device)
fprintf(stderr, " %s", entry->device);
if (entry->mnt_point)
fprintf(stderr, " -> %s", entry->mnt_point);
if (entry->trans)
fprintf(stderr, " -> %s", entry->trans);
fprintf(stderr, " %s (0x%x/0x%x)", entry->deny ? "deny" : "", entry->allow, entry->audit);
fprintf(stderr, ",\n");
}

136
parser/mount.h Normal file
View File

@@ -0,0 +1,136 @@
/*
* Copyright (c) 2010
* Canonical, Ltd. (All rights reserved)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License published by the Free Software Foundation.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, contact Novell, Inc. or Canonical
* Ltd.
*/
#ifndef __AA_MOUNT_H
#define __AA_MOUNT_H
#include "parser.h"
#define MS_RDONLY (1 << 0)
#define MS_RW 0
#define MS_NOSUID (1 << 1)
#define MS_SUID 0
#define MS_NODEV (1 << 2)
#define MS_DEV 0
#define MS_NOEXEC (1 << 3)
#define MS_EXEC 0
#define MS_SYNC (1 << 4)
#define MS_ASYNC 0
#define MS_REMOUNT (1 << 5)
#define MS_MAND (1 << 6)
#define MS_NOMAND 0
#define MS_DIRSYNC (1 << 7)
#define MS_NODIRSYNC 0
#define MS_NOATIME (1 << 10)
#define MS_ATIME 0
#define MS_NODIRATIME (1 << 11)
#define MS_DIRATIME 0
#define MS_BIND (1 << 12)
#define MS_MOVE (1 << 13)
#define MS_REC (1 << 14)
#define MS_VERBOSE (1 << 15)
#define MS_SILENT (1 << 15)
#define MS_LOAD 0
#define MS_ACL (1 << 16)
#define MS_NOACL 0
#define MS_UNBINDABLE (1 << 17)
#define MS_PRIVATE (1 << 18)
#define MS_SLAVE (1 << 19)
#define MS_SHARED (1 << 20)
#define MS_RELATIME (1 << 21)
#define MS_NORELATIME 0
#define MS_IVERSION (1 << 23)
#define MS_NOIVERSION 0
#define MS_STRICTATIME (1 << 24)
#define MS_NOUSER (1 << 31)
#define MS_USER 0
#define MS_ALL_FLAGS (MS_RDONLY | MS_NOSUID | MS_NODEV | MS_NOEXEC | \
MS_SYNC | MS_REMOUNT | MS_MAND | MS_DIRSYNC | \
MS_NOATIME | MS_NODIRATIME | MS_BIND | MS_MOVE | \
MS_REC | MS_VERBOSE | MS_ACL | MS_UNBINDABLE | \
MS_PRIVATE | MS_SLAVE | MS_SHARED | MS_RELATIME | \
MS_IVERSION | MS_STRICTATIME | MS_USER)
#define MS_RBIND (MS_BIND | MS_REC)
#define MS_RUNBINDABLE (MS_UNBINDABLE | MS_REC)
#define MS_RPRIVATE (MS_PRIVATE | MS_REC)
#define MS_RSLAVE (MS_SLAVE | MS_REC)
#define MS_RSHARED (MS_SHARED | MS_REC)
/* set of flags we don't use but define (but not with the kernel values)
* for MNT_FLAGS
*/
#define MS_ACTIVE 0
#define MS_BORN 0
#define MS_KERNMOUNT 0
/* from kernel fs/namespace.c - set of flags masked off */
#define MNT_FLAGS (MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_ACTIVE | \
MS_BORN | MS_NOATIME | MS_NODIRATIME | MS_RELATIME| \
MS_KERNMOUNT | MS_STRICTATIME)
#define MS_BIND_FLAGS (MS_BIND | MS_REC)
#define MS_MAKE_FLAGS ((MS_UNBINDABLE | MS_PRIVATE | MS_SLAVE | MS_SHARED | \
MS_REC) | (MS_ALL_FLAGS & ~(MNT_FLAGS)))
#define MS_MOVE_FLAGS (MS_MOVE)
#define MS_CMDS (MS_MOVE | MS_REMOUNT | MS_BIND | MS_PRIVATE | MS_SLAVE | \
MS_SHARED | MS_UNBINDABLE)
#define MS_REMOUNT_FLAGS (MS_ALL_FLAGS & ~(MS_CMDS & ~MS_REMOUNT))
#define MNT_SRC_OPT 1
#define MNT_DST_OPT 2
#define MNT_COND_FSTYPE 1
#define MNT_COND_OPTIONS 2
#define AA_MAY_PIVOTROOT 1
#define AA_MAY_MOUNT 2
#define AA_MAY_UMOUNT 4
#define AA_MATCH_CONT 0x40
#define AA_AUDIT_MNT_DATA AA_MATCH_CONT
#define AA_DUMMY_REMOUNT 0x40000000 /* dummy perm for remount rule - is
* remapped to a mount option*/
struct mnt_entry {
char *mnt_point;
char *device;
char *trans;
struct value_list *dev_type;
struct value_list *opts;
unsigned int flags, inv_flags;
int allow, audit;
int deny;
struct mnt_entry *next;
};
void print_mnt_entry(struct mnt_entry *entry);
int is_valid_mnt_cond(const char *name, int src);
struct mnt_entry *new_mnt_entry(struct cond_entry *sconds, char *device,
struct cond_entry *dconds, char *mnt_point,
int mode);
struct mnt_entry *dup_mnt_entry(struct mnt_entry *orig);
void free_mnt_entry(struct mnt_entry *ent);
#endif /* __AA_MOUNT_H */

View File

@@ -2,8 +2,8 @@
* Copyright (c) 1999, 2000, 2001, 2002, 2004, 2005, 2006, 2007
* NOVELL (All rights reserved)
*
* Copyright (c) 2010
* Canonical, Ltd. (All rights reserved)
* Copyright (c) 2010 - 2012
* Canonical Ltd. (All rights reserved)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
@@ -19,12 +19,22 @@
* Ltd.
*/
#ifndef __AA_PARSER_H
#define __AA_PARSER_H
#include <netinet/in.h>
#include <sys/resource.h>
#include "immunix.h"
#include "libapparmor_re/apparmor_re.h"
#include "libapparmor_re/aare_rules.h"
struct mnt_ent;
/* Global variable to pass token to lexer. Will be replaced by parameter
* when lexer and parser are made reentrant
*/
extern int parser_token;
typedef enum pattern_t pattern_t;
struct flagval {
@@ -44,6 +54,20 @@ struct cod_pattern {
char *regex; // posix regex
};
struct value_list {
char *value;
struct value_list *next;
};
struct cond_entry {
char *name;
int eq; /* where equals was used in specifying list */
struct value_list *vals;
struct cond_entry *next;
};
struct cod_entry {
char *namespace;
char *name;
@@ -107,7 +131,6 @@ struct codomain {
uint64_t audit_caps;
uint64_t deny_caps;
uint64_t quiet_caps;
uint64_t set_caps;
unsigned int *network_allowed; /* array of type masks
* indexed by AF_FAMILY */
@@ -119,6 +142,8 @@ struct codomain {
char *exec_table[AA_EXEC_COUNT];
struct cod_entry *entries;
struct mnt_entry *mnt_ents;
void *hat_table;
//struct codomain *next;
@@ -126,6 +151,11 @@ struct codomain {
int dfarule_count;
void *dfa;
size_t dfa_size;
aare_ruleset_t *policy_rules;
int policy_rule_count;
void *policy_dfa;
size_t policy_dfa_size;
};
struct sd_hat {
@@ -216,12 +246,19 @@ extern int preprocess_only;
#define __unused __attribute__ ((unused))
#endif
#define list_for_each(LIST, ENTRY) \
for ((ENTRY) = (LIST); (ENTRY); (ENTRY) = (ENTRY)->next)
#define list_for_each_safe(LIST, ENTRY, TMP) \
for ((ENTRY) = (LIST), (TMP) = (LIST) ? (LIST)->next : NULL; (ENTRY); (ENTRY) = (TMP), (TMP) = (TMP) ? (TMP)->next : NULL)
#define list_last_entry(LIST, ENTRY) \
for ((ENTRY) = (LIST); (ENTRY) && (ENTRY)->next; (ENTRY) = (ENTRY)->next)
#define list_append(LISTA, LISTB) \
do { \
typeof(LISTA) ___tmp; \
list_last_entry((LISTA), ___tmp);\
___tmp->next = (LISTB); \
} while (0)
/* from parser_common.c */
extern int regex_type;
@@ -229,6 +266,7 @@ extern int perms_create;
extern int net_af_max_override;
extern int kernel_load;
extern int kernel_supports_network;
extern int kernel_supports_mount;
extern int flag_changehat_version;
extern int conf_verbose;
extern int conf_quiet;
@@ -254,7 +292,7 @@ extern void update_mru_tstamp(FILE *file);
extern FILE *yyin;
extern void yyrestart(FILE *fp);
extern int yyparse(void);
extern void yyerror(char *msg, ...);
extern void yyerror(const char *msg, ...);
extern int yylex(void);
/* parser_include.c */
@@ -265,12 +303,24 @@ extern int process_regex(struct codomain *cod);
extern int post_process_entry(struct cod_entry *entry);
extern void reset_regex(void);
extern int process_policydb(struct codomain *cod);
extern int process_policy_ents(struct codomain *cod);
/* parser_variable.c */
extern int process_variables(struct codomain *cod);
extern struct var_string *split_out_var(char *string);
extern void free_var_string(struct var_string *var);
/* parser_misc.c */
extern struct value_list *new_value_list(char *value);
extern struct value_list *dup_value_list(struct value_list *list);
extern void free_value_list(struct value_list *list);
extern void print_value_list(struct value_list *list);
extern struct cond_entry *new_cond_entry(char *name, int eq, struct value_list *list);
extern void free_cond_entry(struct cond_entry *ent);
extern void print_cond_entry(struct cond_entry *ent);
extern char *processid(char *string, int len);
extern char *processquoted(char *string, int len);
extern char *processunquoted(char *string, int len);
extern int get_keyword_token(const char *keyword);
@@ -293,6 +343,7 @@ extern void debug_cod_list(struct codomain *list);
extern int str_to_boolean(const char* str);
extern struct cod_entry *copy_cod_entry(struct cod_entry *cod);
extern void free_cod_entries(struct cod_entry *list);
extern void free_mnt_entries(struct mnt_entry *list);
/* parser_symtab.c */
struct set_value {;
@@ -330,10 +381,12 @@ extern int cache_fd;
extern void add_to_list(struct codomain *codomain);
extern void add_hat_to_policy(struct codomain *policy, struct codomain *hat);
extern void add_entry_to_policy(struct codomain *policy, struct cod_entry *entry);
extern void post_process_nt_entries(struct codomain *cod);
extern void post_process_file_entries(struct codomain *cod);
extern void post_process_mnt_entries(struct codomain *cod);
extern int post_process_policy(int debug_only);
extern int process_hat_regex(struct codomain *cod);
extern int process_hat_variables(struct codomain *cod);
extern int process_hat_policydb(struct codomain *cod);
extern int post_merge_rules(void);
extern int merge_hat_rules(struct codomain *cod);
extern struct codomain *merge_policy(struct codomain *a, struct codomain *b);
@@ -354,7 +407,7 @@ void free_policies(void);
*/
/* parser_yacc.y */
void yyerror(char *msg, ...)
void yyerror(const char *msg, ...)
{
va_list arg;
char buf[PATH_MAX];
@@ -375,3 +428,5 @@ void yyerror(char *msg, ...)
}
#endif
#endif /** __AA_PARSER_H */

View File

@@ -1,6 +1,6 @@
/*
* Copyright (c) 2010, 2011
* Canonical, Ltd. (All rights reserved)
* Copyright (c) 2010 - 2012
* Canonical Ltd. (All rights reserved)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
@@ -27,6 +27,7 @@ int perms_create = 0; /* perms contain create flag */
int net_af_max_override = -1; /* use kernel to determine af_max */
int kernel_load = 1;
int kernel_supports_network = 1; /* kernel supports network rules */
int kernel_supports_mount = 0; /* kernel supports mount rules */
int flag_changehat_version = FLAG_CHANGEHAT_1_5;
int conf_verbose = 0;
int conf_quiet = 0;
@@ -34,7 +35,7 @@ int names_only = 0;
int current_lineno = 1;
int option = OPTION_ADD;
dfaflags_t dfaflags = DFA_CONTROL_TREE_NORMAL | DFA_CONTROL_TREE_SIMPLE | DFA_CONTROL_MINIMIZE | DFA_CONTROL_MINIMIZE_HASH_TRANS | DFA_CONTROL_MINIMIZE_HASH_PERMS;
dfaflags_t dfaflags = DFA_CONTROL_TREE_NORMAL | DFA_CONTROL_TREE_SIMPLE | DFA_CONTROL_MINIMIZE | DFA_CONTROL_MINIMIZE_HASH_TRANS;
char *subdomainbase = NULL;
char *progname = __FILE__;
@@ -54,18 +55,16 @@ void pwarn(char *fmt, ...)
{
va_list arg;
char *newfmt;
int rc;
if (conf_quiet || names_only || option == OPTION_REMOVE)
return;
rc = asprintf(&newfmt, _("Warning from %s (%s%sline %d): %s"),
profilename ? profilename : "stdin",
current_filename ? current_filename : "",
current_filename ? " " : "",
current_lineno,
fmt);
if (!newfmt)
if (asprintf(&newfmt, _("Warning from %s (%s%sline %d): %s"),
profilename ? profilename : "stdin",
current_filename ? current_filename : "",
current_filename ? " " : "",
current_lineno,
fmt) == -1)
return;
va_start(arg, fmt);

View File

@@ -1,8 +1,8 @@
/*
* Copyright (c) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007
* NOVELL (All rights reserved)
* Copyright (c) 2010
* Canonical, Ltd.
* Copyright (c) 2010 - 2012
* Canonical Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public

View File

@@ -1,8 +1,8 @@
/*
* Copyright (c) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007
* NOVELL (All rights reserved)
* Copyright (c) 2010
* Canonical, Ltd.
* Copyright (c) 2010 - 2012
* Canonical Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public

View File

@@ -59,6 +59,7 @@
#define SUBDOMAIN_INTERFACE_VERSION 2
#define SUBDOMAIN_INTERFACE_DFA_VERSION 5
#define SUBDOMAIN_INTERFACE_POLICY_DB 16
int sd_serialize_codomain(int option, struct codomain *cod);
@@ -609,15 +610,14 @@ int sd_serialize_profile(sd_serialize *p, struct codomain *profile,
#define low_caps(X) ((u32) ((X) & 0xffffffff))
#define high_caps(X) ((u32) (((X) >> 32) & 0xffffffff))
allowed_caps = (profile->capabilities | profile->set_caps) &
~profile->deny_caps;
allowed_caps = (profile->capabilities) & ~profile->deny_caps;
if (!sd_write32(p, low_caps(allowed_caps)))
return 0;
if (!sd_write32(p, low_caps(allowed_caps & profile->audit_caps)))
return 0;
if (!sd_write32(p, low_caps(profile->deny_caps & profile->quiet_caps)))
return 0;
if (!sd_write32(p, low_caps(profile->set_caps & ~profile->deny_caps)))
if (!sd_write32(p, 0))
return 0;
if (!sd_write_struct(p, "caps64"))
@@ -628,7 +628,7 @@ int sd_serialize_profile(sd_serialize *p, struct codomain *profile,
return 0;
if (!sd_write32(p, high_caps(profile->deny_caps & profile->quiet_caps)))
return 0;
if (!sd_write32(p, high_caps(profile->set_caps & ~profile->deny_caps)))
if (!sd_write32(p, 0))
return 0;
if (!sd_write_structend(p))
return 0;
@@ -655,6 +655,15 @@ int sd_serialize_profile(sd_serialize *p, struct codomain *profile,
} else if (profile->network_allowed)
pwarn(_("profile %s network rules not enforced\n"), profile->name);
if (profile->policy_dfa && regex_type == AARE_DFA) {
if (!sd_write_struct(p, "policydb"))
return 0;
if (!sd_serialize_dfa(p, profile->policy_dfa, profile->policy_dfa_size))
return 0;
if (!sd_write_structend(p))
return 0;
}
/* either have a single dfa or lists of different entry types */
if (regex_type == AARE_DFA) {
if (!sd_serialize_dfa(p, profile->dfa, profile->dfa_size))
@@ -686,9 +695,13 @@ int sd_serialize_top_profile(sd_serialize *p, struct codomain *profile)
{
int version;
if (regex_type == AARE_DFA)
version = SUBDOMAIN_INTERFACE_DFA_VERSION;
else
if (regex_type == AARE_DFA) {
/* Not yet
if (profile->policy_dfa)
version = SUBDOMAIN_INTERFACE_POLICYDB;
else */
version = SUBDOMAIN_INTERFACE_DFA_VERSION;
} else
version = SUBDOMAIN_INTERFACE_VERSION;
@@ -763,10 +776,10 @@ int sd_serialize_codomain(int option, struct codomain *cod)
int len = 0;
if (profile_namespace) {
len += strlen(profile_namespace) + 1;
len += strlen(profile_namespace) + 2;
ns = profile_namespace;
} else if (cod->namespace) {
len += strlen(cod->namespace) + 1;
len += strlen(cod->namespace) + 2;
ns = cod->namespace;
}
if (cod->parent) {
@@ -778,7 +791,7 @@ int sd_serialize_codomain(int option, struct codomain *cod)
goto exit;
}
if (ns)
sprintf(name, "%s:%s//%s", ns,
sprintf(name, ":%s:%s//%s", ns,
cod->parent->name, cod->name);
else
sprintf(name, "%s//%s", cod->parent->name,
@@ -790,7 +803,7 @@ int sd_serialize_codomain(int option, struct codomain *cod)
error = -errno;
goto exit;
}
sprintf(name, "%s:%s", ns, cod->name);
sprintf(name, ":%s:%s", ns, cod->name);
} else {
name = cod->name;
}

View File

@@ -1,8 +1,8 @@
/*
* Copyright (c) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007
* NOVELL (All rights reserved)
* Copyright (c) 2010
* Canonical, Ltd.
* Copyright (c) 2010 - 2012
* Canonical Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
@@ -23,6 +23,7 @@
/* eliminates need to link with libfl */
%option noyywrap
%option nounput
%option stack
%{
#include <stdio.h>
@@ -44,7 +45,7 @@
#endif
/* #define DEBUG */
#ifdef DEBUG
#define PDEBUG(fmt, args...) printf("Lexer: " fmt, ## args)
#define PDEBUG(fmt, args...) printf("Lexer (state %d): " fmt, YY_START, ## args)
#else
#define PDEBUG(fmt, args...) /* Do nothing */
#endif
@@ -167,25 +168,35 @@ void include_filename(char *filename, int search)
%}
UP "^"
CARET "^"
OPEN_BRACE \{
CLOSE_BRACE \}
SLASH \/
COLON :
END_OF_RULE [,]
SEPARATOR {UP}
RANGE -
MODE_CHARS ([RrWwaLlMmkXx])|(([Pp]|[Cc])[Xx])|(([Pp]|[Cc])?([IiUu])[Xx])
MODES {MODE_CHARS}+
WS [[:blank:]]
NUMBER [[:digit:]]+
ID [^ \t\n"!,]|(,[^ \t\n"!])
POST_VAR_ID [^ =\+\t\n"!,]|(,[^ =\+\t\n"!])
IP {NUMBER}\.{NUMBER}\.{NUMBER}\.{NUMBER}
ID_CHARS [^ \t\n"!,]
ID {ID_CHARS}|(,{ID_CHARS})
IDS {ID}+
POST_VAR_ID_CHARS [^ \t\n"!,]{-}[=\+]
POST_VAR_ID {POST_VAR_ID_CHARS}|(,{POST_VAR_ID_CHARS})
LIST_VALUE_ID_CHARS [^ \t\n"!,]{-}[()]
LIST_VALUE_ID {LIST_VALUE_ID_CHARS}+
ID_CHARS_NOEQ [^ \t\n"!,]{-}[=]
ID_NOEQ {ID_CHARS_NOEQ}|(,{ID_CHARS_NOEQ})
IDS_NOEQ {ID_NOEQ}+
ALLOWED_QUOTED_ID [^\0"]|\\\"
QUOTED_ID \"{ALLOWED_QUOTED_ID}*\"
HAT hat[ \t]+
IP {NUMBER}\.{NUMBER}\.{NUMBER}\.{NUMBER}
HAT hat{WS}*
PROFILE profile{WS}*
KEYWORD [[:alpha:]_]+
VARIABLE_NAME [[:alpha:]][[:alnum:]_]*
SET_VAR_PREFIX @
@@ -195,25 +206,36 @@ BOOL_VARIABLE $(\{{VARIABLE_NAME}\}|{VARIABLE_NAME})
PATHNAME (\/|{SET_VARIABLE}{POST_VAR_ID}){ID}*
QPATHNAME \"(\/|{SET_VAR_PREFIX})([^\0"]|\\\")*\"
FLAGOPEN_PAREN \(
FLAGCLOSE_PAREN \)
FLAGSEP \,
OPEN_PAREN \(
CLOSE_PAREN \)
COMMA \,
EQUALS =
ADD_ASSIGN \+=
ARROW ->
LT_EQUAL <=
%x SUB_NAME
%x SUB_NAME2
%x SUB_ID
%x SUB_VALUE
%x EXTCOND_MODE
%x NETWORK_MODE
%x FLAGS_MODE
%x LIST_VAL_MODE
%x ASSIGN_MODE
%x RLIMIT_MODE
%x MOUNT_MODE
%x CHANGE_PROFILE_MODE
%x INCLUDE
%%
%{
/* Copied directly into yylex function */
if (parser_token) {
int t = parser_token;
parser_token = 0;
return t;
}
%}
<INCLUDE>{
{WS}+ { /* Eat whitespace */ }
\<([^\> \t\n]+)\> { /* <filename> */
@@ -221,7 +243,7 @@ LT_EQUAL <=
filename[strlen(filename) - 1] = '\0';
include_filename(filename + 1, 1);
free(filename);
BEGIN(INITIAL);
yy_pop_state();
}
\"([^\" \t\n]+)\" { /* "filename" */
@@ -229,12 +251,12 @@ LT_EQUAL <=
filename[strlen(filename) - 1] = '\0';
include_filename(filename + 1, 0);
free(filename);
BEGIN(INITIAL);
yy_pop_state();
}
[^\<\>\"{WS}]+ { /* filename */
include_filename(yytext, 0);
BEGIN(INITIAL);
yy_pop_state();
}
}
@@ -245,112 +267,100 @@ LT_EQUAL <=
if ( !YY_CURRENT_BUFFER ) yyterminate();
}
<SUB_NAME>{
{ID}+ {
/* Ugh, this is a gross hack. I used to use
* {ID}+ to match all TOK_IDs, but that would
* also match TOK_MODE + TOK_END_OF_RULE
* without any spaces in between (because it's
* a longer match). So now, when I want to
* match any random string, I go into a
* separate state. */
DUMP_PREPROCESS;
yylval.id = processunquoted(yytext, yyleng);
PDEBUG("Found sub name: \"%s\"\n", yylval.id);
BEGIN(INITIAL);
return TOK_ID;
}
{QUOTED_ID} {
/* Ugh, this is a gross hack. I used to use
* {ID}+ to match all TOK_IDs, but that would
* also match TOK_MODE + TOK_END_OF_RULE
* without any spaces in between (because it's
* a longer match). So now, when I want to
* match any random string, I go into a
* separate state. */
DUMP_PREPROCESS;
yylval.id = processquoted(yytext, yyleng);
PDEBUG("Found sub name: \"%s\"\n", yylval.id);
BEGIN(INITIAL);
return TOK_ID;
}
[^\n] {
DUMP_PREPROCESS;
/* Something we didn't expect */
yyerror(_("Found unexpected character: '%s'"), yytext);
}
}
<SUB_NAME2>{
{ID}+ {
/* Ugh, this is a gross hack. I used to use
* {ID}+ to match all TOK_IDs, but that would
* also match TOK_MODE + TOK_END_OF_RULE
* without any spaces in between (because it's
* a longer match). So now, when I want to
* match any random string, I go into a
* separate state. */
DUMP_PREPROCESS;
yylval.id = processunquoted(yytext, yyleng);
PDEBUG("Found sub name: \"%s\"\n", yylval.id);
BEGIN(INITIAL);
return TOK_ID;
}
{QUOTED_ID} {
/* Ugh, this is a gross hack. I used to use
* {ID}+ to match all TOK_IDs, but that would
* also match TOK_MODE + TOK_END_OF_RULE
* without any spaces in between (because it's
* a longer match). So now, when I want to
* match any random string, I go into a
* separate state. */
DUMP_PREPROCESS;
yylval.id = processquoted(yytext, yyleng);
PDEBUG("Found sub name: \"%s\"\n", yylval.id);
BEGIN(INITIAL);
return TOK_ID;
}
{WS}+ { DUMP_PREPROCESS; /* Ignoring whitespace */ }
[^\n] {
DUMP_PREPROCESS;
/* Something we didn't expect */
yyerror(_("Found unexpected character: '%s'"), yytext);
}
}
<FLAGS_MODE>{
{FLAGOPEN_PAREN} {
DUMP_PREPROCESS;
PDEBUG("FLag (\n");
return TOK_FLAG_OPENPAREN;
<INITIAL,MOUNT_MODE>{
{VARIABLE_NAME}/{WS}*= {
/* we match to the = in the lexer so that
* can switch scanner state. By the time
* the parser see the = it may be to late
* as bison may have requested the next
* token from the scanner
*/
PDEBUG("conditional %s=\n", yytext);
yylval.id = processid(yytext, yyleng);
yy_push_state(EXTCOND_MODE);
return TOK_CONDID;
}
{FLAGCLOSE_PAREN} {
{VARIABLE_NAME}/{WS}+in{WS}*\( {
/* we match to 'in' in the lexer so that
* we can switch scanner state. By the time
* the parser see the 'in' it may be to late
* as bison may have requested the next
* token from the scanner
*/
PDEBUG("conditional %s=\n", yytext);
yylval.id = processid(yytext, yyleng);
yy_push_state(EXTCOND_MODE);
return TOK_CONDID;
}
}
<SUB_ID>{
({IDS}|{QUOTED_ID}) {
/* Ugh, this is a gross hack. I used to use
* {IDS} to match all TOK_IDs, but that would
* also match TOK_MODE + TOK_END_OF_RULE
* without any spaces in between (because it's
* a longer match). So now, when I want to
* match any random string, I go into a
* separate state. */
DUMP_PREPROCESS;
PDEBUG("Flag )\n");
BEGIN(INITIAL);
return TOK_FLAG_CLOSEPAREN;
yylval.id = processid(yytext, yyleng);
PDEBUG("Found sub name: \"%s\"\n", yylval.id);
yy_pop_state();
return TOK_ID;
}
[^\n] {
DUMP_PREPROCESS;
/* Something we didn't expect */
yyerror(_("Found unexpected character: '%s'"), yytext);
}
}
<SUB_VALUE>{
({IDS}|{QUOTED_ID}) {
/* Ugh, this is a gross hack. I used to use
* {IDS} to match all TOK_IDs, but that would
* also match TOK_MODE + TOK_END_OF_RULE
* without any spaces in between (because it's
* a longer match). So now, when I want to
* match any random string, I go into a
* separate state. */
DUMP_PREPROCESS;
yylval.id = processid(yytext, yyleng);
PDEBUG("Found sub value: \"%s\"\n", yylval.id);
yy_pop_state();
return TOK_VALUE;
}
[^\n] {
DUMP_PREPROCESS;
/* Something we didn't expect */
yyerror(_("Found unexpected character: '%s'"), yytext);
}
}
<LIST_VAL_MODE>{
{CLOSE_PAREN} {
DUMP_PREPROCESS;
PDEBUG("listval: )\n");
yy_pop_state();
return TOK_CLOSEPAREN;
}
{WS}+ { DUMP_PREPROCESS; /* Eat whitespace */ }
{FLAGSEP} {
{COMMA} {
DUMP_PREPROCESS;
PDEBUG("Flag , \n");
return TOK_FLAG_SEP;
PDEBUG("listval: , \n");
/* East comma, its an optional separator */
}
{EQUALS} {
({LIST_VALUE_ID}|{QUOTED_ID}) {
DUMP_PREPROCESS;
PDEBUG("Flag = \n");
return TOK_EQUALS;
}
{KEYWORD} {
DUMP_PREPROCESS;
yylval.flag_id = strdup(yytext);
PDEBUG("Found flag: \"%s\"\n", yylval.flag_id);
return TOK_FLAG_ID;
yylval.id = processid(yytext, yyleng);
PDEBUG("listval: \"%s\"\n", yylval.id);
return TOK_VALUE;
}
[^\n] {
@@ -360,19 +370,51 @@ LT_EQUAL <=
}
}
<EXTCOND_MODE>{
{WS}+ { DUMP_PREPROCESS; /* Eat whitespace */ }
{EQUALS}{WS}*/[^(\n]{-}{WS} {
DUMP_PREPROCESS;
BEGIN(SUB_VALUE);
return TOK_EQUALS;
}
{EQUALS} {
DUMP_PREPROCESS;
return TOK_EQUALS;
}
{OPEN_PAREN} {
DUMP_PREPROCESS;
PDEBUG("extcond listv\n");
/* Don't push state here as this is a transition
* start condition and we want to return to the start
* condition that invoked <EXTCOND_MODE> when
* LIST_VAL_ID is done
*/
BEGIN(LIST_VAL_MODE);
return TOK_OPENPAREN;
}
in {
DUMP_PREPROCESS;
return TOK_IN;
}
[^\n] {
DUMP_PREPROCESS;
/* Something we didn't expect */
yyerror(_("Found unexpected character: '%s' %d"), yytext, *yytext);
}
}
<ASSIGN_MODE>{
{WS}+ { DUMP_PREPROCESS; /* Eat whitespace */ }
{ID}+ {
({IDS}|{QUOTED_ID}) {
DUMP_PREPROCESS;
yylval.var_val = processunquoted(yytext, yyleng);
PDEBUG("Found assignment value: \"%s\"\n", yylval.var_val);
return TOK_VALUE;
}
{QUOTED_ID} {
DUMP_PREPROCESS;
yylval.var_val = processquoted(yytext, yyleng);
yylval.var_val = processid(yytext, yyleng);
PDEBUG("Found assignment value: \"%s\"\n", yylval.var_val);
return TOK_VALUE;
}
@@ -388,7 +430,7 @@ LT_EQUAL <=
\r?\n {
DUMP_PREPROCESS;
current_lineno++;
BEGIN(INITIAL);
yy_pop_state();
}
[^\n] {
DUMP_PREPROCESS;
@@ -400,14 +442,14 @@ LT_EQUAL <=
<NETWORK_MODE>{
{WS}+ { DUMP_PREPROCESS; /* Eat whitespace */ }
{ID}+ {
{IDS} {
DUMP_PREPROCESS;
yylval.id = strdup(yytext);
return TOK_ID;
}
{END_OF_RULE} {
DUMP_PREPROCESS;
BEGIN(INITIAL);
yy_pop_state();
return TOK_END_OF_RULE;
}
[^\n] {
@@ -430,32 +472,18 @@ LT_EQUAL <=
return TOK_ARROW;
}
{ID}+ {
({IDS}|{QUOTED_ID}) {
/* Ugh, this is a gross hack. I used to use
* {ID}+ to match all TOK_IDs, but that would
* {IDS} to match all TOK_IDs, but that would
* also match TOK_MODE + TOK_END_OF_RULE
* without any spaces in between (because it's
* a longer match). So now, when I want to
* match any random string, I go into a
* separate state. */
DUMP_PREPROCESS;
yylval.id = processunquoted(yytext, yyleng);
yylval.id = processid(yytext, yyleng);
PDEBUG("Found change profile name: \"%s\"\n", yylval.id);
BEGIN(INITIAL);
return TOK_ID;
}
{QUOTED_ID} {
/* Ugh, this is a gross hack. I used to use
* {ID}+ to match all TOK_IDs, but that would
* also match TOK_MODE + TOK_END_OF_RULE
* without any spaces in between (because it's
* a longer match). So now, when I want to
* match any random string, I go into a
* separate state. */
DUMP_PREPROCESS;
yylval.id = processquoted(yytext, yyleng);
PDEBUG("Found change profile quoted name: \"%s\"\n", yylval.id);
BEGIN(INITIAL);
yy_pop_state();
return TOK_ID;
}
@@ -467,43 +495,6 @@ LT_EQUAL <=
}
}
#include/.*\r?\n { /* include */
PDEBUG("Matched #include\n");
BEGIN(INCLUDE);
}
#.*\r?\n { /* normal comment */
DUMP_PREPROCESS;
PDEBUG("comment(%d): %s\n", current_lineno, yytext);
current_lineno++;
BEGIN(INITIAL);
}
{END_OF_RULE} { DUMP_PREPROCESS; return TOK_END_OF_RULE; }
{SEPARATOR} {
DUMP_PREPROCESS;
PDEBUG("Matched a separator\n");
BEGIN(SUB_NAME);
return TOK_SEP;
}
{ARROW} {
DUMP_PREPROCESS;
PDEBUG("Matched a arrow\n");
return TOK_ARROW;
}
{EQUALS} {
DUMP_PREPROCESS;
PDEBUG("Matched equals for assignment\n");
BEGIN(ASSIGN_MODE);
return TOK_EQUALS;
}
{ADD_ASSIGN} {
DUMP_PREPROCESS;
PDEBUG("Matched additive value assignment\n");
BEGIN(ASSIGN_MODE);
return TOK_ADD_ASSIGN;
}
<RLIMIT_MODE>{
{WS}+ { DUMP_PREPROCESS; /* Eat whitespace */ }
@@ -526,23 +517,94 @@ LT_EQUAL <=
{END_OF_RULE} {
DUMP_PREPROCESS;
BEGIN(INITIAL);
yy_pop_state();
return TOK_END_OF_RULE;
}
\\\n {
DUMP_PREPROCESS;
current_lineno++;
BEGIN(INITIAL);
yy_pop_state();
}
\r?\n {
DUMP_PREPROCESS;
current_lineno++;
BEGIN(INITIAL);
yy_pop_state();
}
}
<MOUNT_MODE>{
{WS}+ { DUMP_PREPROCESS; /* Ignoring whitespace */ }
{ARROW} {
DUMP_PREPROCESS;
PDEBUG("Matched arrow\n");
return TOK_ARROW;
}
({IDS_NOEQ}|{PATHNAME}|{QUOTED_ID}) {
DUMP_PREPROCESS;
yylval.id = processid(yytext, yyleng);
PDEBUG("Found ID: \"%s\"\n", yylval.id);
return TOK_ID;
}
{END_OF_RULE} {
DUMP_PREPROCESS;
yy_pop_state();
return TOK_END_OF_RULE;
}
[^\n] {
DUMP_PREPROCESS;
/* Something we didn't expect */
yyerror(_("Found unexpected character: '%s'"), yytext);
}
\r?\n {
DUMP_PREPROCESS;
current_lineno++;
yy_pop_state();
}
}
#include/.*\r?\n { /* include */
PDEBUG("Matched #include\n");
yy_push_state(INCLUDE);
}
#.*\r?\n { /* normal comment */
DUMP_PREPROCESS;
PDEBUG("comment(%d): %s\n", current_lineno, yytext);
current_lineno++;
}
{END_OF_RULE} { DUMP_PREPROCESS; return TOK_END_OF_RULE; }
{CARET} {
DUMP_PREPROCESS;
PDEBUG("Matched hat ^\n");
yy_push_state(SUB_ID);
return TOK_CARET;
}
{ARROW} {
DUMP_PREPROCESS;
PDEBUG("Matched a arrow\n");
return TOK_ARROW;
}
{EQUALS} {
DUMP_PREPROCESS;
PDEBUG("Matched equals for assignment\n");
yy_push_state(ASSIGN_MODE);
return TOK_EQUALS;
}
{ADD_ASSIGN} {
DUMP_PREPROCESS;
PDEBUG("Matched additive value assignment\n");
yy_push_state(ASSIGN_MODE);
return TOK_ADD_ASSIGN;
}
{SET_VARIABLE} {
DUMP_PREPROCESS;
yylval.set_var = strdup(yytext);
@@ -568,21 +630,14 @@ LT_EQUAL <=
return TOK_CLOSE;
}
{PATHNAME} {
({PATHNAME}|{QPATHNAME}) {
DUMP_PREPROCESS;
yylval.id = processunquoted(yytext, yyleng);
yylval.id = processid(yytext, yyleng);
PDEBUG("Found id: \"%s\"\n", yylval.id);
return TOK_ID;
}
{QPATHNAME} {
DUMP_PREPROCESS;
yylval.id = processquoted(yytext, yyleng);
PDEBUG("Found id: \"%s\"\n", yylval.id);
return TOK_ID;
}
{MODES} {
({MODES})/([[:space:],]) {
DUMP_PREPROCESS;
yylval.mode = strdup(yytext);
PDEBUG("Found modes: %s\n", yylval.mode);
@@ -591,21 +646,27 @@ LT_EQUAL <=
{HAT} {
DUMP_PREPROCESS;
BEGIN(SUB_NAME2);
yy_push_state(SUB_ID);
return TOK_HAT;
}
{PROFILE} {
DUMP_PREPROCESS;
yy_push_state(SUB_ID);
return TOK_PROFILE;
}
{COLON} {
DUMP_PREPROCESS;
PDEBUG("Found a colon\n");
return TOK_COLON;
}
{FLAGOPEN_PAREN} {
{OPEN_PAREN} {
DUMP_PREPROCESS;
PDEBUG("FLag (\n");
BEGIN(FLAGS_MODE);
return TOK_FLAG_OPENPAREN;
PDEBUG("listval (\n");
yy_push_state(LIST_VAL_MODE);
return TOK_OPENPAREN;
}
{VARIABLE_NAME} {
@@ -620,20 +681,21 @@ LT_EQUAL <=
PDEBUG("Found (var) id: \"%s\"\n", yylval.id);
return TOK_ID;
break;
case TOK_PROFILE:
BEGIN(SUB_NAME2);
break;
case TOK_FLAGS:
BEGIN(FLAGS_MODE);
break;
case TOK_RLIMIT:
BEGIN(RLIMIT_MODE);
yy_push_state(RLIMIT_MODE);
break;
case TOK_NETWORK:
BEGIN(NETWORK_MODE);
yy_push_state(NETWORK_MODE);
break;
case TOK_CHANGE_PROFILE:
BEGIN(CHANGE_PROFILE_MODE);
yy_push_state(CHANGE_PROFILE_MODE);
break;
case TOK_MOUNT:
case TOK_REMOUNT:
case TOK_UMOUNT:
DUMP_PREPROCESS;
PDEBUG("Entering mount\n");
yy_push_state(MOUNT_MODE);
break;
default: /* nothing */
break;

View File

@@ -2,8 +2,8 @@
* Copyright (c) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007
* NOVELL (All rights reserved)
*
* Copyright (c) 2010, 2011
* Canonical, Ltd. (All rights reserved)
* Copyright (c) 2010 - 2012
* Canonical Ltd. (All rights reserved)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
@@ -30,6 +30,7 @@
#include <mntent.h>
#include <libintl.h>
#include <locale.h>
#include <dirent.h>
#define _(s) gettext(s)
/* enable the following line to get voluminous debug info */
@@ -59,7 +60,7 @@
#define UNPRIVILEGED_OPS (!(PRIVILEGED_OPS))
const char *parser_title = "AppArmor parser";
const char *parser_copyright = "Copyright (C) 1999-2008 Novell Inc.\nCopyright 2009-2011 Canonical Ltd.";
const char *parser_copyright = "Copyright (C) 1999-2008 Novell Inc.\nCopyright 2009-2012 Canonical Ltd.";
char *progname;
int opt_force_complain = 0;
@@ -74,8 +75,10 @@ int preprocess_only = 0;
int skip_mode_force = 0;
struct timespec mru_tstamp;
#define FLAGS_STRING_SIZE 1024
char *match_string = NULL;
char *flags_string = NULL;
char *cacheloc = NULL;
/* per-profile settings */
int force_complain = 0;
@@ -106,6 +109,7 @@ struct option long_options[] = {
{"skip-read-cache", 0, 0, 'T'},
{"write-cache", 0, 0, 'W'},
{"show-cache", 0, 0, 'k'},
{"cache-loc", 1, 0, 'L'},
{"debug", 0, 0, 'd'},
{"dump", 1, 0, 'D'},
{"Dump", 1, 0, 'D'},
@@ -147,6 +151,7 @@ static void display_usage(char *command)
"-K, --skip-cache Do not attempt to load or save cached profiles\n"
"-T, --skip-read-cache Do not attempt to load cached profiles\n"
"-W, --write-cache Save cached profile (force with -T)\n"
"-L, --cache-loc n Set the location of the profile cache\n"
"-q, --quiet Don't emit warnings\n"
"-v, --verbose Show profile names as they load\n"
"-Q, --skip-kernel-load Do everything except loading into kernel\n"
@@ -225,10 +230,10 @@ optflag_table_t optflag_table[] = {
{ 2, "expr-right-simplify", "right simplification first",
DFA_CONTROL_TREE_LEFT },
{ 1, "minimize", "dfa state minimization", DFA_CONTROL_MINIMIZE },
{ 1, "hash-perms", "minimization - hash permissions during setup",
DFA_CONTROL_MINIMIZE_HASH_PERMS },
{ 1, "hash-trans", "minimization - hash transitions during setup",
DFA_CONTROL_MINIMIZE_HASH_TRANS },
{ 1, "filter-deny", "filter out deny information from final dfa",
DFA_CONTROL_FILTER_DENY },
{ 1, "remove-unreachable", "dfa unreachable state removal",
DFA_CONTROL_REMOVE_UNREACHABLE },
{ 0, "compress-small",
@@ -522,6 +527,9 @@ static int process_arg(int c, char *optarg)
case 'T':
skip_read_cache = 1;
break;
case 'L':
cacheloc = strdup(optarg);
break;
case 'Q':
kernel_load = 0;
break;
@@ -667,17 +675,139 @@ int have_enough_privilege(void)
return 0;
}
char *snprintf_buffer(char *buf, char *pos, ssize_t size, const char *fmt, ...)
{
va_list args;
int i, remaining = size - (pos - buf);
va_start(args, fmt);
i = vsnprintf(pos, remaining, fmt, args);
va_end(args);
if (i >= size) {
PERROR(_("Feature buffer full."));
exit(1);
}
return pos + i;
}
static char *handle_features_dir(const char *filename, char **buffer, int size,
char *pos)
{
DIR *dir = NULL;
char *dirent_path = NULL;
struct dirent *dirent;
struct stat my_stat;
int len;
PDEBUG("Opened features directory \"%s\"\n", filename);
if (!(dir = opendir(filename))) {
PDEBUG("opendir failed '%s'", filename);
exit(1);
}
while ((dirent = readdir(dir)) != NULL) {
int name_len;
/* skip dotfiles silently. */
if (dirent->d_name[0] == '.')
continue;
if (dirent_path)
free(dirent_path);
if (asprintf(&dirent_path, "%s/%s", filename, dirent->d_name) < 0)
{
PERROR(_("Memory allocation error."));
exit(1);
}
name_len = strlen(dirent->d_name);
if (!name_len)
continue;
if (stat(dirent_path, &my_stat)) {
PERROR(_("stat failed for '%s'"), dirent_path);
exit(1);
}
pos = snprintf_buffer(*buffer, pos, size, "%s {", dirent->d_name);
if (S_ISREG(my_stat.st_mode)) {
int file;
int remaining = size - (pos - *buffer);
if (!(file = open(dirent_path, O_RDONLY))) {
PDEBUG("Could not open '%s' in '%s'", dirent_path, filename);
exit(1);
break;
}
PDEBUG("Opened features \"%s\" in \"%s\"\n", dirent_path, filename);
if (my_stat.st_size > remaining) {
PERROR(_("Feature buffer full."));
exit(1);
}
do {
len = read(file, pos, remaining);
if (len > 0) {
remaining -= len;
pos += len;
*pos = 0;
}
} while (len > 0);
if (len < 0) {
PDEBUG("Error reading feature file '%s'\n",
dirent_path);
exit(1);
}
close(file);
} else if (S_ISDIR(my_stat.st_mode)) {
pos = handle_features_dir(dirent_path, buffer, size,
pos);
if (!pos)
break;
}
pos = snprintf_buffer(*buffer, pos, size, "}\n");
}
if (dirent_path)
free(dirent_path);
closedir(dir);
return pos;
}
/* match_string == NULL --> no match_string available
match_string != NULL --> either a matching string specified on the
command line, or the kernel supplied a match string */
static void get_match_string(void) {
FILE *ms = NULL;
struct stat stat_file;
/* has process_args() already assigned a match string? */
if (match_string)
goto out;
if (stat(FLAGS_FILE, &stat_file) == -1)
goto out;
if (S_ISDIR(stat_file.st_mode)) {
/* if we have a features directory default to */
regex_type = AARE_DFA;
perms_create = 1;
flags_string = malloc(FLAGS_STRING_SIZE);
handle_features_dir(FLAGS_FILE, &flags_string, FLAGS_STRING_SIZE, flags_string);
if (strstr(flags_string, "network"))
kernel_supports_network = 1;
else
kernel_supports_network = 0;
if (strstr(flags_string, "mount"))
kernel_supports_mount = 1;
return;
}
ms = fopen(MATCH_STRING, "r");
if (!ms)
goto out;
@@ -716,20 +846,24 @@ out:
static void get_flags_string(char **flags, char *flags_file) {
char *pos;
FILE *f = NULL;
size_t size;
/* abort if missing or already set */
if (!flags || *flags) return;
if (!flags || *flags)
return;
f = fopen(flags_file, "r");
if (!f)
return;
*flags = malloc(1024);
*flags = malloc(FLAGS_STRING_SIZE);
if (!*flags)
goto fail;
if (!fgets(*flags, 1024, f))
size = fread(*flags, 1, FLAGS_STRING_SIZE - 1, f);
if (!size || ferror(f))
goto fail;
(*flags)[size] = 0;
fclose(f);
pos = strstr(*flags, "change_hat=");
@@ -920,6 +1054,15 @@ int process_profile(int option, char *profilename)
if (retval != 0)
goto out;
/* Test to see if profile is for another namespace, if so disable
* caching for now
* TODO: Add support for caching profiles in an alternate namespace
* TODO: Add support for embedded namespace defines if they aren't
* removed from the language.
*/
if (profile_namespace)
skip_cache = 1;
/* Do secondary test to see if cached binary profile is good,
* instead of checking against a presupplied list of files
* use the timestamps from the files that were parsed.
@@ -928,8 +1071,14 @@ int process_profile(int option, char *profilename)
*/
if ((profilename && option != OPTION_REMOVE) && !force_complain &&
!skip_cache) {
if (asprintf(&cachename, "%s/%s/%s", basedir, "cache", basename)<0) {
perror("asprintf");
if (cacheloc) {
cachename = strdup(cacheloc);
if (!cachename) {
PERROR(_("Memory allocation error."));
exit(1);
}
} else if (asprintf(&cachename, "%s/%s/%s", basedir, "cache", basename)<0) {
PERROR(_("Memory allocation error."));
exit(1);
}
/* Load a binary cache if it exists and is newest */

View File

@@ -17,6 +17,7 @@
/* assistance routines */
#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
@@ -36,6 +37,7 @@
#include "parser.h"
#include "parser_yacc.h"
#include "mount.h"
/* #define DEBUG */
#ifdef DEBUG
@@ -53,8 +55,6 @@ struct keyword_table {
};
static struct keyword_table keyword_table[] = {
/* flags */
{"flags", TOK_FLAGS},
/* network */
{"network", TOK_NETWORK},
/* misc keywords */
@@ -73,12 +73,18 @@ static struct keyword_table keyword_table[] = {
{"subset", TOK_SUBSET},
{"audit", TOK_AUDIT},
{"deny", TOK_DENY},
{"profile", TOK_PROFILE},
{"set", TOK_SET},
{"rlimit", TOK_RLIMIT},
{"alias", TOK_ALIAS},
{"rewrite", TOK_ALIAS},
{"ptrace", TOK_PTRACE},
{"file", TOK_FILE},
{"mount", TOK_MOUNT},
{"remount", TOK_REMOUNT},
{"umount", TOK_UMOUNT},
{"unmount", TOK_UMOUNT},
{"pivot_root", TOK_PIVOTROOT},
{"in", TOK_IN},
/* terminate */
{NULL, 0}
};
@@ -397,6 +403,16 @@ char *processunquoted(char *string, int len)
return tmp;
}
char *processid(char *string, int len)
{
/* lexer should never call this fn if len <= 0 */
assert(len > 0);
if (*string == '"')
return processquoted(string, len);
return processunquoted(string, len);
}
/* rewrite a quoted string substituting escaped characters for the
* real thing. Strip the quotes around the string */
@@ -767,6 +783,20 @@ void free_cod_entries(struct cod_entry *list)
free(list);
}
void free_mnt_entries(struct mnt_entry *list)
{
if (!list)
return;
if (list->next)
free_mnt_entries(list->next);
free(list->mnt_point);
free(list->device);
free_value_list(list->dev_type);
free_value_list(list->opts);
free(list);
}
static void debug_base_perm_mask(int mask)
{
if (HAS_MAY_READ(mask))
@@ -904,8 +934,6 @@ void debug_capabilities(struct codomain *cod)
__debug_capabilities(cod->deny_caps, "Deny Caps");
if (cod->quiet_caps != 0ull)
__debug_capabilities(cod->quiet_caps, "Quiet Caps");
if (cod->set_caps != 0ull)
__debug_capabilities(cod->set_caps, "Set Capabilities");
}
void debug_cod_list(struct codomain *cod)
@@ -932,6 +960,102 @@ void debug_cod_list(struct codomain *cod)
dump_policy_hats(cod);
}
struct value_list *new_value_list(char *value)
{
struct value_list *val = calloc(1, sizeof(struct value_list));
if (val)
val->value = value;
return val;
}
void free_value_list(struct value_list *list)
{
struct value_list *next;
while (list) {
next = list->next;
if (list->value)
free(list->value);
free(list);
list = next;
}
}
struct value_list *dup_value_list(struct value_list *list)
{
struct value_list *entry, *dup, *head = NULL;
char *value;
list_for_each(list, entry) {
value = NULL;
if (list->value) {
value = strdup(list->value);
if (!value)
goto fail2;
}
dup = new_value_list(value);
if (!dup)
goto fail;
if (head)
list_append(head, dup);
else
head = dup;
}
return head;
fail:
free(value);
fail2:
free_value_list(head);
return NULL;
}
void print_value_list(struct value_list *list)
{
struct value_list *entry;
if (!list)
return;
fprintf(stderr, "%s", list->value);
list = list->next;
list_for_each(list, entry) {
fprintf(stderr, ", %s", entry->value);
}
}
struct cond_entry *new_cond_entry(char *name, int eq, struct value_list *list)
{
struct cond_entry *ent = calloc(1, sizeof(struct cond_entry));
if (ent) {
ent->name = name;
ent->vals = list;
ent->eq = eq;
}
return ent;
}
void free_cond_entry(struct cond_entry *ent)
{
if (ent) {
free(ent->name);
free_value_list(ent->vals);
free(ent);
}
}
void print_cond_entry(struct cond_entry *ent)
{
if (ent) {
fprintf(stderr, "%s=(", ent->name);
print_value_list(ent->vals);
fprintf(stderr, ")\n");
}
}
#ifdef UNIT_TEST
int test_str_to_boolean(void)
{

View File

@@ -2,8 +2,8 @@
* Copyright (c) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007
* NOVELL (All rights reserved)
*
* Copyright (c) 2010
* Canonical, Ltd. (All rights reserved)
* Copyright (c) 2010 - 2012
* Canonical Ltd. (All rights reserved)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
@@ -29,6 +29,7 @@
#define _(s) gettext(s)
#include "parser.h"
#include "mount.h"
#include "parser_yacc.h"
/* #define DEBUG */
@@ -95,10 +96,26 @@ void add_hat_to_policy(struct codomain *cod, struct codomain *hat)
}
}
static int add_entry_to_x_table(struct codomain *cod, char *name)
{
int i;
for (i = (AA_EXEC_LOCAL >> 10) + 1; i < AA_EXEC_COUNT; i++) {
if (!cod->exec_table[i]) {
cod->exec_table[i] = name;
return i;
} else if (strcmp(cod->exec_table[i], name) == 0) {
/* name already in table */
free(name);
return i;
}
}
free(name);
return 0;
}
static int add_named_transition(struct codomain *cod, struct cod_entry *entry)
{
char *name = NULL;
int i;
/* check to see if it is a local transition */
if (!entry->namespace) {
@@ -146,18 +163,7 @@ static int add_named_transition(struct codomain *cod, struct cod_entry *entry)
name = entry->nt_name;
}
for (i = (AA_EXEC_LOCAL >> 10) + 1; i < AA_EXEC_COUNT; i++) {
if (!cod->exec_table[i]) {
cod->exec_table[i] = name;
return i;
} else if (strcmp(cod->exec_table[i], name) == 0) {
/* name already in table */
free(name);
return i;
}
}
free(name);
return 0;
return add_entry_to_x_table(cod, name);
}
void add_entry_to_policy(struct codomain *cod, struct cod_entry *entry)
@@ -166,16 +172,17 @@ void add_entry_to_policy(struct codomain *cod, struct cod_entry *entry)
cod->entries = entry;
}
void post_process_nt_entries(struct codomain *cod)
void post_process_file_entries(struct codomain *cod)
{
struct cod_entry *entry;
int cp_mode = 0;
list_for_each(cod->entries, entry) {
if (entry->nt_name) {
int mode = 0;
int n = add_named_transition(cod, entry);
if (!n) {
PERROR("Profile %s has to many specified profile transitions.\n", cod->name);
PERROR("Profile %s has too many specified profile transitions.\n", cod->name);
exit(1);
}
if (entry->mode & AA_USER_EXEC)
@@ -187,9 +194,56 @@ void post_process_nt_entries(struct codomain *cod)
entry->namespace = NULL;
entry->nt_name = NULL;
}
/* FIXME: currently change_profile also implies onexec */
cp_mode |= entry->mode & (AA_CHANGE_PROFILE);
}
/* if there are change_profile rules, this implies that we need
* access to /proc/self/attr/current
*/
if (cp_mode & AA_CHANGE_PROFILE) {
/* FIXME: should use @{PROC}/@{PID}/attr/{current,exec} */
struct cod_entry *new_ent;
char *buffer = strdup("/proc/*/attr/{current,exec}");
if (!buffer) {
PERROR("Memory allocation error\n");
exit(1);
}
new_ent = new_entry(NULL, buffer, AA_MAY_WRITE, NULL);
if (!new_ent) {
PERROR("Memory allocation error\n");
exit(1);
}
add_entry_to_policy(cod, new_ent);
}
}
void post_process_mnt_entries(struct codomain *cod)
{
struct mnt_entry *entry;
list_for_each(cod->mnt_ents, entry) {
if (entry->trans) {
unsigned int mode = 0;
int n = add_entry_to_x_table(cod, entry->trans);
if (!n) {
PERROR("Profile %s has too many specified profile transitions.\n", cod->name);
exit(1);
}
if (entry->allow & AA_USER_EXEC)
mode |= SHIFT_MODE(n << 10, AA_USER_SHIFT);
if (entry->allow & AA_OTHER_EXEC)
mode |= SHIFT_MODE(n << 10, AA_OTHER_SHIFT);
entry->allow = ((entry->allow & ~AA_ALL_EXEC_MODIFIERS) |
(mode & AA_ALL_EXEC_MODIFIERS));
entry->trans = NULL;
}
}
}
static void __merge_rules(const void *nodep, const VISIT value,
const int __unused depth)
{
@@ -294,6 +348,33 @@ int process_hat_regex(struct codomain *cod)
return 0;
}
static void __process_policydb(const void *nodep, const VISIT value,
const int __unused depth)
{
struct codomain **t = (struct codomain **) nodep;
if (value == preorder || value == endorder)
return;
if (process_policydb(*t) != 0) {
PERROR(_("ERROR processing policydb rules for profile %s, failed to load\n"),
(*t)->name);
exit(1);
}
}
int post_process_policydb(void)
{
twalk(policy_list, __process_policydb);
return 0;
}
int process_hat_policydb(struct codomain *cod)
{
twalk(cod->hat_table, __process_policydb);
return 0;
}
static void __process_variables(const void *nodep, const VISIT value,
const int __unused depth)
{
@@ -645,7 +726,6 @@ struct codomain *merge_policy(struct codomain *a, struct codomain *b)
a->audit_caps |= b->audit_caps;
a->deny_caps |= b->deny_caps;
a->quiet_caps |= b->quiet_caps;
a->set_caps |= b->set_caps;
if (a->network_allowed) {
size_t i;
@@ -707,6 +787,15 @@ int post_process_policy(int debug_only)
}
}
if (!debug_only) {
retval = post_process_policydb();
if (retval != 0) {
PERROR(_("%s: Errors found during policydb postprocess. Aborting.\n"),
progname);
return retval;
}
}
return retval;
}
@@ -728,10 +817,15 @@ void free_policy(struct codomain *cod)
return;
free_hat_table(cod->hat_table);
free_cod_entries(cod->entries);
free_mnt_entries(cod->mnt_ents);
if (cod->dfarules)
aare_delete_ruleset(cod->dfarules);
if (cod->dfa)
free(cod->dfa);
if (cod->policy_rules)
aare_delete_ruleset(cod->policy_rules);
if (cod->policy_dfa)
free(cod->policy_dfa);
if (cod->name)
free(cod->name);
if (cod->attachment)

View File

@@ -28,6 +28,8 @@
#include "parser.h"
#include "libapparmor_re/apparmor_re.h"
#include "libapparmor_re/aare_rules.h"
#include "mount.h"
#include "policydb.h"
enum error_type {
e_no_error,
@@ -508,19 +510,28 @@ static int process_dfa_entry(aare_ruleset_t *dfarules, struct cod_entry *entry)
return FALSE;
}
if (entry->mode & AA_CHANGE_PROFILE) {
char *vec[3];
char lbuf[PATH_MAX + 8];
int index = 1;
/* allow change_profile for all execs */
vec[0] = "/[^\\x00]*";
if (entry->namespace) {
char *vec[2];
char lbuf[PATH_MAX + 8];
int pos;
ptype = convert_aaregex_to_pcre(entry->namespace, 0, lbuf, PATH_MAX + 8, &pos);
vec[0] = lbuf;
vec[1] = tbuf;
if (!aare_add_rule_vec(dfarules, 0, AA_CHANGE_PROFILE, 0, 2, vec, dfaflags))
return FALSE;
} else {
if (!aare_add_rule(dfarules, tbuf, 0, AA_CHANGE_PROFILE, 0, dfaflags))
return FALSE;
vec[index++] = lbuf;
}
vec[index++] = tbuf;
/* regular change_profile rule */
if (!aare_add_rule_vec(dfarules, 0, AA_CHANGE_PROFILE | AA_ONEXEC, 0, index - 1, &vec[1], dfaflags))
return FALSE;
/* onexec rules - both rules are needed for onexec */
if (!aare_add_rule_vec(dfarules, 0, AA_ONEXEC, 0, 1, vec, dfaflags))
return FALSE;
if (!aare_add_rule_vec(dfarules, 0, AA_ONEXEC, 0, index, vec, dfaflags))
return FALSE;
}
if (entry->mode & (AA_USER_PTRACE | AA_OTHER_PTRACE)) {
int mode = entry->mode & (AA_USER_PTRACE | AA_OTHER_PTRACE);
@@ -611,6 +622,479 @@ out:
return error;
}
static int build_list_val_expr(char *buffer, int size, struct value_list *list)
{
struct value_list *ent;
char tmp[PATH_MAX + 3];
char *p;
int len;
pattern_t ptype;
int pos;
if (!list) {
strncpy(buffer, "[^\\000]*", size);
return TRUE;
}
p = buffer;
strncpy(p, "(", size - (p - buffer));
p++;
if (p > buffer + size)
goto fail;
ptype = convert_aaregex_to_pcre(list->value, 0, tmp, PATH_MAX+3, &pos);
if (ptype == ePatternInvalid)
goto fail;
len = strlen(tmp);
if (len > size - (p - buffer))
goto fail;
strcpy(p, tmp);
p += len;
list_for_each(list->next, ent) {
ptype = convert_aaregex_to_pcre(ent->value, 0, tmp,
PATH_MAX+3, &pos);
if (ptype == ePatternInvalid)
goto fail;
strncpy(p, "|", size - (p - buffer));
p++;
len = strlen(tmp);
if (len > size - (p - buffer))
goto fail;
strcpy(p, tmp);
p += len;
}
strncpy(p, ")", size - (p - buffer));
p++;
if (p > buffer + size)
goto fail;
return TRUE;
fail:
return FALSE;
}
static int convert_entry(char *buffer, int size, char *entry)
{
pattern_t ptype;
int pos;
if (entry) {
ptype = convert_aaregex_to_pcre(entry, 0, buffer, size, &pos);
if (ptype == ePatternInvalid)
return FALSE;
} else {
/* match any char except \000 0 or more times */
if (size < 8)
return FALSE;
strcpy(buffer, "[^\\000]*");
}
return TRUE;
}
static int build_mnt_flags(char *buffer, int size, unsigned int flags,
unsigned int inv_flags)
{
char *p = buffer;
int i, len = 0;
if (flags == MS_ALL_FLAGS) {
/* all flags are optional */
len = snprintf(p, size, "[^\\000]*");
if (len < 0 || len >= size)
return FALSE;
return TRUE;
}
for (i = 0; i <= 31; ++i) {
if ((flags & inv_flags) & (1 << i))
len = snprintf(p, size, "(\\x%02x|)", i + 1);
else if (flags & (1 << i))
len = snprintf(p, size, "\\x%02x", i + 1);
else /* no entry = not set */
continue;
if (len < 0 || len >= size)
return FALSE;
p += len;
size -= len;
}
/* this needs to go once the backend is updated. */
if (buffer == p) {
/* match nothing - use impossible 254 as regex parser doesn't
* like the empty string
*/
if (size < 9)
return FALSE;
strcpy(p, "(\\xfe|)");
}
return TRUE;
}
static int build_mnt_opts(char *buffer, int size, struct value_list *opts)
{
struct value_list *ent;
char tmp[PATH_MAX + 3];
char *p;
int len;
pattern_t ptype;
int pos;
if (!opts) {
if (size < 8)
return FALSE;
strncpy(buffer, "[^\\000]*", size);
return TRUE;
}
p = buffer;
list_for_each(opts, ent) {
ptype = convert_aaregex_to_pcre(ent->value, 0, tmp,
PATH_MAX+3, &pos);
if (ptype == ePatternInvalid)
goto fail;
len = strlen(tmp);
if (len > size - (p - buffer))
goto fail;
strcpy(p, tmp);
p += len;
if (ent->next && size - (p - buffer) > 1) {
*p++ = ',';
*p = 0;
}
}
return TRUE;
fail:
return FALSE;
}
static int process_mnt_entry(aare_ruleset_t *dfarules, struct mnt_entry *entry)
{
char mntbuf[PATH_MAX + 3];
char devbuf[PATH_MAX + 3];
char typebuf[PATH_MAX + 3];
char flagsbuf[PATH_MAX + 3];
char optsbuf[PATH_MAX + 3];
char *p, *vec[5];
int count = 0;
unsigned int flags, inv_flags;
/* a single mount rule may result in multiple matching rules being
* created in the backend to cover all the possible choices
*/
if ((entry->allow & AA_MAY_MOUNT) && (entry->flags & MS_REMOUNT)
&& !entry->device && !entry->dev_type) {
int allow;
/* remount can't be conditional on device and type */
p = mntbuf;
/* rule class single byte header */
p += sprintf(p, "\\x%02x", AA_CLASS_MOUNT);
if (entry->mnt_point) {
/* both device && mnt_point or just mnt_point */
if (!convert_entry(p, PATH_MAX +3, entry->mnt_point))
goto fail;
vec[0] = mntbuf;
} else {
if (!convert_entry(p, PATH_MAX +3, entry->device))
goto fail;
vec[0] = mntbuf;
}
/* skip device */
if (!convert_entry(devbuf, PATH_MAX +3, NULL))
goto fail;
vec[1] = devbuf;
/* skip type */
vec[2] = devbuf;
flags = entry->flags;
inv_flags = entry->inv_flags;
if (flags != MS_ALL_FLAGS)
flags &= MS_REMOUNT_FLAGS;
if (inv_flags != MS_ALL_FLAGS)
flags &= MS_REMOUNT_FLAGS;
if (!build_mnt_flags(flagsbuf, PATH_MAX, flags, inv_flags))
goto fail;
vec[3] = flagsbuf;
if (!build_mnt_opts(optsbuf, PATH_MAX, entry->opts))
goto fail;
if (entry->opts)
allow = AA_MATCH_CONT;
else
allow = entry->allow;
/* rule for match without required data || data MATCH_CONT */
if (!aare_add_rule_vec(dfarules, entry->deny, allow,
entry->audit | AA_AUDIT_MNT_DATA, 4,
vec, dfaflags))
goto fail;
count++;
if (entry->opts) {
/* rule with data match required */
if (!build_mnt_opts(optsbuf, PATH_MAX, entry->opts))
goto fail;
vec[4] = optsbuf;
if (!aare_add_rule_vec(dfarules, entry->deny,
entry->allow,
entry->audit | AA_AUDIT_MNT_DATA,
5, vec, dfaflags))
goto fail;
count++;
}
}
if ((entry->allow & AA_MAY_MOUNT) && (entry->flags & MS_BIND)
&& !entry->dev_type && !entry->opts) {
/* bind mount rules can't be conditional on dev_type or data */
p = mntbuf;
/* rule class single byte header */
p += sprintf(p, "\\x%02x", AA_CLASS_MOUNT);
if (!convert_entry(p, PATH_MAX +3, entry->mnt_point))
goto fail;
vec[0] = mntbuf;
if (!convert_entry(devbuf, PATH_MAX +3, entry->device))
goto fail;
vec[1] = devbuf;
if (!convert_entry(typebuf, PATH_MAX +3, NULL))
goto fail;
vec[2] = typebuf;
flags = entry->flags;
inv_flags = entry->inv_flags;
if (flags != MS_ALL_FLAGS)
flags &= MS_BIND_FLAGS;
if (inv_flags != MS_ALL_FLAGS)
flags &= MS_BIND_FLAGS;
if (!build_mnt_flags(flagsbuf, PATH_MAX, flags, inv_flags))
goto fail;
vec[3] = flagsbuf;
if (!aare_add_rule_vec(dfarules, entry->deny, entry->allow,
entry->audit, 4, vec, dfaflags))
goto fail;
count++;
}
if ((entry->allow & AA_MAY_MOUNT) &&
(entry->flags & (MS_UNBINDABLE | MS_PRIVATE | MS_SLAVE | MS_SHARED))
&& !entry->device && !entry->dev_type && !entry->opts) {
/* change type base rules can not be conditional on device,
* device type or data
*/
p = mntbuf;
/* rule class single byte header */
p += sprintf(p, "\\x%02x", AA_CLASS_MOUNT);
if (!convert_entry(p, PATH_MAX +3, entry->mnt_point))
goto fail;
vec[0] = mntbuf;
/* skip device and type */
if (!convert_entry(devbuf, PATH_MAX +3, NULL))
goto fail;
vec[1] = devbuf;
vec[2] = devbuf;
flags = entry->flags;
inv_flags = entry->inv_flags;
if (flags != MS_ALL_FLAGS)
flags &= MS_MAKE_FLAGS;
if (inv_flags != MS_ALL_FLAGS)
flags &= MS_MAKE_FLAGS;
if (!build_mnt_flags(flagsbuf, PATH_MAX, flags, inv_flags))
goto fail;
vec[3] = flagsbuf;
if (!aare_add_rule_vec(dfarules, entry->deny, entry->allow,
entry->audit, 4, vec, dfaflags))
goto fail;
count++;
}
if ((entry->allow & AA_MAY_MOUNT) && (entry->flags & MS_MOVE)
&& !entry->dev_type && !entry->opts) {
/* mount move rules can not be conditional on dev_type,
* or data
*/
p = mntbuf;
/* rule class single byte header */
p += sprintf(p, "\\x%02x", AA_CLASS_MOUNT);
if (!convert_entry(p, PATH_MAX +3, entry->mnt_point))
goto fail;
vec[0] = mntbuf;
if (!convert_entry(devbuf, PATH_MAX +3, entry->device))
goto fail;
vec[1] = devbuf;
/* skip type */
if (!convert_entry(typebuf, PATH_MAX +3, NULL))
goto fail;
vec[2] = typebuf;
flags = entry->flags;
inv_flags = entry->inv_flags;
if (flags != MS_ALL_FLAGS)
flags &= MS_MOVE_FLAGS;
if (inv_flags != MS_ALL_FLAGS)
flags &= MS_MOVE_FLAGS;
if (!build_mnt_flags(flagsbuf, PATH_MAX, flags, inv_flags))
goto fail;
vec[3] = flagsbuf;
if (!aare_add_rule_vec(dfarules, entry->deny, entry->allow,
entry->audit, 4, vec, dfaflags))
goto fail;
count++;
}
if ((entry->allow & AA_MAY_MOUNT) &&
(entry->flags | entry->inv_flags) & ~MS_CMDS) {
int allow;
/* generic mount if flags are set that are not covered by
* above commands
*/
p = mntbuf;
/* rule class single byte header */
p += sprintf(p, "\\x%02x", AA_CLASS_MOUNT);
if (!convert_entry(p, PATH_MAX +3, entry->mnt_point))
goto fail;
vec[0] = mntbuf;
if (!convert_entry(devbuf, PATH_MAX +3, entry->device))
goto fail;
vec[1] = devbuf;
if (!build_list_val_expr(typebuf, PATH_MAX+2, entry->dev_type))
goto fail;
vec[2] = typebuf;
flags = entry->flags;
inv_flags = entry->inv_flags;
if (flags != MS_ALL_FLAGS)
flags &= ~MS_CMDS;
if (inv_flags != MS_ALL_FLAGS)
flags &= ~MS_CMDS;
if (!build_mnt_flags(flagsbuf, PATH_MAX, flags, inv_flags))
goto fail;
vec[3] = flagsbuf;
if (entry->opts)
allow = AA_MATCH_CONT;
else
allow = entry->allow;
/* rule for match without required data || data MATCH_CONT */
if (!aare_add_rule_vec(dfarules, entry->deny, allow,
entry->audit | AA_AUDIT_MNT_DATA, 4,
vec, dfaflags))
goto fail;
count++;
if (entry->opts) {
/* rule with data match required */
if (!build_mnt_opts(optsbuf, PATH_MAX, entry->opts))
goto fail;
vec[4] = optsbuf;
if (!aare_add_rule_vec(dfarules, entry->deny,
entry->allow,
entry->audit | AA_AUDIT_MNT_DATA,
5, vec, dfaflags))
goto fail;
count++;
}
}
if (entry->allow & AA_MAY_UMOUNT) {
p = mntbuf;
/* rule class single byte header */
p += sprintf(p, "\\x%02x", AA_CLASS_MOUNT);
if (!convert_entry(p, PATH_MAX +3, entry->mnt_point))
goto fail;
vec[0] = mntbuf;
if (!aare_add_rule_vec(dfarules, entry->deny, entry->allow,
entry->audit, 1, vec, dfaflags))
goto fail;
count++;
}
if (entry->allow & AA_MAY_PIVOTROOT) {
p = mntbuf;
/* rule class single byte header */
p += sprintf(p, "\\x%02x", AA_CLASS_MOUNT);
if (!convert_entry(p, PATH_MAX +3, entry->mnt_point))
goto fail;
vec[0] = mntbuf;
if (!convert_entry(devbuf, PATH_MAX +3, entry->device))
goto fail;
vec[1] = devbuf;
if (!aare_add_rule_vec(dfarules, entry->deny, entry->allow,
entry->audit, 2, vec, dfaflags))
goto fail;
count++;
}
if (!count)
/* didn't actually encode anything */
goto fail;
return TRUE;
fail:
PERROR("Enocoding of mount rule failed\n");
return FALSE;
}
int post_process_policydb_ents(struct codomain *cod)
{
int ret = TRUE;
int count = 0;
/* Add fns for rules that should be added to policydb here */
if (cod->mnt_ents && kernel_supports_mount) {
struct mnt_entry *entry;
list_for_each(cod->mnt_ents, entry) {
if (regex_type == AARE_DFA &&
!process_mnt_entry(cod->policy_rules, entry))
ret = FALSE;
count++;
}
} else if (cod->mnt_ents && !kernel_supports_mount)
pwarn("profile %s mount rules not enforced\n", cod->name);
cod->policy_rule_count = count;
return ret;
}
int process_policydb(struct codomain *cod)
{
int error = -1;
if (regex_type == AARE_DFA) {
cod->policy_rules = aare_new_ruleset(0);
if (!cod->policy_rules)
goto out;
}
if (!post_process_policydb_ents(cod))
goto out;
if (regex_type == AARE_DFA && cod->policy_rule_count > 0) {
cod->policy_dfa = aare_create_dfa(cod->policy_rules,
&cod->policy_dfa_size,
dfaflags);
aare_delete_ruleset(cod->policy_rules);
cod->policy_rules = NULL;
if (!cod->policy_dfa)
goto out;
}
aare_reset_matchflags();
if (process_hat_policydb(cod) != 0)
goto out;
error = 0;
out:
return error;
}
void reset_regex(void)
{
aare_reset_matchflags();

View File

@@ -28,6 +28,7 @@
/* #define DEBUG */
#include "parser.h"
#include "mount.h"
static inline char *get_var_end(char *var)
{
@@ -130,17 +131,19 @@ void free_var_string(struct var_string *var)
free(var);
}
static int expand_entry_variables(struct cod_entry *entry)
/* doesn't handle variables in options atm */
static int expand_entry_variables(char **name, void *entry,
int (dup_and_chain)(void *))
{
struct set_value *valuelist;
int ret = TRUE;
char *value;
struct var_string *split_var;
if (!entry) /* shouldn't happen */
if (!entry) /* can happen when entry is optional */
return ret;
while ((split_var = split_out_var(entry->name))) {
while ((split_var = split_out_var(*name))) {
valuelist = get_set_var(split_var->var);
if (!valuelist) {
int boolean = get_boolean_var(split_var->var);
@@ -159,24 +162,22 @@ static int expand_entry_variables(struct cod_entry *entry)
split_var->var);
exit(1);
}
free(entry->name);
if (asprintf(&(entry->name), "%s%s%s",
free(*name);
if (asprintf(name, "%s%s%s",
split_var->prefix ? split_var->prefix : "",
value,
split_var->suffix ? split_var->suffix : "") == -1)
return FALSE;
while ((value = get_next_set_value(&valuelist))) {
struct cod_entry *dupe = copy_cod_entry(entry);
if (!dupe) {
PERROR("Memory allocaton error while handling set variable %s\n",
if (!dup_and_chain(entry)) {
PERROR("Memory allocation error while handling set variable %s\n",
split_var->var);
exit(1);
}
entry->next = dupe;
free(entry->name);
if (asprintf(&(entry->name), "%s%s%s",
free(*name);
if (asprintf(name, "%s%s%s",
split_var->prefix ? split_var->prefix : "", value,
split_var->suffix ? split_var->suffix : "") == -1)
return FALSE;
@@ -187,15 +188,66 @@ static int expand_entry_variables(struct cod_entry *entry)
return ret;
}
int clone_and_chain_cod(void *v)
{
struct cod_entry *entry = v;
struct cod_entry *dup = copy_cod_entry(entry);
if (!dup)
return 0;
entry->next = dup;
return 1;
}
int clone_and_chain_mnt(void *v)
{
struct mnt_entry *entry = v;
struct mnt_entry *dup = dup_mnt_entry(entry);
if (!dup)
return 0;
entry->next = dup;
return 1;
}
static int process_variables_in_entries(struct cod_entry *entry_list)
{
int ret = TRUE, rc;
struct cod_entry *entry;
list_for_each(entry_list, entry) {
rc = expand_entry_variables(entry);
rc = expand_entry_variables(&entry->name, entry,
clone_and_chain_cod);
if (!rc)
ret = FALSE;
return FALSE;
}
return ret;
}
/* does not currently support expansion of vars in options */
static int process_variables_in_mnt_entries(struct mnt_entry *entry_list)
{
int ret = TRUE, rc;
struct mnt_entry *entry;
list_for_each(entry_list, entry) {
rc = expand_entry_variables(&entry->mnt_point, entry,
clone_and_chain_mnt);
if (!rc)
return FALSE;
rc = expand_entry_variables(&entry->device, entry,
clone_and_chain_mnt);
if (!rc)
return FALSE;
rc = expand_entry_variables(&entry->trans, entry,
clone_and_chain_mnt);
if (!rc)
return FALSE;
}
return ret;
@@ -209,6 +261,10 @@ int process_variables(struct codomain *cod)
error = -1;
}
if (!process_variables_in_mnt_entries(cod->mnt_ents)) {
error = -1;
}
if (process_hat_variables(cod) != 0) {
error = -1;
}

View File

@@ -2,8 +2,8 @@
/*
* Copyright (c) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007
* NOVELL (All rights reserved)
* Copyright (c) 2010
* Canonical, Ltd.
* Copyright (c) 2010-2012
* Canonical Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
@@ -32,6 +32,7 @@
/* #define DEBUG */
#include "parser.h"
#include "mount.h"
#include "parser_include.h"
#include <unistd.h>
#include <netinet/in.h>
@@ -64,21 +65,23 @@
#define CAP_TO_MASK(x) (1ull << (x))
struct value_list {
char *value;
struct value_list *next;
};
int parser_token = 0;
void free_value_list(struct value_list *list);
struct cod_entry *do_file_rule(char *namespace, char *id, int mode,
char *link_id, char *nt);
struct mnt_entry *do_mnt_rule(struct cond_entry *src_conds, char *src,
struct cond_entry *dst_conds, char *dst,
int mode);
struct mnt_entry *do_pivot_rule(struct cond_entry *old, char *root,
char *transition);
void add_local_entry(struct codomain *cod);
%}
%token TOK_ID
%token TOK_SEP
%token TOK_CONDID
%token TOK_CARET
%token TOK_OPEN
%token TOK_CLOSE
%token TOK_MODE
@@ -110,6 +113,15 @@ void add_local_entry(struct codomain *cod);
%token TOK_SET
%token TOK_ALIAS
%token TOK_PTRACE
%token TOK_OPENPAREN
%token TOK_CLOSEPAREN
%token TOK_COMMA
%token TOK_FILE
%token TOK_MOUNT
%token TOK_REMOUNT
%token TOK_UMOUNT
%token TOK_PIVOTROOT
%token TOK_IN
/* rlimits */
%token TOK_RLIMIT
@@ -136,10 +148,6 @@ void add_local_entry(struct codomain *cod);
/* debug flag values */
%token TOK_FLAGS
%token TOK_FLAG_OPENPAREN
%token TOK_FLAG_CLOSEPAREN
%token TOK_FLAG_SEP
%token TOK_FLAG_ID
%union {
char *id;
@@ -149,6 +157,8 @@ void add_local_entry(struct codomain *cod);
struct codomain *cod;
struct cod_net_entry *net_entry;
struct cod_entry *user_entry;
struct mnt_entry *mnt_entry;
struct flagval flags;
int fmode;
uint64_t cap;
@@ -157,11 +167,13 @@ void add_local_entry(struct codomain *cod);
char *bool_var;
char *var_val;
struct value_list *val_list;
struct cond_entry *cond_entry;
int boolean;
struct named_transition transition;
}
%type <id> TOK_ID
%type <id> TOK_CONDID
%type <mode> TOK_MODE
%type <fmode> file_mode
%type <cod> profile_base
@@ -172,14 +184,19 @@ void add_local_entry(struct codomain *cod);
%type <cod> cond_rule
%type <network_entry> network_rule
%type <user_entry> rule
%type <user_entry> file_rule
%type <user_entry> file_rule_tail
%type <user_entry> link_rule
%type <user_entry> ptrace_rule
%type <user_entry> frule
%type <mnt_entry> mnt_rule
%type <cond_entry> opt_conds
%type <cond_entry> cond
%type <flags> flags
%type <flags> flagvals
%type <flags> flagval
%type <flag_id> TOK_FLAG_ID
%type <cap> caps
%type <cap> capability
%type <cap> set_caps
%type <user_entry> change_profile
%type <set_var> TOK_SET_VAR
%type <bool_var> TOK_BOOL_VAR
@@ -191,10 +208,12 @@ void add_local_entry(struct codomain *cod);
%type <boolean> opt_audit_flag
%type <boolean> opt_owner_flag
%type <boolean> opt_profile_flag
%type <boolean> opt_flags
%type <id> opt_namespace
%type <id> opt_id
%type <transition> opt_named_transition
%type <boolean> opt_unsafe
%type <boolean> opt_file
%%
@@ -238,7 +257,8 @@ profile_base: TOK_ID opt_id flags TOK_OPEN rules TOK_CLOSE
if (force_complain)
cod->flags.complain = 1;
post_process_nt_entries(cod);
post_process_file_entries(cod);
post_process_mnt_entries(cod);
PDEBUG("%s: flags='%s%s'\n",
$2,
cod->flags.complain ? "complain, " : "",
@@ -369,26 +389,23 @@ varassign: TOK_BOOL_VAR TOK_EQUALS TOK_VALUE
valuelist: TOK_VALUE
{
struct value_list *new = calloc(1, sizeof(struct value_list));
if (!new)
struct value_list *val = new_value_list($1);
if (!val)
yyerror(_("Memory allocation error."));
PDEBUG("Matched: value (%s)\n", $1);
new->value = $1;
new->next = NULL;
$$ = new;
$$ = val;
}
valuelist: valuelist TOK_VALUE
{
struct value_list *new = calloc(1, sizeof(struct value_list));
if (!new)
struct value_list *val = new_value_list($2);
if (!val)
yyerror(_("Memory allocation error."));
PDEBUG("Matched: value list\n");
new->value = $2;
new->next = $1;
$$ = new;
list_append($1, val);
$$ = $1;
}
flags: { /* nothing */
@@ -397,21 +414,24 @@ flags: { /* nothing */
$$ = fv;
};
flags: TOK_FLAGS TOK_EQUALS TOK_FLAG_OPENPAREN flagvals TOK_FLAG_CLOSEPAREN
opt_flags: { /* nothing */ $$ = 0; }
| TOK_CONDID TOK_EQUALS
{
$$ = $4;
};
flags: TOK_FLAG_OPENPAREN flagvals TOK_FLAG_CLOSEPAREN
{
$$ = $2;
if (strcmp($1, "flags") != 0)
yyerror("expected flags= got %s=", $1);
$$ = 1;
}
flagvals: flagvals TOK_FLAG_SEP flagval
flags: opt_flags TOK_OPENPAREN flagvals TOK_CLOSEPAREN
{
$1.complain = $1.complain || $3.complain;
$1.audit = $1.audit || $3.audit;
$1.path = $1.path | $3.path;
$$ = $3;
};
flagvals: flagvals flagval
{
$1.complain = $1.complain || $2.complain;
$1.audit = $1.audit || $2.audit;
$1.path = $1.path | $2.path;
if (($1.path & (PATH_CHROOT_REL | PATH_NS_REL)) ==
(PATH_CHROOT_REL | PATH_NS_REL))
yyerror(_("Profile flag chroot_relative conflicts with namespace_relative"));
@@ -434,7 +454,7 @@ flagvals: flagval
$$ = $1;
};
flagval: TOK_FLAG_ID
flagval: TOK_VALUE
{
struct flagval fv = { 0, 0, 0, 0 };
if (strcmp($1, "debug") == 0) {
@@ -641,6 +661,25 @@ rules: rules opt_audit_flag network_rule
$$ = $1;
}
rules: rules opt_audit_flag TOK_DENY mnt_rule
{
$4->deny = $4->allow;
if ($2)
$4->audit = $4->allow;
$4->next = $1->mnt_ents;
$1->mnt_ents = $4;
$$ = $1;
}
rules: rules opt_audit_flag mnt_rule
{
if ($2)
$3->audit = $3->allow;
$3->next = $1->mnt_ents;
$1->mnt_ents = $3;
$$ = $1;
}
rules: rules change_profile
{
PDEBUG("matched: rules change_profile\n");
@@ -667,12 +706,6 @@ rules: rules opt_audit_flag capability
$$ = $1;
};
rules: rules set_caps
{
$1->set_caps |= $2;
$$ = $1;
};
rules: rules hat
{
PDEBUG("Matched: hat rule\n");
@@ -892,24 +925,16 @@ opt_named_transition:
$$.name = $5;
};
rule: file_rule { $$ = $1; }
| link_rule { $$ = $1; }
| ptrace_rule {$$ = $1; }
opt_unsafe: { /* nothing */ $$ = 0; }
| TOK_UNSAFE { $$ = 1; };
| TOK_SAFE { $$ = 2; };
rule: opt_unsafe frule
{
if ($1) {
if (!($2->mode & AA_EXEC_BITS))
yyerror(_("unsafe rule missing exec permissions"));
if ($1 == 1) {
$2->mode |= (($2->mode & AA_EXEC_BITS) << 8) &
ALL_AA_EXEC_UNSAFE;
}
else if ($1 == 2)
$2->mode &= ~ALL_AA_EXEC_UNSAFE;
}
$$ = $2;
};
opt_file: { /* nothing */ $$ = 0; }
| TOK_FILE { $$ = 1; }
frule: id_or_var file_mode opt_named_transition TOK_END_OF_RULE
{
@@ -933,16 +958,45 @@ frule: file_mode opt_subset_flag id_or_var opt_named_transition TOK_END_OF_RULE
}
};
rule: opt_unsafe id_or_var file_mode id_or_var
file_rule: TOK_FILE TOK_END_OF_RULE
{
char *path = strdup("/{**,}");
int perms = ((AA_BASE_PERMS & ~AA_EXEC_TYPE) |
(AA_EXEC_INHERIT | AA_MAY_EXEC));
/* duplicate to other permission set */
perms |= perms << AA_OTHER_SHIFT;
if (!path)
yyerror(_("Memory allocation error."));
$$ = do_file_rule(NULL, path, perms, NULL, NULL);
}
| opt_file file_rule_tail { $$ = $2; }
file_rule_tail: opt_unsafe frule
{
if ($1) {
if (!($2->mode & AA_EXEC_BITS))
yyerror(_("unsafe rule missing exec permissions"));
if ($1 == 1) {
$2->mode |= (($2->mode & AA_EXEC_BITS) << 8) &
ALL_AA_EXEC_UNSAFE;
}
else if ($1 == 2)
$2->mode &= ~ALL_AA_EXEC_UNSAFE;
}
$$ = $2;
};
file_rule_tail: opt_unsafe id_or_var file_mode id_or_var
{
/* Oopsie, we appear to be missing an EOL marker. If we
* were *smart*, we could work around it. Since we're
* obviously not smart, we'll just punt with a more
* sensible error. */
yyerror(_("missing an end of line character? (entry: %s)"), $1);
yyerror(_("missing an end of line character? (entry: %s)"), $2);
};
rule: TOK_LINK opt_subset_flag TOK_ID TOK_ARROW TOK_ID TOK_END_OF_RULE
link_rule: TOK_LINK opt_subset_flag TOK_ID TOK_ARROW TOK_ID TOK_END_OF_RULE
{
struct cod_entry *entry;
PDEBUG("Matched: link tok_id (%s) -> (%s)\n", $3, $5);
@@ -952,7 +1006,7 @@ rule: TOK_LINK opt_subset_flag TOK_ID TOK_ARROW TOK_ID TOK_END_OF_RULE
$$ = entry;
};
rule: TOK_PTRACE TOK_ID TOK_END_OF_RULE
ptrace_rule: TOK_PTRACE TOK_ID TOK_END_OF_RULE
{
struct cod_entry *entry;
entry = new_entry(NULL, $2, AA_USER_PTRACE | AA_OTHER_PTRACE, NULL);
@@ -961,7 +1015,7 @@ rule: TOK_PTRACE TOK_ID TOK_END_OF_RULE
$$ = entry;
};
rule: TOK_PTRACE TOK_COLON TOK_ID TOK_COLON TOK_ID TOK_END_OF_RULE
ptrace_rule: TOK_PTRACE TOK_COLON TOK_ID TOK_COLON TOK_ID TOK_END_OF_RULE
{
struct cod_entry *entry;
entry = new_entry($3, $5, AA_USER_PTRACE | AA_OTHER_PTRACE, NULL);
@@ -1009,7 +1063,86 @@ network_rule: TOK_NETWORK TOK_ID TOK_ID TOK_END_OF_RULE
$$ = entry;
}
hat_start: TOK_SEP {}
cond: TOK_CONDID TOK_EQUALS TOK_VALUE
{
struct cond_entry *ent;
struct value_list *value = new_value_list($3);
if (!value)
yyerror(_("Memory allocation error."));
ent = new_cond_entry($1, 1, value);
if (!ent) {
free_value_list(value);
yyerror(_("Memory allocation error."));
}
$$ = ent;
}
cond: TOK_CONDID TOK_EQUALS TOK_OPENPAREN valuelist TOK_CLOSEPAREN
{
struct cond_entry *ent = new_cond_entry($1, 1, $4);
if (!ent)
yyerror(_("Memory allocation error."));
$$ = ent;
}
cond: TOK_CONDID TOK_IN TOK_OPENPAREN valuelist TOK_CLOSEPAREN
{
struct cond_entry *ent = new_cond_entry($1, 0, $4);
if (!ent)
yyerror(_("Memory allocation error."));
$$ = ent;
}
opt_conds: { /* nothing */ $$ = NULL; }
| opt_conds cond
{
$2->next = $1;
$$ = $2;
}
mnt_rule: TOK_MOUNT opt_conds opt_id TOK_END_OF_RULE
{
$$ = do_mnt_rule($2, $3, NULL, NULL, AA_MAY_MOUNT);
}
mnt_rule: TOK_MOUNT opt_conds opt_id TOK_ARROW opt_conds TOK_ID TOK_END_OF_RULE
{
$$ = do_mnt_rule($2, $3, $5, $6, AA_MAY_MOUNT);
}
mnt_rule: TOK_REMOUNT opt_conds opt_id TOK_END_OF_RULE
{
$$ = do_mnt_rule($2, NULL, NULL, $3, AA_DUMMY_REMOUNT);
}
mnt_rule: TOK_UMOUNT opt_conds opt_id TOK_END_OF_RULE
{
$$ = do_mnt_rule($2, NULL, NULL, $3, AA_MAY_UMOUNT);
}
mnt_rule: TOK_PIVOTROOT opt_conds opt_id opt_named_transition TOK_END_OF_RULE
{
char *name = NULL;
if ($4.present && $4.namespace) {
name = malloc(strlen($4.namespace) +
strlen($4.name) + 3);
if (!name) {
PERROR("Memory allocation error\n");
exit(1);
}
sprintf(name, ":%s:%s", $4.namespace, $4.name);
free($4.namespace);
free($4.name);
} else if ($4.present)
name = $4.name;
$$ = do_pivot_rule($2, $3, name);
}
hat_start: TOK_CARET {}
| TOK_HAT {}
file_mode: TOK_MODE
@@ -1043,17 +1176,17 @@ change_profile: TOK_CHANGE_PROFILE TOK_ARROW TOK_COLON TOK_ID TOK_COLON TOK_ID T
};
set_caps: TOK_SET TOK_CAPABILITY caps TOK_END_OF_RULE
{
$$ = $3;
};
capability: TOK_CAPABILITY caps TOK_END_OF_RULE
{
$$ = $2;
if ($2 == 0) {
/* bare capability keyword - set all caps */
$$ = 0xffffffffffffffff;
} else
$$ = $2;
};
caps: caps TOK_ID
caps: { /* nothing */ $$ = 0; }
| caps TOK_ID
{
int cap = name_to_capability($2);
if (cap == -1)
@@ -1062,26 +1195,14 @@ caps: caps TOK_ID
$$ = $1 | CAP_TO_MASK(cap);
}
caps: TOK_ID
{
int cap = name_to_capability($1);
if (cap == -1)
yyerror(_("Invalid capability %s."), $1);
free($1);
$$ = CAP_TO_MASK(cap);
};
%%
#define MAXBUFSIZE 4096
void yyerror(char *msg, ...)
void vprintyyerror(const char *msg, va_list argptr)
{
va_list arg;
char buf[MAXBUFSIZE];
va_start(arg, msg);
vsnprintf(buf, sizeof(buf), msg, arg);
va_end(arg);
vsnprintf(buf, sizeof(buf), msg, argptr);
if (profilename) {
PERROR(_("AppArmor parser error for %s%s%s at line %d: %s\n"),
@@ -1095,21 +1216,26 @@ void yyerror(char *msg, ...)
current_filename ? current_filename : "",
current_lineno, buf);
}
exit(1);
}
void free_value_list(struct value_list *list)
void printyyerror(const char *msg, ...)
{
struct value_list *next;
va_list arg;
while (list) {
next = list->next;
if (list->value)
free(list->value);
free(list);
list = next;
}
va_start(arg, msg);
vprintyyerror(msg, arg);
va_end(arg);
}
void yyerror(const char *msg, ...)
{
va_list arg;
va_start(arg, msg);
vprintyyerror(msg, arg);
va_end(arg);
exit(1);
}
struct cod_entry *do_file_rule(char *namespace, char *id, int mode,
@@ -1149,3 +1275,76 @@ void add_local_entry(struct codomain *cod)
add_entry_to_policy(cod, entry);
}
}
static char *mnt_cond_msg[] = {"",
" not allowed as source conditional",
" not allowed as target conditional",
"",
NULL};
int verify_mnt_conds(struct cond_entry *conds, int src)
{
struct cond_entry *entry;
int error = 0;
if (!conds)
return 0;
list_for_each(conds, entry) {
int res = is_valid_mnt_cond(entry->name, src);
if (res <= 0) {
printyyerror(_("invalid mount conditional %s%s"),
entry->name,
res == -1 ? "" : mnt_cond_msg[src]);
error++;
}
}
return error;
}
struct mnt_entry *do_mnt_rule(struct cond_entry *src_conds, char *src,
struct cond_entry *dst_conds, char *dst,
int mode)
{
struct mnt_entry *ent;
if (verify_mnt_conds(src_conds, MNT_SRC_OPT) != 0)
yyerror(_("bad mount rule"));
/* FIXME: atm conditions are not supported on dst
if (verify_conds(dst_conds, DST_OPT) != 0)
yyerror(_("bad mount rule"));
*/
if (dst_conds)
yyerror(_("mount point conditions not currently supported"));
ent = new_mnt_entry(src_conds, src, dst_conds, dst, mode);
if (!ent) {
yyerror(_("Memory allocation error."));
}
return ent;
}
struct mnt_entry *do_pivot_rule(struct cond_entry *old, char *root,
char *transition)
{
struct mnt_entry *ent = NULL;
char *device = NULL;
if (old) {
if (strcmp(old->name, "oldroot") != 0)
yyerror(_("invalid pivotroot conditional '%s'"), old->name);
if (old->vals) {
device = old->vals->value;
old->vals->value = NULL;
}
free_cond_entry(old);
}
ent = new_mnt_entry(NULL, device, NULL, root,
AA_MAY_PIVOTROOT);
ent->trans = transition;
return ent;
}

38
parser/policydb.h Normal file
View File

@@ -0,0 +1,38 @@
/*
* Copyright 2012 Canonical Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, version 2 of the
* License.
*
* This program 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 General Public License for more details.
*
*/
#ifndef __AA_POLICYDB_H
#define __AA_POLICYDB_H
/*
* Class of mediation types in the AppArmor policy db
*/
#define AA_CLASS_COND 0
#define AA_CLASS_UNKNOWN 1
#define AA_CLASS_FILE 2
#define AA_CLASS_CAP 3
#define AA_CLASS_NET 4
#define AA_CLASS_RLIMITS 5
#define AA_CLASS_DOMAIN 6
#define AA_CLASS_MOUNT 7
#define AA_CLASS_NS_DOMAIN 8
#define AA_CLASS_PTRACE 9
#define AA_CLASS_ENV 16
#define AA_CLASS_DBUS 32
#define AA_CLASS_X 33
#endif /* __AA_POLICYDB_H */

View File

@@ -1,7 +1,7 @@
#!/bin/sh
# ----------------------------------------------------------------------
# Copyright (c) 1999-2008 NOVELL (All rights reserved)
# Copyright (c) 2009-2011 Canonical Ltd. (All rights reserved)
# Copyright (c) 2009-2012 Canonical Ltd. (All rights reserved)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 2 of the GNU General Public

View File

@@ -3,7 +3,7 @@
# 2008, 2009
# NOVELL (All rights reserved)
#
# Copyright (c) 2010
# Copyright (c) 2010 - 2012
# Canonical Ltd. (All rights reserved)
#
# This program is free software; you can redistribute it and/or

View File

@@ -12,8 +12,8 @@ endif
all: tests
.PHONY: tests error_output gen_xtrans parser_sanity caching
tests: error_output gen_xtrans parser_sanity caching
.PHONY: tests error_output gen_xtrans parser_sanity caching minimize
tests: error_output gen_xtrans parser_sanity caching minimize
GEN_TRANS_DIRS=simple_tests/generated_x/ simple_tests/generated_perms_leading/ simple_tests/generated_perms_safe/
@@ -41,6 +41,9 @@ parser_sanity: $(PARSER)
caching: $(PARSER)
LANG=C ./caching.sh
minimize: $(PARSER)
LANG=C ./minimize.sh
$(PARSER):
make -C $(PARSER_DIR) $(PARSER_BIN)

View File

@@ -49,11 +49,34 @@ echo -n "Profiles are cached when requested: "
[ ! -f $basedir/cache/$profile ] && echo "FAIL ($basedir/cache/$profile does not exist)" && exit 1
echo "ok"
read_features_dir()
{
directory="$1"
if [ ! -d "$directory" ] ; then
return
fi
for f in `ls -AU "$directory"` ; do
if [ -f "$directory/$f" ] ; then
read -r -d "" KF < "$directory/$f" || true
echo -e "$f {$KF\n}"
elif [ -d "$directory/$f" ] ; then
echo -n "$f {"
KF=`read_features_dir "$directory/$f" "$KF"` || true
echo "$KF"
echo -e "}"
fi
done
}
echo -n "Kernel features are written to cache: "
[ ! -f $basedir/cache/.features ] && echo "FAIL ($basedir/cache/.features missing)" && exit 1
read CF < $basedir/cache/.features || true
read KF < /sys/kernel/security/apparmor/features || true
[ "$CF" != "$KF" ] && echo "FAIL (feature text mismatch: cache '$CF' vs kernel '$KF')" && exit 1
read -r -d "" CF < $basedir/cache/.features || true
if [ -d /sys/kernel/security/apparmor/features ] ; then
KF=`read_features_dir /sys/kernel/security/apparmor/features`
else
read -r -d "" KF < /sys/kernel/security/apparmor/features || true
fi
[ "$CF" != "$KF" ] && echo -e "FAIL (feature text mismatch:\n cache '$CF'\nvs\n kernel '$KF')" && exit 1
echo "ok"
echo -n "Cache is loaded when it exists and features match: "

199
parser/tst/minimize.sh Executable file
View File

@@ -0,0 +1,199 @@
#!/bin/bash
# Format of -D dfa-states
# dfa-states output is split into 2 parts:
# the accept state infomation
# {state} (allow deny audit XXX) ignore XXX for now
# followed by the transition table information
# {Y} -> {Z}: 0xXX Char #0xXX is the hex dump of Char
# where the start state is always shown as
# {1} <==
#
# Eg. echo "/t { /a r, /b w, /c a, /d l, /e k, /f m, deny /** w, }" | ./apparmor_parser -QT -O minimize -D dfa-states --quiet
#
# {1} <== (allow/deny/audit/quiet)
# {3} (0x 0/2800a/0/2800a)
# {4} (0x 10004/2800a/0/2800a)
# {7} (0x 40010/2800a/0/2800a)
# {8} (0x 80020/2800a/0/2800a)
# {9} (0x 100040/2800a/0/2800a)
# {c} (0x 40030/0/0/0)
#
# {1} -> {2}: 0x2f /
# {2} -> {4}: 0x61 a
# {2} -> {3}: 0x62 b
# {2} -> {3}: 0x63 c
# {2} -> {7}: 0x64 d
# {2} -> {8}: 0x65 e
# {2} -> {9}: 0x66 f
# {2} -> {3}: [^\0x0/]
# {3} -> {3}: [^\0x0]
# {4} -> {3}: [^\0x0]
# {7} -> {a}: 0x0
# {7} -> {3}: []
# {8} -> {3}: [^\0x0]
# {9} -> {3}: [^\0x0]
# {a} -> {b}: 0x2f /
# {b} -> {c}: [^/]
# {c} -> {c}: []
#
# These tests currently only look at the accept state permissions
#
# To view any of these DFAs as graphs replace --D dfa-states with -D dfa-graph
# strip of the test stuff around the parser command and use the the dot
# command to convert
# Eg.
# echo "/t { /a r, /b w, /c a, /d l, /e k, /f m, deny /** w, }" | ./apparmor_parser -QT -O minimize -D dfa-graph --quiet 2>min.graph
# dot -T png -o min.png min.graph
# and then view min.png in your favorite viewer
#
#------------------------------------------------------------------------
# test to see if minimization is eliminating non-redundant accept state
# Test xtrans and regular perms separately. The are the same basic failure
# but can xtrans has an extra code path.
#
# The permission test is setup to have all the none xtrans permissions show
# up once on unique paths and have a global write permission that adds to
# it.
# This should result in a un-minimized dump looking like. Notice it has 6
# states with accept information, 1 for each rule except for the 'w'
# permission which is combined into a single state for /b and /**
#
# {1} <== (allow/deny/audit/quiet)
# {3} (0x 2800a/0/0/0)
# {4} (0x 3800e/0/0/0)
# {5} (0x 6801a/0/0/0)
# {6} (0x a802a/0/0/0)
# {7} (0x 12804a/0/0/0)
# {a} (0x 40030/0/0/0)
# A dump of minimization that is not respecting the uniqueness of the
# permissions on the states looks like below. Notice it has only 3 states
# with accept information
# {1} <== (allow/deny/audit/quiet)
# {3} (0x 1b806e/0/0/0)
# {5} (0x 6801a/0/0/0)
# {a} (0x 40030/0/0/0)
echo -n "Minimize profiles basic perms "
if [ `echo "/t { /a r, /b w, /c a, /d l, /e k, /f m, /** w, }" | ../apparmor_parser -QT -O minimize -D dfa-states 2>&1 | grep -v '<==' | grep '^{.*} (.*)$' | wc -l` -ne 6 ] ; then
echo "failed"
exit 1;
fi
echo "ok"
# same test as above except with audit perms added
# {1} <== (allow/deny/audit/quiet)
# {3} (0x 2800a/0/2800a/0)
# {4} (0x 3800e/0/2800a/0)
# {7} (0x 6801a/0/2800a/0)
# {8} (0x a802a/0/2800a/0)
# {9} (0x 12804a/0/2800a/0)
# {c} (0x 40030/0/0/0)
echo -n "Minimize profiles audit perms "
if [ `echo "/t { /a r, /b w, /c a, /d l, /e k, /f m, audit /** w, }" | ../apparmor_parser -QT -O minimize -D dfa-states 2>&1 | grep -v '<==' | grep '^{.*} (.*)$' | wc -l` -ne 6 ] ; then
echo "failed"
exit 1;
fi
echo "ok"
# same test as above except with deny 'w' perm added to /**, this does not
# elimnates the states with 'w' and 'a' because the quiet information is
# being carried
#
# {1} <== (allow/deny/audit/quiet)
# {3} (0x 0/2800a/0/2800a)
# {4} (0x 10004/2800a/0/2800a)
# {7} (0x 40010/2800a/0/2800a)
# {8} (0x 80020/2800a/0/2800a)
# {9} (0x 100040/2800a/0/2800a)
# {c} (0x 40030/0/0/0)
echo -n "Minimize profiles deny perms "
if [ `echo "/t { /a r, /b w, /c a, /d l, /e k, /f m, deny /** w, }" | ../apparmor_parser -QT -O minimize -D dfa-states 2>&1 | grep -v '<==' | grep '^{.*} (.*)$' | wc -l` -ne 6 ] ; then
echo "failed"
exit 1;
fi
echo "ok"
# same test as above except with audit deny 'w' perm added to /**, with the
# parameter this elimnates the states with 'w' and 'a' because
# the quiet information is NOT being carried
#
# {1} <== (allow/deny/audit/quiet)
# {4} (0x 10004/0/0/0)
# {7} (0x 40010/0/0/0)
# {8} (0x 80020/0/0/0)
# {9} (0x 100040/0/0/0)
# {c} (0x 40030/0/0/0)
echo -n "Minimize profiles audit deny perms "
if [ `echo "/t { /a r, /b w, /c a, /d l, /e k, /f m, audit deny /** w, }" | ../apparmor_parser -QT -O minimize -D dfa-states 2>&1 | grep -v '<==' | grep '^{.*} (.*)$' | wc -l` -ne 5 ] ; then
echo "failed"
exit 1;
fi
echo "ok"
# The x transition test profile is setup so that there are 3 conflicting x
# permissions, two are on paths that won't collide during dfa creation. The
# 3rd is a generic permission that should be overriden during dfa creation.
#
# This should result in a dfa that specifies transitions on 'a' and 'b' to
# unique states that store the alternate accept information. However
# minimization can remove the unique accept permission states if x permissions
# are treated as a single accept state.
#
# The minimized dump should retain the 'a' and 'b' transitions accept states.
# notice the below dump has 3 states with accept information {3}, {4}, {5}
#
# {1} <== (allow/deny/audit/quiet)
# {3} (0x 2914a45/0/0/0)
# {4} (0x 4115045/0/0/0)
# {5} (0x 2514945/0/0/0)
#
# A dump of minimization that is not respecting the uniqueness of the
# permissions on the states transitioned to by 'a' and 'b' looks like
# below. Notice that only state {3} has accept information
# {1} <== (allow/deny/audit/quiet)
# {3} (0x 2514945/0/0/0)
#
echo -n "Minimize profiles xtrans "
if [ `echo "/t { /b px, /* Pixr, /a Cx -> foo, }" | ../apparmor_parser -QT -O minimize -D dfa-states 2>&1 | grep -v '<==' | grep '^{.*} (.*)$' | wc -l` -ne 3 ] ; then
echo "failed"
exit 1;
fi
echo "ok"
# same test as above + audit
echo -n "Minimize profiles audit xtrans "
if [ `echo "/t { /b px, audit /* Pixr, /a Cx -> foo, }" | ../apparmor_parser -QT -O minimize -D dfa-states 2>&1 | grep -v '<==' | grep '^{.*} (.*)$' | wc -l` -ne 3 ] ; then
echo "failed"
exit 1;
fi
echo "ok"
# now try denying x and make sure perms are cleared
# notice that only deny and quiet information is being carried
# {1} <== (allow/deny/audit/quiet)
# {3} (0x 0/fe17f85/0/14005)
echo -n "Minimize profiles deny xtrans "
if [ `echo "/t { /b px, deny /* xr, /a Cx -> foo, }" | ../apparmor_parser -QT -O minimize -D dfa-states 2>&1 | grep -v '<==' | grep '^{.*} (.*)$' | wc -l` -ne 1 ] ; then
echo "failed"
exit 1;
fi
echo "ok"
# now try audit + denying x and make sure perms are cleared
# notice that the deny info is being carried, by an artifical trap state
# {1} <== (allow/deny/audit/quiet)
# {3} (0x 0/fe17f85/0/0)
echo -n "Minimize profiles audit deny xtrans "
if [ `echo "/t { /b px, audit deny /* xr, /a Cx -> foo, }" | ../apparmor_parser -QT -O minimize -D dfa-states 2>&1 | grep -v '<==' | grep '^{.*} (.*)$' | wc -l` -ne 0 ] ; then
echo "failed"
exit 1;
fi
echo "ok"

View File

@@ -0,0 +1,9 @@
#
#=DESCRIPTION fail CAP_XXX syntax.
#=EXRESULT FAIL
# vim:syntax=subdomain
# Last Modified: Sun Apr 17 19:44:44 2005
#
/does/not/exist {
capability chown CAP_CHOWN,
}

View File

@@ -0,0 +1,9 @@
#
#=DESCRIPTION fail unknown keyword
#=EXRESULT FAIL
# vim:syntax=subdomain
# Last Modified: Sun Apr 17 19:44:44 2005
#
/does/not/exist {
capability chown foobar,
}

View File

@@ -0,0 +1,9 @@
#
#=DESCRIPTION validate some uses of capabilties.
#=EXRESULT PASS
# vim:syntax=subdomain
# Last Modified: Sun Apr 17 19:44:44 2005
#
/does/not/exist {
capability,
}

View File

@@ -1,6 +1,6 @@
#
#=DESCRIPTION validate some uses of capabilties.
#=EXRESULT PASS
#=EXRESULT FAIL
# vim:syntax=subdomain
# Last Modified: Sun Apr 17 19:44:44 2005
#

View File

@@ -0,0 +1,7 @@
#
#=Description basic file rule
#=EXRESULT FAIL
#
/usr/bin/foo {
file rw,
}

View File

@@ -0,0 +1,8 @@
#
#=DESCRIPTION w and a conflict
#=EXRESULT FAIL
#
/usr/bin/foo {
file /a wa,
}

View File

@@ -0,0 +1,7 @@
k#
#=DESCRIPTION comma in pathname
#=EXRESULT FAIL
#
/usr/bin/foo {
file /foobar, r,
}

View File

@@ -0,0 +1,6 @@
#=DESCRIPTION Simple test case for embedded spaces
#=EXRESULT FAIL
/bin/foo {
file /abc\ def r,
}

View File

@@ -0,0 +1,8 @@
#
#=DESCRIPTION k to be lower case
#=EXRESULT FAIL
#
/usr/bin/foo {
file /bin/ls K,
}

View File

@@ -0,0 +1,41 @@
# vim:syntax=subdomain
# Last Modified: Wed Aug 31 11:14:09 2005
#=DESCRIPTION dos line endings
#=EXRESULT PASS
/usr/lib/RealPlayer10/realplay {
#include <includes/base>
#include <includes/fonts>
file /bin/bash ix,
file /bin/sed ixr,
file /bin/true ixr,
file /etc/opt/gnome/pango/pango.modules r,
file /opt/gnome/lib/gtk-2.0/2.4.0/loaders/* r,
file /opt/gnome/lib/lib*so* r,
file /opt/gnome/lib/pango/1.4.0/modules/* r,
file /opt/gnome/share/icons r,
file /opt/gnome/share/icons/** r,
file /opt/gnome/bin/nautilus rux,
file /root r,
file /root/.Xauthority r,
file /root/.fonts.cache-1 r,
file /root/.realplayerrc rw,
file /home/*/ r,
file /home/*/.Xauthority r,
file /home/*/.fonts.cache-1 r,
file /home/*/.realplayerrc rw,
file /usr/X11R6/lib/Acrobat7/Resource/Font/* r,
file /usr/X11R6/lib/Acrobat7/Resource/Font/PFM/* r,
file /usr/lib/RealPlayer10/** r,
file /usr/lib/RealPlayer10/realplay.bin ixr,
file /usr/lib/jvm/java-1.4.2-sun-1.4.2.06/jre/lib/fonts/** r,
file /usr/lib/ooo-2.0/share/fonts/** r,
file /opt/MozillaFirefox/bin/firefox.sh pxr,
file /opt/MozillaFirefox/lib/firefox-bin pxr,
file /opt/MozillaFirefox/lib/init.d r,
file /usr/bin/opera pxr,
file /usr/share/icons r,
file /usr/share/icons/** r,
file /opt/gnome/share/pixmaps r,
file /opt/gnome/share/pixmaps/** r,
}

View File

@@ -0,0 +1,24 @@
#
#=DESCRIPTION perms before pathname
#=EXRESULT PASS
#
/usr/bin/foo {
file r /foo1,
file w /foo1,
file a /foo1,
file k /foo1,
file m /foo1,
file l /foo1,
file px /foo1,
file Px /foo2,
file ux /foo3,
file Ux /foo4,
file ix /foo5,
file unsafe px /foo6,
file unsafe Px /foo7,
file unsafe ux /foo8,
file unsafe Ux /foo9,
file unsafe ix /foo10,
}

View File

@@ -0,0 +1,7 @@
#
#=Description basic file rule
#=EXRESULT PASS
#
/usr/bin/foo {
file /usr/bin/foo r,
}

View File

@@ -0,0 +1,7 @@
#
#=Description basic file rule
#=EXRESULT PASS
#
/usr/bin/foo {
file,
}

View File

@@ -0,0 +1,9 @@
#
#=DESCRIPTION A simple successful profile
#=EXRESULT PASS
#
/usr/bin/foo {
file /usr/bin/foo r,
file /usr/bin/blah rix,
}

View File

@@ -0,0 +1,13 @@
#
#=DESCRIPTION test append
#=EXRESULT PASS
#
/usr/bin/foo {
file /bin/cat a,
file /bin/true ra,
file /bin/false ma,
file /lib/libc.so la,
file /bin/less ixa,
file /bin/more pxa,
file /a uxa,
}

View File

@@ -0,0 +1,7 @@
#
#=DESCRIPTION carat in pathname
#=EXRESULT PASS
#
/usr/bin/foo {
file /foo^bar r,
}

View File

@@ -0,0 +1,7 @@
#
#=DESCRIPTION trailing carat in pathname
#=EXRESULT PASS
#
/usr/bin/foo {
file /foo/bar^ r,
}

View File

@@ -0,0 +1,7 @@
#
#=DESCRIPTION comma in pathname
#=EXRESULT PASS
#
/usr/bin/foo {
file /foo,bar r,
}

View File

@@ -0,0 +1,7 @@
#
#=DESCRIPTION comma at end of pathname
#=EXRESULT PASS
#
/usr/bin/foo {
file "/foobar," r,
}

View File

@@ -0,0 +1,6 @@
#=DESCRIPTION Simple test case for embedded spaces
#=EXRESULT PASS
/bin/foo {
file "/abc\ def" r,
}

View File

@@ -0,0 +1,6 @@
#=DESCRIPTION Simple test case for embedded spaces
#=EXRESULT PASS
/bin/foo {
file "/abc def" r,
}

View File

@@ -0,0 +1,6 @@
#=DESCRIPTION Simple test case for embedded spaces
#=EXRESULT PASS
"/bin/fo o" {
file "/abc def" r,
}

View File

@@ -0,0 +1,7 @@
#
#=DESCRIPTION carat in pathname
#=EXRESULT PASS
#
/usr/bin/foo {
file /foo[^me]bar r,
}

View File

@@ -0,0 +1,17 @@
#
#=DESCRIPTION k and other perms do not conflict
#=EXRESULT PASS
#
/usr/bin/foo {
file /bin/a k,
file /bin/b rk,
file /bin/c wk,
file /bin/d ak,
file /bin/e lk,
file /bin/e mk,
file /bin/f pxk,
file /bin/g Pxk,
file /bin/h ixk,
file /bin/i uxk,
file /bin/j Uxk,
}

View File

@@ -0,0 +1,12 @@
#
#=DESCRIPTION m and [uUpPi]x do not conflict
#=EXRESULT PASS
#
/usr/bin/foo {
file /bin/cat mix,
file /bin/true mpx,
file /bin/false mux,
file /lib/libc.so rwlm,
file /bin/less mUx,
file /bin/more mPx,
}

View File

@@ -0,0 +1,14 @@
#
#=DESCRIPTION m and [upi]x do not conflict, seperate rules
#=EXRESULT PASS
#
/usr/bin/foo {
file /bin/cat rm,
file /bin/cat ix,
file /bin/true px,
file /bin/true m,
file /bin/false m,
file /bin/false ux,
file /lib/libc.so rwl,
file /lib/libc.so m,
}

View File

@@ -0,0 +1,8 @@
#
#=DESCRIPTION owner can not follow path name
#=EXRESULT FAIL
#
/usr/bin/foo {
file /foo owner rw,
}

View File

@@ -0,0 +1,8 @@
#
#=DESCRIPTION owner cannot follow permission
#=EXRESULT FAIL
#
/usr/bin/foo {
file /foo rw owner,
}

View File

@@ -0,0 +1,8 @@
#
#=DESCRIPTION owner rules must have comma termination
#=EXRESULT FAIL
#
/usr/bin/foo {
owner file /foo rw
file /bar rw,
}

View File

@@ -0,0 +1,8 @@
#
#=DESCRIPTION owner not allowed after forward perm
#=EXRESULT FAIL
#
/usr/bin/foo {
file rw owner /foo,
}

View File

@@ -0,0 +1,8 @@
#
#=DESCRIPTION owner not allowed after pathname in forward rule
#=EXRESULT FAIL
#
/usr/bin/foo {
file rw /foo owner,
}

View File

@@ -0,0 +1,9 @@
#
#=DESCRIPTION owner block needs } termination
#=EXRESULT FAIL
#
/usr/bin/foo {
owner {
file rw foo,
}

View File

@@ -0,0 +1,25 @@
#
#=DESCRIPTION test owner flag for file rules
#=EXRESULT PASS
#
/usr/bin/foo {
owner file /foo rw,
owner file /foo/** rw,
owner file rw /bar,
owner file rw /bar/**,
owner {
file /one rw,
file /one/** rw,
file rw /two,
file rw /two/**,
}
owner {
}
}

View File

@@ -0,0 +1,7 @@
#
#=Description basic mount rule
#=EXRESULT PASS
#
/usr/bin/foo {
mount options in (rw) -> /foo,
}

View File

@@ -0,0 +1,7 @@
#
#=Description basic mount rule
#=EXRESULT PASS
#
/usr/bin/foo {
mount options in (rw, ro) -> /foo,
}

View File

@@ -0,0 +1,7 @@
#
#=Description basic mount rule
#=EXRESULT PASS
#
/usr/bin/foo {
mount options in (rw ro) -> /foo,
}

View File

@@ -0,0 +1,7 @@
#
#=Description basic mount rule
#=EXRESULT PASS
#
/usr/bin/foo {
mount options in (rw ro) fstype=procfs -> /foo,
}

Some files were not shown because too many files have changed in this diff Show More