2
0
mirror of https://gitlab.com/apparmor/apparmor synced 2025-09-02 15:25:27 +00:00

Compare commits

..

68 Commits

Author SHA1 Message Date
John Johansen
7b98d8a227 Bump version to apparmor 2.8.0 2012-05-31 10:27:48 -07:00
John Johansen
b0443467aa Bump version number to 2.8.0 2012-05-31 10:25:02 -07:00
John Johansen
41b454f2e5 Older C++ compilers complain about the use of a class with a non trivial
constructor in a union.  Change the ProtoState class to use an init fn
instead of a constructor.
2012-05-30 14:31:41 -07:00
John Johansen
2347b6628d Kernel patches for v3.2, v3.3, v3.4 kernels 2012-05-21 20:23:15 -07:00
Jamie Strandboge
64a8698a5f Adjust path for thunderbird to include non-versioned path
Bug-Ubuntu: https://launchpad.net/bugs/990931

Acked-By: Jamie Strandboge <jamie@canonical.com>
2012-05-18 15:30:22 -05:00
Jamie Strandboge
d418a16703 mark easyprof and easyprof test scripts as executable 2012-05-09 11:05:07 -07:00
Christian Boltz
440e9c3d5d various changes in building techdoc.tex:
- make table of contents, footnotes etc. clickable hyperlinks
- use timestamp of techdoc.tex (instead of build time) as creationdate
  in the PDF metadata
- don't include build date on first page of the PDF
- make clean:
  - delete techdoc.out (created by pdftex)
  - fix deletion of techdoc.txt (was techdo_r_.txt)

The initial target was to get reproduceable PDF builds (therefore the 
timestamp-related changes), the other things came up during discussing
this patch with David Haller.

The only remaining difference in the PDF from build to build is the /ID
line.  This line can't be controlled in pdflatex and is now filtered 
out by build-compare in the openSUSE build service (bnc#760867).

Credits go to David Haller for writing large parts of this patch
(but he didn't notice the techdo_r_.txt ;-)


Signed-Off-By: Christian Boltz <apparmor@cboltz.de>
2012-05-09 00:41:06 +02:00
Jamie Strandboge
1db463f4de This patchset is broken into 4 parts:
* the application, library, documentation and installation script
* the initial templates and policy groups. This will undoubtedly need
  refinement as we get feedback from users. Initial policy is based on Ubuntu's
  Application Review Board (ARB) requirements[2].
* tests for the library
* Makefile integration

Templates are stored in /usr/share/apparmor/easyprof/templates and policy
groups in /usr/share/apparmor/easyprof/policygroups. This can be adjusted via
/etc/apparmor/easyprof.conf.

The aa-easyprof.pod has complete documentation on usage with some
additional information in utils/easyprof/README (mostly duplicated
here).

Testing can be performed in a number of ways:
$ cd utils ; make check # runs unit tests and pyflakes

Unit tests manually:
$ ./test/test-aa-easyprof.py

In source manual testing:
$ ./aa-easyprof --templates-dir=./easyprof/templates \
                --policy-groups-dir=./easyprof/policygroups \
                ... \
                /opt/foo/bin/foo

Post-install manual testing:
$ make DESTDIR=/tmp/test PERLDIR=/tmp/test/usr/share/perl5/Immunix install
$ cd /tmp/test
$ PYTHONPATH=/tmp/test/usr/local/.../dist-packages ./usr/bin/aa-easyprof \
    --templates-dir=/tmp/test/usr/share/apparmor/easyprof/templates \
    --policy-groups-dir=/tmp/test/usr/share/apparmor/easyprof/policygroups \
    /opt/bin/foo

(you may also adjust /tmp/test/etc/apparmor/easyprof.conf to avoid
specifying --templates-dir and --policy-groups-dir).

Committing this now based on conversation with John and Steve.

Acked-By: Jamie Strandboge <jamie@canonical.com>
2012-05-07 22:37:48 -07:00
Jamie Strandboge
279b5945cb Allow Google Chrome and chromium-browser to work under sanitized helper. While
the chromium and chrome sandboxes are setuid root, they only link in limited
libraries so glibc's secure execution should be enough to not require the
santized_helper (ie, LD_PRELOAD will only use standard system paths (man
ld.so)). Also allow some paths in /opt for Chrome.

Ubuntu-Bug: https://launchpad.net/bugs/964510

Acked-By: Jamie Strandboge <jamie@canonical.com>
2012-05-02 07:44:55 -05:00
Jamie Strandboge
d2bcf440e8 Allow software center to work again from browsers. It was blocked by
sanitized_helper. For now this only allows software-center scripts in
/usr/share, but we may need to increase what is allowed in /usr/share if more
things are denied when they shouldn't be.

Ubuntu-Bug: https://launchpad.net/bugs/972367

Acked-By: Jamie Strandboge <jamie@canonical.com>
2012-05-02 07:43:30 -05:00
Kees Cook
33557e22ed The m4 shipped to handle Python was incorrectly clearing
$CPPFLAGS. Additionally, do not repeat compiler flags for automake
targets that already include them, and pass more flags to the Perl build.

Signed-off-by: Kees Cook <kees@ubuntu.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-04-25 12:15:19 -07:00
Kees Cook
67ce4c3bd9 Include IceWeasel in ubuntu-browsers abstraction.
Author: Intrigeri <intrigeri@debian.org>
Bug-Debian: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=661176

Signed-off-by: Kees Cook <kees@ubuntu.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
Acked-By: Jamie Strandboge <jamie@canonical.com>
2012-04-25 12:13:15 -07:00
Kees Cook
dd91c7791b Updates the X abstraction to include gdm3 path.
Author: Intrigeri <intrigeri@debian.org>
Bug-Debian: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=660079

Signed-off-by: Kees Cook <kees@ubuntu.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-04-25 11:36:51 -07:00
Steve Beattie
fc6b59e8b1 Subject: fix aa-logprof rewrite of PUx modes.
When writing out a profile, aa-logprof incorrectly converts PUx execute
permission modes to the syntactically invalid UPx mode, because the
function that converts the internal representation of permissions to
a string emits the U(nconfined) mode bit before the P bit.

This patch corrects this by reordering the way the exec permissions
are emitted, so that P and C modes come before U and i. Based on
http://wiki.apparmor.net/index.php/AppArmor_Core_Policy_Reference#Execute_rules
this should emit the modes correctly in all combined exec modes.
Other approaches to fixing this would require adjusting the data
structure that contains the permission modes, resulting in a more
invasive patch.

Bug: https://launchpad.net/bugs/982619
2012-04-24 11:00:18 -07:00
Christian Boltz
ebe8803e80 If tftp server for dnsmasq is configured it won't serve the boot
file. This patch adds read permissions for /srv/tftpboot/

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

Somehow ;-) [1] Acked-By: John Johansen

[1] see mailinglist for details ;-)
2012-04-16 23:10:43 +02:00
Steve Beattie
a078c1feb5 With the fixing of the change_profile rules to automatically allow
access to /proc/*/attr/{current,exec}, the onexec testcase that
attempted to do things without explicit access granted to
/proc/*/attr/exec in the testsuite passes instead of fails. This commit
takes that into account.
2012-04-11 23:17:52 -07:00
John Johansen
b6c08d74a6 bump version tag for apparmor 2.8 beta-5 2012-04-11 17:24:07 -07:00
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
96 changed files with 7453 additions and 657 deletions

View File

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

View File

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

View File

@@ -1 +1 @@
2.7.99
2.8.0

View File

@@ -0,0 +1,553 @@
From 125fccb600288968aa3395883c0a394c47176fcd 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 3783202..7459547 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 741dd13..ee8043e 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.9.5

View File

@@ -0,0 +1,391 @@
From 004192fb5223c7b81a949e36a080a5da56132826 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 69ddb47..867995c 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 @@ static 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.9.5

View File

@@ -0,0 +1,69 @@
From e5d90918aa31f948ecec2f3c088567dbab30c90b 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 file changed, 17 insertions(+)
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.9.5

View File

@@ -0,0 +1,553 @@
From 1023c7c2f9d9c5707147479104312c4c3d1a2c2b 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 97ce8fa..a54adbc 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 741dd13..ee8043e 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.9.5

View File

@@ -0,0 +1,391 @@
From da1ce2265ebb70860b9c137a542e48b170e4606b 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 e39df6d..235e9fa 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 @@ static 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.9.5

View File

@@ -0,0 +1,69 @@
From 5d05f2909c12f6f03581bca9c1fa52dafa10fb0f 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 file changed, 17 insertions(+)
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.9.5

View File

@@ -0,0 +1,264 @@
From 8de755e4dfdbc40bfcaca848ae6b5aeaf0ede0e8 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 1/3] UBUNTU: SAUCE: AppArmor: Add profile introspection file
to interface
Add the dynamic profiles file to the interace, to allow load policy
introspection.
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
Signed-off-by: Tim Gardner <tim.gardner@canonical.com>
---
security/apparmor/apparmorfs.c | 227 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 227 insertions(+)
diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c
index 16c15ec..89bdc62 100644
--- a/security/apparmor/apparmorfs.c
+++ b/security/apparmor/apparmorfs.c
@@ -182,6 +182,232 @@ const struct file_operations aa_fs_seq_file_ops = {
.release = single_release,
};
+/**
+ * __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,
+};
+
/** Base file system setup **/
static struct aa_fs_entry aa_fs_entry_file[] = {
@@ -210,6 +436,7 @@ static struct aa_fs_entry aa_fs_entry_apparmor[] = {
AA_FS_FILE_FOPS(".load", 0640, &aa_fs_profile_load),
AA_FS_FILE_FOPS(".replace", 0640, &aa_fs_profile_replace),
AA_FS_FILE_FOPS(".remove", 0640, &aa_fs_profile_remove),
+ AA_FS_FILE_FOPS("profiles", 0640, &aa_fs_profiles_fops),
AA_FS_DIR("features", aa_fs_entry_features),
{ }
};
--
1.7.9.5

View File

@@ -0,0 +1,603 @@
From 423e2cb454d75d6185eecd0c1b5cf6ccc2d8482d 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 2/3] UBUNTU: SAUCE: AppArmor: basic networking rules
Base support for network mediation.
Signed-off-by: John Johansen <john.johansen@canonical.com>
---
security/apparmor/.gitignore | 2 +-
security/apparmor/Makefile | 42 +++++++++-
security/apparmor/apparmorfs.c | 1 +
security/apparmor/include/audit.h | 4 +
security/apparmor/include/net.h | 44 ++++++++++
security/apparmor/include/policy.h | 3 +
security/apparmor/lsm.c | 112 +++++++++++++++++++++++++
security/apparmor/net.c | 162 ++++++++++++++++++++++++++++++++++++
security/apparmor/policy.c | 1 +
security/apparmor/policy_unpack.c | 46 ++++++++++
10 files changed, 414 insertions(+), 3 deletions(-)
create mode 100644 security/apparmor/include/net.h
create mode 100644 security/apparmor/net.c
diff --git a/security/apparmor/.gitignore b/security/apparmor/.gitignore
index 4d995ae..d5b291e 100644
--- a/security/apparmor/.gitignore
+++ b/security/apparmor/.gitignore
@@ -1,6 +1,6 @@
#
# Generated include files
#
-af_names.h
+net_names.h
capability_names.h
rlim_names.h
diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile
index 806bd19..19daa85 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 net_names.h
# Build a lower case string table of capability names
@@ -20,6 +20,38 @@ cmd_make-caps = echo "static const char *const capability_names[] = {" > $@ ;\
-e 's/^\#define[ \t]+CAP_([A-Z0-9_]+)[ \t]+([0-9]+)/[\2] = "\L\1",/p';\
echo "};" >> $@
+# Build a lower case string table of address family names
+# Transform lines from
+# define AF_LOCAL 1 /* POSIX name for AF_UNIX */
+# #define AF_INET 2 /* Internet IP Protocol */
+# to
+# [1] = "local",
+# [2] = "inet",
+#
+# and build the securityfs entries for the mapping.
+# Transforms lines from
+# #define AF_INET 2 /* Internet IP Protocol */
+# to
+# #define AA_FS_AF_MASK "local 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 "};" >> $@ ;\
+ echo -n '\#define AA_FS_AF_MASK "' >> $@ ;\
+ sed -r -n 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+)(.*)/\L\1/p'\
+ $< | tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@
+
+# Build a lower case string table of sock type names
+# Transform lines from
+# SOCK_STREAM = 1,
+# to
+# [1] = "stream",
+quiet_cmd_make-sock = GEN $@
+cmd_make-sock = echo "static const char *sock_type_names[] = {" >> $@ ;\
+ sed $^ >>$@ -r -n \
+ -e 's/^\tSOCK_([A-Z0-9_]+)[\t]+=[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\
+ echo "};" >> $@
# Build a lower case string table of rlimit names.
# Transforms lines from
@@ -56,6 +88,7 @@ cmd_make-rlim = echo "static const char *const rlim_names[RLIM_NLIMITS] = {" \
tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@
$(obj)/capability.o : $(obj)/capability_names.h
+$(obj)/net.o : $(obj)/net_names.h
$(obj)/resource.o : $(obj)/rlim_names.h
$(obj)/capability_names.h : $(srctree)/include/linux/capability.h \
$(src)/Makefile
@@ -63,3 +96,8 @@ $(obj)/capability_names.h : $(srctree)/include/linux/capability.h \
$(obj)/rlim_names.h : $(srctree)/include/asm-generic/resource.h \
$(src)/Makefile
$(call cmd,make-rlim)
+$(obj)/net_names.h : $(srctree)/include/linux/socket.h \
+ $(srctree)/include/linux/net.h \
+ $(src)/Makefile
+ $(call cmd,make-af)
+ $(call cmd,make-sock)
diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c
index 89bdc62..c66315d 100644
--- a/security/apparmor/apparmorfs.c
+++ b/security/apparmor/apparmorfs.c
@@ -427,6 +427,7 @@ static struct aa_fs_entry aa_fs_entry_domain[] = {
static struct aa_fs_entry aa_fs_entry_features[] = {
AA_FS_DIR("domain", aa_fs_entry_domain),
AA_FS_DIR("file", aa_fs_entry_file),
+ AA_FS_DIR("network", aa_fs_entry_network),
AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK),
AA_FS_DIR("rlimit", aa_fs_entry_rlimit),
{ }
diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h
index 3868b1e..c1ff09c 100644
--- a/security/apparmor/include/audit.h
+++ b/security/apparmor/include/audit.h
@@ -126,6 +126,10 @@ struct apparmor_audit_data {
u32 denied;
uid_t ouid;
} fs;
+ struct {
+ int type, protocol;
+ struct sock *sk;
+ } net;
};
};
diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h
new file mode 100644
index 0000000..cb8a121
--- /dev/null
+++ b/security/apparmor/include/net.h
@@ -0,0 +1,44 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor network mediation definitions.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-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.
+ */
+
+#ifndef __AA_NET_H
+#define __AA_NET_H
+
+#include <net/sock.h>
+
+#include "apparmorfs.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 struct aa_fs_entry aa_fs_entry_network[];
+
+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 bda4569..eb13a73 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 *const profile_mode_names[];
@@ -157,6 +158,7 @@ struct aa_policydb {
* @policy: general match rules governing policy
* @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
@@ -194,6 +196,7 @@ struct aa_profile {
struct aa_policydb policy;
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 ad05d39..3cde194 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"
@@ -622,6 +623,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",
@@ -653,6 +752,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..084232b
--- /dev/null
+++ b/security/apparmor/net.c
@@ -0,0 +1,162 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor network mediation
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-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.
+ */
+
+#include "include/apparmor.h"
+#include "include/audit.h"
+#include "include/context.h"
+#include "include/net.h"
+#include "include/policy.h"
+
+#include "net_names.h"
+
+struct aa_fs_entry aa_fs_entry_network[] = {
+ AA_FS_FILE_STRING("af_mask", AA_FS_AF_MASK),
+ { }
+};
+
+/* 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;
+ struct apparmor_audit_data aad = { };
+ struct lsm_network_audit net = { };
+ if (sk) {
+ COMMON_AUDIT_DATA_INIT(&sa, NET);
+ } else {
+ COMMON_AUDIT_DATA_INIT(&sa, NONE);
+ }
+ /* todo fill in socket addr info */
+ sa.aad = &aad;
+ sa.u.net = &net;
+ 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 f1f7506..b8100a7 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 deab7c7..8f8e9c1 100644
--- a/security/apparmor/policy_unpack.c
+++ b/security/apparmor/policy_unpack.c
@@ -193,6 +193,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)) {
@@ -471,6 +484,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
{
struct aa_profile *profile = NULL;
const char *name = NULL;
+ size_t size = 0;
int i, error = -EPROTO;
kernel_cap_t tmpcap;
u32 tmp;
@@ -564,6 +578,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;
+
if (unpack_nameX(e, AA_STRUCT, "policydb")) {
/* generic policy dfa - optional and may be NULL */
profile->policy.dfa = unpack_dfa(e);
--
1.7.9.5

View File

@@ -0,0 +1,957 @@
From a94d5e11c0484af59e5feebf144cc48c186892ad Mon Sep 17 00:00:00 2001
From: John Johansen <john.johansen@canonical.com>
Date: Wed, 16 May 2012 10:58:05 -0700
Subject: [PATCH 3/3] UBUNTU: SAUCE: apparmor: Add the ability to mediate
mount
Add the ability for apparmor to do mediation of mount operations. Mount
rules require an updated apparmor_parser (2.8 series) for policy compilation.
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>
remount is just a short cut for mount options=remount
where [conds] can be
fstype=<expr>
options=<expr>
Example mount commands
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=/) -> /mnt/
umount,
umount /m*,
See the apparmor userspace for full documentation
Signed-off-by: John Johansen <john.johansen@canonical.com>
Acked-by: Kees Cook <kees@ubuntu.com>
---
security/apparmor/Makefile | 2 +-
security/apparmor/apparmorfs.c | 13 +
security/apparmor/audit.c | 4 +
security/apparmor/domain.c | 2 +-
security/apparmor/include/apparmor.h | 3 +-
security/apparmor/include/audit.h | 11 +
security/apparmor/include/domain.h | 2 +
security/apparmor/include/mount.h | 54 +++
security/apparmor/lsm.c | 59 ++++
security/apparmor/mount.c | 620 ++++++++++++++++++++++++++++++++++
10 files changed, 767 insertions(+), 3 deletions(-)
create mode 100644 security/apparmor/include/mount.h
create mode 100644 security/apparmor/mount.c
diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile
index 19daa85..63e0a4c 100644
--- a/security/apparmor/Makefile
+++ b/security/apparmor/Makefile
@@ -4,7 +4,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
+ resource.o sid.o file.o net.o mount.o
clean-files := capability_names.h rlim_names.h net_names.h
diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c
index c66315d..ff19009 100644
--- a/security/apparmor/apparmorfs.c
+++ b/security/apparmor/apparmorfs.c
@@ -424,10 +424,23 @@ static struct aa_fs_entry aa_fs_entry_domain[] = {
{ }
};
+static struct aa_fs_entry aa_fs_entry_mount[] = {
+ AA_FS_FILE_STRING("mask", "mount umount"),
+ { }
+};
+
+static struct aa_fs_entry aa_fs_entry_namespaces[] = {
+ AA_FS_FILE_BOOLEAN("profile", 1),
+ AA_FS_FILE_BOOLEAN("pivot_root", 1),
+ { }
+};
+
static struct aa_fs_entry aa_fs_entry_features[] = {
AA_FS_DIR("domain", aa_fs_entry_domain),
AA_FS_DIR("file", aa_fs_entry_file),
AA_FS_DIR("network", aa_fs_entry_network),
+ AA_FS_DIR("mount", aa_fs_entry_mount),
+ AA_FS_DIR("namespaces", aa_fs_entry_namespaces),
AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK),
AA_FS_DIR("rlimit", aa_fs_entry_rlimit),
{ }
diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c
index cc3520d..b9f5ee9 100644
--- a/security/apparmor/audit.c
+++ b/security/apparmor/audit.c
@@ -44,6 +44,10 @@ const char *const op_table[] = {
"file_mmap",
"file_mprotect",
+ "pivotroot",
+ "mount",
+ "umount",
+
"create",
"post_create",
"bind",
diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c
index 6327685..dfdc47b 100644
--- a/security/apparmor/domain.c
+++ b/security/apparmor/domain.c
@@ -242,7 +242,7 @@ static const char *next_name(int xtype, const char *name)
*
* Returns: refcounted profile, or NULL on failure (MAYBE NULL)
*/
-static struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex)
+struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex)
{
struct aa_profile *new_profile = NULL;
struct aa_namespace *ns = profile->ns;
diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h
index 40aedd9..e243d96 100644
--- a/security/apparmor/include/apparmor.h
+++ b/security/apparmor/include/apparmor.h
@@ -29,8 +29,9 @@
#define AA_CLASS_NET 4
#define AA_CLASS_RLIMITS 5
#define AA_CLASS_DOMAIN 6
+#define AA_CLASS_MOUNT 7
-#define AA_CLASS_LAST AA_CLASS_DOMAIN
+#define AA_CLASS_LAST AA_CLASS_MOUNT
/* Control parameters settable through module/boot flags */
extern enum audit_mode aa_g_audit;
diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h
index c1ff09c..7b90900c 100644
--- a/security/apparmor/include/audit.h
+++ b/security/apparmor/include/audit.h
@@ -73,6 +73,10 @@ enum aa_ops {
OP_FMMAP,
OP_FMPROT,
+ OP_PIVOTROOT,
+ OP_MOUNT,
+ OP_UMOUNT,
+
OP_CREATE,
OP_POST_CREATE,
OP_BIND,
@@ -121,6 +125,13 @@ struct apparmor_audit_data {
unsigned long max;
} rlim;
struct {
+ const char *src_name;
+ const char *type;
+ const char *trans;
+ const char *data;
+ unsigned long flags;
+ } mnt;
+ struct {
const char *target;
u32 request;
u32 denied;
diff --git a/security/apparmor/include/domain.h b/security/apparmor/include/domain.h
index de04464..a3f70c5 100644
--- a/security/apparmor/include/domain.h
+++ b/security/apparmor/include/domain.h
@@ -23,6 +23,8 @@ struct aa_domain {
char **table;
};
+struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex);
+
int apparmor_bprm_set_creds(struct linux_binprm *bprm);
int apparmor_bprm_secureexec(struct linux_binprm *bprm);
void apparmor_bprm_committing_creds(struct linux_binprm *bprm);
diff --git a/security/apparmor/include/mount.h b/security/apparmor/include/mount.h
new file mode 100644
index 0000000..bc17a53
--- /dev/null
+++ b/security/apparmor/include/mount.h
@@ -0,0 +1,54 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor file mediation function definitions.
+ *
+ * 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.
+ */
+
+#ifndef __AA_MOUNT_H
+#define __AA_MOUNT_H
+
+#include <linux/fs.h>
+#include <linux/path.h>
+
+#include "domain.h"
+#include "policy.h"
+
+/* mount perms */
+#define AA_MAY_PIVOTROOT 0x01
+#define AA_MAY_MOUNT 0x02
+#define AA_MAY_UMOUNT 0x04
+#define AA_AUDIT_DATA 0x40
+#define AA_CONT_MATCH 0x40
+
+#define AA_MS_IGNORE_MASK (MS_KERNMOUNT | MS_NOSEC | MS_ACTIVE | MS_BORN)
+
+int aa_remount(struct aa_profile *profile, struct path *path,
+ unsigned long flags, void *data);
+
+int aa_bind_mount(struct aa_profile *profile, struct path *path,
+ const char *old_name, unsigned long flags);
+
+
+int aa_mount_change_type(struct aa_profile *profile, struct path *path,
+ unsigned long flags);
+
+int aa_move_mount(struct aa_profile *profile, struct path *path,
+ const char *old_name);
+
+int aa_new_mount(struct aa_profile *profile, const char *dev_name,
+ struct path *path, const char *type, unsigned long flags,
+ void *data);
+
+int aa_umount(struct aa_profile *profile, struct vfsmount *mnt, int flags);
+
+int aa_pivotroot(struct aa_profile *profile, struct path *old_path,
+ struct path *new_path);
+
+#endif /* __AA_MOUNT_H */
diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index 3cde194..4512cc6 100644
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -36,6 +36,7 @@
#include "include/path.h"
#include "include/policy.h"
#include "include/procattr.h"
+#include "include/mount.h"
/* Flag indicating whether initialization completed */
int apparmor_initialized __initdata;
@@ -512,6 +513,60 @@ static int apparmor_file_mprotect(struct vm_area_struct *vma,
!(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0);
}
+static int apparmor_sb_mount(char *dev_name, struct path *path, char *type,
+ unsigned long flags, void *data)
+{
+ struct aa_profile *profile;
+ int error = 0;
+
+ /* Discard magic */
+ if ((flags & MS_MGC_MSK) == MS_MGC_VAL)
+ flags &= ~MS_MGC_MSK;
+
+ flags &= ~AA_MS_IGNORE_MASK;
+
+ profile = __aa_current_profile();
+ if (!unconfined(profile)) {
+ if (flags & MS_REMOUNT)
+ error = aa_remount(profile, path, flags, data);
+ else if (flags & MS_BIND)
+ error = aa_bind_mount(profile, path, dev_name, flags);
+ else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE |
+ MS_UNBINDABLE))
+ error = aa_mount_change_type(profile, path, flags);
+ else if (flags & MS_MOVE)
+ error = aa_move_mount(profile, path, dev_name);
+ else
+ error = aa_new_mount(profile, dev_name, path, type,
+ flags, data);
+ }
+ return error;
+}
+
+static int apparmor_sb_umount(struct vfsmount *mnt, int flags)
+{
+ struct aa_profile *profile;
+ int error = 0;
+
+ profile = __aa_current_profile();
+ if (!unconfined(profile))
+ error = aa_umount(profile, mnt, flags);
+
+ return error;
+}
+
+static int apparmor_sb_pivotroot(struct path *old_path, struct path *new_path)
+{
+ struct aa_profile *profile;
+ int error = 0;
+
+ profile = __aa_current_profile();
+ if (!unconfined(profile))
+ error = aa_pivotroot(profile, old_path, new_path);
+
+ return error;
+}
+
static int apparmor_getprocattr(struct task_struct *task, char *name,
char **value)
{
@@ -729,6 +784,10 @@ static struct security_operations apparmor_ops = {
.capget = apparmor_capget,
.capable = apparmor_capable,
+ .sb_mount = apparmor_sb_mount,
+ .sb_umount = apparmor_sb_umount,
+ .sb_pivotroot = apparmor_sb_pivotroot,
+
.path_link = apparmor_path_link,
.path_unlink = apparmor_path_unlink,
.path_symlink = apparmor_path_symlink,
diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c
new file mode 100644
index 0000000..63d8493
--- /dev/null
+++ b/security/apparmor/mount.c
@@ -0,0 +1,620 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor mediation of files
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-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.
+ */
+
+#include <linux/fs.h>
+#include <linux/mount.h>
+#include <linux/namei.h>
+
+#include "include/apparmor.h"
+#include "include/audit.h"
+#include "include/context.h"
+#include "include/domain.h"
+#include "include/file.h"
+#include "include/match.h"
+#include "include/mount.h"
+#include "include/path.h"
+#include "include/policy.h"
+
+
+static void audit_mnt_flags(struct audit_buffer *ab, unsigned long flags)
+{
+ if (flags & MS_RDONLY)
+ audit_log_format(ab, "ro");
+ else
+ audit_log_format(ab, "rw");
+ if (flags & MS_NOSUID)
+ audit_log_format(ab, ", nosuid");
+ if (flags & MS_NODEV)
+ audit_log_format(ab, ", nodev");
+ if (flags & MS_NOEXEC)
+ audit_log_format(ab, ", noexec");
+ if (flags & MS_SYNCHRONOUS)
+ audit_log_format(ab, ", sync");
+ if (flags & MS_REMOUNT)
+ audit_log_format(ab, ", remount");
+ if (flags & MS_MANDLOCK)
+ audit_log_format(ab, ", mand");
+ if (flags & MS_DIRSYNC)
+ audit_log_format(ab, ", dirsync");
+ if (flags & MS_NOATIME)
+ audit_log_format(ab, ", noatime");
+ if (flags & MS_NODIRATIME)
+ audit_log_format(ab, ", nodiratime");
+ if (flags & MS_BIND)
+ audit_log_format(ab, flags & MS_REC ? ", rbind" : ", bind");
+ if (flags & MS_MOVE)
+ audit_log_format(ab, ", move");
+ if (flags & MS_SILENT)
+ audit_log_format(ab, ", silent");
+ if (flags & MS_POSIXACL)
+ audit_log_format(ab, ", acl");
+ if (flags & MS_UNBINDABLE)
+ audit_log_format(ab, flags & MS_REC ? ", runbindable" :
+ ", unbindable");
+ if (flags & MS_PRIVATE)
+ audit_log_format(ab, flags & MS_REC ? ", rprivate" :
+ ", private");
+ if (flags & MS_SLAVE)
+ audit_log_format(ab, flags & MS_REC ? ", rslave" :
+ ", slave");
+ if (flags & MS_SHARED)
+ audit_log_format(ab, flags & MS_REC ? ", rshared" :
+ ", shared");
+ if (flags & MS_RELATIME)
+ audit_log_format(ab, ", relatime");
+ if (flags & MS_I_VERSION)
+ audit_log_format(ab, ", iversion");
+ if (flags & MS_STRICTATIME)
+ audit_log_format(ab, ", strictatime");
+ if (flags & MS_NOUSER)
+ audit_log_format(ab, ", nouser");
+}
+
+/**
+ * audit_cb - call back for mount specific audit fields
+ * @ab: audit_buffer (NOT NULL)
+ * @va: audit struct to audit values of (NOT NULL)
+ */
+static void audit_cb(struct audit_buffer *ab, void *va)
+{
+ struct common_audit_data *sa = va;
+
+ if (sa->aad->mnt.type) {
+ audit_log_format(ab, " fstype=");
+ audit_log_untrustedstring(ab, sa->aad->mnt.type);
+ }
+ if (sa->aad->mnt.src_name) {
+ audit_log_format(ab, " srcname=");
+ audit_log_untrustedstring(ab, sa->aad->mnt.src_name);
+ }
+ if (sa->aad->mnt.trans) {
+ audit_log_format(ab, " trans=");
+ audit_log_untrustedstring(ab, sa->aad->mnt.trans);
+ }
+ if (sa->aad->mnt.flags || sa->aad->op == OP_MOUNT) {
+ audit_log_format(ab, " flags=\"");
+ audit_mnt_flags(ab, sa->aad->mnt.flags);
+ audit_log_format(ab, "\"");
+ }
+ if (sa->aad->mnt.data) {
+ audit_log_format(ab, " options=");
+ audit_log_untrustedstring(ab, sa->aad->mnt.data);
+ }
+}
+
+/**
+ * audit_mount - handle the auditing of mount operations
+ * @profile: the profile being enforced (NOT NULL)
+ * @gfp: allocation flags
+ * @op: operation being mediated (NOT NULL)
+ * @name: name of object being mediated (MAYBE NULL)
+ * @src_name: src_name of object being mediated (MAYBE_NULL)
+ * @type: type of filesystem (MAYBE_NULL)
+ * @trans: name of trans (MAYBE NULL)
+ * @flags: filesystem idependent mount flags
+ * @data: filesystem mount flags
+ * @request: permissions requested
+ * @perms: the permissions computed for the request (NOT NULL)
+ * @info: extra information message (MAYBE NULL)
+ * @error: 0 if operation allowed else failure error code
+ *
+ * Returns: %0 or error on failure
+ */
+static int audit_mount(struct aa_profile *profile, gfp_t gfp, int op,
+ const char *name, const char *src_name,
+ const char *type, const char *trans,
+ unsigned long flags, const void *data, u32 request,
+ struct file_perms *perms, const char *info, int error)
+{
+ int audit_type = AUDIT_APPARMOR_AUTO;
+ struct common_audit_data sa;
+ struct apparmor_audit_data aad = { };
+
+ if (likely(!error)) {
+ u32 mask = perms->audit;
+
+ if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL))
+ mask = 0xffff;
+
+ /* mask off perms that are not being force audited */
+ request &= mask;
+
+ if (likely(!request))
+ return 0;
+ audit_type = AUDIT_APPARMOR_AUDIT;
+ } else {
+ /* only report permissions that were denied */
+ request = request & ~perms->allow;
+
+ if (request & perms->kill)
+ audit_type = AUDIT_APPARMOR_KILL;
+
+ /* quiet known rejects, assumes quiet and kill do not overlap */
+ if ((request & perms->quiet) &&
+ AUDIT_MODE(profile) != AUDIT_NOQUIET &&
+ AUDIT_MODE(profile) != AUDIT_ALL)
+ request &= ~perms->quiet;
+
+ if (!request)
+ return COMPLAIN_MODE(profile) ?
+ complain_error(error) : error;
+ }
+
+ COMMON_AUDIT_DATA_INIT(&sa, NONE);
+ sa.aad = &aad;
+ sa.aad->op = op;
+ sa.aad->name = name;
+ sa.aad->mnt.src_name = src_name;
+ sa.aad->mnt.type = type;
+ sa.aad->mnt.trans = trans;
+ sa.aad->mnt.flags = flags;
+ if (data && (perms->audit & AA_AUDIT_DATA))
+ sa.aad->mnt.data = data;
+ sa.aad->info = info;
+ sa.aad->error = error;
+
+ return aa_audit(audit_type, profile, gfp, &sa, audit_cb);
+}
+
+/**
+ * match_mnt_flags - Do an ordered match on mount flags
+ * @dfa: dfa to match against
+ * @state: state to start in
+ * @flags: mount flags to match against
+ *
+ * Mount flags are encoded as an ordered match. This is done instead of
+ * checking against a simple bitmask, to allow for logical operations
+ * on the flags.
+ *
+ * Returns: next state after flags match
+ */
+static unsigned int match_mnt_flags(struct aa_dfa *dfa, unsigned int state,
+ unsigned long flags)
+{
+ unsigned int i;
+
+ for (i = 0; i <= 31 ; ++i) {
+ if ((1 << i) & flags)
+ state = aa_dfa_next(dfa, state, i + 1);
+ }
+
+ return state;
+}
+
+/**
+ * compute_mnt_perms - compute mount permission associated with @state
+ * @dfa: dfa to match against (NOT NULL)
+ * @state: state match finished in
+ *
+ * Returns: mount permissions
+ */
+static struct file_perms compute_mnt_perms(struct aa_dfa *dfa,
+ unsigned int state)
+{
+ struct file_perms perms;
+
+ perms.kill = 0;
+ perms.allow = dfa_user_allow(dfa, state);
+ perms.audit = dfa_user_audit(dfa, state);
+ perms.quiet = dfa_user_quiet(dfa, state);
+ perms.xindex = dfa_user_xindex(dfa, state);
+
+ return perms;
+}
+
+static const char const *mnt_info_table[] = {
+ "match succeeded",
+ "failed mntpnt match",
+ "failed srcname match",
+ "failed type match",
+ "failed flags match",
+ "failed data match"
+};
+
+/*
+ * Returns 0 on success else element that match failed in, this is the
+ * index into the mnt_info_table above
+ */
+static int do_match_mnt(struct aa_dfa *dfa, unsigned int start,
+ const char *mntpnt, const char *devname,
+ const char *type, unsigned long flags,
+ void *data, bool binary, struct file_perms *perms)
+{
+ unsigned int state;
+
+ state = aa_dfa_match(dfa, start, mntpnt);
+ state = aa_dfa_null_transition(dfa, state);
+ if (!state)
+ return 1;
+
+ if (devname)
+ state = aa_dfa_match(dfa, state, devname);
+ state = aa_dfa_null_transition(dfa, state);
+ if (!state)
+ return 2;
+
+ if (type)
+ state = aa_dfa_match(dfa, state, type);
+ state = aa_dfa_null_transition(dfa, state);
+ if (!state)
+ return 3;
+
+ state = match_mnt_flags(dfa, state, flags);
+ if (!state)
+ return 4;
+ *perms = compute_mnt_perms(dfa, state);
+ if (perms->allow & AA_MAY_MOUNT)
+ return 0;
+
+ /* only match data if not binary and the DFA flags data is expected */
+ if (data && !binary && (perms->allow & AA_CONT_MATCH)) {
+ state = aa_dfa_null_transition(dfa, state);
+ if (!state)
+ return 4;
+
+ state = aa_dfa_match(dfa, state, data);
+ if (!state)
+ return 5;
+ *perms = compute_mnt_perms(dfa, state);
+ if (perms->allow & AA_MAY_MOUNT)
+ return 0;
+ }
+
+ /* failed at end of flags match */
+ return 4;
+}
+
+/**
+ * match_mnt - handle path matching for mount
+ * @profile: the confining profile
+ * @mntpnt: string for the mntpnt (NOT NULL)
+ * @devname: string for the devname/src_name (MAYBE NULL)
+ * @type: string for the dev type (MAYBE NULL)
+ * @flags: mount flags to match
+ * @data: fs mount data (MAYBE NULL)
+ * @binary: whether @data is binary
+ * @perms: Returns: permission found by the match
+ * @info: Returns: infomation string about the match for logging
+ *
+ * Returns: 0 on success else error
+ */
+static int match_mnt(struct aa_profile *profile, const char *mntpnt,
+ const char *devname, const char *type,
+ unsigned long flags, void *data, bool binary,
+ struct file_perms *perms, const char **info)
+{
+ int pos;
+
+ if (!profile->policy.dfa)
+ return -EACCES;
+
+ pos = do_match_mnt(profile->policy.dfa,
+ profile->policy.start[AA_CLASS_MOUNT],
+ mntpnt, devname, type, flags, data, binary, perms);
+ if (pos) {
+ *info = mnt_info_table[pos];
+ return -EACCES;
+ }
+
+ return 0;
+}
+
+static int path_flags(struct aa_profile *profile, struct path *path)
+{
+ return profile->path_flags |
+ S_ISDIR(path->dentry->d_inode->i_mode) ? PATH_IS_DIR : 0;
+}
+
+int aa_remount(struct aa_profile *profile, struct path *path,
+ unsigned long flags, void *data)
+{
+ struct file_perms perms = { };
+ const char *name, *info = NULL;
+ char *buffer = NULL;
+ int binary, error;
+
+ binary = path->dentry->d_sb->s_type->fs_flags & FS_BINARY_MOUNTDATA;
+
+ error = aa_path_name(path, path_flags(profile, path), &buffer, &name,
+ &info);
+ if (error)
+ goto audit;
+
+ error = match_mnt(profile, name, NULL, NULL, flags, data, binary,
+ &perms, &info);
+
+audit:
+ error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, NULL, NULL,
+ NULL, flags, data, AA_MAY_MOUNT, &perms, info,
+ error);
+ kfree(buffer);
+
+ return error;
+}
+
+int aa_bind_mount(struct aa_profile *profile, struct path *path,
+ const char *dev_name, unsigned long flags)
+{
+ struct file_perms perms = { };
+ char *buffer = NULL, *old_buffer = NULL;
+ const char *name, *old_name = NULL, *info = NULL;
+ struct path old_path;
+ int error;
+
+ if (!dev_name || !*dev_name)
+ return -EINVAL;
+
+ flags &= MS_REC | MS_BIND;
+
+ error = aa_path_name(path, path_flags(profile, path), &buffer, &name,
+ &info);
+ if (error)
+ goto audit;
+
+ error = kern_path(dev_name, LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT, &old_path);
+ if (error)
+ goto audit;
+
+ error = aa_path_name(&old_path, path_flags(profile, &old_path),
+ &old_buffer, &old_name, &info);
+ path_put(&old_path);
+ if (error)
+ goto audit;
+
+ error = match_mnt(profile, name, old_name, NULL, flags, NULL, 0,
+ &perms, &info);
+
+audit:
+ error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, old_name,
+ NULL, NULL, flags, NULL, AA_MAY_MOUNT, &perms,
+ info, error);
+ kfree(buffer);
+ kfree(old_buffer);
+
+ return error;
+}
+
+int aa_mount_change_type(struct aa_profile *profile, struct path *path,
+ unsigned long flags)
+{
+ struct file_perms perms = { };
+ char *buffer = NULL;
+ const char *name, *info = NULL;
+ int error;
+
+ /* These are the flags allowed by do_change_type() */
+ flags &= (MS_REC | MS_SILENT | MS_SHARED | MS_PRIVATE | MS_SLAVE |
+ MS_UNBINDABLE);
+
+ error = aa_path_name(path, path_flags(profile, path), &buffer, &name,
+ &info);
+ if (error)
+ goto audit;
+
+ error = match_mnt(profile, name, NULL, NULL, flags, NULL, 0, &perms,
+ &info);
+
+audit:
+ error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, NULL, NULL,
+ NULL, flags, NULL, AA_MAY_MOUNT, &perms, info,
+ error);
+ kfree(buffer);
+
+ return error;
+}
+
+int aa_move_mount(struct aa_profile *profile, struct path *path,
+ const char *orig_name)
+{
+ struct file_perms perms = { };
+ char *buffer = NULL, *old_buffer = NULL;
+ const char *name, *old_name = NULL, *info = NULL;
+ struct path old_path;
+ int error;
+
+ if (!orig_name || !*orig_name)
+ return -EINVAL;
+
+ error = aa_path_name(path, path_flags(profile, path), &buffer, &name,
+ &info);
+ if (error)
+ goto audit;
+
+ error = kern_path(orig_name, LOOKUP_FOLLOW, &old_path);
+ if (error)
+ goto audit;
+
+ error = aa_path_name(&old_path, path_flags(profile, &old_path),
+ &old_buffer, &old_name, &info);
+ path_put(&old_path);
+ if (error)
+ goto audit;
+
+ error = match_mnt(profile, name, old_name, NULL, MS_MOVE, NULL, 0,
+ &perms, &info);
+
+audit:
+ error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, old_name,
+ NULL, NULL, MS_MOVE, NULL, AA_MAY_MOUNT, &perms,
+ info, error);
+ kfree(buffer);
+ kfree(old_buffer);
+
+ return error;
+}
+
+int aa_new_mount(struct aa_profile *profile, const char *orig_dev_name,
+ struct path *path, const char *type, unsigned long flags,
+ void *data)
+{
+ struct file_perms perms = { };
+ char *buffer = NULL, *dev_buffer = NULL;
+ const char *name = NULL, *dev_name = NULL, *info = NULL;
+ int binary = 1;
+ int error;
+
+ dev_name = orig_dev_name;
+ if (type) {
+ int requires_dev;
+ struct file_system_type *fstype = get_fs_type(type);
+ if (!fstype)
+ return -ENODEV;
+
+ binary = fstype->fs_flags & FS_BINARY_MOUNTDATA;
+ requires_dev = fstype->fs_flags & FS_REQUIRES_DEV;
+ put_filesystem(fstype);
+
+ if (requires_dev) {
+ struct path dev_path;
+
+ if (!dev_name || !*dev_name) {
+ error = -ENOENT;
+ goto out;
+ }
+
+ error = kern_path(dev_name, LOOKUP_FOLLOW, &dev_path);
+ if (error)
+ goto audit;
+
+ error = aa_path_name(&dev_path,
+ path_flags(profile, &dev_path),
+ &dev_buffer, &dev_name, &info);
+ path_put(&dev_path);
+ if (error)
+ goto audit;
+ }
+ }
+
+ error = aa_path_name(path, path_flags(profile, path), &buffer, &name,
+ &info);
+ if (error)
+ goto audit;
+
+ error = match_mnt(profile, name, dev_name, type, flags, data, binary,
+ &perms, &info);
+
+audit:
+ error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, dev_name,
+ type, NULL, flags, data, AA_MAY_MOUNT, &perms, info,
+ error);
+ kfree(buffer);
+ kfree(dev_buffer);
+
+out:
+ return error;
+
+}
+
+int aa_umount(struct aa_profile *profile, struct vfsmount *mnt, int flags)
+{
+ struct file_perms perms = { };
+ char *buffer = NULL;
+ const char *name, *info = NULL;
+ int error;
+
+ struct path path = { mnt, mnt->mnt_root };
+ error = aa_path_name(&path, path_flags(profile, &path), &buffer, &name,
+ &info);
+ if (error)
+ goto audit;
+
+ if (!error && profile->policy.dfa) {
+ unsigned int state;
+ state = aa_dfa_match(profile->policy.dfa,
+ profile->policy.start[AA_CLASS_MOUNT],
+ name);
+ perms = compute_mnt_perms(profile->policy.dfa, state);
+ }
+
+ if (AA_MAY_UMOUNT & ~perms.allow)
+ error = -EACCES;
+
+audit:
+ error = audit_mount(profile, GFP_KERNEL, OP_UMOUNT, name, NULL, NULL,
+ NULL, 0, NULL, AA_MAY_UMOUNT, &perms, info, error);
+ kfree(buffer);
+
+ return error;
+}
+
+int aa_pivotroot(struct aa_profile *profile, struct path *old_path,
+ struct path *new_path)
+{
+ struct file_perms perms = { };
+ struct aa_profile *target = NULL;
+ char *old_buffer = NULL, *new_buffer = NULL;
+ const char *old_name, *new_name = NULL, *info = NULL;
+ int error;
+
+ error = aa_path_name(old_path, path_flags(profile, old_path),
+ &old_buffer, &old_name, &info);
+ if (error)
+ goto audit;
+
+ error = aa_path_name(new_path, path_flags(profile, new_path),
+ &new_buffer, &new_name, &info);
+ if (error)
+ goto audit;
+
+ if (profile->policy.dfa) {
+ unsigned int state;
+ state = aa_dfa_match(profile->policy.dfa,
+ profile->policy.start[AA_CLASS_MOUNT],
+ new_name);
+ state = aa_dfa_null_transition(profile->policy.dfa, state);
+ state = aa_dfa_match(profile->policy.dfa, state, old_name);
+ perms = compute_mnt_perms(profile->policy.dfa, state);
+ }
+
+ if (AA_MAY_PIVOTROOT & perms.allow) {
+ if ((perms.xindex & AA_X_TYPE_MASK) == AA_X_TABLE) {
+ target = x_table_lookup(profile, perms.xindex);
+ if (!target)
+ error = -ENOENT;
+ else
+ error = aa_replace_current_profile(target);
+ }
+ } else
+ error = -EACCES;
+
+audit:
+ error = audit_mount(profile, GFP_KERNEL, OP_PIVOTROOT, new_name,
+ old_name, NULL, target ? target->base.name : NULL,
+ 0, NULL, AA_MAY_PIVOTROOT, &perms, info, error);
+ aa_put_profile(target);
+ kfree(old_buffer);
+ kfree(new_buffer);
+
+ return error;
+}
--
1.7.9.5

View File

@@ -69,7 +69,8 @@ does not handle buffer allocation.
=head1 RETURN VALUE
On success zero is returned. On error, -1 is returned, and
On success size of data placed in the buffer is returned, this includes the
mode if present and any terminating characters. On error, -1 is returned, and
errno(3) is set appropriately.
=head1 ERRORS

View File

@@ -158,6 +158,8 @@ $ac_distutils_result])
AC_MSG_CHECKING([consistency of all components of python development environment])
AC_LANG_PUSH([C])
# save current global flags
ac_save_LIBS="$LIBS"
ac_save_CPPFLAGS="$CPPFLAGS"
LIBS="$ac_save_LIBS $PYTHON_LDFLAGS"
CPPFLAGS="$ac_save_CPPFLAGS $PYTHON_CPPFLAGS"
AC_TRY_LINK([

View File

@@ -3,7 +3,8 @@ INCLUDES = $(all_includes)
BUILT_SOURCES = grammar.h scanner.h af_protos.h
AM_LFLAGS = -v
AM_YFLAGS = -d -p aalogparse_
AM_CFLAGS = @CFLAGS@ -D_GNU_SOURCE -Wall
AM_CFLAGS = -Wall
AM_CPPFLAGS = -D_GNU_SOURCE
scanner.h: scanner.l
$(LEX) -v $<

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,7 +10,7 @@ WriteMakefile(
'FIRST_MAKEFILE' => 'Makefile.perl',
'ABSTRACT' => q[Perl interface to AppArmor] ,
'VERSION' => q[@VERSION@],
'INC' => q[-I@top_srcdir@/src @CFLAGS@],
'INC' => q[@CPPFLAGS@ -I@top_srcdir@/src @CFLAGS@],
'LIBS' => q[-L@top_builddir@/src/.libs/ -lapparmor @LIBS@],
'OBJECT' => 'libapparmor_wrap.o', # $(OBJ_EXT)
) ;

View File

@@ -10,8 +10,7 @@ AM_CFLAGS = -Wall
noinst_PROGRAMS = test_multi.multi
test_multi_multi_SOURCES = test_multi.c
test_multi_multi_CFLAGS = $(CFLAGS) -Wall
test_multi_multi_LDFLAGS = $(LDFLAGS)
test_multi_multi_CFLAGS = -Wall
test_multi_multi_LDADD = -L../src/.libs -lapparmor
clean-local:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -118,7 +118,8 @@ po/${NAME}.pot: ${SRCS} ${HDRS}
$(MAKE) -C po ${NAME}.pot NAME=${NAME} SOURCES="${SRCS} ${HDRS}"
techdoc.pdf: techdoc.tex
while pdflatex $< ${BUILD_OUTPUT} || exit 1 ; \
timestamp=$(shell date "+%Y%m%d%H%M%S+02'00'" -r $< );\
while pdflatex "\def\fixedpdfdate{$$timestamp}\input $<" ${BUILD_OUTPUT} || exit 1 ; \
grep -q "Label(s) may have changed" techdoc.log; \
do :; done
@@ -207,22 +208,18 @@ parser_version.h: Makefile
@echo \#define PARSER_VERSION \"$(VERSION)\" > .ver
@mv -f .ver $@
# These are the families that it doesn't make sense for apparmor to mediate.
# We use PF_ here since that is what is required in bits/socket.h, but we will
# rewrite these as AF_.
FILTER_FAMILIES=PF_MAX PF_UNSPEC PF_UNIX PF_LOCAL PF_NETLINK
__FILTER=$(shell echo $(strip $(FILTER_FAMILIES)) | sed -e 's/ /\\\|/g')
# af_names and capabilities generation has moved to common/Make.rules,
# as well as the filtering that occurs for network protocols that
# apparmor should not mediate.
.PHONY: af_names.h
af_names.h:
echo "#include <sys/socket.h>" | cpp -dM | LC_ALL=C sed -n -e '/$(__FILTER)/d' -e "s/^\#define[ \\t]\\+PF_\\([A-Z0-9_]\\+\\)[ \\t]\\+\\([0-9]\\+\\)\\(.*\\)\$$/#ifndef AF_\\1\\n# define AF_\\1 \\2\\n#endif\\nAA_GEN_NET_ENT(\"\\L\\1\", \\UAF_\\1)\\n/p" > $@
echo "#include <sys/socket.h>" | cpp -dM | LC_ALL=C sed -n -e "s/^\#define[ \\t]\\+PF_MAX[ \\t]\\+\\([0-9]\\+\\)\\+.*/#define AA_AF_MAX \\1\n/p" >> $@
echo "$(AF_NAMES)" | LC_ALL=C sed -n -e 's/[ \t]\?AF_MAX[ \t]\+[0-9]\+,//g' -e 's/[ \t]\+\?AF_\([A-Z0-9_]\+\)[ \t]\+\([0-9]\+\),/#ifndef AF_\1\n# define AF_\1 \2\n#endif\nAA_GEN_NET_ENT("\L\1", \UAF_\1)\n\n/pg' > $@
echo "$(AF_NAMES)" | LC_ALL=C sed -n -e 's/.*,[ \t]\+AF_MAX[ \t]\+\([0-9]\+\),\?.*/#define AA_AF_MAX \1\n/p' >> $@
# cat $@
cap_names.h: /usr/include/linux/capability.h
LC_ALL=C sed -n -e "/CAP_EMPTY_SET/d" -e "s/^\#define[ \\t]\\+CAP_\\([A-Z0-9_]\\+\\)[ \\t]\\+\\([0-9xa-f]\\+\\)\\(.*\\)\$$/\{\"\\L\\1\", \\UCAP_\\1\},/p" $< > $@
echo "$(CAPABILITIES)" | LC_ALL=C sed -n -e "s/[ \\t]\\?CAP_\\([A-Z0-9_]\\+\\)/\{\"\\L\\1\", \\UCAP_\\1\},\\n/pg" > $@
tst_%: parser_%.c parser.h $(filter-out parser_%.o, ${TEST_OBJECTS})
$(CC) $(TEST_CFLAGS) -o $@ $< $(filter-out $(<:.c=.o), ${TEST_OBJECTS}) $(TEST_LDFLAGS)
@@ -306,7 +303,7 @@ clean: _clean
rm -f $(NAME)*.tar.gz $(NAME)*.tgz
rm -f af_names.h
rm -f cap_names.h
rm -rf techdoc.aux techdoc.log techdoc.pdf techdoc.toc techdor.txt techdoc/
rm -rf techdoc.aux techdoc.out techdoc.log techdoc.pdf techdoc.toc techdoc.txt techdoc/
$(MAKE) -s -C $(AAREDIR) clean
$(MAKE) -s -C po clean
$(MAKE) -s -C tst clean

View File

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

View File

@@ -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

@@ -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)

View File

@@ -101,7 +101,8 @@ State *DFA::add_new_state(NodeSet *nodes, State *other)
nnodev = nnodes_cache.insert(nnodes);
anodes = anodes_cache.insert(anodes);
ProtoState proto(nnodev, anodes);
ProtoState proto;
proto.init(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) {
@@ -340,14 +341,8 @@ void DFA::remove_unreachable(dfaflags_t flags)
cerr << "unreachable: " << **i;
if (*i == start)
cerr << " <==";
if (!(*i)->perms.is_null()) {
cerr << " (0x" << hex
<< (*i)->perms.allow << " "
<< (*i)->perms.deny << " "
<< (*i)->perms.audit << " "
<< (*i)->perms.quiet << dec
<< ')';
}
if ((*i)->perms.is_accept())
(*i)->perms.dump(cerr);
cerr << "\n";
}
State *current = *i;
@@ -366,26 +361,17 @@ void DFA::remove_unreachable(dfaflags_t flags)
/* test if two states have the same transitions under partition_map */
bool DFA::same_mappings(State *s1, State *s2)
{
if (s1->otherwise != nonmatching) {
if (s2->otherwise == nonmatching)
return false;
Partition *p1 = s1->otherwise->partition;
Partition *p2 = s2->otherwise->partition;
if (p1 != p2)
return false;
} else if (s2->otherwise != nonmatching) {
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;
Partition *p1 = j1->second->partition;
Partition *p2 = j2->second->partition;
if (p1 != p2)
if (j1->second->partition != j2->second->partition)
return false;
}
@@ -553,10 +539,8 @@ void DFA::minimize(dfaflags_t flags)
cerr << *rep << " : ";
/* update representative state's transitions */
if (rep->otherwise != nonmatching) {
Partition *partition = rep->otherwise->partition;
rep->otherwise = *partition->begin();
}
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();
@@ -573,7 +557,7 @@ void DFA::minimize(dfaflags_t flags)
(*i)->label = -1;
rep->perms.add((*i)->perms);
}
if (!rep->perms.is_null())
if (rep->perms.is_accept())
final_accept++;
//if ((*p)->size() > 1)
//cerr << "\n";
@@ -628,16 +612,12 @@ out:
void DFA::dump(ostream & os)
{
for (Partition::iterator i = states.begin(); i != states.end(); i++) {
if (*i == start || !(*i)->perms.is_null()) {
if (*i == start || (*i)->perms.is_accept()) {
os << **i;
if (*i == start)
os << " <== (allow/deny/audit/quiet)";
if (!(*i)->perms.is_null()) {
os << " (0x " << hex << (*i)->perms.allow << "/"
<< (*i)->perms.deny << "/"
<< (*i)->perms.audit << "/"
<< (*i)->perms.quiet << ')';
}
if ((*i)->perms.is_accept())
(*i)->perms.dump(os);
os << "\n";
}
}
@@ -651,16 +631,22 @@ void DFA::dump(ostream & os)
if (j->second == nonmatching) {
excluded.insert(j->first);
} else {
os << **i << " -> " << *(j)->second << ": 0x"
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 << "\n";
os << dec << "\n";
}
}
if ((*i)->otherwise != nonmatching) {
os << **i << " -> " << *(*i)->otherwise << ": [";
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();
@@ -668,7 +654,7 @@ void DFA::dump(ostream & os)
if (isprint(*k))
os << *k;
else
os << "\\0x" << hex << (int) *k;
os << "\\0x" << hex << (int) *k << dec;
}
}
os << "]\n";
@@ -692,12 +678,10 @@ void DFA::dump_dot_graph(ostream & os)
if (*i == start) {
os << "\t\tstyle=bold" << "\n";
}
if (!(*i)->perms.is_null()) {
os << "\t\tlabel=\"" << **i << "\\n(0x " << hex
<< (*i)->perms.allow << "/"
<< (*i)->perms.deny << "/"
<< (*i)->perms.audit << "/"
<< (*i)->perms.quiet << ")\"\n";
if ((*i)->perms.is_accept()) {
os << "\t\tlabel=\"" << **i << "\\n";
(*i)->perms.dump(os);
os << "\"\n";
}
os << "\t]" << "\n";
}
@@ -714,7 +698,7 @@ void DFA::dump_dot_graph(ostream & os)
if (isprint(j->first))
os << j->first;
else
os << "\\0xhex" << (int) j->first;
os << "\\0x" << hex << (int) j->first << dec;
os << "\"\n\t]" << "\n";
}
@@ -729,7 +713,7 @@ void DFA::dump_dot_graph(ostream & os)
if (isprint(*i))
os << *i;
else
os << "\\0x" << hex << (int) *i;
os << "\\0x" << hex << (int) *i << dec;
}
os << "]\"" << "\n";
}

View File

@@ -43,7 +43,14 @@ class perms_t {
public:
perms_t(void) throw(int): allow(0), deny(0), audit(0), quiet(0), exact(0) { };
bool is_null(void) { return !(allow | deny | audit | quiet); }
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)
@@ -99,7 +106,7 @@ public:
allow &= ~deny;
quiet &= deny;
deny = 0;
return is_null();
return !is_accept();
}
return 0;
}
@@ -303,7 +310,15 @@ public:
hashedNodeVec *nnodes;
NodeSet *anodes;
ProtoState(hashedNodeVec *n, NodeSet *a = NULL): nnodes(n), anodes(a) { };
/* init is used instead of a constructor because ProtoState is used
* in a union
*/
void init(hashedNodeVec *n, NodeSet *a = NULL)
{
nnodes = n;
anodes = a;
}
bool operator<(ProtoState const &rhs)const
{
if (nnodes == rhs.nnodes)
@@ -331,7 +346,7 @@ public:
* 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
* proto: Is a temporary work variable used during dfa creation. It can
* be replaced by using the nodemap, but that is slower
*/
class State {
@@ -372,8 +387,8 @@ public:
/* temp storage for State construction */
union {
Partition *partition;
ProtoState proto;
Partition *partition; /* used during minimization */
ProtoState proto; /* used during creation */
};
};

View File

@@ -362,15 +362,16 @@ static struct value_list *extract_fstype(struct cond_entry **conds)
return list;
}
static struct value_list *extract_options(struct cond_entry **conds)
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) {
if ((strcmp(entry->name, "options") == 0 ||
strcmp(entry->name, "option") == 0) &&
entry->eq == eq) {
if (prev)
prev->next = tmp;
if (entry == *conds)
@@ -402,12 +403,31 @@ struct mnt_entry *new_mnt_entry(struct cond_entry *src_conds, char *device,
ent->dev_type = extract_fstype(&src_conds);
ent->flags = 0;
ent->inv_flags = 0;
if (src_conds) {
ent->opts = extract_options(&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) {
@@ -416,8 +436,8 @@ struct mnt_entry *new_mnt_entry(struct cond_entry *src_conds, char *device,
ent->inv_flags = 0;
} else if (!(ent->flags | ent->inv_flags)) {
/* no flag options, and not remount, allow everything */
ent->flags = 0xffffffff;
ent->inv_flags = 0xffffffff;
ent->flags = MS_ALL_FLAGS;
ent->inv_flags = MS_ALL_FLAGS;
}
ent->allow = allow;

View File

@@ -85,7 +85,6 @@
MS_BORN | MS_NOATIME | MS_NODIRATIME | MS_RELATIME| \
MS_KERNMOUNT | MS_STRICTATIME)
#define MS_REMOUNT_FLAGS (MS_REMOUNT | MNT_FLAGS)
#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)))
@@ -93,6 +92,7 @@
#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
@@ -103,8 +103,10 @@
#define AA_MAY_PIVOTROOT 1
#define AA_MAY_MOUNT 2
#define AA_MAY_UMOUNT 4
#define AA_DUMMY_REMOUNT 32 /* dummy perm for remount rule - is remapped
* to a mount option*/
#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 {

View File

@@ -62,6 +62,7 @@ struct value_list {
struct cond_entry {
char *name;
int eq; /* where equals was used in specifying list */
struct value_list *vals;
struct cond_entry *next;
@@ -316,7 +317,7 @@ 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, 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);
@@ -380,7 +381,7 @@ 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);

View File

@@ -280,6 +280,18 @@ LT_EQUAL <=
yy_push_state(EXTCOND_MODE);
return TOK_CONDID;
}
{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>{
@@ -384,6 +396,11 @@ LT_EQUAL <=
return TOK_OPENPAREN;
}
in {
DUMP_PREPROCESS;
return TOK_IN;
}
[^\n] {
DUMP_PREPROCESS;
/* Something we didn't expect */

View File

@@ -801,6 +801,8 @@ static void get_match_string(void) {
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;

View File

@@ -83,7 +83,8 @@ static struct keyword_table keyword_table[] = {
{"remount", TOK_REMOUNT},
{"umount", TOK_UMOUNT},
{"unmount", TOK_UMOUNT},
{"pivotroot", TOK_PIVOTROOT},
{"pivot_root", TOK_PIVOTROOT},
{"in", TOK_IN},
/* terminate */
{NULL, 0}
};
@@ -1025,12 +1026,13 @@ void print_value_list(struct value_list *list)
}
}
struct cond_entry *new_cond_entry(char *name, struct value_list *list)
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;

View File

@@ -172,9 +172,10 @@ 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) {
@@ -193,6 +194,27 @@ 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);
}
}

View File

@@ -510,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);
@@ -692,7 +701,7 @@ static int build_mnt_flags(char *buffer, int size, unsigned int flags,
char *p = buffer;
int i, len = 0;
if (flags == 0xffffffff) {
if (flags == MS_ALL_FLAGS) {
/* all flags are optional */
len = snprintf(p, size, "[^\\000]*");
if (len < 0 || len >= size)
@@ -704,7 +713,8 @@ static int build_mnt_flags(char *buffer, int size, unsigned int flags,
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 */
else /* no entry = not set */
continue;
if (len < 0 || len >= size)
return FALSE;
@@ -712,6 +722,7 @@ static int build_mnt_flags(char *buffer, int size, unsigned int flags,
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
@@ -719,7 +730,7 @@ static int build_mnt_flags(char *buffer, int size, unsigned int flags,
if (size < 9)
return FALSE;
strcpy(p, "(\\0xfe|)");
strcpy(p, "(\\xfe|)");
}
return TRUE;
@@ -774,6 +785,7 @@ static int process_mnt_entry(aare_ruleset_t *dfarules, struct mnt_entry *entry)
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
@@ -781,6 +793,7 @@ static int process_mnt_entry(aare_ruleset_t *dfarules, struct mnt_entry *entry)
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 */
@@ -801,18 +814,43 @@ static int process_mnt_entry(aare_ruleset_t *dfarules, struct mnt_entry *entry)
vec[1] = devbuf;
/* skip type */
vec[2] = devbuf;
if (!build_mnt_flags(flagsbuf, PATH_MAX,
entry->flags & MS_REMOUNT_FLAGS,
entry->inv_flags & MS_REMOUNT_FLAGS))
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;
vec[4] = optsbuf;
if (!aare_add_rule_vec(dfarules, entry->deny, entry->allow,
entry->audit, 5, vec, dfaflags))
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) {
@@ -829,9 +867,14 @@ static int process_mnt_entry(aare_ruleset_t *dfarules, struct mnt_entry *entry)
if (!convert_entry(typebuf, PATH_MAX +3, NULL))
goto fail;
vec[2] = typebuf;
if (!build_mnt_flags(flagsbuf, PATH_MAX,
entry->flags & MS_BIND_FLAGS,
entry->inv_flags & MS_BIND_FLAGS))
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,
@@ -856,9 +899,14 @@ static int process_mnt_entry(aare_ruleset_t *dfarules, struct mnt_entry *entry)
goto fail;
vec[1] = devbuf;
vec[2] = devbuf;
if (!build_mnt_flags(flagsbuf, PATH_MAX,
entry->flags & MS_MAKE_FLAGS,
entry->inv_flags & MS_MAKE_FLAGS))
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,
@@ -884,9 +932,14 @@ static int process_mnt_entry(aare_ruleset_t *dfarules, struct mnt_entry *entry)
if (!convert_entry(typebuf, PATH_MAX +3, NULL))
goto fail;
vec[2] = typebuf;
if (!build_mnt_flags(flagsbuf, PATH_MAX,
entry->flags & MS_MOVE_FLAGS,
entry->inv_flags & MS_MOVE_FLAGS))
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,
@@ -896,6 +949,7 @@ static int process_mnt_entry(aare_ruleset_t *dfarules, struct mnt_entry *entry)
}
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
*/
@@ -911,18 +965,41 @@ static int process_mnt_entry(aare_ruleset_t *dfarules, struct mnt_entry *entry)
if (!build_list_val_expr(typebuf, PATH_MAX+2, entry->dev_type))
goto fail;
vec[2] = typebuf;
if (!build_mnt_flags(flagsbuf, PATH_MAX,
entry->flags & ~MS_CMDS,
entry->inv_flags & ~MS_CMDS))
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 (!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, 5, vec, dfaflags))
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;

View File

@@ -121,6 +121,7 @@ void add_local_entry(struct codomain *cod);
%token TOK_REMOUNT
%token TOK_UMOUNT
%token TOK_PIVOTROOT
%token TOK_IN
/* rlimits */
%token TOK_RLIMIT
@@ -256,7 +257,7 @@ profile_base: TOK_ID opt_id flags TOK_OPEN rules TOK_CLOSE
if (force_complain)
cod->flags.complain = 1;
post_process_nt_entries(cod);
post_process_file_entries(cod);
post_process_mnt_entries(cod);
PDEBUG("%s: flags='%s%s'\n",
$2,
@@ -435,10 +436,6 @@ flagvals: flagvals flagval
(PATH_CHROOT_REL | PATH_NS_REL))
yyerror(_("Profile flag chroot_relative conflicts with namespace_relative"));
if (!($1.path & PATH_NS_REL))
/* default to chroot relative profiles */
$1.path |= PATH_CHROOT_REL;
if (($1.path & (PATH_MEDIATE_DELETED | PATH_DELEGATE_DELETED)) ==
(PATH_MEDIATE_DELETED | PATH_DELEGATE_DELETED))
yyerror(_("Profile flag mediate_deleted conflicts with delegate_deleted"));
@@ -963,7 +960,7 @@ frule: file_mode opt_subset_flag id_or_var opt_named_transition TOK_END_OF_RULE
file_rule: TOK_FILE TOK_END_OF_RULE
{
char *path = strdup("/**");
char *path = strdup("/{**,}");
int perms = ((AA_BASE_PERMS & ~AA_EXEC_TYPE) |
(AA_EXEC_INHERIT | AA_MAY_EXEC));
/* duplicate to other permission set */
@@ -1072,7 +1069,7 @@ cond: TOK_CONDID TOK_EQUALS TOK_VALUE
struct value_list *value = new_value_list($3);
if (!value)
yyerror(_("Memory allocation error."));
ent = new_cond_entry($1, value);
ent = new_cond_entry($1, 1, value);
if (!ent) {
free_value_list(value);
yyerror(_("Memory allocation error."));
@@ -1082,7 +1079,17 @@ cond: TOK_CONDID TOK_EQUALS TOK_VALUE
cond: TOK_CONDID TOK_EQUALS TOK_OPENPAREN valuelist TOK_CLOSEPAREN
{
struct cond_entry *ent = new_cond_entry($1, $4);
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."));
@@ -1116,14 +1123,23 @@ 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 TOK_END_OF_RULE
mnt_rule: TOK_PIVOTROOT opt_conds opt_id opt_named_transition TOK_END_OF_RULE
{
$$ = do_pivot_rule($2, $3, NULL);
}
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;
mnt_rule: TOK_PIVOTROOT opt_conds opt_id TOK_ARROW TOK_ID TOK_END_OF_RULE
{
$$ = do_pivot_rule($2, $3, $5);
$$ = do_pivot_rule($2, $3, name);
}
hat_start: TOK_CARET {}
@@ -1315,18 +1331,20 @@ 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, old->vals->value, NULL, root,
ent = new_mnt_entry(NULL, device, NULL, root,
AA_MAY_PIVOTROOT);
ent->trans = transition;
old->vals->value = NULL;
free_cond_entry(old);
return ent;
}

View File

@@ -5,6 +5,17 @@
\usepackage{url}
%\usepackage{times}
\usepackage[pdftex,
pdfauthor={Andreas Gruenbacher and Seth Arnold},
pdftitle={AppArmor Technical Documentation},%
\ifx\fixedpdfdate\@empty\else
pdfcreationdate={\fixedpdfdate},
pdfmoddate={\fixedpdfdate},
\fi
pdfsubject={AppArmor},
pdfkeywords={AppArmor}
]{hyperref}
\hyphenation{App-Armor}
\hyphenation{name-space}
@@ -14,7 +25,8 @@
\author{Andreas Gruenbacher and Seth Arnold \\
\url{{agruen,seth.arnold}@suse.de} \\
SUSE Labs / Novell}
%\date{}
% don't include the (build!) date
\date{}
\begin{document}

16
parser/tst/minimize.sh Normal file → Executable file
View File

@@ -75,7 +75,7 @@
# {a} (0x 40030/0/0/0)
echo -n "Minimize profiles basic perms "
if [ `echo "/t { /a r, /b w, /c a, /d l, /e k, /f m, /** w, }" | ../apparmor_parser -QT -O minimize -D dfa-states 2>&1 | grep -v '<==' | grep '(.*)$' | wc -l` -ne 6 ] ; then
if [ `echo "/t { /a r, /b w, /c a, /d l, /e k, /f m, /** w, }" | ../apparmor_parser -QT -O minimize -D dfa-states 2>&1 | grep -v '<==' | grep '^{.*} (.*)$' | wc -l` -ne 6 ] ; then
echo "failed"
exit 1;
fi
@@ -90,7 +90,7 @@ echo "ok"
# {9} (0x 12804a/0/2800a/0)
# {c} (0x 40030/0/0/0)
echo -n "Minimize profiles audit perms "
if [ `echo "/t { /a r, /b w, /c a, /d l, /e k, /f m, audit /** w, }" | ../apparmor_parser -QT -O minimize -D dfa-states 2>&1 | grep -v '<==' | grep '(.*)$' | wc -l` -ne 6 ] ; then
if [ `echo "/t { /a r, /b w, /c a, /d l, /e k, /f m, audit /** w, }" | ../apparmor_parser -QT -O minimize -D dfa-states 2>&1 | grep -v '<==' | grep '^{.*} (.*)$' | wc -l` -ne 6 ] ; then
echo "failed"
exit 1;
fi
@@ -109,7 +109,7 @@ echo "ok"
# {c} (0x 40030/0/0/0)
echo -n "Minimize profiles deny perms "
if [ `echo "/t { /a r, /b w, /c a, /d l, /e k, /f m, deny /** w, }" | ../apparmor_parser -QT -O minimize -D dfa-states 2>&1 | grep -v '<==' | grep '(.*)$' | wc -l` -ne 6 ] ; then
if [ `echo "/t { /a r, /b w, /c a, /d l, /e k, /f m, deny /** w, }" | ../apparmor_parser -QT -O minimize -D dfa-states 2>&1 | grep -v '<==' | grep '^{.*} (.*)$' | wc -l` -ne 6 ] ; then
echo "failed"
exit 1;
fi
@@ -127,7 +127,7 @@ echo "ok"
# {c} (0x 40030/0/0/0)
echo -n "Minimize profiles audit deny perms "
if [ `echo "/t { /a r, /b w, /c a, /d l, /e k, /f m, audit deny /** w, }" | ../apparmor_parser -QT -O minimize -D dfa-states 2>&1 | grep -v '<==' | grep '(.*)$' | wc -l` -ne 6 ] ; then
if [ `echo "/t { /a r, /b w, /c a, /d l, /e k, /f m, audit deny /** w, }" | ../apparmor_parser -QT -O minimize -D dfa-states 2>&1 | grep -v '<==' | grep '^{.*} (.*)$' | wc -l` -ne 5 ] ; then
echo "failed"
exit 1;
fi
@@ -159,7 +159,7 @@ echo "ok"
#
echo -n "Minimize profiles xtrans "
if [ `echo "/t { /b px, /* Pixr, /a Cx -> foo, }" | ../apparmor_parser -QT -O minimize -D dfa-states 2>&1 | grep -v '<==' | grep '(.*)$' | wc -l` -ne 3 ] ; then
if [ `echo "/t { /b px, /* Pixr, /a Cx -> foo, }" | ../apparmor_parser -QT -O minimize -D dfa-states 2>&1 | grep -v '<==' | grep '^{.*} (.*)$' | wc -l` -ne 3 ] ; then
echo "failed"
exit 1;
fi
@@ -167,7 +167,7 @@ echo "ok"
# same test as above + audit
echo -n "Minimize profiles audit xtrans "
if [ `echo "/t { /b px, audit /* Pixr, /a Cx -> foo, }" | ../apparmor_parser -QT -O minimize -D dfa-states 2>&1 | grep -v '<==' | grep '(.*)$' | wc -l` -ne 3 ] ; then
if [ `echo "/t { /b px, audit /* Pixr, /a Cx -> foo, }" | ../apparmor_parser -QT -O minimize -D dfa-states 2>&1 | grep -v '<==' | grep '^{.*} (.*)$' | wc -l` -ne 3 ] ; then
echo "failed"
exit 1;
fi
@@ -180,7 +180,7 @@ echo "ok"
# {3} (0x 0/fe17f85/0/14005)
echo -n "Minimize profiles deny xtrans "
if [ `echo "/t { /b px, deny /* xr, /a Cx -> foo, }" | ../apparmor_parser -QT -O minimize -D dfa-states 2>&1 | grep -v '<==' | grep '(.*)$' | wc -l` -ne 1 ] ; then
if [ `echo "/t { /b px, deny /* xr, /a Cx -> foo, }" | ../apparmor_parser -QT -O minimize -D dfa-states 2>&1 | grep -v '<==' | grep '^{.*} (.*)$' | wc -l` -ne 1 ] ; then
echo "failed"
exit 1;
fi
@@ -192,7 +192,7 @@ echo "ok"
# {3} (0x 0/fe17f85/0/0)
echo -n "Minimize profiles audit deny xtrans "
if [ `echo "/t { /b px, audit deny /* xr, /a Cx -> foo, }" | ../apparmor_parser -QT -O minimize -D dfa-states 2>&1 | grep -v '<==' | grep '(.*)$' | wc -l` -ne 1 ] ; then
if [ `echo "/t { /b px, audit deny /* xr, /a Cx -> foo, }" | ../apparmor_parser -QT -O minimize -D dfa-states 2>&1 | grep -v '<==' | grep '^{.*} (.*)$' | wc -l` -ne 0 ] ; then
echo "failed"
exit 1;
fi

View File

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

View File

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

View File

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

View File

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

View File

@@ -17,7 +17,7 @@
# .Xauthority files required for X connections, per user
@{HOME}/.Xauthority r,
owner /{,var/}run/gdm/*/database r,
owner /{,var/}run/gdm{,3}/*/database r,
owner /{,var/}run/lightdm/authority/[0-9]* r,
# the unix socket to use to connect to the display

View File

@@ -32,6 +32,10 @@
/usr/bin/firefox Cxr -> sanitized_helper,
/usr/lib/firefox*/firefox*.sh Cx -> sanitized_helper,
# Iceweasel
/usr/bin/iceweasel Cxr -> sanitized_helper,
/usr/lib/iceweasel/iceweasel Cx -> sanitized_helper,
# some unpackaged, but popular browsers
/usr/lib/icecat-*/icecat Cx -> sanitized_helper,
/usr/bin/opera Cx -> sanitized_helper,

View File

@@ -18,6 +18,5 @@
/usr/bin/sylpheed Cx -> sanitized_helper,
/usr/bin/tkrat Cx -> sanitized_helper,
/usr/lib/thunderbird/thunderbird Cx -> sanitized_helper,
/usr/lib/thunderbird-[1-9]*/thunderbird{,.sh} Cx -> sanitized_helper,
/usr/lib/thunderbird*/thunderbird{,.sh} Cx -> sanitized_helper,

View File

@@ -48,6 +48,20 @@ profile sanitized_helper {
# Allow exec of libexec applications in /usr/lib*
/usr/lib*/{,**/}* Pixr,
# Allow exec of software-center scripts. We may need to allow wider
# permissions for /usr/share, but for now just do this. (LP: #972367)
/usr/share/software-center/* Pixr,
# While the chromium and chrome sandboxes are setuid root, they only link
# in limited libraries so glibc's secure execution should be enough to not
# require the santized_helper (ie, LD_PRELOAD will only use standard system
# paths (man ld.so)).
/usr/lib/chromium-browser/chromium-browser-sandbox PUxr,
/opt/google/chrome/chrome-sandbox PUxr,
/opt/google/chrome/google-chrome Pixr,
/opt/google/chrome/chrome Pixr,
/opt/google/chrome/lib*.so{,.*} m,
# Full access
/ r,
/** rwkl,

View File

@@ -11,6 +11,7 @@
capability sys_chroot,
network inet stream,
network inet6 stream,
/usr/lib/dovecot/imap-login mr,
/{,var/}run/dovecot/login/ r,

View File

@@ -9,7 +9,7 @@
#
# ------------------------------------------------------------------
@{TFTP_DIR}=/var/tftp
@{TFTP_DIR}=/var/tftp /srv/tftpboot
#include <tunables/global>
/usr/sbin/dnsmasq {

View File

@@ -8,6 +8,7 @@
SRC=access.c \
introspect.c \
changeprofile.c \
onexec.c \
changehat.c \
changehat_fork.c \
changehat_misc.c \
@@ -110,6 +111,7 @@ TESTS=access \
introspect \
capabilities \
changeprofile \
onexec \
changehat \
changehat_fork \
changehat_misc \

View File

@@ -109,16 +109,16 @@ for TEST in ${TESTS} ; do
# okay, now check to see if the capability functions from within
# a subprofile.
settest ${testwrapper}
genprofile hat:${TEST} addimage:${bin}/${TEST} ${my_entries}
runchecktest "${TEST} changehat -- no caps" fail ${TEST} ${my_arg}
genprofile hat:$bin/${TEST} addimage:${bin}/${TEST} ${my_entries}
runchecktest "${TEST} changehat -- no caps" fail $bin/${TEST} ${my_arg}
for cap in ${CAPABILITIES} ; do
if [ "X$(eval echo \${${TEST}_${cap}})" == "XTRUE" ] ; then
expected_result=pass
else
expected_result=fail
fi
genprofile hat:${TEST} addimage:${bin}/${TEST} cap:${cap} ${my_entries}
runchecktest "${TEST} changehat -- capability ${cap}" ${expected_result} ${TEST} ${my_arg}
genprofile hat:$bin/${TEST} addimage:${bin}/${TEST} cap:${cap} ${my_entries}
runchecktest "${TEST} changehat -- capability ${cap}" ${expected_result} $bin/${TEST} ${my_arg}
done
done

View File

@@ -154,7 +154,7 @@ int main(int argc, char *argv[]) {
perror ("FAIL: child malloc");
return -1;
}
sprintf (pname, "./%s", argv[optind]);
sprintf (pname, "%s", argv[optind]);
rc = !manual ? change_hat(argv[optind], magic_token)
: manual_change_hat(argv[optind], manual_string);
@@ -173,7 +173,7 @@ int main(int argc, char *argv[]) {
perror("FAIL: pipe failed");
exit(1);
}
exit(execv(pname, &argv[optind]));
}
@@ -190,7 +190,7 @@ int main(int argc, char *argv[]) {
if ((WEXITSTATUS(waitstatus) == 0) && strcmp("PASS\n", buf) == 0) {
printf("PASS\n");
}
}
return WEXITSTATUS(waitstatus);
}

View File

@@ -31,7 +31,8 @@ int main(int argc, char *argv[])
}
if (strcmp(argv[1], "self") == 0){
if (aa_getcon(&profile, &mode) == -1) {
rc = aa_getcon(&profile, &mode);
if (rc == -1) {
int serrno = errno;
fprintf(stderr,
"FAIL: introspect_confinement %s failed - %s\n",
@@ -47,12 +48,15 @@ int main(int argc, char *argv[])
"FAIL: query_confinement - invalid pid: %s\n",
argv[1]);
exit(serrno);
} else if (aa_gettaskcon(pid, &profile, &mode) == -1) {
int serrno = errno;
fprintf(stderr,
"FAIL: query_confinement %s failed - %s\n",
argv[1], strerror(errno));
exit(serrno);
} else {
rc = aa_gettaskcon(pid, &profile, &mode);
if (rc == -1) {
int serrno = errno;
fprintf(stderr,
"FAIL: query_confinement %s failed - %s\n",
argv[1], strerror(errno));
exit(serrno);
}
}
}
if (strcmp(profile, argv[2]) != 0) {
@@ -61,6 +65,21 @@ int main(int argc, char *argv[])
profile);
exit(1);
}
if (mode) {
if (rc != strlen(profile) + strlen(mode) + 4) {
/* rc includes mode. + 2 null term + 1 ( + 1 space */
fprintf(stderr,
"FAIL: expected return len %d != actual %d\n",
strlen(profile) + strlen(mode) + 4, rc);
exit(1);
}
} else if (rc != strlen(profile) + 1) {
/* rc includes null termination */
fprintf(stderr,
"FAIL: expected return len %d != actual %d\n",
strlen(profile) + 1, rc);
exit(1);
}
if (argv[3] && (!mode || strcmp(mode, argv[3]) != 0)) {
fprintf(stderr,
"FAIL: expected mode \"%s\" != \"%s\"\n", argv[3],

View File

@@ -16,6 +16,7 @@ my $nowarn = '';
my $nodefault;
my $noimage;
my $escape = '';
my $usestdin = '';
my %output_rules;
my $hat = "__no_hat";
my %flags;
@@ -26,19 +27,22 @@ GetOptions(
'help|h' => \$help,
'nodefault|N' => \$nodefault,
'noimage|I' => \$noimage,
'stdin' => \$usestdin,
);
sub usage {
print STDERR "$__VERSION__\n";
print STDERR "Usage $0 [--nowarn|--escape] execname [rules]\n";
print STDERR " $0 --help\n";
print STDERR " $0 --stdin\n";
print STDERR " nowarn: don't warn if execname does not exist\n";
print STDERR " nodefault: don't include default rules/ldd output\n";
print STDERR " escape: escape stuff that would be treated as regexs\n";
print STDERR " help: print this message\n";
}
&usage && exit 0 if ($help || @ARGV < 1);
# genprofile passes in $bin:w as default rule atm
&usage && exit 0 if ($help || (!$usestdin && @ARGV < 1) || ($usestdin && @ARGV != 2));
sub head ($) {
my $file = shift;
@@ -214,34 +218,6 @@ sub gen_addimage($) {
}
}
my $bin = shift @ARGV;
!(-e $bin || $nowarn) && print STDERR "Warning: execname '$bin': no such file or directory\n";
unless ($nodefault) {
gen_default_rules();
gen_binary($bin);
}
for my $rule (@ARGV) {
#($fn, @rules) = split (/:/, $rule);
if ($rule =~ /^(tcp|udp)/) {
# netdomain rules
gen_netdomain($rule);
} elsif ($rule =~ /^network:/) {
gen_network($rule);
} elsif ($rule =~ /^cap:/) {
gen_cap($rule);
} elsif ($rule =~ /^flag:/) {
gen_flag($rule);
} elsif ($rule =~ /^hat:/) {
gen_hat($rule);
} elsif ($rule =~ /^addimage:/) {
gen_addimage($rule);
} else {
gen_file($rule);
}
}
sub emit_flags($) {
my $hat = shift;
@@ -255,26 +231,99 @@ sub emit_flags($) {
}
}
print STDOUT "# Profile autogenerated by $__VERSION__\n";
print STDOUT "$bin ";
emit_flags('__no_hat');
print STDOUT "{\n";
foreach my $outrule (@{$output_rules{'__no_hat'}}) {
print STDOUT $outrule;
}
foreach my $hat (keys %output_rules) {
if (not $hat =~ /^__no_hat$/) {
print STDOUT "\n ^$hat";
emit_flags($hat);
print STDOUT " {\n";
foreach my $outrule (@{$output_rules{$hat}}) {
print STDOUT " $outrule";
# generate profiles based on cmd line arguments
sub gen_from_args() {
my $bin = shift @ARGV;
!(-e $bin || $nowarn) && print STDERR "Warning: execname '$bin': no such file or directory\n";
unless ($nodefault) {
gen_default_rules();
gen_binary($bin);
}
for my $rule (@ARGV) {
#($fn, @rules) = split (/:/, $rule);
if ($rule =~ /^(tcp|udp)/) {
# netdomain rules
gen_netdomain($rule);
} elsif ($rule =~ /^network:/) {
gen_network($rule);
} elsif ($rule =~ /^cap:/) {
gen_cap($rule);
} elsif ($rule =~ /^flag:/) {
gen_flag($rule);
} elsif ($rule =~ /^hat:/) {
gen_hat($rule);
} elsif ($rule =~ /^addimage:/) {
gen_addimage($rule);
} else {
gen_file($rule);
}
}
print STDOUT "# Profile autogenerated by $__VERSION__\n";
print STDOUT "$bin ";
emit_flags('__no_hat');
print STDOUT "{\n";
foreach my $outrule (@{$output_rules{'__no_hat'}}) {
print STDOUT $outrule;
}
foreach my $hat (keys %output_rules) {
if (not $hat =~ /^__no_hat$/) {
print STDOUT "\n ^$hat";
emit_flags($hat);
print STDOUT " {\n";
foreach my $outrule (@{$output_rules{$hat}}) {
print STDOUT " $outrule";
}
print STDOUT " }\n";
}
}
#foreach my $hat keys
#foreach my $outrule (@output_rules) {
# print STDOUT $outrule;
#}
print STDOUT "}\n";
}
#generate the profiles from stdin, interpreting and replacing the following sequences
# @{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}
sub emit_and_clear_rules() {
foreach my $outrule (@{$output_rules{'__no_hat'}}) {
print STDOUT $outrule;
}
undef %output_rules;
}
sub gen_from_stdin() {
while(<STDIN>) {
chomp;
if ($_ =~ m/@\{gen_def}/) {
gen_default_rules();
emit_and_clear_rules();
} elsif ($_ =~ m/@\{gen_bin\s+(.+)\}/) {
gen_binary($1);
emit_and_clear_rules();
} elsif ($_ =~ m/@\{gen_elf\s+(.+)\}/) {
gen_elf_binary($1);
emit_and_clear_rules();
} elsif ($_ =~ m/@\{gen\s+(.+)\}/) {
gen_default_rules();
gen_binary($1);
emit_and_clear_rules();
} else {
print STDOUT "$_\n" ;
}
print STDOUT " }\n";
}
}
#foreach my $hat keys
#foreach my $outrule (@output_rules) {
# print STDOUT $outrule;
#}
print STDOUT "}\n";
if ($usestdin) {
gen_from_stdin();
} else {
gen_from_args();
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C) 2002-2005 Novell/SUSE
*
* 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 <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <linux/unistd.h>
#include <sys/apparmor.h>
#include "changehat.h"
int main(int argc, char *argv[])
{
int rc = 0;
extern char **environ;
if (argc < 3){
fprintf(stderr, "usage: %s profile executable args\n",
argv[0]);
return 1;
}
/* change profile if profile name != nochange */
if (strcmp(argv[1], "nochange") != 0){
rc = aa_change_onexec(argv[1]);
if (rc == -1){
fprintf(stderr, "FAIL: change_onexec %s failed - %s\n",
argv[1], strerror(errno));
exit(errno);
}
}
/* stop after onexec and wait to for continue before exec so
* caller can introspect task */
(void)kill(getpid(), SIGSTOP);
(void)execve(argv[2], &argv[2], environ);
/* exec failed, kill outselves to flag parent */
rc = errno;
fprintf(stderr, "FAIL: exec to '%s' failed\n", argv[2]);
return rc;
}

View File

@@ -0,0 +1,181 @@
#! /bin/bash
# Copyright (C) 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.
#=NAME onexec
#=DESCRIPTION
# Verifies basic file access permission checks for change_onexec
#=END
pwd=`dirname $0`
pwd=`cd $pwd ; /bin/pwd`
bin=$pwd
. $bin/prologue.inc
file=$tmpdir/file
subfile=$tmpdir/file2
okperm=rw
othertest="$pwd/rename"
subtest="sub"
fqsubbase="$pwd/onexec"
fqsubtest="$fqsubbase//$subtest"
subtest2="$pwd//sub2"
subtest3="$pwd//sub3"
onexec="/proc/*/attr/exec"
touch $file $subfile
check_exec()
{
local rc
local actual
actual=`cat /proc/$1/attr/exec 2>/dev/null`
rc=$?
# /proc/$1/attr/exec returns invalid argument if onexec has not been called
if [ $rc -ne 0 ] ; then
if [ "$2" == "nochange" ] ; then
return 0
fi
echo "ONEXEC - exec transition not set"
return $rc
fi
if [ "${actual% (*)}" != "$2" ] ; then
echo "ONEXEC - check exec '${actual% (*)}' != expected '$2'"
return 1
fi
return 0
}
check_current()
{
local rc
local actual
actual=`cat /proc/$1/attr/current 2>/dev/null`
rc=$?
# /proc/$1/attr/current return enoent if the onexec process already exited due to error
if [ $rc -ne 0 ] ; then
return $rc
fi
if [ "${actual% (*)}" != "$2" ] ; then
echo "ONEXEC - check current '${actual% (*)}' != expected '$2'"
return 1
fi
return 0
}
do_test()
{
local desc="$1"
local prof="$2"
local target_prof="$3"
local res="$4"
shift 4
#ignore prologue.inc error trapping that catches our subfn return values
runtestbg "ONEXEC $desc ($prof -> $target_prof)" $res $target_prof "$@"
# check that transition does not happen before exec, and that transition
# is set
if ! check_current $_pid $prof ; then
checktestfg
return
fi
if ! check_exec $_pid $target_prof ; then
checktestfg
return
fi
kill -CONT $_pid
checktestbg
}
# ONEXEC from UNCONFINED - don't change profile
do_test "" unconfined nochange pass $bin/open $file
# ONEXEC from UNCONFINED - target does NOT exist
genprofile image=$bin/rw $bin/open:rix $file:rw -- image=$bin/open
do_test "" unconfined noexist fail $bin/open $file
# ONEXEC from UNCONFINED - change to rw profile, no exec profile to override
genprofile image=$bin/rw $bin/open:rix $file:rw
do_test "no px profile" unconfined $bin/rw pass $bin/open $file
# ONEXEC from UNCONFINED - don't change profile, make sure exec profile is applied
genprofile image=$bin/rw $bin/open:px $file:rw -- image=$bin/open $file:rw
do_test "nochange px" unconfined nochange pass $bin/open $file
# ONEXEC from UNCONFINED - change to rw profile, override regular exec profile, exec profile doesn't have perms
genprofile image=$bin/rw $bin/open:px $file:rw -- image=$bin/open
do_test "override px" unconfined $bin/rw pass $bin/open $file
#------
# ONEXEC from CONFINED - don't change profile, open can't exec
genprofile 'change_profile->':$bin/rw $onexec:w
do_test "no px perm" $bin/onexec nochange fail $bin/open $file
# ONEXEC from CONFINED - don't change profile, open is run unconfined
genprofile 'change_profile->':$bin/rw $bin/open:rux $onexec:w
do_test "nochange rux" $bin/onexec nochange pass $bin/open $file
# ONEXEC from CONFINED - don't change profile, open is run confined without necessary perms
genprofile 'change_profile->':$bin/rw $onexec:w -- image=$bin/open $file:rw
do_test "nochange px - no px perm" $bin/onexec nochange fail $bin/open $file
# ONEXEC from CONFINED - don't change profile, open is run confined without necessary perms
genprofile 'change_profile->':$bin/rw $bin/open:rpx $onexec:w -- image=$bin/open
do_test "nochange px - no file perm" $bin/onexec nochange fail $bin/open $file
# ONEXEC from CONFINED - target does NOT exist
genprofile 'change_profile->':$bin/open $onexec:w -- image=$bin/rw $bin/open:rix $file:rw -- image=$bin/open
do_test "noexist px" $bin/onexec noexist fail $bin/open $file
# ONEXEC from CONFINED - change to rw profile, no exec profile to override
genprofile 'change_profile->':$bin/rw $onexec:w -- image=$bin/rw $bin/open:rix $file:rw
do_test "change profile - override rix" $bin/onexec $bin/rw pass $bin/open $file
# ONEXEC from CONFINED - change to rw profile, no exec profile to override, no explicit access to /proc/*/attr/exec
genprofile 'change_profile->':$bin/rw -- image=$bin/rw $bin/open:rix $file:rw
do_test "change profile - no onexec:w" $bin/onexec $bin/rw pass $bin/open $file
# ONEXEC from CONFINED - don't change profile, make sure exec profile is applied
genprofile 'change_profile->':$bin/rw $onexec:w $bin/open:rpx -- image=$bin/rw $bin/open:rix $file:rw -- image=$bin/open $file:rw
do_test "nochange px" $bin/onexec nochange pass $bin/open $file
# ONEXEC from CONFINED - change to rw profile, override regular exec profile, exec profile doesn't have perms
genprofile 'change_profile->':$bin/rw $onexec:w -- image=$bin/rw $bin/open:rix $file:rw -- image=$bin/open
do_test "override px" $bin/onexec $bin/rw pass $bin/open $file
# ONEXEC from - change to rw profile, override regular exec profile, exec profile has perms, rw doesn't
genprofile 'change_profile->':$bin/rw $onexec:w -- image=$bin/rw $bin/open:rix -- image=$bin/open $file:rw
do_test "override px" $bin/onexec $bin/rw fail $bin/open $file
# ONEXEC from COFINED - change to rw profile via glob rule, override exec profile, exec profile doesn't have perms
genprofile 'change_profile->':/** $onexec:w -- image=$bin/rw $bin/open:rix $file:rw -- image=$bin/open
do_test "glob override px" $bin/onexec $bin/rw pass $bin/open $file
# ONEXEC from COFINED - change to exec profile via glob rule, override exec profile, exec profile doesn't have perms
genprofile 'change_profile->':/** $onexec:w -- image=$bin/rw $bin/open:rix $file:rw -- image=$bin/open
do_test "glob override px" $bin/onexec $bin/open fail $bin/open $file
# ONEXEC from COFINED - change to exec profile via glob rule, override exec profile, exec profile has perms
genprofile 'change_profile->':/** $onexec:w -- image=$bin/rw $bin/open:rix $file:rw -- image=$bin/open $file:rw
do_test "glob override px" $bin/onexec $bin/rw pass $bin/open $file

View File

@@ -42,6 +42,12 @@ testfailed()
# global num_testfailures teststatus
num_testfailures=$(($num_testfailures + 1))
teststatus="fail"
# if we are retaining the tmpdir we are debugging failures so
# stop so it can be looked at
if [ $retaintmpdir == "true" ] ; then
exit 127
fi
}
error_handler()

View File

@@ -26,14 +26,20 @@ badperm=ix
mkdir $dir
# CHDIR TEST
# READDIR TEST
genprofile $dir/:$okperm
runchecktest "READDIR" pass $dir
# CHDIR TEST (no perm)
# READDIR TEST (no perm)
genprofile $dir/:$badperm
runchecktest "READDIR (no perm)" fail $dir
# this test is to make sure the raw 'file' rule allows access
# to directories
genprofile file
runchecktest "READDIR 'file' dir" pass $dir
# this test is to make sure the raw 'file' rule allows access
# to '/'
genprofile file
runchecktest "READDIR 'file' '/'" pass '/'

View File

@@ -748,22 +748,12 @@ sub create_new_profile($) {
my $fqdbin = shift;
my $profile;
if ($fqdbin =~ /^\// ) {
$profile = {
$fqdbin => {
flags => "complain",
include => { "abstractions/base" => 1 },
path => { $fqdbin => { mode => str_to_mode("mr") } },
}
};
} else {
$profile = {
$fqdbin => {
flags => "complain",
include => { "abstractions/base" => 1 },
}
};
}
$profile = {
$fqdbin => {
flags => "complain",
include => { "abstractions/base" => 1 },
}
};
# if the executable exists on this system, pull in extra dependencies
if (-f $fqdbin) {
@@ -771,12 +761,12 @@ sub create_new_profile($) {
if ($hashbang && $hashbang =~ /^#!\s*(\S+)/) {
my $interpreter = get_full_path($1);
$profile->{$fqdbin}{allow}{path}->{$fqdbin}{mode} |= str_to_mode("r");
$profile->{$fqdbin}{allow}{path}->{$fqdbin}{mode} |= 0;
$profile->{$fqdbin}{allow}{path}->{$fqdbin}{audit} |= 0;
$profile->{$fqdbin}{allow}{path}->{$interpreter}{mode} |= str_to_mode("ix");
$profile->{$fqdbin}{allow}{path}->{$interpreter}{audit} |= 0;
if ($interpreter =~ /perl/) {
$profile->{$fqdbin}{include}->{"abstractions/perl"} = 1;
} elsif ($interpreter =~ m/\/bin\/(bash|sh)/) {
} elsif ($interpreter =~ m/\/bin\/(bash|dash|sh)/) {
$profile->{$fqdbin}{include}->{"abstractions/bash"} = 1;
} elsif ($interpreter =~ m/python/) {
$profile->{$fqdbin}{include}->{"abstractions/python"} = 1;
@@ -785,6 +775,8 @@ sub create_new_profile($) {
}
handle_binfmt($profile->{$fqdbin}, $interpreter);
} else {
$profile->{$fqdbin}{allow}{path}->{$fqdbin}{mode} |= str_to_mode("mr");
$profile->{$fqdbin}{allow}{path}->{$fqdbin}{audit} |= 0;
handle_binfmt($profile->{$fqdbin}, $fqdbin);
}
}
@@ -798,6 +790,7 @@ sub create_new_profile($) {
}
}
push @created, $fqdbin;
$DEBUGGING && debug( Data::Dumper->Dump([$profile], [qw(*profile)]));
return { $fqdbin => $profile };
}
@@ -2398,8 +2391,18 @@ sub handlechildren($$$) {
# put in enforce mode with genprof
$sd{$profile}{$hat}{flags} = $sd{$profile}{$profile}{flags} if $profile ne $hat;
# autodep our new child
my $stub_profile = create_new_profile($hat);
$sd{$profile}{$hat}{flags} = 'complain';
$sd{$profile}{$hat}{allow}{path} = { };
if (defined $stub_profile->{$hat}{$hat}{allow}{path}) {
$sd{$profile}{$hat}{allow}{path} = $stub_profile->{$hat}{$hat}{allow}{path};
}
$sd{$profile}{$hat}{include} = { };
if (defined $stub_profile->{$hat}{$hat}{include}) {
$sd{$profile}{$hat}{include} = $stub_profile->{$hat}{$hat}{include};
}
$sd{$profile}{$hat}{allow}{netdomain} = { };
my $file = $sd{$profile}{$profile}{filename};
$filelist{$file}{profiles}{$profile}{$hat} = 1;
@@ -2850,7 +2853,21 @@ sub add_event_to_tree ($) {
$e->{name},
""
);
}
} elsif (defined $e->{name}) {
add_to_tree( $e->{pid},
$e->{parent},
"exec",
$profile,
$hat,
$prog,
$sdmode,
$e->{denied_mask},
$e->{name},
""
);
} else {
$DEBUGGING && debug "add_event_to_tree: dropped exec event in $e->{profile}";
}
} elsif ($e->{operation} =~ m/file_/) {
add_to_tree( $e->{pid},
$e->{parent},
@@ -4797,13 +4814,9 @@ sub sub_mode_to_str($) {
$str .= "a" if ($mode & $AA_MAY_APPEND);
$str .= "l" if ($mode & $AA_MAY_LINK);
$str .= "k" if ($mode & $AA_MAY_LOCK);
if ($mode & $AA_EXEC_UNCONFINED) {
if ($mode & $AA_EXEC_UNSAFE) {
$str .= "u";
} else {
$str .= "U";
}
}
# modes P and C *must* come before I and U; otherwise syntactically
# invalid profiles result
if ($mode & ($AA_EXEC_PROFILE | $AA_EXEC_NT)) {
if ($mode & $AA_EXEC_UNSAFE) {
$str .= "p";
@@ -4818,7 +4831,18 @@ sub sub_mode_to_str($) {
$str .= "C";
}
}
# modes P and C *must* come before I and U; otherwise syntactically
# invalid profiles result
if ($mode & $AA_EXEC_UNCONFINED) {
if ($mode & $AA_EXEC_UNSAFE) {
$str .= "u";
} else {
$str .= "U";
}
}
$str .= "i" if ($mode & $AA_EXEC_INHERIT);
$str .= "x" if ($mode & $AA_MAY_EXEC);
return $str;

View File

@@ -28,15 +28,18 @@ endif
MODDIR = Immunix
PERLTOOLS = aa-genprof aa-logprof aa-autodep aa-audit aa-complain aa-enforce \
aa-unconfined aa-notify aa-disable
aa-unconfined aa-notify aa-disable aa-exec
TOOLS = ${PERLTOOLS} aa-decode aa-status
MODULES = ${MODDIR}/AppArmor.pm ${MODDIR}/Repository.pm \
${MODDIR}/Config.pm ${MODDIR}/Severity.pm
PYTOOLS = aa-easyprof
PYSETUP = python-tools-setup.py
MANPAGES = ${TOOLS:=.8} logprof.conf.5
MANPAGES = ${TOOLS:=.8} logprof.conf.5 ${PYTOOLS:=.8}
all: ${MANPAGES} ${HTMLMANPAGES}
$(MAKE) -C po all
$(MAKE) -C vim all
# need some better way of determining this
DESTDIR=/
@@ -44,9 +47,10 @@ BINDIR=${DESTDIR}/usr/sbin
CONFDIR=${DESTDIR}/etc/apparmor
VENDOR_PERL=$(shell perl -e 'use Config; print $$Config{"vendorlib"};')
PERLDIR=${DESTDIR}${VENDOR_PERL}/${MODDIR}
PYPREFIX=/usr
po/${NAME}.pot: ${TOOLS}
$(MAKE) -C po ${NAME}.pot NAME=${NAME} SOURCES="${TOOLS} ${MODULES}"
po/${NAME}.pot: ${TOOLS} ${PYTOOLS}
$(MAKE) -C po ${NAME}.pot NAME=${NAME} SOURCES="${TOOLS} ${MODULES} ${PYTOOLS}"
.PHONY: install
install: ${MANPAGES} ${HTMLMANPAGES}
@@ -59,16 +63,48 @@ install: ${MANPAGES} ${HTMLMANPAGES}
install -m 644 ${MODULES} ${PERLDIR}
$(MAKE) -C po install DESTDIR=${DESTDIR} NAME=${NAME}
$(MAKE) install_manpages DESTDIR=${DESTDIR}
$(MAKE) -C vim install DESTDIR=${DESTDIR}
ln -sf aa-status.8 ${DESTDIR}/${MANDIR}/man8/apparmor_status.8
python ${PYSETUP} install --prefix=${PYPREFIX} --root=${DESTDIR} --version=${VERSION}
.PHONY: clean
ifndef VERBOSE
.SILENT: clean
endif
clean: _clean
rm -f core core.* *.o *.s *.a *~
rm -f Make.rules
$(MAKE) -C po clean
$(MAKE) -C vim clean
rm -rf staging/ build/
rm -f apparmor/*.pyc
check:
# ${CAPABILITIES} is defined in common/Make.rules
.PHONY: check_severity_db
.SILENT: check_severity_db
check_severity_db: /usr/include/linux/capability.h severity.db
# The sed statement is based on the one in the parser's makefile
RC=0 ; for cap in ${CAPABILITIES} ; do \
if ! grep -q -w $${cap} severity.db ; then \
echo "Warning! capability $${cap} not found in severity.db" ; \
RC=1 ; \
fi ;\
done ; \
test "$$RC" -eq 0
.PHONY: check
.SILENT: check
check: check_severity_db
for i in ${MODULES} ${PERLTOOLS} ; do \
perl -c $$i || exit 1; \
done
tmpfile=$$(mktemp --tmpdir aa-pyflakes-XXXXXX); \
for i in ${PYTOOLS} apparmor aa-status test/*.py; do \
echo Checking $$i; \
pyflakes $$i 2>&1 | grep -v "undefined name '_'" > $$tmpfile; \
test -s $$tmpfile && cat $$tmpfile && rm -f $$tmpfile && exit 1; \
done || true; \
rm -f $$tmpfile
for i in test/* ; do \
python $$i || exit 1; \
done

65
utils/aa-easyprof Executable file
View File

@@ -0,0 +1,65 @@
#! /usr/bin/env python
# ------------------------------------------------------------------
#
# Copyright (C) 2011-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
# License published by the Free Software Foundation.
#
# ------------------------------------------------------------------
import apparmor.easyprof
from apparmor.easyprof import AppArmorException, error
import os
import sys
if __name__ == "__main__":
def usage():
'''Return usage information'''
return 'USAGE: %s [options] <path to binary>' % \
os.path.basename(sys.argv[0])
(opt, args) = apparmor.easyprof.parse_args()
binary = None
m = usage()
if opt.show_policy_group and not opt.policy_groups:
error("Must specify -p with --show-policy-group")
elif not opt.template and not opt.policy_groups and len(args) < 1:
error("Must specify full path to binary\n%s" % m)
binary = None
if len(args) >= 1:
binary = args[0]
try:
easyp = apparmor.easyprof.AppArmorEasyProfile(binary, opt)
except AppArmorException, e:
error(e.value)
except Exception:
raise
if opt.list_templates:
apparmor.easyprof.print_basefilenames(easyp.get_templates())
sys.exit(0)
elif opt.template and opt.show_template:
files = [os.path.join(easyp.dirs['templates'], opt.template)]
apparmor.easyprof.print_files(files)
sys.exit(0)
elif opt.list_policy_groups:
apparmor.easyprof.print_basefilenames(easyp.get_policy_groups())
sys.exit(0)
elif opt.policy_groups and opt.show_policy_group:
for g in opt.policy_groups.split(','):
files = [os.path.join(easyp.dirs['policygroups'], g)]
apparmor.easyprof.print_files(files)
sys.exit(0)
elif binary == None:
error("Must specify full path to binary\n%s" % m)
# if we made it here, generate a profile
params = apparmor.easyprof.gen_policy_params(binary, opt)
p = easyp.gen_policy(**params)
print p,

146
utils/aa-easyprof.pod Normal file
View File

@@ -0,0 +1,146 @@
# 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 adheres 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-easyprof - AppArmor profile generation made easy.
=head1 SYNOPSIS
B<aa-easyprof> [option] <path to binary>
=head1 DESCRIPTION
B<aa-easyprof> provides an easy to use interface for AppArmor policy
generation. B<aa-easyprof> supports the use of templates and policy groups to
quickly profile an application. Please note that while this tool can help
with policy generation, its utility is dependent on the quality of the
templates, policy groups and abstractions used. Also, this tool may create
policy which is less restricted than creating policy by hand or with
B<aa-genprof> and B<aa-logprof>.
=head1 OPTIONS
B<aa-easyprof> accepts the following arguments:
=over 4
=item -t TEMPLATE, --template=TEMPLATE
Specify which template to use. May specify either a system template from
/usr/share/apparmor/easyprof/templates or a filename for the template to
use. If not specified, use /usr/share/apparmor/easyprof/templates/default.
=item -p POLICYGROUPS, --policy-groups=POLICYGROUPS
Specify POLICY as a comma-separated list of policy groups. See --list-templates
for supported policy groups. The available policy groups are in
/usr/share/apparmor/easyprof/policy. Policy groups are simply groupings of
AppArmor rules or policies. They are similar to AppArmor abstractions, but
usually encompass more policy rules.
=item -a ABSTRACTIONS, --abstractions=ABSTRACTIONS
Specify ABSTRACTIONS as a comma-separated list of AppArmor abstractions. It is
usually recommended you use policy groups instead, but this is provided as a
convenience. AppArmor abstractions are located in /etc/apparmor.d/abstractions.
See apparmor.d(5) for details.
=item -r PATH, --read-path=PATH
Specify a PATH to allow owner reads. May be specified multiple times. If the
PATH ends in a '/', then PATH is treated as a directory and reads are allowed
to all files under this directory. Can optionally use '/*' at the end of the
PATH to only allow reads to files directly in PATH.
=item -w PATH, --write-dir=PATH
Like --read-path but also allow owner writes in additions to reads.
=item -n NAME, --name=NAME
Specify NAME of policy. If not specified, NAME is set to the name of the
binary. The NAME of the policy is often used as part of the path in the
various templates.
=item --template-var="@{VAR}=VALUE"
Set VAR to VALUE in the resulting policy. This typically only makes sense if
the specified template uses this value. May be specified multiple times.
=item --list-templates
List available templates.
=item --show-template=TEMPLATE
Display template specified with --template.
=item --templates-dir=PATH
Use PATH instead of system templates directory.
=item --list-policy-groups
List available policy groups.
=item --show-policy-group
Display policy groups specified with --policy.
=item --policy-groups-dir=PATH
Use PATH instead of system policy-groups directory.
=item --author
Specify author of the policy.
=item --copyright
Specify copyright of the policy.
=item --comment
Specify comment for the policy.
=back
=head1 EXAMPLE
Example usage for a program named 'foo' which is installed in /opt/foo:
=over
$ aa-easyprof --template=user-application --template-var="@{APPNAME}=foo" --policy-groups=opt-application,user-application /opt/foo/bin/FooApp
=back
=head1 BUGS
If you find any additional bugs, please report them to Launchpad at
L<https://bugs.launchpad.net/apparmor/+filebug>.
=head1 SEE ALSO
apparmor(7) apparmor.d(5)
=cut

124
utils/aa-exec Executable file
View File

@@ -0,0 +1,124 @@
#!/usr/bin/perl
# ------------------------------------------------------------------
#
# Copyright (C) 2011-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
# License published by the Free Software Foundation.
#
# ------------------------------------------------------------------
use strict;
use warnings;
use Errno;
require LibAppArmor;
require POSIX;
require Time::Local;
require File::Basename;
my $opt_d = '';
my $opt_h = '';
my $opt_p = '';
my $opt_n = '';
my $opt_i = '';
my $opt_v = '';
my $opt_f = '';
sub _warn {
my $msg = $_[0];
print STDERR "aa-exec: WARN: $msg\n";
}
sub _error {
my $msg = $_[0];
print STDERR "aa-exec: ERROR: $msg\n";
exit 1
}
sub _debug {
$opt_d or return;
my $msg = $_[0];
print STDERR "aa-exec: DEBUG: $msg\n";
}
sub _verbose {
$opt_v or return;
my $msg = $_[0];
print STDERR "$msg\n";
}
sub usage() {
my $s = <<'EOF';
USAGE: aa-exec [OPTIONS] <prog> <args>
Confine <prog> with the specified PROFILE.
OPTIONS:
-p PROFILE, --profile=PROFILE PROFILE to confine <prog> with
-n NAMESPACE, --namespace=NAMESPACE NAMESPACE to confine <prog> in
-f FILE, --file FILE profile file to load
-i, --immediate change profile immediately instead of at exec
-v, --verbose show messages with stats
-h, --help display this help
EOF
print $s;
}
use Getopt::Long;
GetOptions(
'debug|d' => \$opt_d,
'help|h' => \$opt_h,
'profile|p=s' => \$opt_p,
'namespace|n=s' => \$opt_n,
'file|f=s' => \$opt_f,
'immediate|i' => \$opt_i,
'verbose|v' => \$opt_v,
);
if ($opt_h) {
usage();
exit(0);
}
if ($opt_n || $opt_p) {
my $test;
my $prof;
if ($opt_n) {
$prof = ":$opt_n:";
}
$prof .= $opt_p;
if ($opt_f) {
system("apparmor_parser", "-r", "$opt_f") == 0
or _error("\'aborting could not load $opt_f\'");
}
if ($opt_i) {
_verbose("aa_change_profile(\"$prof\")");
$test = LibAppArmor::aa_change_profile($prof);
_debug("$test = aa_change_profile(\"$prof\"); $!");
} else {
_verbose("aa_change_onexec(\"$prof\")");
$test = LibAppArmor::aa_change_onexec($prof);
_debug("$test = aa_change_onexec(\"$prof\"); $!");
}
if ($test != 0) {
if ($!{ENOENT} || $!{EACCESS}) {
my $pre = ($opt_p) ? "profile" : "namespace";
_error("$pre \'$prof\' does not exist\n");
} elsif ($!{EINVAL}) {
_error("AppArmor interface not available\n");
} else {
_error("$!\n");
}
}
}
_verbose("exec @ARGV");
exec @ARGV;

97
utils/aa-exec.pod Normal file
View File

@@ -0,0 +1,97 @@
# 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 adheres 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-exec - confine a program with the specified AppArmor profile
=head1 SYNOPSIS
B<aa-exec> [options] [--] [I<E<lt>commandE<gt>> ...]
=head1 DESCRIPTION
B<aa-exec> is used to launch a program confined by the specified profile
and or namespace. If both a profile and namespace are specified command
will be confined by profile in the new policy namespace. If only a namespace
is specified, the profile name of the current confinement will be used. If
neither a profile or namespace is specified command will be run using
standard profile attachment (ie. as if run without the aa-exec command).
If the arguments are to be pasted to the I<E<lt>commandE<gt>> being invoked
by aa-exec then -- should be used to separate aa-exec arguments from the
command.
aa-exec -p profile1 -- ls -l
=head1 OPTIONS
B<aa-exec> accepts the following arguments:
=over 4
=item -p PROFILE, --profile=PROFILE
confine I<E<lt>commandE<gt>> with PROFILE. If the PROFILE is not specified
use the current profile name (likely unconfined).
=item -n NAMESPACE, --namespace=NAMESPACE
use profiles in NAMESPACE. This will result in confinement transitioning
to using the new profile namespace.
=item -f FILE, --file=FILE
a file or directory containing profiles to load before confining the program.
=item -i, --immediate
transition to PROFILE before doing executing I<E<lt>commandE<gt>>. This
subjects the running of I<E<lt>commandE<gt>> to the exec transition rules
of the current profile.
=item -v, --verbose
show commands being performed
=item -d, --debug
show commands and error codes
=item --
Signal the end of options and disables further option processing. Any
arguments after the -- are treated as arguments of the command. This is
useful when passing arguments to the I<E<lt>commandE<gt>> being invoked by
aa-exec.
=back
=head1 BUGS
If you find any bugs, please report them at
L<http://https://bugs.launchpad.net/apparmor/+filebug>.
=head1 SEE ALSO
aa-stack(8), aa-namespace(8), apparmor(7), apparmor.d(5), aa_change_profile(3),
aa_change_onexec(3) and L<http://wiki.apparmor.net>.
=cut

View File

@@ -1,234 +0,0 @@
" $Id: apparmor.vim,v 1.11 2011/01/31 22:48:07 cb Exp $
"
" ----------------------------------------------------------------------
" Copyright (c) 2005 Novell, Inc. All Rights Reserved.
" Copyright (c) 2006-2011 Christian Boltz. 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 as 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.
"
" To contact Novell about this file by physical or electronic mail,
" you may find current contact information at www.novell.com.
"
" To contact Christian Boltz about this file by physical or electronic
" mail, you may find current contact information at www.cboltz.de/en/kontakt.
"
" If you want to report a bug via bugzilla.novell.com, please assign it
" to suse-beta[AT]cboltz.de (replace [AT] with @).
" ----------------------------------------------------------------------
"
" stick this file into ~/.vim/syntax/ and add these commands into your .vimrc
" to have vim automagically use this syntax file for these directories:
"
" autocmd BufNewFile,BufRead /etc/apparmor.d/* set syntax=apparmor
" autocmd BufNewFile,BufRead /etc/apparmor/profiles/* set syntax=apparmor
" profiles are case sensitive
syntax case match
" color setup...
" adjust colors according to the background
" switching colors depending on the background color doesn't work
" unfortunately, so we use colors that work with light and dark background.
" Patches welcome ;-)
"if &background == "light"
" light background
hi sdProfileName ctermfg=lightblue
hi sdHatName ctermfg=darkblue
hi sdExtHat ctermfg=darkblue
" hi sdComment2 ctermfg=darkblue
hi sdGlob ctermfg=darkmagenta
hi sdAlias ctermfg=darkmagenta
hi sdEntryWriteExec ctermfg=black ctermbg=yellow
hi sdEntryUX ctermfg=darkred cterm=underline
hi sdEntryUXe ctermfg=darkred
hi sdEntryIX ctermfg=darkcyan
hi sdEntryM ctermfg=darkcyan
hi sdEntryPX ctermfg=darkgreen cterm=underline
hi sdEntryPXe ctermfg=darkgreen
hi sdEntryW ctermfg=darkyellow
hi sdCap ctermfg=lightblue
hi sdSetCap ctermfg=black ctermbg=yellow
hi sdNetwork ctermfg=lightblue
hi sdNetworkDanger ctermfg=darkred
hi sdCapKey cterm=underline ctermfg=lightblue
hi sdCapDanger ctermfg=darkred
hi sdRLimit ctermfg=lightblue
hi def link sdEntryR Normal
hi def link sdEntryK Normal
hi def link sdFlags Normal
hi sdEntryChangeProfile ctermfg=darkgreen cterm=underline
"else
" dark background
" hi sdProfileName ctermfg=white
" hi sdHatName ctermfg=white
" hi sdGlob ctermfg=magenta
" hi sdEntryWriteExec ctermfg=black ctermbg=yellow
" hi sdEntryUX ctermfg=red cterm=underline
" hi sdEntryUXe ctermfg=red
" hi sdEntryIX ctermfg=cyan
" hi sdEntryM ctermfg=cyan
" hi sdEntryPX ctermfg=green cterm=underline
" hi sdEntryPXe ctermfg=green
" hi sdEntryW ctermfg=yellow
" hi sdCap ctermfg=lightblue
" hi sdCapKey cterm=underline ctermfg=lightblue
" hi def link sdEntryR Normal
" hi def link sdFlags Normal
" hi sdCapDanger ctermfg=red
"endif
hi def link sdInclude Include
high def link sdComment Comment
"high def link sdComment2 Comment
high def link sdFlagKey TODO
high def link sdError ErrorMsg
" always sync from the start. should be relatively quick since we don't have
" that many rules and profiles shouldn't be _extremely_ large...
syn sync fromstart
syn keyword sdFlagKey complain debug
" highlight invalid syntax
syn match sdError /{/ contained
syn match sdError /}/
syn match sdError /^.*$/ contains=sdComment "highlight all non-valid lines as error
" TODO: do not mark lines containing only whitespace as error
" TODO: the sdGlob pattern is not anchored with ^ and $, so it matches all lines matching ^@{...}.*
" This allows incorrect lines also and should be checked better.
" This also (accidently ;-) includes variable definitions (@{FOO}=/bar)
" TODO: make a separate pattern for variable definitions, then mark sdGlob as contained
syn match sdGlob /\v\?|\*|\{.*,.*\}|[[^\]]\+\]|\@\{[a-zA-Z][a-zA-Z0-9_]*\}/
syn match sdAlias /\v^alias\s+(\/|\@\{\S*\})\S*\s+-\>\s+(\/|\@\{\S*\})\S*\s*,(\s*$|(\s*#.*$)\@=)/ contains=sdGlob
" syn match sdComment /#.*/
syn cluster sdEntry contains=sdEntryWriteExec,sdEntryR,sdEntryW,sdEntryIX,sdEntryPX,sdEntryPXe,sdEntryUX,sdEntryUXe,sdEntryM,sdCap,sdSetCap,sdExtHat,sdRLimit,sdNetwork,sdNetworkDanger,sdEntryChangeProfile
" TODO: support audit and deny keywords for all rules (not only for files)
" TODO: higlight audit and deny keywords everywhere
" Capability line
" normal capabilities - really keep this list? syn match sdCap should be enough... (difference: sdCapKey words would loose underlining)
syn keyword sdCapKey chown dac_override dac_read_search fowner fsetid kill setgid setuid setpcap linux_immutable net_bind_service net_broadcast net_admin net_raw ipc_lock ipc_owner sys_module sys_rawio sys_chroot sys_ptrace sys_pacct sys_boot sys_nice sys_resource sys_time sys_tty_config mknod lease
" dangerous capabilities - highlighted separately
syn keyword sdCapDanger sys_admin audit_control audit_write set_fcap mac_override mac_admin
" full line. Keywords are from sdCapKey + sdCapDanger
syn match sdCap /\v^\s*(audit\s+)?(deny\s+)?capability\s+(chown|dac_override|dac_read_search|fowner|fsetid|kill|setgid|setuid|setpcap|linux_immutable|net_bind_service|net_broadcast|net_admin|net_raw|ipc_lock|ipc_owner|sys_module|sys_rawio|sys_chroot|sys_ptrace|sys_pacct|sys_boot|sys_nice|sys_resource|sys_time|sys_tty_config|mknod|lease|sys_admin|audit_control|audit_write|set_fcap|mac_override|mac_admin)\s*,(\s*$|(\s*#.*$)\@=)/ contains=sdCapKey,sdCapDanger,sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude
" set capability was removed - TODO: remove everywhere in apparmor.vim
" syn match sdSetCap /\v^\s*set\s+capability\s+(chown|dac_override|dac_read_search|fowner|fsetid|kill|setgid|setuid|setpcap|linux_immutable|net_bind_service|net_broadcast|net_admin|net_raw|ipc_lock|ipc_owner|sys_module|sys_rawio|sys_chroot|sys_ptrace|sys_pacct|sys_boot|sys_nice|sys_resource|sys_time|sys_tty_config|mknod|lease|sys_admin|audit_control|audit_write|set_fcap|mac_override|mac_admin)\s*,(\s*$|(\s*#.*$)\@=)/ contains=sdCapKey,sdCapDanger,sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude
" Network line
" Syntax: network domain (inet, ...) type (stream, ...) protocol (tcp, ...)
" TODO: 'owner' isn't supported, but will be (JJ, 2011-01-11)
syn match sdNetwork /\v^\s*(audit\s+)?(deny\s+)?network(\s+(inet|ax25|ipx|appletalk|netrom|bridge|atmpvc|x25|inet6|rose|netbeui|security|key|packet|ash|econet|atmsvc|sna|irda|pppox|wanpipe|bluetooth))?(\s+(stream|dgram|seqpacket|rdm|packet))?(\s+tcp|\s+udp|\s+icmp)?\s*,(\s*$|(\s*#.*$)\@=)/ contains=sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude
" network rules containing 'raw'
syn match sdNetworkDanger /\v^\s*(audit\s+)?(deny\s+)?network(\s+(inet|ax25|ipx|appletalk|netrom|bridge|atmpvc|x25|inet6|rose|netbeui|security|key|packet|ash|econet|atmsvc|sna|irda|pppox|wanpipe|bluetooth))?(\s+(raw))(\s+tcp|\s+udp|\s+icmp)?\s*,(\s*$|(\s*#.*$)\@=)/ contains=sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude
" 'all networking' includes raw -> mark as dangerous
syn match sdNetworkDanger /\v^\s*(audit\s+)?(deny\s+)?network\s*,(\s*$|(\s*#.*$)\@=)/ contains=sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude
" Change Profile
" TODO: audit and deny support will be added (JJ, 2011-01-11)
syn match sdEntryChangeProfile /\v^\s*change_profile\s+-\>\s+\S+\s*,(\s*$|(\s*#.*$)\@=)/ contains=sdGlob,sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude
" rlimit
" TODO: audit and deny support will be added (JJ, 2011-01-11)
"
"syn match sdRLimit /\v^\s*rlimit\s+()\s*,(\s*$|(\s*#.*$)\@=)/ contains=sdComment
syn match sdRLimit /\v^\s*set\s+rlimit\s+(nofile|nproc|rtprio)\s+[0-9]+\s*,(\s*$|(\s*#.*$)\@=)/ contains=sdComment
syn match sdRLimit /\v^\s*set\s+rlimit\s+(locks|sigpending)\s+\<\=\s+[0-9]+\s*,(\s*$|(\s*#.*$)\@=)/ contains=sdComment
syn match sdRLimit /\v^\s*set\s+rlimit\s+(fsize|data|stack|core|rss|as|memlock|msgqueue)\s+\<\=\s+[0-9]+([KMG])?\s*,(\s*$|(\s*#.*$)\@=)/ contains=sdComment
syn match sdRLimit /\v^\s*set\s+rlimit\s+nice\s+\<\=\s+(-1?[0-9]|-20|1?[0-9])\s*,(\s*$|(\s*#.*$)\@=)/ contains=sdComment
" link rules
syn match sdEntryW /\v^\s+(audit\s+)?(deny\s+)?(owner\s+)?link\s+(subset\s+)?(\/|\@\{\S*\})\S*\s+-\>\s+(\/|\@\{\S*\})\S*\s*,(\s*$|(\s*#.*$)\@=)/ contains=sdGlob
" file permissions
"
" TODO: Support filenames enclosed in quotes ("/home/foo/My Documents/") - ideally by only allowing quotes pair-wise
"
" write + exec/mmap - danger!
" known bug: accepts 'aw' to keep things simple
syn match sdEntryWriteExec /\v^\s*(audit\s+)?(deny\s+)?(owner\s+)?(\/|\@\{\S*\})\S*\s+(l|r|w|a|m|k|[iuUpPcC]x)+(\s+-\>\s+\S+)?\s*,(\s*$|(\s*#.*$)\@=)/ contains=sdGlob,sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude
" ux(mr) - unconstrained entry, flag the line red
syn match sdEntryUX /\v^\s*(audit\s+)?(deny\s+)?(owner\s+)?(\/|\@\{\S*\})\S*\s+(r|m|k|ux)+(\s+-\>\s+\S+)?\s*,(\s*$|(\s*#.*$)\@=)/ contains=sdGlob,sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude
" Ux(mr) - like ux + clean environment
syn match sdEntryUXe /\v^\s*(audit\s+)?(deny\s+)?(owner\s+)?(\/|\@\{\S*\})\S*\s+(r|m|k|Ux)+(\s+-\>\s+\S+)?\s*,(\s*$|(\s*#.*$)\@=)/ contains=sdGlob,sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude
" px/cx/pix/cix(mrk) - standard exec entry, flag the line blue
syn match sdEntryPX /\v^\s*(audit\s+)?(deny\s+)?(owner\s+)?(\/|\@\{\S*\})\S*\s+(r|m|k|px|cx|pix|cix)+(\s+-\>\s+\S+)?\s*,(\s*$|(\s*#.*$)\@=)/ contains=sdGlob,sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude
" Px/Cx/Pix/Cix(mrk) - like px/cx + clean environment
syn match sdEntryPXe /\v^\s*(audit\s+)?(deny\s+)?(owner\s+)?(\/|\@\{\S*\})\S*\s+(r|m|k|Px|Cx|Pix|Cix)+(\s+-\>\s+\S+)?\s*,(\s*$|(\s*#.*$)\@=)/ contains=sdGlob,sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude
" ix(mr) - standard exec entry, flag the line green
syn match sdEntryIX /\v^\s*(audit\s+)?(deny\s+)?(owner\s+)?(\/|\@\{\S*\})\S*\s+(r|m|k|ix)+\s*,(\s*$|(\s*#.*$)\@=)/ contains=sdGlob,sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude
" mr - mmap with PROT_EXEC
syn match sdEntryM /\v^\s*(audit\s+)?(deny\s+)?(owner\s+)?(\/|\@\{\S*\})\S*\s+(r|m|k)+\s*,(\s*$|(\s*#.*$)\@=)/ contains=sdGlob,sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude
" if we've got u or i without x, it's an error
" rule is superfluous because of the '/.*/ is an error' rule ;-)
"syn match sdError /\v^\s*(audit\s+)?(deny\s+)?(owner\s+)?(\/|\@\{\S*\})\S*\s+(l|r|w|k|u|p|i)+\s*,(\s*$|(\s*#.*$)\@=)/ contains=sdGlob,sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude
" write + append is an error also
"syn match sdError /\v^\s*(audit\s+)?(deny\s+)?(owner\s+)?(\/|\@\{\S*\})\S*\s+(\S*r\S*a\S*|\S*a\S*w\S*)\s*,(\s*$|(\s*#.*$)\@=)/ contains=sdGlob,sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude
syn match sdError /\v^\s*(audit\s+)?(deny\s+)?(owner\s+)?(\/|\@\{\S*\})\S*\s+\S*(w\S*a|a\S*w)\S*\s*,(\s*$|(\s*#.*$)\@=)/ contains=sdGlob,sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude
" write entry, flag the line yellow
syn match sdEntryW /\v^\s*(audit\s+)?(deny\s+)?(owner\s+)?(\/|\@\{\S*\})\S*\s+(l|r|w|k)+\s*,(\s*$|(\s*#.*$)\@=)/ contains=sdGlob,sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude
" append entry, flag the line yellow
syn match sdEntryW /\v^\s*(audit\s+)?(deny\s+)?(owner\s+)?(\/|\@\{\S*\})\S*\s+(l|r|a|k)+\s*,(\s*$|(\s*#.*$)\@=)/ contains=sdGlob,sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude
" read entry + locking, currently no highlighting
syn match sdEntryK /\v^\s*(audit\s+)?(deny\s+)?(owner\s+)?(\/|\@\{\S*\})\S*\s+[rlk]+\s*,(\s*$|(\s*#.*$)\@=)/ contains=sdGlob,sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude
" read entry, no highlighting
syn match sdEntryR /\v^\s*(audit\s+)?(deny\s+)?(owner\s+)?(\/|\@\{\S*\})\S*\s+[rl]+\s*,(\s*$|(\s*#.*$)\@=)/ contains=sdGlob,sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude
syn match sdExtHat /\v^\s+(\^|profile\s+)\S+\s*,(\s*$|(\s*#.*$)\@=)/ contains=sdComment " hat without {...}
syn match sdProfileName /\v^((profile\s+)?\/\S+|profile\s+([a-zA-Z0-9]\S*\s)?\S+)\s+((flags\s*\=\s*)?\(\s*(complain|audit|attach_disconnect|no_attach_disconnected|chroot_attach|chroot_no_attach|chroot_relative|namespace_relative)(\s*,\s*(complain|audit|attach_disconnect|no_attach_disconnected|chroot_attach|chroot_no_attach|chroot_relative|namespace_relative))*\s*\)\s+)=\{/ contains=sdProfileStart,sdHatName,sdFlags,sdComment,sdGlob
syn match sdProfileStart /{/ contained
syn match sdProfileEnd /^}\s*(#.*)?$/ contained " TODO: syn region does not (yet?) allow usage of comment in end=
" TODO: Removing the $ mark from end= will allow non-comments also :-(
syn match sdHatName /\v^\s+(\^|profile\s+)\S+\s+((flags\s*\=\s*)?\(\s*(complain|audit|attach_disconnect|no_attach_disconnected|chroot_attach|chroot_no_attach|chroot_relative|namespace_relative)(\s*,\s*(complain|audit|attach_disconnect|no_attach_disconnected|chroot_attach|chroot_no_attach|chroot_relative|namespace_relative))*\s*\)\s+)=\{/ contains=sdProfileStart,sdFlags,sdComment
syn match sdHatStart /{/ contained
syn match sdHatEnd /}/ contained " TODO: allow comments + [same as for syn match sdProfileEnd]
syn match sdFlags /\v((flags\s*\=\s*)?\(\s*(complain|audit|attach_disconnect|no_attach_disconnected|chroot_attach|chroot_no_attach|chroot_relative|namespace_relative)(\s*,\s*(complain|audit|attach_disconnect|no_attach_disconnected|chroot_attach|chroot_no_attach|chroot_relative|namespace_relative))*\s*\)\s+)/ contained contains=sdFlagKey
syn match sdComment /\s*#.*$/
" NOTE: contains=sdComment changes #include highlighting to comment color.
" NOTE: Comment highlighting still works without contains=sdComment.
syn match sdInclude /\s*#include\s<\S*>/ " TODO: doesn't check until $
syn match sdInclude /\s*include\s<\S*>/ " TODO: doesn't check until $
" basic profile block...
" \s+ does not work in end=, therefore using \s\s*
syn region Normal start=/\v^(profile\s+)?\S+\s+((flags\s*\=\s*)?\(\s*(complain|audit|attach_disconnect|no_attach_disconnected|chroot_attach|chroot_no_attach|chroot_relative|namespace_relative)(\s*,\s*(complain|audit|attach_disconnect|no_attach_disconnected|chroot_attach|chroot_no_attach|chroot_relative|namespace_relative))*\s*\)\s+)=\{/ matchgroup=sdProfileEnd end=/^}\s*$/ contains=sdProfileName,Hat,@sdEntry,sdComment,sdError,sdInclude
syn region Hat start=/\v^\s+(\^|profile\s+)\S+\s+((flags\s*\=\s*)?\(\s*(complain|audit|attach_disconnect|no_attach_disconnected|chroot_attach|chroot_no_attach|chroot_relative|namespace_relative)(\s*,\s*(complain|audit|attach_disconnect|no_attach_disconnected|chroot_attach|chroot_no_attach|chroot_relative|namespace_relative))*\s*\)\s+)=\{/ matchgroup=sdHatEnd end=/^\s\s*}\s*$/ contains=sdHatName,@sdEntry,sdComment,sdError,sdInclude

View File

@@ -0,0 +1,9 @@
# ------------------------------------------------------------------
#
# Copyright (C) 2011-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
# License published by the Free Software Foundation.
#
# ------------------------------------------------------------------

567
utils/apparmor/easyprof.py Normal file
View File

@@ -0,0 +1,567 @@
# ------------------------------------------------------------------
#
# Copyright (C) 2011-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
# License published by the Free Software Foundation.
#
# ------------------------------------------------------------------
import codecs
import glob
import optparse
import os
import re
import subprocess
import sys
import tempfile
#
# TODO: move this out to the common library
#
#from apparmor import AppArmorException
class AppArmorException(Exception):
'''This class represents AppArmor exceptions'''
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
#
# End common
#
DEBUGGING = False
#
# TODO: move this out to a utilities library
#
def error(out, exit_code=1, do_exit=True):
'''Print error message and exit'''
try:
print >> sys.stderr, "ERROR: %s" % (out)
except IOError:
pass
if do_exit:
sys.exit(exit_code)
def warn(out):
'''Print warning message'''
try:
print >> sys.stderr, "WARN: %s" % (out)
except IOError:
pass
def msg(out, output=sys.stdout):
'''Print message'''
try:
print >> output, "%s" % (out)
except IOError:
pass
def cmd(command):
'''Try to execute the given command.'''
debug(command)
try:
sp = subprocess.Popen(command, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
except OSError, ex:
return [127, str(ex)]
out = sp.communicate()[0]
return [sp.returncode, out]
def cmd_pipe(command1, command2):
'''Try to pipe command1 into command2.'''
try:
sp1 = subprocess.Popen(command1, stdout=subprocess.PIPE)
sp2 = subprocess.Popen(command2, stdin=sp1.stdout)
except OSError, ex:
return [127, str(ex)]
out = sp2.communicate()[0]
return [sp2.returncode, out]
def debug(out):
'''Print debug message'''
if DEBUGGING:
try:
print >> sys.stderr, "DEBUG: %s" % (out)
except IOError:
pass
def valid_binary_path(path):
'''Validate name'''
try:
a_path = os.path.abspath(path)
except Exception:
debug("Could not find absolute path for binary")
return False
if path != a_path:
debug("Binary should use a normalized absolute path")
return False
if not os.path.exists(a_path):
return True
r_path = os.path.realpath(path)
if r_path != a_path:
debug("Binary should not be a symlink")
return False
return True
def valid_variable_name(var):
'''Validate variable name'''
if re.search(r'[a-zA-Z0-9_]+$', var):
return True
return False
def valid_path(path):
'''Valid path'''
# No relative paths
m = "Invalid path: %s" % (path)
if not path.startswith('/'):
debug("%s (relative)" % (m))
return False
try:
os.path.normpath(path)
except Exception:
debug("%s (could not normalize)" % (m))
return False
return True
def get_directory_contents(path):
'''Find contents of the given directory'''
if not valid_path(path):
return None
files = []
for f in glob.glob(path + "/*"):
files.append(f)
files.sort()
return files
def open_file_read(path):
'''Open specified file read-only'''
try:
orig = codecs.open(path, 'r', "UTF-8")
except Exception:
raise
return orig
def verify_policy(policy):
'''Verify policy compiles'''
exe = "/sbin/apparmor_parser"
if not os.path.exists(exe):
rc, exe = cmd(['which', 'apparmor_parser'])
if rc != 0:
warn("Could not find apparmor_parser. Skipping verify")
return True
fn = ""
# if policy starts with '/' and is one line, assume it is a path
if len(policy.splitlines()) == 1 and valid_path(policy):
fn = policy
else:
f, fn = tempfile.mkstemp(prefix='aa-easyprof')
os.write(f, policy)
os.close(f)
rc, out = cmd([exe, '-p', fn])
os.unlink(fn)
if rc == 0:
return True
return False
#
# End utility functions
#
class AppArmorEasyProfile:
'''Easy profile class'''
def __init__(self, binary, opt):
self.conffile = "/etc/apparmor/easyprof.conf"
if opt.conffile:
self.conffile = os.path.abspath(opt.conffile)
self.dirs = dict()
if os.path.isfile(self.conffile):
self._get_defaults()
if opt.templates_dir and os.path.isdir(opt.templates_dir):
self.dirs['templates'] = os.path.abspath(opt.templates_dir)
elif not opt.templates_dir and \
opt.template and \
os.path.isfile(opt.template) and \
valid_path(opt.template):
# If we specified the template and it is an absolute path, just set
# the templates directory to the parent of the template so we don't
# have to require --template-dir with absolute paths.
self.dirs['templates'] = os.path.abspath(os.path.dirname(opt.template))
if opt.policy_groups_dir and os.path.isdir(opt.policy_groups_dir):
self.dirs['policygroups'] = os.path.abspath(opt.policy_groups_dir)
if not self.dirs.has_key('templates'):
raise AppArmorException("Could not find templates directory")
if not self.dirs.has_key('policygroups'):
raise AppArmorException("Could not find policygroups directory")
self.aa_topdir = "/etc/apparmor.d"
self.binary = binary
if binary != None:
if not valid_binary_path(binary):
raise AppArmorException("Invalid path for binary: '%s'" % binary)
self.set_template(opt.template)
self.set_policygroup(opt.policy_groups)
if opt.name:
self.set_name(opt.name)
elif self.binary != None:
self.set_name(self.binary)
self.templates = get_directory_contents(self.dirs['templates'])
self.policy_groups = get_directory_contents(self.dirs['policygroups'])
def _get_defaults(self):
'''Read in defaults from configuration'''
if not os.path.exists(self.conffile):
raise AppArmorException("Could not find '%s'" % self.conffile)
# Read in the configuration
f = open_file_read(self.conffile)
pat = re.compile(r'^\w+=".*"?')
for line in f:
if not pat.search(line):
continue
if line.startswith("POLICYGROUPS_DIR="):
d = re.split(r'=', line.strip())[1].strip('["\']')
self.dirs['policygroups'] = d
elif line.startswith("TEMPLATES_DIR="):
d = re.split(r'=', line.strip())[1].strip('["\']')
self.dirs['templates'] = d
f.close()
keys = self.dirs.keys()
if 'templates' not in keys:
raise AppArmorException("Could not find TEMPLATES_DIR in '%s'" % self.conffile)
if 'policygroups' not in keys:
raise AppArmorException("Could not find POLICYGROUPS_DIR in '%s'" % self.conffile)
for k in self.dirs.keys():
if not os.path.isdir(self.dirs[k]):
raise AppArmorException("Could not find '%s'" % self.dirs[k])
def set_name(self, name):
'''Set name of policy'''
self.name = name
def get_template(self):
'''Get contents of current template'''
return open(self.template).read()
def set_template(self, template):
'''Set current template'''
self.template = template
if not template.startswith('/'):
self.template = os.path.join(self.dirs['templates'], template)
if not os.path.exists(self.template):
raise AppArmorException('%s does not exist' % (self.template))
def get_templates(self):
'''Get list of all available templates by filename'''
return self.templates
def get_policygroup(self, policygroup):
'''Get contents of specific policygroup'''
p = policygroup
if not p.startswith('/'):
p = os.path.join(self.dirs['policygroups'], p)
if self.policy_groups == None or not p in self.policy_groups:
raise AppArmorException("Policy group '%s' does not exist" % p)
return open(p).read()
def set_policygroup(self, policygroups):
'''Set policygroups'''
self.policy_groups = []
if policygroups != None:
for p in policygroups.split(','):
if not p.startswith('/'):
p = os.path.join(self.dirs['policygroups'], p)
if not os.path.exists(p):
raise AppArmorException('%s does not exist' % (p))
self.policy_groups.append(p)
def get_policy_groups(self):
'''Get list of all policy groups by filename'''
return self.policy_groups
def gen_abstraction_rule(self, abstraction):
'''Generate an abstraction rule'''
p = os.path.join(self.aa_topdir, "abstractions", abstraction)
if not os.path.exists(p):
raise AppArmorException("%s does not exist" % p)
return "#include <abstractions/%s>" % abstraction
def gen_variable_declaration(self, dec):
'''Generate a variable declaration'''
if not re.search(r'^@\{[a-zA-Z_]+\}=.+', dec):
raise AppArmorException("Invalid variable declaration '%s'" % dec)
return dec
def gen_path_rule(self, path, access):
rule = []
if not path.startswith('/') and not path.startswith('@'):
raise AppArmorException("'%s' should not be relative path" % path)
owner = ""
if path.startswith('/home/') or path.startswith("@{HOME"):
owner = "owner "
if path.endswith('/'):
rule.append("%s %s," % (path, access))
rule.append("%s%s** %s," % (owner, path, access))
elif path.endswith('/**') or path.endswith('/*'):
rule.append("%s %s," % (os.path.dirname(path), access))
rule.append("%s%s %s," % (owner, path, access))
else:
rule.append("%s%s %s," % (owner, path, access))
return rule
def gen_policy(self, name, binary, template_var=[], abstractions=None, policy_groups=None, read_path=[], write_path=[], author=None, comment=None, copyright=None):
def find_prefix(t, s):
'''Calculate whitespace prefix based on occurrence of s in t'''
pat = re.compile(r'^ *%s' % s)
p = ""
for line in t.splitlines():
if pat.match(line):
p = " " * (len(line) - len(line.lstrip()))
break
return p
policy = self.get_template()
if '###ENDUSAGE###' in policy:
found = False
tmp = ""
for line in policy.splitlines():
if not found:
if line.startswith('###ENDUSAGE###'):
found = True
continue
tmp += line + "\n"
policy = tmp
# Fill-in profile name and binary
policy = re.sub(r'###NAME###', name, policy)
policy = re.sub(r'###BINARY###', binary, policy)
# Fill-in various comment fields
if comment != None:
policy = re.sub(r'###COMMENT###', "Comment: %s" % comment, policy)
if author != None:
policy = re.sub(r'###AUTHOR###', "Author: %s" % author, policy)
if copyright != None:
policy = re.sub(r'###COPYRIGHT###', "Copyright: %s" % copyright, policy)
# Fill-in rules and variables with proper indenting
search = '###ABSTRACTIONS###'
prefix = find_prefix(policy, search)
s = "%s# No abstractions specified" % prefix
if abstractions != None:
s = "%s# Specified abstractions" % (prefix)
for i in abstractions.split(','):
s += "\n%s%s" % (prefix, self.gen_abstraction_rule(i))
policy = re.sub(r' *%s' % search, s, policy)
search = '###POLICYGROUPS###'
prefix = find_prefix(policy, search)
s = "%s# No policy groups specified" % prefix
if policy_groups != None:
s = "%s# Rules specified via policy groups" % (prefix)
for i in policy_groups.split(','):
for line in self.get_policygroup(i).splitlines():
s += "\n%s%s" % (prefix, line)
if i != policy_groups.split(',')[-1]:
s += "\n"
policy = re.sub(r' *%s' % search, s, policy)
search = '###VAR###'
prefix = find_prefix(policy, search)
s = "%s# No template variables specified" % prefix
if len(template_var) > 0:
s = "%s# Specified profile variables" % (prefix)
for i in template_var:
s += "\n%s%s" % (prefix, self.gen_variable_declaration(i))
policy = re.sub(r' *%s' % search, s, policy)
search = '###READS###'
prefix = find_prefix(policy, search)
s = "%s# No read paths specified" % prefix
if len(read_path) > 0:
s = "%s# Specified read permissions" % (prefix)
for i in read_path:
for r in self.gen_path_rule(i, 'r'):
s += "\n%s%s" % (prefix, r)
policy = re.sub(r' *%s' % search, s, policy)
search = '###WRITES###'
prefix = find_prefix(policy, search)
s = "%s# No write paths specified" % prefix
if len(write_path) > 0:
s = "%s# Specified write permissions" % (prefix)
for i in write_path:
for r in self.gen_path_rule(i, 'rwk'):
s += "\n%s%s" % (prefix, r)
policy = re.sub(r' *%s' % search, s, policy)
if not verify_policy(policy):
debug("\n" + policy)
raise AppArmorException("Invalid policy")
return policy
def print_basefilenames(files):
for i in files:
print "%s" % (os.path.basename(i))
def print_files(files):
for i in files:
print open(i).read()
def parse_args(args=None):
'''Parse arguments'''
global DEBUGGING
parser = optparse.OptionParser()
parser.add_option("-c", "--config-file",
dest="conffile",
help="Use alternate configuration file",
metavar="FILE")
parser.add_option("-d", "--debug",
help="Show debugging output",
action='store_true',
default=False)
parser.add_option("-t", "--template",
dest="template",
help="Use non-default policy template",
metavar="TEMPLATE",
default='default')
parser.add_option("--list-templates",
help="List available templates",
action='store_true',
default=False)
parser.add_option("--templates-dir",
dest="templates_dir",
help="Use non-default templates directory",
metavar="DIR")
parser.add_option("--show-template",
help="Show specified template",
action='store_true',
default=False)
parser.add_option("-p", "--policy-groups",
help="Comma-separated list of policy groups",
metavar="POLICYGROUPS")
parser.add_option("--list-policy-groups",
help="List available policy groups",
action='store_true',
default=False)
parser.add_option("--policy-groups-dir",
dest="policy_groups_dir",
help="Use non-default policy-groups directory",
metavar="DIR")
parser.add_option("--show-policy-group",
help="Show specified policy groups",
action='store_true',
default=False)
parser.add_option("-a", "--abstractions",
dest="abstractions",
help="Comma-separated list of abstractions",
metavar="ABSTRACTIONS")
parser.add_option("--read-path",
dest="read_path",
help="Path allowing owner reads",
metavar="PATH",
action="append")
parser.add_option("--write-path",
dest="write_path",
help="Path allowing owner writes",
metavar="PATH",
action="append")
parser.add_option("-n", "--name",
dest="name",
help="Name of policy",
metavar="NAME")
parser.add_option("--comment",
dest="comment",
help="Comment for policy",
metavar="COMMENT")
parser.add_option("--author",
dest="author",
help="Author of policy",
metavar="COMMENT")
parser.add_option("--copyright",
dest="copyright",
help="Copyright for policy",
metavar="COMMENT")
parser.add_option("--template-var",
dest="template_var",
help="Declare AppArmor variable",
metavar="@{VARIABLE}=VALUE",
action="append")
(my_opt, my_args) = parser.parse_args(args)
if my_opt.debug:
DEBUGGING = True
return (my_opt, my_args)
def gen_policy_params(binary, opt):
'''Generate parameters for gen_policy'''
params = dict(binary=binary)
if opt.name:
params['name'] = opt.name
else:
params['name'] = os.path.basename(binary)
if opt.template_var: # What about specified multiple times?
params['template_var'] = opt.template_var
if opt.abstractions:
params['abstractions'] = opt.abstractions
if opt.policy_groups:
params['policy_groups'] = opt.policy_groups
if opt.read_path:
params['read_path'] = opt.read_path
if opt.write_path:
params['write_path'] = opt.write_path
if opt.abstractions:
params['abstractions'] = opt.abstractions
if opt.comment:
params['comment'] = opt.comment
if opt.author:
params['author'] = opt.author
if opt.copyright:
params['copyright'] = opt.copyright
return params

44
utils/easyprof/README Normal file
View File

@@ -0,0 +1,44 @@
AppArmor Easy Profiler
----------------------
aa-easyprof is a standalone CLI application which can also be imported into
developer SDKs. See test/test-aa-easyprof.py for an example of how to import
this into your SDK.
Templates
---------
Any number of templates can be used. The user may specify one on the command
line or use a system-wide template from /usr/share/apparmor/easyprof/templates.
Currently the combination of the user-application and the opt-application and
user-application policygroups should achieve a working policy for Ubuntu's
Application Review Board:
- http://developer.ubuntu.com/publish/my-apps-packages/
Eg:
$ aa-easyprof --template=user-application \
--template-var="@{APPNAME}=foo" \
--policy-groups=opt-application,user-application \
/opt/foo/bin/foo
Testing
-------
Unit tests:
$ ./test/test-aa-easyprof.py
In source manual testing:
$ ./aa-easyprof --templates-dir=./easyprof/templates \
--policy-groups-dir=./easyprof/policygroups \
... \
/opt/foo/bin/foo
Post-install manual testing:
$ make DESTDIR=/tmp/test PERLDIR=/tmp/test/usr/share/perl5/Immunix install
$ cd /tmp/test
$ PYTHONPATH=/tmp/test/usr/local/.../dist-packages ./usr/bin/aa-easyprof \
--templates-dir=/tmp/test/usr/share/apparmor/easyprof/templates \
--policy-groups-dir=/tmp/test/usr/share/apparmor/easyprof/policygroups \
/opt/bin/foo
(you may also adjust /tmp/test/etc/apparmor/easyprof.conf to avoid specifying
--templates-dir and --policy-groups-dir).

View File

@@ -0,0 +1,5 @@
# Location of system policygroups
POLICYGROUPS_DIR="/usr/share/apparmor/easyprof/policygroups"
# Location of system templates
TEMPLATES_DIR="/usr/share/apparmor/easyprof/templates"

View File

@@ -0,0 +1,2 @@
# Policygroup to allow networking
#include <abstractions/nameservice>

View File

@@ -0,0 +1,3 @@
# Policy group for applications installed in /opt
/opt/@{APPNAME}/ r,
/opt/@{APPNAME}/** mrk,

View File

@@ -0,0 +1,8 @@
# Policy group allowing various writes to standard directories in @{HOMEDIRS}
#include <abstractions/xdg-desktop>
owner @{HOMEDIRS}/.cache/@{APPNAME}/ rw,
owner @{HOMEDIRS}/.cache/@{APPNAME}/** rwkl,
owner @{HOMEDIRS}/.config/@{APPNAME}/ rw,
owner @{HOMEDIRS}/.config/@{APPNAME}/** rwkl,
owner @{HOMEDIRS}/.local/share/@{APPNAME}/ rw,
owner @{HOMEDIRS}/.local/share/@{APPNAME}/** rwkl,

View File

@@ -0,0 +1,26 @@
#
# Example usage:
# $ aa-easyprof --policy-groups=user-application /usr/bin/foo
#
###ENDUSAGE###
# vim:syntax=apparmor
# AppArmor policy for ###NAME###
# ###AUTHOR###
# ###COPYRIGHT###
# ###COMMENT###
#include <tunables/global>
###VAR###
###BINARY### {
#include <abstractions/base>
###ABSTRACTIONS###
###POLICYGROUPS###
###READS###
###WRITES###
}

View File

@@ -0,0 +1,29 @@
#
# Example usage for a program named 'foo' which is installed in /opt/foo
# $ aa-easyprof --template=user-application \
# --template-var="@{APPNAME}=foo" \
# --policy-groups=opt-application,user-application \
# /opt/foo/bin/foo
#
###ENDUSAGE###
# vim:syntax=apparmor
# AppArmor policy for ###NAME###
# ###AUTHOR###
# ###COPYRIGHT###
# ###COMMENT###
#include <tunables/global>
###VAR###
###BINARY### {
#include <abstractions/base>
###ABSTRACTIONS###
###POLICYGROUPS###
###READS###
###WRITES###
}

View File

@@ -0,0 +1,79 @@
# ----------------------------------------------------------------------
# Copyright (c) 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
# 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 Canonical, Ltd.
# ----------------------------------------------------------------------
#
# Usage:
# $ python ./python-tools-setup.py install --root=... --version=...
#
# Note: --version=... must be the last argument to this script
#
from distutils.command.install import install as _install
from distutils.core import setup
import os
import shutil
import sys
class Install(_install, object):
'''Override distutils to install the files where we want them.'''
def run(self):
# Now byte-compile everything
super(Install, self).run()
prefix = self.prefix
if self.root != None:
prefix = self.root
# Install scripts, configuration files and data
scripts = ['/usr/bin/aa-easyprof']
self.mkpath(prefix + os.path.dirname(scripts[0]))
for s in scripts:
self.copy_file(os.path.basename(s), prefix + s)
configs = ['easyprof/easyprof.conf']
self.mkpath(prefix + "/etc/apparmor")
for c in configs:
self.copy_file(c, os.path.join(prefix + "/etc/apparmor", os.path.basename(c)))
data = ['easyprof/templates', 'easyprof/policygroups']
self.mkpath(prefix + "/usr/share/apparmor/easyprof")
for d in data:
self.copy_tree(d, os.path.join(prefix + "/usr/share/apparmor/easyprof", os.path.basename(d)))
if os.path.exists('staging'):
shutil.rmtree('staging')
shutil.copytree('apparmor', 'staging')
# Support the --version=... since this will be part of a Makefile
version = "unknown-version"
if "--version=" in sys.argv[-1]:
version=sys.argv[-1].split('=')[1]
sys.argv = sys.argv[0:-1]
setup (name='apparmor',
version=version,
description='Python libraries for AppArmor utilities',
long_description='Python libraries for AppArmor utilities',
author='AppArmor Developers',
author_email='apparmor@lists.ubuntu.com',
url='https://launchpad.net/apparmor',
license='GPL-2',
cmdclass={'install': Install},
package_dir={'apparmor': 'staging'},
py_modules=['apparmor.easyprof']
)
shutil.rmtree('staging')

View File

@@ -14,9 +14,12 @@
CAP_SYS_MODULE 10
CAP_SYS_PTRACE 10
CAP_SYS_RAWIO 10
CAP_MAC_ADMIN 10
CAP_MAC_OVERRIDE 10
# Allow other processes to 0wn the machine:
CAP_SETPCAP 9
CAP_CHOWN 9
CAP_SETFCAP 9
CAP_CHOWN 9
CAP_FSETID 9
CAP_MKNOD 9
CAP_LINUX_IMMUTABLE 9
@@ -38,9 +41,11 @@
CAP_LEASE 8
CAP_IPC_LOCK 8
CAP_SYS_TTY_CONFIG 8
CAP_DAC_READ_SEARCH 7
CAP_AUDIT_CONTROL 8
CAP_AUDIT_WRITE 8
CAP_SYSLOG 8
CAP_WAKE_ALARM 8
CAP_DAC_READ_SEARCH 7
# unused
CAP_NET_BROADCAST 0

5
utils/test/easyprof.conf Normal file
View File

@@ -0,0 +1,5 @@
# Location of system policygroups
POLICYGROUPS_DIR="./policygroups"
# Location of system templates
TEMPLATES_DIR="./templates"

882
utils/test/test-aa-easyprof.py Executable file
View File

@@ -0,0 +1,882 @@
#! /usr/bin/env python
# ------------------------------------------------------------------
#
# Copyright (C) 2011-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
# License published by the Free Software Foundation.
#
# ------------------------------------------------------------------
import glob
import os
import shutil
import sys
import tempfile
import unittest
topdir = None
debugging = False
def recursive_rm(dirPath, contents_only=False):
'''recursively remove directory'''
names = os.listdir(dirPath)
for name in names:
path = os.path.join(dirPath, name)
if os.path.islink(path) or not os.path.isdir(path):
os.unlink(path)
else:
recursive_rm(path)
if contents_only == False:
os.rmdir(dirPath)
#
# Our test class
#
class T(unittest.TestCase):
def setUp(self):
'''Setup for tests'''
global topdir
self.tmpdir = tempfile.mkdtemp(prefix='test-aa-easyprof')
# Copy everything into place
for d in ['easyprof/policygroups', 'easyprof/templates']:
shutil.copytree(os.path.join(topdir, d), os.path.join(self.tmpdir, os.path.basename(d)))
# Create a test template
self.test_template = "test-template"
contents = '''# vim:syntax=apparmor
# %s
# AppArmor policy for ###NAME###
# ###AUTHOR###
# ###COPYRIGHT###
# ###COMMENT###
#include <tunables/global>
###VAR###
###BINARY### {
#include <abstractions/base>
###ABSTRACTIONS###
###POLICYGROUPS###
###READS###
###WRITES###
}
''' % (self.test_template)
open(os.path.join(self.tmpdir, 'templates', self.test_template), 'w').write(contents)
# Create a test policygroup
self.test_policygroup = "test-policygroup"
contents = '''
# %s
#include <abstractions/gnome>
#include <abstractions/nameservice>
''' % (self.test_policygroup)
open(os.path.join(self.tmpdir, 'policygroups', self.test_policygroup), 'w').write(contents)
# setup our conffile
self.conffile = os.path.join(self.tmpdir, 'easyprof.conf')
contents = '''
POLICYGROUPS_DIR="%s/policygroups"
TEMPLATES_DIR="%s/templates"
''' % (self.tmpdir, self.tmpdir)
open(self.conffile, 'w').write(contents)
self.binary = "/opt/bin/foo"
self.full_args = ['-c', self.conffile, self.binary]
if debugging:
self.full_args.append('-d')
(self.options, self.args) = easyprof.parse_args(self.full_args + [self.binary])
def tearDown(self):
'''Teardown for tests'''
if os.path.exists(self.tmpdir):
recursive_rm(self.tmpdir)
#
# config file tests
#
def test_configuration_file_p_invalid(self):
'''Test config parsing (invalid POLICYGROUPS_DIR)'''
contents = '''
POLICYGROUPS_DIR=
TEMPLATES_DIR="%s/templates"
''' % (self.tmpdir)
open(self.conffile, 'w').write(contents)
try:
easyprof.AppArmorEasyProfile(self.binary, self.options)
except easyprof.AppArmorException:
return
except Exception:
raise
raise Exception ("File should have been invalid")
def test_configuration_file_p_empty(self):
'''Test config parsing (empty POLICYGROUPS_DIR)'''
contents = '''
POLICYGROUPS_DIR="%s"
TEMPLATES_DIR="%s/templates"
''' % ('', self.tmpdir)
open(self.conffile, 'w').write(contents)
try:
easyprof.AppArmorEasyProfile(self.binary, self.options)
except easyprof.AppArmorException:
return
except Exception:
raise
raise Exception ("File should have been invalid")
def test_configuration_file_p_nonexistent(self):
'''Test config parsing (nonexistent POLICYGROUPS_DIR)'''
contents = '''
POLICYGROUPS_DIR="%s/policygroups"
TEMPLATES_DIR="%s/templates"
''' % ('/nonexistent', self.tmpdir)
open(self.conffile, 'w').write(contents)
try:
easyprof.AppArmorEasyProfile(self.binary, self.options)
except easyprof.AppArmorException:
return
except Exception:
raise
raise Exception ("File should have been invalid")
def test_policygroups_dir_relative(self):
'''Test --policy-groups-dir (relative DIR)'''
os.chdir(self.tmpdir)
rel = os.path.join(self.tmpdir, 'relative')
os.mkdir(rel)
shutil.copy(os.path.join(self.tmpdir, 'policygroups', self.test_policygroup), os.path.join(rel, self.test_policygroup))
args = self.full_args
args += ['--policy-groups-dir', './relative', '--show-policy-group', '--policy-groups=%s' % self.test_policygroup]
(self.options, self.args) = easyprof.parse_args(args)
easyp = easyprof.AppArmorEasyProfile(self.binary, self.options)
# no fallback
self.assertTrue(easyp.dirs['policygroups'] == rel, "Not using specified --policy-groups-dir")
self.assertFalse(easyp.get_policy_groups() == None, "Could not find policy-groups")
def test_policygroups_dir_nonexistent(self):
'''Test --policy-groups-dir (nonexistent DIR)'''
os.chdir(self.tmpdir)
rel = os.path.join(self.tmpdir, 'nonexistent')
args = self.full_args
args += ['--policy-groups-dir', rel, '--show-policy-group', '--policy-groups=%s' % self.test_policygroup]
(self.options, self.args) = easyprof.parse_args(args)
easyp = easyprof.AppArmorEasyProfile(self.binary, self.options)
# test if using fallback
self.assertFalse(easyp.dirs['policygroups'] == rel, "Using nonexistent --policy-groups-dir")
# test fallback
self.assertTrue(easyp.get_policy_groups() != None, "Found policy-groups when shouldn't have")
def test_policygroups_dir_valid(self):
'''Test --policy-groups-dir (valid DIR)'''
os.chdir(self.tmpdir)
valid = os.path.join(self.tmpdir, 'valid')
os.mkdir(valid)
shutil.copy(os.path.join(self.tmpdir, 'policygroups', self.test_policygroup), os.path.join(valid, self.test_policygroup))
args = self.full_args
args += ['--policy-groups-dir', valid, '--show-policy-group', '--policy-groups=%s' % self.test_policygroup]
(self.options, self.args) = easyprof.parse_args(args)
easyp = easyprof.AppArmorEasyProfile(self.binary, self.options)
# no fallback
self.assertTrue(easyp.dirs['policygroups'] == valid, "Not using specified --policy-groups-dir")
self.assertFalse(easyp.get_policy_groups() == None, "Could not find policy-groups")
def test_configuration_file_t_invalid(self):
'''Test config parsing (invalid TEMPLATES_DIR)'''
contents = '''
TEMPLATES_DIR=
POLICYGROUPS_DIR="%s/templates"
''' % (self.tmpdir)
open(self.conffile, 'w').write(contents)
try:
easyprof.AppArmorEasyProfile(self.binary, self.options)
except easyprof.AppArmorException:
return
except Exception:
raise
raise Exception ("File should have been invalid")
def test_configuration_file_t_empty(self):
'''Test config parsing (empty TEMPLATES_DIR)'''
contents = '''
TEMPLATES_DIR="%s"
POLICYGROUPS_DIR="%s/templates"
''' % ('', self.tmpdir)
open(self.conffile, 'w').write(contents)
try:
easyprof.AppArmorEasyProfile(self.binary, self.options)
except easyprof.AppArmorException:
return
except Exception:
raise
raise Exception ("File should have been invalid")
def test_configuration_file_t_nonexistent(self):
'''Test config parsing (nonexistent TEMPLATES_DIR)'''
contents = '''
TEMPLATES_DIR="%s/policygroups"
POLICYGROUPS_DIR="%s/templates"
''' % ('/nonexistent', self.tmpdir)
open(self.conffile, 'w').write(contents)
try:
easyprof.AppArmorEasyProfile(self.binary, self.options)
except easyprof.AppArmorException:
return
except Exception:
raise
raise Exception ("File should have been invalid")
def test_templates_dir_relative(self):
'''Test --templates-dir (relative DIR)'''
os.chdir(self.tmpdir)
rel = os.path.join(self.tmpdir, 'relative')
os.mkdir(rel)
shutil.copy(os.path.join(self.tmpdir, 'templates', self.test_template), os.path.join(rel, self.test_template))
args = self.full_args
args += ['--templates-dir', './relative', '--show-template', '--template=%s' % self.test_template]
(self.options, self.args) = easyprof.parse_args(args)
easyp = easyprof.AppArmorEasyProfile(self.binary, self.options)
# no fallback
self.assertTrue(easyp.dirs['templates'] == rel, "Not using specified --template-dir")
self.assertFalse(easyp.get_templates() == None, "Could not find templates")
def test_templates_dir_nonexistent(self):
'''Test --templates-dir (nonexistent DIR)'''
os.chdir(self.tmpdir)
rel = os.path.join(self.tmpdir, 'nonexistent')
args = self.full_args
args += ['--templates-dir', rel, '--show-template', '--template=%s' % self.test_template]
(self.options, self.args) = easyprof.parse_args(args)
easyp = easyprof.AppArmorEasyProfile(self.binary, self.options)
# test if using fallback
self.assertFalse(easyp.dirs['templates'] == rel, "Using nonexistent --template-dir")
# test fallback
self.assertTrue(easyp.get_templates() != None, "Found templates when shouldn't have")
def test_templates_dir_valid(self):
'''Test --templates-dir (valid DIR)'''
os.chdir(self.tmpdir)
valid = os.path.join(self.tmpdir, 'valid')
os.mkdir(valid)
shutil.copy(os.path.join(self.tmpdir, 'templates', self.test_template), os.path.join(valid, self.test_template))
args = self.full_args
args += ['--templates-dir', valid, '--show-template', '--template=%s' % self.test_template]
(self.options, self.args) = easyprof.parse_args(args)
easyp = easyprof.AppArmorEasyProfile(self.binary, self.options)
# no fallback
self.assertTrue(easyp.dirs['templates'] == valid, "Not using specified --template-dir")
self.assertFalse(easyp.get_templates() == None, "Could not find templates")
#
# Binary file tests
#
def test_binary(self):
'''Test binary'''
easyprof.AppArmorEasyProfile('/bin/ls', self.options)
def test_binary_nonexistent(self):
'''Test binary (nonexistent)'''
easyprof.AppArmorEasyProfile(os.path.join(self.tmpdir, 'nonexistent'), self.options)
def test_binary_relative(self):
'''Test binary (relative)'''
try:
easyprof.AppArmorEasyProfile('./foo', self.options)
except easyprof.AppArmorException:
return
except Exception:
raise
raise Exception ("Binary should have been invalid")
def test_binary_symlink(self):
'''Test binary (symlink)'''
exe = os.path.join(self.tmpdir, 'exe')
open(exe, 'wa').close()
symlink = exe + ".lnk"
os.symlink(exe, symlink)
try:
easyprof.AppArmorEasyProfile(symlink, self.options)
except easyprof.AppArmorException:
return
except Exception:
raise
raise Exception ("Binary should have been invalid")
#
# Templates tests
#
def test_templates_list(self):
'''Test templates (list)'''
args = self.full_args
args.append('--list-templates')
(self.options, self.args) = easyprof.parse_args(args)
easyp = easyprof.AppArmorEasyProfile(None, self.options)
for i in easyp.get_templates():
self.assertTrue(os.path.exists(i), "Could not find '%s'" % i)
def test_templates_show(self):
'''Test templates (show)'''
files = []
for f in glob.glob("%s/templates/*" % self.tmpdir):
files.append(f)
for f in files:
args = self.full_args
args += ['--show-template', '--template', f]
(self.options, self.args) = easyprof.parse_args(args)
easyp = easyprof.AppArmorEasyProfile(None, self.options)
path = os.path.join(easyp.dirs['templates'], f)
self.assertTrue(os.path.exists(path), "Could not find '%s'" % path)
open(path).read()
#
# Policygroups tests
#
def test_policygroups_list(self):
'''Test policygroups (list)'''
args = self.full_args
args.append('--list-policy-groups')
(self.options, self.args) = easyprof.parse_args(args)
easyp = easyprof.AppArmorEasyProfile(None, self.options)
for i in easyp.get_templates():
self.assertTrue(os.path.exists(i), "Could not find '%s'" % i)
def test_policygroups_show(self):
'''Test policygroups (show)'''
files = []
for f in glob.glob("%s/policygroups/*" % self.tmpdir):
files.append(f)
for f in files:
args = self.full_args
args += ['--show-template', '--template', f]
(self.options, self.args) = easyprof.parse_args(args)
easyp = easyprof.AppArmorEasyProfile(None, self.options)
path = os.path.join(easyp.dirs['policygroups'], f)
self.assertTrue(os.path.exists(path), "Could not find '%s'" % path)
open(path).read()
#
# Test genpolicy
#
def _gen_policy(self, name=None, template=None, extra_args=[]):
'''Generate a policy'''
# Build up our args
args = self.full_args
if template == None:
args.append('--template=%s' % self.test_template)
else:
args.append('--template=%s' % template)
if name != None:
args.append('--name=%s' % name)
if len(extra_args) > 0:
args += extra_args
args.append(self.binary)
# Now parse our args
(self.options, self.args) = easyprof.parse_args(args)
easyp = easyprof.AppArmorEasyProfile(self.binary, self.options)
params = easyprof.gen_policy_params(self.binary, self.options)
p = easyp.gen_policy(**params)
# We always need to check for these
search_terms = [self.binary]
if name != None:
search_terms.append(name)
if template == None:
search_terms.append(self.test_template)
for s in search_terms:
self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p))
# ###NAME### should be replaced with self.binary or 'name'. Check for that
inv_s = '###NAME###'
self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p))
if debugging:
print p
return p
def test_genpolicy_templates_abspath(self):
'''Test genpolicy (abspath to template)'''
# create a new template
template = os.path.join(self.tmpdir, "test-abspath-template")
shutil.copy(os.path.join(self.tmpdir, 'templates', self.test_template), template)
contents = open(template).read()
test_string = "#teststring"
open(template, 'w').write(contents + "\n%s\n" % test_string)
p = self._gen_policy(template=template)
for s in [self.test_template, test_string]:
self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p))
def test_genpolicy_templates_system(self):
'''Test genpolicy (system template)'''
self._gen_policy()
def test_genpolicy_templates_nonexistent(self):
'''Test genpolicy (nonexistent template)'''
try:
self._gen_policy(template=os.path.join(self.tmpdir, "/nonexistent"))
except easyprof.AppArmorException:
return
except Exception:
raise
raise Exception ("template should be invalid")
def test_genpolicy_name(self):
'''Test genpolicy (name)'''
self._gen_policy(name='test-foo')
def test_genpolicy_comment(self):
'''Test genpolicy (comment)'''
s = "test comment"
p = self._gen_policy(extra_args=['--comment=%s' % s])
self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p))
inv_s = '###COMMENT###'
self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p))
def test_genpolicy_author(self):
'''Test genpolicy (author)'''
s = "Archibald Poindexter"
p = self._gen_policy(extra_args=['--author=%s' % s])
self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p))
inv_s = '###AUTHOR###'
self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p))
def test_genpolicy_copyright(self):
'''Test genpolicy (copyright)'''
s = "2112/01/01"
p = self._gen_policy(extra_args=['--copyright=%s' % s])
self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p))
inv_s = '###COPYRIGHT###'
self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p))
def test_genpolicy_abstractions(self):
'''Test genpolicy (single abstraction)'''
s = "nameservice"
p = self._gen_policy(extra_args=['--abstractions=%s' % s])
search = "#include <abstractions/%s>" % s
self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p))
inv_s = '###ABSTRACTIONS###'
self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p))
def test_genpolicy_abstractions_multiple(self):
'''Test genpolicy (multiple abstractions)'''
abstractions = "authentication,X,user-tmp"
p = self._gen_policy(extra_args=['--abstractions=%s' % abstractions])
for s in abstractions.split(','):
search = "#include <abstractions/%s>" % s
self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p))
inv_s = '###ABSTRACTIONS###'
self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p))
def test_genpolicy_policygroups(self):
'''Test genpolicy (single policygroup)'''
groups = self.test_policygroup
p = self._gen_policy(extra_args=['--policy-groups=%s' % groups])
for s in ['#include <abstractions/nameservice>', '#include <abstractions/gnome>']:
self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p))
inv_s = '###POLICYGROUPS###'
self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p))
def test_genpolicy_policygroups_multiple(self):
'''Test genpolicy (multiple policygroups)'''
test_policygroup2 = "test-policygroup2"
contents = '''
# %s
#include <abstractions/kde>
#include <abstractions/openssl>
''' % (self.test_policygroup)
open(os.path.join(self.tmpdir, 'policygroups', test_policygroup2), 'w').write(contents)
groups = "%s,%s" % (self.test_policygroup, test_policygroup2)
p = self._gen_policy(extra_args=['--policy-groups=%s' % groups])
for s in ['#include <abstractions/nameservice>',
'#include <abstractions/gnome>',
'#include <abstractions/kde>',
'#include <abstractions/openssl>']:
self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p))
inv_s = '###POLICYGROUPS###'
self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p))
def test_genpolicy_policygroups_nonexistent(self):
'''Test genpolicy (nonexistent policygroup)'''
try:
self._gen_policy(extra_args=['--policy-groups=nonexistent'])
except easyprof.AppArmorException:
return
except Exception:
raise
raise Exception ("policygroup should be invalid")
def test_genpolicy_readpath_file(self):
'''Test genpolicy (read-path file)'''
s = "/opt/test-foo"
p = self._gen_policy(extra_args=['--read-path=%s' % s])
search = "%s r," % s
self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p))
inv_s = '###READPATH###'
self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p))
def test_genpolicy_readpath_home_file(self):
'''Test genpolicy (read-path file in /home)'''
s = "/home/*/test-foo"
p = self._gen_policy(extra_args=['--read-path=%s' % s])
search = "owner %s r," % s
self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p))
inv_s = '###READPATH###'
self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p))
def test_genpolicy_readpath_homevar_file(self):
'''Test genpolicy (read-path file in @{HOME})'''
s = "@{HOME}/test-foo"
p = self._gen_policy(extra_args=['--read-path=%s' % s])
search = "owner %s r," % s
self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p))
inv_s = '###READPATH###'
self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p))
def test_genpolicy_readpath_homedirs_file(self):
'''Test genpolicy (read-path file in @{HOMEDIRS})'''
s = "@{HOMEDIRS}/test-foo"
p = self._gen_policy(extra_args=['--read-path=%s' % s])
search = "owner %s r," % s
self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p))
inv_s = '###READPATH###'
self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p))
def test_genpolicy_readpath_dir(self):
'''Test genpolicy (read-path directory/)'''
s = "/opt/test-foo-dir/"
p = self._gen_policy(extra_args=['--read-path=%s' % s])
search_terms = ["%s r," % s, "%s** r," % s]
for search in search_terms:
self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p))
inv_s = '###READPATH###'
self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p))
def test_genpolicy_readpath_dir_glob(self):
'''Test genpolicy (read-path directory/*)'''
s = "/opt/test-foo-dir/*"
p = self._gen_policy(extra_args=['--read-path=%s' % s])
search_terms = ["%s r," % os.path.dirname(s), "%s r," % s]
for search in search_terms:
self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p))
inv_s = '###READPATH###'
self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p))
def test_genpolicy_readpath_dir_glob_all(self):
'''Test genpolicy (read-path directory/**)'''
s = "/opt/test-foo-dir/**"
p = self._gen_policy(extra_args=['--read-path=%s' % s])
search_terms = ["%s r," % os.path.dirname(s), "%s r," % s]
for search in search_terms:
self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p))
inv_s = '###READPATH###'
self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p))
def test_genpolicy_readpath_multiple(self):
'''Test genpolicy (read-path multiple)'''
paths = ["/opt/test-foo",
"/home/*/test-foo",
"@{HOME}/test-foo",
"@{HOMEDIRS}/test-foo",
"/opt/test-foo-dir/",
"/opt/test-foo-dir/*",
"/opt/test-foo-dir/**"]
args = []
search_terms = []
for s in paths:
args.append('--read-path=%s' % s)
# This mimics easyprof.gen_path_rule()
owner = ""
if s.startswith('/home/') or s.startswith("@{HOME"):
owner = "owner "
if s.endswith('/'):
search_terms.append("%s r," % (s))
search_terms.append("%s%s** r," % (owner, s))
elif s.endswith('/**') or s.endswith('/*'):
search_terms.append("%s r," % (os.path.dirname(s)))
search_terms.append("%s%s r," % (owner, s))
else:
search_terms.append("%s%s r," % (owner, s))
p = self._gen_policy(extra_args=args)
for search in search_terms:
self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p))
inv_s = '###READPATH###'
self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p))
def test_genpolicy_readpath_bad(self):
'''Test genpolicy (read-path bad)'''
s = "bar"
try:
self._gen_policy(extra_args=['--read-path=%s' % s])
except easyprof.AppArmorException:
return
except Exception:
raise
raise Exception ("read-path should be invalid")
def test_genpolicy_writepath_file(self):
'''Test genpolicy (write-path file)'''
s = "/opt/test-foo"
p = self._gen_policy(extra_args=['--write-path=%s' % s])
search = "%s rwk," % s
self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p))
inv_s = '###READPATH###'
self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p))
def test_genpolicy_writepath_home_file(self):
'''Test genpolicy (write-path file in /home)'''
s = "/home/*/test-foo"
p = self._gen_policy(extra_args=['--write-path=%s' % s])
search = "owner %s rwk," % s
self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p))
inv_s = '###READPATH###'
self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p))
def test_genpolicy_writepath_homevar_file(self):
'''Test genpolicy (write-path file in @{HOME})'''
s = "@{HOME}/test-foo"
p = self._gen_policy(extra_args=['--write-path=%s' % s])
search = "owner %s rwk," % s
self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p))
inv_s = '###READPATH###'
self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p))
def test_genpolicy_writepath_homedirs_file(self):
'''Test genpolicy (write-path file in @{HOMEDIRS})'''
s = "@{HOMEDIRS}/test-foo"
p = self._gen_policy(extra_args=['--write-path=%s' % s])
search = "owner %s rwk," % s
self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p))
inv_s = '###READPATH###'
self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p))
def test_genpolicy_writepath_dir(self):
'''Test genpolicy (write-path directory/)'''
s = "/opt/test-foo-dir/"
p = self._gen_policy(extra_args=['--write-path=%s' % s])
search_terms = ["%s rwk," % s, "%s** rwk," % s]
for search in search_terms:
self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p))
inv_s = '###READPATH###'
self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p))
def test_genpolicy_writepath_dir_glob(self):
'''Test genpolicy (write-path directory/*)'''
s = "/opt/test-foo-dir/*"
p = self._gen_policy(extra_args=['--write-path=%s' % s])
search_terms = ["%s rwk," % os.path.dirname(s), "%s rwk," % s]
for search in search_terms:
self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p))
inv_s = '###READPATH###'
self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p))
def test_genpolicy_writepath_dir_glob_all(self):
'''Test genpolicy (write-path directory/**)'''
s = "/opt/test-foo-dir/**"
p = self._gen_policy(extra_args=['--write-path=%s' % s])
search_terms = ["%s rwk," % os.path.dirname(s), "%s rwk," % s]
for search in search_terms:
self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p))
inv_s = '###READPATH###'
self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p))
def test_genpolicy_writepath_multiple(self):
'''Test genpolicy (write-path multiple)'''
paths = ["/opt/test-foo",
"/home/*/test-foo",
"@{HOME}/test-foo",
"@{HOMEDIRS}/test-foo",
"/opt/test-foo-dir/",
"/opt/test-foo-dir/*",
"/opt/test-foo-dir/**"]
args = []
search_terms = []
for s in paths:
args.append('--write-path=%s' % s)
# This mimics easyprof.gen_path_rule()
owner = ""
if s.startswith('/home/') or s.startswith("@{HOME"):
owner = "owner "
if s.endswith('/'):
search_terms.append("%s rwk," % (s))
search_terms.append("%s%s** rwk," % (owner, s))
elif s.endswith('/**') or s.endswith('/*'):
search_terms.append("%s rwk," % (os.path.dirname(s)))
search_terms.append("%s%s rwk," % (owner, s))
else:
search_terms.append("%s%s rwk," % (owner, s))
p = self._gen_policy(extra_args=args)
for search in search_terms:
self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p))
inv_s = '###READPATH###'
self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p))
def test_genpolicy_writepath_bad(self):
'''Test genpolicy (write-path bad)'''
s = "bar"
try:
self._gen_policy(extra_args=['--write-path=%s' % s])
except easyprof.AppArmorException:
return
except Exception:
raise
raise Exception ("write-path should be invalid")
def test_genpolicy_templatevar(self):
'''Test genpolicy (template-var single)'''
s = "@{FOO}=bar"
p = self._gen_policy(extra_args=['--template-var=%s' % s])
self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p))
inv_s = '###TEMPLATEVAR###'
self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p))
def test_genpolicy_templatevar_multiple(self):
'''Test genpolicy (template-var multiple)'''
variables = ["@{FOO}=bar", "@{BAR}=baz"]
args = []
for s in variables:
args.append('--template-var=%s' % s)
p = self._gen_policy(extra_args=args)
for s in variables:
self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p))
inv_s = '###TEMPLATEVAR###'
self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p))
def test_genpolicy_templatevar_bad(self):
'''Test genpolicy (template-var bad)'''
s = "{FOO}=bar"
try:
self._gen_policy(extra_args=['--template-var=%s' % s])
except easyprof.AppArmorException:
return
except Exception:
raise
raise Exception ("template-var should be invalid")
def test_genpolicy_invalid_template_policy(self):
'''Test genpolicy (invalid template policy)'''
# create a new template
template = os.path.join(self.tmpdir, "test-invalid-template")
shutil.copy(os.path.join(self.tmpdir, 'templates', self.test_template), template)
contents = open(template).read()
bad_pol = ""
bad_string = "bzzzt"
for line in contents.splitlines():
if '}' in line:
bad_pol += bad_string
else:
bad_pol += line
bad_pol += "\n"
open(template, 'w').write(bad_pol)
try:
self._gen_policy(template=template)
except easyprof.AppArmorException:
return
except Exception:
raise
raise Exception ("policy should be invalid")
#
# End test class
#
#
# Main
#
if __name__ == '__main__':
def cleanup(files):
for f in files:
if os.path.exists(f):
os.unlink(f)
absfn = os.path.abspath(sys.argv[0])
topdir = os.path.dirname(os.path.dirname(absfn))
if len(sys.argv) > 1 and (sys.argv[1] == '-d' or sys.argv[1] == '--debug'):
debugging = True
created = []
# Create the necessary files to import aa-easyprof
init = os.path.join(os.path.dirname(absfn), '__init__.py')
if not os.path.exists(init):
open(init, 'wa').close()
created.append(init)
symlink = os.path.join(os.path.dirname(absfn), 'easyprof.py')
if not os.path.exists(symlink):
os.symlink(os.path.join(topdir, 'apparmor', 'easyprof.py'), symlink)
created.append(symlink)
created.append(symlink + 'c')
# Now that we have everything we need, import aa-easyprof
import easyprof
# run the tests
suite = unittest.TestSuite()
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(T))
rc = unittest.TextTestRunner(verbosity=2).run(suite)
cleanup(created)
if not rc.wasSuccessful():
sys.exit(1)

View File

@@ -1,5 +1,25 @@
apparmor.vim: apparmor.vim.in Makefile create-apparmor.vim.sh
sh create-apparmor.vim.sh
COMMONDIR=../../common/
all:
include common/Make.rules
COMMONDIR_EXISTS=$(strip $(shell [ -d ${COMMONDIR} ] && echo true))
ifeq ($(COMMONDIR_EXISTS), true)
common/Make.rules: $(COMMONDIR)/Make.rules
ln -sf $(COMMONDIR) .
endif
VIM_INSTALL_PATH=${DESTDIR}/usr/share/apparmor
all: apparmor.vim
apparmor.vim: apparmor.vim.in Makefile create-apparmor.vim.py
python create-apparmor.vim.py > $@
install: apparmor.vim
install -d $(VIM_INSTALL_PATH)
install -m 644 $< $(VIM_INSTALL_PATH)
clean:
rm -f apparmor.vim
rm -f apparmor.vim common

View File

@@ -0,0 +1,118 @@
#!/usr/bin/python
#
# Copyright (C) 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
# License published by the Free Software Foundation.
#
# Written by Steve Beattie <steve@nxnw.org>, based on work by
# Christian Boltz <apparmor@cboltz.de>
import os
import re
import subprocess
import sys
# dangerous capabilities
danger_caps=["audit_control",
"audit_write",
"mac_override",
"mac_admin",
"set_fcap",
"sys_admin",
"sys_module",
"sys_rawio"]
def cmd(command, input = None, stderr = subprocess.STDOUT, stdout = subprocess.PIPE, stdin = None, timeout = None):
'''Try to execute given command (array) and return its stdout, or
return a textual error if it failed.'''
try:
sp = subprocess.Popen(command, stdin=stdin, stdout=stdout, stderr=stderr, close_fds=True)
except OSError, e:
return [127, str(e)]
out, outerr = sp.communicate(input)
# Handle redirection of stdout
if out == None:
out = ''
# Handle redirection of stderr
if outerr == None:
outerr = ''
return [sp.returncode,out+outerr]
# get capabilities list
(rc, output) = cmd(['make', '-s', '--no-print-directory', 'list_capabilities'])
if rc != 0:
print >>sys.stderr, ("make list_capabilities failed: " + output)
exit(rc)
capabilities = re.sub('CAP_', '', output.strip()).lower().split(" ")
benign_caps =[]
for cap in capabilities:
if cap not in danger_caps:
benign_caps.append(cap)
# get network protos list
(rc, output) = cmd(['make', '-s', '--no-print-directory', 'list_af_names'])
if rc != 0:
print >>sys.stderr, ("make list_af_names failed: " + output)
exit(rc)
af_names = []
af_pairs = re.sub('AF_', '', output.strip()).lower().split(",")
for af_pair in af_pairs:
af_name = af_pair.lstrip().split(" ")[0]
# skip max af name definition
if len(af_name) > 0 and af_name != "max":
af_names.append(af_name)
# TODO: does a "debug" flag exist? Listed in apparmor.vim.in sdFlagKey,
# but not in aa_flags...
# -> currently (2011-01-11) not, but might come back
aa_network_types=r'\s+tcp|\s+udp|\s+icmp'
aa_flags=['complain',
'audit',
'attach_disconnect',
'no_attach_disconnected',
'chroot_attach',
'chroot_no_attach',
'chroot_relative',
'namespace_relative']
filename=r'(\/|\@\{\S*\})\S*'
aa_regex_map = {
'FILENAME': filename,
'FILE': r'\v^\s*(audit\s+)?(deny\s+)?(owner\s+)?' + filename + r'\s+', # Start of a file rule
# (whitespace_+_, owner etc. flag_?_, filename pattern, whitespace_+_)
'DENYFILE': r'\v^\s*(audit\s+)?deny\s+(owner\s+)?' + filename + r'\s+', # deny, otherwise like FILE
'auditdenyowner': r'(audit\s+)?(deny\s+)?(owner\s+)?',
'auditdeny': r'(audit\s+)?(deny\s+)?',
'EOL': r'\s*,(\s*$|(\s*#.*$)\@=)', # End of a line (whitespace_?_, comma, whitespace_?_ comment.*)
'TRANSITION': r'(\s+-\>\s+\S+)?',
'sdKapKey': " ".join(benign_caps),
'sdKapKeyDanger': " ".join(danger_caps),
'sdKapKeyRegex': "|".join(capabilities),
'sdNetworkType': aa_network_types,
'sdNetworkProto': "|".join(af_names),
'flags': r'((flags\s*\=\s*)?\(\s*(' + '|'.join(aa_flags) + r')(\s*,\s*(' + '|'.join(aa_flags) + r'))*\s*\)\s+)',
}
def my_repl(matchobj):
#print matchobj.group(1)
if matchobj.group(1) in aa_regex_map:
return aa_regex_map[matchobj.group(1)]
return matchobj.group(0)
regex = "@@(" + "|".join(aa_regex_map) + ")@@"
with file("apparmor.vim.in") as template:
for line in template:
line = re.sub(regex, my_repl, line.rstrip())
print line

View File

@@ -1,129 +0,0 @@
#!/bin/bash
# not-too-dangerous capabilities
sdKapKey="chown dac_override dac_read_search fowner fsetid kill setgid setuid setpcap linux_immutable net_bind_service net_broadcast net_admin net_raw ipc_lock ipc_owner sys_chroot sys_ptrace sys_pacct sys_boot sys_nice sys_resource sys_time sys_tty_config syslog mknod lease"
# dangerous capabilities
sdKapKeyDanger="audit_control audit_write mac_override mac_admin set_fcap sys_admin sys_module sys_rawio"
sdNetworkProto="inet|ax25|ipx|appletalk|netrom|bridge|atmpvc|x25|inet6|rose|netbeui|security|key|packet|ash|econet|atmsvc|sna|irda|pppox|wanpipe|bluetooth"
sdNetworkType='\s+tcp|\s+udp|\s+icmp'
sdFlags="complain|audit|attach_disconnect|no_attach_disconnected|chroot_attach|chroot_no_attach|chroot_relative|namespace_relative"
# TODO: does a "debug" flag exist? Listed in apparmor.vim.in sdFlagKey, but not in sdFlags...
# -> currently (2011-01-11) not, but might come back
sdKapKeyRegex="$(echo "$sdKapKey $sdKapKeyDanger" | sed 's/ /|/g')"
sdFlagsRegex="($sdFlags)"
# '@@FILE@@' '\v^\s*((owner\s+)|(audit\s+)|(deny\s+))*(\/|\@\{\S*\})\S*\s+' \
replace \
'@@FILE@@' '\v^\s*(audit\s+)?(deny\s+)?(owner\s+)?(\/|\@\{\S*\})\S*\s+' \
'@@DENYFILE@@' '\v^\s*(audit\s+)?deny\s+(owner\s+)?(\/|\@\{\S*\})\S*\s+' \
'@@auditdenyowner@@' '(audit\s+)?(deny\s+)?(owner\s+)?' \
'@@auditdeny@@' '(audit\s+)?(deny\s+)?' \
'@@FILENAME@@' '(\/|\@\{\S*\})\S*' \
'@@EOL@@' '\s*,(\s*$|(\s*#.*$)\@=)' \
'@@TRANSITION@@' '(\s+-\>\s+\S+)?' \
'@@sdKapKey@@' "$sdKapKey" \
'@@sdKapKeyDanger@@' "$sdKapKeyDanger" \
'@@sdKapKeyRegex@@' "$sdKapKeyRegex" \
'@@sdNetworkProto@@' "$sdNetworkProto" \
'@@sdNetworkType@@' "$sdNetworkType" \
'@@flags@@' "((flags\s*\=\s*)?\(\s*$sdFlagsRegex(\s*,\s*$sdFlagsRegex)*\s*\)\s+)" \
\
< apparmor.vim.in \
> apparmor.vim
# @@FILE@@: Start of a file rule (whitespace_+_, owner etc. flag_?_, filename pattern, whitespace_+_)
# @@FILENAME@@: Just a filename (taken from @@FILE@@)
# @@EOL@@: End of a line (whitespace_?_, comma, whitespace_?_ comment.*)
# I had to learn that vim has a restriction on the number of (...) I may use in
# a RegEx (up to 9 are allowed), and therefore had to change the RegEx that
# matches tcp/udp/icmp from "(\s+(tcp|udp|icmp))?" to
# "(\s+tcp|\s+udp|\s+icmp)?". *argh*
# (sdNetworkProto could be changed the same way if needed)
# TODO: permissions first
# valid rules:
# owner rw /foo,
# owner /foo rw,
# INVALID rules
# rw owner /foo,
# rw /foo owner,
# /foo owner rw,
# /foo rw owner,
# the *** proposed *** syntax for owner= and user= is
#
# owner=<name> <whitespace> <rule>
# owner='('<names>')' <whitespace> <rule>
#
# where the list followed the syntax for the flags value, however the list
# syntax part needs to be made consistent, ie. we either need to fix the
# flags list separator or make the list separator here the same as flags
# and also fix it for variables, etc. switching flags to use just whitespace
# is by far the easiest.
#
# So going with the whitespace separator we would have
# owner=jj /foo r,
# owner=(jj) /foo r,
# owner=(jj smb) /foo r,
# > capability dac_override {
# > /file/bar rw,
# > }
# > capability chown {
# > /file/bar (user1, user2),
# > }
# > (Are those things specific to dac_override and chown?)
# >
# Hehe, now your veering even more into unimplemented stuff :) Those where
# merely proposed syntax and I don't believe we are using them now.
# The idea behind those was a way to enhance the capabilities and remain
# backwards compatible.
#
# And use the syntax for each would have to be capability (or type specific)
#
# eg. for chown we could have a path and user
#
# chown /foo to (user1 user2),
#
# but for setuid it wouldn't have a path.
# setuid to (user1 user2)
#
#
# > uses ipc,
# > ipc rw /profile,
# > ipc signal w (child) /profile,
# > deny ipc signal w (kill) /profile,
# >
# > Which keywords can apply to ipc? I'd guess audit and deny. What about
# > owner?
# >
# owner and user could be selectively applied but not to allow of ipc
#
# owner doesn't really make sense for signal, but user might this is just
# another place we need to look at before we commit to the syntax.
#
# ipc may hit spring 2011
# > That all said: are there some example profiles I could use to test
# > apparmor.vim?
# >
# Hrmmm, yes. The goal is to keep adding to the parser test suite, and
# get it to contain at least on example of every valid syntax and also
# example profiles of invalid syntax. I won't say that the coverage
# is complete yet but it does have hundreds of simple examples.
#
# it can be found in parser/tst/simple_tests/
#