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

Compare commits

...

374 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
John Johansen
3c11c66ff2 AppArmor 2.7 tag 2011-12-15 04:39:29 -08:00
John Johansen
c52cf4d537 AppArmor 2.7 2011-12-15 04:20:50 -08:00
Steve Beattie
1e8dc4f375 This fixes the libapparmor log parsing library to properly parse log
entries where the comm entry has been hex-encoded. This occurs when the
binary being confined contains a space or other problematic character in
its filename. A test case is included.
2011-11-30 11:07:48 -08:00
Steve Beattie
7988124de5 Add a testcase for libapparmor for encoded profiles to ensure that
https://bugs.launchpad.net/apparmor/+bug/897957/ is in the utils and not
the parsing library itself.
2011-11-30 09:53:04 -08:00
Steve Beattie
f0aa65c832 abstractions/python: for some reason, the python runtimes need access
to pyconfig.h

Bug: https://launchpad.net/bugs/840734
2011-11-30 08:56:45 -08:00
Christian Boltz
1c96c09ec9 /usr/sbin/traceroute6 is a symlink to /usr/sbin/traceroute.
This patch allows "network inet6 raw" in addition to the ipv4 rule.

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

Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-11-30 13:15:21 +01:00
Christian Boltz
9685aae967 rpmlint complains about an outdated FSF address in parser/COPYING.GPL.
This commit upgrades the COPYING.GPL file to the latest version from
http://www.gnu.org/licenses/gpl-2.0.txt - which means some small text 
and whitespace changes and of course the correct address.

Acked-by: John Johansen <john.johansen@canonical.com>
2011-11-27 13:52:06 +01:00
John Johansen
c7a6608777 bzr tag apparmor_2.7.0-rc2 2011-11-10 09:59:36 -08:00
John Johansen
bca67d3d27 apparmor-2.7~rc2 2011-11-10 09:51:18 -08:00
John Johansen
9c39909a9c Author: Michael (kensington)
When executing apparmor_status from rc functions and utils are not installed, this message is received:

AppArmor is enabled,
Install the apparmor-utils package to receive more detailed
status information here (or examine directly).

Signed-off-by: John Johansen <john.johansen@canonical.com>
2011-11-10 09:43:10 -08:00
John Johansen
f278505db2 Author: Michael (kensington)
parser/Makefile has a number of issues.

* Some warnings are produced: "make[1]: warning: jobserver unavailable: using -j1. Add `+' to parent make rule."
* CXX is not always respected
* LDFLAGS are not always respected

modified to apply and retain $(Q) by John Johansen

Signed-off-by: John Johansen <john.johansen@canoncial.com>
2011-11-10 09:36:52 -08:00
John Johansen
e91cc118df Author: Michael (kensington)
When running installing apparmor-utils from Makefile, some warnings are
generated: make[1]: warning: jobserver unavailable: using -j1. Add
 `+' to parent make rule.

Use $(MAKE) instead of make

Signed-off-by: John Johansen <john.johansen@canonical.com>
2011-11-10 09:27:29 -08:00
John Johansen
e1763ba13c Auther: Michael (kensington)
libapparmor python bindings fail to build with python-3.

Signed-off-by: John Johansen <john.johansen@canonical.com>
2011-11-10 09:22:09 -08:00
Seth Arnold
cad0c3c8a9 Include mknod in file operations aa-logprof / aa-genprof processing 2011-11-08 19:06:49 -08:00
Christian Boltz
49103b30f2 Make abstractions/winbind work on 64bit systems (valid.dat etc. are in
/usr/lib64/samba/ in openSUSE 64bit installations)

Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-11-01 18:35:29 +01:00
Christian Boltz
df47a59ecb Allow loading the libraries used for Samba "vfs objects = ..."
References: https://bugzilla.novell.com/show_bug.cgi?id=725967

Signed-off-by: Christian Boltz <apparmor@cboltz.de>

Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-11-01 18:28:49 +01:00
Christian Boltz
091ec763f9 split off abstractions/ldapclient from abstractions/nameservice
Original openSUSE changelog entry:

Thu Jan  6 16:23:19 UTC 2011 - rhafer@suse.de

- Splitted ldap related things from nameservice into separate
  profile and added some missing paths (bnc#662761)
2011-11-01 17:08:37 +01:00
Christian Boltz
33b7c5316f create the directory /etc/apparmor.d/disable which is required by aa-disable
Acked-by: John Johansen <john.johansen@canonical.com>
2011-10-20 00:23:19 +02:00
Christian Boltz
986093cf8d More helpful error message for "aa-notify -p" if a user is not member of
the group configured in notify.conf / use_group=...

Acked-by: John Johansen <john.johansen@canonical.com>
2011-10-20 00:20:02 +02:00
John Johansen
33c62acc5c Update version to 2.7~rc1 2011-10-12 16:45:45 -07:00
Christian Boltz
8b671f013a add the option --display to set the $DISPLAY environment variable in aa-notify.
If $DISPLAY is not set and --display is not used, aa-notify prints a 
warning that notifications won't be shown (exact warning text depends if 
using sudo or not).

Acked-by: John Johansen <john.johansen@canonical.com>
2011-10-12 13:08:25 +02:00
Christian Boltz
5789ab84a6 allow read access for /proc/*/mounts in the dovecot profile
Reported by Tim Edwards in the opensuse-factory	mailinglist.


Acked-by: John Johansen <john.johansen@canonical.com>
2011-10-12 13:05:00 +02:00
Christian Boltz
6d6e04dc12 fix a syntax error in remove_profiles() in rc.apparmor.functions by
adding a forgotten pipe.

Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-10-12 00:45:11 +02:00
John Johansen
7a7c99f3a1 Commit the example parser.conf file that was supposed to be part of
commit r1834

Signed-off-by: John Johansen <john.johansen@canonical.com>
2011-10-09 20:15:03 -07:00
John Johansen
9896f5edbd Add an example parser.conf file
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
2011-10-07 14:43:54 -07:00
John Johansen
12a98135bf Provide a more user friendly error message when cache is
requested and fails to be created.  Also don't make the
warning output conditional on the showcache flag as we
should be showing warning/errors by default.

Signed-off-by: John Johansen <john.johansen@canonical.com>
2011-10-07 14:42:55 -07:00
Jamie Strandboge
a30dfb6b19 utils/aa-notify:
- set HOME (and DISPLAY) only once on startup to avoid NSS lookups

Acked-by: Jamie Strandboge <jamie@canonical.com>
Acked-by: John Johansen <john.johansen@canonical.com>
Acked-by: Christian Boltz <opensuse@cboltz.de>
2011-09-30 18:00:52 -05:00
Christian Boltz
a4d4eddd92 while aa-eventd is deprecated and no longer installed, parser/Makefile
happily continued to install the initscript for aa-eventd.

This was reported by Jiri Srain as part of 
https://bugzilla.novell.com/show_bug.cgi?id=720617

This commit removes the lines that install the aaeventd initscript
from parser/Makefile.

Acked-by: John Johansen <john.johansen@canonical.com>
2011-10-01 00:23:39 +02:00
Christian Boltz
b393893c7a sudo resets the environment variable $HOME and removes $DISPLAY on
openSUSE, which results in non-working desktop notifications in aa-notify
because $notify_exe is unable to connect to DBUS to display the message.

This patch sets the correct value for $HOME.

The code for setting $DISPLAY is still under discussion, therefore only
a TODO note is included in this commit for $DISPLAY.

Acked-By: John Johansen <john.johansen@canonical.com>
2011-09-24 13:19:10 +02:00
John Johansen
572fb7e943 AppArmor_2.7-beta2
Signed-off-by: John Johansen@canonical.com
2011-09-15 13:55:48 -07:00
John Johansen
7173d7a6a5 Remove extra space insert at from of ${TAG_VERSION} when doing the ~ to -
substitution.

Signed-off-by: John Johansen <john.johansen@canonical.com>
2011-09-15 13:28:01 -07:00
John Johansen
7b577d72b5 Remove ~ based tags again
Signed-off-by: John Johansen <john.johansen@canonical.com>
2011-09-15 12:50:10 -07:00
Christian Boltz
5a140c2e5c while debugging the issue in rc.apparmor.suse (see patch for
rc.apparmor.suse), I noticed that "rcapparmor restart" is
totally silent.

The attached patch prints a message in __apparmor_restart().

It also replaces the hardcoded "return 0" with $?. I'm quite sure this
won't catch all errors, but it's still better than the hardcoded success
message.

Acked-by: John Johansen <john.johansen@canonical.com>
2011-09-15 21:23:25 +02:00
Christian Boltz
e922cdb113 sbin.syslog-ng profile:
- fix permissions for additional-log-sockets.conf (the comma in {var/,} 
  was at the wrong place, which broke the /var/run/ case)
- add read permissions for /sys/devices/system/cpu/online
  (that was even new for Peter, but I trust him not to post faked 
  audit.log lines ;-)

Acked-by: John Johansen <john.johansen@canonical.com>
2011-09-15 21:21:57 +02:00
Christian Boltz
189da8236c the last patch to the usr.sbin.dnsmasq profile (r1758) contained a wrong
filename.

References: https://bugzilla.novell.com/show_bug.cgi?id=678749#c11

Acked-by: John Johansen <john.johansen@canonical.com>
2011-09-15 20:58:54 +02:00
Christian Boltz
2c62d802b6 It looks like rc.apparmor.functions renamed "aa_log_action_begin()" to
"aa_log_action_start()" (probably for 2.6), but the rc.apparmor.suse 
initscript was not updated.

This patch fixes the function name in the rc.apparmor.suse initscript.

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

Acked-by: John Johansen <john.johansen@canonical.com>
2011-09-15 20:20:23 +02:00
Christian Boltz
23df761b70 Make the VENDOR_PERL directoy autodetected in utils/Makefile instead of
hardcoding it.

Acked-by: Kees Cook <kees@ubuntu.com>
2011-09-15 20:17:58 +02:00
Christian Boltz
6a80641ee2 perl modules don't need execute permissions.
This patch installs the perl modules with mode 644 instead of 755.

Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-09-13 20:58:24 +02:00
Christian Boltz
02e1e94ab9 add a missing "make install" call to README in the libapparmor section.
Acked-by: John Johansen <john.johansen@canonical.com>
2011-09-12 22:30:17 +02:00
Jamie Strandboge
f28b91b8cf add gwenview to abstractions/ubuntu-browsers.d/multimedia. This is the
default image viewer in Kubuntu (LP: #840973)
2011-09-07 15:00:45 -05:00
Jamie Strandboge
6849615de6 adjust ubuntu-email abstraction for thunderbird rapid release process 2011-09-02 13:21:06 -05:00
John Johansen
743f84099d Allow passing of DEBUG from make into the compile
Fix the build so
    
  make DEBUG=1
    
results in a compile with DEBUG turned on.
    
Also fix build errors in the compile with DEBUG is defined
    
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees.cook@canonical.com>
2011-09-01 11:57:54 -07:00
John Johansen
a041b1738c Remove apparmor_2.6.0~rc1 and apparmor_2.6.0~rc1 tags so git can sync
yet again!

Signed-off-by: John Johansen <john.johansen@canonical.com>
2011-08-31 16:15:44 -07:00
John Johansen
1056ef8418 Add missing documentation updates for aa_is_confined and aa_get_con.
This documentation should have been checked in as part of the patches
that added aa_is_confined and aa_get_con.

Signed-off-by: John Johansen <john.johansen@canonical.com>
2011-08-31 16:07:55 -07:00
John Johansen
ac77e10a0f Fix missing pid_t dependency in apparmor.h
The define for pid_t is missing in apparmor.h so that if it is included
in programs that don't also include sys/types.h the compile will break.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees.cook@canonical.com>
2011-08-31 16:01:54 -07:00
John Johansen
eae6f0525c Add missing introspection regression test that should have been checked in
with the introspection patches.

Signed-off-by: John Johansen <john.johansen@canonical.com>
2011-08-31 15:58:27 -07:00
John Johansen
341b7e61da Add compatibility patches for linux kernel 3.1 2011-08-27 12:56:53 -07:00
John Johansen
eb6d2224bd Updated kernel compatibilty patches for linux 3.0 2011-08-27 12:25:22 -07:00
Christian Boltz
bb1b5f986b Add permissions needed for Active Directory authentification to Samba
profiles.

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

Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-08-27 20:50:42 +02:00
Steve Beattie
1c1cc65cbd Update useradd and userdel profiles to use wutmp abstraction. 2011-08-26 18:58:31 -07:00
Steve Beattie
5dedd16bf5 Cleanup no longer necessary wutmp permission now that it's covered by
the wutmp abstraction.
2011-08-26 18:51:03 -07:00
Christian Boltz
66d51b575d From: Jeff Mahoney <jeffm@suse.com>
Subject: apparmor-profiles: Add samba config files
References: bnc#679182 bnc#666450

Signed-off-by: Jeff Mahoney <jeffm@suse.com>

- updated to match trunk
- added changed path to nmbd profile (/var/cache/samba has moved to 
  /var/lib/samba on (at least) openSUSE 11.4), bnc#679182#c8
  For backward compability, it also allows /var/spool/samba.
- Note: The smbd profile already contains both locations.
by Christian Boltz <apparmor@cboltz.de>

updated according to the comments from Steve Beattie
by Christian Boltz <apparmor@cboltz.de>

Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-08-27 01:52:27 +02:00
Christian Boltz
f26df713c0 Dovecot profile update:
- allow /var/spool/mail, not only the /var/mail symlink
- allow @{HOME}/Mail/
- allow capability fsetid, read access to /etc/lsb-release and 
  SuSE-release and k for /var/{lib,run}/dovecot in usr.bin.dovecot

References:
- dovecot: Added support for /var/spool/mail (bnc#691072)
- Updated dovecot profile (bnc#681267).

Patch taken from openSUSE:11.4:Update:Test, file apparmor-profiles-dovecot
updated to match trunk by Christian Boltz <apparmor@cboltz.de>

Change compared to the patch posted to the ML:
- link rule instead of adding l permissions for /var/lib/dovecot and 
  /var/run/dovecot (as proposed by John Johansen)

Acked-By: John Johansen <john.johansen@canonical.com> on IRC
2011-08-27 01:12:10 +02:00
Steve Beattie
b8f486dee9 Attached is a patch to make the initscript not fail if /tmp is full
by converting the comm(1) usage on temporary files to an embedded
awk script. On both Ubuntu and OpenSUSE, a version of awk (mawk in
Ubuntu, gawk in OpenSUSE) is either a direct or indirect dependency
on the minimal or base package set, and the original reporter also
mentioned that an awk-based solution would be palatable in a way that
converting to bash, or using perl or python here would not be.

In the embedded awk script, I've tried to avoid gawk or mawk specific
behaviors or extensions; e.g. this is the reason for the call to sort
on the output of the awk script, rather than using gawk's asort(). But
please let me know if you see anything that shouldn't be portable
across awk implementations.

An additional issue that is fixed in both scripts is handling child
profiles (e.g. hats) during reload. If child profiles are filtered
out (via grep -v '//') of the list to consider, then on reloading
a profile where a child profile has been removed or renamed, that
child profile will continue to stick around. However, if the profile
containing child profiles is removed entirely, if the initscript
attempts to unload the child profiles after the parent is removed,
this will fail because they were unloaded when the parent was unloaded.
Thus I removed any filtering of child profiles out, but do a post-awk
reverse sort which guarantees that any child profiles will be removed
before their parent is. I also added the LC_COLLATE=C (based on the
Ubuntu version) to the sort call to ensure a consistent sort order.

To restate, the problem with the existing code is that it creates
temporary files in $TMPDIR (by default /tmp) and if that partition
is full, problems with the reload action ensue. Alternate solutions
include switching the initscript to use bash and its <$() extension
or setting TMPDIR to /dev/shm/. The former is unpalatable to some
(particularly for an initscript), and for the latter, /dev/shm is
only guaranteed to exist on GNU libc based systems (glibc apparently
expects /dev/shm to exist for its POSIX shared memory implementation;
see shm_overview(7)).  So to me, awk (sans GNU extensions) looks to
be the least bad option here.

Bug: https://launchpad.net/bugs/775785
2011-08-26 15:55:43 -07:00
Christian Boltz
ac7e66684c Add various capabilities to smbd that are needed for the way powerpoint
handles files. See bnc#705319 for details.

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

Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-08-26 23:19:30 +02:00
Steve Beattie
be3d2bc1a4 Update traceroute profile to include installed path on Ubuntu/Debian. 2011-08-25 15:36:30 -07:00
Christian Boltz
8f28eebe5a Add capability setuid and setgid to nscd profile. Needed by unscd
to switch to a non-root user. unscd is installed as /usr/sbin/nscd
at least at openSUSE.

Original changelog entry from unscd package:
Mon Sep  7 17:30:36 CEST 2009 - pbaudis[at]suse.cz
- Provide the /etc/apparmor.d/usr.sbin.nscd file and make it allow
  for change to the nobody user [bnc#535467]

Currently the nscd package from glibc and the unscd package both contain
a usr.sbin.nscd profile which needs to maintained/updated manually.
With this patch, the profile could be moved back to the
apparmor-profiles package.


Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-08-24 00:57:42 +02:00
Christian Boltz
d17a87bd28 bnc#685674 - The "-I" flag of traceroute is blocked by apparmor
* Do Apr 07 2011 jeffm@suse.de
- Add raw network access to traceroute profile (bnc#685674).

Acked-By: Steve Beattie <steve@nxnw.org>
2011-08-23 02:10:14 +02:00
Christian Boltz
59eb0af705 nscd: allow mkdir for /var/run/nscd/
References: https://bugzilla.novell.com/show_bug.cgi?id=647718

Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-08-23 01:22:41 +02:00
Christian Boltz
7d5840d449 apparmor.vim:
- allow "deny [filename] x" rules
- add support for "make clean"

Acked-by: John Johansen <john.johansen@canonical.com>
2011-08-21 23:49:25 +02:00
John Johansen
fb7f0ddaaa Remove tags
apparmor_2.6.0~rc1
  apparmor_2.6.1~rc1

that have crept in yet again!!!
2011-08-19 16:57:30 -07:00
John Johansen
4bdc7ea51a From: Jeff Mahoney <jeffm@suse.com>
Subject: apparmor-utils: Add support for creds and path operations
References: bnc#564316

 2.6.29 introduced the path security_operations and credentials

 This patch adds support for those operations to the log parser.

Signed-off-by: Jeff Mahoney <jeffm@suse.com>

Resolved merge conflict and removal operation already supported by
the log parser.
Acked-by: John Johansen <john.johansen@canonical.com>
2011-08-18 16:29:48 -07:00
Kees Cook
3ac9f7e676 Due to the Ubuntu multiarch project, internal paths to things keep
changing. Since we arguably shouldn't be hardcoding this kind of
thing, this changes a path around to use the C preprocessor to do the
work of finding the kernel definitions.

Signed-off-by: Kees Cook <kees.cook@canonical.com>
Acked-by: John Johansen <john.johansen@canonical.com>
2011-08-18 18:26:02 -05:00
Kees Cook
843ef93202 As /var/log/messages has been phased out, start preferring syslog
instead.

Needed at least on upgraded Ubuntu machines that went from messages to
syslog recently. If this causes problems, we can easily revert it.

Signed-off-by: Kees Cook <kees.cook@canonical.com>
Acked-by: John Johansen <john.johansen@canonical.com>
2011-08-18 18:17:22 -05:00
Kees Cook
00726ed51a The printk ratelimit needs to be disabled when running genprof via
syslog, otherwise audit events will get dropped.

This runs the risk of having the kernel log wrap around, but that
is a less common case that what this solves. This is a work-around
that will go away when complain info takes a different path in the
future.
2011-08-18 18:15:41 -05:00
Christian Boltz
0cbad5b99b Allow pux and PUx permissions in apparmor.vim
I intentionally don't allow pUx and Pux since the behaviour of those is 
very unexpected (the first letter decides if the environment is cleaned 
up or not - at least that's the result of the discussion in April) and 
the average user won't know this.

Acked-by: John Johansen <john.johansen@canonical.com>
2011-08-19 00:28:10 +02:00
Christian Boltz
d180252c52 This commit adds "capability syslog" to the syslogd and syslog-ng
profiles.

It also adds a comment to the klogd profile that capability sys_admin is 
only needed for backward compatibility with older kernels.

Acked-by: John Johansen <john.johansen@canonical.com>
2011-08-19 00:27:03 +02:00
Christian Boltz
ca5cd28d48 add syslog capability to apparmor.vim
Acked-by: John Johansen <john.johansen@canonical.com>
2011-08-19 00:24:48 +02:00
Christian Boltz
12e0d064cd openSUSE patch klog-needs-CAP_SYSLOG
This patch adds the syslog capability to parser/parser_misc.c
and to the sbin.klogd profile.
2011-08-18 23:54:24 +02:00
Steve Beattie
386a5abc7d logprof and genprof were creating Px and Cx execute permissions with
the modifiers as lowercase (meaning to pass on sensitive environment
variables to the exec'ed process) even if the user told them not to
when prompted. This patch fixes the issue.
2011-08-17 16:26:41 -07:00
Jamie Strandboge
084233a207 utils/aa-notify:
aa-notify would abort if it could not stat the logfile, as can happen
when using auditd and the directory perms for the logfile do not allow access
(x). Add raise_privileges() and drop_privileges() helper functions and adjust
get_logfile_size() and get_logfile_inode() to raise then drop privileges if the
logfile parent directory is not executable. Also adjust reopen_logfile() to use
these helpers.

When error checking in these helpers, use '$> == ...' instead of '$> = ... or
die...' since perl always dies when raising privs in this manner even though
the euid did change (and $!, $@, $^E, and $? are all the same). Not sure why
this is happening but the '==' check should be sufficient.
2011-08-17 13:47:52 -05:00
Jamie Strandboge
94e665b3fa utils/aa-notify:
aa-notify would abort if it could not stat the logfile, as can happen
when using auditd and the directory perms for the logfile do not allow access
(x). Add raise_privileges() and drop_privileges() helper functions and adjust
get_logfile_size() and get_logfile_inode() to raise then drop privileges if the
logfile parent directory is not executable. Also adjust reopen_logfile() to use
these helpers.

When error checking, use '$> == ...' instead of '$> = ... or die...' since perl
always dies when raising privs in this manner even though the euid did change
(and $!, $@, $^E, and $? are all the same). Not sure why this is happening but
the '==' check should be sufficient.
2011-08-17 09:48:12 -05:00
Jamie Strandboge
0a5c4fa159 utils/aa-notify:
- drop supplemental group privileges too. While POSIX::setgid() works nice in
  that it will set both the real uid and euid, it doesn't do anything with the
  supplemental groups (sigh). Instead, assign to $( and $) in a manner that
  clears the supplemental groups.
2011-08-17 08:35:52 -05:00
Jamie Strandboge
9fddd7a716 utils/aa-notify.pod: update to clarify '-u' argument when using '-p' 2011-08-17 07:49:00 -05:00
Steve Beattie
92473dfc46 This patch adds multiarch support for the X DRI paths.
Bug: https://launchpad.net/bugs/826914
2011-08-16 22:47:34 -07:00
Steve Beattie
8ee0292d8e This patch fixes up a conflicting execute permission that was added
in merging in the opensuse fixes to the dhclient profile. It does so
by merging them, using the profile for dhclient-script if it exists
and inheriting dhclient's profile if it does not.
2011-08-16 16:35:57 -07:00
Christian Boltz
174105b9fa From: Jeff Mahoney <jeffm@suse.com>
Subject: apparmor-utils: Fix handling of files in /
References: bnc#397883

 The separate handling of files and directories with realpath is broken.

 For files e.g. /foo, $dir ends up being empty since the / is eaten by
 the regex. realpath resolves an empty argument as the current directory,
 resulting in an incorrect path.

 There's no explanation of why the separate handling was used in the
 first place.

Signed-off-by: Jeff Mahoney <jeffm@suse.com>

Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-08-17 00:06:07 +02:00
Christian Boltz
7f45708c86 Merge k permission for /var/log/lastlog into abstractions/wutmp
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-08-16 12:26:44 +02:00
Christian Boltz
015229d551 From: Jeff Mahoney <jeffm@suse.com>
Subject: Fix for sshd profile
References: bnc#457072

 Without this patch, sshd won't work in enforce mode.

 libselinux accesses /proc/filesystems to determine if it's enabled
 bash won't execute
 audit_control is probably from libselinux too

Updated by Christian Boltz <apparmor@cboltz.de>:
- add /proc/*/oom_adj and oom_score_adj rw
- add /var/log/btmp r
- add /var/log/lastlog k
- removed capability sys_ptrace - doesn't seem to be needed
- changed all login shells to rUx, not only bash
- removed /proc/filesystems (already part of abstractions/base)

Acked-By: John Johansen <john.johansen@canonical.com>
with the following note:
 ACK because I don't see a choice right now but for the 3.0 release
 (next year) I'll ask you to retest and add newer audit controls.
2011-08-13 22:43:33 +02:00
Christian Boltz
267e9610c7 openSUSE patch to remove the "-f" parameter from startproc in rc.aaeventd.suse /
start_aa_event().

Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-08-13 14:22:35 +02:00
Christian Boltz
32797f5780 openSUSE patch to add systemd support to rc.apparmor.functions
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-08-13 14:15:58 +02:00
Christian Boltz
ce85512673 openSUSE patch with some changes in the initscripts.
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-08-13 14:13:49 +02:00
Christian Boltz
5f630f2c5d From: Jeff Mahoney <jeffm@suse.com>
Subject: apparmor-utils: Add check_for_apparmor helper.

 This should be an alias but those get complicated quickly in perl.

Signed-off-by: Jeff Mahoney <jeffm@suse.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-08-13 14:11:20 +02:00
Christian Boltz
4fc9106d37 From: Jeff Mahoney <jeffm@suse.com>
Subject: apparmor-utils: setprofileflags() drops leading whitespace
References: bnc#480795

 setprofileflags() drops leading whitespace for subprofiles. writeheader()
 properly indents subprofiles 2 spaces per nesting level but when
 genprof sets the profile to enforce mode at completion, the whitespace
 is removed.

 This patch adds the whitespace globbing to the regexp and uses it to
 prefix the sub-profile with the correct spacing.

 Reported at: https://bugzilla.novell.com/show_bug.cgi?id=480795

Signed-off-by: Jeff Mahoney <jeffm@suse.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-08-13 14:09:53 +02:00
Christian Boltz
614c051a71 moved rules for /var/lib/libvirt/dnsmasq/ in dnsmasq profile to one place
Pre-Acked by Steve Beattie <sbeattie@ubuntu.com>
2011-08-12 22:14:00 +02:00
Kees Cook
94f06d23c0 add missing libraries/libapparmor/doc/aa_find_mountpoint.pod from https://lists.ubuntu.com/archives/apparmor/2011-July/001280.html 2011-08-10 22:52:05 -07:00
Kees Cook
a8e0b0d0a3 add missed libraries/libapparmor/doc/aa_getcon.pod from https://lists.ubuntu.com/archives/apparmor/2011-July/001282.html 2011-08-10 22:49:16 -07:00
John Johansen
0f3263f710 Gah really make it beta1 instead of RC1 2011-08-10 15:56:17 -07:00
John Johansen
2a81cadd27 Update version to 2.7-beta1. No new feature except maybe pending
feature freeze exception for dbus mediation.
2011-08-10 15:54:51 -07:00
John Johansen
f69822107f Update apparmor's handling of rlimits for cpu limit and more natural units
Allow for rlimit cpu to specified which is now supported by the kernel.

Previously the rlimit units where limited to K, M, G and would fail when
KB, MB, GB where used.  Allow for both, also allow for units on lengths
of time, by specifying "seconds", "minutes", "hours".. or any unique subset
eg. "s", "sec", "m", "min", "h", "hour" ..

NOTE:
This patch does not extend rlimits to be able to handle setting of tasks
that are confined by other profiles.

 Signed-off-by: John Johansen <john.johansen@canonical.com>
2011-08-10 15:53:39 -07:00
John Johansen
4dec6cab65 Add the ability for the parser to have a basic conf file, that defaults
to /etc/apparmor/parser.conf (NOTE option to allow changing this is not
provided currently).

Signed-off-by: John Johansen <john.johansen@canonical.com>
2011-08-09 06:52:43 -07:00
John Johansen
0287ce0c9d Function to test if apparmor support is enabled.
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees.cook@canonical.com>
2011-08-09 06:48:56 -07:00
John Johansen
aae597bfde Library function to find the apparmorfs filesystem mount point
Signed-off-by: John Johansen <john.johansen@canonical.com>
2011-08-09 06:48:17 -07:00
John Johansen
8347fb69c2 Library interface for tasks introspecting confinement.
Signed-off-by: John Johansen <john.johansen@canonical.com>
2011-08-09 06:47:40 -07:00
John Johansen
685632db0c Add the ability to read proc attr interfaces
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees.cook@canonical.com>
2011-08-09 06:45:51 -07:00
Christian Boltz
5169b4556a Replace the last occurrence of /var/run with /{,var/}run/
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-08-09 12:20:50 +02:00
Steve Beattie
3f099da257 .bzrignore: ignore the generated testfiles in the parser testsuite, all
25000+ of them. I swore we used to have this already.
2011-08-09 01:17:42 -07:00
Steve Beattie
d4c4cbe62b It's possible that git doesn't like to create empty directories, so
create the generated_* directories themselves if they don't exist before
running the script to generate them.

Also modify the default invocation of prove to add -f, which reports the
details of failing test cases.
2011-08-09 01:10:19 -07:00
Steve Beattie
c66975ffc5 Make parser's 'tests' target depend on the apparmor_parser binary having
been generated first; otherwise, 'make clean tests' fails.
2011-08-09 00:54:14 -07:00
Christian Boltz
c35ac067b2 My last commit (r1759) accidently did not include
/{,var/}run/cups/ rw,
Adding it now.
2011-08-09 00:42:38 +02:00
Christian Boltz
851a8c8b3a Various cupsd profile fixes/additions
Changed /var/run/cups/** rw, to
  /{,var/}run/cups/ rw,
  /{,var/}run/cups/** rw,
as requested by Steve Beattie

With this change:
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-08-08 23:18:54 +02:00
Christian Boltz
d081f7cb85 From: Jeff Mahoney <jeffm@suse.com>
Subject: dnsmasq: Profile fixes
References: bnc#666090 bnc#678749

Signed-off-by: Jeff Mahoney <jeffm@suse.com>

Updated to match master by
Christian Boltz <apparmor@cboltz.de>

Updated for systemd (/{,var/},run/ instead of /var/run/) by
Christian Boltz <apparmor@cboltz.de> as requested by Steve Beattie

With this change:
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
(final confirmation on IRC in #apparmor)
2011-08-08 23:13:15 +02:00
Christian Boltz
2c5534ca18 syslog-ng profile fixes/additions
updated to match master by
Christian Boltz <apparmor@cboltz.de>

updated to work with systemd (/{,var/}run/ instead of /var/run)
Christian Boltz <apparmor@cboltz.de> as requested by Steve Beattie

With this change:
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-08-08 22:59:28 +02:00
Christian Boltz
08fb58e10d From: Jeff Mahoney <jeffm@suse.com>
Subject: profiles: update dhclient
References: bnc#561152

Signed-off-by: Jeff Mahoney <jeffm@suse.com>

Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-08-08 22:54:23 +02:00
Christian Boltz
13c3e40044 From: Jeff Mahoney <jeffm@suse.com>
Subject: dhcpd: Fix apparmor profile
References: bnc#692428

 This patch adds the network rules needed, corrects the path to dhcpd.leases,
 and adds the path for TSIG DNS keys.

Reported-by: Andrew Beames <suseforum@roocomputing.co.uk>
Signed-off-by: Jeff Mahoney <jeffm@suse.com>

updated to match trunk by
Christian Boltz <apparmor@cboltz.de>

updated to use
    /var/lib/dhcp/{db/,}dhcpd.leases*    rwl,
(instead of just /var/lib/dhcp/db/dhcpd.leases* rwl) to keep the profile
Ubuntu-compatible as requested by Steve Beattie.

With this change:
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-08-08 22:29:45 +02:00
Christian Boltz
b5e525b251 From: Jeff Mahoney <jeffm@suse.com>
Subject: profiles: Add openssl abstraction
References: bnc#623886

 Profiles that use openssl have been adding the openssl files piecemeal.

 This patch creates a new openssl abstraction that can be inherited by
 all profiles that use it.


Signed-off-by: Jeff Mahoney <jeffm@suse.com>

Patch for 
- profiles/apparmor.d/abstractions/ssl_certs 
- profiles/apparmor/profiles/extras/usr.sbin.httpd2-prefork (second chunk)
updated by Christian Boltz <apparmor@cboltz.de>
(didn't apply to trunk)

Acked-By: Steve Beattie <sbeattie@ubuntu.com>

Copyright header in profiles/apparmor.d/abstractions/openssl added by
Christian Boltz <apparmor@cboltz.de>
2011-08-08 22:22:03 +02:00
Christian Boltz
663698c7a6 From: Jeff Mahoney <jeffm@suse.com>
Subject: profile: ntpd -N needs sys_nice
References: bnc#657054

 ntpd -N allows the administrator to increase or decrease priority of the
 ntp server. Since the profile doesn't allow it, the operation is denied.

 This patch adds support for that operation.

Signed-off-by: Jeff Mahoney <jeffm@suse.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-08-08 22:16:06 +02:00
Christian Boltz
957f1ae00f Fix syntax error (introduced in r1751) 2011-08-06 01:35:37 +02:00
Steve Beattie
763855fe9e From: Christian Boltz <apparmor@cboltz.de>
Based on https://bugzilla.novell.com/show_bug.cgi?id=643387

Update man profile and abstraction.
2011-08-05 13:12:35 -07:00
Steve Beattie
e6880f9584 Bug: https://bugs.launchpad.net/bugs/794974
Add openal config support to abstractions/audio.

Bug: https://launchpad.net/bugs/794974
2011-08-04 16:52:45 -07:00
Steve Beattie
786f592d7e From: Arkadiusz Miskiewicz <arekm@maven.pl>
Actually skip loading profiles that we report we're skipping.
2011-08-04 16:20:26 -07:00
Jamie Strandboge
38d83c3c76 fix LP: #815883 2011-07-27 09:30:18 -05:00
Jamie Strandboge
7897ba2293 fix LP: #815883
- update dnsmasq profile for new libvirt lease file path
2011-07-25 08:28:04 -05:00
John Johansen
a43931e987 Update the man page for change_hat/change_profile
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees.cook@canonical.com>
2011-07-22 05:20:06 -07:00
John Johansen
2fec4eb2b4 Make setprocattr more abstract so it will work for any pid
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees.cook@canonical.com>
2011-07-21 11:10:35 -07:00
John Johansen
3f59259dec Abstract out the construction of proc path generation more.
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees.cook@canonical.com>
2011-07-21 11:09:22 -07:00
John Johansen
2c110ed3d5 Abstract the libraries getting of the tid
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees.cook@canonical.com>
2011-07-21 11:06:57 -07:00
Marc Deslauriers
0d9a39bd26 This adds a blurb about the new profiles repository to aa-genprof,
along with a link to the wiki page. This helps users locate profiles
that possibly already exist for the application they are attempting to
confine, and suggests they contribute the profile when they're done.
2011-07-18 10:34:49 -04:00
Jamie Strandboge
5e273b0892 update profiles/apparmor.d/abstractions/audio to fixup shm patch from
yesterday. Thanks to Christian Boltz.
2011-07-15 13:53:35 -05:00
Jamie Strandboge
e91c83964a adjustments for /var/run -> /run, /var/lock -> /run/lock and /dev/shm ->
/run/shm transition (LP: #810270)
2011-07-14 11:29:54 -05:00
Jamie Strandboge
35acee98d5 and last one for /dev/shm to /run/shm 2011-07-14 08:21:01 -05:00
Jamie Strandboge
8434463621 make a similar change for /var/lock 2011-07-14 08:09:19 -05:00
Jamie Strandboge
20f117500f update for /var/run -> /run udev transition. For compatibility, distributions
(eg Ubuntu) are providing a symlink from /var/run to /run, so our profiles
should handle both situations.
2011-07-14 07:57:57 -05:00
Jamie Strandboge
850bce2754 Add /usr/share/gnome/applications/mimeinfo.cache to gnome abstraction 2011-07-12 15:24:52 -05:00
Jamie Strandboge
7c05b9ed91 profiles/apparmor.d/abstractions/gnome: add read access to
/usr/share/gnome/applications/mimeinfo.cache, which is now needed by
 Gnome applications.
2011-07-12 07:38:17 -05:00
John Johansen
5abb79d5be Add documentation directory and start of the dfa variables documentation
Signed-off-by: John Johansen <john.johansen@canonical.com>
2011-06-27 10:32:04 +01:00
John Johansen
70a06835b8 Remove tags with ~ in them
Signed-off-by: John Johansen <john.johansen@canonical.com>
2011-06-27 10:24:08 +01:00
Jamie Strandboge
2e73225586 allow owner read access to /var/run/lightdm/authority/[0-9]* in the X
abstraction
2011-06-22 22:26:14 -05:00
Jamie Strandboge
65c7473407 profiles/apparmor.d/abstractions/ssl_certs: allow access to
/usr/local/share/ca-certificates
2011-06-22 16:42:22 -05:00
John Johansen
901e67a83f Rename tags
apparmor_2.6.1~rc1 to apparmor_2.6.1-rc1
  apparmor_2.6.0~rc1 to apparmor_2.6.0-rc1

to be consistent with the new format and to enable mirroring to a git
repository
2011-06-02 21:08:42 -07:00
Steve Beattie
33c615e5f9 This patch adjusts the tag make target to use a separate version with
'~' replaced by '-'. This is needed for mirroring to git as git can't
handle '~'s embedded in tag or branch names.

Tested by setting up a separate tag_version target like so:

tag_version:
	echo ${TAG_VERSION}
2011-06-02 18:54:56 -07:00
Steve Beattie
fdae9784f4 Bug: https://bugs.launchpad.net/apparmor/+bug/788616
This patch fixes the init scripts helper functions file to
filter out the hat/child process separator as currently used
by the parser, '//' rather than what used to be used, the '^'
symbol. This fixes bugs where profiles that covered regexs (e.g.
'/usr/lib/firefox-4.0.1/firefox{,*[^s][^h]}') and thus were being
improperly filtered away and unloaded when reloading apparmor policy.
2011-06-01 07:52:35 -07:00
Kees Cook
25f7aa6621 adjust documentation "release" name to match other manpages.
Signed-off-by: Kees Cook <kees.cook@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-05-27 15:04:42 -07:00
Kees Cook
156a980c30 In some cases, it is desirable to build the parser without building the
binary portions (Hurd). This patch splits up the build targets so this is
possible:

"main" becomes "arch"

"indep" is created and depends on "docs"
po building is moved from "main" to "indep"

"all" has "tests" removed (standard build practices are to "make" then
"make check" so I think "tests"/"check" should stay separate from "all").

redundant chunk is removed (this exists twice in the Makefile):
-.SILENT: check
-check: tests

"install" is split into "install-indep" and "install-arch"

"install-arch" requires "arch" and only installs the binaries

"install-indep" requires "indep" and only install non-binaries

Additionally, update the README to mention the "check" target both for the
parser and the utils.

Signed-off-by: Kees Cook <kees.cook@canonical.com>
Acked-by: Steve Beattie <steve@nxnw.org>
2011-05-27 14:57:43 -07:00
Kees Cook
e9d5d491b8 define PATH_MAX when missing from limits.h
Some non-Linux systems do not define PATH_MAX (Hurd). Since I have no
interest in supporting a fully dynamic PATH_MAX in AppArmor, work around
this by just defining a static value that matches Linux's limits.h value.

Signed-off-by: Kees Cook <kees.cook@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-05-27 14:20:03 -07:00
Marc Deslauriers
b9bfc10d0f - added new aa-status tool rewritten in Python
- moved old Perl aa-status tool to the deprecated directory
2011-05-27 15:08:50 -04:00
Steve Beattie
12876afa07 This patch fixes up a bit that leaked into kees' commit rev 1727
to support a top level make clean target.
2011-05-23 16:05:10 -07:00
Kees Cook
e66e56b020 Add pending local commits. 2011-05-23 11:30:11 -07:00
Kees Cook
6a7a20da88 include explicit parser_common.o rule, thanks to Steve Beattie 2011-05-23 11:29:41 -07:00
Kees Cook
35cdeebe19 fix sprintf arguments 2011-05-23 11:28:26 -07:00
Steve Beattie
c2fdcf7d39 This patch makes it possible to override the default set of language
translations to be built via the LANGS make argument whitelist. For
example:

  cd parser; make all install "LANGS=en_US fr"

will build and install the en_US and fr .mo files for the parser.
2011-05-20 13:34:29 -07:00
John Johansen
627638a6cf Add debugging dump for DFA partition minimization
Allow dumping out which states where dropped during partition minimization
and which state became the partitions representative state.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-05-20 09:26:44 -07:00
John Johansen
414e5bf560 Fix the dfa-graph dump
The dfa graph dump was broken by previous dfa cleanups so that the graph
transition target is the output of a pointer instead of the dfa state
number.
    
Signed-off-by: John Johansen <john.johansen@canonical.com>
2011-05-20 09:24:40 -07:00
John Johansen
343749dc26 Add kernel patches for 2.6.39 2011-05-19 10:57:20 -07:00
Kees Cook
6a68aa2ecb [v2: added clean-ups, backed off on some of the build silencing]
This is a rather large rearrangement of how a subset of the parser global
variables are defined. Right now, there are unit tests built without
linking against parser_main.c. As a result, none of the globals defined in
parser_main.c could be used in the code that is built for unit tests
(misc, regex, symtab, variable). To get a clean build, either stubs needed
to be added to "#ifdef UNIT_TEST" blocks in each .c file, or we had to
depend on link-time optimizations that would throw out the unused routines.

First, this is a problem because all the compile-time warnings had to be
explicitly silenced, so reviewing the build logs becomes difficult on
failures, and we can potentially (in really unlucky situations) test
something that isn't actually part of the "real" parser.

Second, not all compilers will allow this kind of linking (e.g. mips gcc),
and the missing symbols at link time will fail the entire build even though
they're technically not needed.

To solve all of this, I've moved all of the global variables used in lex,
yacc, and main to parser_common.c, and adjusted the .h files. On top of
this, I made sure to fully link the tst builds so all symbols are resolved
(including aare lib) and removedonly  tst build-log silencing (for now,
deferring to another future patchset to consolidate the build silencing).

Signed-off-by: Kees Cook <kees.cook@canonical.com>
2011-05-13 02:12:49 -07:00
Jamie Strandboge
70bb296a70 profiles/apparmor.d/abstractions/dbus-session: Per discussion with John
Johansen, use 'ix' instead of 'Pix' for dbus-launch since if someone happens to
define a profile for dbus-launch and it is loosely confined, then users of this
abstraction could end up launching a program via dbus-launch in a less confined
manner than intended. This sort of thing should not be possible via an
abstraction (and people are always free to profile using Pix if they prefer).
2011-05-09 18:09:24 +02:00
Kees Cook
a25a2ec0d9 On Hurd and kfreebsd under Debian, I have disabled the build of the parser
since things go extremely badly when capabilities disappear. If someone
wants to work on it, I have some initial patch attempts, but it was getting
too time-consuming, so I back-burnered the parser. A very small change was
needed to get the libraries to build, and this is it.

Description: Workaround non-Linux environments to build everything but the
 parser.
Author: Kees Cook <kees@debian.org>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-05-02 13:38:12 -07:00
Kees Cook
1644ce31e7 Description: Improve generated test readability and build-time cleanup.
Author: Kees Cook <kees@debian.org>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-05-02 13:36:55 -07:00
Kees Cook
abcf66292d Description: adjust for missing or incorrect includes.
Author: Kees Cook <kees@ubuntu.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-05-02 13:34:58 -07:00
Kees Cook
42a5483ba9 Description: fix minor manpage type pointed out by lintian.
Author: Kees Cook <kees@debian.org>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-05-02 13:33:31 -07:00
Jamie Strandboge
83282f8700 add kwallet to ubuntu-browsers.d/user-files 2011-04-19 06:03:35 -05:00
Jamie Strandboge
8485c8e417 profiles/apparmor.d/abstractions/private-files:
- add zsh files (LP: #761217)
- add .inputrc (bash)
- add .login and .logout (csh, tcsh, etc)
2011-04-19 05:55:32 -05:00
Kees Cook
4670afee7c Nothing in deprecated/ should be built any more, much less shipped in the
tarball. Let's drop this from future tarball creation.

Signed-off-by: Kees Cook <kees.cook@canonical.com>
Acked-by: John Johansen <john.johansen@canonical.com>
2011-04-18 18:09:29 -07:00
Jamie Strandboge
e946b88d82 ubuntu-browsers.d/multimedia: add f-spot, shotwell and digikam to image viewers
since they are invoked when using sites such as Facebook
2011-04-18 09:32:14 -05:00
Jamie Strandboge
780ae4663d profiles/apparmor.d/abstractions/private-files:
- add zsh files (LP: #761217)
- add .inputrc (bash)
- add .login and .logout (csh, tcsh, etc)
2011-04-18 08:55:50 -05:00
Christian Boltz
34676334b6 added some missing permissions to profiles/extra
ACKed by Kees Cook
2011-04-12 00:10:41 +02:00
Christian Boltz
d799edbcc6 create-apparmor.vim.sh:
- move cap_sys_module and cap_sys_rawio to "dangerous" capabilities
- sorted sdKapKeyDanger

Proposed by Seth Arnold,
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-04-09 18:47:42 +02:00
Jamie Strandboge
00f32d555d adjust ubuntu-media-players abstraction to allow (LP: #750381):
- reading of configs required by gnash
- owner writing of @{HOME}/.gnash
2011-04-07 09:50:20 -05:00
Steve Beattie
b7a8a01ac7 This patch fixes a compilation warning that was trying to tell us
about an actual bug in the parser; namely that when handling strings
encapsulated in quotes, that our handling of octals is busted. It
fixes this by fixing the case entries so that 3 digit octals will
get parsed correctly, rather than dropped.

It also adds a bunch of unit tests for the processquoted() function.
2011-04-05 20:55:19 -07:00
Steve Beattie
3a8546732a This patch fixes warnings emitted by the compiler when compiling on a
32bit arch, due to size_t objects being passed to fprintf with format
strings expecting longs. It does this by adjusting the fprintf rules
to expect size_t objects.
2011-04-05 20:53:35 -07:00
Steve Beattie
d656afa1d5 This patch fixes the parser's dfa generation library makefile to use
the default compilation rules when compiling C++ files, so that things
like CFLAGS et al will be honored. Without this, doing 'make DEBUG=y'
in the parser/ tree will not have its added -pg flag honored, breaking
profiling of the parser.
2011-04-05 20:51:02 -07:00
Christian Boltz
5b3190d39c Add files to generate apparmor.vim to bzr.
Basically the files will generate apparmor.vim as included in openSUSE 
11.4 (and posted here before at the end of january). The only difference 
is that the patch that Steve posted some days ago is already included 
(patch summary: sdGlob: first character of variable name has to be 
 :alpha:, followed by any number of :alnum: or _)
2011-04-05 23:56:14 +02:00
Christian Boltz
c5dca95504 apparmor.d.pod:
- fix ALPHANUMERIC to include 0...9 instead of 1...9
- enforce linebreaks for network example rules

Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-04-01 20:35:14 +02:00
Steve Beattie
dce1d5d5e6 Bah, fix up screwed up testcase. 2011-03-29 02:46:09 -07:00
Steve Beattie
bb31faff1b This patch extends the stress profile generator to add variable
definition and references.
2011-03-28 21:07:44 -07:00
Steve Beattie
162b49b417 This patch attempts to address the over-aggressive behavior of the
stress tests on the parser, by dropping the maximum number of rules
each profile can have, as well as reducing the number of profiles to
generate by default to 50. It also cleans up the emitted profiles
a little, creates the profile names with the suffix .sd [1], fixes
stress.sh to actually honor the -p (alternate parser) argument, fixes
the profile flags generation to not generate duplicates flags, and
fixes the file rules to always start with a constant randomly-generated
prefix element (rather than a regex or variable) to greatly reduce
the possibility of X dominance collisions in the parser
2011-03-28 20:48:30 -07:00
Steve Beattie
f8b43d5ba9 The parser's lexer supports variables defined matching the regex
'[[:alpha:]][[:alnum:]_]*' (i.e. a single alpha followed by any number
of alphanumerics or underscores). Unfortunately, the code that expends
variables inside a profile does not match this, it incorrectly matched
'([[:alpha:]]|_)+' (one or more alphas or underscores). This patch
corrects the behavior there as well as synchronizing the expected
variable names in the apparmor.d manpage and apparmor.vim syntax file.

It also adds unit tests and testcases to verify the behavior.

Signed-off-by: Steve Beattie <sbeattie@ubuntu.com>
2011-03-28 10:52:02 -07:00
Steve Beattie
e8f297db5a Fix up the profiles make install target for the tunables/multiarch.d/
change.
2011-03-23 16:10:33 -07:00
Steve Beattie
4d332ff241 Fix multi-arch comment tyop, thanks to Kees for pointing it out. 2011-03-23 13:44:51 -07:00
Steve Beattie
83007d7600 Author: Jamie Strandboge <jamie@canonical.com>,
Steve Langasek <steve.langasek@linaro.org>,
 Steve Beattie <sbeattie@ubuntu.com>
Description: add multiarch support to abstractions
Bug-Ubuntu: https://bugs.launchpad.net/bugs/736870

This patch add multiarch support for common shared library locations, as
well as a tunables file and directory to ease adding addiotional
multiarch paths.

Bug: https://launchpad.net/bugs/736870
2011-03-23 12:24:11 -07:00
Steve Beattie
5f72ae98eb Update the toplevel README file to discuss the needed apparmor
compatibility patches, mention the profile consistency check, and
remove some variables that no longer need to be set at build time.
2011-03-18 23:13:58 -07:00
Steve Beattie
940ead1fb8 disable the local include in the extras firefox profile; the build
process does not generate local files for things in extras, and even if
it did, this one is named in a non-standard fashion (usr.bin.firefox vs.
usr.lib.firefox.firefox).
2011-03-18 22:38:55 -07:00
Steve Beattie
bb28ca5371 profiles/Makefile: fix 'check' target to iterate over the profiles in
the extras directory as intended and fail the make if a parse failure
occurs. Also, set the default parser and logprof to be the intree ones;
the system ones can still be used by setting environment variables.
Finally, have the 'all' target generate the local files. Also, set the
parser base directory to the apparmor.d directory (rather than as an
added include, to avoid outside contamination from system profiles and
includes).

With these changes, make && make check should verify the profile set is
compilable and mostly consistent. (Alas, the current profiles are not
quite consistent).
2011-03-18 22:31:26 -07:00
Steve Beattie
0edae73a9a This fixes the apparmor apache2 module to link correctly against the
built libapparmor, as well as working around libtool so that the
libapparmor library build directory does not get added as an rpath to
the module.

Bug: https://launchpad.net/bugs/737074
2011-03-17 23:32:34 -07:00
Steve Beattie
19a2d6d169 Reduce the number of network protocols filtered from the parser.
https://bugs.launchpad.net/bugs/732837

Bug: https://launchpad.net/bugs/732837
2011-03-17 10:50:53 -07:00
Steve Beattie
f3b847c4b0 Fix from PLD/Arkadiusz Miskiewicz <arekm@maven.pl> to the initscript
helper functions to correct some log messages and to unload hats first,
before their parents.
2011-03-17 10:21:06 -07:00
John Johansen
1a2484e5bc Finish renaming regexp to regex
Signed-off-by: John Johansen <john.johansen@canonical.com>
2011-03-13 06:01:21 -07:00
John Johansen
6ed55cb1d5 Update Makefile dependencies
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-03-13 06:00:31 -07:00
John Johansen
099f19f99c Update the licence for apparmor_re.h
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-03-13 05:59:48 -07:00
John Johansen
7d2a6b53d4 Lindent parse + hand cleanups
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Seth Arnold <seth.arnold@gmail.com>
2011-03-13 05:58:54 -07:00
John Johansen
6f0c68a4d4 Lindent + some hand cleanups expr-tree
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Seth Arnold <seth.arnold@gmail.com>
2011-03-13 05:57:39 -07:00
John Johansen
9a377bb9da Lindent + some hand cleanups hfa
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Seth Arnold <seth.arnold@gmail.com>
2011-03-13 05:55:25 -07:00
John Johansen
3cfe47d3f0 Lindent + hand cleanups compressed-dfa
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-03-13 05:54:18 -07:00
John Johansen
84c0bba1ef Lindent + hand cleanups aare_rules
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-03-13 05:53:08 -07:00
John Johansen
6aad970d1c Split out compressed dfa "transition table" compression
Split hfa into hfa and compressed_hfa files.  The hfa portion focuses on
creating an manipulating hfas, while compressed_hfa is used for creating
compressed hfas that can be used/reused at run time with much less memory
usage than the full blown hfa.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-03-13 05:50:34 -07:00
John Johansen
298a36bffb Split out aare_rules which are used to encapsulate creating the dfa
Split out the aare_rule bits that encapsulate the convertion of apparmor
rules into the final compressed dfa.

This patch will not compile because of the it needs hfa to export an interface
but hfa is going to be split so just delay until hfa and transtable are
split and they can each export their own interface.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-03-13 05:49:15 -07:00
John Johansen
846cee5066 Split out parsing and expression trees from regexp.y
Start of splitting regexp.y into logical components instead of the mess
it is today.  Split out the expr-tree and parsing components from regexp.y
int expr-tree.x and parse.y and since regexp.y no longer does parsing
rename it to hfa.cc

Some code cleanups snuck their way into this patch and since I am to
lazy to redo it, I have left them in.

Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2011-03-13 05:46:29 -07:00
Steve Beattie
50760ef05b Adjust version to indicate that 2.6 has branched off and that trunk is
working towards a 2.7.0 or 3.0.0 release.
2011-03-10 10:25:55 -08:00
320 changed files with 14901 additions and 5084 deletions

View File

@@ -1,3 +1,4 @@
apparmor-*
parser/po/*.mo
parser/af_names.h
parser/cap_names.h
@@ -5,6 +6,7 @@ parser/tst_misc
parser/tst_regex
parser/tst_symtab
parser/tst_variable
parser/tst/simple_tests/generated_*/*
parser/parser_lex.c
parser/parser_version.h
parser/parser_yacc.c

View File

@@ -7,13 +7,9 @@ include common/Make.rules
DIRS=parser \
profiles \
utils \
changehat/libapparmor \
libraries/libapparmor \
changehat/mod_apparmor \
changehat/pam_apparmor \
management/apparmor-dbus \
management/applets/apparmorapplet-gnome \
management/yastui \
common \
tests
REPO_URL?=lp:apparmor
@@ -24,12 +20,17 @@ REPO_URL?=lp:apparmor
RELEASE_DIR=apparmor-${VERSION}
__SETUP_DIR?=.
# We create a separate version for tags because git can't handle tags
# with embedded ~s in them. No spaces around '-' or they'll get
# embedded in ${VERSION}
TAG_VERSION=$(subst ~,-,${VERSION})
.PHONY: tarball
tarball: clean
REPO_VERSION=`$(value REPO_VERSION_CMD)` ; \
make export_dir __EXPORT_DIR=${RELEASE_DIR} __REPO_VERSION=$${REPO_VERSION} ; \
make setup __SETUP_DIR=${RELEASE_DIR} ; \
tar cvzf ${RELEASE_DIR}.tar.gz ${RELEASE_DIR}
tar --exclude deprecated -cvzf ${RELEASE_DIR}.tar.gz ${RELEASE_DIR}
.PHONY: snapshot
snapshot: clean
@@ -37,7 +38,7 @@ snapshot: clean
SNAPSHOT_DIR=apparmor-${VERSION}~$${REPO_VERSION} ;\
make export_dir __EXPORT_DIR=$${SNAPSHOT_DIR} __REPO_VERSION=$${REPO_VERSION} ; \
make setup __SETUP_DIR=$${SNAPSHOT_DIR} ; \
tar cvzf $${SNAPSHOT_DIR}.tar.gz $${SNAPSHOT_DIR} ;
tar --exclude deprecated -cvzf $${SNAPSHOT_DIR}.tar.gz $${SNAPSHOT_DIR} ;
.PHONY: export_dir
@@ -49,6 +50,9 @@ export_dir:
.PHONY: clean
clean:
-rm -rf ${RELEASE_DIR} ./apparmor-${VERSION}~*
for dir in $(DIRS); do \
make -C $$dir clean; \
done
.PHONY: setup
setup:
@@ -56,4 +60,5 @@ setup:
.PHONY: tag
tag:
bzr tag apparmor_${VERSION}
bzr tag apparmor_${TAG_VERSION}

42
README
View File

@@ -30,13 +30,26 @@ AppArmor consists of several different parts:
changehat/ source for using changehat with Apache, PAM and Tomcat
common/ common makefile rules
desktop/ empty
kernel-patches/ patches for various kernel versions
kernel-patches/ compatibility patches for various kernel versions
libraries/ libapparmor source and language bindings
parser/ source for parser/loader and corresponding documentation
profiles/ configuration files, reference profiles and abstractions
tests/ regression and stress testsuites
utils/ high-level utilities for working with AppArmor
--------------------------------------
Important note on AppArmor kernel code
--------------------------------------
While most of the kernel AppArmor code has been accepted in the
upstream Linux kernel, a few important pieces were not included. These
missing pieces unfortunately are important bits for AppArmor userspace
and kernel interaction; therefore we have included compatibility
patches in the kernel-patches/ subdirectory, versioned by upstream
kernel (2.6.37 patches should apply cleanly to 2.6.38 source).
Without these patches applied to the kernel, the AppArmor userspace
will not function correctly.
------------------------------------------
Building and Installing AppArmor Userspace
@@ -49,43 +62,52 @@ the following order.
libapparmor:
$ cd ./libraries/libapparmor
$ sh ./autogen.sh
$ sh ./configure --prefix=/usr --with-perl
$ sh ./configure --prefix=/usr --with-perl # see below
$ make
$ make check
$ make install
[optional arguments to libapparmor's configure include --with-python
and --with-ruby, to generate python and ruby bindings to libapparmor,
respectively.]
Utilities:
$ cd utils
$ make
$ make check
$ make install
parser:
$ cd parser
$ make
$ make tests # not strictly necessary as they are run during the
# build by default
$ make check
$ make install
Apache mod_apparmor:
$ cd changehat/mod_apparmor
$ LIBS="-lapparmor" make
$ make # depends on libapparmor having been built first
$ make install
PAM AppArmor:
$ cd changehat/pam_apparmor
$ LIBS="-lapparmor -lpam" make
$ make # depends on libapparmor having been built first
$ make install
Profiles:
$ cd profiles
$ make
$ make check # depends on the parser having been built first
$ make install
[Note that for the parser and the utils, if you only with to build/use
some of the locale languages, you can override the default by passing
the LANGS arguments to make; e.g. make all install "LANGS=en_US fr".]
-------------------
AppArmor Testsuites
@@ -124,6 +146,14 @@ For details on structure and adding tests, see libraries/libapparmor/README.
$ cd libraries/libapparmor
$ make check
Profile checks
--------------
A basic consistency check to ensure that the parser and aa-logprof parse
successfully the current set of shipped profiles. The system or other
parser and logprof can be passed in by overriding the PARSER and LOGPROF
variables.
$ cd profiles
$ make && make check
Stress Tests
------------

View File

@@ -41,12 +41,15 @@ APXS:=$(shell if [ -x "/usr/sbin/apxs2" ] ; then \
fi )
APXS_INSTALL_DIR=$(shell ${APXS} -q LIBEXECDIR)
DESTDIR=
LIBAPPARMOR_FLAGS="-I../../libraries/libapparmor/src -L../../libraries/libapparmor/src/.libs -lapparmor"
# Need to pass -Wl twice here to get past both apxs2 and libtool, as
# libtool will add the path to the RPATH of the library if passed -L/some/path
LIBAPPARMOR_FLAGS=-I../../libraries/libapparmor/src -Wl,-Wl,-L../../libraries/libapparmor/src/.libs
LDLIBS=-lapparmor
all: $(TARGET) ${MANPAGES} ${HTMLMANPAGES}
%.so: %.c
${APXS} ${LIBAPPARMOR_FLAGS} -c $<
${APXS} ${LIBAPPARMOR_FLAGS} -c $< ${LDLIBS}
mv .libs/$@ .
.PHONY: install

View File

@@ -37,4 +37,4 @@ clean:
rm -f tomcat_apparmor.spec ${NAME}-*.tar.gz Make.rules
install: $(SPECFILE)
ant -Dversion=$(VERSION) -Drelease=$(RELEASE) -Dcatalina_home=${CATALINA_HOME} -Dinstall_lib=${LIB} install_jar install_jni
ant -Dversion=$(VERSION) -Drelease=$(MAN_RELEASE) -Dcatalina_home=${CATALINA_HOME} -Dinstall_lib=${LIB} install_jar install_jni

View File

@@ -37,4 +37,4 @@ clean:
rm -f tomcat_apparmor.spec ${NAME}-*.tar.gz Make.rules
install: $(SPECFILE)
ant -Dversion=$(VERSION) -Drelease=$(RELEASE) -Dcatalina_home=${CATALINA_HOME} -Dinstall_lib=${LIB} install_jar install_jni
ant -Dversion=$(VERSION) -Drelease=$(MAN_RELEASE) -Dcatalina_home=${CATALINA_HOME} -Dinstall_lib=${LIB} install_jar install_jni

View File

@@ -27,7 +27,8 @@ XGETTEXT_ARGS=--copyright-holder="NOVELL, Inc." --msgid-bugs-address=apparmor@li
# pass in the list of sources in the SOURCES variable
PARENT_SOURCES=$(foreach source, ${SOURCES}, ../${source})
LANGS=$(patsubst %.po, %, $(wildcard *.po))
# Can override by passing LANGS=whatever here
LANGS?=$(patsubst %.po, %, $(wildcard *.po))
TARGET_MOS=$(foreach lang, $(filter-out $(DISABLED_LANGS),$(LANGS)), ${lang}.mo)
.PHONY: all

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.6.0
2.7.102

218
deprecated/utils/aa-status Normal file
View File

@@ -0,0 +1,218 @@
#!/usr/bin/perl -w
# ------------------------------------------------------------------
#
# Copyright (C) 2005-2006 Novell/SUSE
#
# 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.
#
# ------------------------------------------------------------------
use strict;
use Getopt::Long;
use Cwd 'abs_path';
my $confdir = "/etc/apparmor";
my $sd_mountpoint;
my $check_enabled = 0;
my $count_enforced = 0;
my $count_profiled = 0;
my $count_complain = 0;
my $verbose = 0;
my $help;
GetOptions(
'complaining' => \$count_complain,
'enabled' => \$check_enabled,
'enforced' => \$count_enforced,
'profiled' => \$count_profiled,
'verbose|v' => \$verbose,
'help|h' => \$help,
) or usage();
sub usage {
print "Usage: $0 [OPTIONS]\n";
print "Displays various information about the currently loaded AppArmor policy.\n";
print "OPTIONS (one only):\n";
print " --enabled returns error code if subdomain not enabled\n";
print " --profiled prints the number of loaded policies\n";
print " --enforced prints the number of loaded enforcing policies\n";
print " --complaining prints the number of loaded non-enforcing policies\n";
print " --verbose (default) displays multiple data points about loaded policy set\n";
print " --help this message\n";
exit;
}
$verbose = 1 if ($count_complain + $check_enabled + $count_enforced + $count_profiled == 0);
usage() if $help or ($count_complain + $check_enabled + $count_enforced + $count_profiled + $verbose > 1);
sub is_subdomain_loaded() {
return 1 if (-d "/sys/module/apparmor");
if(open(MODULES, "/proc/modules")) {
while(<MODULES>) {
return 1 if m/^(subdomain|apparmor)\s+/;
}
}
return 0;
}
sub find_subdomainfs() {
my $sd_mountpoint;
if(open(MOUNTS, "/proc/mounts")) {
while(<MOUNTS>) {
$sd_mountpoint = "$1/apparmor" if m/^\S+\s+(\S+)\s+securityfs\s/ && -e "$1/apparmor";
$sd_mountpoint = "$1/subdomain" if m/^\S+\s+(\S+)\s+securityfs\s/ && -e "$1/subdomain";
$sd_mountpoint = $1 if m/^\S+\s+(\S+)\s+subdomainfs\s/ && -e "$1";
}
close(MOUNTS);
}
return $sd_mountpoint;
}
sub get_profiles {
my $mountpoint = shift;
my %profiles = ();
if (open(PROFILES, "$mountpoint/profiles")) {
while(<PROFILES>) {
$profiles{$1} = $2 if m/^([^\(]+)\s+\((\w+)\)$/;
}
close(PROFILES);
}
return (%profiles);
}
sub get_processes {
my %profiles = @_;
my %processes = ();
if (opendir(PROC, "/proc")) {
my $file;
while (defined($file = readdir(PROC))) {
if ($file =~ m/^\d+/) {
if (open(CURRENT, "/proc/$file/attr/current")) {
while (<CURRENT>) {
if (m/^([^\(]+)\s+\((\w+)\)$/) {
$processes{$file}{'profile'} = $1;
$processes{$file}{'mode'} = $2;
} elsif (grep(abs_path("/proc/$file/exe") eq $_ , keys(%profiles))) {
# keep only unconfined processes that have a profile defined
$processes{$file}{'profile'} = abs_path("/proc/$file/exe");
$processes{$file}{'mode'} = 'unconfined';
}
}
close(CURRENT);
}
}
}
closedir(PROC);
}
return (%processes);
}
my $is_loaded = is_subdomain_loaded();
if (!$is_loaded) {
print STDERR "apparmor module is not loaded.\n" if $verbose;
exit 1;
}
print "apparmor module is loaded.\n" if $verbose;
$sd_mountpoint = find_subdomainfs();
if (!$sd_mountpoint) {
print STDERR "apparmor filesystem is not mounted.\n" if $verbose;
exit 3;
}
if (! -r "$sd_mountpoint/profiles") {
print STDERR "You do not have enough privilege to read the profile set.\n" if $verbose;
exit 4;
}
#print "subdomainfs is at $sd_mountpoint.\n" if $verbose;
# processes is a hash table :
# * keys : processes pid
# * values : hash containing information about the running process:
# * 'profile' : name of the profile applied to the running process
# * 'mode' : mode of the profile applied to the running process
my %processes = ();
my %enforced_processes = ();
my %complain_processes = ();
my %unconfined_processes = ();
# profiles is a hash table :
# * keys : profile name
# * value : profile mode
my %profiles;
my @enforced_profiles = ();
my @complain_profiles = ();
%profiles = get_profiles($sd_mountpoint);
@enforced_profiles = grep { $profiles{$_} eq 'enforce' } keys %profiles;
@complain_profiles = grep { $profiles{$_} eq 'complain' } keys %profiles;
# we consider the case where no profiles are loaded to be "disabled" as well
my $rc = (keys(%profiles) == 0) ? 2 : 0;
if ($check_enabled) {
exit $rc;
}
if ($count_profiled) {
print scalar(keys(%profiles)). "\n";
exit $rc;
}
if ($count_enforced) {
print $#enforced_profiles + 1 . "\n";
exit $rc;
}
if ($count_complain) {
print $#complain_profiles + 1 . "\n";
exit $rc;
}
if ($verbose) {
print keys(%profiles) . " profiles are loaded.\n";
print $#enforced_profiles + 1 . " profiles are in enforce mode.\n";
for (sort(@enforced_profiles)) {
print " " . $_ . "\n";
}
print $#complain_profiles + 1 . " profiles are in complain mode.\n";
for (sort(@complain_profiles)) {
print " " . $_ . "\n";
}
}
%processes = get_processes(%profiles);
if ($verbose) {
for (keys(%processes)) {
$enforced_processes{$_} = $processes{$_} if $processes{$_}{'mode'} eq 'enforce';
$complain_processes{$_} = $processes{$_} if $processes{$_}{'mode'} eq 'complain';
# some early code uses unconfined instead of unconfined.
$unconfined_processes{$_} = $processes{$_} if $processes{$_}{'mode'} =~ /uncon(fi|strai)ned/;
}
print keys(%processes) . " processes have profiles defined.\n";
print keys(%enforced_processes) . " processes are in enforce mode :\n";
for (sort { $enforced_processes{$a}{'profile'} cmp $enforced_processes{$b}{'profile'} } keys(%enforced_processes)) {
print " " . $enforced_processes{$_}{'profile'} . " ($_) \n";
}
print keys(%complain_processes) . " processes are in complain mode.\n";
for (sort { $complain_processes{$a}{'profile'} cmp $complain_processes{$b}{'profile'} } keys(%complain_processes)) {
print " " . $complain_processes{$_}{'profile'} . " ($_) \n";
}
print keys(%unconfined_processes) . " processes are unconfined but have a profile defined.\n";
for (sort { $unconfined_processes{$a}{'profile'} cmp $unconfined_processes{$b}{'profile'} } keys(%unconfined_processes)) {
print " " . $unconfined_processes{$_}{'profile'} . " ($_) \n";
}
}
exit $rc;

Binary file not shown.

View File

@@ -0,0 +1,553 @@
From 0ae314bc92d8b22250f04f85e4bd36ee9ed30890 Mon Sep 17 00:00:00 2001
From: John Johansen <john.johansen@canonical.com>
Date: Mon, 4 Oct 2010 15:03:36 -0700
Subject: [PATCH 1/3] AppArmor: compatibility patch for v5 network controll
Add compatibility for v5 network rules.
Signed-off-by: John Johansen <john.johansen@canonical.com>
---
include/linux/lsm_audit.h | 4 +
security/apparmor/Makefile | 19 ++++-
security/apparmor/include/net.h | 40 +++++++++
security/apparmor/include/policy.h | 3 +
security/apparmor/lsm.c | 112 +++++++++++++++++++++++
security/apparmor/net.c | 170 ++++++++++++++++++++++++++++++++++++
security/apparmor/policy.c | 1 +
security/apparmor/policy_unpack.c | 48 ++++++++++-
8 files changed, 394 insertions(+), 3 deletions(-)
create mode 100644 security/apparmor/include/net.h
create mode 100644 security/apparmor/net.c
diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h
index 112a550..d5f3dd7 100644
--- a/include/linux/lsm_audit.h
+++ b/include/linux/lsm_audit.h
@@ -123,6 +123,10 @@ struct common_audit_data {
u32 denied;
uid_t ouid;
} fs;
+ struct {
+ int type, protocol;
+ struct sock *sk;
+ } net;
};
} apparmor_audit_data;
#endif
diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile
index 2dafe50..7cefef9 100644
--- a/security/apparmor/Makefile
+++ b/security/apparmor/Makefile
@@ -4,9 +4,9 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o
apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \
path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \
- resource.o sid.o file.o
+ resource.o sid.o file.o net.o
-clean-files := capability_names.h rlim_names.h
+clean-files := capability_names.h rlim_names.h af_names.h
# Build a lower case string table of capability names
@@ -44,9 +44,24 @@ cmd_make-rlim = echo "static const char *rlim_names[] = {" > $@ ;\
sed -r -n "s/^\# ?define[ \t]+(RLIMIT_[A-Z0-9_]+).*/\1,/p" $< >> $@ ;\
echo "};" >> $@
+# Build a lower case string table of address family names.
+# Transform lines from
+# #define AF_INET 2 /* Internet IP Protocol */
+# to
+# [2] = "inet",
+quiet_cmd_make-af = GEN $@
+cmd_make-af = echo "static const char *address_family_names[] = {" > $@ ;\
+ sed $< >> $@ -r -n -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e \
+ 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+).*/[\2] = "\L\1",/p';\
+ echo "};" >> $@
+
+
$(obj)/capability.o : $(obj)/capability_names.h
$(obj)/resource.o : $(obj)/rlim_names.h
+$(obj)/net.o : $(obj)/af_names.h
$(obj)/capability_names.h : $(srctree)/include/linux/capability.h
$(call cmd,make-caps)
$(obj)/rlim_names.h : $(srctree)/include/asm-generic/resource.h
$(call cmd,make-rlim)
+$(obj)/af_names.h : $(srctree)/include/linux/socket.h
+ $(call cmd,make-af)
\ No newline at end of file
diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h
new file mode 100644
index 0000000..3c7d599
--- /dev/null
+++ b/security/apparmor/include/net.h
@@ -0,0 +1,40 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor network mediation definitions.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 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.
+ */
+
+#ifndef __AA_NET_H
+#define __AA_NET_H
+
+#include <net/sock.h>
+
+/* struct aa_net - network confinement data
+ * @allowed: basic network families permissions
+ * @audit_network: which network permissions to force audit
+ * @quiet_network: which network permissions to quiet rejects
+ */
+struct aa_net {
+ u16 allow[AF_MAX];
+ u16 audit[AF_MAX];
+ u16 quiet[AF_MAX];
+};
+
+extern int aa_net_perm(int op, struct aa_profile *profile, u16 family,
+ int type, int protocol, struct sock *sk);
+extern int aa_revalidate_sk(int op, struct sock *sk);
+
+static inline void aa_free_net_rules(struct aa_net *new)
+{
+ /* NOP */
+}
+
+#endif /* __AA_NET_H */
diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h
index aeda5cf..6776929 100644
--- a/security/apparmor/include/policy.h
+++ b/security/apparmor/include/policy.h
@@ -27,6 +27,7 @@
#include "capability.h"
#include "domain.h"
#include "file.h"
+#include "net.h"
#include "resource.h"
extern const char *profile_mode_names[];
@@ -145,6 +146,7 @@ struct aa_namespace {
* @size: the memory consumed by this profiles rules
* @file: The set of rules governing basic file access and domain transitions
* @caps: capabilities for the profile
+ * @net: network controls for the profile
* @rlimits: rlimits for the profile
*
* The AppArmor profile contains the basic confinement data. Each profile
@@ -181,6 +183,7 @@ struct aa_profile {
struct aa_file_rules file;
struct aa_caps caps;
+ struct aa_net net;
struct aa_rlimit rlimits;
};
diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index ae3a698..05c018b 100644
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -32,6 +32,7 @@
#include "include/context.h"
#include "include/file.h"
#include "include/ipc.h"
+#include "include/net.h"
#include "include/path.h"
#include "include/policy.h"
#include "include/procattr.h"
@@ -620,6 +621,104 @@ static int apparmor_task_setrlimit(struct task_struct *task,
return error;
}
+static int apparmor_socket_create(int family, int type, int protocol, int kern)
+{
+ struct aa_profile *profile;
+ int error = 0;
+
+ if (kern)
+ return 0;
+
+ profile = __aa_current_profile();
+ if (!unconfined(profile))
+ error = aa_net_perm(OP_CREATE, profile, family, type, protocol,
+ NULL);
+ return error;
+}
+
+static int apparmor_socket_bind(struct socket *sock,
+ struct sockaddr *address, int addrlen)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(OP_BIND, sk);
+}
+
+static int apparmor_socket_connect(struct socket *sock,
+ struct sockaddr *address, int addrlen)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(OP_CONNECT, sk);
+}
+
+static int apparmor_socket_listen(struct socket *sock, int backlog)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(OP_LISTEN, sk);
+}
+
+static int apparmor_socket_accept(struct socket *sock, struct socket *newsock)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(OP_ACCEPT, sk);
+}
+
+static int apparmor_socket_sendmsg(struct socket *sock,
+ struct msghdr *msg, int size)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(OP_SENDMSG, sk);
+}
+
+static int apparmor_socket_recvmsg(struct socket *sock,
+ struct msghdr *msg, int size, int flags)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(OP_RECVMSG, sk);
+}
+
+static int apparmor_socket_getsockname(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(OP_GETSOCKNAME, sk);
+}
+
+static int apparmor_socket_getpeername(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(OP_GETPEERNAME, sk);
+}
+
+static int apparmor_socket_getsockopt(struct socket *sock, int level,
+ int optname)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(OP_GETSOCKOPT, sk);
+}
+
+static int apparmor_socket_setsockopt(struct socket *sock, int level,
+ int optname)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(OP_SETSOCKOPT, sk);
+}
+
+static int apparmor_socket_shutdown(struct socket *sock, int how)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(OP_SOCK_SHUTDOWN, sk);
+}
+
static struct security_operations apparmor_ops = {
.name = "apparmor",
@@ -651,6 +750,19 @@ static struct security_operations apparmor_ops = {
.getprocattr = apparmor_getprocattr,
.setprocattr = apparmor_setprocattr,
+ .socket_create = apparmor_socket_create,
+ .socket_bind = apparmor_socket_bind,
+ .socket_connect = apparmor_socket_connect,
+ .socket_listen = apparmor_socket_listen,
+ .socket_accept = apparmor_socket_accept,
+ .socket_sendmsg = apparmor_socket_sendmsg,
+ .socket_recvmsg = apparmor_socket_recvmsg,
+ .socket_getsockname = apparmor_socket_getsockname,
+ .socket_getpeername = apparmor_socket_getpeername,
+ .socket_getsockopt = apparmor_socket_getsockopt,
+ .socket_setsockopt = apparmor_socket_setsockopt,
+ .socket_shutdown = apparmor_socket_shutdown,
+
.cred_alloc_blank = apparmor_cred_alloc_blank,
.cred_free = apparmor_cred_free,
.cred_prepare = apparmor_cred_prepare,
diff --git a/security/apparmor/net.c b/security/apparmor/net.c
new file mode 100644
index 0000000..1765901
--- /dev/null
+++ b/security/apparmor/net.c
@@ -0,0 +1,170 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor network mediation
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 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.
+ */
+
+#include "include/apparmor.h"
+#include "include/audit.h"
+#include "include/context.h"
+#include "include/net.h"
+#include "include/policy.h"
+
+#include "af_names.h"
+
+static const char *sock_type_names[] = {
+ "unknown(0)",
+ "stream",
+ "dgram",
+ "raw",
+ "rdm",
+ "seqpacket",
+ "dccp",
+ "unknown(7)",
+ "unknown(8)",
+ "unknown(9)",
+ "packet",
+};
+
+/* audit callback for net specific fields */
+static void audit_cb(struct audit_buffer *ab, void *va)
+{
+ struct common_audit_data *sa = va;
+
+ audit_log_format(ab, " family=");
+ if (address_family_names[sa->u.net.family]) {
+ audit_log_string(ab, address_family_names[sa->u.net.family]);
+ } else {
+ audit_log_format(ab, " \"unknown(%d)\"", sa->u.net.family);
+ }
+
+ audit_log_format(ab, " sock_type=");
+ if (sock_type_names[sa->aad.net.type]) {
+ audit_log_string(ab, sock_type_names[sa->aad.net.type]);
+ } else {
+ audit_log_format(ab, "\"unknown(%d)\"", sa->aad.net.type);
+ }
+
+ audit_log_format(ab, " protocol=%d", sa->aad.net.protocol);
+}
+
+/**
+ * audit_net - audit network access
+ * @profile: profile being enforced (NOT NULL)
+ * @op: operation being checked
+ * @family: network family
+ * @type: network type
+ * @protocol: network protocol
+ * @sk: socket auditing is being applied to
+ * @error: error code for failure else 0
+ *
+ * Returns: %0 or sa->error else other errorcode on failure
+ */
+static int audit_net(struct aa_profile *profile, int op, u16 family, int type,
+ int protocol, struct sock *sk, int error)
+{
+ int audit_type = AUDIT_APPARMOR_AUTO;
+ struct common_audit_data sa;
+ if (sk) {
+ COMMON_AUDIT_DATA_INIT(&sa, NET);
+ } else {
+ COMMON_AUDIT_DATA_INIT(&sa, NONE);
+ }
+ /* todo fill in socket addr info */
+
+ sa.aad.op = op,
+ sa.u.net.family = family;
+ sa.u.net.sk = sk;
+ sa.aad.net.type = type;
+ sa.aad.net.protocol = protocol;
+ sa.aad.error = error;
+
+ if (likely(!sa.aad.error)) {
+ u16 audit_mask = profile->net.audit[sa.u.net.family];
+ if (likely((AUDIT_MODE(profile) != AUDIT_ALL) &&
+ !(1 << sa.aad.net.type & audit_mask)))
+ return 0;
+ audit_type = AUDIT_APPARMOR_AUDIT;
+ } else {
+ u16 quiet_mask = profile->net.quiet[sa.u.net.family];
+ u16 kill_mask = 0;
+ u16 denied = (1 << sa.aad.net.type) & ~quiet_mask;
+
+ if (denied & kill_mask)
+ audit_type = AUDIT_APPARMOR_KILL;
+
+ if ((denied & quiet_mask) &&
+ AUDIT_MODE(profile) != AUDIT_NOQUIET &&
+ AUDIT_MODE(profile) != AUDIT_ALL)
+ return COMPLAIN_MODE(profile) ? 0 : sa.aad.error;
+ }
+
+ return aa_audit(audit_type, profile, GFP_KERNEL, &sa, audit_cb);
+}
+
+/**
+ * aa_net_perm - very course network access check
+ * @op: operation being checked
+ * @profile: profile being enforced (NOT NULL)
+ * @family: network family
+ * @type: network type
+ * @protocol: network protocol
+ *
+ * Returns: %0 else error if permission denied
+ */
+int aa_net_perm(int op, struct aa_profile *profile, u16 family, int type,
+ int protocol, struct sock *sk)
+{
+ u16 family_mask;
+ int error;
+
+ if ((family < 0) || (family >= AF_MAX))
+ return -EINVAL;
+
+ if ((type < 0) || (type >= SOCK_MAX))
+ return -EINVAL;
+
+ /* unix domain and netlink sockets are handled by ipc */
+ if (family == AF_UNIX || family == AF_NETLINK)
+ return 0;
+
+ family_mask = profile->net.allow[family];
+
+ error = (family_mask & (1 << type)) ? 0 : -EACCES;
+
+ return audit_net(profile, op, family, type, protocol, sk, error);
+}
+
+/**
+ * aa_revalidate_sk - Revalidate access to a sock
+ * @op: operation being checked
+ * @sk: sock being revalidated (NOT NULL)
+ *
+ * Returns: %0 else error if permission denied
+ */
+int aa_revalidate_sk(int op, struct sock *sk)
+{
+ struct aa_profile *profile;
+ int error = 0;
+
+ /* aa_revalidate_sk should not be called from interrupt context
+ * don't mediate these calls as they are not task related
+ */
+ if (in_interrupt())
+ return 0;
+
+ profile = __aa_current_profile();
+ if (!unconfined(profile))
+ error = aa_net_perm(op, profile, sk->sk_family, sk->sk_type,
+ sk->sk_protocol, sk);
+
+ return error;
+}
diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c
index 4f0eade..4d5ce13 100644
--- a/security/apparmor/policy.c
+++ b/security/apparmor/policy.c
@@ -745,6 +745,7 @@ static void free_profile(struct aa_profile *profile)
aa_free_file_rules(&profile->file);
aa_free_cap_rules(&profile->caps);
+ aa_free_net_rules(&profile->net);
aa_free_rlimit_rules(&profile->rlimits);
aa_free_sid(profile->sid);
diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c
index e33aaf7..fa3f1b4 100644
--- a/security/apparmor/policy_unpack.c
+++ b/security/apparmor/policy_unpack.c
@@ -190,6 +190,19 @@ fail:
return 0;
}
+static bool unpack_u16(struct aa_ext *e, u16 *data, const char *name)
+{
+ if (unpack_nameX(e, AA_U16, name)) {
+ if (!inbounds(e, sizeof(u16)))
+ return 0;
+ if (data)
+ *data = le16_to_cpu(get_unaligned((u16 *) e->pos));
+ e->pos += sizeof(u16);
+ return 1;
+ }
+ return 0;
+}
+
static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name)
{
if (unpack_nameX(e, AA_U32, name)) {
@@ -468,7 +481,8 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
{
struct aa_profile *profile = NULL;
const char *name = NULL;
- int error = -EPROTO;
+ size_t size = 0;
+ int i, error = -EPROTO;
kernel_cap_t tmpcap;
u32 tmp;
@@ -559,6 +573,38 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
if (!unpack_rlimits(e, profile))
goto fail;
+ size = unpack_array(e, "net_allowed_af");
+ if (size) {
+
+ for (i = 0; i < size; i++) {
+ /* discard extraneous rules that this kernel will
+ * never request
+ */
+ if (i > AF_MAX) {
+ u16 tmp;
+ if (!unpack_u16(e, &tmp, NULL) ||
+ !unpack_u16(e, &tmp, NULL) ||
+ !unpack_u16(e, &tmp, NULL))
+ goto fail;
+ continue;
+ }
+ if (!unpack_u16(e, &profile->net.allow[i], NULL))
+ goto fail;
+ if (!unpack_u16(e, &profile->net.audit[i], NULL))
+ goto fail;
+ if (!unpack_u16(e, &profile->net.quiet[i], NULL))
+ goto fail;
+ }
+ if (!unpack_nameX(e, AA_ARRAYEND, NULL))
+ goto fail;
+ /*
+ * allow unix domain and netlink sockets they are handled
+ * by IPC
+ */
+ }
+ profile->net.allow[AF_UNIX] = 0xffff;
+ profile->net.allow[AF_NETLINK] = 0xffff;
+
/* get file rules */
profile->file.dfa = unpack_dfa(e);
if (IS_ERR(profile->file.dfa)) {
--
1.7.0.4

View File

@@ -0,0 +1,391 @@
From cdc6b35345e5bcfe92bb2b52ef003f94ceedd40d Mon Sep 17 00:00:00 2001
From: John Johansen <john.johansen@canonical.com>
Date: Thu, 22 Jul 2010 02:32:02 -0700
Subject: [PATCH 2/3] AppArmor: compatibility patch for v5 interface
Signed-off-by: John Johansen <john.johansen@canonical.com>
---
security/apparmor/Kconfig | 9 +
security/apparmor/Makefile | 1 +
security/apparmor/apparmorfs-24.c | 287 ++++++++++++++++++++++++++++++++
security/apparmor/apparmorfs.c | 18 ++-
security/apparmor/include/apparmorfs.h | 6 +
5 files changed, 319 insertions(+), 2 deletions(-)
create mode 100644 security/apparmor/apparmorfs-24.c
diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig
index 9b9013b..51ebf96 100644
--- a/security/apparmor/Kconfig
+++ b/security/apparmor/Kconfig
@@ -29,3 +29,12 @@ config SECURITY_APPARMOR_BOOTPARAM_VALUE
boot.
If you are unsure how to answer this question, answer 1.
+
+config SECURITY_APPARMOR_COMPAT_24
+ bool "Enable AppArmor 2.4 compatability"
+ depends on SECURITY_APPARMOR
+ default y
+ help
+ This option enables compatability with AppArmor 2.4. It is
+ recommended if compatability with older versions of AppArmor
+ is desired.
diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile
index 7cefef9..0bb604b 100644
--- a/security/apparmor/Makefile
+++ b/security/apparmor/Makefile
@@ -5,6 +5,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o
apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \
path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \
resource.o sid.o file.o net.o
+apparmor-$(CONFIG_SECURITY_APPARMOR_COMPAT_24) += apparmorfs-24.o
clean-files := capability_names.h rlim_names.h af_names.h
diff --git a/security/apparmor/apparmorfs-24.c b/security/apparmor/apparmorfs-24.c
new file mode 100644
index 0000000..dc8c744
--- /dev/null
+++ b/security/apparmor/apparmorfs-24.c
@@ -0,0 +1,287 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor /sys/kernel/secrutiy/apparmor interface functions
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 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 file contain functions providing an interface for <= AppArmor 2.4
+ * compatibility. It is dependent on CONFIG_SECURITY_APPARMOR_COMPAT_24
+ * being set (see Makefile).
+ */
+
+#include <linux/security.h>
+#include <linux/vmalloc.h>
+#include <linux/module.h>
+#include <linux/seq_file.h>
+#include <linux/uaccess.h>
+#include <linux/namei.h>
+
+#include "include/apparmor.h"
+#include "include/audit.h"
+#include "include/context.h"
+#include "include/policy.h"
+
+
+/* apparmor/matching */
+static ssize_t aa_matching_read(struct file *file, char __user *buf,
+ size_t size, loff_t *ppos)
+{
+ const char matching[] = "pattern=aadfa audit perms=crwxamlk/ "
+ "user::other";
+
+ return simple_read_from_buffer(buf, size, ppos, matching,
+ sizeof(matching) - 1);
+}
+
+const struct file_operations aa_fs_matching_fops = {
+ .read = aa_matching_read,
+};
+
+/* apparmor/features */
+static ssize_t aa_features_read(struct file *file, char __user *buf,
+ size_t size, loff_t *ppos)
+{
+ const char features[] = "file=3.1 capability=2.0 network=1.0 "
+ "change_hat=1.5 change_profile=1.1 " "aanamespaces=1.1 rlimit=1.1";
+
+ return simple_read_from_buffer(buf, size, ppos, features,
+ sizeof(features) - 1);
+}
+
+const struct file_operations aa_fs_features_fops = {
+ .read = aa_features_read,
+};
+
+/**
+ * __next_namespace - find the next namespace to list
+ * @root: root namespace to stop search at (NOT NULL)
+ * @ns: current ns position (NOT NULL)
+ *
+ * Find the next namespace from @ns under @root and handle all locking needed
+ * while switching current namespace.
+ *
+ * Returns: next namespace or NULL if at last namespace under @root
+ * NOTE: will not unlock root->lock
+ */
+static struct aa_namespace *__next_namespace(struct aa_namespace *root,
+ struct aa_namespace *ns)
+{
+ struct aa_namespace *parent;
+
+ /* is next namespace a child */
+ if (!list_empty(&ns->sub_ns)) {
+ struct aa_namespace *next;
+ next = list_first_entry(&ns->sub_ns, typeof(*ns), base.list);
+ read_lock(&next->lock);
+ return next;
+ }
+
+ /* check if the next ns is a sibling, parent, gp, .. */
+ parent = ns->parent;
+ while (parent) {
+ read_unlock(&ns->lock);
+ list_for_each_entry_continue(ns, &parent->sub_ns, base.list) {
+ read_lock(&ns->lock);
+ return ns;
+ }
+ if (parent == root)
+ return NULL;
+ ns = parent;
+ parent = parent->parent;
+ }
+
+ return NULL;
+}
+
+/**
+ * __first_profile - find the first profile in a namespace
+ * @root: namespace that is root of profiles being displayed (NOT NULL)
+ * @ns: namespace to start in (NOT NULL)
+ *
+ * Returns: unrefcounted profile or NULL if no profile
+ */
+static struct aa_profile *__first_profile(struct aa_namespace *root,
+ struct aa_namespace *ns)
+{
+ for ( ; ns; ns = __next_namespace(root, ns)) {
+ if (!list_empty(&ns->base.profiles))
+ return list_first_entry(&ns->base.profiles,
+ struct aa_profile, base.list);
+ }
+ return NULL;
+}
+
+/**
+ * __next_profile - step to the next profile in a profile tree
+ * @profile: current profile in tree (NOT NULL)
+ *
+ * Perform a depth first taversal on the profile tree in a namespace
+ *
+ * Returns: next profile or NULL if done
+ * Requires: profile->ns.lock to be held
+ */
+static struct aa_profile *__next_profile(struct aa_profile *p)
+{
+ struct aa_profile *parent;
+ struct aa_namespace *ns = p->ns;
+
+ /* is next profile a child */
+ if (!list_empty(&p->base.profiles))
+ return list_first_entry(&p->base.profiles, typeof(*p),
+ base.list);
+
+ /* is next profile a sibling, parent sibling, gp, subling, .. */
+ parent = p->parent;
+ while (parent) {
+ list_for_each_entry_continue(p, &parent->base.profiles,
+ base.list)
+ return p;
+ p = parent;
+ parent = parent->parent;
+ }
+
+ /* is next another profile in the namespace */
+ list_for_each_entry_continue(p, &ns->base.profiles, base.list)
+ return p;
+
+ return NULL;
+}
+
+/**
+ * next_profile - step to the next profile in where ever it may be
+ * @root: root namespace (NOT NULL)
+ * @profile: current profile (NOT NULL)
+ *
+ * Returns: next profile or NULL if there isn't one
+ */
+static struct aa_profile *next_profile(struct aa_namespace *root,
+ struct aa_profile *profile)
+{
+ struct aa_profile *next = __next_profile(profile);
+ if (next)
+ return next;
+
+ /* finished all profiles in namespace move to next namespace */
+ return __first_profile(root, __next_namespace(root, profile->ns));
+}
+
+/**
+ * p_start - start a depth first traversal of profile tree
+ * @f: seq_file to fill
+ * @pos: current position
+ *
+ * Returns: first profile under current namespace or NULL if none found
+ *
+ * acquires first ns->lock
+ */
+static void *p_start(struct seq_file *f, loff_t *pos)
+ __acquires(root->lock)
+{
+ struct aa_profile *profile = NULL;
+ struct aa_namespace *root = aa_current_profile()->ns;
+ loff_t l = *pos;
+ f->private = aa_get_namespace(root);
+
+
+ /* find the first profile */
+ read_lock(&root->lock);
+ profile = __first_profile(root, root);
+
+ /* skip to position */
+ for (; profile && l > 0; l--)
+ profile = next_profile(root, profile);
+
+ return profile;
+}
+
+/**
+ * p_next - read the next profile entry
+ * @f: seq_file to fill
+ * @p: profile previously returned
+ * @pos: current position
+ *
+ * Returns: next profile after @p or NULL if none
+ *
+ * may acquire/release locks in namespace tree as necessary
+ */
+static void *p_next(struct seq_file *f, void *p, loff_t *pos)
+{
+ struct aa_profile *profile = p;
+ struct aa_namespace *root = f->private;
+ (*pos)++;
+
+ return next_profile(root, profile);
+}
+
+/**
+ * p_stop - stop depth first traversal
+ * @f: seq_file we are filling
+ * @p: the last profile writen
+ *
+ * Release all locking done by p_start/p_next on namespace tree
+ */
+static void p_stop(struct seq_file *f, void *p)
+ __releases(root->lock)
+{
+ struct aa_profile *profile = p;
+ struct aa_namespace *root = f->private, *ns;
+
+ if (profile) {
+ for (ns = profile->ns; ns && ns != root; ns = ns->parent)
+ read_unlock(&ns->lock);
+ }
+ read_unlock(&root->lock);
+ aa_put_namespace(root);
+}
+
+/**
+ * seq_show_profile - show a profile entry
+ * @f: seq_file to file
+ * @p: current position (profile) (NOT NULL)
+ *
+ * Returns: error on failure
+ */
+static int seq_show_profile(struct seq_file *f, void *p)
+{
+ struct aa_profile *profile = (struct aa_profile *)p;
+ struct aa_namespace *root = f->private;
+
+ if (profile->ns != root)
+ seq_printf(f, ":%s://", aa_ns_name(root, profile->ns));
+ seq_printf(f, "%s (%s)\n", profile->base.hname,
+ COMPLAIN_MODE(profile) ? "complain" : "enforce");
+
+ return 0;
+}
+
+static const struct seq_operations aa_fs_profiles_op = {
+ .start = p_start,
+ .next = p_next,
+ .stop = p_stop,
+ .show = seq_show_profile,
+};
+
+static int profiles_open(struct inode *inode, struct file *file)
+{
+ return seq_open(file, &aa_fs_profiles_op);
+}
+
+static int profiles_release(struct inode *inode, struct file *file)
+{
+ return seq_release(inode, file);
+}
+
+const struct file_operations aa_fs_profiles_fops = {
+ .open = profiles_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = profiles_release,
+};
diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c
index 0848292..28c52ac 100644
--- a/security/apparmor/apparmorfs.c
+++ b/security/apparmor/apparmorfs.c
@@ -187,7 +187,11 @@ void __init aa_destroy_aafs(void)
aafs_remove(".remove");
aafs_remove(".replace");
aafs_remove(".load");
-
+#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24
+ aafs_remove("profiles");
+ aafs_remove("matching");
+ aafs_remove("features");
+#endif
securityfs_remove(aa_fs_dentry);
aa_fs_dentry = NULL;
}
@@ -218,7 +222,17 @@ int __init aa_create_aafs(void)
aa_fs_dentry = NULL;
goto error;
}
-
+#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24
+ error = aafs_create("matching", 0444, &aa_fs_matching_fops);
+ if (error)
+ goto error;
+ error = aafs_create("features", 0444, &aa_fs_features_fops);
+ if (error)
+ goto error;
+#endif
+ error = aafs_create("profiles", 0440, &aa_fs_profiles_fops);
+ if (error)
+ goto error;
error = aafs_create(".load", 0640, &aa_fs_profile_load);
if (error)
goto error;
diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h
index cb1e93a..14f955c 100644
--- a/security/apparmor/include/apparmorfs.h
+++ b/security/apparmor/include/apparmorfs.h
@@ -17,4 +17,10 @@
extern void __init aa_destroy_aafs(void);
+#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24
+extern const struct file_operations aa_fs_matching_fops;
+extern const struct file_operations aa_fs_features_fops;
+extern const struct file_operations aa_fs_profiles_fops;
+#endif
+
#endif /* __AA_APPARMORFS_H */
--
1.7.0.4

View File

@@ -0,0 +1,68 @@
From f17b28f64b963c47e76737f7bb7f58ce3a7c5249 Mon Sep 17 00:00:00 2001
From: John Johansen <john.johansen@canonical.com>
Date: Tue, 20 Jul 2010 06:57:08 -0700
Subject: [PATCH 3/3] AppArmor: Allow dfa backward compatibility with broken userspace
The apparmor_parser when compiling policy could generate invalid dfas
that did not have sufficient padding to avoid invalid references, when
used by the kernel. The kernels check to verify the next/check table
size was broken meaning invalid dfas were being created by userspace
and not caught.
To remain compatible with old tools that are not fixed, pad the loaded
dfas next/check table. The dfa's themselves are valid except for the
high padding for potentially invalid transitions (high bounds error),
which have a maximimum is 256 entries. So just allocate an extra null filled
256 entries for the next/check tables. This will guarentee all bounds
are good and invalid transitions go to the null (0) state.
Signed-off-by: John Johansen <john.johansen@canonical.com>
---
security/apparmor/match.c | 17 +++++++++++++++++
1 files changed, 17 insertions(+), 0 deletions(-)
diff --git a/security/apparmor/match.c b/security/apparmor/match.c
index 06d764c..cf92856 100644
--- a/security/apparmor/match.c
+++ b/security/apparmor/match.c
@@ -57,8 +57,17 @@ static struct table_header *unpack_table(char *blob, size_t bsize)
if (bsize < tsize)
goto out;
+ /* Pad table allocation for next/check by 256 entries to remain
+ * backwards compatible with old (buggy) tools and remain safe without
+ * run time checks
+ */
+ if (th.td_id == YYTD_ID_NXT || th.td_id == YYTD_ID_CHK)
+ tsize += 256 * th.td_flags;
+
table = kvmalloc(tsize);
if (table) {
+ /* ensure the pad is clear, else there will be errors */
+ memset(table, 0, tsize);
*table = th;
if (th.td_flags == YYTD_DATA8)
UNPACK_ARRAY(table->td_data, blob, th.td_lolen,
@@ -134,11 +143,19 @@ static int verify_dfa(struct aa_dfa *dfa, int flags)
goto out;
if (flags & DFA_FLAG_VERIFY_STATES) {
+ int warning = 0;
for (i = 0; i < state_count; i++) {
if (DEFAULT_TABLE(dfa)[i] >= state_count)
goto out;
/* TODO: do check that DEF state recursion terminates */
if (BASE_TABLE(dfa)[i] + 255 >= trans_count) {
+ if (warning)
+ continue;
+ printk(KERN_WARNING "AppArmor DFA next/check "
+ "upper bounds error fixed, upgrade "
+ "user space tools \n");
+ warning = 1;
+ } else if (BASE_TABLE(dfa)[i] >= trans_count) {
printk(KERN_ERR "AppArmor DFA next/check upper "
"bounds error\n");
goto out;
--
1.7.0.4

View File

@@ -0,0 +1,553 @@
From dc13dec93dbd04bfa7a9ba67df1b8ed3431d8d48 Mon Sep 17 00:00:00 2001
From: John Johansen <john.johansen@canonical.com>
Date: Wed, 10 Aug 2011 22:02:39 -0700
Subject: [PATCH 1/3] AppArmor: compatibility patch for v5 network controll
Add compatibility for v5 network rules.
Signed-off-by: John Johansen <john.johansen@canonical.com>
---
include/linux/lsm_audit.h | 4 +
security/apparmor/Makefile | 19 ++++-
security/apparmor/include/net.h | 40 +++++++++
security/apparmor/include/policy.h | 3 +
security/apparmor/lsm.c | 112 +++++++++++++++++++++++
security/apparmor/net.c | 170 ++++++++++++++++++++++++++++++++++++
security/apparmor/policy.c | 1 +
security/apparmor/policy_unpack.c | 48 ++++++++++-
8 files changed, 394 insertions(+), 3 deletions(-)
create mode 100644 security/apparmor/include/net.h
create mode 100644 security/apparmor/net.c
diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h
index 88e78de..c63979a 100644
--- a/include/linux/lsm_audit.h
+++ b/include/linux/lsm_audit.h
@@ -124,6 +124,10 @@ struct common_audit_data {
u32 denied;
uid_t ouid;
} fs;
+ struct {
+ int type, protocol;
+ struct sock *sk;
+ } net;
};
} apparmor_audit_data;
#endif
diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile
index 2dafe50..7cefef9 100644
--- a/security/apparmor/Makefile
+++ b/security/apparmor/Makefile
@@ -4,9 +4,9 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o
apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \
path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \
- resource.o sid.o file.o
+ resource.o sid.o file.o net.o
-clean-files := capability_names.h rlim_names.h
+clean-files := capability_names.h rlim_names.h af_names.h
# Build a lower case string table of capability names
@@ -44,9 +44,24 @@ cmd_make-rlim = echo "static const char *rlim_names[] = {" > $@ ;\
sed -r -n "s/^\# ?define[ \t]+(RLIMIT_[A-Z0-9_]+).*/\1,/p" $< >> $@ ;\
echo "};" >> $@
+# Build a lower case string table of address family names.
+# Transform lines from
+# #define AF_INET 2 /* Internet IP Protocol */
+# to
+# [2] = "inet",
+quiet_cmd_make-af = GEN $@
+cmd_make-af = echo "static const char *address_family_names[] = {" > $@ ;\
+ sed $< >> $@ -r -n -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e \
+ 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+).*/[\2] = "\L\1",/p';\
+ echo "};" >> $@
+
+
$(obj)/capability.o : $(obj)/capability_names.h
$(obj)/resource.o : $(obj)/rlim_names.h
+$(obj)/net.o : $(obj)/af_names.h
$(obj)/capability_names.h : $(srctree)/include/linux/capability.h
$(call cmd,make-caps)
$(obj)/rlim_names.h : $(srctree)/include/asm-generic/resource.h
$(call cmd,make-rlim)
+$(obj)/af_names.h : $(srctree)/include/linux/socket.h
+ $(call cmd,make-af)
\ No newline at end of file
diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h
new file mode 100644
index 0000000..3c7d599
--- /dev/null
+++ b/security/apparmor/include/net.h
@@ -0,0 +1,40 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor network mediation definitions.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 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.
+ */
+
+#ifndef __AA_NET_H
+#define __AA_NET_H
+
+#include <net/sock.h>
+
+/* struct aa_net - network confinement data
+ * @allowed: basic network families permissions
+ * @audit_network: which network permissions to force audit
+ * @quiet_network: which network permissions to quiet rejects
+ */
+struct aa_net {
+ u16 allow[AF_MAX];
+ u16 audit[AF_MAX];
+ u16 quiet[AF_MAX];
+};
+
+extern int aa_net_perm(int op, struct aa_profile *profile, u16 family,
+ int type, int protocol, struct sock *sk);
+extern int aa_revalidate_sk(int op, struct sock *sk);
+
+static inline void aa_free_net_rules(struct aa_net *new)
+{
+ /* NOP */
+}
+
+#endif /* __AA_NET_H */
diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h
index aeda5cf..6776929 100644
--- a/security/apparmor/include/policy.h
+++ b/security/apparmor/include/policy.h
@@ -27,6 +27,7 @@
#include "capability.h"
#include "domain.h"
#include "file.h"
+#include "net.h"
#include "resource.h"
extern const char *profile_mode_names[];
@@ -145,6 +146,7 @@ struct aa_namespace {
* @size: the memory consumed by this profiles rules
* @file: The set of rules governing basic file access and domain transitions
* @caps: capabilities for the profile
+ * @net: network controls for the profile
* @rlimits: rlimits for the profile
*
* The AppArmor profile contains the basic confinement data. Each profile
@@ -181,6 +183,7 @@ struct aa_profile {
struct aa_file_rules file;
struct aa_caps caps;
+ struct aa_net net;
struct aa_rlimit rlimits;
};
diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index 3d2fd14..aa293ae 100644
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -32,6 +32,7 @@
#include "include/context.h"
#include "include/file.h"
#include "include/ipc.h"
+#include "include/net.h"
#include "include/path.h"
#include "include/policy.h"
#include "include/procattr.h"
@@ -621,6 +622,104 @@ static int apparmor_task_setrlimit(struct task_struct *task,
return error;
}
+static int apparmor_socket_create(int family, int type, int protocol, int kern)
+{
+ struct aa_profile *profile;
+ int error = 0;
+
+ if (kern)
+ return 0;
+
+ profile = __aa_current_profile();
+ if (!unconfined(profile))
+ error = aa_net_perm(OP_CREATE, profile, family, type, protocol,
+ NULL);
+ return error;
+}
+
+static int apparmor_socket_bind(struct socket *sock,
+ struct sockaddr *address, int addrlen)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(OP_BIND, sk);
+}
+
+static int apparmor_socket_connect(struct socket *sock,
+ struct sockaddr *address, int addrlen)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(OP_CONNECT, sk);
+}
+
+static int apparmor_socket_listen(struct socket *sock, int backlog)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(OP_LISTEN, sk);
+}
+
+static int apparmor_socket_accept(struct socket *sock, struct socket *newsock)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(OP_ACCEPT, sk);
+}
+
+static int apparmor_socket_sendmsg(struct socket *sock,
+ struct msghdr *msg, int size)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(OP_SENDMSG, sk);
+}
+
+static int apparmor_socket_recvmsg(struct socket *sock,
+ struct msghdr *msg, int size, int flags)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(OP_RECVMSG, sk);
+}
+
+static int apparmor_socket_getsockname(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(OP_GETSOCKNAME, sk);
+}
+
+static int apparmor_socket_getpeername(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(OP_GETPEERNAME, sk);
+}
+
+static int apparmor_socket_getsockopt(struct socket *sock, int level,
+ int optname)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(OP_GETSOCKOPT, sk);
+}
+
+static int apparmor_socket_setsockopt(struct socket *sock, int level,
+ int optname)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(OP_SETSOCKOPT, sk);
+}
+
+static int apparmor_socket_shutdown(struct socket *sock, int how)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(OP_SOCK_SHUTDOWN, sk);
+}
+
static struct security_operations apparmor_ops = {
.name = "apparmor",
@@ -652,6 +751,19 @@ static struct security_operations apparmor_ops = {
.getprocattr = apparmor_getprocattr,
.setprocattr = apparmor_setprocattr,
+ .socket_create = apparmor_socket_create,
+ .socket_bind = apparmor_socket_bind,
+ .socket_connect = apparmor_socket_connect,
+ .socket_listen = apparmor_socket_listen,
+ .socket_accept = apparmor_socket_accept,
+ .socket_sendmsg = apparmor_socket_sendmsg,
+ .socket_recvmsg = apparmor_socket_recvmsg,
+ .socket_getsockname = apparmor_socket_getsockname,
+ .socket_getpeername = apparmor_socket_getpeername,
+ .socket_getsockopt = apparmor_socket_getsockopt,
+ .socket_setsockopt = apparmor_socket_setsockopt,
+ .socket_shutdown = apparmor_socket_shutdown,
+
.cred_alloc_blank = apparmor_cred_alloc_blank,
.cred_free = apparmor_cred_free,
.cred_prepare = apparmor_cred_prepare,
diff --git a/security/apparmor/net.c b/security/apparmor/net.c
new file mode 100644
index 0000000..1765901
--- /dev/null
+++ b/security/apparmor/net.c
@@ -0,0 +1,170 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor network mediation
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 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.
+ */
+
+#include "include/apparmor.h"
+#include "include/audit.h"
+#include "include/context.h"
+#include "include/net.h"
+#include "include/policy.h"
+
+#include "af_names.h"
+
+static const char *sock_type_names[] = {
+ "unknown(0)",
+ "stream",
+ "dgram",
+ "raw",
+ "rdm",
+ "seqpacket",
+ "dccp",
+ "unknown(7)",
+ "unknown(8)",
+ "unknown(9)",
+ "packet",
+};
+
+/* audit callback for net specific fields */
+static void audit_cb(struct audit_buffer *ab, void *va)
+{
+ struct common_audit_data *sa = va;
+
+ audit_log_format(ab, " family=");
+ if (address_family_names[sa->u.net.family]) {
+ audit_log_string(ab, address_family_names[sa->u.net.family]);
+ } else {
+ audit_log_format(ab, " \"unknown(%d)\"", sa->u.net.family);
+ }
+
+ audit_log_format(ab, " sock_type=");
+ if (sock_type_names[sa->aad.net.type]) {
+ audit_log_string(ab, sock_type_names[sa->aad.net.type]);
+ } else {
+ audit_log_format(ab, "\"unknown(%d)\"", sa->aad.net.type);
+ }
+
+ audit_log_format(ab, " protocol=%d", sa->aad.net.protocol);
+}
+
+/**
+ * audit_net - audit network access
+ * @profile: profile being enforced (NOT NULL)
+ * @op: operation being checked
+ * @family: network family
+ * @type: network type
+ * @protocol: network protocol
+ * @sk: socket auditing is being applied to
+ * @error: error code for failure else 0
+ *
+ * Returns: %0 or sa->error else other errorcode on failure
+ */
+static int audit_net(struct aa_profile *profile, int op, u16 family, int type,
+ int protocol, struct sock *sk, int error)
+{
+ int audit_type = AUDIT_APPARMOR_AUTO;
+ struct common_audit_data sa;
+ if (sk) {
+ COMMON_AUDIT_DATA_INIT(&sa, NET);
+ } else {
+ COMMON_AUDIT_DATA_INIT(&sa, NONE);
+ }
+ /* todo fill in socket addr info */
+
+ sa.aad.op = op,
+ sa.u.net.family = family;
+ sa.u.net.sk = sk;
+ sa.aad.net.type = type;
+ sa.aad.net.protocol = protocol;
+ sa.aad.error = error;
+
+ if (likely(!sa.aad.error)) {
+ u16 audit_mask = profile->net.audit[sa.u.net.family];
+ if (likely((AUDIT_MODE(profile) != AUDIT_ALL) &&
+ !(1 << sa.aad.net.type & audit_mask)))
+ return 0;
+ audit_type = AUDIT_APPARMOR_AUDIT;
+ } else {
+ u16 quiet_mask = profile->net.quiet[sa.u.net.family];
+ u16 kill_mask = 0;
+ u16 denied = (1 << sa.aad.net.type) & ~quiet_mask;
+
+ if (denied & kill_mask)
+ audit_type = AUDIT_APPARMOR_KILL;
+
+ if ((denied & quiet_mask) &&
+ AUDIT_MODE(profile) != AUDIT_NOQUIET &&
+ AUDIT_MODE(profile) != AUDIT_ALL)
+ return COMPLAIN_MODE(profile) ? 0 : sa.aad.error;
+ }
+
+ return aa_audit(audit_type, profile, GFP_KERNEL, &sa, audit_cb);
+}
+
+/**
+ * aa_net_perm - very course network access check
+ * @op: operation being checked
+ * @profile: profile being enforced (NOT NULL)
+ * @family: network family
+ * @type: network type
+ * @protocol: network protocol
+ *
+ * Returns: %0 else error if permission denied
+ */
+int aa_net_perm(int op, struct aa_profile *profile, u16 family, int type,
+ int protocol, struct sock *sk)
+{
+ u16 family_mask;
+ int error;
+
+ if ((family < 0) || (family >= AF_MAX))
+ return -EINVAL;
+
+ if ((type < 0) || (type >= SOCK_MAX))
+ return -EINVAL;
+
+ /* unix domain and netlink sockets are handled by ipc */
+ if (family == AF_UNIX || family == AF_NETLINK)
+ return 0;
+
+ family_mask = profile->net.allow[family];
+
+ error = (family_mask & (1 << type)) ? 0 : -EACCES;
+
+ return audit_net(profile, op, family, type, protocol, sk, error);
+}
+
+/**
+ * aa_revalidate_sk - Revalidate access to a sock
+ * @op: operation being checked
+ * @sk: sock being revalidated (NOT NULL)
+ *
+ * Returns: %0 else error if permission denied
+ */
+int aa_revalidate_sk(int op, struct sock *sk)
+{
+ struct aa_profile *profile;
+ int error = 0;
+
+ /* aa_revalidate_sk should not be called from interrupt context
+ * don't mediate these calls as they are not task related
+ */
+ if (in_interrupt())
+ return 0;
+
+ profile = __aa_current_profile();
+ if (!unconfined(profile))
+ error = aa_net_perm(op, profile, sk->sk_family, sk->sk_type,
+ sk->sk_protocol, sk);
+
+ return error;
+}
diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c
index 4f0eade..4d5ce13 100644
--- a/security/apparmor/policy.c
+++ b/security/apparmor/policy.c
@@ -745,6 +745,7 @@ static void free_profile(struct aa_profile *profile)
aa_free_file_rules(&profile->file);
aa_free_cap_rules(&profile->caps);
+ aa_free_net_rules(&profile->net);
aa_free_rlimit_rules(&profile->rlimits);
aa_free_sid(profile->sid);
diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c
index d6d9a57..f4874c4 100644
--- a/security/apparmor/policy_unpack.c
+++ b/security/apparmor/policy_unpack.c
@@ -190,6 +190,19 @@ fail:
return 0;
}
+static bool unpack_u16(struct aa_ext *e, u16 *data, const char *name)
+{
+ if (unpack_nameX(e, AA_U16, name)) {
+ if (!inbounds(e, sizeof(u16)))
+ return 0;
+ if (data)
+ *data = le16_to_cpu(get_unaligned((u16 *) e->pos));
+ e->pos += sizeof(u16);
+ return 1;
+ }
+ return 0;
+}
+
static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name)
{
if (unpack_nameX(e, AA_U32, name)) {
@@ -468,7 +481,8 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
{
struct aa_profile *profile = NULL;
const char *name = NULL;
- int error = -EPROTO;
+ size_t size = 0;
+ int i, error = -EPROTO;
kernel_cap_t tmpcap;
u32 tmp;
@@ -559,6 +573,38 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
if (!unpack_rlimits(e, profile))
goto fail;
+ size = unpack_array(e, "net_allowed_af");
+ if (size) {
+
+ for (i = 0; i < size; i++) {
+ /* discard extraneous rules that this kernel will
+ * never request
+ */
+ if (i >= AF_MAX) {
+ u16 tmp;
+ if (!unpack_u16(e, &tmp, NULL) ||
+ !unpack_u16(e, &tmp, NULL) ||
+ !unpack_u16(e, &tmp, NULL))
+ goto fail;
+ continue;
+ }
+ if (!unpack_u16(e, &profile->net.allow[i], NULL))
+ goto fail;
+ if (!unpack_u16(e, &profile->net.audit[i], NULL))
+ goto fail;
+ if (!unpack_u16(e, &profile->net.quiet[i], NULL))
+ goto fail;
+ }
+ if (!unpack_nameX(e, AA_ARRAYEND, NULL))
+ goto fail;
+ /*
+ * allow unix domain and netlink sockets they are handled
+ * by IPC
+ */
+ }
+ profile->net.allow[AF_UNIX] = 0xffff;
+ profile->net.allow[AF_NETLINK] = 0xffff;
+
/* get file rules */
profile->file.dfa = unpack_dfa(e);
if (IS_ERR(profile->file.dfa)) {
--
1.7.5.4

View File

@@ -0,0 +1,391 @@
From a2515f25ad5a7833ddc5a032d34eee6a5ddee3a2 Mon Sep 17 00:00:00 2001
From: John Johansen <john.johansen@canonical.com>
Date: Wed, 10 Aug 2011 22:02:40 -0700
Subject: [PATCH 2/3] AppArmor: compatibility patch for v5 interface
Signed-off-by: John Johansen <john.johansen@canonical.com>
---
security/apparmor/Kconfig | 9 +
security/apparmor/Makefile | 1 +
security/apparmor/apparmorfs-24.c | 287 ++++++++++++++++++++++++++++++++
security/apparmor/apparmorfs.c | 18 ++-
security/apparmor/include/apparmorfs.h | 6 +
5 files changed, 319 insertions(+), 2 deletions(-)
create mode 100644 security/apparmor/apparmorfs-24.c
diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig
index 9b9013b..51ebf96 100644
--- a/security/apparmor/Kconfig
+++ b/security/apparmor/Kconfig
@@ -29,3 +29,12 @@ config SECURITY_APPARMOR_BOOTPARAM_VALUE
boot.
If you are unsure how to answer this question, answer 1.
+
+config SECURITY_APPARMOR_COMPAT_24
+ bool "Enable AppArmor 2.4 compatability"
+ depends on SECURITY_APPARMOR
+ default y
+ help
+ This option enables compatability with AppArmor 2.4. It is
+ recommended if compatability with older versions of AppArmor
+ is desired.
diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile
index 7cefef9..0bb604b 100644
--- a/security/apparmor/Makefile
+++ b/security/apparmor/Makefile
@@ -5,6 +5,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o
apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \
path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \
resource.o sid.o file.o net.o
+apparmor-$(CONFIG_SECURITY_APPARMOR_COMPAT_24) += apparmorfs-24.o
clean-files := capability_names.h rlim_names.h af_names.h
diff --git a/security/apparmor/apparmorfs-24.c b/security/apparmor/apparmorfs-24.c
new file mode 100644
index 0000000..dc8c744
--- /dev/null
+++ b/security/apparmor/apparmorfs-24.c
@@ -0,0 +1,287 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor /sys/kernel/secrutiy/apparmor interface functions
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 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 file contain functions providing an interface for <= AppArmor 2.4
+ * compatibility. It is dependent on CONFIG_SECURITY_APPARMOR_COMPAT_24
+ * being set (see Makefile).
+ */
+
+#include <linux/security.h>
+#include <linux/vmalloc.h>
+#include <linux/module.h>
+#include <linux/seq_file.h>
+#include <linux/uaccess.h>
+#include <linux/namei.h>
+
+#include "include/apparmor.h"
+#include "include/audit.h"
+#include "include/context.h"
+#include "include/policy.h"
+
+
+/* apparmor/matching */
+static ssize_t aa_matching_read(struct file *file, char __user *buf,
+ size_t size, loff_t *ppos)
+{
+ const char matching[] = "pattern=aadfa audit perms=crwxamlk/ "
+ "user::other";
+
+ return simple_read_from_buffer(buf, size, ppos, matching,
+ sizeof(matching) - 1);
+}
+
+const struct file_operations aa_fs_matching_fops = {
+ .read = aa_matching_read,
+};
+
+/* apparmor/features */
+static ssize_t aa_features_read(struct file *file, char __user *buf,
+ size_t size, loff_t *ppos)
+{
+ const char features[] = "file=3.1 capability=2.0 network=1.0 "
+ "change_hat=1.5 change_profile=1.1 " "aanamespaces=1.1 rlimit=1.1";
+
+ return simple_read_from_buffer(buf, size, ppos, features,
+ sizeof(features) - 1);
+}
+
+const struct file_operations aa_fs_features_fops = {
+ .read = aa_features_read,
+};
+
+/**
+ * __next_namespace - find the next namespace to list
+ * @root: root namespace to stop search at (NOT NULL)
+ * @ns: current ns position (NOT NULL)
+ *
+ * Find the next namespace from @ns under @root and handle all locking needed
+ * while switching current namespace.
+ *
+ * Returns: next namespace or NULL if at last namespace under @root
+ * NOTE: will not unlock root->lock
+ */
+static struct aa_namespace *__next_namespace(struct aa_namespace *root,
+ struct aa_namespace *ns)
+{
+ struct aa_namespace *parent;
+
+ /* is next namespace a child */
+ if (!list_empty(&ns->sub_ns)) {
+ struct aa_namespace *next;
+ next = list_first_entry(&ns->sub_ns, typeof(*ns), base.list);
+ read_lock(&next->lock);
+ return next;
+ }
+
+ /* check if the next ns is a sibling, parent, gp, .. */
+ parent = ns->parent;
+ while (parent) {
+ read_unlock(&ns->lock);
+ list_for_each_entry_continue(ns, &parent->sub_ns, base.list) {
+ read_lock(&ns->lock);
+ return ns;
+ }
+ if (parent == root)
+ return NULL;
+ ns = parent;
+ parent = parent->parent;
+ }
+
+ return NULL;
+}
+
+/**
+ * __first_profile - find the first profile in a namespace
+ * @root: namespace that is root of profiles being displayed (NOT NULL)
+ * @ns: namespace to start in (NOT NULL)
+ *
+ * Returns: unrefcounted profile or NULL if no profile
+ */
+static struct aa_profile *__first_profile(struct aa_namespace *root,
+ struct aa_namespace *ns)
+{
+ for ( ; ns; ns = __next_namespace(root, ns)) {
+ if (!list_empty(&ns->base.profiles))
+ return list_first_entry(&ns->base.profiles,
+ struct aa_profile, base.list);
+ }
+ return NULL;
+}
+
+/**
+ * __next_profile - step to the next profile in a profile tree
+ * @profile: current profile in tree (NOT NULL)
+ *
+ * Perform a depth first taversal on the profile tree in a namespace
+ *
+ * Returns: next profile or NULL if done
+ * Requires: profile->ns.lock to be held
+ */
+static struct aa_profile *__next_profile(struct aa_profile *p)
+{
+ struct aa_profile *parent;
+ struct aa_namespace *ns = p->ns;
+
+ /* is next profile a child */
+ if (!list_empty(&p->base.profiles))
+ return list_first_entry(&p->base.profiles, typeof(*p),
+ base.list);
+
+ /* is next profile a sibling, parent sibling, gp, subling, .. */
+ parent = p->parent;
+ while (parent) {
+ list_for_each_entry_continue(p, &parent->base.profiles,
+ base.list)
+ return p;
+ p = parent;
+ parent = parent->parent;
+ }
+
+ /* is next another profile in the namespace */
+ list_for_each_entry_continue(p, &ns->base.profiles, base.list)
+ return p;
+
+ return NULL;
+}
+
+/**
+ * next_profile - step to the next profile in where ever it may be
+ * @root: root namespace (NOT NULL)
+ * @profile: current profile (NOT NULL)
+ *
+ * Returns: next profile or NULL if there isn't one
+ */
+static struct aa_profile *next_profile(struct aa_namespace *root,
+ struct aa_profile *profile)
+{
+ struct aa_profile *next = __next_profile(profile);
+ if (next)
+ return next;
+
+ /* finished all profiles in namespace move to next namespace */
+ return __first_profile(root, __next_namespace(root, profile->ns));
+}
+
+/**
+ * p_start - start a depth first traversal of profile tree
+ * @f: seq_file to fill
+ * @pos: current position
+ *
+ * Returns: first profile under current namespace or NULL if none found
+ *
+ * acquires first ns->lock
+ */
+static void *p_start(struct seq_file *f, loff_t *pos)
+ __acquires(root->lock)
+{
+ struct aa_profile *profile = NULL;
+ struct aa_namespace *root = aa_current_profile()->ns;
+ loff_t l = *pos;
+ f->private = aa_get_namespace(root);
+
+
+ /* find the first profile */
+ read_lock(&root->lock);
+ profile = __first_profile(root, root);
+
+ /* skip to position */
+ for (; profile && l > 0; l--)
+ profile = next_profile(root, profile);
+
+ return profile;
+}
+
+/**
+ * p_next - read the next profile entry
+ * @f: seq_file to fill
+ * @p: profile previously returned
+ * @pos: current position
+ *
+ * Returns: next profile after @p or NULL if none
+ *
+ * may acquire/release locks in namespace tree as necessary
+ */
+static void *p_next(struct seq_file *f, void *p, loff_t *pos)
+{
+ struct aa_profile *profile = p;
+ struct aa_namespace *root = f->private;
+ (*pos)++;
+
+ return next_profile(root, profile);
+}
+
+/**
+ * p_stop - stop depth first traversal
+ * @f: seq_file we are filling
+ * @p: the last profile writen
+ *
+ * Release all locking done by p_start/p_next on namespace tree
+ */
+static void p_stop(struct seq_file *f, void *p)
+ __releases(root->lock)
+{
+ struct aa_profile *profile = p;
+ struct aa_namespace *root = f->private, *ns;
+
+ if (profile) {
+ for (ns = profile->ns; ns && ns != root; ns = ns->parent)
+ read_unlock(&ns->lock);
+ }
+ read_unlock(&root->lock);
+ aa_put_namespace(root);
+}
+
+/**
+ * seq_show_profile - show a profile entry
+ * @f: seq_file to file
+ * @p: current position (profile) (NOT NULL)
+ *
+ * Returns: error on failure
+ */
+static int seq_show_profile(struct seq_file *f, void *p)
+{
+ struct aa_profile *profile = (struct aa_profile *)p;
+ struct aa_namespace *root = f->private;
+
+ if (profile->ns != root)
+ seq_printf(f, ":%s://", aa_ns_name(root, profile->ns));
+ seq_printf(f, "%s (%s)\n", profile->base.hname,
+ COMPLAIN_MODE(profile) ? "complain" : "enforce");
+
+ return 0;
+}
+
+static const struct seq_operations aa_fs_profiles_op = {
+ .start = p_start,
+ .next = p_next,
+ .stop = p_stop,
+ .show = seq_show_profile,
+};
+
+static int profiles_open(struct inode *inode, struct file *file)
+{
+ return seq_open(file, &aa_fs_profiles_op);
+}
+
+static int profiles_release(struct inode *inode, struct file *file)
+{
+ return seq_release(inode, file);
+}
+
+const struct file_operations aa_fs_profiles_fops = {
+ .open = profiles_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = profiles_release,
+};
diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c
index 0848292..28c52ac 100644
--- a/security/apparmor/apparmorfs.c
+++ b/security/apparmor/apparmorfs.c
@@ -187,7 +187,11 @@ void __init aa_destroy_aafs(void)
aafs_remove(".remove");
aafs_remove(".replace");
aafs_remove(".load");
-
+#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24
+ aafs_remove("profiles");
+ aafs_remove("matching");
+ aafs_remove("features");
+#endif
securityfs_remove(aa_fs_dentry);
aa_fs_dentry = NULL;
}
@@ -218,7 +222,17 @@ int __init aa_create_aafs(void)
aa_fs_dentry = NULL;
goto error;
}
-
+#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24
+ error = aafs_create("matching", 0444, &aa_fs_matching_fops);
+ if (error)
+ goto error;
+ error = aafs_create("features", 0444, &aa_fs_features_fops);
+ if (error)
+ goto error;
+#endif
+ error = aafs_create("profiles", 0440, &aa_fs_profiles_fops);
+ if (error)
+ goto error;
error = aafs_create(".load", 0640, &aa_fs_profile_load);
if (error)
goto error;
diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h
index cb1e93a..14f955c 100644
--- a/security/apparmor/include/apparmorfs.h
+++ b/security/apparmor/include/apparmorfs.h
@@ -17,4 +17,10 @@
extern void __init aa_destroy_aafs(void);
+#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24
+extern const struct file_operations aa_fs_matching_fops;
+extern const struct file_operations aa_fs_features_fops;
+extern const struct file_operations aa_fs_profiles_fops;
+#endif
+
#endif /* __AA_APPARMORFS_H */
--
1.7.5.4

View File

@@ -0,0 +1,69 @@
From 7a10d093f9779f42cb8d6affcb6a4436d3ebd6d3 Mon Sep 17 00:00:00 2001
From: John Johansen <john.johansen@canonical.com>
Date: Wed, 10 Aug 2011 22:02:41 -0700
Subject: [PATCH 3/3] AppArmor: Allow dfa backward compatibility with broken
userspace
The apparmor_parser when compiling policy could generate invalid dfas
that did not have sufficient padding to avoid invalid references, when
used by the kernel. The kernels check to verify the next/check table
size was broken meaning invalid dfas were being created by userspace
and not caught.
To remain compatible with old tools that are not fixed, pad the loaded
dfas next/check table. The dfa's themselves are valid except for the
high padding for potentially invalid transitions (high bounds error),
which have a maximimum is 256 entries. So just allocate an extra null filled
256 entries for the next/check tables. This will guarentee all bounds
are good and invalid transitions go to the null (0) state.
Signed-off-by: John Johansen <john.johansen@canonical.com>
---
security/apparmor/match.c | 17 +++++++++++++++++
1 files changed, 17 insertions(+), 0 deletions(-)
diff --git a/security/apparmor/match.c b/security/apparmor/match.c
index 94de6b4..081491e 100644
--- a/security/apparmor/match.c
+++ b/security/apparmor/match.c
@@ -57,8 +57,17 @@ static struct table_header *unpack_table(char *blob, size_t bsize)
if (bsize < tsize)
goto out;
+ /* Pad table allocation for next/check by 256 entries to remain
+ * backwards compatible with old (buggy) tools and remain safe without
+ * run time checks
+ */
+ if (th.td_id == YYTD_ID_NXT || th.td_id == YYTD_ID_CHK)
+ tsize += 256 * th.td_flags;
+
table = kvmalloc(tsize);
if (table) {
+ /* ensure the pad is clear, else there will be errors */
+ memset(table, 0, tsize);
*table = th;
if (th.td_flags == YYTD_DATA8)
UNPACK_ARRAY(table->td_data, blob, th.td_lolen,
@@ -134,11 +143,19 @@ static int verify_dfa(struct aa_dfa *dfa, int flags)
goto out;
if (flags & DFA_FLAG_VERIFY_STATES) {
+ int warning = 0;
for (i = 0; i < state_count; i++) {
if (DEFAULT_TABLE(dfa)[i] >= state_count)
goto out;
/* TODO: do check that DEF state recursion terminates */
if (BASE_TABLE(dfa)[i] + 255 >= trans_count) {
+ if (warning)
+ continue;
+ printk(KERN_WARNING "AppArmor DFA next/check "
+ "upper bounds error fixed, upgrade "
+ "user space tools \n");
+ warning = 1;
+ } else if (BASE_TABLE(dfa)[i] >= trans_count) {
printk(KERN_ERR "AppArmor DFA next/check upper "
"bounds error\n");
goto out;
--
1.7.5.4

View File

@@ -0,0 +1,553 @@
From dc13dec93dbd04bfa7a9ba67df1b8ed3431d8d48 Mon Sep 17 00:00:00 2001
From: John Johansen <john.johansen@canonical.com>
Date: Wed, 10 Aug 2011 22:02:39 -0700
Subject: [PATCH 1/3] AppArmor: compatibility patch for v5 network controll
Add compatibility for v5 network rules.
Signed-off-by: John Johansen <john.johansen@canonical.com>
---
include/linux/lsm_audit.h | 4 +
security/apparmor/Makefile | 19 ++++-
security/apparmor/include/net.h | 40 +++++++++
security/apparmor/include/policy.h | 3 +
security/apparmor/lsm.c | 112 +++++++++++++++++++++++
security/apparmor/net.c | 170 ++++++++++++++++++++++++++++++++++++
security/apparmor/policy.c | 1 +
security/apparmor/policy_unpack.c | 48 ++++++++++-
8 files changed, 394 insertions(+), 3 deletions(-)
create mode 100644 security/apparmor/include/net.h
create mode 100644 security/apparmor/net.c
diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h
index 88e78de..c63979a 100644
--- a/include/linux/lsm_audit.h
+++ b/include/linux/lsm_audit.h
@@ -124,6 +124,10 @@ struct common_audit_data {
u32 denied;
uid_t ouid;
} fs;
+ struct {
+ int type, protocol;
+ struct sock *sk;
+ } net;
};
} apparmor_audit_data;
#endif
diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile
index 2dafe50..7cefef9 100644
--- a/security/apparmor/Makefile
+++ b/security/apparmor/Makefile
@@ -4,9 +4,9 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o
apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \
path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \
- resource.o sid.o file.o
+ resource.o sid.o file.o net.o
-clean-files := capability_names.h rlim_names.h
+clean-files := capability_names.h rlim_names.h af_names.h
# Build a lower case string table of capability names
@@ -44,9 +44,24 @@ cmd_make-rlim = echo "static const char *rlim_names[] = {" > $@ ;\
sed -r -n "s/^\# ?define[ \t]+(RLIMIT_[A-Z0-9_]+).*/\1,/p" $< >> $@ ;\
echo "};" >> $@
+# Build a lower case string table of address family names.
+# Transform lines from
+# #define AF_INET 2 /* Internet IP Protocol */
+# to
+# [2] = "inet",
+quiet_cmd_make-af = GEN $@
+cmd_make-af = echo "static const char *address_family_names[] = {" > $@ ;\
+ sed $< >> $@ -r -n -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e \
+ 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+).*/[\2] = "\L\1",/p';\
+ echo "};" >> $@
+
+
$(obj)/capability.o : $(obj)/capability_names.h
$(obj)/resource.o : $(obj)/rlim_names.h
+$(obj)/net.o : $(obj)/af_names.h
$(obj)/capability_names.h : $(srctree)/include/linux/capability.h
$(call cmd,make-caps)
$(obj)/rlim_names.h : $(srctree)/include/asm-generic/resource.h
$(call cmd,make-rlim)
+$(obj)/af_names.h : $(srctree)/include/linux/socket.h
+ $(call cmd,make-af)
\ No newline at end of file
diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h
new file mode 100644
index 0000000..3c7d599
--- /dev/null
+++ b/security/apparmor/include/net.h
@@ -0,0 +1,40 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor network mediation definitions.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 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.
+ */
+
+#ifndef __AA_NET_H
+#define __AA_NET_H
+
+#include <net/sock.h>
+
+/* struct aa_net - network confinement data
+ * @allowed: basic network families permissions
+ * @audit_network: which network permissions to force audit
+ * @quiet_network: which network permissions to quiet rejects
+ */
+struct aa_net {
+ u16 allow[AF_MAX];
+ u16 audit[AF_MAX];
+ u16 quiet[AF_MAX];
+};
+
+extern int aa_net_perm(int op, struct aa_profile *profile, u16 family,
+ int type, int protocol, struct sock *sk);
+extern int aa_revalidate_sk(int op, struct sock *sk);
+
+static inline void aa_free_net_rules(struct aa_net *new)
+{
+ /* NOP */
+}
+
+#endif /* __AA_NET_H */
diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h
index aeda5cf..6776929 100644
--- a/security/apparmor/include/policy.h
+++ b/security/apparmor/include/policy.h
@@ -27,6 +27,7 @@
#include "capability.h"
#include "domain.h"
#include "file.h"
+#include "net.h"
#include "resource.h"
extern const char *profile_mode_names[];
@@ -145,6 +146,7 @@ struct aa_namespace {
* @size: the memory consumed by this profiles rules
* @file: The set of rules governing basic file access and domain transitions
* @caps: capabilities for the profile
+ * @net: network controls for the profile
* @rlimits: rlimits for the profile
*
* The AppArmor profile contains the basic confinement data. Each profile
@@ -181,6 +183,7 @@ struct aa_profile {
struct aa_file_rules file;
struct aa_caps caps;
+ struct aa_net net;
struct aa_rlimit rlimits;
};
diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index 3d2fd14..aa293ae 100644
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -32,6 +32,7 @@
#include "include/context.h"
#include "include/file.h"
#include "include/ipc.h"
+#include "include/net.h"
#include "include/path.h"
#include "include/policy.h"
#include "include/procattr.h"
@@ -621,6 +622,104 @@ static int apparmor_task_setrlimit(struct task_struct *task,
return error;
}
+static int apparmor_socket_create(int family, int type, int protocol, int kern)
+{
+ struct aa_profile *profile;
+ int error = 0;
+
+ if (kern)
+ return 0;
+
+ profile = __aa_current_profile();
+ if (!unconfined(profile))
+ error = aa_net_perm(OP_CREATE, profile, family, type, protocol,
+ NULL);
+ return error;
+}
+
+static int apparmor_socket_bind(struct socket *sock,
+ struct sockaddr *address, int addrlen)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(OP_BIND, sk);
+}
+
+static int apparmor_socket_connect(struct socket *sock,
+ struct sockaddr *address, int addrlen)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(OP_CONNECT, sk);
+}
+
+static int apparmor_socket_listen(struct socket *sock, int backlog)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(OP_LISTEN, sk);
+}
+
+static int apparmor_socket_accept(struct socket *sock, struct socket *newsock)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(OP_ACCEPT, sk);
+}
+
+static int apparmor_socket_sendmsg(struct socket *sock,
+ struct msghdr *msg, int size)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(OP_SENDMSG, sk);
+}
+
+static int apparmor_socket_recvmsg(struct socket *sock,
+ struct msghdr *msg, int size, int flags)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(OP_RECVMSG, sk);
+}
+
+static int apparmor_socket_getsockname(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(OP_GETSOCKNAME, sk);
+}
+
+static int apparmor_socket_getpeername(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(OP_GETPEERNAME, sk);
+}
+
+static int apparmor_socket_getsockopt(struct socket *sock, int level,
+ int optname)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(OP_GETSOCKOPT, sk);
+}
+
+static int apparmor_socket_setsockopt(struct socket *sock, int level,
+ int optname)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(OP_SETSOCKOPT, sk);
+}
+
+static int apparmor_socket_shutdown(struct socket *sock, int how)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(OP_SOCK_SHUTDOWN, sk);
+}
+
static struct security_operations apparmor_ops = {
.name = "apparmor",
@@ -652,6 +751,19 @@ static struct security_operations apparmor_ops = {
.getprocattr = apparmor_getprocattr,
.setprocattr = apparmor_setprocattr,
+ .socket_create = apparmor_socket_create,
+ .socket_bind = apparmor_socket_bind,
+ .socket_connect = apparmor_socket_connect,
+ .socket_listen = apparmor_socket_listen,
+ .socket_accept = apparmor_socket_accept,
+ .socket_sendmsg = apparmor_socket_sendmsg,
+ .socket_recvmsg = apparmor_socket_recvmsg,
+ .socket_getsockname = apparmor_socket_getsockname,
+ .socket_getpeername = apparmor_socket_getpeername,
+ .socket_getsockopt = apparmor_socket_getsockopt,
+ .socket_setsockopt = apparmor_socket_setsockopt,
+ .socket_shutdown = apparmor_socket_shutdown,
+
.cred_alloc_blank = apparmor_cred_alloc_blank,
.cred_free = apparmor_cred_free,
.cred_prepare = apparmor_cred_prepare,
diff --git a/security/apparmor/net.c b/security/apparmor/net.c
new file mode 100644
index 0000000..1765901
--- /dev/null
+++ b/security/apparmor/net.c
@@ -0,0 +1,170 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor network mediation
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 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.
+ */
+
+#include "include/apparmor.h"
+#include "include/audit.h"
+#include "include/context.h"
+#include "include/net.h"
+#include "include/policy.h"
+
+#include "af_names.h"
+
+static const char *sock_type_names[] = {
+ "unknown(0)",
+ "stream",
+ "dgram",
+ "raw",
+ "rdm",
+ "seqpacket",
+ "dccp",
+ "unknown(7)",
+ "unknown(8)",
+ "unknown(9)",
+ "packet",
+};
+
+/* audit callback for net specific fields */
+static void audit_cb(struct audit_buffer *ab, void *va)
+{
+ struct common_audit_data *sa = va;
+
+ audit_log_format(ab, " family=");
+ if (address_family_names[sa->u.net.family]) {
+ audit_log_string(ab, address_family_names[sa->u.net.family]);
+ } else {
+ audit_log_format(ab, " \"unknown(%d)\"", sa->u.net.family);
+ }
+
+ audit_log_format(ab, " sock_type=");
+ if (sock_type_names[sa->aad.net.type]) {
+ audit_log_string(ab, sock_type_names[sa->aad.net.type]);
+ } else {
+ audit_log_format(ab, "\"unknown(%d)\"", sa->aad.net.type);
+ }
+
+ audit_log_format(ab, " protocol=%d", sa->aad.net.protocol);
+}
+
+/**
+ * audit_net - audit network access
+ * @profile: profile being enforced (NOT NULL)
+ * @op: operation being checked
+ * @family: network family
+ * @type: network type
+ * @protocol: network protocol
+ * @sk: socket auditing is being applied to
+ * @error: error code for failure else 0
+ *
+ * Returns: %0 or sa->error else other errorcode on failure
+ */
+static int audit_net(struct aa_profile *profile, int op, u16 family, int type,
+ int protocol, struct sock *sk, int error)
+{
+ int audit_type = AUDIT_APPARMOR_AUTO;
+ struct common_audit_data sa;
+ if (sk) {
+ COMMON_AUDIT_DATA_INIT(&sa, NET);
+ } else {
+ COMMON_AUDIT_DATA_INIT(&sa, NONE);
+ }
+ /* todo fill in socket addr info */
+
+ sa.aad.op = op,
+ sa.u.net.family = family;
+ sa.u.net.sk = sk;
+ sa.aad.net.type = type;
+ sa.aad.net.protocol = protocol;
+ sa.aad.error = error;
+
+ if (likely(!sa.aad.error)) {
+ u16 audit_mask = profile->net.audit[sa.u.net.family];
+ if (likely((AUDIT_MODE(profile) != AUDIT_ALL) &&
+ !(1 << sa.aad.net.type & audit_mask)))
+ return 0;
+ audit_type = AUDIT_APPARMOR_AUDIT;
+ } else {
+ u16 quiet_mask = profile->net.quiet[sa.u.net.family];
+ u16 kill_mask = 0;
+ u16 denied = (1 << sa.aad.net.type) & ~quiet_mask;
+
+ if (denied & kill_mask)
+ audit_type = AUDIT_APPARMOR_KILL;
+
+ if ((denied & quiet_mask) &&
+ AUDIT_MODE(profile) != AUDIT_NOQUIET &&
+ AUDIT_MODE(profile) != AUDIT_ALL)
+ return COMPLAIN_MODE(profile) ? 0 : sa.aad.error;
+ }
+
+ return aa_audit(audit_type, profile, GFP_KERNEL, &sa, audit_cb);
+}
+
+/**
+ * aa_net_perm - very course network access check
+ * @op: operation being checked
+ * @profile: profile being enforced (NOT NULL)
+ * @family: network family
+ * @type: network type
+ * @protocol: network protocol
+ *
+ * Returns: %0 else error if permission denied
+ */
+int aa_net_perm(int op, struct aa_profile *profile, u16 family, int type,
+ int protocol, struct sock *sk)
+{
+ u16 family_mask;
+ int error;
+
+ if ((family < 0) || (family >= AF_MAX))
+ return -EINVAL;
+
+ if ((type < 0) || (type >= SOCK_MAX))
+ return -EINVAL;
+
+ /* unix domain and netlink sockets are handled by ipc */
+ if (family == AF_UNIX || family == AF_NETLINK)
+ return 0;
+
+ family_mask = profile->net.allow[family];
+
+ error = (family_mask & (1 << type)) ? 0 : -EACCES;
+
+ return audit_net(profile, op, family, type, protocol, sk, error);
+}
+
+/**
+ * aa_revalidate_sk - Revalidate access to a sock
+ * @op: operation being checked
+ * @sk: sock being revalidated (NOT NULL)
+ *
+ * Returns: %0 else error if permission denied
+ */
+int aa_revalidate_sk(int op, struct sock *sk)
+{
+ struct aa_profile *profile;
+ int error = 0;
+
+ /* aa_revalidate_sk should not be called from interrupt context
+ * don't mediate these calls as they are not task related
+ */
+ if (in_interrupt())
+ return 0;
+
+ profile = __aa_current_profile();
+ if (!unconfined(profile))
+ error = aa_net_perm(op, profile, sk->sk_family, sk->sk_type,
+ sk->sk_protocol, sk);
+
+ return error;
+}
diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c
index 4f0eade..4d5ce13 100644
--- a/security/apparmor/policy.c
+++ b/security/apparmor/policy.c
@@ -745,6 +745,7 @@ static void free_profile(struct aa_profile *profile)
aa_free_file_rules(&profile->file);
aa_free_cap_rules(&profile->caps);
+ aa_free_net_rules(&profile->net);
aa_free_rlimit_rules(&profile->rlimits);
aa_free_sid(profile->sid);
diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c
index d6d9a57..f4874c4 100644
--- a/security/apparmor/policy_unpack.c
+++ b/security/apparmor/policy_unpack.c
@@ -190,6 +190,19 @@ fail:
return 0;
}
+static bool unpack_u16(struct aa_ext *e, u16 *data, const char *name)
+{
+ if (unpack_nameX(e, AA_U16, name)) {
+ if (!inbounds(e, sizeof(u16)))
+ return 0;
+ if (data)
+ *data = le16_to_cpu(get_unaligned((u16 *) e->pos));
+ e->pos += sizeof(u16);
+ return 1;
+ }
+ return 0;
+}
+
static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name)
{
if (unpack_nameX(e, AA_U32, name)) {
@@ -468,7 +481,8 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
{
struct aa_profile *profile = NULL;
const char *name = NULL;
- int error = -EPROTO;
+ size_t size = 0;
+ int i, error = -EPROTO;
kernel_cap_t tmpcap;
u32 tmp;
@@ -559,6 +573,38 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
if (!unpack_rlimits(e, profile))
goto fail;
+ size = unpack_array(e, "net_allowed_af");
+ if (size) {
+
+ for (i = 0; i < size; i++) {
+ /* discard extraneous rules that this kernel will
+ * never request
+ */
+ if (i >= AF_MAX) {
+ u16 tmp;
+ if (!unpack_u16(e, &tmp, NULL) ||
+ !unpack_u16(e, &tmp, NULL) ||
+ !unpack_u16(e, &tmp, NULL))
+ goto fail;
+ continue;
+ }
+ if (!unpack_u16(e, &profile->net.allow[i], NULL))
+ goto fail;
+ if (!unpack_u16(e, &profile->net.audit[i], NULL))
+ goto fail;
+ if (!unpack_u16(e, &profile->net.quiet[i], NULL))
+ goto fail;
+ }
+ if (!unpack_nameX(e, AA_ARRAYEND, NULL))
+ goto fail;
+ /*
+ * allow unix domain and netlink sockets they are handled
+ * by IPC
+ */
+ }
+ profile->net.allow[AF_UNIX] = 0xffff;
+ profile->net.allow[AF_NETLINK] = 0xffff;
+
/* get file rules */
profile->file.dfa = unpack_dfa(e);
if (IS_ERR(profile->file.dfa)) {
--
1.7.5.4

View File

@@ -0,0 +1,391 @@
From a2515f25ad5a7833ddc5a032d34eee6a5ddee3a2 Mon Sep 17 00:00:00 2001
From: John Johansen <john.johansen@canonical.com>
Date: Wed, 10 Aug 2011 22:02:40 -0700
Subject: [PATCH 2/3] AppArmor: compatibility patch for v5 interface
Signed-off-by: John Johansen <john.johansen@canonical.com>
---
security/apparmor/Kconfig | 9 +
security/apparmor/Makefile | 1 +
security/apparmor/apparmorfs-24.c | 287 ++++++++++++++++++++++++++++++++
security/apparmor/apparmorfs.c | 18 ++-
security/apparmor/include/apparmorfs.h | 6 +
5 files changed, 319 insertions(+), 2 deletions(-)
create mode 100644 security/apparmor/apparmorfs-24.c
diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig
index 9b9013b..51ebf96 100644
--- a/security/apparmor/Kconfig
+++ b/security/apparmor/Kconfig
@@ -29,3 +29,12 @@ config SECURITY_APPARMOR_BOOTPARAM_VALUE
boot.
If you are unsure how to answer this question, answer 1.
+
+config SECURITY_APPARMOR_COMPAT_24
+ bool "Enable AppArmor 2.4 compatability"
+ depends on SECURITY_APPARMOR
+ default y
+ help
+ This option enables compatability with AppArmor 2.4. It is
+ recommended if compatability with older versions of AppArmor
+ is desired.
diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile
index 7cefef9..0bb604b 100644
--- a/security/apparmor/Makefile
+++ b/security/apparmor/Makefile
@@ -5,6 +5,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o
apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \
path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \
resource.o sid.o file.o net.o
+apparmor-$(CONFIG_SECURITY_APPARMOR_COMPAT_24) += apparmorfs-24.o
clean-files := capability_names.h rlim_names.h af_names.h
diff --git a/security/apparmor/apparmorfs-24.c b/security/apparmor/apparmorfs-24.c
new file mode 100644
index 0000000..dc8c744
--- /dev/null
+++ b/security/apparmor/apparmorfs-24.c
@@ -0,0 +1,287 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor /sys/kernel/secrutiy/apparmor interface functions
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 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 file contain functions providing an interface for <= AppArmor 2.4
+ * compatibility. It is dependent on CONFIG_SECURITY_APPARMOR_COMPAT_24
+ * being set (see Makefile).
+ */
+
+#include <linux/security.h>
+#include <linux/vmalloc.h>
+#include <linux/module.h>
+#include <linux/seq_file.h>
+#include <linux/uaccess.h>
+#include <linux/namei.h>
+
+#include "include/apparmor.h"
+#include "include/audit.h"
+#include "include/context.h"
+#include "include/policy.h"
+
+
+/* apparmor/matching */
+static ssize_t aa_matching_read(struct file *file, char __user *buf,
+ size_t size, loff_t *ppos)
+{
+ const char matching[] = "pattern=aadfa audit perms=crwxamlk/ "
+ "user::other";
+
+ return simple_read_from_buffer(buf, size, ppos, matching,
+ sizeof(matching) - 1);
+}
+
+const struct file_operations aa_fs_matching_fops = {
+ .read = aa_matching_read,
+};
+
+/* apparmor/features */
+static ssize_t aa_features_read(struct file *file, char __user *buf,
+ size_t size, loff_t *ppos)
+{
+ const char features[] = "file=3.1 capability=2.0 network=1.0 "
+ "change_hat=1.5 change_profile=1.1 " "aanamespaces=1.1 rlimit=1.1";
+
+ return simple_read_from_buffer(buf, size, ppos, features,
+ sizeof(features) - 1);
+}
+
+const struct file_operations aa_fs_features_fops = {
+ .read = aa_features_read,
+};
+
+/**
+ * __next_namespace - find the next namespace to list
+ * @root: root namespace to stop search at (NOT NULL)
+ * @ns: current ns position (NOT NULL)
+ *
+ * Find the next namespace from @ns under @root and handle all locking needed
+ * while switching current namespace.
+ *
+ * Returns: next namespace or NULL if at last namespace under @root
+ * NOTE: will not unlock root->lock
+ */
+static struct aa_namespace *__next_namespace(struct aa_namespace *root,
+ struct aa_namespace *ns)
+{
+ struct aa_namespace *parent;
+
+ /* is next namespace a child */
+ if (!list_empty(&ns->sub_ns)) {
+ struct aa_namespace *next;
+ next = list_first_entry(&ns->sub_ns, typeof(*ns), base.list);
+ read_lock(&next->lock);
+ return next;
+ }
+
+ /* check if the next ns is a sibling, parent, gp, .. */
+ parent = ns->parent;
+ while (parent) {
+ read_unlock(&ns->lock);
+ list_for_each_entry_continue(ns, &parent->sub_ns, base.list) {
+ read_lock(&ns->lock);
+ return ns;
+ }
+ if (parent == root)
+ return NULL;
+ ns = parent;
+ parent = parent->parent;
+ }
+
+ return NULL;
+}
+
+/**
+ * __first_profile - find the first profile in a namespace
+ * @root: namespace that is root of profiles being displayed (NOT NULL)
+ * @ns: namespace to start in (NOT NULL)
+ *
+ * Returns: unrefcounted profile or NULL if no profile
+ */
+static struct aa_profile *__first_profile(struct aa_namespace *root,
+ struct aa_namespace *ns)
+{
+ for ( ; ns; ns = __next_namespace(root, ns)) {
+ if (!list_empty(&ns->base.profiles))
+ return list_first_entry(&ns->base.profiles,
+ struct aa_profile, base.list);
+ }
+ return NULL;
+}
+
+/**
+ * __next_profile - step to the next profile in a profile tree
+ * @profile: current profile in tree (NOT NULL)
+ *
+ * Perform a depth first taversal on the profile tree in a namespace
+ *
+ * Returns: next profile or NULL if done
+ * Requires: profile->ns.lock to be held
+ */
+static struct aa_profile *__next_profile(struct aa_profile *p)
+{
+ struct aa_profile *parent;
+ struct aa_namespace *ns = p->ns;
+
+ /* is next profile a child */
+ if (!list_empty(&p->base.profiles))
+ return list_first_entry(&p->base.profiles, typeof(*p),
+ base.list);
+
+ /* is next profile a sibling, parent sibling, gp, subling, .. */
+ parent = p->parent;
+ while (parent) {
+ list_for_each_entry_continue(p, &parent->base.profiles,
+ base.list)
+ return p;
+ p = parent;
+ parent = parent->parent;
+ }
+
+ /* is next another profile in the namespace */
+ list_for_each_entry_continue(p, &ns->base.profiles, base.list)
+ return p;
+
+ return NULL;
+}
+
+/**
+ * next_profile - step to the next profile in where ever it may be
+ * @root: root namespace (NOT NULL)
+ * @profile: current profile (NOT NULL)
+ *
+ * Returns: next profile or NULL if there isn't one
+ */
+static struct aa_profile *next_profile(struct aa_namespace *root,
+ struct aa_profile *profile)
+{
+ struct aa_profile *next = __next_profile(profile);
+ if (next)
+ return next;
+
+ /* finished all profiles in namespace move to next namespace */
+ return __first_profile(root, __next_namespace(root, profile->ns));
+}
+
+/**
+ * p_start - start a depth first traversal of profile tree
+ * @f: seq_file to fill
+ * @pos: current position
+ *
+ * Returns: first profile under current namespace or NULL if none found
+ *
+ * acquires first ns->lock
+ */
+static void *p_start(struct seq_file *f, loff_t *pos)
+ __acquires(root->lock)
+{
+ struct aa_profile *profile = NULL;
+ struct aa_namespace *root = aa_current_profile()->ns;
+ loff_t l = *pos;
+ f->private = aa_get_namespace(root);
+
+
+ /* find the first profile */
+ read_lock(&root->lock);
+ profile = __first_profile(root, root);
+
+ /* skip to position */
+ for (; profile && l > 0; l--)
+ profile = next_profile(root, profile);
+
+ return profile;
+}
+
+/**
+ * p_next - read the next profile entry
+ * @f: seq_file to fill
+ * @p: profile previously returned
+ * @pos: current position
+ *
+ * Returns: next profile after @p or NULL if none
+ *
+ * may acquire/release locks in namespace tree as necessary
+ */
+static void *p_next(struct seq_file *f, void *p, loff_t *pos)
+{
+ struct aa_profile *profile = p;
+ struct aa_namespace *root = f->private;
+ (*pos)++;
+
+ return next_profile(root, profile);
+}
+
+/**
+ * p_stop - stop depth first traversal
+ * @f: seq_file we are filling
+ * @p: the last profile writen
+ *
+ * Release all locking done by p_start/p_next on namespace tree
+ */
+static void p_stop(struct seq_file *f, void *p)
+ __releases(root->lock)
+{
+ struct aa_profile *profile = p;
+ struct aa_namespace *root = f->private, *ns;
+
+ if (profile) {
+ for (ns = profile->ns; ns && ns != root; ns = ns->parent)
+ read_unlock(&ns->lock);
+ }
+ read_unlock(&root->lock);
+ aa_put_namespace(root);
+}
+
+/**
+ * seq_show_profile - show a profile entry
+ * @f: seq_file to file
+ * @p: current position (profile) (NOT NULL)
+ *
+ * Returns: error on failure
+ */
+static int seq_show_profile(struct seq_file *f, void *p)
+{
+ struct aa_profile *profile = (struct aa_profile *)p;
+ struct aa_namespace *root = f->private;
+
+ if (profile->ns != root)
+ seq_printf(f, ":%s://", aa_ns_name(root, profile->ns));
+ seq_printf(f, "%s (%s)\n", profile->base.hname,
+ COMPLAIN_MODE(profile) ? "complain" : "enforce");
+
+ return 0;
+}
+
+static const struct seq_operations aa_fs_profiles_op = {
+ .start = p_start,
+ .next = p_next,
+ .stop = p_stop,
+ .show = seq_show_profile,
+};
+
+static int profiles_open(struct inode *inode, struct file *file)
+{
+ return seq_open(file, &aa_fs_profiles_op);
+}
+
+static int profiles_release(struct inode *inode, struct file *file)
+{
+ return seq_release(inode, file);
+}
+
+const struct file_operations aa_fs_profiles_fops = {
+ .open = profiles_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = profiles_release,
+};
diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c
index 0848292..28c52ac 100644
--- a/security/apparmor/apparmorfs.c
+++ b/security/apparmor/apparmorfs.c
@@ -187,7 +187,11 @@ void __init aa_destroy_aafs(void)
aafs_remove(".remove");
aafs_remove(".replace");
aafs_remove(".load");
-
+#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24
+ aafs_remove("profiles");
+ aafs_remove("matching");
+ aafs_remove("features");
+#endif
securityfs_remove(aa_fs_dentry);
aa_fs_dentry = NULL;
}
@@ -218,7 +222,17 @@ int __init aa_create_aafs(void)
aa_fs_dentry = NULL;
goto error;
}
-
+#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24
+ error = aafs_create("matching", 0444, &aa_fs_matching_fops);
+ if (error)
+ goto error;
+ error = aafs_create("features", 0444, &aa_fs_features_fops);
+ if (error)
+ goto error;
+#endif
+ error = aafs_create("profiles", 0440, &aa_fs_profiles_fops);
+ if (error)
+ goto error;
error = aafs_create(".load", 0640, &aa_fs_profile_load);
if (error)
goto error;
diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h
index cb1e93a..14f955c 100644
--- a/security/apparmor/include/apparmorfs.h
+++ b/security/apparmor/include/apparmorfs.h
@@ -17,4 +17,10 @@
extern void __init aa_destroy_aafs(void);
+#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24
+extern const struct file_operations aa_fs_matching_fops;
+extern const struct file_operations aa_fs_features_fops;
+extern const struct file_operations aa_fs_profiles_fops;
+#endif
+
#endif /* __AA_APPARMORFS_H */
--
1.7.5.4

View File

@@ -0,0 +1,69 @@
From 7a10d093f9779f42cb8d6affcb6a4436d3ebd6d3 Mon Sep 17 00:00:00 2001
From: John Johansen <john.johansen@canonical.com>
Date: Wed, 10 Aug 2011 22:02:41 -0700
Subject: [PATCH 3/3] AppArmor: Allow dfa backward compatibility with broken
userspace
The apparmor_parser when compiling policy could generate invalid dfas
that did not have sufficient padding to avoid invalid references, when
used by the kernel. The kernels check to verify the next/check table
size was broken meaning invalid dfas were being created by userspace
and not caught.
To remain compatible with old tools that are not fixed, pad the loaded
dfas next/check table. The dfa's themselves are valid except for the
high padding for potentially invalid transitions (high bounds error),
which have a maximimum is 256 entries. So just allocate an extra null filled
256 entries for the next/check tables. This will guarentee all bounds
are good and invalid transitions go to the null (0) state.
Signed-off-by: John Johansen <john.johansen@canonical.com>
---
security/apparmor/match.c | 17 +++++++++++++++++
1 files changed, 17 insertions(+), 0 deletions(-)
diff --git a/security/apparmor/match.c b/security/apparmor/match.c
index 94de6b4..081491e 100644
--- a/security/apparmor/match.c
+++ b/security/apparmor/match.c
@@ -57,8 +57,17 @@ static struct table_header *unpack_table(char *blob, size_t bsize)
if (bsize < tsize)
goto out;
+ /* Pad table allocation for next/check by 256 entries to remain
+ * backwards compatible with old (buggy) tools and remain safe without
+ * run time checks
+ */
+ if (th.td_id == YYTD_ID_NXT || th.td_id == YYTD_ID_CHK)
+ tsize += 256 * th.td_flags;
+
table = kvmalloc(tsize);
if (table) {
+ /* ensure the pad is clear, else there will be errors */
+ memset(table, 0, tsize);
*table = th;
if (th.td_flags == YYTD_DATA8)
UNPACK_ARRAY(table->td_data, blob, th.td_lolen,
@@ -134,11 +143,19 @@ static int verify_dfa(struct aa_dfa *dfa, int flags)
goto out;
if (flags & DFA_FLAG_VERIFY_STATES) {
+ int warning = 0;
for (i = 0; i < state_count; i++) {
if (DEFAULT_TABLE(dfa)[i] >= state_count)
goto out;
/* TODO: do check that DEF state recursion terminates */
if (BASE_TABLE(dfa)[i] + 255 >= trans_count) {
+ if (warning)
+ continue;
+ printk(KERN_WARNING "AppArmor DFA next/check "
+ "upper bounds error fixed, upgrade "
+ "user space tools \n");
+ warning = 1;
+ } else if (BASE_TABLE(dfa)[i] >= trans_count) {
printk(KERN_ERR "AppArmor DFA next/check upper "
"bounds error\n");
goto out;
--
1.7.5.4

View File

@@ -2,7 +2,7 @@
POD2MAN = pod2man
man_MANS = aa_change_hat.2 aa_change_profile.2
man_MANS = aa_change_hat.2 aa_change_profile.2 aa_getcon.2 aa_find_mountpoint.2
PODS = $(subst .2,.pod,$(man_MANS))
@@ -14,8 +14,7 @@ BUILT_SOURCES = $(man_MANS)
%.2: %.pod
$(POD2MAN) \
--section=2 \
--release="NOVELL/SUSE" \
--release="AppArmor $(VERSION)" \
--center="AppArmor" \
--date="2007-07-27" \
--stderr \
$< > $@
$

View File

@@ -29,7 +29,9 @@ aa_change_hat - change to or from a "hat" within a AppArmor profile
B<#include E<lt>sys/apparmor.hE<gt>>
B<int aa_change_hat (char *subprofile, unsigned long magic_token);>
B<int aa_change_hatv (char *subprofiles[], unsigned long magic_token);>
B<int aa_change_hat_vargs (unsigned long magic_token, ...);>
Link with B<-lapparmor> when compiling.
@@ -244,7 +246,8 @@ should be used.
=head1 SEE ALSO
apparmor(7), apparmor.d(5), apparmor_parser(8), aa_change_profile(2) and
apparmor(7), apparmor.d(5), apparmor_parser(8), aa_change_profile(2),
aa_getcon(2) and
L<http://wiki.apparmor.net>.
=cut

View File

@@ -22,14 +22,16 @@
=head1 NAME
aa_change_profile - change to another profile within an AppArmor profile
aa_change_onexec - change to another profile at the next exec
aa_change_profile, aa_change_onexec - change a tasks profile
=head1 SYNOPSIS
B<#include E<lt>sys/apparmor.hE<gt>>
B<int aa_change_profile(const char *profile);>
B<int aa_change_onexec(const char *profile);>
Link with B<-lapparmor> when compiling.
=head1 DESCRIPTION
@@ -83,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
@@ -173,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

@@ -0,0 +1,120 @@
# This publication is intellectual property of Canonical Ltd. Its contents
# can be duplicated, either in part or in whole, provided that a copyright
# label is visibly located on each copy.
#
# All information found in this book has been compiled with utmost
# attention to detail. However, this does not guarantee complete accuracy.
# Neither Canonical Ltd, the authors, nor the translators shall be held
# liable for possible errors or the consequences thereof.
#
# Many of the software and hardware descriptions cited in this book
# are registered trademarks. All trade names are subject to copyright
# restrictions and may be registered trade marks. Canonical Ltd.
# essentially adhere to the manufacturer's spelling.
#
# Names of products and trademarks appearing in this book (with or without
# specific notation) are likewise subject to trademark and trade protection
# laws and may thus fall under copyright restrictions.
#
=pod
=head1 NAME
aa_is_enabled - determine if apparmor is available
aa_find_mountpoint - find where the apparmor interface filesystem is mounted
=head1 SYNOPSIS
B<#include E<lt>sys/apparmor.hE<gt>>
B<int aa_is_enabled(void);>
B<int aa_find_mountpoint(char **mnt);>
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(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
caller's responsibility to free(3) the returned path.
=head1 RETURN VALUE
B<aa_is_enabled>
On success 1 is returned. On error, 0 is returned, and errno(3) is set
appropriately.
B<aa_find_mountpoint>
On success zero is returned. On error, -1 is returned, and errno(3) is set
appropriately.
=head1 ERRORS
B<aa_is_enabled>
=over 4
=item B<ENOSYS>
AppArmor extensions to the system are not available.
=item B<ECANCELED>
AppArmor is available on the system but has been disabled at boot.
=item B<ENOENT>
AppArmor is available (and maybe even enforcing policy) but the interface is
not available.
=item B<ENOMEM>
Insufficient memory was available.
=item B<EPERM>
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.
=back
B<aa_find_mountpoint>
=over 4
=item B<ENOMEM>
Insufficient memory was available.
=item B<EACCES>
Access to the required paths was denied.
=item B<ENOENT>
The apparmor filesystem mount could not be found
=back
=head1 BUGS
None known. If you find any, please report them at
L<http://https://bugs.launchpad.net/apparmor/+filebug>.
=head1 SEE ALSO
apparmor(7), apparmor.d(5), apparmor_parser(8), and
L<http://wiki.apparmor.net>.
=cut

View File

@@ -0,0 +1,113 @@
# This publication is intellectual property of Canonical Ltd. Its contents
# can be duplicated, either in part or in whole, provided that a copyright
# label is visibly located on each copy.
#
# All information found in this book has been compiled with utmost
# attention to detail. However, this does not guarantee complete accuracy.
# Neither Canonical Ltd, the authors, nor the translators shall be held
# liable for possible errors or the consequences thereof.
#
# Many of the software and hardware descriptions cited in this book
# are registered trademarks. All trade names are subject to copyright
# restrictions and may be registered trade marks. Canonical Ltd.
# essentially adhere to the manufacturer's spelling.
#
# Names of products and trademarks appearing in this book (with or without
# specific notation) are likewise subject to trademark and trade protection
# laws and may thus fall under copyright restrictions.
#
=pod
=head1 NAME
aa_getprocattr_raw, aa_getprocattr - read and parse procattr data
aa_getcon, aa_gettaskcon - get task confinement information
aa_getpeercon - get the confinement of a socket's other end (peer)
=head1 SYNOPSIS
B<#include E<lt>sys/apparmor.hE<gt>>
B<int aa_getprocattr_raw(pid_t tid, const char *attr, char *buf, int len,
char **mode);>
B<int aa_getprocattr(pid_t tid, const char *attr, char **buf, char **mode);>
B<int aa_gettaskcon(pid_t target, char **con, char **mode);>
B<int aa_getcon(char **con, char **mode);>
B<int aa_getpeercon(int fd, char **con);>
Link with B<-lapparmor> when compiling.
=head1 DESCRIPTION
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().
The aa_gettaskcon function is like the aa_getcon function except it will work
for any arbitrary task in the system.
The aa_getpeercon function is similar to that of aa_gettaskcon except that
it returns the confinement information for task on the other end of a socket
connection.
The aa_getprocattr function is the backend for the aa_getcon and aa_gettaskcon
functions and handles the reading and parsing of the confinement data from
different arbitrary attr files and returns the processed results in
an allocated buffer.
The aa_getprocattr_raw() is the backend for the aa_getprocattr function and
does not handle buffer allocation.
=head1 RETURN VALUE
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
=over 4
=item B<EINVAL>
The apparmor kernel module is not loaded or the communication via the
F</proc/*/attr/file> did not conform to protocol.
=item B<ENOMEM>
Insufficient kernel memory was available.
=item B<EACCES>
Access to the specified I<file/task> was denied.
=item B<ENOENT>
The specified I<file/task> does not exist or is not visible.
=item B<ERANGE>
The confinement data is to large to fit in the supplied buffer.
=back
=head1 BUGS
None known. If you find any, please report them at
L<http://https://bugs.launchpad.net/apparmor/+filebug>.
=head1 SEE ALSO
apparmor(7), apparmor.d(5), apparmor_parser(8), aa_change_profile(2) and
L<http://wiki.apparmor.net>.
=cut

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

@@ -18,8 +18,14 @@
#ifndef _SYS_APPARMOR_H_
#define _SYS_APPARMOR_H 1
#include <sys/types.h>
__BEGIN_DECLS
/* Prototypes for apparmor state queries */
extern int aa_is_enabled(void);
extern int aa_find_mountpoint(char **mnt);
/* Prototypes for self directed domain transitions
* see <http://apparmor.net>
* Please see the change_hat(2) manpage for information.
@@ -34,6 +40,17 @@ extern int aa_change_onexec(const char *profile);
extern int aa_change_hatv(const char *subprofiles[], unsigned long token);
extern int (aa_change_hat_vargs)(unsigned long token, int count, ...);
/* Protypes for introspecting task confinement
* Please see the aa_getcon(2) manpage for information
*/
extern int aa_getprocattr_raw(pid_t tid, const char *attr, char *buf, int len,
char **mode);
extern int aa_getprocattr(pid_t tid, const char *attr, char **buf, char **mode);
extern int aa_gettaskcon(pid_t target, char **con, char **mode);
extern int aa_getcon(char **con, char **mode);
extern int aa_getpeercon_raw(int fd, char *buffer, int *size);
extern int aa_getpeercon(int fd, char **con);
#define __macroarg_counter(Y...) __macroarg_count1 ( , ##Y)
#define __macroarg_count1(Y...) __macroarg_count2 (Y, 16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0)
#define __macroarg_count2(_,x0,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,n,Y...) n

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
@@ -246,7 +251,7 @@ key: TOK_KEY_OPERATION TOK_EQUALS TOK_QUOTED_STRING
{ ret_record->fsuid = $3;}
| TOK_KEY_OUID TOK_EQUALS TOK_DIGITS
{ ret_record->ouid = $3;}
| TOK_KEY_COMM TOK_EQUALS TOK_QUOTED_STRING
| TOK_KEY_COMM TOK_EQUALS safe_string
{ ret_record->comm = $3;}
| TOK_KEY_APPARMOR TOK_EQUALS apparmor_event
| TOK_KEY_CAPABILITY TOK_EQUALS TOK_DIGITS
@@ -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

@@ -22,32 +22,284 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <mntent.h>
#include "apparmor.h"
/* some non-Linux systems do not define a static value */
#ifndef PATH_MAX
# define PATH_MAX 4096
#endif
#define symbol_version(real, name, version) \
__asm__ (".symver " #real "," #name "@" #version)
#define default_symbol_version(real, name, version) \
__asm__ (".symver " #real "," #name "@@" #version)
static int setprocattr(const char *path, const char *buf, int len)
/**
* aa_find_mountpoint - find where the apparmor interface filesystem is mounted
* @mnt: returns buffer with the mountpoint string
*
* Returns: 0 on success else -1 on error
*
* NOTE: this function only supports versions of apparmor using securityfs
*/
int aa_find_mountpoint(char **mnt)
{
struct stat statbuf;
struct mntent *mntpt;
FILE *mntfile;
int rc = -1;
if (!mnt) {
errno = EINVAL;
return -1;
}
mntfile = setmntent("/proc/mounts", "r");
if (!mntfile)
return -1;
while ((mntpt = getmntent(mntfile))) {
char *proposed = NULL;
if (strcmp(mntpt->mnt_type, "securityfs") != 0)
continue;
if (asprintf(&proposed, "%s/apparmor", mntpt->mnt_dir) < 0)
/* ENOMEM */
break;
if (stat(proposed, &statbuf) == 0) {
*mnt = proposed;
rc = 0;
break;
}
free(proposed);
}
endmntent(mntfile);
if (rc == -1)
errno = ENOENT;
return rc;
}
/**
* aa_is_enabled - determine if apparmor is enabled
*
* Returns: 1 if enabled else reason it is not, or 0 on error
*
* ENOSYS - no indication apparmor is present in the system
* ENOENT - enabled but interface could not be found
* ECANCELED - disabled at boot
* ENOMEM - out of memory
*/
int aa_is_enabled(void)
{
int serrno, fd, rc, size;
char buffer[2];
char *mnt;
/* if the interface mountpoint is available apparmor is enabled */
rc = aa_find_mountpoint(&mnt);
if (rc == 0) {
free(mnt);
return 1;
}
/* determine why the interface mountpoint isn't available */
fd = open("/sys/module/apparmor/parameters/enabled", O_RDONLY);
if (fd == -1) {
if (errno == ENOENT)
errno = ENOSYS;
return 0;
}
size = read(fd, &buffer, 2);
serrno = errno;
close(fd);
errno = serrno;
if (size > 0) {
if (buffer[0] == 'Y')
errno = ENOENT;
else
errno = ECANCELED;
}
return 0;
}
static inline pid_t aa_gettid(void)
{
#ifdef SYS_gettid
return syscall(SYS_gettid);
#else
return getpid();
#endif
}
static char *procattr_path(pid_t pid, const char *attr)
{
char *path = NULL;
if (asprintf(&path, "/proc/%d/attr/%s", pid, attr) > 0)
return path;
return NULL;
}
/**
* aa_getprocattr_raw - get the contents of @attr for @tid into @buf
* @tid: tid of task to query
* @attr: which /proc/<tid>/attr/<attr> to query
* @buf: buffer to store the result in
* @len: size of the buffer
* @mode: if set will point to mode string within buffer if it is present
*
* Returns: size of data read or -1 on error, and sets errno
*/
int aa_getprocattr_raw(pid_t tid, const char *attr, char *buf, int len,
char **mode)
{
int rc = -1;
int fd, ret, ctlerr = 0;
int fd, ret;
char *tmp = NULL;
int size = 0;
if (!buf || len <= 0) {
errno = EINVAL;
goto out;
}
tmp = procattr_path(tid, attr);
if (!tmp)
goto out;
fd = open(tmp, O_RDONLY);
free(tmp);
if (fd == -1) {
goto out;
}
tmp = buf;
do {
ret = read(fd, tmp, len);
if (ret <= 0)
break;
tmp += ret;
size += ret;
len -= ret;
if (len < 0) {
errno = ERANGE;
goto out2;
}
} while (ret > 0);
if (ret < 0) {
int saved;
if (ret != -1) {
errno = EPROTO;
}
saved = errno;
(void)close(fd);
errno = saved;
goto out;
} else if (size > 0 && buf[size - 1] != 0) {
/* check for null termination */
if (buf[size - 1] == '\n') {
buf[size - 1] = 0;
} else if (len == 0) {
errno = ERANGE;
goto out2;
} else {
buf[size] = 0;
size++;
}
/*
* now separate the mode. If we don't find it just
* return NULL
*/
if (mode)
*mode = NULL;
if (strcmp(buf, "unconfined") != 0 &&
size > 4 && buf[size - 2] == ')') {
int pos = size - 3;
while (pos > 0 &&
!(buf[pos] == ' ' && buf[pos + 1] == '('))
pos--;
if (pos > 0) {
buf[pos] = 0; /* overwrite ' ' */
buf[size - 2] = 0; /* overwrite trailing ) */
if (mode)
*mode = &buf[pos + 2]; /* skip '(' */
}
}
}
rc = size;
out2:
(void)close(fd);
out:
return rc;
}
#define INITIAL_GUESS_SIZE 128
/**
* aa_getprocattr - get the contents of @attr for @tid into @buf
* @tid: tid of task to query
* @attr: which /proc/<tid>/attr/<attr> to query
* @buf: allocated buffer the result is stored in
* @mode: if set will point to mode string within buffer if it is present
*
* Returns: size of data read or -1 on error, and sets errno
*/
int aa_getprocattr(pid_t tid, const char *attr, char **buf, char **mode)
{
int rc, size = INITIAL_GUESS_SIZE/2;
char *buffer = NULL;
if (!buf) {
errno = EINVAL;
return -1;
}
do {
size <<= 1;
buffer = realloc(buffer, size);
if (!buffer)
return -1;
memset(buffer, 0, size);
rc = aa_getprocattr_raw(tid, attr, buffer, size, mode);
} while (rc == -1 && errno == ERANGE);
if (rc == -1) {
free(buffer);
*buf = NULL;
*mode = NULL;
} else
*buf = buffer;
return rc;
}
static int setprocattr(pid_t tid, const char *attr, const char *buf, int len)
{
int rc = -1;
int fd, ret;
char *ctl = NULL;
pid_t tid = syscall(SYS_gettid);
if (!buf) {
errno = EINVAL;
goto out;
}
ctlerr = asprintf(&ctl, path, tid);
if (ctlerr < 0) {
ctl = procattr_path(tid, attr);
if (!ctl)
goto out;
}
fd = open(ctl, O_WRONLY);
if (fd == -1) {
@@ -99,7 +351,7 @@ int aa_change_hat(const char *subprofile, unsigned long token)
goto out;
}
rc = setprocattr("/proc/%d/attr/current", buf, len);
rc = setprocattr(aa_gettid(), "current", buf, len);
out:
if (buf) {
/* clear local copy of magic token before freeing */
@@ -130,7 +382,7 @@ int aa_change_profile(const char *profile)
if (len < 0)
return -1;
rc = setprocattr("/proc/%d/attr/current", buf, len);
rc = setprocattr(aa_gettid(), "current", buf, len);
free(buf);
return rc;
@@ -151,7 +403,7 @@ int aa_change_onexec(const char *profile)
if (len < 0)
return -1;
rc = setprocattr("/proc/%d/attr/exec", buf, len);
rc = setprocattr(aa_gettid(), "exec", buf, len);
free(buf);
return rc;
@@ -212,7 +464,7 @@ int aa_change_hatv(const char *subprofiles[], unsigned long token)
/* step pos past trailing \0 */
pos++;
rc = setprocattr("/proc/%d/attr/current", buf, pos - buf);
rc = setprocattr(aa_gettid(), "current", buf, pos - buf);
out:
if (buf) {
@@ -251,3 +503,125 @@ int (aa_change_hat_vargs)(unsigned long token, int nhats, ...)
va_end(ap);
return aa_change_hatv(argv, token);
}
/**
* aa_gettaskcon - get the confinement for task @target in an allocated buffer
* @target: task to query
* @con: pointer to returned buffer with the confinement string
* @mode: if provided will point to the mode string in @con if present
*
* Returns: length of confinement data or -1 on error and sets errno
*
* Guarentees that @con and @mode are null terminated. The length returned
* is for all data including both @con and @mode, and maybe > than strlen(@con)
* even if @mode is NULL
*
* Caller is responsible for freeing the buffer returned in @con. @mode is
* always contained within @con's buffer and so NEVER do free(@mode)
*/
int aa_gettaskcon(pid_t target, char **con, char **mode)
{
return aa_getprocattr(target, "current", con, mode);
}
/**
* aa_getcon - get the confinement for current task in an allocated buffer
* @con: pointer to return buffer with the confinement if successful
* @mode: if provided will point to the mode string in @con if present
*
* Returns: length of confinement data or -1 on error and sets errno
*
* Guarentees that @con and @mode are null terminated. The length returned
* is for all data including both @con and @mode, and may > than strlen(@con)
* even if @mode is NULL
*
* Caller is responsible for freeing the buffer returned in @con. @mode is
* always contained within @con's buffer and so NEVER do free(@mode)
*/
int aa_getcon(char **con, char **mode)
{
return aa_gettaskcon(aa_gettid(), con, mode);
}
#ifndef SO_PEERSEC
#define SO_PEERSEC 31
#endif
/**
* aa_getpeercon_raw - get the confinement of the socket's peer (other end)
* @fd: socket to get peer confinement for
* @con: pointer to buffer to store confinement string
* @size: initially contains size of the buffer, returns size of data read
*
* Returns: length of confinement data including null termination or -1 on error
* if errno == ERANGE then @size will hold the size needed
*/
int aa_getpeercon_raw(int fd, char *buffer, int *size)
{
socklen_t optlen = *size;
int rc;
if (optlen <= 0 || buffer == NULL) {
errno = EINVAL;
return -1;
}
rc = getsockopt(fd, SOL_SOCKET, SO_PEERSEC, buffer, &optlen);
if (rc == -1 || optlen <= 0)
goto out;
/* check for null termination */
if (buffer[optlen - 1] != 0) {
if (optlen < *size) {
buffer[optlen] = 0;
optlen++;
} else {
/* buffer needs to be bigger by 1 */
rc = -1;
errno = ERANGE;
optlen++;
}
}
out:
*size = optlen;
return rc;
}
/**
* aa_getpeercon - get the confinement of the socket's peer (other end)
* @fd: socket to get peer confinement for
* @con: pointer to allocated buffer with the confinement string
*
* Returns: length of confinement data including null termination or -1 on error
*
* Caller is responsible for freeing the buffer returned.
*/
int aa_getpeercon(int fd, char **con)
{
int rc, size = INITIAL_GUESS_SIZE;
char *buffer = NULL;
if (!con) {
errno = EINVAL;
return -1;
}
do {
buffer = realloc(buffer, size);
if (!buffer)
return -1;
memset(buffer, 0, size);
rc = aa_getpeercon_raw(fd, buffer, &size);
} while (rc == -1 && errno == ERANGE);
if (rc == -1) {
free(buffer);
*con = NULL;
size = -1;
} else
*con = buffer;
return size;
}

View File

@@ -16,13 +16,22 @@ APPARMOR_1.0 {
APPARMOR_1.1 {
global:
aa_is_enabled;
aa_find_mountpoint;
aa_change_hat;
aa_change_hatv;
aa_change_hat_vargs;
aa_change_profile;
aa_change_onexec;
aa_gettaskcon;
aa_getcon;
aa_getpeercon_raw;
aa_getpeercon;
parse_record;
free_record;
aa_getprocattr_raw;
aa_getprocattr;
local:
*;
} APPARMOR_1.0;

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); }
@@ -265,11 +279,15 @@ yy_flex_debug = 0;
{key_error} { return(TOK_KEY_ERROR); }
{key_fsuid} { return(TOK_KEY_FSUID); }
{key_ouid} { return(TOK_KEY_OUID); }
{key_comm} { return(TOK_KEY_COMM); }
{key_comm} { BEGIN(safe_string); return(TOK_KEY_COMM); }
{key_capability} { return(TOK_KEY_CAPABILITY); }
{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

@@ -13,9 +13,17 @@
* are manually inserted here
*/
extern int aa_is_enabled(void);
extern int aa_find_mountpoint(char **mnt);
extern int aa_change_hat(const char *subprofile, unsigned long magic_token);
extern int aa_change_profile(const char *profile);
extern int aa_change_onexec(const char *profile);
extern int aa_change_hatv(const char *subprofiles[], unsigned long token);
extern int aa_change_hat_vargs(unsigned long token, int count, ...);
extern int aa_getprocattr_raw(pid_t tid, const char *attr, char *buf, int len,
char **mode);
extern int aa_getprocattr(pid_t tid, const char *attr, char **buf, char **mode);
extern int aa_gettaskcon(pid_t target, char **con, char **mode);
extern int aa_getcon(char **con, char **mode);
extern int aa_getpeercon_raw(int fd, char *buffer, int *size);
extern int aa_getpeercon(int fd, char **con);

View File

@@ -13,7 +13,7 @@ setup(name = 'LibAppArmor',
ext_package = 'LibAppArmor',
ext_modules = [Extension('_LibAppArmor', ['libapparmor_wrap.c'],
include_dirs=['@top_srcdir@/src'],
extra_link_args = string.split('-L@top_builddir@/src/.libs -lapparmor'),
# static: extra_link_args = string.split('@top_builddir@/src/.libs/libapparmor.a'),
extra_link_args = '-L@top_builddir@/src/.libs -lapparmor'.split(),
# static: extra_link_args = '@top_builddir@/src/.libs/libapparmor.a'.split(),
)],
)

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 @@
type=AVC msg=audit(1322676143.201:455): apparmor="ALLOWED" operation="open" parent=10357 profile=2F686F6D652F73746576652F746D702F6D792070726F672E7368 name=2F686F6D652F73746576652F746D702F6D792070726F672E7368 pid=22918 comm=6D792070726F672E7368 requested_mask="r" denied_mask="r" fsuid=1000 ouid=1000

View File

@@ -0,0 +1,16 @@
START
File: test_multi/testcase_encoded_comm.in
Event type: AA_RECORD_ALLOWED
Audit ID: 1322676143.201:455
Operation: open
Mask: r
Denied Mask: r
fsuid: 1000
ouid: 1000
Profile: /home/steve/tmp/my prog.sh
Name: /home/steve/tmp/my prog.sh
Command: my prog.sh
Parent: 10357
PID: 22918
Epoch: 1322676143
Audit subid: 455

View File

@@ -0,0 +1 @@
Aug 23 17:29:45 hostname kernel: [289763.843292] type=1400 audit(1322614912.304:857): apparmor="ALLOWED" operation="getattr" parent=16001 profile=74657374207370616365 name="/lib/x86_64-linux-gnu/libdl-2.13.so" pid=17011 comm="bash" requested_mask="r" denied_mask="r" fsuid=0 ouid=0

View File

@@ -0,0 +1,16 @@
START
File: test_multi/testcase_encoded_profile.in
Event type: AA_RECORD_ALLOWED
Audit ID: 1322614912.304:857
Operation: getattr
Mask: r
Denied Mask: r
fsuid: 0
ouid: 0
Profile: test space
Name: /lib/x86_64-linux-gnu/libdl-2.13.so
Command: bash
Parent: 16001
PID: 17011
Epoch: 1322614912
Audit subid: 857

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

@@ -1,15 +1,15 @@
This license applies to all source files within the AppArmor parser
package.
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
@@ -18,7 +18,7 @@ software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
@@ -58,8 +58,8 @@ patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
@@ -113,7 +113,7 @@ above, provided that you also meet all of these conditions:
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
@@ -171,7 +171,7 @@ access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
@@ -228,7 +228,7 @@ impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
@@ -258,7 +258,7 @@ make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
@@ -280,9 +280,9 @@ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
@@ -294,7 +294,7 @@ convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) 19yy <name of author>
Copyright (C) <year> <name of author>
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
@@ -306,17 +306,16 @@ the "copyright" line and a pointer to where the full notice is found.
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, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) 19yy name of author
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
@@ -339,5 +338,5 @@ necessary. Here is a sample; alter the names:
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

View File

@@ -49,7 +49,7 @@ ifndef CFLAGS
CFLAGS = -g -O2 -pipe
ifdef DEBUG
CFLAGS += -pg
CFLAGS += -pg -D DEBUG
endif
endif #CFLAGS
@@ -73,29 +73,31 @@ EXTRA_CFLAGS+=-DPACKAGE=\"${NAME}\" -DLOCALEDIR=\"${LOCALEDIR}\"
# Compile-time configuration of the location of the config file
EXTRA_CFLAGS+=-DSUBDOMAIN_CONFDIR=\"${CONFDIR}\"
SRCS = 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
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 mount.c
HDRS = parser.h parser_include.h immunix.h mount.h
TOOLS = apparmor_parser
OBJECTS = parser_lex.o parser_yacc.o parser_main.o parser_interface.o \
parser_include.o parser_merge.o parser_symtab.o parser_misc.o \
parser_regex.o parser_variable.o parser_policy.o parser_alias.o
OBJECTS = $(SRCS:.c=.o)
AAREDIR= libapparmor_re
AAREOBJECTS = ${AAREDIR}/libapparmor_re.a
AAREOBJECT = ${AAREDIR}/libapparmor_re.a
AAREOBJECTS = $(AAREOBJECT) libstdc++.a
AARE_LDFLAGS=-static-libgcc -L.
LEX_C_FILES = parser_lex.c
YACC_C_FILES = parser_yacc.c parser_yacc.h
TESTS = tst_regex tst_misc tst_symtab tst_variable
TEST_FLAGS = -Wl,--warn-unresolved-symbols
DISABLED_TESTS =
TEST_OBJECTS = $(filter-out parser_lex.o, \
$(filter-out parser_yacc.o, \
$(filter-out parser_main.o, ${OBJECTS})))
TEST_CFLAGS = $(EXTRA_CFLAGS) -DUNIT_TEST -Wno-unused-result
TEST_OBJECTS = $(filter-out \
parser_lex.o \
parser_yacc.o \
parser_main.o, ${OBJECTS}) \
$(AAREOBJECTS)
TEST_LDFLAGS = $(AARE_LDFLAGS)
ifdef V
VERBOSE = 1
@@ -113,7 +115,7 @@ endif
export Q VERBOSE BUILD_OUTPUT
po/${NAME}.pot: ${SRCS} ${HDRS}
make -C po ${NAME}.pot NAME=${NAME} SOURCES="${SRCS} ${HDRS}"
$(MAKE) -C po ${NAME}.pot NAME=${NAME} SOURCES="${SRCS} ${HDRS}"
techdoc.pdf: techdoc.tex
while pdflatex $< ${BUILD_OUTPUT} || exit 1 ; \
@@ -128,8 +130,7 @@ techdoc.txt: techdoc/index.html
# targets arranged this way so that people who don't want full docs can
# pick specific targets they want.
main: $(TOOLS)
$(Q)make -C po all
arch: $(TOOLS)
manpages: $(MANPAGES)
@@ -139,13 +140,20 @@ pdf: techdoc.pdf
docs: manpages htmlmanpages pdf
all: main docs tests
indep: docs
$(Q)$(MAKE) -C po all
all: arch indep
.PHONY: libstdc++.a
libstdc++.a:
rm -f ./libstdc++.a
ln -s `$(CXX) -print-file-name=libstdc++.a`
apparmor_parser: $(OBJECTS) $(AAREOBJECTS)
rm -f ./libstdc++.a
ln -s `g++ -print-file-name=libstdc++.a`
g++ $(EXTRA_CFLAGS) -o $@ $(OBJECTS) $(LIBS) \
${LEXLIB} $(AAREOBJECTS) -static-libgcc -L.
$(CXX) $(EXTRA_CFLAGS) -o $@ $(OBJECTS) $(LIBS) \
${LEXLIB} $(AAREOBJECTS) $(AARE_LDFLAGS)
parser_yacc.c parser_yacc.h: parser_yacc.y parser.h
$(YACC) $(YFLAGS) -o parser_yacc.c parser_yacc.y
@@ -189,54 +197,46 @@ parser_policy.o: parser_policy.c parser.h parser_yacc.h
parser_alias.o: parser_alias.c parser.h
$(CC) $(EXTRA_CFLAGS) -c -o $@ $<
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_RXRPC PF_MAX PF_UNSPEC PF_UNIX PF_LOCAL PF_NETLINK PF_LLC PF_IUCV PF_TIPC PF_CAN PF_ISDN PF_PHONET
# 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.
__FILTER=$(shell echo $(strip $(FILTER_FAMILIES)) | sed -e 's/ /\\\|/g')
af_names.h: /usr/include/bits/socket.h
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" $< > $@
LC_ALL=C sed -n -e "s/^\#define[ \\t]\\+PF_MAX[ \\t]\\+\\([0-9]\\+\\)[ \\t]\\+.*/#define AA_AF_MAX \\1\n/p" $< >> $@
.PHONY: af_names.h
af_names.h:
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_symtab: parser_symtab.c parser.h parser_variable.o
$(Q)$(CC) -DUNIT_TEST $(EXTRA_CFLAGS) $(TEST_FLAGS) -o $@ $< parser_variable.o $(BUILD_OUTPUT)
tst_variable: parser_variable.c parser.h $(filter-out parser_variable.o, ${TEST_OBJECTS})
$(Q)$(CC) -DUNIT_TEST $(EXTRA_CFLAGS) $(TEST_FLAGS) -o $@ $< $(filter-out parser_variable.o, ${TEST_OBJECTS}) $(BUILD_OUTPUT)
tst_misc: parser_misc.c parser.h parser_yacc.h af_names.h cap_names.h
$(Q)$(CC) -DUNIT_TEST $(EXTRA_CFLAGS) $(TEST_FLAGS) -o $@ $< $(BUILD_OUTPUT)
tst_regex: parser_regex.c parser.h parser_yacc.h
$(Q)$(CC) -DUNIT_TEST $(EXTRA_CFLAGS) $(TEST_FLAGS) -o $@ $< $(BUILD_OUTPUT)
tst_%: parser_%.c parser.h $(filter-out parser_%.o, ${TEST_OBJECTS})
$(CC) $(TEST_CFLAGS) -o $@ $< $(filter-out $(<:.c=.o), ${TEST_OBJECTS}) $(TEST_LDFLAGS)
.SILENT: check
.PHONY: check
check: tests
.SILENT: tests
tests: ${TESTS}
sh -e -c 'for test in ${TESTS} ; do echo "*** running $${test}" && ./$${test} $(BUILD_OUTPUT) ; done'
$(Q)make -s -C tst tests
.SILENT: check
check: tests
tests: apparmor_parser ${TESTS}
sh -e -c 'for test in ${TESTS} ; do echo "*** running $${test}" && ./$${test}; done'
$(Q)$(MAKE) -s -C tst tests
# always need to rebuild.
.SILENT: $(AAREOBJECTS)
.PHONY: $(AAREOBJECTS)
$(AAREOBJECTS):
make -C $(AAREDIR) CFLAGS="$(EXTRA_CXXFLAGS)"
.SILENT: $(AAREOBJECT)
.PHONY: $(AAREOBJECT)
$(AAREOBJECT):
$(MAKE) -C $(AAREDIR) CFLAGS="$(EXTRA_CXXFLAGS)"
.PHONY: install-rhel4
install-rhel4: install-redhat
@@ -245,17 +245,14 @@ install-rhel4: install-redhat
install-redhat:
install -m 755 -d $(DESTDIR)/etc/init.d
install -m 755 rc.apparmor.$(subst install-,,$@) $(DESTDIR)/etc/init.d/apparmor
install -m 755 rc.aaeventd.redhat $(DESTDIR)/etc/init.d/aaeventd
.PHONY: install-suse
install-suse:
install -m 755 -d $(DESTDIR)/etc/init.d
install -m 755 rc.apparmor.$(subst install-,,$(@)) $(DESTDIR)/etc/init.d/boot.apparmor
install -m 755 rc.aaeventd.$(subst install-,,$(@)) $(DESTDIR)/etc/init.d/aaeventd
install -m 755 -d $(DESTDIR)/sbin
ln -sf /etc/init.d/boot.apparmor $(DESTDIR)/sbin/rcapparmor
ln -sf rcapparmor $(DESTDIR)/sbin/rcsubdomain
ln -sf /etc/init.d/aaeventd $(DESTDIR)/sbin/rcaaeventd
.PHONY: install-slackware
install-slackware:
@@ -270,22 +267,29 @@ install-debian:
.PHONY: install-unknown
install-unknown:
INSTALLDEPS=$(TOOLS)
INSTALLDEPS=arch
ifdef DISTRO
INSTALLDEPS+=install-$(DISTRO)
endif
.PHONY: install
install: $(INSTALLDEPS)
install: install-indep install-arch
.PHONY: install-arch
install-arch: $(INSTALLDEPS)
install -m 755 -d $(DESTDIR)/sbin
install -m 755 ${TOOLS} $(DESTDIR)/sbin
.PHONY: install-indep
install-indep:
install -m 755 -d $(INSTALL_CONFDIR)
install -m 644 subdomain.conf $(INSTALL_CONFDIR)
install -m 644 parser.conf $(INSTALL_CONFDIR)
install -m 755 -d ${DESTDIR}/var/lib/apparmor
install -m 755 -d $(APPARMOR_BIN_PREFIX)
install -m 755 rc.apparmor.functions $(APPARMOR_BIN_PREFIX)
make -C po install NAME=${NAME} DESTDIR=${DESTDIR}
make install_manpages DESTDIR=${DESTDIR}
$(MAKE) -C po install NAME=${NAME} DESTDIR=${DESTDIR}
$(MAKE) install_manpages DESTDIR=${DESTDIR}
.SILENT: clean
.PHONY: clean
@@ -296,15 +300,14 @@ clean: _clean
rm -f $(YACC_C_FILES)
rm -f parser_version.h
rm -f $(NAME)*.tar.gz $(NAME)*.tgz
rm -f libstdc++.a
rm -f af_names.h
rm -f cap_names.h
rm -rf techdoc.aux techdoc.log techdoc.pdf techdoc.toc techdor.txt techdoc/
make -s -C $(AAREDIR) clean
make -s -C po clean
make -s -C tst clean
$(MAKE) -s -C $(AAREDIR) clean
$(MAKE) -s -C po clean
$(MAKE) -s -C tst clean
.SILENT: dist_clean
dist_clean:
@make clean
@$(MAKE) clean
@rm -f $(LEX_C_FILES) $(YACC_C_FILES)

View File

@@ -103,6 +103,7 @@ make install DESTDIR=${RPM_BUILD_ROOT} \
/etc/init.d/aaeventd
%endif
%config(noreplace) /etc/apparmor/subdomain.conf
%config(noreplace) /etc/apparmor/parser.conf
/var/lib/apparmor
%dir %attr(-, root, root) %{apparmor_bin_prefix}
%{apparmor_bin_prefix}/rc.apparmor.functions

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,15 +75,41 @@ 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.)
B<VARIABLE> = '@{' I<ALPHA> [ I<ALPHANUMERIC> ... ] '}'
B<VARIABLE> = '@{' I<ALPHA> [ ( I<ALPHANUMERIC> | '_' ) ... ] '}'
B<VARIABLE ASSIGNMENT> = I<VARIABLE> ('=' | '+=') (space separated values)
@@ -91,7 +117,7 @@ B<ALIAS RULE> = I<ABS PATH> '->' I<REWRITTEN ABS PATH> ','
B<ALPHA> = ('a', 'b', 'c', ... 'z', 'A', 'B', ... 'Z')
B<ALPHANUMERIC> = ('1', '2', '3', ... '9', 'a', 'b', 'c', ... 'z', 'A', 'B', ... 'Z')
B<ALPHANUMERIC> = ('0', '1', '2', ... '9', 'a', 'b', 'c', ... 'z', 'A', 'B', ... 'Z')
=back
@@ -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
@@ -323,10 +345,285 @@ as further information is specified.
eg.
network, #allow access to all networking
network tcp, #allow access to tcp
network inet tcp, #allow access to tcp only for inet4 addresses
network inet6 tcp, #allow access to tcp only for inet6 addresses
network, #allow access to all networking
network tcp, #allow access to tcp
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
@@ -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
@@ -199,6 +204,38 @@ Give a quick reference guide.
=back
=head1 CONFIG FILE
An optional config file /etc/apparmor/parser.conf can be used to specify the
default options for the parser, which then can be overridden using the command
line options.
The config file ignores leading whitespace and treats lines that begin with #
as comments. Config options are specified one per line using the same format
as the longform command line options (without the preceding --).
Eg.
#comment
optimize=no-expr-tree
optimize=compress-fast
As with the command line some options accumulate and others override, ie. when
there are conflicting versions of switch the last option is the one chosen.
Eg.
Optimize=no-minimize
Optimize=minimize
would result in Optimize=minimize being set.
The Include, Dump, and Optimize options accululate except for the inversion
option (no-X vs. X), and a couple options that work by setting/clearing
multiple options (compress-small). In that case the option will override
the flags it sets but will may accumulate with others.
All other options override previously set values.
=head1 BUGS
If you find any bugs, please report them at

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,14 +12,21 @@ BISON := bison
all : ${TARGET}
libapparmor_re.a: regexp.o
libapparmor_re.a: parse.o expr-tree.o hfa.o chfa.o aare_rules.o
ar ${ARFLAGS} $@ $^
regexp.o : regexp.cc apparmor_re.h
$(LINK.cc) $< -c -o $@
expr-tree.o: expr-tree.cc expr-tree.h
regexp.cc : regexp.y flex-tables.h ../immunix.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 chfa.h parse.h ../immunix.h
chfa.o: chfa.cc chfa.h ../immunix.h
parse.o : parse.cc apparmor_re.h expr-tree.h
parse.cc : parse.y parse.h flex-tables.h ../immunix.h
${BISON} -o $@ $<
clean:
rm -f regexp.o regexp.cc regexp.so regexp.a regexp ${TARGET}
rm -f *.o parse.cc ${TARGET}

View File

@@ -0,0 +1,336 @@
/*
* (C) 2006, 2007 Andreas Gruenbacher <agruen@suse.de>
* Copyright (c) 2003-2008 Novell, Inc. (All rights reserved)
* Copyright 2009-2012 Canonical Ltd.
*
* The libapparmor library is licensed under the terms of the GNU
* Lesser General Public License, version 2.1. Please see the file
* COPYING.LGPL.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
* Wrapper around the dfa to convert aa rules into a dfa
*/
#include <ostream>
#include <iostream>
#include <fstream>
#include <sstream>
#include <ext/stdio_filebuf.h>
#include <assert.h>
#include <stdlib.h>
#include "aare_rules.h"
#include "expr-tree.h"
#include "parse.h"
#include "hfa.h"
#include "chfa.h"
#include "../immunix.h"
struct aare_ruleset {
int reverse;
Node *root;
};
extern "C" aare_ruleset_t *aare_new_ruleset(int reverse)
{
aare_ruleset_t *container = (aare_ruleset_t *) malloc(sizeof(aare_ruleset_t));
if (!container)
return NULL;
container->root = NULL;
container->reverse = reverse;
return container;
}
extern "C" void aare_delete_ruleset(aare_ruleset_t *rules)
{
if (rules) {
if (rules->root)
rules->root->release();
free(rules);
}
}
extern "C" int aare_add_rule(aare_ruleset_t *rules, char *rule, int deny,
uint32_t perms, uint32_t audit, dfaflags_t flags)
{
return aare_add_rule_vec(rules, deny, perms, audit, 1, &rule, flags);
}
#define FLAGS_WIDTH 2
#define MATCH_FLAGS_SIZE (sizeof(uint32_t) * 8 - 1)
MatchFlag *match_flags[FLAGS_WIDTH][MATCH_FLAGS_SIZE];
DenyMatchFlag *deny_flags[FLAGS_WIDTH][MATCH_FLAGS_SIZE];
#define EXEC_MATCH_FLAGS_SIZE (AA_EXEC_COUNT *2 * 2 * 2) /* double for each of ix pux, unsafe x bits * u::o */
MatchFlag *exec_match_flags[FLAGS_WIDTH][EXEC_MATCH_FLAGS_SIZE]; /* mods + unsafe + ix + pux * u::o */
ExactMatchFlag *exact_match_flags[FLAGS_WIDTH][EXEC_MATCH_FLAGS_SIZE]; /* mods + unsafe + ix + pux *u::o */
extern "C" void aare_reset_matchflags(void)
{
uint32_t i, j;
#define RESET_FLAGS(group, size) { \
for (i = 0; i < FLAGS_WIDTH; i++) { \
for (j = 0; j < size; j++) { \
if ((group)[i][j]) delete (group)[i][j]; \
(group)[i][j] = NULL; \
} \
} \
}
RESET_FLAGS(match_flags, MATCH_FLAGS_SIZE);
RESET_FLAGS(deny_flags, MATCH_FLAGS_SIZE);
RESET_FLAGS(exec_match_flags, EXEC_MATCH_FLAGS_SIZE);
RESET_FLAGS(exact_match_flags, EXEC_MATCH_FLAGS_SIZE);
#undef RESET_FLAGS
}
extern "C" int aare_add_rule_vec(aare_ruleset_t *rules, int deny,
uint32_t perms, uint32_t audit,
int count, char **rulev, dfaflags_t flags)
{
Node *tree = NULL, *accept;
int exact_match;
uint32_t allow = perms;
assert(perms != 0);
if (regex_parse(&tree, rulev[0]))
return 0;
for (int i = 1; i < count; i++) {
Node *subtree = NULL;
Node *node = new CharNode(0);
if (!node)
return 0;
tree = new CatNode(tree, node);
if (regex_parse(&subtree, rulev[i]))
return 0;
tree = new CatNode(tree, subtree);
}
/*
* Check if we have an expression with or without wildcards. This
* determines how exec modifiers are merged in accept_perms() based
* on how we split permission bitmasks here.
*/
exact_match = 1;
for (depth_first_traversal i(tree); i; i++) {
if (dynamic_cast<StarNode *>(*i) ||
dynamic_cast<PlusNode *>(*i) ||
dynamic_cast<AnyCharNode *>(*i) ||
dynamic_cast<CharSetNode *>(*i) ||
dynamic_cast<NotCharSetNode *>(*i))
exact_match = 0;
}
if (rules->reverse)
flip_tree(tree);
/* 0x7f == 4 bits x mods + 1 bit unsafe mask + 1 bit ix, + 1 pux after shift */
#define EXTRACT_X_INDEX(perm, shift) (((perm) >> (shift + 7)) & 0x7f)
//if (perms & ALL_AA_EXEC_TYPE && (!perms & AA_EXEC_BITS))
// fprintf(stderr, "adding X rule without MAY_EXEC: 0x%x %s\n", perms, rulev[0]);
//if (perms & ALL_EXEC_TYPE)
// fprintf(stderr, "adding X rule %s 0x%x\n", rulev[0], perms);
//if (audit)
//fprintf(stderr, "adding rule with audit bits set: 0x%x %s\n", audit, rulev[0]);
//if (perms & AA_CHANGE_HAT)
// fprintf(stderr, "adding change_hat rule %s\n", rulev[0]);
/* the permissions set is assumed to be non-empty if any audit
* bits are specified */
accept = NULL;
for (unsigned int n = 0; perms && n < (sizeof(perms) * 8); n++) {
uint32_t mask = 1 << n;
if (!(perms & mask))
continue;
int ai = audit & mask ? 1 : 0;
perms &= ~mask;
Node *flag;
if (mask & ALL_AA_EXEC_TYPE)
/* these cases are covered by EXEC_BITS */
continue;
if (deny) {
if (deny_flags[ai][n]) {
flag = deny_flags[ai][n];
} else {
//fprintf(stderr, "Adding deny ai %d mask 0x%x audit 0x%x\n", ai, mask, audit & mask);
deny_flags[ai][n] = new DenyMatchFlag(mask, audit & mask);
flag = deny_flags[ai][n];
}
} else if (mask & AA_EXEC_BITS) {
uint32_t eperm = 0;
uint32_t index = 0;
if (mask & AA_USER_EXEC) {
eperm = mask | (perms & AA_USER_EXEC_TYPE);
index = EXTRACT_X_INDEX(eperm, AA_USER_SHIFT);
} else {
eperm = mask | (perms & AA_OTHER_EXEC_TYPE);
index = EXTRACT_X_INDEX(eperm, AA_OTHER_SHIFT) + (AA_EXEC_COUNT << 2);
}
//fprintf(stderr, "index %d eperm 0x%x\n", index, eperm);
if (exact_match) {
if (exact_match_flags[ai][index]) {
flag = exact_match_flags[ai][index];
} else {
exact_match_flags[ai][index] = new ExactMatchFlag(eperm, audit & mask);
flag = exact_match_flags[ai][index];
}
} else {
if (exec_match_flags[ai][index]) {
flag = exec_match_flags[ai][index];
} else {
exec_match_flags[ai][index] = new MatchFlag(eperm, audit & mask);
flag = exec_match_flags[ai][index];
}
}
} else {
if (match_flags[ai][n]) {
flag = match_flags[ai][n];
} else {
match_flags[ai][n] = new MatchFlag(mask, audit & mask);
flag = match_flags[ai][n];
}
}
if (accept)
accept = new AltNode(accept, flag);
else
accept = flag;
} /* for ... */
if (flags & DFA_DUMP_RULE_EXPR) {
cerr << "rule: ";
cerr << rulev[0];
for (int i = 1; i < count; i++) {
cerr << "\\x00";
cerr << rulev[i];
}
cerr << " -> ";
tree->dump(cerr);
if (deny)
cerr << " deny";
cerr << " (0x" << hex << allow <<"/" << audit << dec << ")";
accept->dump(cerr);
cerr << "\n\n";
}
if (rules->root)
rules->root = new AltNode(rules->root, new CatNode(tree, accept));
else
rules->root = new CatNode(tree, accept);
return 1;
}
/* create a dfa from the ruleset
* returns: buffer contain dfa tables, @size set to the size of the tables
* else NULL on failure
*/
extern "C" void *aare_create_dfa(aare_ruleset_t *rules, size_t *size,
dfaflags_t flags)
{
char *buffer = NULL;
label_nodes(rules->root);
if (flags & DFA_DUMP_TREE) {
cerr << "\nDFA: Expression Tree\n";
rules->root->dump(cerr);
cerr << "\n\n";
}
if (flags & DFA_CONTROL_TREE_SIMPLE) {
rules->root = simplify_tree(rules->root, flags);
if (flags & DFA_DUMP_SIMPLE_TREE) {
cerr << "\nDFA: Simplified Expression Tree\n";
rules->root->dump(cerr);
cerr << "\n\n";
}
}
stringstream stream;
try {
DFA dfa(rules->root, flags);
if (flags & DFA_DUMP_UNIQ_PERMS)
dfa.dump_uniq_perms("dfa");
if (flags & DFA_CONTROL_MINIMIZE) {
dfa.minimize(flags);
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);
if (flags & DFA_DUMP_STATES)
dfa.dump(cerr);
if (flags & DFA_DUMP_GRAPH)
dfa.dump_dot_graph(cerr);
map<uchar, uchar> eq;
if (flags & DFA_CONTROL_EQUIV) {
eq = dfa.equivalence_classes(flags);
dfa.apply_equivalence_classes(eq);
if (flags & DFA_DUMP_EQUIV) {
cerr << "\nDFA equivalence class\n";
dump_equivalence_classes(cerr, eq);
}
} else if (flags & DFA_DUMP_EQUIV)
cerr << "\nDFA did not generate an equivalence class\n";
CHFA chfa(dfa, eq, flags);
if (flags & DFA_DUMP_TRANS_TABLE)
chfa.dump(cerr);
chfa.flex_table(stream, "");
}
catch(int error) {
*size = 0;
return NULL;
}
stringbuf *buf = stream.rdbuf();
buf->pubseekpos(0);
*size = buf->in_avail();
buffer = (char *)malloc(*size);
if (!buffer)
return NULL;
buf->sgetn(buffer, *size);
return buffer;
}

View File

@@ -0,0 +1,49 @@
/*
* (C) 2006, 2007 Andreas Gruenbacher <agruen@suse.de>
* Copyright (c) 2003-2008 Novell, Inc. (All rights reserved)
* Copyright 2009-2012 Canonical Ltd.
*
* The libapparmor library is licensed under the terms of the GNU
* Lesser General Public License, version 2.1. Please see the file
* COPYING.LGPL.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
* Wrapper around the dfa to convert aa rules into a dfa
*/
#ifndef __LIBAA_RE_RULES_H
#define __LIBAA_RE_RULES_H
#include <stdint.h>
#include "apparmor_re.h"
#ifdef __cplusplus
extern "C" {
#endif
struct aare_ruleset;
typedef struct aare_ruleset aare_ruleset_t;
aare_ruleset_t *aare_new_ruleset(int reverse);
void aare_delete_ruleset(aare_ruleset_t *rules);
int aare_add_rule(aare_ruleset_t *rules, char *rule, int deny, uint32_t perms,
uint32_t audit, dfaflags_t flags);
int aare_add_rule_vec(aare_ruleset_t *rules, int deny, uint32_t perms,
uint32_t audit, int count, char **rulev,
dfaflags_t flags);
void *aare_create_dfa(aare_ruleset_t *rules, size_t *size, dfaflags_t flags);
void aare_reset_matchflags(void);
#ifdef __cplusplus
}
#endif
#endif /* __LIBAA_RE_RULES_H */

View File

@@ -1,11 +1,20 @@
/*
* Copyright (c) 2003, 2004, 2005, 2006, 2007 Novell, Inc.
* (All rights reserved)
* (C) 2006, 2007 Andreas Gruenbacher <agruen@suse.de>
* Copyright (c) 2003-2008 Novell, Inc. (All rights reserved)
* Copyright 2009-2012 Canonical Ltd.
*
* The libapparmor library is licensed under the terms of the GNU
* Lesser General Public License, version 2.1. Please see the file
* COPYING.LGPL.
*/
* The libapparmor library is licensed under the terms of the GNU
* Lesser General Public License, version 2.1. Please see the file
* COPYING.LGPL.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef APPARMOR_RE_H
#define APPARMOR_RE_H
@@ -17,10 +26,11 @@ 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,
DFA_DUMP_MIN_PARTS = 1 << 13,
DFA_DUMP_UNIQ_PERMS = 1 << 14,
DFA_DUMP_MIN_UNIQ_PERMS = 1 << 15,
DFA_DUMP_TREE_STATS = 1 << 16,
@@ -41,25 +51,4 @@ typedef enum dfaflags {
DFA_DUMP_NODE_TO_DFA = 1 << 31,
} dfaflags_t;
#ifdef __cplusplus
extern "C" {
#endif
struct aare_ruleset;
typedef struct aare_ruleset aare_ruleset_t;
aare_ruleset_t *aare_new_ruleset(int reverse);
void aare_delete_ruleset(aare_ruleset_t *rules);
int aare_add_rule(aare_ruleset_t *rules, char *rule, int deny,
uint32_t perms, uint32_t audit, dfaflags_t flags);
int aare_add_rule_vec(aare_ruleset_t *rules, int deny, uint32_t perms,
uint32_t audit, int count, char **rulev, dfaflags_t flags);
void *aare_create_dfa(aare_ruleset_t *rules, size_t *size, dfaflags_t flags);
void aare_reset_matchflags(void);
#ifdef __cplusplus
}
#endif
#endif /* APPARMOR_RE_H */

View File

@@ -0,0 +1,420 @@
/*
* (C) 2006, 2007 Andreas Gruenbacher <agruen@suse.de>
* Copyright (c) 2003-2008 Novell, Inc. (All rights reserved)
* Copyright 2009-2012 Canonical Ltd.
*
* The libapparmor library is licensed under the terms of the GNU
* Lesser General Public License, version 2.1. Please see the file
* COPYING.LGPL.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
* Create a compressed hfa from and hfa
*/
#include <map>
#include <vector>
#include <ostream>
#include <iostream>
#include <fstream>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include "hfa.h"
#include "chfa.h"
#include "../immunix.h"
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++) {
if (prev)
free_list[prev].second = i;
free_list[i].first = prev;
prev = i;
}
free_list[free_list.size() - 1].second = 0;
}
/**
* new Construct the transition table.
*/
CHFA::CHFA(DFA &dfa, map<uchar, uchar> &eq, dfaflags_t flags): eq(eq)
{
if (flags & DFA_DUMP_TRANS_PROGRESS)
fprintf(stderr, "Compressing HFA:\r");
if (eq.empty())
max_eq = 255;
else {
max_eq = 0;
for (map<uchar, uchar>::iterator i = eq.begin();
i != eq.end(); i++) {
if (i->second > max_eq)
max_eq = i->second;
}
}
/* Do initial setup adding up all the transitions and sorting by
* transition count.
*/
size_t optimal = 2;
multimap<size_t, State *> order;
vector<pair<size_t, size_t> > free_list;
for (Partition::iterator i = dfa.states.begin(); i != dfa.states.end(); i++) {
if (*i == dfa.start || *i == dfa.nonmatching)
continue;
optimal += (*i)->trans.size();
if (flags & DFA_CONTROL_TRANS_HIGH) {
size_t range = 0;
if ((*i)->trans.size())
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));
}
}
/* Insert the dummy nonmatching transition by hand */
next_check.push_back(make_pair(dfa.nonmatching, dfa.nonmatching));
default_base.push_back(make_pair(dfa.nonmatching, 0));
num.insert(make_pair(dfa.nonmatching, num.size()));
accept.resize(dfa.states.size());
accept2.resize(dfa.states.size());
next_check.resize(optimal);
free_list.resize(optimal);
accept[0] = 0;
accept2[0] = 0;
first_free = 1;
init_free_list(free_list, 0, 1);
insert_state(free_list, dfa.start, dfa);
accept[1] = 0;
accept2[1] = 0;
num.insert(make_pair(dfa.start, num.size()));
int count = 2;
if (!(flags & DFA_CONTROL_TRANS_HIGH)) {
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)->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)) {
count++;
if (count % 100 == 0)
fprintf(stderr, "\033[2KCompressing trans table: insert state: %d/%zd\r",
count, dfa.states.size());
}
}
} else {
for (multimap<size_t, State *>::iterator i = order.begin();
i != order.end(); i++) {
if (i->second != dfa.nonmatching &&
i->second != dfa.start) {
insert_state(free_list, i->second, dfa);
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)) {
count++;
if (count % 100 == 0)
fprintf(stderr, "\033[2KCompressing trans table: insert state: %d/%zd\r",
count, dfa.states.size());
}
}
}
if (flags & (DFA_DUMP_TRANS_STATS | DFA_DUMP_TRANS_PROGRESS)) {
ssize_t size = 4 * next_check.size() + 6 * dfa.states.size();
fprintf(stderr, "\033[2KCompressed trans table: states %zd, next/check %zd, optimal next/check %zd avg/state %.2f, compression %zd/%zd = %.2f %%\n",
dfa.states.size(), next_check.size(), optimal,
(float)next_check.size() / (float)dfa.states.size(),
size, 512 * dfa.states.size(),
100.0 - ((float)size * 100.0 /(float)(512 * dfa.states.size())));
}
}
/**
* Does <trans> fit into position <base> of the transition table?
*/
bool CHFA::fits_in(vector<pair<size_t, size_t> > &free_list
__attribute__ ((unused)), size_t pos,
StateTrans &trans)
{
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 */
if (c >= next_check.size())
return true;
if (next_check[c].second)
return false;
}
return true;
}
/**
* Insert <state> of <dfa> into the transition table.
*/
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;
StateTrans &trans = from->trans;
size_t c = trans.begin()->first;
size_t prev = 0;
size_t x = first_free;
if (from->otherwise)
default_state = from->otherwise;
if (trans.empty())
goto do_insert;
repeat:
resize = 0;
/* get the first free entry that won't underflow */
while (x && (x < c)) {
prev = x;
x = free_list[x].second;
}
/* try inserting until we succeed. */
while (x && !fits_in(free_list, x, trans)) {
prev = x;
x = free_list[x].second;
}
if (!x) {
resize = 256 - trans.begin()->first;
x = free_list.size();
/* set prev to last free */
} 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;
}
if (resize) {
/* expand next_check and free_list */
size_t old_size = free_list.size();
next_check.resize(next_check.size() + resize);
free_list.resize(free_list.size() + resize);
init_free_list(free_list, prev, old_size);
if (!first_free)
first_free = old_size;;
if (x == old_size)
goto repeat;
}
base = x - c;
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;
if (prev)
free_list[prev].second = next;
if (next)
free_list[next].first = prev;
if (base + j->first == first_free)
first_free = next;
}
do_insert:
default_base.push_back(make_pair(default_state, base));
}
/**
* Text-dump the transition table (for debugging).
*/
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++) {
st.insert(make_pair(i->second, i->first));
}
os << "size=" << default_base.size() << " (accept, default, base): {state} -> {default state}" << "\n";
for (size_t i = 0; i < default_base.size(); i++) {
os << i << ": ";
os << "(" << accept[i] << ", " << num[default_base[i].first]
<< ", " << default_base[i].second << ")";
if (st[i])
os << " " << *st[i];
if (default_base[i].first)
os << " -> " << *default_base[i].first;
os << "\n";
}
os << "size=" << next_check.size() << " (next, check): {check state} -> {next state} : offset from base\n";
for (size_t i = 0; i < next_check.size(); i++) {
if (!next_check[i].second)
continue;
os << i << ": ";
if (next_check[i].second) {
os << "(" << num[next_check[i].first] << ", "
<< num[next_check[i].second] << ")" << " "
<< *next_check[i].second << " -> "
<< *next_check[i].first << ": ";
size_t offs = i - default_base[num[next_check[i].second]].second;
if (eq.size())
os << offs;
else
os << (uchar) offs;
}
os << "\n";
}
}
/**
* Create a flex-style binary dump of the DFA tables. The table format
* was partly reverse engineered from the flex sources and from
* examining the tables that flex creates with its --tables-file option.
* (Only the -Cf and -Ce formats are currently supported.)
*/
#include "flex-tables.h"
#define YYTH_REGEX_MAGIC 0x1B5E783D
static inline size_t pad64(size_t i)
{
return (i + (size_t) 7) & ~(size_t) 7;
}
string fill64(size_t i)
{
const char zeroes[8] = { };
string fill(zeroes, (i & 7) ? 8 - (i & 7) : 0);
return fill;
}
template<class Iter> size_t flex_table_size(Iter pos, Iter end)
{
return pad64(sizeof(struct table_header) + sizeof(*pos) * (end - pos));
}
template<class Iter>
void write_flex_table(ostream &os, int id, Iter pos, Iter end)
{
struct table_header td = { 0, 0, 0, 0 };
size_t size = end - pos;
td.td_id = htons(id);
td.td_flags = htons(sizeof(*pos));
td.td_lolen = htonl(size);
os.write((char *)&td, sizeof(td));
for (; pos != end; ++pos) {
switch (sizeof(*pos)) {
case 4:
os.put((char)(*pos >> 24));
os.put((char)(*pos >> 16));
case 2:
os.put((char)(*pos >> 8));
case 1:
os.put((char)*pos);
}
}
os << fill64(sizeof(td) + sizeof(*pos) * size);
}
void CHFA::flex_table(ostream &os, const char *name)
{
const char th_version[] = "notflex";
struct table_set_header th = { 0, 0, 0, 0 };
/**
* Change the following two data types to adjust the maximum flex
* table size.
*/
typedef uint16_t state_t;
typedef uint32_t trans_t;
if (default_base.size() >= (state_t) - 1) {
cerr << "Too many states (" << default_base.size() << ") for "
"type state_t\n";
exit(1);
}
if (next_check.size() >= (trans_t) - 1) {
cerr << "Too many transitions (" << next_check.size()
<< ") for " "type trans_t\n";
exit(1);
}
/**
* Create copies of the data structures so that we can dump the tables
* using the generic write_flex_table() routine.
*/
vector<uint8_t> equiv_vec;
if (eq.size()) {
equiv_vec.resize(256);
for (map<uchar, uchar>::iterator i = eq.begin(); i != eq.end(); i++) {
equiv_vec[i->first] = i->second;
}
}
vector<state_t> default_vec;
vector<trans_t> base_vec;
for (DefaultBase::iterator i = default_base.begin(); i != default_base.end(); i++) {
default_vec.push_back(num[i->first]);
base_vec.push_back(i->second);
}
vector<state_t> next_vec;
vector<state_t> check_vec;
for (NextCheck::iterator i = next_check.begin(); i != next_check.end(); i++) {
next_vec.push_back(num[i->first]);
check_vec.push_back(num[i->second]);
}
/* Write the actual flex parser table. */
size_t hsize = pad64(sizeof(th) + sizeof(th_version) + strlen(name) + 1);
th.th_magic = htonl(YYTH_REGEX_MAGIC);
th.th_hsize = htonl(hsize);
th.th_ssize = htonl(hsize +
flex_table_size(accept.begin(), accept.end()) +
flex_table_size(accept2.begin(), accept2.end()) +
(eq.size() ? flex_table_size(equiv_vec.begin(), equiv_vec.end()) : 0) +
flex_table_size(base_vec.begin(), base_vec.end()) +
flex_table_size(default_vec.begin(), default_vec.end()) +
flex_table_size(next_vec.begin(), next_vec.end()) +
flex_table_size(check_vec.begin(), check_vec.end()));
os.write((char *)&th, sizeof(th));
os << th_version << (char)0 << name << (char)0;
os << fill64(sizeof(th) + sizeof(th_version) + strlen(name) + 1);
write_flex_table(os, YYTD_ID_ACCEPT, accept.begin(), accept.end());
write_flex_table(os, YYTD_ID_ACCEPT2, accept2.begin(), accept2.end());
if (eq.size())
write_flex_table(os, YYTD_ID_EC, equiv_vec.begin(),
equiv_vec.end());
write_flex_table(os, YYTD_ID_BASE, base_vec.begin(), base_vec.end());
write_flex_table(os, YYTD_ID_DEF, default_vec.begin(), default_vec.end());
write_flex_table(os, YYTD_ID_NXT, next_vec.begin(), next_vec.end());
write_flex_table(os, YYTD_ID_CHK, check_vec.begin(), check_vec.end());
}

View File

@@ -0,0 +1,56 @@
/*
* (C) 2006, 2007 Andreas Gruenbacher <agruen@suse.de>
* Copyright (c) 2003-2008 Novell, Inc. (All rights reserved)
* Copyright 2009-2012 Canonical Ltd.
*
* The libapparmor library is licensed under the terms of the GNU
* Lesser General Public License, version 2.1. Please see the file
* COPYING.LGPL.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
* Create a compressed hfa (chfa) from and hfa
*/
#ifndef __LIBAA_RE_CHFA_H
#define __LIBAA_RE_CHFA_H
#include <map>
#include <vector>
#include "hfa.h"
using namespace std;
class CHFA {
typedef vector<pair<const State *, size_t> > DefaultBase;
typedef vector<pair<const State *, const State *> > NextCheck;
public:
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,
StateTrans &cases);
void insert_state(vector<pair<size_t, size_t> > &free_list,
State *state, DFA &dfa);
private:
vector<uint32_t> accept;
vector<uint32_t> accept2;
DefaultBase default_base;
NextCheck next_check;
map<const State *, size_t> num;
map<uchar, uchar> &eq;
uchar max_eq;
size_t first_free;
};
#endif /* __LIBAA_RE_CHFA_H */

View File

@@ -0,0 +1,610 @@
/*
* (C) 2006, 2007 Andreas Gruenbacher <agruen@suse.de>
* Copyright (c) 2003-2008 Novell, Inc. (All rights reserved)
* Copyright 2009-2012 Canonical Ltd.
*
* The libapparmor library is licensed under the terms of the GNU
* Lesser General Public License, version 2.1. Please see the file
* COPYING.LGPL.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
* Functions to create/manipulate an expression tree for regular expressions
* that have been parsed.
*
* The expression tree can be used directly after the parse creates it, or
* it can be factored so that the set of important nodes is smaller.
* Having a reduced set of important nodes generally results in a dfa that
* is closer to minimum (fewer redundant states are created). It also
* results in fewer important nodes in a the state set during subset
* construction resulting in less memory used to create a dfa.
*
* Generally it is worth doing expression tree simplification before dfa
* construction, if the regular expression tree contains any alternations.
* Even if the regular expression doesn't simplification should be fast
* enough that it can be used with minimal overhead.
*/
#include <stdio.h>
#include <string.h>
#include "expr-tree.h"
#include "apparmor_re.h"
/* Use a single static EpsNode as it carries no node specific information */
EpsNode epsnode;
ostream &operator<<(ostream &os, uchar c)
{
const char *search = "\a\033\f\n\r\t|*+[](). ",
*replace = "aefnrt|*+[](). ", *s;
if ((s = strchr(search, c)) && *s != '\0') {
os << '\\' << replace[s - search];
} else if (c < 32 || c >= 127) {
os << '\\' << '0' << char ('0' + (c >> 6))
<< char ('0' + ((c >> 3) & 7)) << char ('0' + (c & 7));
} else {
os << (char)c;
}
return os;
}
/**
* Text-dump a state (for debugging).
*/
ostream &operator<<(ostream &os, const NodeSet &state)
{
os << '{';
if (!state.empty()) {
NodeSet::iterator i = state.begin();
for (;;) {
os << (*i)->label;
if (++i == state.end())
break;
os << ',';
}
}
os << '}';
return os;
}
ostream &operator<<(ostream &os, Node &node)
{
node.dump(os);
return os;
}
/**
* hash_NodeSet - generate a hash for the Nodes in the set
*/
unsigned long hash_NodeSet(NodeSet *ns)
{
unsigned long hash = 5381;
for (NodeSet::iterator i = ns->begin(); i != ns->end(); i++) {
hash = ((hash << 5) + hash) + (unsigned long)*i;
}
return hash;
}
/**
* label_nodes - label the node positions for pretty-printing debug output
*
* TODO: separate - node labels should be separate and optional, if not
* present pretty printing should use Node address
*/
void label_nodes(Node *root)
{
int nodes = 1;
for (depth_first_traversal i(root); i; i++)
i->label = nodes++;
}
/**
* Text-dump the syntax tree (for debugging).
*/
void Node::dump_syntax_tree(ostream &os)
{
for (depth_first_traversal i(this); i; i++) {
os << i->label << '\t';
if ((*i)->child[0] == 0)
os << **i << '\t' << (*i)->followpos << endl;
else {
if ((*i)->child[1] == 0)
os << (*i)->child[0]->label << **i;
else
os << (*i)->child[0]->label << **i
<< (*i)->child[1]->label;
os << '\t' << (*i)->firstpos << (*i)->lastpos << endl;
}
}
os << endl;
}
/*
* Normalize the regex parse tree for factoring and cancelations. Normalization
* reorganizes internal (alt and cat) nodes into a fixed "normalized" form that
* simplifies factoring code, in that it produces a canonicalized form for
* the direction being normalized so that the factoring code does not have
* to consider as many cases.
*
* left normalization (dir == 0) uses these rules
* (E | a) -> (a | E)
* (a | b) | c -> a | (b | c)
* (ab)c -> a(bc)
*
* right normalization (dir == 1) uses the same rules but reversed
* (a | E) -> (E | a)
* a | (b | c) -> (a | b) | c
* a(bc) -> (ab)c
*
* Note: This is written iteratively for a given node (the top node stays
* fixed and the children are rotated) instead of recursively.
* For a given node under examination rotate over nodes from
* dir to !dir. Until no dir direction node meets the criterial.
* Then recurse to the children (which will have a different node type)
* to make sure they are normalized.
* Normalization of a child node is guarenteed to not affect the
* normalization of the parent.
*
* For cat nodes the depth first traverse order is guarenteed to be
* maintained. This is not necessary for altnodes.
*
* Eg. For left normalization
*
* |1 |1
* / \ / \
* |2 T -> a |2
* / \ / \
* |3 c b |3
* / \ / \
* a b c T
*
*/
static void rotate_node(Node *t, int dir)
{
// (a | b) | c -> a | (b | c)
// (ab)c -> a(bc)
Node *left = t->child[dir];
t->child[dir] = left->child[dir];
left->child[dir] = left->child[!dir];
left->child[!dir] = t->child[!dir];
t->child[!dir] = left;
}
void normalize_tree(Node *t, int dir)
{
if (dynamic_cast<LeafNode *>(t))
return;
for (;;) {
if (dynamic_cast<TwoChildNode *>(t) &&
(&epsnode == t->child[dir]) &&
(&epsnode != t->child[!dir])) {
// (E | a) -> (a | E)
// Ea -> aE
// 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) &&
dynamic_cast<AltNode *>(t->child[dir])) ||
(dynamic_cast<CatNode *>(t) &&
dynamic_cast<CatNode *>(t->child[dir]))) {
// (a | b) | c -> a | (b | c)
// (ab)c -> a(bc)
rotate_node(t, dir);
} else if (dynamic_cast<AltNode *>(t) &&
dynamic_cast<CharSetNode *>(t->child[dir]) &&
dynamic_cast<CharNode *>(t->child[!dir])) {
// [a] | b -> b | [a]
Node *c = t->child[dir];
t->child[dir] = t->child[!dir];
t->child[!dir] = c;
} else {
break;
}
}
if (t->child[dir])
normalize_tree(t->child[dir], dir);
if (t->child[!dir])
normalize_tree(t->child[!dir], dir);
}
//charset conversion is disabled for now,
//it hinders tree optimization in some cases, so it need to be either
//done post optimization, or have extra factoring rules added
#if 0
static Node *merge_charset(Node *a, Node *b)
{
if (dynamic_cast<CharNode *>(a) && dynamic_cast<CharNode *>(b)) {
Chars chars;
chars.insert(dynamic_cast<CharNode *>(a)->c);
chars.insert(dynamic_cast<CharNode *>(b)->c);
CharSetNode *n = new CharSetNode(chars);
return n;
} else if (dynamic_cast<CharNode *>(a) &&
dynamic_cast<CharSetNode *>(b)) {
Chars *chars = &dynamic_cast<CharSetNode *>(b)->chars;
chars->insert(dynamic_cast<CharNode *>(a)->c);
return b;
} else if (dynamic_cast<CharSetNode *>(a) &&
dynamic_cast<CharSetNode *>(b)) {
Chars *from = &dynamic_cast<CharSetNode *>(a)->chars;
Chars *to = &dynamic_cast<CharSetNode *>(b)->chars;
for (Chars::iterator i = from->begin(); i != from->end(); i++)
to->insert(*i);
return b;
}
//return ???;
}
static Node *alt_to_charsets(Node *t, int dir)
{
/*
Node *first = NULL;
Node *p = t;
Node *i = t;
for (;dynamic_cast<AltNode *>(i);) {
if (dynamic_cast<CharNode *>(i->child[dir]) ||
dynamic_cast<CharNodeSet *>(i->child[dir])) {
if (!first) {
first = i;
p = i;
i = i->child[!dir];
} else {
first->child[dir] = merge_charset(first->child[dir],
i->child[dir]);
p->child[!dir] = i->child[!dir];
Node *tmp = i;
i = tmp->child[!dir];
tmp->child[!dir] = NULL;
tmp->release();
}
} else {
p = i;
i = i->child[!dir];
}
}
// last altnode of chain check other dir as well
if (first && (dynamic_cast<charNode *>(i) ||
dynamic_cast<charNodeSet *>(i))) {
}
*/
/*
if (dynamic_cast<CharNode *>(t->child[dir]) ||
dynamic_cast<CharSetNode *>(t->child[dir]))
char_test = true;
(char_test &&
(dynamic_cast<CharNode *>(i->child[dir]) ||
dynamic_cast<CharSetNode *>(i->child[dir])))) {
*/
return t;
}
#endif
static Node *basic_alt_factor(Node *t, int dir)
{
if (!dynamic_cast<AltNode *>(t))
return t;
if (t->child[dir]->eq(t->child[!dir])) {
// (a | a) -> a
Node *tmp = t->child[dir];
t->child[dir] = NULL;
t->release();
return tmp;
}
// (ab) | (ac) -> a(b|c)
if (dynamic_cast<CatNode *>(t->child[dir]) &&
dynamic_cast<CatNode *>(t->child[!dir]) &&
t->child[dir]->child[dir]->eq(t->child[!dir]->child[dir])) {
// (ab) | (ac) -> a(b|c)
Node *left = t->child[dir];
Node *right = t->child[!dir];
t->child[dir] = left->child[!dir];
t->child[!dir] = right->child[!dir];
right->child[!dir] = NULL;
right->release();
left->child[!dir] = t;
return left;
}
// a | (ab) -> a (E | b) -> a (b | E)
if (dynamic_cast<CatNode *>(t->child[!dir]) &&
t->child[dir]->eq(t->child[!dir]->child[dir])) {
Node *c = t->child[!dir];
t->child[dir]->release();
t->child[dir] = c->child[!dir];
t->child[!dir] = &epsnode;
c->child[!dir] = t;
return c;
}
// ab | (a) -> a (b | E)
if (dynamic_cast<CatNode *>(t->child[dir]) &&
t->child[dir]->child[dir]->eq(t->child[!dir])) {
Node *c = t->child[dir];
t->child[!dir]->release();
t->child[dir] = c->child[!dir];
t->child[!dir] = &epsnode;
c->child[!dir] = t;
return c;
}
return t;
}
static Node *basic_simplify(Node *t, int dir)
{
if (dynamic_cast<CatNode *>(t) && &epsnode == t->child[!dir]) {
// aE -> a
Node *tmp = t->child[dir];
t->child[dir] = NULL;
t->release();
return tmp;
}
return basic_alt_factor(t, dir);
}
/*
* assumes a normalized tree. reductions shown for left normalization
* aE -> a
* (a | a) -> a
** factoring patterns
* a | (a | b) -> (a | b)
* a | (ab) -> a (E | b) -> a (b | E)
* (ab) | (ac) -> a(b|c)
*
* returns t - if no simplifications were made
* a new root node - if simplifications were made
*/
Node *simplify_tree_base(Node *t, int dir, bool &mod)
{
if (dynamic_cast<ImportantNode *>(t))
return t;
for (int i = 0; i < 2; i++) {
if (t->child[i]) {
Node *c = simplify_tree_base(t->child[i], dir, mod);
if (c != t->child[i]) {
t->child[i] = c;
mod = true;
}
}
}
// only iterate on loop if modification made
for (;; mod = true) {
Node *tmp = basic_simplify(t, dir);
if (tmp != t) {
t = tmp;
continue;
}
/* all tests after this must meet 2 alt node condition */
if (!dynamic_cast<AltNode *>(t) ||
!dynamic_cast<AltNode *>(t->child[!dir]))
break;
// a | (a | b) -> (a | b)
// a | (b | (c | a)) -> (b | (c | a))
Node *p = t;
Node *i = t->child[!dir];
for (; dynamic_cast<AltNode *>(i); p = i, i = i->child[!dir]) {
if (t->child[dir]->eq(i->child[dir])) {
Node *tmp = t->child[!dir];
t->child[!dir] = NULL;
t->release();
t = tmp;
continue;
}
}
// last altnode of chain check other dir as well
if (t->child[dir]->eq(p->child[!dir])) {
Node *tmp = t->child[!dir];
t->child[!dir] = NULL;
t->release();
t = tmp;
continue;
}
//exact match didn't work, try factoring front
//a | (ac | (ad | () -> (a (E | c)) | (...)
//ab | (ac | (...)) -> (a (b | c)) | (...)
//ab | (a | (...)) -> (a (b | E)) | (...)
Node *pp;
int count = 0;
Node *subject = t->child[dir];
Node *a = subject;
if (dynamic_cast<CatNode *>(subject))
a = subject->child[dir];
for (pp = p = t, i = t->child[!dir];
dynamic_cast<AltNode *>(i);) {
if ((dynamic_cast<CatNode *>(i->child[dir]) &&
a->eq(i->child[dir]->child[dir])) ||
(a->eq(i->child[dir]))) {
// extract matching alt node
p->child[!dir] = i->child[!dir];
i->child[!dir] = subject;
subject = basic_simplify(i, dir);
if (dynamic_cast<CatNode *>(subject))
a = subject->child[dir];
else
a = subject;
i = p->child[!dir];
count++;
} else {
pp = p;
p = i;
i = i->child[!dir];
}
}
// last altnode in chain check other dir as well
if ((dynamic_cast<CatNode *>(i) &&
a->eq(i->child[dir])) || (a->eq(i))) {
count++;
if (t == p) {
t->child[dir] = subject;
t = basic_simplify(t, dir);
} else {
t->child[dir] = p->child[dir];
p->child[dir] = subject;
pp->child[!dir] = basic_simplify(p, dir);
}
} else {
t->child[dir] = i;
p->child[!dir] = subject;
}
if (count == 0)
break;
}
return t;
}
int debug_tree(Node *t)
{
int nodes = 1;
if (!dynamic_cast<ImportantNode *>(t)) {
if (t->child[0])
nodes += debug_tree(t->child[0]);
if (t->child[1])
nodes += debug_tree(t->child[1]);
}
return nodes;
}
static void count_tree_nodes(Node *t, struct node_counts *counts)
{
if (dynamic_cast<AltNode *>(t)) {
counts->alt++;
count_tree_nodes(t->child[0], counts);
count_tree_nodes(t->child[1], counts);
} else if (dynamic_cast<CatNode *>(t)) {
counts->cat++;
count_tree_nodes(t->child[0], counts);
count_tree_nodes(t->child[1], counts);
} else if (dynamic_cast<PlusNode *>(t)) {
counts->plus++;
count_tree_nodes(t->child[0], counts);
} else if (dynamic_cast<StarNode *>(t)) {
counts->star++;
count_tree_nodes(t->child[0], counts);
} else if (dynamic_cast<CharNode *>(t)) {
counts->charnode++;
} else if (dynamic_cast<AnyCharNode *>(t)) {
counts->any++;
} else if (dynamic_cast<CharSetNode *>(t)) {
counts->charset++;
} else if (dynamic_cast<NotCharSetNode *>(t)) {
counts->notcharset++;
}
}
#include "stdio.h"
#include "stdint.h"
#include "apparmor_re.h"
Node *simplify_tree(Node *t, dfaflags_t flags)
{
bool update;
if (flags & DFA_DUMP_TREE_STATS) {
struct node_counts counts = { 0, 0, 0, 0, 0, 0, 0, 0 };
count_tree_nodes(t, &counts);
fprintf(stderr,
"expr tree: c %d, [] %d, [^] %d, | %d, + %d, * %d, . %d, cat %d\n",
counts.charnode, counts.charset, counts.notcharset,
counts.alt, counts.plus, counts.star, counts.any,
counts.cat);
}
do {
update = false;
//default to right normalize first as this reduces the number
//of trailing nodes which might follow an internal *
//or **, which is where state explosion can happen
//eg. in one test this makes the difference between
// the dfa having about 7 thousands states,
// and it having about 1.25 million states
int dir = 1;
if (flags & DFA_CONTROL_TREE_LEFT)
dir = 0;
for (int count = 0; count < 2; count++) {
bool modified;
do {
modified = false;
if (flags & DFA_CONTROL_TREE_NORMAL)
normalize_tree(t, dir);
t = simplify_tree_base(t, dir, modified);
if (modified)
update = true;
} while (modified);
if (flags & DFA_CONTROL_TREE_LEFT)
dir++;
else
dir--;
}
} while (update);
if (flags & DFA_DUMP_TREE_STATS) {
struct node_counts counts = { 0, 0, 0, 0, 0, 0, 0, 0 };
count_tree_nodes(t, &counts);
fprintf(stderr,
"simplified expr tree: c %d, [] %d, [^] %d, | %d, + %d, * %d, . %d, cat %d\n",
counts.charnode, counts.charset, counts.notcharset,
counts.alt, counts.plus, counts.star, counts.any,
counts.cat);
}
return t;
}
/**
* Flip the children of all cat nodes. This causes strings to be matched
* back-forth.
*/
void flip_tree(Node *node)
{
for (depth_first_traversal i(node); i; i++) {
if (CatNode *cat = dynamic_cast<CatNode *>(*i)) {
swap(cat->child[0], cat->child[1]);
}
}
}
void dump_regex_rec(ostream &os, Node *tree)
{
if (tree->child[0])
dump_regex_rec(os, tree->child[0]);
os << *tree;
if (tree->child[1])
dump_regex_rec(os, tree->child[1]);
}
void dump_regex(ostream &os, Node *tree)
{
dump_regex_rec(os, tree);
os << endl;
}

View File

@@ -0,0 +1,597 @@
/*
* (C) 2006, 2007 Andreas Gruenbacher <agruen@suse.de>
* Copyright (c) 2003-2008 Novell, Inc. (All rights reserved)
* Copyright 2009-2012 Canonical Ltd.
*
* The libapparmor library is licensed under the terms of the GNU
* Lesser General Public License, version 2.1. Please see the file
* COPYING.LGPL.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
* Functions to create/manipulate an expression tree for regular expressions
* that have been parsed.
*
* The expression tree can be used directly after the parse creates it, or
* it can be factored so that the set of important nodes is smaller.
* Having a reduced set of important nodes generally results in a dfa that
* is closer to minimum (fewer redundant states are created). It also
* results in fewer important nodes in a the state set during subset
* construction resulting in less memory used to create a dfa.
*
* Generally it is worth doing expression tree simplification before dfa
* construction, if the regular expression tree contains any alternations.
* Even if the regular expression doesn't simplification should be fast
* enough that it can be used with minimal overhead.
*/
#ifndef __LIBAA_RE_EXPR_H
#define __LIBAA_RE_EXPR_H
#include <map>
#include <set>
#include <stack>
#include <ostream>
#include <stdint.h>
#include "apparmor_re.h"
using namespace std;
typedef unsigned char uchar;
typedef set<uchar> Chars;
ostream &operator<<(ostream &os, uchar c);
/* Compute the union of two sets. */
template<class T> set<T> operator+(const set<T> &a, const set<T> &b)
{
set<T> c(a);
c.insert(b.begin(), b.end());
return c;
}
/**
* When creating DFAs from regex trees, a DFA state is constructed from
* a set of important nodes in the syntax tree. This includes AcceptNodes,
* which indicate that when a match ends in a particular state, the
* regular expressions that the AcceptNode belongs to match.
*/
class Node;
class ImportantNode;
typedef set<ImportantNode *> NodeSet;
/**
* Text-dump a state (for debugging).
*/
ostream &operator<<(ostream &os, const NodeSet &state);
/**
* Out-edges from a state to another: we store the follow-set of Nodes
* for each input character that is not a default match in
* cases (i.e., following a CharNode or CharSetNode), and default
* matches in otherwise as well as in all matching explicit cases
* (i.e., following an AnyCharNode or NotCharSetNode). This avoids
* enumerating all the explicit tranitions for default matches.
*/
typedef struct Cases {
typedef map<uchar, NodeSet *>::iterator iterator;
iterator begin() { return cases.begin(); }
iterator end() { return cases.end(); }
Cases(): otherwise(0) { }
map<uchar, NodeSet *> cases;
NodeSet *otherwise;
} Cases;
ostream &operator<<(ostream &os, Node &node);
/* An abstract node in the syntax tree. */
class Node {
public:
Node(): nullable(false) { child[0] = child[1] = 0; }
Node(Node *left): nullable(false)
{
child[0] = left;
child[1] = 0;
}
Node(Node *left, Node *right): nullable(false)
{
child[0] = left;
child[1] = right;
}
virtual ~Node()
{
if (child[0])
child[0]->release();
if (child[1])
child[1]->release();
}
/**
* See the "Dragon Book" for an explanation of nullable, firstpos,
* lastpos, and followpos.
*/
virtual void compute_nullable() { }
virtual void compute_firstpos() = 0;
virtual void compute_lastpos() = 0;
virtual void compute_followpos() { }
virtual int eq(Node *other) = 0;
virtual ostream &dump(ostream &os) = 0;
void dump_syntax_tree(ostream &os);
bool nullable;
NodeSet firstpos, lastpos, followpos;
/* child 0 is left, child 1 is right */
Node *child[2];
unsigned int label; /* unique number for debug etc */
/**
* We indirectly release Nodes through a virtual function because
* accept and Eps Nodes are shared, and must be treated specially.
* We could use full reference counting here but the indirect release
* is sufficient and has less overhead
*/
virtual void release(void) { delete this; }
};
class InnerNode: public Node {
public:
InnerNode(): Node() { };
InnerNode(Node *left): Node(left) { };
InnerNode(Node *left, Node *right): Node(left, right) { };
};
class OneChildNode: public InnerNode {
public:
OneChildNode(Node *left): InnerNode(left) { };
};
class TwoChildNode: public InnerNode {
public:
TwoChildNode(Node *left, Node *right): InnerNode(left, right) { };
};
class LeafNode: public Node {
public:
LeafNode(): Node() { };
};
/* Match nothing (//). */
class EpsNode: public LeafNode {
public:
EpsNode(): LeafNode()
{
nullable = true;
label = 0;
}
void release(void)
{
/* don't delete Eps nodes because there is a single static
* instance shared by all trees. Look for epsnode in the code
*/
}
void compute_firstpos() { }
void compute_lastpos() { }
int eq(Node *other)
{
if (dynamic_cast<EpsNode *>(other))
return 1;
return 0;
}
ostream &dump(ostream &os)
{
return os << "[]";
}
};
/**
* Leaf nodes in the syntax tree are important to us: they describe the
* characters that the regular expression matches. We also consider
* AcceptNodes import: they indicate when a regular expression matches.
*/
class ImportantNode: public LeafNode {
public:
ImportantNode(): LeafNode() { }
void compute_firstpos() { firstpos.insert(this); }
void compute_lastpos() { lastpos.insert(this); }
virtual void follow(Cases &cases) = 0;
virtual int is_accept(void) = 0;
};
/* common base class for all the different classes that contain
* character information.
*/
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(Cases &cases)
{
NodeSet **x = &cases.cases[c];
if (!*x) {
if (cases.otherwise)
*x = new NodeSet(*cases.otherwise);
else
*x = new NodeSet;
}
(*x)->insert(followpos.begin(), followpos.end());
}
int eq(Node *other)
{
CharNode *o = dynamic_cast<CharNode *>(other);
if (o) {
return c == o->c;
}
return 0;
}
ostream &dump(ostream &os)
{
return os << c;
}
uchar c;
};
/* Match a set of characters (/[abc]/). */
class CharSetNode: public CNode {
public:
CharSetNode(Chars &chars): chars(chars) { }
void follow(Cases &cases)
{
for (Chars::iterator i = chars.begin(); i != chars.end(); i++) {
NodeSet **x = &cases.cases[*i];
if (!*x) {
if (cases.otherwise)
*x = new NodeSet(*cases.otherwise);
else
*x = new NodeSet;
}
(*x)->insert(followpos.begin(), followpos.end());
}
}
int eq(Node *other)
{
CharSetNode *o = dynamic_cast<CharSetNode *>(other);
if (!o || chars.size() != o->chars.size())
return 0;
for (Chars::iterator i = chars.begin(), j = o->chars.begin();
i != chars.end() && j != o->chars.end(); i++, j++) {
if (*i != *j)
return 0;
}
return 1;
}
ostream &dump(ostream &os)
{
os << '[';
for (Chars::iterator i = chars.begin(); i != chars.end(); i++)
os << *i;
return os << ']';
}
Chars chars;
};
/* Match all except one character (/[^abc]/). */
class NotCharSetNode: public CNode {
public:
NotCharSetNode(Chars &chars): chars(chars) { }
void follow(Cases &cases)
{
if (!cases.otherwise)
cases.otherwise = new NodeSet;
for (Chars::iterator j = chars.begin(); j != chars.end(); j++) {
NodeSet **x = &cases.cases[*j];
if (!*x)
*x = new NodeSet(*cases.otherwise);
}
/* Note: Add to the nonmatching characters after copying away
* the old otherwise state for the matching characters.
*/
cases.otherwise->insert(followpos.begin(), followpos.end());
for (Cases::iterator i = cases.begin(); i != cases.end();
i++) {
if (chars.find(i->first) == chars.end())
i->second->insert(followpos.begin(),
followpos.end());
}
}
int eq(Node *other)
{
NotCharSetNode *o = dynamic_cast<NotCharSetNode *>(other);
if (!o || chars.size() != o->chars.size())
return 0;
for (Chars::iterator i = chars.begin(), j = o->chars.begin();
i != chars.end() && j != o->chars.end(); i++, j++) {
if (*i != *j)
return 0;
}
return 1;
}
ostream &dump(ostream &os)
{
os << "[^";
for (Chars::iterator i = chars.begin(); i != chars.end(); i++)
os << *i;
return os << ']';
}
Chars chars;
};
/* Match any character (/./). */
class AnyCharNode: public CNode {
public:
AnyCharNode() { }
void follow(Cases &cases)
{
if (!cases.otherwise)
cases.otherwise = new NodeSet;
cases.otherwise->insert(followpos.begin(), followpos.end());
for (Cases::iterator i = cases.begin(); i != cases.end();
i++)
i->second->insert(followpos.begin(), followpos.end());
}
int eq(Node *other)
{
if (dynamic_cast<AnyCharNode *>(other))
return 1;
return 0;
}
ostream &dump(ostream &os) { return os << "."; }
};
/**
* Indicate that a regular expression matches. An AcceptNode itself
* doesn't match anything, so it will never generate any transitions.
*/
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
* will be deleted when the table the are stored in is deleted
*/
}
void follow(Cases &cases __attribute__ ((unused)))
{
/* Nothing to follow. */
}
/* requires accept nodes to be common by pointer */
int eq(Node *other)
{
if (dynamic_cast<AcceptNode *>(other))
return (this == other);
return 0;
}
};
/* Match a node zero or more times. (This is a unary operator.) */
class StarNode: public OneChildNode {
public:
StarNode(Node *left): OneChildNode(left) { nullable = true; }
void compute_firstpos() { firstpos = child[0]->firstpos; }
void compute_lastpos() { lastpos = child[0]->lastpos; }
void compute_followpos()
{
NodeSet from = child[0]->lastpos, to = child[0]->firstpos;
for (NodeSet::iterator i = from.begin(); i != from.end(); i++) {
(*i)->followpos.insert(to.begin(), to.end());
}
}
int eq(Node *other)
{
if (dynamic_cast<StarNode *>(other))
return child[0]->eq(other->child[0]);
return 0;
}
ostream &dump(ostream &os)
{
os << '(';
child[0]->dump(os);
return os << ")*";
}
};
/* Match a node one or more times. (This is a unary operator.) */
class PlusNode: public OneChildNode {
public:
PlusNode(Node *left): OneChildNode(left) {
}
void compute_nullable() { nullable = child[0]->nullable; }
void compute_firstpos() { firstpos = child[0]->firstpos; }
void compute_lastpos() { lastpos = child[0]->lastpos; }
void compute_followpos()
{
NodeSet from = child[0]->lastpos, to = child[0]->firstpos;
for (NodeSet::iterator i = from.begin(); i != from.end(); i++) {
(*i)->followpos.insert(to.begin(), to.end());
}
}
int eq(Node *other) {
if (dynamic_cast<PlusNode *>(other))
return child[0]->eq(other->child[0]);
return 0;
}
ostream &dump(ostream &os) {
os << '(';
child[0]->dump(os);
return os << ")+";
}
};
/* Match a pair of consecutive nodes. */
class CatNode: public TwoChildNode {
public:
CatNode(Node *left, Node *right): TwoChildNode(left, right) { }
void compute_nullable()
{
nullable = child[0]->nullable && child[1]->nullable;
}
void compute_firstpos()
{
if (child[0]->nullable)
firstpos = child[0]->firstpos + child[1]->firstpos;
else
firstpos = child[0]->firstpos;
}
void compute_lastpos()
{
if (child[1]->nullable)
lastpos = child[0]->lastpos + child[1]->lastpos;
else
lastpos = child[1]->lastpos;
}
void compute_followpos()
{
NodeSet from = child[0]->lastpos, to = child[1]->firstpos;
for (NodeSet::iterator i = from.begin(); i != from.end(); i++) {
(*i)->followpos.insert(to.begin(), to.end());
}
}
int eq(Node *other)
{
if (dynamic_cast<CatNode *>(other)) {
if (!child[0]->eq(other->child[0]))
return 0;
return child[1]->eq(other->child[1]);
}
return 0;
}
ostream &dump(ostream &os)
{
child[0]->dump(os);
child[1]->dump(os);
return os;
}
};
/* Match one of two alternative nodes. */
class AltNode: public TwoChildNode {
public:
AltNode(Node *left, Node *right): TwoChildNode(left, right) { }
void compute_nullable()
{
nullable = child[0]->nullable || child[1]->nullable;
}
void compute_lastpos()
{
lastpos = child[0]->lastpos + child[1]->lastpos;
}
void compute_firstpos()
{
firstpos = child[0]->firstpos + child[1]->firstpos;
}
int eq(Node *other)
{
if (dynamic_cast<AltNode *>(other)) {
if (!child[0]->eq(other->child[0]))
return 0;
return child[1]->eq(other->child[1]);
}
return 0;
}
ostream &dump(ostream &os)
{
os << '(';
child[0]->dump(os);
os << '|';
child[1]->dump(os);
os << ')';
return os;
}
};
/* Traverse the syntax tree depth-first in an iterator-like manner. */
class depth_first_traversal {
stack<Node *>pos;
void push_left(Node *node) {
pos.push(node);
while (dynamic_cast<InnerNode *>(node)) {
pos.push(node->child[0]);
node = node->child[0];
}
}
public:
depth_first_traversal(Node *node) { push_left(node); }
Node *operator*() { return pos.top(); }
Node *operator->() { return pos.top(); }
operator bool() { return !pos.empty(); }
void operator++(int)
{
Node *last = pos.top();
pos.pop();
if (!pos.empty()) {
/* no need to dynamic cast, as we just popped a node so
* the top node must be an inner node */
InnerNode *node = (InnerNode *) (pos.top());
if (node->child[1] && node->child[1] != last) {
push_left(node->child[1]);
}
}
}
};
struct node_counts {
int charnode;
int charset;
int notcharset;
int alt;
int plus;
int star;
int any;
int cat;
};
extern EpsNode epsnode;
int debug_tree(Node *t);
Node *simplify_tree(Node *t, dfaflags_t flags);
void label_nodes(Node *root);
unsigned long hash_NodeSet(NodeSet *ns);
void flip_tree(Node *node);
class MatchFlag: public AcceptNode {
public:
MatchFlag(uint32_t flag, uint32_t audit): flag(flag), audit(audit) { }
ostream &dump(ostream &os) { return os << '<' << flag << '>'; }
uint32_t flag;
uint32_t audit;
};
class ExactMatchFlag: public MatchFlag {
public:
ExactMatchFlag(uint32_t flag, uint32_t audit): MatchFlag(flag, audit) {}
};
class DenyMatchFlag: public MatchFlag {
public:
DenyMatchFlag(uint32_t flag, uint32_t quiet): MatchFlag(flag, quiet) {}
};
#endif /* __LIBAA_RE_EXPR */

View File

@@ -0,0 +1,932 @@
/*
* (C) 2006, 2007 Andreas Gruenbacher <agruen@suse.de>
* Copyright (c) 2003-2008 Novell, Inc. (All rights reserved)
* Copyright 2009-2012 Canonical Ltd.
*
* The libapparmor library is licensed under the terms of the GNU
* Lesser General Public License, version 2.1. Please see the file
* COPYING.LGPL.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
* Base of implementation based on the Lexical Analysis chapter of:
* Alfred V. Aho, Ravi Sethi, Jeffrey D. Ullman:
* Compilers: Principles, Techniques, and Tools (The "Dragon Book"),
* Addison-Wesley, 1986.
*/
#include <list>
#include <vector>
#include <stack>
#include <set>
#include <map>
#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 */
os << '{';
os << state.label;
os << '}';
return os;
}
static void split_node_types(NodeSet *nodes, NodeSet **anodes, NodeSet **nnodes
)
{
*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::add_new_state(NodeSet *nodes, State *other)
{
/* 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);
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 {
states.push_back(state);
work_queue.push_back(state);
}
return x.first->second;
}
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
* transitions.
*
* 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
*/
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
* sure that there is a state that maps to it, and add the
* matching case to the state.
*/
/* check the default transition first */
if (cases.otherwise)
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 (Cases::iterator j = cases.begin(); j != cases.end(); j++) {
State *target;
target = add_new_state(j->second, nonmatching);
/* Don't insert transition that the otherwise transition
* already covers
*/
if (target != state->otherwise)
state->trans[j->first] = target;
}
}
/* WARNING: This routine can only be called from within DFA creation as
* the nodes value is only valid during dfa construction.
*/
void DFA::dump_node_to_dfa(void)
{
cerr << "Mapping of States to expr nodes\n"
" State <= Nodes\n"
"-------------------\n";
for (Partition::iterator i = states.begin(); i != states.end(); i++)
cerr << " " << (*i)->label << " <= " << (*i)->proto << "\n";
}
/**
* Construct a DFA from a syntax tree.
*/
DFA::DFA(Node *root, dfaflags_t flags): root(root)
{
int i = 0;
if (flags & DFA_DUMP_PROGRESS)
fprintf(stderr, "Creating dfa:\r");
for (depth_first_traversal i(root); i; i++) {
(*i)->compute_nullable();
(*i)->compute_firstpos();
(*i)->compute_lastpos();
}
if (flags & DFA_DUMP_PROGRESS)
fprintf(stderr, "Creating dfa: followpos\r");
for (depth_first_traversal i(root); i; i++) {
(*i)->compute_followpos();
}
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
* algorithm instead of a work_queue, but it would be slightly slower
* and consume more memory.
*
* TODO: currently the work_queue is treated in a breadth first
* search manner. Test using the work_queue in a depth first
* manner, this may help reduce the number of entries on the
* work_queue at any given time, thus reducing peak memory use.
*/
work_queue.push_back(start);
while (!work_queue.empty()) {
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();
work_queue.pop_front();
/* Update 'from's transitions, and if it transitions to any
* unknown State create it and add it to the work_queue
*/
update_state_transitions(from);
} /* while (!work_queue.empty()) */
/* cleanup Sets of nodes used computing the DFA as they are no longer
* needed.
*/
for (depth_first_traversal i(root); i; i++) {
(*i)->firstpos.clear();
(*i)->lastpos.clear();
(*i)->followpos.clear();
}
if (flags & DFA_DUMP_NODE_TO_DFA)
dump_node_to_dfa();
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<perms_t> uniq;
for (Partition::iterator i = states.begin(); i != states.end(); i++)
uniq.insert((*i)->perms);
cerr << "Unique Permission sets: " << s << " (" << uniq.size() << ")\n";
cerr << "----------------------\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";
}
}
/* Remove dead or unreachable states */
void DFA::remove_unreachable(dfaflags_t flags)
{
set<State *> reachable;
/* find the set of reachable states */
reachable.insert(nonmatching);
work_queue.push_back(start);
while (!work_queue.empty()) {
State *from = work_queue.front();
work_queue.pop_front();
reachable.insert(from);
if (from->otherwise != nonmatching &&
reachable.find(from->otherwise) == reachable.end())
work_queue.push_back(from->otherwise);
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);
}
}
/* walk the set of states and remove any that aren't reachable */
if (reachable.size() < states.size()) {
int count = 0;
Partition::iterator i;
Partition::iterator next;
for (i = states.begin(); i != states.end(); i = next) {
next = i;
next++;
if (reachable.find(*i) == reachable.end()) {
if (flags & DFA_DUMP_UNREACHABLE) {
cerr << "unreachable: " << **i;
if (*i == start)
cerr << " <==";
if ((*i)->perms.is_accept())
(*i)->perms.dump(cerr);
cerr << "\n";
}
State *current = *i;
states.erase(i);
delete(current);
count++;
}
}
if (count && (flags & DFA_DUMP_STATS))
cerr << "DFA: states " << states.size() << " removed "
<< count << " unreachable states\n";
}
}
/* test if two states have the same transitions under partition_map */
bool DFA::same_mappings(State *s1, State *s2)
{
if (s1->otherwise->partition != s2->otherwise->partition)
return false;
if (s1->trans.size() != s2->trans.size())
return false;
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;
if (j1->second->partition != j2->second->partition)
return false;
}
return true;
}
/* 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
* 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
*/
size_t DFA::hash_trans(State *s)
{
unsigned long hash = 5381;
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->trans.size();
}
if (s->otherwise != nonmatching) {
hash = ((hash << 5) + hash) + 5381;
State *k = s->otherwise;
hash = ((hash << 5) + hash) + k->trans.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)
{
map<pair<uint64_t, size_t>, Partition *> perm_map;
list<Partition *> partitions;
/* Set up the initial partitions
* minimium of - 1 non accepting, and 1 accepting
* if trans hashing is used the accepting and non-accepting partitions
* can be further split based on the number and type of transitions
* a state makes.
* If permission hashing is enabled the accepting partitions can
* be further divided by permissions. This can result in not
* obtaining a truely minimized dfa but comes close, and can speedup
* minimization.
*/
int accept_count = 0;
int final_accept = 0;
for (Partition::iterator i = states.begin(); i != states.end(); i++) {
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)
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();
part->push_back(*i);
perm_map.insert(make_pair(group, part));
partitions.push_back(part);
(*i)->partition = part;
if (permtype)
accept_count++;
} else {
(*i)->partition = p->second;
p->second->push_back(*i);
}
if ((flags & DFA_DUMP_PROGRESS) && (partitions.size() % 1000 == 0))
cerr << "\033[2KMinimize dfa: partitions "
<< partitions.size() << "\tinit " << partitions.size()
<< " (accept " << accept_count << ")\r";
}
/* perm_map is no longer needed so free the memory it is using.
* Don't remove - doing it manually here helps reduce peak memory usage.
*/
perm_map.clear();
int init_count = partitions.size();
if (flags & DFA_DUMP_PROGRESS)
cerr << "\033[2KMinimize dfa: partitions " << partitions.size()
<< "\tinit " << init_count << " (accept "
<< accept_count << ")\r";
/* Now do repartitioning until each partition contains the set of
* states that are the same. This will happen when the partition
* splitting stables. With a worse case of 1 state per partition
* ie. already minimized.
*/
Partition *new_part;
int new_part_count;
do {
new_part_count = 0;
for (list<Partition *>::iterator p = partitions.begin();
p != partitions.end(); p++) {
new_part = NULL;
State *rep = *((*p)->begin());
Partition::iterator next;
for (Partition::iterator s = ++(*p)->begin(); s != (*p)->end();) {
if (same_mappings(rep, *s)) {
++s;
continue;
}
if (!new_part) {
new_part = new Partition;
list<Partition *>::iterator tmp = p;
partitions.insert(++tmp, new_part);
new_part_count++;
}
new_part->push_back(*s);
s = (*p)->erase(s);
}
/* remapping partition_map for new_part entries
* Do not do this above as it messes up same_mappings
*/
if (new_part) {
for (Partition::iterator m = new_part->begin();
m != new_part->end(); m++) {
(*m)->partition = new_part;
}
}
if ((flags & DFA_DUMP_PROGRESS) && (partitions.size() % 100 == 0))
cerr << "\033[2KMinimize dfa: partitions "
<< partitions.size() << "\tinit "
<< init_count << " (accept "
<< accept_count << ")\r";
}
} while (new_part_count);
if (partitions.size() == states.size()) {
if (flags & DFA_DUMP_STATS)
cerr << "\033[2KDfa minimization no states removed: partitions "
<< partitions.size() << "\tinit " << init_count
<< " (accept " << accept_count << ")\n";
goto out;
}
/* Remap the dfa so it uses the representative states
* Use the first state of a partition as the representative state
* At this point all states with in a partion have transitions
* to states within the same partitions, however this can slow
* down compressed dfa compression as there are more states,
*/
if (flags & DFA_DUMP_MIN_PARTS)
cerr << "Partitions after minimization\n";
for (list<Partition *>::iterator p = partitions.begin();
p != partitions.end(); p++) {
/* representative state for this partition */
State *rep = *((*p)->begin());
if (flags & DFA_DUMP_MIN_PARTS)
cerr << *rep << " : ";
/* update representative state's transitions */
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();
}
//if ((*p)->size() > 1)
//cerr << rep->label << ": ";
/* clear the state label for all non representative states,
* and accumulate permissions */
for (Partition::iterator i = ++(*p)->begin(); i != (*p)->end(); i++) {
//cerr << " " << (*i)->label;
if (flags & DFA_DUMP_MIN_PARTS)
cerr << **i << ", ";
(*i)->label = -1;
rep->perms.add((*i)->perms);
}
if (rep->perms.is_accept())
final_accept++;
//if ((*p)->size() > 1)
//cerr << "\n";
if (flags & DFA_DUMP_MIN_PARTS)
cerr << "\n";
}
if (flags & DFA_DUMP_STATS)
cerr << "\033[2KMinimized dfa: final partitions "
<< partitions.size() << " (accept " << final_accept
<< ")" << "\tinit " << init_count << " (accept "
<< accept_count << ")\n";
/* make sure nonmatching and start state are up to date with the
* mappings */
{
Partition *partition = nonmatching->partition;
if (*partition->begin() != nonmatching) {
nonmatching = *partition->begin();
}
partition = start->partition;
if (*partition->begin() != start) {
start = *partition->begin();
}
}
/* Now that the states have been remapped, remove all states
* that are not the representive states for their partition, they
* will have a label == -1
*/
for (Partition::iterator i = states.begin(); i != states.end();) {
if ((*i)->label == -1) {
State *s = *i;
i = states.erase(i);
delete(s);
} else
i++;
}
out:
/* Cleanup */
while (!partitions.empty()) {
Partition *p = partitions.front();
partitions.pop_front();
delete(p);
}
}
/**
* text-dump the DFA (for debugging).
*/
void DFA::dump(ostream & os)
{
for (Partition::iterator i = states.begin(); i != states.end(); i++) {
if (*i == start || (*i)->perms.is_accept()) {
os << **i;
if (*i == start)
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++) {
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";
}
/**
* Create a dot (graphviz) graph from the DFA (for debugging).
*/
void DFA::dump_dot_graph(ostream & os)
{
os << "digraph \"dfa\" {" << "\n";
for (Partition::iterator i = states.begin(); i != states.end(); i++) {
if (*i == nonmatching)
continue;
os << "\t\"" << **i << "\" [" << "\n";
if (*i == start) {
os << "\t\tstyle=bold" << "\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++) {
Chars excluded;
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=\"";
if (isprint(j->first))
os << j->first;
else
os << "\\0x" << hex << (int) j->first << dec;
os << "\"\n\t]" << "\n";
}
}
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++) {
if (isprint(*i))
os << *i;
else
os << "\\0x" << hex << (int) *i << dec;
}
os << "]\"" << "\n";
}
os << "\t]" << "\n";
}
}
os << '}' << "\n";
}
/**
* Compute character equivalence classes in the DFA to save space in the
* transition table.
*/
map<uchar, uchar> DFA::equivalence_classes(dfaflags_t flags)
{
map<uchar, uchar> classes;
uchar next_class = 1;
for (Partition::iterator i = states.begin(); i != states.end(); i++) {
/* Group edges to the same next state together */
map<const State *, Chars> node_sets;
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();
j != node_sets.end(); j++) {
/* Group edges to the same next state together by class */
map<uchar, Chars> node_classes;
bool class_used = false;
for (Chars::iterator k = j->second.begin();
k != j->second.end(); k++) {
pair<map<uchar, uchar>::iterator, bool> x = classes.insert(make_pair(*k, next_class));
if (x.second)
class_used = true;
pair<map<uchar, Chars>::iterator, bool> y = node_classes.insert(make_pair(x.first->second, Chars()));
y.first->second.insert(*k);
}
if (class_used) {
next_class++;
class_used = false;
}
for (map<uchar, Chars>::iterator k = node_classes.begin();
k != node_classes.end(); k++) {
/**
* If any other characters are in the same class, move
* the characters in this class into their own new
* class
*/
map<uchar, uchar>::iterator l;
for (l = classes.begin(); l != classes.end(); l++) {
if (l->second == k->first &&
k->second.find(l->first) == k->second.end()) {
class_used = true;
break;
}
}
if (class_used) {
for (Chars::iterator l = k->second.begin();
l != k->second.end(); l++) {
classes[*l] = next_class;
}
next_class++;
class_used = false;
}
}
}
}
if (flags & DFA_DUMP_EQUIV_STATS)
fprintf(stderr, "Equiv class reduces to %d classes\n",
next_class - 1);
return classes;
}
/**
* Text-dump the equivalence classes (for debugging).
*/
void dump_equivalence_classes(ostream &os, map<uchar, uchar> &eq)
{
map<uchar, Chars> rev;
for (map<uchar, uchar>::iterator i = eq.begin(); i != eq.end(); i++) {
Chars &chars = rev.insert(make_pair(i->second, Chars())).first->second;
chars.insert(i->first);
}
os << "(eq):" << "\n";
for (map<uchar, Chars>::iterator i = rev.begin(); i != rev.end(); i++) {
os << (int)i->first << ':';
Chars &chars = i->second;
for (Chars::iterator j = chars.begin(); j != chars.end(); j++) {
os << ' ' << *j;
}
os << "\n";
}
}
/**
* Replace characters with classes (which are also represented as
* characters) in the DFA transition table.
*/
void DFA::apply_equivalence_classes(map<uchar, uchar> &eq)
{
/**
* Note: We only transform the transition table; the nodes continue to
* contain the original characters.
*/
for (Partition::iterator i = states.begin(); i != states.end(); i++) {
map<uchar, State *> tmp;
tmp.swap((*i)->trans);
for (StateTrans::iterator j = tmp.begin(); j != tmp.end(); j++)
(*i)->trans.insert(make_pair(eq[j->first], j->second));
}
}
#if 0
typedef set <ImportantNode *>AcceptNodes;
map<ImportantNode *, AcceptNodes> dominance(DFA & dfa)
{
map<ImportantNode *, AcceptNodes> is_dominated;
for (States::iterator i = dfa.states.begin(); i != dfa.states.end(); i++) {
AcceptNodes set1;
for (State::iterator j = (*i)->begin(); j != (*i)->end(); j++) {
if (AcceptNode * accept = dynamic_cast<AcceptNode *>(*j))
set1.insert(accept);
}
for (AcceptNodes::iterator j = set1.begin(); j != set1.end(); j++) {
pair<map<ImportantNode *, AcceptNodes>::iterator, bool> x = is_dominated.insert(make_pair(*j, set1));
if (!x.second) {
AcceptNodes & set2(x.first->second), set3;
for (AcceptNodes::iterator l = set2.begin();
l != set2.end(); l++) {
if (set1.find(*l) != set1.end())
set3.insert(*l);
}
set3.swap(set2);
}
}
}
return is_dominated;
}
#endif
static inline int diff_qualifiers(uint32_t perm1, uint32_t perm2)
{
return ((perm1 & AA_EXEC_TYPE) && (perm2 & AA_EXEC_TYPE) &&
(perm1 & AA_EXEC_TYPE) != (perm2 & AA_EXEC_TYPE));
}
/**
* Compute the permission flags that this state corresponds to. If we
* have any exact matches, then they override the execute and safe
* execute flags.
*/
int accept_perms(NodeSet *state, perms_t &perms)
{
int error = 0;
uint32_t exact_match_allow = 0;
uint32_t exact_audit = 0;
perms.clear();
if (!state)
return error;
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_allow,
match->flag))
error = 1;;
exact_match_allow |= match->flag;
exact_audit |= match->audit;
} else if (dynamic_cast<DenyMatchFlag *>(match)) {
perms.deny |= match->flag;
perms.quiet |= match->audit;
} else {
if (!is_merged_x_consistent(perms.allow, match->flag))
error = 1;
perms.allow |= match->flag;
perms.audit |= match->audit;
}
}
perms.allow |= exact_match_allow & ~(ALL_AA_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_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 (AA_USER_EXEC & perms.deny)
perms.deny |= AA_USER_EXEC_TYPE;
if (AA_OTHER_EXEC & perms.deny)
perms.deny |= AA_OTHER_EXEC_TYPE;
perms.allow &= ~perms.deny;
perms.quiet &= perms.deny;
if (error)
fprintf(stderr, "profile has merged rule with conflicting x modifiers\n");
return error;
}

462
parser/libapparmor_re/hfa.h Normal file
View File

@@ -0,0 +1,462 @@
/*
* (C) 2006, 2007 Andreas Gruenbacher <agruen@suse.de>
* Copyright (c) 2003-2008 Novell, Inc. (All rights reserved)
* Copyright 2009-2012 Canonical Ltd.
*
* The libapparmor library is licensed under the terms of the GNU
* Lesser General Public License, version 2.1. Please see the file
* COPYING.LGPL.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
* Base of implementation based on the Lexical Analysis chapter of:
* Alfred V. Aho, Ravi Sethi, Jeffrey D. Ullman:
* Compilers: Principles, Techniques, and Tools (The "Dragon Book"),
* Addison-Wesley, 1986.
*/
#ifndef __LIBAA_RE_HFA_H
#define __LIBAA_RE_HFA_H
#include <list>
#include <map>
#include <vector>
#include <stdint.h>
#include "expr-tree.h"
class State;
typedef map<uchar, State *> StateTrans;
typedef list<State *> Partition;
#include "../immunix.h"
class perms_t {
public:
perms_t(void) throw(int): allow(0), deny(0), audit(0), quiet(0), exact(0) { };
bool is_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
* label: a unique label to identify the state used for pretty printing
* the non-matching state is setup to have label == 0 and
* the start state is setup to have label == 1
* audit: the audit permission mask for the state
* accept: the accept permissions for the state
* trans: set of transitions from this state
* otherwise: the default state for transitions not in @trans
* parition: Is a temporary work variable used during dfa minimization.
* it can be replaced with a map, but that is slower and uses more
* memory.
* nodes: Is a temporary work variable used during dfa creation. It can
* be replaced by using the nodemap, but that is slower
*/
class State {
public:
State(int l, ProtoState &n, State *other) throw(int):
label(l), perms(), trans()
{
int error;
if (other)
otherwise = other;
else
otherwise = this;
proto = n;
/* Compute permissions associated with the State. */
error = accept_perms(n.anodes, perms);
if (error) {
//cerr << "Failing on accept perms " << error << "\n";
throw error;
}
};
State *next(uchar c) {
StateTrans::iterator i = trans.find(c);
if (i != trans.end())
return i->second;
return otherwise;
};
int apply_and_clear_deny(void) { return perms.apply_and_clear_deny(); }
int label;
perms_t perms;
StateTrans trans;
State *otherwise;
/* temp storage for State construction */
union {
Partition *partition;
ProtoState proto;
};
};
ostream &operator<<(ostream &os, const State &state);
class NodeMap: public CacheStats
{
public:
typedef map<ProtoState, State *>::iterator iterator;
iterator begin() { return cache.begin(); }
iterator end() { return cache.end(); }
map<ProtoState, State *> cache;
NodeMap(void): cache() { };
~NodeMap() { clear(); };
virtual unsigned long size(void) const { return cache.size(); }
void clear()
{
cache.clear();
CacheStats::clear();
}
pair<iterator,bool> insert(ProtoState &proto, State *state)
{
pair<iterator,bool> uniq;
uniq = cache.insert(make_pair(proto, state));
if (uniq.second == false) {
dup++;
} else {
sum += proto.size();
if (proto.size() > max)
max = proto.size();
}
return uniq;
}
};
/* Transitions in the DFA. */
class DFA {
void dump_node_to_dfa(void);
State *add_new_state(NodeSet *nodes, State *other);
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);
map<uchar, uchar> equivalence_classes(dfaflags_t flags);
void apply_equivalence_classes(map<uchar, uchar> &eq);
Node *root;
State *nonmatching, *start;
Partition states;
};
void dump_equivalence_classes(ostream &os, map<uchar, uchar> &eq);
#endif /* __LIBAA_RE_HFA_H */

View File

@@ -0,0 +1,27 @@
/*
* (C) 2006, 2007 Andreas Gruenbacher <agruen@suse.de>
* Copyright (c) 2003-2008 Novell, Inc. (All rights reserved)
* Copyright 2009-2010 Canonical Ltd.
*
* The libapparmor library is licensed under the terms of the GNU
* Lesser General Public License, version 2.1. Please see the file
* COPYING.LGPL.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
* Parsing of regular expression into expression trees as implemented in
* expr-tree
*/
#ifndef __LIBAA_RE_PARSE_H
#define __LIBAA_RE_PARSE_H
int regex_parse(Node **tree, const char *rule);
#endif /* __LIBAA_RE_PARSE_H */

View File

@@ -0,0 +1,262 @@
/*
* (C) 2006, 2007 Andreas Gruenbacher <agruen@suse.de>
* Copyright (c) 2003-2008 Novell, Inc. (All rights reserved)
* Copyright 2009-2010 Canonical Ltd.
*
* The libapparmor library is licensed under the terms of the GNU
* Lesser General Public License, version 2.1. Please see the file
* COPYING.LGPL.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
* Parsing of regular expression into expression trees as implemented in
* expr-tree
*/
%{
/* #define DEBUG_TREE */
#include "expr-tree.h"
%}
%union {
char c;
Node *node;
Chars *cset;
}
%{
void regex_error(Node **, const char *, const char *);
#define YYLEX_PARAM &text
int regex_lex(YYSTYPE *, const char **);
static inline Chars *insert_char(Chars* cset, uchar a)
{
cset->insert(a);
return cset;
}
static inline Chars* insert_char_range(Chars* cset, uchar a, uchar b)
{
if (a > b)
swap(a, b);
for (uchar i = a; i <= b; i++)
cset->insert(i);
return cset;
}
%}
%pure-parser
/* %error-verbose */
%parse-param {Node **root}
%parse-param {const char *text}
%name-prefix = "regex_"
%token <c> CHAR
%type <c> regex_char cset_char1 cset_char cset_charN
%type <cset> charset cset_chars
%type <node> regex expr terms0 terms qterm term
/**
* Note: destroy all nodes upon failure, but *not* the start symbol once
* parsing succeeds!
*/
%destructor { $$->release(); } expr terms0 terms qterm term
%%
/* FIXME: Does not parse "[--]", "[---]", "[^^-x]". I don't actually know
which precise grammer Perl regexs use, and rediscovering that
is proving to be painful. */
regex : /* empty */ { *root = $$ = &epsnode; }
| expr { *root = $$ = $1; }
;
expr : terms
| expr '|' terms0 { $$ = new AltNode($1, $3); }
| '|' terms0 { $$ = new AltNode(&epsnode, $2); }
;
terms0 : /* empty */ { $$ = &epsnode; }
| terms
;
terms : qterm
| terms qterm { $$ = new CatNode($1, $2); }
;
qterm : term
| term '*' { $$ = new StarNode($1); }
| term '+' { $$ = new PlusNode($1); }
;
term : '.' { $$ = new AnyCharNode; }
| regex_char { $$ = new CharNode($1); }
| '[' charset ']' { $$ = new CharSetNode(*$2);
delete $2; }
| '[' '^' charset ']'
{ $$ = new NotCharSetNode(*$3);
delete $3; }
| '[' '^' '^' cset_chars ']'
{ $4->insert('^');
$$ = new NotCharSetNode(*$4);
delete $4; }
| '(' regex ')' { $$ = $2; }
;
regex_char : CHAR
| '^' { $$ = '^'; }
| '-' { $$ = '-'; }
| ']' { $$ = ']'; }
;
charset : cset_char1 cset_chars
{ $$ = insert_char($2, $1); }
| cset_char1 '-' cset_charN cset_chars
{ $$ = insert_char_range($4, $1, $3); }
;
cset_chars : /* nothing */ { $$ = new Chars; }
| cset_chars cset_charN
{ $$ = insert_char($1, $2); }
| cset_chars cset_charN '-' cset_charN
{ $$ = insert_char_range($1, $2, $4); }
;
cset_char1 : cset_char
| ']' { $$ = ']'; }
| '-' { $$ = '-'; }
;
cset_charN : cset_char
| '^' { $$ = '^'; }
;
cset_char : CHAR
| '[' { $$ = '['; }
| '*' { $$ = '*'; }
| '+' { $$ = '+'; }
| '.' { $$ = '.'; }
| '|' { $$ = '|'; }
| '(' { $$ = '('; }
| ')' { $$ = ')'; }
;
%%
int octdigit(char c)
{
if (c >= '0' && c <= '7')
return c - '0';
return -1;
}
int hexdigit(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
else if (c >= 'A' && c <= 'F')
return 10 + c - 'A';
else if (c >= 'a' && c <= 'f')
return 10 + c - 'a';
else
return -1;
}
int regex_lex(YYSTYPE *val, const char **pos)
{
int c;
val->c = **pos;
switch(*(*pos)++) {
case '\0':
(*pos)--;
return 0;
case '*': case '+': case '.': case '|': case '^': case '-':
case '[': case ']': case '(' : case ')':
return *(*pos - 1);
case '\\':
val->c = **pos;
switch(*(*pos)++) {
case '\0':
(*pos)--;
/* fall through */
case '\\':
val->c = '\\';
break;
case '0':
val->c = 0;
if ((c = octdigit(**pos)) >= 0) {
val->c = c;
(*pos)++;
}
if ((c = octdigit(**pos)) >= 0) {
val->c = (val->c << 3) + c;
(*pos)++;
}
if ((c = octdigit(**pos)) >= 0) {
val->c = (val->c << 3) + c;
(*pos)++;
}
break;
case 'x':
val->c = 0;
if ((c = hexdigit(**pos)) >= 0) {
val->c = c;
(*pos)++;
}
if ((c = hexdigit(**pos)) >= 0) {
val->c = (val->c << 4) + c;
(*pos)++;
}
break;
case 'a':
val->c = '\a';
break;
case 'e':
val->c = 033 /* ESC */;
break;
case 'f':
val->c = '\f';
break;
case 'n':
val->c = '\n';
break;
case 'r':
val->c = '\r';
break;
case 't':
val->c = '\t';
break;
}
}
return CHAR;
}
void regex_error(Node ** __attribute__((unused)),
const char *text __attribute__((unused)),
const char *error __attribute__((unused)))
{
/* We don't want the library to print error messages. */
}

View File

@@ -1,10 +0,0 @@
#ifndef __REGEXP_H
#define __REGEXP_H
/**
* Flex file format, but without state compression and with negative
* match results in the YYTD_ID_DEF table instead.
*/
#define YYTH_REGEXP_MAGIC 0x1B5E783D
#endif /* __REGEXP_H */

File diff suppressed because it is too large Load Diff

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 */

58
parser/parser.conf Normal file
View File

@@ -0,0 +1,58 @@
# parser.conf is a global AppArmor config file for the apparmor_parser
#
# It can be used to specify the default options for the parser, which
# can then be overriden by options passed on the command line.
#
# Leading whitespace is ignored and lines that begin with # are treated
# as comments.
#
# Config options are specified one per line using the same format as the
# longform command line options (without the preceding --).
#
# If a value is specified twice the last version to appear is used.
## Suppress Warnings
#quiet
## Be verbose
#verbose
## Set include path
#Include /etc/apparmor.d/abstractions
## Set location of apparmor filesystem
#subdomainfs /sys/kernel/security/apparmor
## Set match-string to use - for forcing compiler to treat different kernels
## the same
# match-string "pattern=aadfa audit perms=crwxamlk/ user::other"
## Turn creating/updating of the cache on by default
#write-cache
## Show cache hits
#show-cache
## skip cached policy
#skip-cache
## skip reading cache but allow updating
#skip-read-cache
#### Set Optimizaions. Multiple Optimizations can be set, one per line ####
# For supported optimizations see
# apparmor_parser --help=O
## Turn on equivalence classes
#equiv
## Turn off expr tree simplification
#Optimize=no-expr-simplify
## Turn off DFA minimization
#Optimize=no-minimize
## Adjust compression
#Optimize=compress-small
#Optimize=compress-fast

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,10 +19,21 @@
* 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;
@@ -43,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;
@@ -106,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 */
@@ -118,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;
@@ -125,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 {
@@ -178,13 +209,8 @@ struct var_string {
#define FLAG_CHANGEHAT_1_4 2
#define FLAG_CHANGEHAT_1_5 3
extern int kernel_supports_network;
extern int net_af_max_override;
extern int flag_changehat_version;
extern int read_implies_exec;
extern dfaflags_t dfaflags;
extern int preprocess_only;
extern FILE *ofile;
#define PATH_CHROOT_REL 0x1
#define PATH_NS_REL 0x2
@@ -220,34 +246,53 @@ extern FILE *ofile;
#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)
/* Some external definitions to make b0rken programs happy */
/* from parser_common.c */
extern int regex_type;
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;
extern int names_only;
extern int option;
extern int current_lineno;
extern dfaflags_t dfaflags;
extern char *progname;
extern char *subdomainbase;
extern char *profilename;
extern char *profile_namespace;
/* from parser_main */
extern int force_complain;
extern int conf_quiet;
extern int conf_verbose;
extern int kernel_load;
extern int regex_type;
extern int perms_create;
extern struct timespec mru_tstamp;
extern void update_mru_tstamp(FILE *file);
extern char *current_filename;
extern FILE *ofile;
extern int read_implies_exec;
extern void pwarn(char *fmt, ...) __attribute__((__format__(__printf__, 1, 2)));
/* from parser_main (cannot be used in tst builds) */
extern int force_complain;
extern struct timespec mru_tstamp;
extern void update_mru_tstamp(FILE *file);
/* provided by parser_lex.l (cannot be used in tst builds) */
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 */
@@ -258,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);
@@ -286,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 {;
@@ -323,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);
@@ -339,3 +399,34 @@ extern void dump_policy_hats(struct codomain *cod);
extern void dump_policy_names(void);
extern int die_if_any_regex(void);
void free_policies(void);
#ifdef UNIT_TEST
/* For the unit-test builds, we must include function stubs for stuff that
* only exists in the excluded object files; everything else should live
* in parser_common.c.
*/
/* parser_yacc.y */
void yyerror(const char *msg, ...)
{
va_list arg;
char buf[PATH_MAX];
va_start(arg, msg);
vsnprintf(buf, sizeof(buf), msg, arg);
va_end(arg);
PERROR(_("AppArmor parser error: %s\n"), buf);
exit(1);
}
#define MY_TEST(statement, error) \
if (!(statement)) { \
PERROR("FAIL: %s\n", error); \
rc = 1; \
}
#endif
#endif /** __AA_PARSER_H */

75
parser/parser_common.c Normal file
View File

@@ -0,0 +1,75 @@
/*
* 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
* 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.
*/
#include <stdlib.h>
#include <stdarg.h>
#include <libintl.h>
#include <locale.h>
#define _(s) gettext(s)
#include "parser.h"
int regex_type = AARE_DFA;
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;
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;
char *subdomainbase = NULL;
char *progname = __FILE__;
char *profile_namespace = NULL;
char *profilename = NULL;
char *current_filename = NULL;
FILE *ofile = NULL;
#ifdef FORCE_READ_IMPLIES_EXEC
int read_implies_exec = 1;
#else
int read_implies_exec = 0;
#endif
void pwarn(char *fmt, ...)
{
va_list arg;
char *newfmt;
if (conf_quiet || names_only || option == OPTION_REMOVE)
return;
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);
vfprintf(stderr, newfmt, arg);
va_end(arg);
free(newfmt);
}

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
@@ -21,8 +21,6 @@
#define PARSER_INCLUDE_H
extern int preprocess_only;
extern int current_lineno;
extern char *current_filename;
extern int add_search_dir(char *dir);
extern void init_base_dir(void);

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
@@ -54,9 +55,6 @@
#define YY_NO_INPUT
int current_lineno = 1;
char *current_filename = NULL;
struct ignored_suffix_t {
char * text;
int len;
@@ -170,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 @
@@ -198,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> */
@@ -224,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" */
@@ -232,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();
}
}
@@ -248,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] {
@@ -363,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;
}
@@ -391,7 +430,7 @@ LT_EQUAL <=
\r?\n {
DUMP_PREPROCESS;
current_lineno++;
BEGIN(INITIAL);
yy_pop_state();
}
[^\n] {
DUMP_PREPROCESS;
@@ -403,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] {
@@ -433,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;
}
@@ -470,48 +495,11 @@ 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 */ }
-?{NUMBER}[kKMG]? {
-?{NUMBER}[[:alpha:]]* {
DUMP_PREPROCESS;
yylval.var_val = strdup(yytext);
return TOK_VALUE;
@@ -529,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);
@@ -571,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);
@@ -594,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} {
@@ -623,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
* 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,6 +19,7 @@
* Ltd.
*/
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
@@ -28,14 +29,15 @@
#include <fcntl.h>
#include <mntent.h>
#include <libintl.h>
#include <linux/limits.h>
#include <locale.h>
#include <dirent.h>
#define _(s) gettext(s)
/* enable the following line to get voluminous debug info */
/* #define DEBUG */
#include <unistd.h>
#include <limits.h>
#include <sys/sysctl.h>
#include <sys/types.h>
#include <sys/stat.h>
@@ -58,46 +60,28 @@
#define UNPRIVILEGED_OPS (!(PRIVILEGED_OPS))
const char *parser_title = "AppArmor parser";
const char *parser_copyright = "Copyright (C) 1999-2008 Novell Inc.\nCopyright 2009-2010 Canonical Ltd.";
const char *parser_copyright = "Copyright (C) 1999-2008 Novell Inc.\nCopyright 2009-2012 Canonical Ltd.";
char *progname;
int option = OPTION_ADD;
int opt_force_complain = 0;
int binary_input = 0;
int names_only = 0;
int dump_vars = 0;
int dump_expanded_vars = 0;
dfaflags_t dfaflags = DFA_CONTROL_TREE_NORMAL | DFA_CONTROL_TREE_SIMPLE | DFA_CONTROL_MINIMIZE | DFA_CONTROL_MINIMIZE_HASH_TRANS | DFA_CONTROL_MINIMIZE_HASH_PERMS;
int conf_verbose = 0;
int conf_quiet = 0;
int kernel_load = 1;
int show_cache = 0;
int skip_cache = 0;
int skip_read_cache = 0;
int write_cache = 0;
#ifdef FORCE_READ_IMPLIES_EXEC
int read_implies_exec = 1;
#else
int read_implies_exec = 0;
#endif
int preprocess_only = 0;
int skip_mode_force = 0;
struct timespec mru_tstamp;
char *subdomainbase = NULL;
#define FLAGS_STRING_SIZE 1024
char *match_string = NULL;
char *flags_string = NULL;
int regex_type = AARE_DFA;
int perms_create = 0; /* perms contain create flag */
int kernel_supports_network = 1; /* kernel supports network rules */
int net_af_max_override = -1; /* use kernel to determine af_max */
char *profile_namespace = NULL;
int flag_changehat_version = FLAG_CHANGEHAT_1_5;
FILE *ofile = NULL;
char *cacheloc = NULL;
/* per-profile settings */
int force_complain = 0;
char *profilename = NULL;
struct option long_options[] = {
{"add", 0, 0, 'a'},
@@ -125,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'},
@@ -166,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"
@@ -216,6 +202,8 @@ optflag_table_t dumpflag_table[] = {
DFA_DUMP_UNIQ_PERMS },
{ 1, "dfa-minimize-uniq-perms", "Dump unique perms post minimization",
DFA_DUMP_MIN_UNIQ_PERMS },
{ 1, "dfa-minimize-partitions", "Dump dfa minimization partitions",
DFA_DUMP_MIN_PARTS },
{ 1, "compress-progress", "Dump progress of compression",
DFA_DUMP_TRANS_PROGRESS | DFA_DUMP_TRANS_STATS },
{ 1, "compress-stats", "Dump stats on compression",
@@ -242,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",
@@ -322,29 +310,243 @@ static void display_optimize(char *command)
print_flag_table(optflag_table);
}
void pwarn(char *fmt, ...)
/* Treat conf file like options passed on command line
*/
static int getopt_long_file(FILE *f, const struct option *longopts,
char **optarg, int *longindex)
{
va_list arg;
char *newfmt;
int rc;
static char line[256];
char *pos, *opt, *save;
int i;
if (conf_quiet || names_only || option == OPTION_REMOVE)
return;
for (;;) {
if (!fgets(line, 256, f))
return -1;
pos = line;
while (isblank(*pos))
pos++;
if (*pos == '#')
continue;
opt = strtok_r(pos, " \t\r\n=", &save);
/* blank line */
if (!opt)
continue;
rc = asprintf(&newfmt, _("Warning from %s (%s%sline %d): %s"),
profilename ? profilename : "stdin",
current_filename ? current_filename : "",
current_filename ? " " : "",
current_lineno,
fmt);
if (!newfmt)
return;
for (i = 0; longopts[i].name &&
strcmp(longopts[i].name, opt) != 0; i++) ;
if (!longopts[i].name) {
PERROR("%s: unknown option (%s) in config file.\n",
progname, opt);
/* skip it */
continue;
}
break;
}
va_start(arg, fmt);
vfprintf(stderr, newfmt, arg);
va_end(arg);
if (longindex)
*longindex = i;
free(newfmt);
if (*save) {
int len;
while(isblank(*save))
save++;
len = strlen(save) - 1;
if (save[len] == '\n')
save[len] = 0;
}
switch (longopts[i].has_arg) {
case 0:
*optarg = NULL;
break;
case 1:
if (!strlen(save)) {
*optarg = NULL;
return '?';
}
*optarg = save;
break;
case 2:
*optarg = save;
break;
default:
PERROR("%s: internal error bad longopt value\n", progname);
exit(1);
}
if (longopts[i].flag == NULL)
return longopts[i].val;
else
*longopts[i].flag = longopts[i].val;
return 0;
}
/* process a single argment from getopt_long
* Returns: 1 if an action arg, else 0
*/
static int process_arg(int c, char *optarg)
{
int count = 0;
switch (c) {
case 0:
PERROR("Assert, in getopt_long handling\n");
display_usage(progname);
exit(0);
break;
case 'a':
count++;
option = OPTION_ADD;
break;
case 'd':
debug++;
skip_read_cache = 1;
break;
case 'h':
if (!optarg) {
display_usage(progname);
} else if (strcmp(optarg, "Dump") == 0 ||
strcmp(optarg, "dump") == 0 ||
strcmp(optarg, "D") == 0) {
display_dump(progname);
} else if (strcmp(optarg, "Optimize") == 0 ||
strcmp(optarg, "optimize") == 0 ||
strcmp(optarg, "O") == 0) {
display_optimize(progname);
} else {
PERROR("%s: Invalid --help option %s\n",
progname, optarg);
exit(1);
}
exit(0);
break;
case 'r':
count++;
option = OPTION_REPLACE;
break;
case 'R':
count++;
option = OPTION_REMOVE;
skip_cache = 1;
break;
case 'V':
display_version();
exit(0);
break;
case 'I':
add_search_dir(optarg);
break;
case 'b':
set_base_dir(optarg);
break;
case 'B':
binary_input = 1;
skip_cache = 1;
break;
case 'C':
opt_force_complain = 1;
skip_cache = 1;
break;
case 'N':
names_only = 1;
skip_cache = 1;
break;
case 'S':
count++;
option = OPTION_STDOUT;
skip_read_cache = 1;
kernel_load = 0;
break;
case 'o':
count++;
option = OPTION_OFILE;
skip_read_cache = 1;
kernel_load = 0;
ofile = fopen(optarg, "w");
if (!ofile) {
PERROR("%s: Could not open file %s\n",
progname, optarg);
exit(1);
}
break;
case 'f':
subdomainbase = strndup(optarg, PATH_MAX);
break;
case 'D':
skip_read_cache = 1;
if (!optarg) {
dump_vars = 1;
} else if (strcmp(optarg, "variables") == 0) {
dump_vars = 1;
} else if (strcmp(optarg, "expanded-variables") == 0) {
dump_expanded_vars = 1;
} else if (!handle_flag_table(dumpflag_table, optarg,
&dfaflags)) {
PERROR("%s: Invalid --Dump option %s\n",
progname, optarg);
exit(1);
}
break;
case 'O':
skip_read_cache = 1;
if (!handle_flag_table(optflag_table, optarg,
&dfaflags)) {
PERROR("%s: Invalid --Optimize option %s\n",
progname, optarg);
exit(1);
}
break;
case 'm':
match_string = strdup(optarg);
break;
case 'q':
conf_verbose = 0;
conf_quiet = 1;
break;
case 'v':
conf_verbose = 1;
conf_quiet = 0;
break;
case 'n':
profile_namespace = strdup(optarg);
break;
case 'X':
read_implies_exec = 1;
break;
case 'K':
skip_cache = 1;
break;
case 'k':
show_cache = 1;
break;
case 'W':
write_cache = 1;
break;
case 'T':
skip_read_cache = 1;
break;
case 'L':
cacheloc = strdup(optarg);
break;
case 'Q':
kernel_load = 0;
break;
case 'p':
count++;
kernel_load = 0;
skip_cache = 1;
preprocess_only = 1;
skip_mode_force = 1;
break;
default:
display_usage(progname);
exit(0);
break;
}
return count;
}
static int process_args(int argc, char *argv[])
@@ -355,159 +557,7 @@ static int process_args(int argc, char *argv[])
while ((c = getopt_long(argc, argv, "adf:h::rRVvI:b:BCD:NSm:qQn:XKTWkO:po:", long_options, &o)) != -1)
{
switch (c) {
case 0:
PERROR("Assert, in getopt_long handling\n");
display_usage(progname);
exit(0);
break;
case 'a':
count++;
option = OPTION_ADD;
break;
case 'd':
debug++;
skip_read_cache = 1;
break;
case 'h':
if (!optarg) {
display_usage(progname);
} else if (strcmp(optarg, "Dump") == 0 ||
strcmp(optarg, "dump") == 0 ||
strcmp(optarg, "D") == 0) {
display_dump(progname);
} else if (strcmp(optarg, "Optimize") == 0 ||
strcmp(optarg, "optimize") == 0 ||
strcmp(optarg, "O") == 0) {
display_optimize(progname);
} else {
PERROR("%s: Invalid --help option %s\n",
progname, optarg);
exit(1);
}
exit(0);
break;
case 'r':
count++;
option = OPTION_REPLACE;
break;
case 'R':
count++;
option = OPTION_REMOVE;
skip_cache = 1;
break;
case 'V':
display_version();
exit(0);
break;
case 'I':
add_search_dir(optarg);
break;
case 'b':
set_base_dir(optarg);
break;
case 'B':
binary_input = 1;
skip_cache = 1;
break;
case 'C':
opt_force_complain = 1;
skip_cache = 1;
break;
case 'N':
names_only = 1;
skip_cache = 1;
break;
case 'S':
count++;
option = OPTION_STDOUT;
skip_read_cache = 1;
kernel_load = 0;
break;
case 'o':
count++;
option = OPTION_OFILE;
skip_read_cache = 1;
kernel_load = 0;
ofile = fopen(optarg, "w");
if (!ofile) {
PERROR("%s: Could not open file %s\n",
progname, optarg);
exit(1);
}
break;
case 'f':
subdomainbase = strndup(optarg, PATH_MAX);
break;
case 'D':
skip_read_cache = 1;
if (!optarg) {
dump_vars = 1;
} else if (strcmp(optarg, "variables") == 0) {
dump_vars = 1;
} else if (strcmp(optarg, "expanded-variables") == 0) {
dump_expanded_vars = 1;
} else if (!handle_flag_table(dumpflag_table, optarg,
&dfaflags)) {
PERROR("%s: Invalid --Dump option %s\n",
progname, optarg);
exit(1);
}
break;
case 'O':
skip_read_cache = 1;
if (!handle_flag_table(optflag_table, optarg,
&dfaflags)) {
PERROR("%s: Invalid --Optimize option %s\n",
progname, optarg);
exit(1);
}
break;
case 'm':
match_string = strdup(optarg);
break;
case 'q':
conf_verbose = 0;
conf_quiet = 1;
break;
case 'v':
conf_verbose = 1;
conf_quiet = 0;
break;
case 'n':
profile_namespace = strdup(optarg);
break;
case 'X':
read_implies_exec = 1;
break;
case 'K':
skip_cache = 1;
break;
case 'k':
show_cache = 1;
break;
case 'W':
write_cache = 1;
break;
case 'T':
skip_read_cache = 1;
break;
case 'Q':
kernel_load = 0;
break;
case 'p':
count++;
kernel_load = 0;
skip_cache = 1;
preprocess_only = 1;
skip_mode_force = 1;
break;
default:
display_usage(progname);
exit(0);
break;
}
count += process_arg(c, optarg);
}
if (count > 1) {
@@ -521,6 +571,21 @@ static int process_args(int argc, char *argv[])
return optind;
}
static int process_config_file(const char *name)
{
char *optarg;
FILE *f;
int c, o;
f = fopen(name, "r");
if (!f)
return 0;
while ((c = getopt_long_file(f, long_options, &optarg, &o)) != -1)
process_arg(c, optarg);
return 1;
}
static inline char *try_subdomainfs_mountpoint(const char *mntpnt,
const char *path)
{
@@ -610,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;
@@ -659,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=");
@@ -863,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.
@@ -871,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 */
@@ -950,8 +1156,7 @@ out:
}
else {
unlink(cachetemp);
if (show_cache)
PERROR("Removed cache attempt: %s\n", cachetemp);
PERROR("Warning failed to create cache: %s\n", basename);
}
free(cachetemp);
}
@@ -1033,6 +1238,7 @@ int main(int argc, char *argv[])
init_base_dir();
process_config_file("/etc/apparmor/parser.conf");
optind = process_args(argc, argv);
setlocale(LC_MESSAGES, "");

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}
};
@@ -129,6 +135,9 @@ static int get_table_token(const char *name __unused, struct keyword_table *tabl
static struct keyword_table capability_table[] = {
/* capabilities */
#include "cap_names.h"
#ifndef CAP_SYSLOG
{"syslog", 34},
#endif
/* terminate */
{NULL, 0}
};
@@ -394,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 */
@@ -430,7 +449,7 @@ char *processquoted(char *string, int len)
*s = '\\';
l++;
break;
case '0' - '3':
case '0': case '1': case '2': case '3':
if ((l < len - 4) &&
strchr("0123456789", string[l + 2]) &&
strchr("0123456789", string[l + 3])) {
@@ -764,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))
@@ -866,6 +899,7 @@ static const char *capnames[] = {
"audit_control",
"setfcap",
"mac_override"
"syslog",
};
const char *capability_to_name(unsigned int cap)
@@ -900,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)
@@ -928,28 +960,103 @@ void debug_cod_list(struct codomain *cod)
dump_policy_hats(cod);
}
#ifdef UNIT_TEST
#define MY_TEST(statement, error) \
if (!(statement)) { \
PERROR("FAIL: %s\n", error); \
rc = 1; \
}
/* Guh, fake routine */
void yyerror(char *msg, ...)
struct value_list *new_value_list(char *value)
{
va_list arg;
char buf[PATH_MAX];
va_start(arg, msg);
vsnprintf(buf, sizeof(buf), msg, arg);
va_end(arg);
PERROR(_("AppArmor parser error: %s\n"), buf);
exit(1);
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)
{
int rc = 0;
@@ -973,7 +1080,7 @@ int test_str_to_boolean(void)
int test_processunquoted(void)
{
int rc = 0;
const char *teststring, *processedstring;
char *teststring, *processedstring;
teststring = "";
MY_TEST(strcmp(teststring, processunquoted(teststring, strlen(teststring))) == 0,
@@ -998,6 +1105,106 @@ int test_processunquoted(void)
return rc;
}
int test_processquoted(void)
{
int rc = 0;
char *teststring, *processedstring;
char *out;
teststring = "";
out = processquoted(teststring, strlen(teststring));
MY_TEST(strcmp(teststring, out) == 0,
"processquoted on empty string");
free(out);
teststring = "\"abcdefg\"";
processedstring = "abcdefg";
out = processquoted(teststring, strlen(teststring));
MY_TEST(strcmp(processedstring, out) == 0,
"processquoted on simple string");
free(out);
teststring = "\"abcd\\tefg\"";
processedstring = "abcd\tefg";
out = processquoted(teststring, strlen(teststring));
MY_TEST(strcmp(processedstring, out) == 0,
"processquoted on string with tab");
free(out);
teststring = "\"abcdefg\\\"";
processedstring = "abcdefg\\";
out = processquoted(teststring, strlen(teststring));
MY_TEST(strcmp(processedstring, out) == 0,
"processquoted on trailing slash");
free(out);
teststring = "\"a\\\\bcdefg\"";
processedstring = "a\\bcdefg";
out = processquoted(teststring, strlen(teststring));
MY_TEST(strcmp(processedstring, out) == 0,
"processquoted on quoted slash");
free(out);
teststring = "\"a\\\"bcde\\\"fg\"";
processedstring = "a\"bcde\"fg";
out = processquoted(teststring, strlen(teststring));
MY_TEST(strcmp(processedstring, out) == 0,
"processquoted on quoted quotes");
free(out);
teststring = "\"\\rabcdefg\"";
processedstring = "\rabcdefg";
out = processquoted(teststring, strlen(teststring));
MY_TEST(strcmp(processedstring, out) == 0,
"processquoted on quoted \\r");
free(out);
teststring = "\"abcdefg\\n\"";
processedstring = "abcdefg\n";
out = processquoted(teststring, strlen(teststring));
MY_TEST(strcmp(processedstring, out) == 0,
"processquoted on quoted \\n");
free(out);
teststring = "\"\\Uabc\\Ndefg\\x\"";
processedstring = "\\Uabc\\Ndefg\\x";
out = processquoted(teststring, strlen(teststring));
MY_TEST(strcmp(processedstring, out) == 0,
"processquoted passthrough on invalid quoted chars");
free(out);
teststring = "\"abc\\042defg\"";
processedstring = "abc\"defg";
out = processquoted(teststring, strlen(teststring));
MY_TEST(strcmp(processedstring, out) == 0,
"processquoted on quoted octal \\042");
free(out);
teststring = "\"abcdefg\\176\"";
processedstring = "abcdefg~";
out = processquoted(teststring, strlen(teststring));
MY_TEST(strcmp(processedstring, out) == 0,
"processquoted on quoted octal \\176");
free(out);
/* yes, our octal processing is lame; patches accepted */
teststring = "\"abc\\42defg\"";
processedstring = "abc\\42defg";
out = processquoted(teststring, strlen(teststring));
MY_TEST(strcmp(processedstring, out) == 0,
"processquoted passthrough quoted invalid octal \\42");
free(out);
teststring = "\"abcdefg\\04\"";
processedstring = "abcdefg\\04";
out = processquoted(teststring, strlen(teststring));
MY_TEST(strcmp(processedstring, out) == 0,
"processquoted passthrough quoted invalid trailing octal \\04");
free(out);
return rc;
}
int main(void)
{
int rc = 0;
@@ -1010,6 +1217,11 @@ int main(void)
retval = test_processunquoted();
if (retval != 0)
rc = retval;
retval = test_processquoted();
if (retval != 0)
rc = retval;
return rc;
}
#endif /* UNIT_TEST */

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

@@ -16,6 +16,7 @@
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <libintl.h>
@@ -26,6 +27,9 @@
#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,
@@ -506,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);
@@ -609,35 +622,485 @@ 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();
}
#ifdef UNIT_TEST
#define MY_TEST(statement, error) \
if (!(statement)) { \
PERROR("FAIL: %s\n", error); \
rc = 1; \
}
/* Guh, fake routine */
void yyerror(char *msg, ...)
{
va_list arg;
char buf[PATH_MAX];
va_start(arg, msg);
vsnprintf(buf, sizeof(buf), msg, arg);
va_end(arg);
PERROR(_("AppArmor parser error: %s\n"), buf);
exit(1);
}
/* Guh, fake symbol */
char *progname;
static int test_filter_slashes(void)
{
int rc = 0;

View File

@@ -539,30 +539,6 @@ void free_symtabs(void)
}
#ifdef UNIT_TEST
#define MY_TEST(statement, error) \
if (!(statement)) { \
PERROR("FAIL: %s\n", error); \
rc = 1; \
}
/* Guh, fake symbol */
char *progname;
/* Guh, fake routine */
void yyerror(char *msg, ...)
{
va_list arg;
char buf[PATH_MAX];
va_start(arg, msg);
vsnprintf(buf, sizeof(buf), msg, arg);
va_end(arg);
PERROR(_("AppArmor parser error: %s\n"), buf);
exit(1);
}
int main(void)
{
int rc = 0;
@@ -589,7 +565,7 @@ int main(void)
retval = new_set_var("test", "different value");
MY_TEST(retval != 0, "new set variable 2");
retval = new_set_var("testes", "testes");
retval = new_set_var("testing", "testing");
MY_TEST(retval == 0, "new set variable 3");
retval = new_set_var("monopuff", "Mockingbird");

View File

@@ -28,6 +28,7 @@
/* #define DEBUG */
#include "parser.h"
#include "mount.h"
static inline char *get_var_end(char *var)
{
@@ -36,8 +37,14 @@ static inline char *get_var_end(char *var)
while (*eptr) {
if (*eptr == '}')
return eptr;
if (!(*eptr == '_' || isalpha(*eptr)))
return NULL; /* invalid char */
/* first character must be alpha */
if (eptr == var) {
if (!isalpha(*eptr))
return NULL; /* invalid char */
} else {
if (!(*eptr == '_' || isalnum(*eptr)))
return NULL; /* invalid char */
}
eptr++;
}
return NULL; /* no terminating '}' */
@@ -124,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);
@@ -153,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;
@@ -181,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;
@@ -203,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;
}
@@ -210,29 +272,6 @@ int process_variables(struct codomain *cod)
}
#ifdef UNIT_TEST
#define MY_TEST(statement, error) \
if (!(statement)) { \
PERROR("FAIL: %s\n", error); \
rc = 1; \
}
/* Guh, fake routine */
void yyerror(char *msg, ...)
{
va_list arg;
char buf[PATH_MAX];
va_start(arg, msg);
vsnprintf(buf, sizeof(buf), msg, arg);
va_end(arg);
PERROR(_("AppArmor parser error: %s\n"), buf);
exit(1);
}
/* Guh, fake symbol */
char *progname;
int test_get_var_end(void)
{
int rc = 0;
@@ -271,7 +310,7 @@ int test_split_string(void)
char *var = "boogie";
char *suffix = "suffixication";
(void) asprintf(&tst_string, "%s@{%s}%s", prefix, var, suffix);
asprintf(&tst_string, "%s@{%s}%s", prefix, var, suffix);
var_start = tst_string + strlen(prefix);
var_end = var_start + strlen(var) + strlen("@\{");
ret_struct = split_string(tst_string, var_start, var_end);
@@ -317,6 +356,8 @@ int test_split_out_var(void)
struct var_string *ret_struct;
char *prefix = "abcdefg";
char *var = "boogie";
char *var2 = "V4rW1thNum5";
char *var3 = "boogie_board";
char *suffix = "suffixication";
/* simple case */
@@ -394,6 +435,34 @@ int test_split_out_var(void)
MY_TEST(strcmp(ret_struct->suffix, suffix) == 0, "split out var 7 suffix");
free_var_string(ret_struct);
/* numeric char in var, should succeed */
asprintf(&tst_string, "%s@{%s}%s", prefix, var2, suffix);
ret_struct = split_out_var(tst_string);
MY_TEST(ret_struct && strcmp(ret_struct->prefix, prefix) == 0, "split out numeric var prefix");
MY_TEST(ret_struct && strcmp(ret_struct->var, var2) == 0, "split numeric var var");
MY_TEST(ret_struct && strcmp(ret_struct->suffix, suffix) == 0, "split out numeric var suffix");
free_var_string(ret_struct);
/* numeric first char in var, should fail */
asprintf(&tst_string, "%s@{6%s}%s", prefix, var2, suffix);
ret_struct = split_out_var(tst_string);
MY_TEST(ret_struct == NULL, "split out var - numeric first char in var");
free_var_string(ret_struct);
/* underscore char in var, should succeed */
asprintf(&tst_string, "%s@{%s}%s", prefix, var3, suffix);
ret_struct = split_out_var(tst_string);
MY_TEST(ret_struct && strcmp(ret_struct->prefix, prefix) == 0, "split out underscore var prefix");
MY_TEST(ret_struct && strcmp(ret_struct->var, var3) == 0, "split out underscore var");
MY_TEST(ret_struct && strcmp(ret_struct->suffix, suffix) == 0, "split out underscore var suffix");
free_var_string(ret_struct);
/* underscore first char in var, should fail */
asprintf(&tst_string, "%s@{_%s%s}%s", prefix, var2, var3, suffix);
ret_struct = split_out_var(tst_string);
MY_TEST(ret_struct == NULL, "split out var - underscore first char in var");
free_var_string(ret_struct);
return rc;
}
int main(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.
* 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,9 +257,10 @@ 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",
$3,
$2,
cod->flags.complain ? "complain, " : "",
cod->flags.audit ? "audit" : "");
@@ -280,7 +300,7 @@ hat: hat_start profile_base
{
struct codomain *cod = $2;
if ($2)
PDEBUG("Matched: hat %s { ... }\n", code->name);
PDEBUG("Matched: hat %s { ... }\n", cod->name);
cod->flags.hat = 1;
$$ = cod;
@@ -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 (%s)\n", $1);
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");
@@ -711,10 +744,31 @@ rules: rules TOK_SET TOK_RLIMIT TOK_ID TOK_LE TOK_VALUE TOK_END_OF_RULE
if (strcmp($6, "infinity") == 0) {
value = RLIM_INFINITY;
} else {
const char *seconds = "seconds";
const char *minutes = "minutes";
const char *hours = "hours";
const char *days = "days";
const char *kb = "KB";
const char *mb = "MB";
const char *gb = "GB";
tmp = strtoll($6, &end, 0);
switch (limit) {
case RLIMIT_CPU:
yyerror("RLIMIT '%s' is currently unsupported\n", $4);
if (!end || $6 == end || tmp < 0)
yyerror("RLIMIT '%s' invalid value %s\n", $4, $6);
if (*end == '\0' ||
strstr(seconds, end) == seconds) {
value = tmp;
} else if (strstr(minutes, end) == minutes) {
value = tmp * 60;
} else if (strstr(hours, end) == hours) {
value = tmp * 60 * 60;
} else if (strstr(days, end) == days) {
value = tmp * 60 * 60 * 24;
} else {
yyerror("RLIMIT '%s' invalid value %s\n", $4, $6);
}
break;
case RLIMIT_NOFILE:
case RLIMIT_NPROC:
@@ -722,14 +776,14 @@ rules: rules TOK_SET TOK_RLIMIT TOK_ID TOK_LE TOK_VALUE TOK_END_OF_RULE
case RLIMIT_SIGPENDING:
#ifdef RLIMIT_RTPRIO
case RLIMIT_RTPRIO:
if ($6 == end || *end != '\0' || tmp < 0)
if (!end || $6 == end || *end != '\0' || tmp < 0)
yyerror("RLIMIT '%s' invalid value %s\n", $4, $6);
value = tmp;
break;
#endif
#ifdef RLIMIT_NICE
case RLIMIT_NICE:
if ($6 == end || *end != '\0')
if (!end || $6 == end || *end != '\0')
yyerror("RLIMIT '%s' invalid value %s\n", $4, $6);
if (tmp < -20 || tmp > 19)
yyerror("RLIMIT '%s' out of range (-20 .. 19) %d\n", $4, tmp);
@@ -746,11 +800,11 @@ rules: rules TOK_SET TOK_RLIMIT TOK_ID TOK_LE TOK_VALUE TOK_END_OF_RULE
case RLIMIT_MSGQUEUE:
if ($6 == end || tmp < 0)
yyerror("RLIMIT '%s' invalid value %s\n", $4, $6);
if (strcmp(end, "K") == 0) {
if (strstr(kb, end) == kb) {
tmp *= 1024;
} else if (strcmp(end, "M") == 0) {
} else if (strstr(mb, end) == mb) {
tmp *= 1024*1024;
} else if (strcmp(end, "G") == 0) {
} else if (strstr(gb, end) == gb) {
tmp *= 1024*1024*1024;
} else if (*end != '\0') {
yyerror("RLIMIT '%s' invalid value %s\n", $4, $6);
@@ -871,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
{
@@ -912,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);
@@ -931,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);
@@ -940,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);
@@ -988,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
@@ -1022,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)
@@ -1041,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"),
@@ -1074,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,
@@ -1128,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

@@ -27,7 +27,7 @@
### BEGIN INIT INFO
# Provides: aaeventd
# Required-Start: apparmor
# Required-Stop:
# Required-Stop: $null
# Default-Start: 2 3 5
# Default-Stop:
# Short-Description: AppArmor Notification and Reporting
@@ -78,9 +78,9 @@ usage() {
start_aa_event() {
if [ -x "$AA_EV_BIN" -a "${APPARMOR_ENABLE_AAEVENTD}" = "yes" ] ; then
sd_action "Starting AppArmor Event daemon" startproc -f -p $AA_EV_PIDFILE $AA_EV_BIN -p $AA_EV_PIDFILE
sd_action "Starting AppArmor Event daemon" startproc -p $AA_EV_PIDFILE $AA_EV_BIN -p $AA_EV_PIDFILE
elif [ -x "$SD_EV_BIN" -a "${APPARMOR_ENABLE_AAEVENTD}" = "yes" ] ; then
sd_action "Starting AppArmor Event daemon" startproc -f -p $SD_EV_PIDFILE $SD_EV_BIN -p $SD_EV_PIDFILE
sd_action "Starting AppArmor Event daemon" startproc -p $SD_EV_PIDFILE $SD_EV_BIN -p $SD_EV_PIDFILE
fi
}

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
@@ -83,15 +83,6 @@ SECURITYFS=/sys/kernel/security
SUBDOMAINFS_MOUNTPOINT=$(grep subdomainfs /etc/fstab | \
sed -e 's|^[[:space:]]*[^[:space:]]\+[[:space:]]\+\(/[^[:space:]]*\)[[:space:]]\+subdomainfs.*$|\1|' 2> /dev/null)
if [ -d "/var/lib/${MODULE}" ] ; then
APPARMOR_TMPDIR="/var/lib/${MODULE}"
elif [ -d "/var/lib/${OLD_MODULE}" ] ; then
APPARMOR_TMPDIR="/var/lib/${OLD_MODULE}"
else
APPARMOR_TMPDIR="/tmp"
fi
# keep exit status from parser during profile load. 0 is good, 1 is bad
STATUS=0
@@ -108,9 +99,7 @@ is_apparmor_present() {
# check for subdomainfs version of module
grep -qE "^($modules)[[:space:]]" /proc/modules
if [ $? -ne 0 ] ; then
ls /sys/module/apparmor 2>/dev/null | grep -qE "^($modules)"
fi
[ $? -ne 0 -a -d /sys/module/apparmor ]
return $?
}
@@ -170,7 +159,7 @@ parse_profiles() {
exit 1
;;
esac
aa_log_action_begin "$PARSER_MSG"
aa_log_action_start "$PARSER_MSG"
# run the parser on all of the apparmor profiles
if [ ! -f "$PARSER" ]; then
aa_log_failure_msg "AppArmor parser not found"
@@ -198,6 +187,7 @@ parse_profiles() {
aa_log_skipped_msg "$profile"
logger -t "AppArmor(init)" -p daemon.warn "Skipping profile $profile"
STATUS=2
continue
elif [ "$skip" -ne 0 ]; then
continue
fi
@@ -222,7 +212,6 @@ parse_profiles() {
profiles_names_list() {
# run the parser on all of the apparmor profiles
TMPFILE=$1
if [ ! -f "$PARSER" ]; then
aa_log_failure_msg "- AppArmor parser not found"
exit 1
@@ -235,9 +224,9 @@ profiles_names_list() {
for profile in $PROFILE_DIR/*; do
if skip_profile "${profile}" && [ -f "${profile}" ] ; then
LIST_ADD=$($PARSER $ABSTRACTIONS -N "$profile" | grep -v '\^')
LIST_ADD=$($PARSER $ABSTRACTIONS -N "$profile" )
if [ $? -eq 0 ]; then
echo "$LIST_ADD" >>$TMPFILE
echo "$LIST_ADD"
fi
fi
done
@@ -297,7 +286,7 @@ is_apparmor_loaded() {
}
is_securityfs_mounted() {
grep -q securityfs /proc/filesystems && grep -q securityfs /proc/mounts
test -d ${SECURITYFS} -a -d /sys/fs/cgroup/systemd || grep -q securityfs /proc/filesystems && grep -q securityfs /proc/mounts
return $?
}
@@ -377,10 +366,11 @@ apparmor_start() {
configure_owlsm
# if there is anything in the profiles file don't load
cat "$SFS_MOUNTPOINT/profiles" | if ! read line ; then
if ! read line < "$SFS_MOUNTPOINT/profiles"; then
parse_profiles load
else
aa_log_skipped_msg "AppArmor already loaded with profiles."
aa_log_skipped_msg ": already loaded with profiles."
return 0
fi
aa_log_end_msg 0
return 0
@@ -408,18 +398,16 @@ remove_profiles() {
fi
retval=0
#the list of profiles isn't stable once we start adding or removing
#them so stor to tmp first
MODULE_PLIST=$(mktemp ${APPARMOR_TMPDIR}/tmp.XXXXXXXX)
sed -e "s/ (\(enforce\|complain\))$//" "$SFS_MOUNTPOINT/profiles" | sort >"$MODULE_PLIST"
cat "$MODULE_PLIST" | while read profile ; do
# We filter child profiles as removing the parent will remove
# the children
sed -e "s/ (\(enforce\|complain\))$//" "$SFS_MOUNTPOINT/profiles" | \
LC_COLLATE=C sort | grep -v // | while read profile ; do
echo -n "$profile" > "$SFS_MOUNTPOINT/.remove"
rc=$?
if [ ${rc} -ne 0 ] ; then
retval=${rc}
fi
done
rm "$MODULE_PLIST"
return ${retval}
}
@@ -427,7 +415,7 @@ apparmor_stop() {
aa_log_daemon_msg "Unloading AppArmor profiles "
remove_profiles
rc=$?
log_end_msg $rc
aa_log_end_msg $rc
return $rc
}
@@ -459,20 +447,41 @@ __apparmor_restart() {
return 4
fi
aa_log_daemon_msg "Restarting AppArmor"
configure_owlsm
parse_profiles reload
PNAMES_LIST=$(mktemp ${APPARMOR_TMPDIR}/tmp.XXXXXXXX)
profiles_names_list ${PNAMES_LIST}
MODULE_PLIST=$(mktemp ${APPARMOR_TMPDIR}/tmp.XXXXXXXX)
# Clean out running profiles not associated with the current profile
# set, excluding the libvirt dynamically generated profiles.
sed -e "s/ (\(enforce\|complain\))$//" "$SFS_MOUNTPOINT/profiles" | egrep -v '^libvirt-[0-9a-f\-]+$' | sort >"$MODULE_PLIST"
sort "$PNAMES_LIST" | comm -2 -3 "$MODULE_PLIST" - | while IFS= read profile ; do
# Note that we reverse sort the list of profiles to remove to
# ensure that child profiles (e.g. hats) are removed before the
# parent. We *do* need to remove the child profile and not rely
# on removing the parent profile when the profile has had its
# child profile names changed.
profiles_names_list | awk '
BEGIN {
while (getline < "'${SFS_MOUNTPOINT}'/profiles" ) {
str = sub(/ \((enforce|complain)\)$/, "", $0);
if (match($0, /^libvirt-[0-9a-f\-]+$/) == 0)
arr[$str] = $str
}
}
{ if (length(arr[$0]) > 0) { delete arr[$0] } }
END {
for (key in arr)
if (length(arr[key]) > 0) {
printf("%s\n", arr[key])
}
}
' | LC_COLLATE=C sort -r | while IFS= read profile ; do
echo -n "$profile" > "$SFS_MOUNTPOINT/.remove"
done
rm "$MODULE_PLIST"
rm "$PNAMES_LIST"
return 0
# will not catch all errors, but still better than nothing
rc=$?
aa_log_end_msg $rc
return $rc
}
apparmor_restart() {
@@ -516,11 +525,11 @@ apparmor_status () {
${SD_STATUS} --verbose
return $?
fi
if ! is_apparmor_present apparmor subdomain ; then
if ! is_apparmor_loaded ; then
echo "AppArmor is not loaded."
rc=1
else
echo "AppArmor is enabled,"
echo "AppArmor is enabled."
rc=0
fi
echo "Install the apparmor-utils package to receive more detailed"

View File

@@ -31,6 +31,7 @@
# Required-Start: boot.cleanup
# Required-Stop: $null
# Should-Start: $local_fs
# Should-Stop: $null
# Default-Start: B
# Default-Stop:
# Short-Description: AppArmor initialization
@@ -73,7 +74,19 @@ aa_log_warning_msg() {
}
aa_log_failure_msg() {
log_failure_msg $*
log_failure_msg '\n'$*
}
aa_log_action_start() {
echo -n
}
aa_log_action_end() {
echo -n
}
aa_log_daemon_msg() {
echo -en "$@ "
}
aa_log_skipped_msg() {
@@ -81,6 +94,14 @@ aa_log_skipped_msg() {
echo -e "$rc_skipped"
}
aa_log_end_msg() {
v="-v"
if [ "$1" != '0' ]; then
rc="-v$1"
fi
rc_status $v
}
usage() {
echo "Usage: $0 {start|stop|restart|try-restart|reload|force-reload|status|kill}"
}

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

@@ -4,19 +4,25 @@ TESTS=simple.pl
PARSER_DIR=..
PARSER_BIN=apparmor_parser
PARSER=$(PARSER_DIR)/$(PARSER_BIN)
PROVE_ARG=-f
ifeq ($(VERBOSE),1)
PROVE_ARG=-v
PROVE_ARG+=-v
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_xtrans:
GEN_TRANS_DIRS=simple_tests/generated_x/ simple_tests/generated_perms_leading/ simple_tests/generated_perms_safe/
gen_xtrans: $(GEN_TRANS_DIRS)
./gen-xtrans.pl
$(GEN_TRANS_DIRS):
mkdir $@
error_output: $(PARSER)
$(PARSER) -S -I errors >/dev/null errors/okay.sd
LANG=C $(PARSER) -S -I errors 2>&1 >/dev/null errors/single.sd | \
@@ -35,10 +41,11 @@ parser_sanity: $(PARSER)
caching: $(PARSER)
LANG=C ./caching.sh
minimize: $(PARSER)
LANG=C ./minimize.sh
$(PARSER):
make -C $(PARSER_DIR) $(PARSER_BIN)
clean:
rm -f simple_tests/generated_x/*
rm -f simple_tests/generated_perms_leading/*
rm -f simple_tests/generated_perms_safe/*
find $(GEN_TRANS_DIRS) -type f | xargs rm -f

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: "

View File

@@ -118,7 +118,7 @@ sub gen_file($$$$$$$$$$$$) {
print $file "/usr/bin/foo {\n";
print_rule($file, $leading1, $qual1, $rule1, $perm1, $target1);
print_rule($file, $leading2, $qual2, $rule2, $perm2, $target2);
print $file "}";
print $file "}\n";
close($file);
$count++;

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