2
0
mirror of https://gitlab.com/apparmor/apparmor synced 2025-09-03 07:45:50 +00:00

Compare commits

...

24 Commits

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

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

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

rule just like the presence of hats does for change_hat


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

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

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

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

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

  eg.
    /bin/foo
    unconfined

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

  /executable/name\x00profile_name

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

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

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

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

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

To generate a profile you do

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Signed-off-by: John Johansen <john.johansen@canonical.com>
2012-03-26 06:09:04 -07:00
Jamie Strandboge
83ead1217f clean up utils/vim/common symlink on clean
Acked-By: Jamie Strandboge <jamie@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-03-23 11:02:20 -05:00
Jamie Strandboge
4a89f974f6 utils/aa-exec: update copyright year to be 2011-2012 since it was committed
in 2012
2012-03-22 18:07:07 -05:00
Jamie Strandboge
93308e4a29 Use linux/capability.h instead of sys/capability.h
Acked-By: Jamie Strandboge <jamie@canonical.com>
Acked-By: Steve Beattie <sbeattie@ubuntu.com>
2012-03-22 16:41:08 -05:00
John Johansen
593cb59d38 bump revision and set tag for apparmor_2.8-beta4 2012-03-22 13:29:46 -07:00
49 changed files with 1021 additions and 139 deletions

View File

@@ -152,12 +152,12 @@ _clean:
# =====================
# generate list of capabilities based on
# /usr/include/sys/capabilities.h for use in multiple locations in
# /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 <sys/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)
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
@@ -206,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.101
2.7.102

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

@@ -84,6 +84,7 @@ static struct keyword_table keyword_table[] = {
{"umount", TOK_UMOUNT},
{"unmount", TOK_UMOUNT},
{"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);

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,
@@ -1068,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."));
@@ -1078,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."));

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

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

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

@@ -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
genprofile 'change_profile->':$bin/rw -- image=$bin/rw $bin/open:rix $file:rw
do_test "change profile - no onexec:w" $bin/onexec $bin/rw fail $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

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

View File

@@ -64,7 +64,9 @@ install: ${MANPAGES} ${HTMLMANPAGES}
ln -sf aa-status.8 ${DESTDIR}/${MANDIR}/man8/apparmor_status.8
.PHONY: clean
ifndef VERBOSE
.SILENT: clean
endif
clean: _clean
rm -f core core.* *.o *.s *.a *~
rm -f Make.rules
@@ -74,7 +76,7 @@ clean: _clean
# ${CAPABILITIES} is defined in common/Make.rules
.PHONY: check_severity_db
.SILENT: check_severity_db
check_severity_db: /usr/include/sys/capability.h 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 \

View File

@@ -1,7 +1,7 @@
#!/usr/bin/perl
# ------------------------------------------------------------------
#
# Copyright (C) 2011 Canonical Ltd.
# 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

View File

@@ -82,6 +82,8 @@ 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

View File

@@ -22,4 +22,4 @@ install: apparmor.vim
clean:
rm -f apparmor.vim
rm -f apparmor.vim common

View File

@@ -24,10 +24,6 @@ danger_caps=["audit_control",
"sys_module",
"sys_rawio"]
aa_network_types=r'\s+tcp|\s+udp|\s+icmp'
aa_flags=r'(complain|audit|attach_disconnect|no_attach_disconnected|chroot_attach|chroot_no_attach|chroot_relative|namespace_relative)'
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.'''
@@ -77,20 +73,34 @@ for af_pair in af_pairs:
# 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 = {
'FILE': r'\v^\s*(audit\s+)?(deny\s+)?(owner\s+)?(\/|\@\{\S*\})\S*\s+',
'DENYFILE': r'\v^\s*(audit\s+)?deny\s+(owner\s+)?(\/|\@\{\S*\})\S*\s+',
'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+)?',
'FILENAME': r'(\/|\@\{\S*\})\S*',
'EOL': r'\s*,(\s*$|(\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*' + aa_flags + r'(\s*,\s*' + aa_flags + r')*\s*\)\s+)',
'flags': r'((flags\s*\=\s*)?\(\s*(' + '|'.join(aa_flags) + r')(\s*,\s*(' + '|'.join(aa_flags) + r'))*\s*\)\s+)',
}
def my_repl(matchobj):