mirror of
https://gitlab.com/apparmor/apparmor
synced 2025-09-04 08:15:21 +00:00
Compare commits
41 Commits
v2.11.0
...
v2.11-bran
Author | SHA1 | Date | |
---|---|---|---|
|
766f5e160e | ||
|
68cba4fe27 | ||
|
5452095203 | ||
|
929b1acf6f | ||
|
e04b50ce95 | ||
|
8901b3e835 | ||
|
1285d81547 | ||
|
8ce02c20fa | ||
|
71566d36e3 | ||
|
fe421f6952 | ||
|
566b053bdf | ||
|
054d8f795f | ||
|
9a8c6885cb | ||
|
7ab65fa5f1 | ||
|
b98e9df766 | ||
|
7066649144 | ||
|
361b63d30b | ||
|
ea0732becc | ||
|
f30ab46af7 | ||
|
2db1b83869 | ||
|
8935457c63 | ||
|
d4d4d50d84 | ||
|
984ed2801e | ||
|
633f833a6e | ||
|
20817ef77b | ||
|
20091ca87d | ||
|
2eee4d6acb | ||
|
04240fe6de | ||
|
2c4119d98c | ||
|
2e3a871b11 | ||
|
50623fca92 | ||
|
f5384469b5 | ||
|
85178293f5 | ||
|
d1fa70ac22 | ||
|
4ec82daa00 | ||
|
627856d6b4 | ||
|
cfa0a37e58 | ||
|
ca093f7223 | ||
|
1cae419b4d | ||
|
dd27256bb3 | ||
|
67b75e84d0 |
@@ -55,7 +55,7 @@ libapparmor by adding USE_SYSTEM=1 to your make command.${nl}\
|
||||
AA_LDLIBS = -lapparmor
|
||||
endif
|
||||
EXTRA_CFLAGS=$(CFLAGS) $(CPPFLAGS) -fPIC -shared -Wall $(LIBAPPARMOR_INCLUDE)
|
||||
LINK_FLAGS=-Xlinker -x $(AA_LINK_FLAGS)
|
||||
LINK_FLAGS=-Xlinker -x $(AA_LINK_FLAGS) $(LDFLAGS)
|
||||
LIBS=-lpam $(AA_LDLIBS)
|
||||
OBJECTS=${NAME}.o get_options.o
|
||||
|
||||
|
@@ -13,5 +13,6 @@ WriteMakefile(
|
||||
'INC' => q[@CPPFLAGS@ -I@top_srcdir@/include @CFLAGS@],
|
||||
'LIBS' => q[-L@top_builddir@/src/.libs/ -lapparmor @LIBS@],
|
||||
'OBJECT' => 'libapparmor_wrap.o', # $(OBJ_EXT)
|
||||
'dynamic_lib' => { 'OTHERLDFLAGS' => q[@LDFLAGS@], },
|
||||
) ;
|
||||
|
||||
|
@@ -0,0 +1 @@
|
||||
Feb 21 23:22:01 mail-20170118 kernel: [1222198.459750] audit: type=1400 audit(1487719321.954:218): apparmor="ALLOWED" operation="change_hat" info="unconfined can not change_hat" error=-1 profile="unconfined" pid=19941 comm="apache2"
|
@@ -0,0 +1,12 @@
|
||||
START
|
||||
File: unconfined-change_hat.in
|
||||
Event type: AA_RECORD_ALLOWED
|
||||
Audit ID: 1487719321.954:218
|
||||
Operation: change_hat
|
||||
Profile: unconfined
|
||||
Command: apache2
|
||||
Info: unconfined can not change_hat
|
||||
ErrorCode: 1
|
||||
PID: 19941
|
||||
Epoch: 1487719321
|
||||
Audit subid: 218
|
@@ -0,0 +1,2 @@
|
||||
profile unconfined {
|
||||
}
|
@@ -86,7 +86,7 @@ OBJECTS = $(patsubst %.cc, %.o, $(SRCS:.c=.o))
|
||||
AAREDIR= libapparmor_re
|
||||
AAREOBJECT = ${AAREDIR}/libapparmor_re.a
|
||||
AAREOBJECTS = $(AAREOBJECT)
|
||||
AARE_LDFLAGS = -static-libgcc -static-libstdc++ -L.
|
||||
AARE_LDFLAGS = -static-libgcc -static-libstdc++ -L. $(LDFLAGS)
|
||||
AALIB = -Wl,-Bstatic -lapparmor -Wl,-Bdynamic -lpthread
|
||||
|
||||
ifdef USE_SYSTEM
|
||||
|
@@ -672,7 +672,7 @@ public:
|
||||
|
||||
~hashedNodeVec()
|
||||
{
|
||||
delete nodes;
|
||||
delete [] nodes;
|
||||
}
|
||||
|
||||
unsigned long size()const { return len; }
|
||||
|
@@ -451,34 +451,7 @@ __apparmor_restart() {
|
||||
|
||||
configure_owlsm
|
||||
parse_profiles reload
|
||||
# Clean out running profiles not associated with the current profile
|
||||
# set, excluding the libvirt dynamically generated profiles.
|
||||
# Note that we reverse sort the list of profiles to remove to
|
||||
# ensure that child profiles (e.g. hats) are removed before the
|
||||
# parent. We *do* need to remove the child profile and not rely
|
||||
# on removing the parent profile when the profile has had its
|
||||
# child profile names changed.
|
||||
profiles_names_list | awk '
|
||||
BEGIN {
|
||||
while (getline < "'${SFS_MOUNTPOINT}'/profiles" ) {
|
||||
str = sub(/ \((enforce|complain)\)$/, "", $0);
|
||||
if (match($0, /^libvirt-[0-9a-f\-]+$/) == 0)
|
||||
arr[$str] = $str
|
||||
}
|
||||
}
|
||||
|
||||
{ if (length(arr[$0]) > 0) { delete arr[$0] } }
|
||||
|
||||
END {
|
||||
for (key in arr)
|
||||
if (length(arr[key]) > 0) {
|
||||
printf("%s\n", arr[key])
|
||||
}
|
||||
}
|
||||
' | LC_COLLATE=C sort -r | while IFS= read profile ; do
|
||||
echo -n "$profile" > "$SFS_MOUNTPOINT/.remove"
|
||||
done
|
||||
# will not catch all errors, but still better than nothing
|
||||
rc=$?
|
||||
aa_log_end_msg $rc
|
||||
return $rc
|
||||
|
@@ -8,6 +8,8 @@
|
||||
signal (receive) peer=unconfined,
|
||||
# Allow apache to send us signals by default
|
||||
signal (receive) peer=/usr/sbin/apache2,
|
||||
# Allow other hats to signal by default
|
||||
signal peer=/usr/sbin/apache2//*,
|
||||
# Allow us to signal ourselves
|
||||
signal peer=@{profile_name},
|
||||
|
||||
@@ -25,3 +27,8 @@
|
||||
|
||||
/dev/urandom r,
|
||||
|
||||
# sasl-auth
|
||||
/run/saslauthd/mux rw,
|
||||
|
||||
# OCSP stapling
|
||||
/var/log/apache2/stapling-cache rw,
|
||||
|
@@ -85,7 +85,7 @@
|
||||
/sys/devices/system/cpu/online r,
|
||||
|
||||
# glibc's *printf protections read the maps file
|
||||
@{PROC}/@{pid}/maps r,
|
||||
@{PROC}/@{pid}/{maps,auxv,status} r,
|
||||
|
||||
# libgcrypt reads some flags from /proc
|
||||
@{PROC}/sys/crypto/* r,
|
||||
|
@@ -8,8 +8,9 @@
|
||||
/etc/vdpau_wrapper.cfg r,
|
||||
|
||||
# device files
|
||||
/dev/nvidia0 rw,
|
||||
/dev/nvidiactl rw,
|
||||
/dev/nvidiactl rw,
|
||||
/dev/nvidia-modeset rw,
|
||||
/dev/nvidia[0-9]* rw,
|
||||
|
||||
@{PROC}/interrupts r,
|
||||
@{PROC}/sys/vm/max_map_count r,
|
||||
@@ -18,3 +19,5 @@
|
||||
|
||||
owner @{HOME}/.nv/GLCache/ r,
|
||||
owner @{HOME}/.nv/GLCache/** rwk,
|
||||
|
||||
unix (send, receive) type=dgram peer=(addr="@nvidia[0-9a-f]*"),
|
||||
|
@@ -18,6 +18,7 @@
|
||||
capability setuid,
|
||||
capability sys_chroot,
|
||||
|
||||
/run/dovecot/anvil rw,
|
||||
/usr/lib/dovecot/anvil mr,
|
||||
|
||||
# Site-specific additions and overrides. See local/README for details.
|
||||
|
@@ -37,6 +37,9 @@
|
||||
/var/tmp/sieve_* rw,
|
||||
/var/tmp/smtp_* rw,
|
||||
|
||||
/run/dovecot/auth-master rw,
|
||||
/run/dovecot/auth-worker rw,
|
||||
/run/dovecot/login/login rw,
|
||||
/{var/,}run/dovecot/auth-token-secret.dat{,.tmp} rw,
|
||||
/{var/,}run/dovecot/stats-user rw,
|
||||
/{var/,}run/dovecot/anvil-auth-penalty rw,
|
||||
|
@@ -12,7 +12,7 @@
|
||||
#include <tunables/global>
|
||||
#include <tunables/dovecot>
|
||||
|
||||
/usr/lib/dovecot/dovecot-lda {
|
||||
/usr/lib/dovecot/dovecot-lda flags=(attach_disconnected) {
|
||||
#include <abstractions/base>
|
||||
#include <abstractions/nameservice>
|
||||
#include <abstractions/dovecot-common>
|
||||
@@ -26,9 +26,11 @@
|
||||
/proc/*/mounts r,
|
||||
owner /tmp/dovecot.lda.* rw,
|
||||
/{var/,}run/dovecot/mounts r,
|
||||
/run/dovecot/auth-userdb rw,
|
||||
/usr/bin/doveconf mrix,
|
||||
/usr/lib/dovecot/dovecot-lda mrix,
|
||||
/usr/sbin/sendmail Cx,
|
||||
/usr/share/dovecot/protocols.d/ r,
|
||||
|
||||
# Site-specific additions and overrides. See local/README for details.
|
||||
#include <local/usr.lib.dovecot.dovecot-lda>
|
||||
|
@@ -21,6 +21,8 @@
|
||||
capability setuid,
|
||||
deny capability block_suspend,
|
||||
|
||||
network unix stream,
|
||||
|
||||
@{DOVECOT_MAILSTORE}/ rw,
|
||||
@{DOVECOT_MAILSTORE}/** rwkl,
|
||||
|
||||
@@ -33,6 +35,7 @@
|
||||
/usr/bin/doveconf rix,
|
||||
/usr/lib/dovecot/imap mrix,
|
||||
/usr/share/dovecot/** r,
|
||||
/run/dovecot/login/imap rw,
|
||||
/{,var/}run/dovecot/auth-master rw,
|
||||
/{,var/}run/dovecot/mounts r,
|
||||
|
||||
|
@@ -22,6 +22,7 @@
|
||||
|
||||
network inet stream,
|
||||
network inet6 stream,
|
||||
network unix stream,
|
||||
|
||||
/usr/lib/dovecot/imap-login mr,
|
||||
/{,var/}run/dovecot/anvil rw,
|
||||
|
@@ -15,6 +15,7 @@
|
||||
#include <abstractions/base>
|
||||
#include <abstractions/dovecot-common>
|
||||
|
||||
/run/dovecot/login/ssl-params rw,
|
||||
/usr/lib/dovecot/ssl-params mr,
|
||||
/var/lib/dovecot/ssl-parameters.dat rw,
|
||||
/var/lib/dovecot/ssl-parameters.dat.tmp rwk,
|
||||
|
@@ -36,21 +36,21 @@
|
||||
/etc/SuSE-release r,
|
||||
@{PROC}/@{pid}/mounts r,
|
||||
/usr/bin/doveconf rix,
|
||||
/usr/lib/dovecot/anvil Px,
|
||||
/usr/lib/dovecot/auth Px,
|
||||
/usr/lib/dovecot/config Px,
|
||||
/usr/lib/dovecot/dict Px,
|
||||
/usr/lib/dovecot/anvil mrPx,
|
||||
/usr/lib/dovecot/auth mrPx,
|
||||
/usr/lib/dovecot/config mrPx,
|
||||
/usr/lib/dovecot/dict mrPx,
|
||||
/usr/lib/dovecot/dovecot-auth Pxmr,
|
||||
/usr/lib/dovecot/imap Pxmr,
|
||||
/usr/lib/dovecot/imap-login Pxmr,
|
||||
/usr/lib/dovecot/lmtp Px,
|
||||
/usr/lib/dovecot/log Px,
|
||||
/usr/lib/dovecot/managesieve Px,
|
||||
/usr/lib/dovecot/lmtp mrPx,
|
||||
/usr/lib/dovecot/log mrPx,
|
||||
/usr/lib/dovecot/managesieve mrPx,
|
||||
/usr/lib/dovecot/managesieve-login Pxmr,
|
||||
/usr/lib/dovecot/pop3 Px,
|
||||
/usr/lib/dovecot/pop3 mrPx,
|
||||
/usr/lib/dovecot/pop3-login Pxmr,
|
||||
/usr/lib/dovecot/ssl-build-param rix,
|
||||
/usr/lib/dovecot/ssl-params Px,
|
||||
/usr/lib/dovecot/ssl-params mrPx,
|
||||
/usr/sbin/dovecot mrix,
|
||||
/usr/share/dovecot/protocols.d/ r,
|
||||
/usr/share/dovecot/protocols.d/** r,
|
||||
|
@@ -2,6 +2,8 @@
|
||||
# ------------------------------------------------------------------
|
||||
#
|
||||
# Copyright (C) 2002-2006 Novell/SUSE
|
||||
# Copyright (C) 2016 Seth Arnold
|
||||
# Copyright (C) 2016 Daniel Curtis
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of version 2 of the GNU General Public
|
||||
@@ -16,38 +18,58 @@
|
||||
#include <abstractions/bash>
|
||||
#include <abstractions/nameservice>
|
||||
|
||||
/{usr/,}bin/bash mixr,
|
||||
capability chown,
|
||||
capability dac_override,
|
||||
capability dac_read_search,
|
||||
capability fowner,
|
||||
capability fsetid,
|
||||
|
||||
/{usr/,}bin/{ba,da,}sh mixr,
|
||||
/{usr/,}bin/cat mixr,
|
||||
/{usr/,}bin/gzip mixr,
|
||||
/{usr/,}bin/kill mixr,
|
||||
/{usr/,}bin/logger mixr,
|
||||
/{usr/,}bin/mv mixr,
|
||||
/{usr/,}bin/sed mixr,
|
||||
/{usr/,}bin/sleep mrix,
|
||||
/{usr/,}bin/true mixr,
|
||||
/etc/init.d/* mixr,
|
||||
/usr/bin/head mrix,
|
||||
/usr/bin/killall mixr,
|
||||
/usr/sbin/invoke-rc.d mrix,
|
||||
/usr/sbin/logrotate mixr,
|
||||
|
||||
/var/log r,
|
||||
/var/log/** wrl,
|
||||
## see https://lists.ubuntu.com/archives/apparmor/2016-December/010359.html
|
||||
/{usr/,}sbin/initctl Ux,
|
||||
/{usr/,}sbin/runlevel Ux,
|
||||
|
||||
/var/log/ r,
|
||||
/var/log/** rwl,
|
||||
|
||||
/var/lib/privoxy/log/** rwl,
|
||||
/var/lib64/privoxy/log/** rwl,
|
||||
|
||||
/ r,
|
||||
/dev/tty wr,
|
||||
/dev/tty rw,
|
||||
/etc/cron.daily/logrotate r,
|
||||
/etc/logrotate.conf r,
|
||||
/etc/logrotate.d r,
|
||||
/etc/logrotate.d/ r,
|
||||
/etc/logrotate.d/* r,
|
||||
/etc/subdomain.d r,
|
||||
@{PROC} r,
|
||||
@{PROC}/@{pid} r,
|
||||
/tmp w,
|
||||
/tmp/file* wl,
|
||||
/tmp/logrot* wlr,
|
||||
/var/lib/logrotate.status wr,
|
||||
/etc/lsb-base-logging.sh r,
|
||||
|
||||
# @{PROC} r,
|
||||
# @{PROC}/@{pid} r,
|
||||
owner /tmp/file* wl,
|
||||
owner /tmp/logrot* rwl,
|
||||
|
||||
/var/lib/logrotate/ r,
|
||||
/var/lib/logrotate/* rw,
|
||||
|
||||
/{run,var}/lock/samba r,
|
||||
/{,var/}run/httpd.pid r,
|
||||
/{,var/}run/syslogd.pid r,
|
||||
/var/spool/slrnpull wr,
|
||||
/{,var/}run/rsyslogd.pid r,
|
||||
|
||||
/var/spool/slrnpull/ wr,
|
||||
/var/spool/slrnpull/log* wrl,
|
||||
}
|
||||
|
@@ -140,5 +140,5 @@
|
||||
/usr/lib/openssh/sftp-server PUx,
|
||||
|
||||
# Site-specific additions and overrides. See local/README for details.
|
||||
#include <local/usr.sbin.sshd>
|
||||
## include <local/usr.sbin.sshd>
|
||||
}
|
||||
|
@@ -63,6 +63,8 @@ int main(int argc, char *argv[])
|
||||
if (retval == RET_CHLD_SUCCESS) {
|
||||
printf("PASS\n");
|
||||
retval = 0;
|
||||
} else {
|
||||
printf("FAIL: Child failed\n");
|
||||
}
|
||||
|
||||
} else if (pid == 0) {
|
||||
|
@@ -1,13 +1,20 @@
|
||||
/*
|
||||
* Copyright (C) 2002-2006 Novell/SUSE
|
||||
* Copyright (C) 2017 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.
|
||||
*
|
||||
* We attempt to test both getdents() and getdents64() here, but
|
||||
* some architectures like aarch64 only support getdents64().
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
@@ -17,37 +24,118 @@
|
||||
#include <string.h>
|
||||
#include <sys/syscall.h>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
/* error if neither SYS_getdents or SYS_getdents64 is defined */
|
||||
#if !defined(SYS_getdents) && !defined(SYS_getdents64)
|
||||
#error "Neither SYS_getdents or SYS_getdents64 is defined, something has gone wrong!"
|
||||
#endif
|
||||
|
||||
/* define DEBUG to enable debugging statements i.e. gcc -DDEBUG */
|
||||
#ifdef DEBUG
|
||||
void pdebug(const char *format, ...)
|
||||
{
|
||||
va_list arg;
|
||||
|
||||
va_start(arg, format);
|
||||
vprintf(format, arg);
|
||||
va_end(arg);
|
||||
}
|
||||
#else
|
||||
void pdebug(const char *format, ...) { return; }
|
||||
#endif
|
||||
|
||||
#ifdef SYS_getdents
|
||||
int my_readdir(char *dirname)
|
||||
{
|
||||
int fd;
|
||||
struct dirent dir;
|
||||
|
||||
if (argc != 2){
|
||||
fprintf(stderr, "usage: %s dir\n",
|
||||
argv[0]);
|
||||
return 1;
|
||||
fd = open(dirname, O_RDONLY, 0);
|
||||
if (fd == -1) {
|
||||
pdebug("open failed: %s\n", strerror(errno));
|
||||
return errno;
|
||||
}
|
||||
|
||||
fd = open(argv[1], O_RDONLY, 0);
|
||||
if (fd == -1){
|
||||
printf("FAIL - open %s\n", strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
if (fchdir(fd) == -1){
|
||||
printf("FAIL - fchdir %s\n", strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
*/
|
||||
|
||||
/* getdents isn't exported by glibc, so must use syscall() */
|
||||
if (syscall(SYS_getdents, fd, &dir, sizeof(struct dirent)) == -1){
|
||||
printf("FAIL - getdents %s\n", strerror(errno));
|
||||
return 1;
|
||||
if (syscall(SYS_getdents, fd, &dir, sizeof(dir)) == -1){
|
||||
pdebug("getdents failed: %s\n", strerror(errno));
|
||||
close(fd);
|
||||
return errno;
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef SYS_getdents64
|
||||
int my_readdir64(char *dirname)
|
||||
{
|
||||
int fd;
|
||||
struct dirent64 dir;
|
||||
|
||||
fd = open(dirname, O_RDONLY, 0);
|
||||
if (fd == -1) {
|
||||
pdebug("open failed: %s\n", strerror(errno));
|
||||
return errno;
|
||||
}
|
||||
|
||||
/* getdents isn't exported by glibc, so must use syscall() */
|
||||
if (syscall(SYS_getdents64, fd, &dir, sizeof(dir)) == -1){
|
||||
pdebug("getdents64 failed: %s\n", strerror(errno));
|
||||
close(fd);
|
||||
return errno;
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int rc = 0, err = 0;
|
||||
char *dirpath, *endptr;
|
||||
int expected;
|
||||
|
||||
if (argc != 3) {
|
||||
fprintf(stderr, "usage: %s [dir] [expected retval]\n",
|
||||
argv[0]);
|
||||
err = 1;
|
||||
goto err;
|
||||
}
|
||||
|
||||
dirpath = argv[1];
|
||||
|
||||
errno = 0;
|
||||
expected = (int) strtol(argv[2], &endptr, 10);
|
||||
if (errno != 0 || endptr == argv[2]) {
|
||||
fprintf(stderr, "ERROR: couldn't convert '%s' to an integer\n",
|
||||
argv[2]);
|
||||
err = 1;
|
||||
goto err;
|
||||
}
|
||||
pdebug("expected = %d\n", expected);
|
||||
|
||||
#ifdef SYS_getdents
|
||||
rc = my_readdir(dirpath);
|
||||
if (rc != expected) {
|
||||
printf("FAIL - my_readdir returned %d, expected %d\n", rc, expected);
|
||||
err = rc;
|
||||
goto err;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef SYS_getdents64
|
||||
rc = my_readdir64(argv[1]);
|
||||
if (rc != expected) {
|
||||
printf("FAIL - my_readdir64 returned %d, expected %d\n", rc, expected);
|
||||
err = rc;
|
||||
goto err;
|
||||
}
|
||||
#endif
|
||||
|
||||
printf("PASS\n");
|
||||
|
||||
return 0;
|
||||
err:
|
||||
return err;
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
#! /bin/bash
|
||||
# Copyright (C) 2002-2005 Novell/SUSE
|
||||
# Copyright (C) 2017 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
|
||||
@@ -8,7 +9,7 @@
|
||||
|
||||
#=NAME readdir
|
||||
#=DESCRIPTION
|
||||
# AppArmor requires 'r' permission on a directory in order for a confined task
|
||||
# AppArmor requires 'r' permission on a directory in order for a confined task
|
||||
# to be able to read the directory contents. This test verifies this.
|
||||
#=END
|
||||
|
||||
@@ -26,20 +27,31 @@ badperm=ix
|
||||
|
||||
mkdir $dir
|
||||
|
||||
# The readdir utility expects the return value to be passed as the
|
||||
# second argument and returns success if the succeeding or failing calls
|
||||
# match the expected value. It will fail the test if they don't, so for
|
||||
# example the result differs acrorss getdents() and getdents64() this
|
||||
# will be detected.
|
||||
|
||||
# READDIR TEST
|
||||
genprofile $dir/:$okperm
|
||||
runchecktest "READDIR" pass $dir
|
||||
runchecktest "READDIR" pass $dir 0
|
||||
|
||||
EACCES=13
|
||||
# READDIR TEST (no perm)
|
||||
genprofile $dir/:$badperm
|
||||
runchecktest "READDIR (no perm)" fail $dir
|
||||
runchecktest "READDIR (no perm)" pass $dir ${EACCES}
|
||||
|
||||
# READDIR TEST (write perm) - ensure write perm isn't sufficient
|
||||
genprofile $dir/:w
|
||||
runchecktest "READDIR (write perm)" pass $dir ${EACCES}
|
||||
|
||||
# this test is to make sure the raw 'file' rule allows access
|
||||
# to directories
|
||||
genprofile file
|
||||
runchecktest "READDIR 'file' dir" pass $dir
|
||||
runchecktest "READDIR 'file' dir" pass $dir 0
|
||||
|
||||
# this test is to make sure the raw 'file' rule allows access
|
||||
# to '/'
|
||||
genprofile file
|
||||
runchecktest "READDIR 'file' '/'" pass '/'
|
||||
runchecktest "READDIR 'file' '/'" pass '/' 0
|
||||
|
@@ -24,7 +24,7 @@ PERLTOOLS = aa-notify
|
||||
PYTOOLS = aa-easyprof aa-genprof aa-logprof aa-cleanprof aa-mergeprof \
|
||||
aa-autodep aa-audit aa-complain aa-enforce aa-disable \
|
||||
aa-status aa-unconfined
|
||||
TOOLS = ${PERLTOOLS} ${PYTOOLS} aa-decode
|
||||
TOOLS = ${PERLTOOLS} ${PYTOOLS} aa-decode aa-remove-unknown
|
||||
PYSETUP = python-tools-setup.py
|
||||
PYMODULES = $(wildcard apparmor/*.py apparmor/rule/*.py)
|
||||
|
||||
|
@@ -57,6 +57,12 @@ for supported policy groups. The available policy groups are in
|
||||
AppArmor rules or policies. They are similar to AppArmor abstractions, but
|
||||
usually encompass more policy rules.
|
||||
|
||||
=item --parser PATH
|
||||
|
||||
Specify the PATH of the apparmor_parser binary to use when verifying
|
||||
policy. If this option is not specified, aa-easyprof will attempt to
|
||||
locate the path starting with /sbin/apparmor_parser.
|
||||
|
||||
=item -a ABSTRACTIONS, --abstractions=ABSTRACTIONS
|
||||
|
||||
Specify ABSTRACTIONS as a comma-separated list of AppArmor abstractions. It is
|
||||
@@ -64,6 +70,16 @@ 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 -b PATH, --base=PATH
|
||||
|
||||
Set the base PATH for resolving abstractions specified by --abstractions.
|
||||
See the same option in apparmor_parser(8) for details.
|
||||
|
||||
=item -I PATH, --Include=PATH
|
||||
|
||||
Add PATH to the search paths used for resolving abstractions specified by
|
||||
--abstractions. See the same option in apparmor_parser(8) for details.
|
||||
|
||||
=item -r PATH, --read-path=PATH
|
||||
|
||||
Specify a PATH to allow owner reads. May be specified multiple times. If the
|
||||
|
@@ -66,6 +66,7 @@ args = parser.parse_args()
|
||||
profiling = args.program
|
||||
profiledir = args.dir
|
||||
|
||||
apparmor.init_aa()
|
||||
apparmor.set_logfile(args.file)
|
||||
|
||||
aa_mountpoint = apparmor.check_for_apparmor()
|
||||
|
@@ -34,6 +34,7 @@ args = parser.parse_args()
|
||||
profiledir = args.dir
|
||||
logmark = args.mark or ''
|
||||
|
||||
apparmor.init_aa()
|
||||
apparmor.set_logfile(args.file)
|
||||
|
||||
aa_mountpoint = apparmor.check_for_apparmor()
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#! /usr/bin/python3
|
||||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.com>
|
||||
# Copyright (C) 2014-2016 Christian Boltz <apparmor@cboltz.de>
|
||||
# Copyright (C) 2014-2017 Christian Boltz <apparmor@cboltz.de>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of version 2 of the GNU General Public
|
||||
@@ -23,10 +23,6 @@ import apparmor.severity
|
||||
import apparmor.cleanprofile as cleanprofile
|
||||
import apparmor.ui as aaui
|
||||
|
||||
from apparmor.aa import (add_to_options, available_buttons, combine_name, delete_duplicates,
|
||||
get_profile_filename, is_known_rule, match_includes, profile_storage,
|
||||
set_options_audit_mode, propose_file_rules, selection_to_rule_obj)
|
||||
from apparmor.aare import AARE
|
||||
from apparmor.common import AppArmorException
|
||||
from apparmor.regex import re_match_include
|
||||
|
||||
@@ -41,16 +37,15 @@ _ = init_translation()
|
||||
|
||||
parser = argparse.ArgumentParser(description=_('Merge the given profiles into /etc/apparmor.d/ (or the directory specified with -d)'))
|
||||
parser.add_argument('files', nargs='+', type=str, help=_('Profile(s) to merge'))
|
||||
#parser.add_argument('other', nargs='?', type=str, help=_('other profile'))
|
||||
parser.add_argument('-d', '--dir', type=str, help=_('path to profiles'))
|
||||
#parser.add_argument('-a', '--auto', action='store_true', help=_('Automatically merge profiles, exits incase of *x conflicts'))
|
||||
args = parser.parse_args()
|
||||
|
||||
args.other = None
|
||||
# 2-way merge or 3-way merge based on number of params
|
||||
merge_mode = 2 #if args.other == None else 3
|
||||
|
||||
profiles = [args.files, [args.other]]
|
||||
apparmor.aa.init_aa()
|
||||
|
||||
profiles = args.files
|
||||
|
||||
profiledir = args.dir
|
||||
if profiledir:
|
||||
@@ -87,61 +82,29 @@ def find_files_from_profiles(profiles):
|
||||
return profile_to_filename
|
||||
|
||||
def main():
|
||||
profiles_to_merge = set()
|
||||
base_profile_to_file = find_profiles_from_files(profiles)
|
||||
|
||||
base_files, other_files = profiles
|
||||
|
||||
base_profile_to_file = find_profiles_from_files(base_files)
|
||||
|
||||
profiles_to_merge = profiles_to_merge.union(set(base_profile_to_file.keys()))
|
||||
|
||||
other_profile_to_file = dict()
|
||||
|
||||
if merge_mode == 3:
|
||||
other_profile_to_file = find_profiles_from_files(other_files)
|
||||
profiles_to_merge.add(other_profile_to_file.keys())
|
||||
profiles_to_merge = set(base_profile_to_file.keys())
|
||||
|
||||
user_profile_to_file = find_files_from_profiles(profiles_to_merge)
|
||||
|
||||
# print(base_files,"\n",other_files)
|
||||
# print(base_profile_to_file,"\n",other_profile_to_file,"\n",user_profile_to_file)
|
||||
# print(profiles_to_merge)
|
||||
|
||||
for profile_name in profiles_to_merge:
|
||||
aaui.UI_Info("\n\n" + _("Merging profile for %s" % profile_name))
|
||||
user_file = user_profile_to_file[profile_name]
|
||||
base_file = base_profile_to_file.get(profile_name, None)
|
||||
other_file = None
|
||||
|
||||
if merge_mode == 3:
|
||||
other_file = other_profile_to_file.get(profile_name, None)
|
||||
|
||||
if base_file == None:
|
||||
if other_file == None:
|
||||
continue
|
||||
|
||||
act([user_file, other_file, None], 2, profile_name)
|
||||
else:
|
||||
if other_file == None:
|
||||
act([user_file, base_file, None], 2, profile_name)
|
||||
else:
|
||||
act([user_file, base_file, other_file], 3, profile_name)
|
||||
act([user_file, base_file], profile_name)
|
||||
|
||||
reset_aa()
|
||||
|
||||
def act(files, merge_mode, merging_profile):
|
||||
def act(files, merging_profile):
|
||||
mergeprofiles = Merge(files)
|
||||
#Get rid of common/superfluous stuff
|
||||
mergeprofiles.clear_common()
|
||||
|
||||
# if not args.auto:
|
||||
if 1 == 1: # workaround to avoid lots of whitespace changes
|
||||
if merge_mode == 3:
|
||||
mergeprofiles.ask_the_questions('other', merging_profile)
|
||||
|
||||
mergeprofiles.clear_common()
|
||||
|
||||
mergeprofiles.ask_the_questions('base', merging_profile)
|
||||
mergeprofiles.ask_merge_questions()
|
||||
|
||||
q = aaui.PromptQuestion()
|
||||
q.title = _('Changed Local Profiles')
|
||||
@@ -172,7 +135,7 @@ def act(files, merge_mode, merging_profile):
|
||||
|
||||
class Merge(object):
|
||||
def __init__(self, profiles):
|
||||
user, base, other = profiles
|
||||
user, base = profiles
|
||||
|
||||
#Read and parse base profile and save profile data, include data from it and reset them
|
||||
apparmor.aa.read_profile(base, True)
|
||||
@@ -180,12 +143,6 @@ class Merge(object):
|
||||
|
||||
reset_aa()
|
||||
|
||||
#Read and parse other profile and save profile data, include data from it and reset them
|
||||
if merge_mode == 3:
|
||||
apparmor.aa.read_profile(other, True)
|
||||
self.other = cleanprofile.Prof(other)
|
||||
reset_aa()
|
||||
|
||||
#Read and parse user profile
|
||||
apparmor.aa.read_profile(user, True)
|
||||
self.user = cleanprofile.Prof(user)
|
||||
@@ -193,67 +150,18 @@ class Merge(object):
|
||||
def clear_common(self):
|
||||
deleted = 0
|
||||
|
||||
if merge_mode == 3:
|
||||
#Remove off the parts in other profile which are common/superfluous from user profile
|
||||
user_other = cleanprofile.CleanProf(False, self.user, self.other)
|
||||
deleted += user_other.compare_profiles()
|
||||
|
||||
#Remove off the parts in base profile which are common/superfluous from user profile
|
||||
user_base = cleanprofile.CleanProf(False, self.user, self.base)
|
||||
deleted += user_base.compare_profiles()
|
||||
|
||||
if merge_mode == 3:
|
||||
#Remove off the parts in other profile which are common/superfluous from base profile
|
||||
base_other = cleanprofile.CleanProf(False, self.base, self.other)
|
||||
deleted += base_other.compare_profiles()
|
||||
def ask_merge_questions(self):
|
||||
other = self.base
|
||||
log_dict = {'merge': other.aa}
|
||||
|
||||
def ask_conflict_mode(self, profile, hat, old_profile, merge_profile):
|
||||
'''ask user about conflicting exec rules'''
|
||||
for oldrule in old_profile['file'].rules:
|
||||
conflictingrules = merge_profile['file'].get_exec_conflict_rules(oldrule)
|
||||
|
||||
if conflictingrules.rules:
|
||||
q = aaui.PromptQuestion()
|
||||
q.headers = [_('Path'), oldrule.path.regex]
|
||||
q.headers += [_('Select the appropriate mode'), '']
|
||||
options = []
|
||||
options.append(oldrule.get_clean())
|
||||
for rule in conflictingrules.rules:
|
||||
options.append(rule.get_clean())
|
||||
q.options = options
|
||||
q.functions = ['CMD_ALLOW', 'CMD_ABORT']
|
||||
done = False
|
||||
while not done:
|
||||
ans, selected = q.promptUser()
|
||||
if ans == 'CMD_ALLOW':
|
||||
if selected == 0:
|
||||
pass # just keep the existing rule
|
||||
elif selected > 0:
|
||||
# replace existing rule with merged one
|
||||
old_profile['file'].delete(oldrule)
|
||||
old_profile['file'].add(conflictingrules.rules[selected - 1])
|
||||
else:
|
||||
raise AppArmorException(_('Unknown selection'))
|
||||
|
||||
for rule in conflictingrules.rules:
|
||||
merge_profile['file'].delete(rule) # make sure aa-mergeprof doesn't ask to add conflicting rules later
|
||||
|
||||
done = True
|
||||
|
||||
def ask_the_questions(self, other, profile):
|
||||
aa = self.user.aa # keep references so that the code in this function can use the short name
|
||||
changed = apparmor.aa.changed # (and be more in sync with aa.py ask_the_questions())
|
||||
|
||||
if other == 'other':
|
||||
other = self.other
|
||||
else:
|
||||
other = self.base
|
||||
#print(other.aa)
|
||||
|
||||
#Add the file-wide includes from the other profile to the user profile
|
||||
apparmor.aa.loadincludes()
|
||||
done = False
|
||||
|
||||
#Add the file-wide includes from the other profile to the user profile
|
||||
options = []
|
||||
for inc in other.filelist[other.filename]['include'].keys():
|
||||
if not inc in self.user.filelist[self.user.filename]['include'].keys():
|
||||
@@ -281,211 +189,10 @@ class Merge(object):
|
||||
elif ans == 'CMD_FINISHED':
|
||||
return
|
||||
|
||||
sev_db = apparmor.aa.sev_db
|
||||
if not sev_db:
|
||||
sev_db = apparmor.severity.Severity(apparmor.aa.CONFDIR + '/severity.db', _('unknown'))
|
||||
if not apparmor.aa.sev_db:
|
||||
apparmor.aa.sev_db = apparmor.severity.Severity(apparmor.aa.CONFDIR + '/severity.db', _('unknown'))
|
||||
|
||||
sev_db.unload_variables()
|
||||
sev_db.load_variables(get_profile_filename(profile))
|
||||
|
||||
for hat in sorted(other.aa[profile].keys()):
|
||||
|
||||
if not aa[profile].get(hat):
|
||||
ans = ''
|
||||
while ans not in ['CMD_ADDHAT', 'CMD_ADDSUBPROFILE', 'CMD_DENY']:
|
||||
q = aaui.PromptQuestion()
|
||||
q.headers += [_('Profile'), profile]
|
||||
|
||||
if other.aa[profile][hat]['profile']:
|
||||
q.headers += [_('Requested Subprofile'), hat]
|
||||
q.functions.append('CMD_ADDSUBPROFILE')
|
||||
else:
|
||||
q.headers += [_('Requested Hat'), hat]
|
||||
q.functions.append('CMD_ADDHAT')
|
||||
|
||||
q.functions += ['CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED']
|
||||
|
||||
q.default = 'CMD_DENY'
|
||||
|
||||
ans = q.promptUser()[0]
|
||||
|
||||
if ans == 'CMD_FINISHED':
|
||||
return
|
||||
|
||||
if ans == 'CMD_DENY':
|
||||
continue # don't ask about individual rules if the user doesn't want the additional subprofile/hat
|
||||
|
||||
if other.aa[profile][hat]['profile']:
|
||||
aa[profile][hat] = profile_storage(profile, hat, 'mergeprof ask_the_questions() - missing subprofile')
|
||||
aa[profile][hat]['profile'] = True
|
||||
else:
|
||||
aa[profile][hat] = profile_storage(profile, hat, 'mergeprof ask_the_questions() - missing hat')
|
||||
aa[profile][hat]['profile'] = False
|
||||
|
||||
#Add the includes from the other profile to the user profile
|
||||
done = False
|
||||
|
||||
options = []
|
||||
for inc in other.aa[profile][hat]['include'].keys():
|
||||
if not inc in aa[profile][hat]['include'].keys():
|
||||
options.append('#include <%s>' %inc)
|
||||
|
||||
default_option = 1
|
||||
|
||||
q = aaui.PromptQuestion()
|
||||
q.options = options
|
||||
q.selected = default_option - 1
|
||||
q.headers = [_('File includes'), _('Select the ones you wish to add')]
|
||||
q.functions = ['CMD_ALLOW', 'CMD_IGNORE_ENTRY', 'CMD_ABORT', 'CMD_FINISHED']
|
||||
q.default = 'CMD_ALLOW'
|
||||
|
||||
while not done and options:
|
||||
ans, selected = q.promptUser()
|
||||
if ans == 'CMD_IGNORE_ENTRY':
|
||||
done = True
|
||||
elif ans == 'CMD_ALLOW':
|
||||
selection = options[selected]
|
||||
inc = re_match_include(selection)
|
||||
deleted = apparmor.aa.delete_duplicates(aa[profile][hat], inc)
|
||||
aa[profile][hat]['include'][inc] = True
|
||||
options.pop(selected)
|
||||
aaui.UI_Info(_('Adding %s to the file.') % selection)
|
||||
if deleted:
|
||||
aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted)
|
||||
elif ans == 'CMD_FINISHED':
|
||||
return
|
||||
|
||||
# check for and ask about conflicting exec modes
|
||||
self.ask_conflict_mode(profile, hat, aa[profile][hat], other.aa[profile][hat])
|
||||
|
||||
for ruletype in apparmor.aa.ruletypes:
|
||||
if other.aa[profile][hat].get(ruletype, False): # needed until we have proper profile initialization
|
||||
for rule_obj in other.aa[profile][hat][ruletype].rules:
|
||||
|
||||
if is_known_rule(aa[profile][hat], ruletype, rule_obj):
|
||||
continue
|
||||
|
||||
default_option = 1
|
||||
options = []
|
||||
newincludes = match_includes(aa[profile][hat], ruletype, rule_obj)
|
||||
q = aaui.PromptQuestion()
|
||||
if newincludes:
|
||||
options += list(map(lambda inc: '#include <%s>' % inc, sorted(set(newincludes))))
|
||||
|
||||
if ruletype == 'file' and rule_obj.path:
|
||||
options += propose_file_rules(aa[profile][hat], rule_obj)
|
||||
else:
|
||||
options.append(rule_obj.get_clean())
|
||||
|
||||
done = False
|
||||
while not done:
|
||||
q.options = options
|
||||
q.selected = default_option - 1
|
||||
q.headers = [_('Profile'), combine_name(profile, hat)]
|
||||
q.headers += rule_obj.logprof_header()
|
||||
|
||||
# Load variables into sev_db? Not needed/used for capabilities and network rules.
|
||||
severity = rule_obj.severity(sev_db)
|
||||
if severity != sev_db.NOT_IMPLEMENTED:
|
||||
q.headers += [_('Severity'), severity]
|
||||
|
||||
q.functions = available_buttons(rule_obj)
|
||||
q.default = q.functions[0]
|
||||
|
||||
ans, selected = q.promptUser()
|
||||
selection = options[selected]
|
||||
if ans == 'CMD_IGNORE_ENTRY':
|
||||
done = True
|
||||
break
|
||||
|
||||
elif ans == 'CMD_FINISHED':
|
||||
return
|
||||
|
||||
elif ans.startswith('CMD_AUDIT'):
|
||||
if ans == 'CMD_AUDIT_NEW':
|
||||
rule_obj.audit = True
|
||||
rule_obj.raw_rule = None
|
||||
else:
|
||||
rule_obj.audit = False
|
||||
rule_obj.raw_rule = None
|
||||
|
||||
options = set_options_audit_mode(rule_obj, options)
|
||||
|
||||
elif ans == 'CMD_ALLOW':
|
||||
done = True
|
||||
changed[profile] = True
|
||||
|
||||
inc = re_match_include(selection)
|
||||
if inc:
|
||||
deleted = delete_duplicates(aa[profile][hat], inc)
|
||||
|
||||
aa[profile][hat]['include'][inc] = True
|
||||
|
||||
aaui.UI_Info(_('Adding %s to profile.') % selection)
|
||||
if deleted:
|
||||
aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted)
|
||||
|
||||
else:
|
||||
rule_obj = selection_to_rule_obj(rule_obj, selection)
|
||||
deleted = aa[profile][hat][ruletype].add(rule_obj, cleanup=True)
|
||||
|
||||
aaui.UI_Info(_('Adding %s to profile.') % rule_obj.get_clean())
|
||||
if deleted:
|
||||
aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted)
|
||||
|
||||
elif ans == 'CMD_DENY':
|
||||
if re_match_include(selection):
|
||||
aaui.UI_Important("Denying via an include file isn't supported by the AppArmor tools")
|
||||
|
||||
else:
|
||||
done = True
|
||||
changed[profile] = True
|
||||
|
||||
rule_obj = selection_to_rule_obj(rule_obj, selection)
|
||||
rule_obj.deny = True
|
||||
rule_obj.raw_rule = None # reset raw rule after manually modifying rule_obj
|
||||
deleted = aa[profile][hat][ruletype].add(rule_obj, cleanup=True)
|
||||
aaui.UI_Info(_('Adding %s to profile.') % rule_obj.get_clean())
|
||||
if deleted:
|
||||
aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted)
|
||||
|
||||
elif ans == 'CMD_GLOB':
|
||||
if not re_match_include(selection):
|
||||
globbed_rule_obj = selection_to_rule_obj(rule_obj, selection)
|
||||
globbed_rule_obj.glob()
|
||||
options, default_option = add_to_options(options, globbed_rule_obj.get_raw())
|
||||
|
||||
elif ans == 'CMD_GLOBEXT':
|
||||
if not re_match_include(selection):
|
||||
globbed_rule_obj = selection_to_rule_obj(rule_obj, selection)
|
||||
globbed_rule_obj.glob_ext()
|
||||
options, default_option = add_to_options(options, globbed_rule_obj.get_raw())
|
||||
|
||||
elif ans == 'CMD_NEW':
|
||||
if not re_match_include(selection):
|
||||
edit_rule_obj = selection_to_rule_obj(rule_obj, selection)
|
||||
prompt, oldpath = edit_rule_obj.edit_header()
|
||||
|
||||
newpath = aaui.UI_GetString(prompt, oldpath)
|
||||
if newpath:
|
||||
try:
|
||||
input_matches_path = rule_obj.validate_edit(newpath) # note that we check against the original rule_obj here, not edit_rule_obj (which might be based on a globbed path)
|
||||
except AppArmorException:
|
||||
aaui.UI_Important(_('The path you entered is invalid (not starting with / or a variable)!'))
|
||||
continue
|
||||
|
||||
if not input_matches_path:
|
||||
ynprompt = _('The specified path does not match this log entry:\n\n Log Entry: %(path)s\n Entered Path: %(ans)s\nDo you really want to use this path?') % { 'path': oldpath, 'ans': newpath }
|
||||
key = aaui.UI_YesNo(ynprompt, 'n')
|
||||
if key == 'n':
|
||||
continue
|
||||
|
||||
edit_rule_obj.store_edit(newpath)
|
||||
options, default_option = add_to_options(options, edit_rule_obj.get_raw())
|
||||
apparmor.aa.user_globs[newpath] = AARE(newpath, True)
|
||||
|
||||
else:
|
||||
done = False
|
||||
apparmor.aa.ask_the_questions(log_dict)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
@@ -341,7 +341,7 @@ sub send_message {
|
||||
|
||||
# 'system' uses execvp() so no shell metacharacters here.
|
||||
# $notify_exe is an absolute path so execvp won't search PATH.
|
||||
system "$notify_exe", "-i", "gtk-dialog-warning", "-u", "critical", "--", "AppArmor Message", "$msg";
|
||||
system "$notify_exe", "-i", "gtk-dialog-warning", "-u", "normal", "--", "AppArmor Message", "$msg";
|
||||
my $exit_code = $? >> 8;
|
||||
exit($exit_code);
|
||||
}
|
||||
|
108
utils/aa-remove-unknown
Normal file
108
utils/aa-remove-unknown
Normal file
@@ -0,0 +1,108 @@
|
||||
#!/bin/sh
|
||||
# ----------------------------------------------------------------------
|
||||
# Copyright (c) 2017 Canonical Ltd. (All rights reserved)
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of version 2 of the GNU General Public
|
||||
# License published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
APPARMOR_FUNCTIONS=/lib/apparmor/rc.apparmor.functions
|
||||
APPARMORFS=/sys/kernel/security/apparmor
|
||||
PROFILES="${APPARMORFS}/profiles"
|
||||
REMOVE="${APPARMORFS}/.remove"
|
||||
|
||||
DRY_RUN=0
|
||||
|
||||
. $APPARMOR_FUNCTIONS
|
||||
|
||||
usage() {
|
||||
local progname="$1"
|
||||
local rc="$2"
|
||||
local msg="usage: ${progname} [options]\n
|
||||
Remove profiles unknown to the system
|
||||
|
||||
Options:
|
||||
-h, --help Show this help message and exit
|
||||
-n Dry run; don't remove profiles"
|
||||
|
||||
if [ "$rc" -ne 0 ] ; then
|
||||
echo "$msg" 1>&2
|
||||
else
|
||||
echo "$msg"
|
||||
fi
|
||||
|
||||
exit "$rc"
|
||||
}
|
||||
|
||||
if [ "$#" -gt 1 ] ; then
|
||||
usage "$0" 1
|
||||
elif [ "$#" -eq 1 ] ; then
|
||||
if [ "$1" = "-h" -o "$1" = "--help" ] ; then
|
||||
usage "$0" 0
|
||||
elif [ "$1" = "-n" ] ; then
|
||||
DRY_RUN=1
|
||||
else
|
||||
usage "$0" 1
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
# We can't use a -r test here because while $PROFILES is world-readable,
|
||||
# apparmorfs may still return EACCES from open()
|
||||
#
|
||||
# We have to do this check because error checking awk's getline() below is
|
||||
# tricky and, as is, results in an infinite loop when apparmorfs returns an
|
||||
# error from open().
|
||||
if ! IFS= read line < "$PROFILES" ; then
|
||||
echo "ERROR: Unable to read apparmorfs profiles file" 1>&2
|
||||
exit 1
|
||||
elif [ ! -w "$REMOVE" ] ; then
|
||||
echo "ERROR: Unable to write to apparmorfs remove file" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Clean out running profiles not associated with the current profile
|
||||
# set, excluding the libvirt dynamically generated profiles.
|
||||
# Note that we reverse sort the list of profiles to remove to
|
||||
# ensure that child profiles (e.g. hats) are removed before the
|
||||
# parent. We *do* need to remove the child profile and not rely
|
||||
# on removing the parent profile when the profile has had its
|
||||
# child profile names changed.
|
||||
profiles_names_list | awk '
|
||||
BEGIN {
|
||||
while (getline < "'${PROFILES}'" ) {
|
||||
str = sub(/ \((enforce|complain)\)$/, "", $0);
|
||||
if (match($0, /^libvirt-[0-9a-f\-]+$/) == 0)
|
||||
arr[$str] = $str
|
||||
}
|
||||
}
|
||||
|
||||
{ if (length(arr[$0]) > 0) { delete arr[$0] } }
|
||||
|
||||
END {
|
||||
for (key in arr)
|
||||
if (length(arr[key]) > 0) {
|
||||
printf("%s\n", arr[key])
|
||||
}
|
||||
}
|
||||
' | LC_COLLATE=C sort -r | \
|
||||
while IFS= read profile ; do
|
||||
if [ "$DRY_RUN" -ne 0 ]; then
|
||||
echo "Would remove '${profile}'"
|
||||
else
|
||||
echo "Removing '${profile}'"
|
||||
echo -n "$profile" > "${REMOVE}"
|
||||
fi
|
||||
done
|
||||
|
||||
# will not catch all errors, but still better than nothing
|
||||
exit $?
|
51
utils/aa-remove-unknown.pod
Normal file
51
utils/aa-remove-unknown.pod
Normal file
@@ -0,0 +1,51 @@
|
||||
=pod
|
||||
|
||||
=head1 NAME
|
||||
|
||||
aa-remove-unknown - remove unknown AppArmor profiles
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
B<aa-remove-unknown> [option]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
B<aa-remove-unknown> will inventory all profiles in /etc/apparmor.d/, compare
|
||||
that list to the profiles currently loaded into the kernel, and then remove all
|
||||
of the loaded profiles that were not found in /etc/apparmor.d/. It will also
|
||||
report the name of each profile that it removes on standard out.
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
=over 4
|
||||
|
||||
=item -h, --help
|
||||
|
||||
displays a short usage statement.
|
||||
|
||||
=item -n
|
||||
|
||||
dry run; only prints the names of profiles that would be removed
|
||||
|
||||
=back
|
||||
|
||||
=head1 EXAMPLES
|
||||
|
||||
$ sudo ./aa-remove-unknown -n
|
||||
Would remove 'test//null-/usr/bin/whoami'
|
||||
Would remove 'test'
|
||||
|
||||
$ sudo ./aa-remove-unknown
|
||||
Removing 'test//null-/usr/bin/whoami'
|
||||
Removing 'test'
|
||||
|
||||
=head1 BUGS
|
||||
|
||||
None. Please report any you find to Launchpad at
|
||||
L<https://bugs.launchpad.net/apparmor/+filebug>.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
apparmor(7)
|
||||
|
||||
=cut
|
@@ -40,6 +40,7 @@ args = parser.parse_args()
|
||||
|
||||
paranoid = args.paranoid
|
||||
|
||||
aa.init_aa()
|
||||
aa_mountpoint = aa.check_for_apparmor()
|
||||
if not aa_mountpoint:
|
||||
raise aa.AppArmorException(_("It seems AppArmor was not started. Please enable AppArmor and try again."))
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.com>
|
||||
# Copyright (C) 2014-2016 Christian Boltz <apparmor@cboltz.de>
|
||||
# Copyright (C) 2014-2017 Christian Boltz <apparmor@cboltz.de>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of version 2 of the GNU General Public
|
||||
@@ -73,16 +73,14 @@ _ = init_translation()
|
||||
# Setup logging incase of debugging is enabled
|
||||
debug_logger = DebugLogger('aa')
|
||||
|
||||
CONFDIR = '/etc/apparmor'
|
||||
running_under_genprof = False
|
||||
unimplemented_warning = False
|
||||
|
||||
# The database for severity
|
||||
sev_db = None
|
||||
# The file to read log messages from
|
||||
### Was our
|
||||
logfile = None
|
||||
|
||||
CONFDIR = None
|
||||
conf = None
|
||||
cfg = None
|
||||
repo_cfg = None
|
||||
|
||||
@@ -99,12 +97,7 @@ existing_profiles = dict()
|
||||
# format: user_globs['/foo*'] = AARE('/foo*')
|
||||
user_globs = {}
|
||||
|
||||
# The key for representing bare "file," rules
|
||||
ALL = '\0ALL'
|
||||
|
||||
## Variables used under logprof
|
||||
### Were our
|
||||
t = hasher() # dict()
|
||||
transitions = hasher()
|
||||
|
||||
aa = hasher() # Profiles originally in sd, replace by aa
|
||||
@@ -112,15 +105,12 @@ original_aa = hasher()
|
||||
extras = hasher() # Inactive profiles from extras
|
||||
### end our
|
||||
log = []
|
||||
pid = dict()
|
||||
log_pid = dict() # handed over to ReadLog, gets filled in logparser.py. The only case the previous content of this variable _might_(?) be used is aa-genprof (multiple do_logprof_pass() runs)
|
||||
|
||||
seen = hasher() # dir()
|
||||
profile_changes = hasher()
|
||||
prelog = hasher()
|
||||
log_dict = hasher() # dict()
|
||||
changed = dict()
|
||||
created = []
|
||||
skip = hasher()
|
||||
helpers = dict() # Preserve this between passes # was our
|
||||
### logprof ends
|
||||
|
||||
@@ -1486,16 +1476,17 @@ def order_globs(globs, original_path):
|
||||
|
||||
return globs
|
||||
|
||||
def ask_the_questions():
|
||||
def ask_the_questions(log_dict):
|
||||
for aamode in sorted(log_dict.keys()):
|
||||
# Describe the type of changes
|
||||
if aamode == 'PERMITTING':
|
||||
aaui.UI_Info(_('Complain-mode changes:'))
|
||||
elif aamode == 'REJECTING':
|
||||
aaui.UI_Info(_('Enforce-mode changes:'))
|
||||
elif aamode == 'merge':
|
||||
pass # aa-mergeprof
|
||||
else:
|
||||
# This is so wrong!
|
||||
fatal_error(_('Invalid mode found: %s') % aamode)
|
||||
raise AppArmorBug(_('Invalid mode found: %s') % aamode)
|
||||
|
||||
for profile in sorted(log_dict[aamode].keys()):
|
||||
# Update the repo profiles
|
||||
@@ -1513,16 +1504,83 @@ def ask_the_questions():
|
||||
|
||||
for hat in hats:
|
||||
|
||||
if not aa[profile].get(hat).get('file'):
|
||||
# Ignore log events for a non-existing profile or child profile. Such events can occour
|
||||
# after deleting a profile or hat manually, or when processing a foreign log.
|
||||
# (Checking for 'file' is a simplified way to check if it's a profile_storage() struct.)
|
||||
debug_logger.debug("Ignoring events for non-existing profile %s" % combine_name(profile, hat))
|
||||
continue
|
||||
if not aa[profile].get(hat, {}).get('file'):
|
||||
if aamode != 'merge':
|
||||
# Ignore log events for a non-existing profile or child profile. Such events can occour
|
||||
# after deleting a profile or hat manually, or when processing a foreign log.
|
||||
# (Checking for 'file' is a simplified way to check if it's a profile_storage() struct.)
|
||||
debug_logger.debug("Ignoring events for non-existing profile %s" % combine_name(profile, hat))
|
||||
continue
|
||||
|
||||
ans = ''
|
||||
while ans not in ['CMD_ADDHAT', 'CMD_ADDSUBPROFILE', 'CMD_DENY']:
|
||||
q = aaui.PromptQuestion()
|
||||
q.headers += [_('Profile'), profile]
|
||||
|
||||
if log_dict[aamode][profile][hat]['profile']:
|
||||
q.headers += [_('Requested Subprofile'), hat]
|
||||
q.functions.append('CMD_ADDSUBPROFILE')
|
||||
else:
|
||||
q.headers += [_('Requested Hat'), hat]
|
||||
q.functions.append('CMD_ADDHAT')
|
||||
|
||||
q.functions += ['CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED']
|
||||
|
||||
q.default = 'CMD_DENY'
|
||||
|
||||
ans = q.promptUser()[0]
|
||||
|
||||
if ans == 'CMD_FINISHED':
|
||||
return
|
||||
|
||||
if ans == 'CMD_DENY':
|
||||
continue # don't ask about individual rules if the user doesn't want the additional subprofile/hat
|
||||
|
||||
if log_dict[aamode][profile][hat]['profile']:
|
||||
aa[profile][hat] = profile_storage(profile, hat, 'mergeprof ask_the_questions() - missing subprofile')
|
||||
aa[profile][hat]['profile'] = True
|
||||
else:
|
||||
aa[profile][hat] = profile_storage(profile, hat, 'mergeprof ask_the_questions() - missing hat')
|
||||
aa[profile][hat]['profile'] = False
|
||||
|
||||
#Add the includes from the other profile to the user profile
|
||||
done = False
|
||||
|
||||
options = []
|
||||
for inc in log_dict[aamode][profile][hat]['include'].keys():
|
||||
if not inc in aa[profile][hat]['include'].keys():
|
||||
options.append('#include <%s>' %inc)
|
||||
|
||||
default_option = 1
|
||||
|
||||
q = aaui.PromptQuestion()
|
||||
q.options = options
|
||||
q.selected = default_option - 1
|
||||
q.headers = [_('File includes'), _('Select the ones you wish to add')]
|
||||
q.functions = ['CMD_ALLOW', 'CMD_IGNORE_ENTRY', 'CMD_ABORT', 'CMD_FINISHED']
|
||||
q.default = 'CMD_ALLOW'
|
||||
|
||||
while not done and options:
|
||||
ans, selected = q.promptUser()
|
||||
if ans == 'CMD_IGNORE_ENTRY':
|
||||
done = True
|
||||
elif ans == 'CMD_ALLOW':
|
||||
selection = options[selected]
|
||||
inc = re_match_include(selection)
|
||||
deleted = apparmor.aa.delete_duplicates(aa[profile][hat], inc)
|
||||
aa[profile][hat]['include'][inc] = True
|
||||
options.pop(selected)
|
||||
aaui.UI_Info(_('Adding %s to the file.') % selection)
|
||||
if deleted:
|
||||
aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted)
|
||||
elif ans == 'CMD_FINISHED':
|
||||
return
|
||||
|
||||
# check for and ask about conflicting exec modes
|
||||
ask_conflict_mode(profile, hat, aa[profile][hat], log_dict[aamode][profile][hat])
|
||||
|
||||
for ruletype in ruletypes:
|
||||
for rule_obj in log_dict[aamode][profile][hat][ruletype].rules:
|
||||
# XXX aa-mergeprof also has this code - if you change it, keep aa-mergeprof in sync!
|
||||
|
||||
if is_known_rule(aa[profile][hat], ruletype, rule_obj):
|
||||
continue
|
||||
@@ -1655,7 +1713,6 @@ def ask_the_questions():
|
||||
|
||||
else:
|
||||
done = False
|
||||
# END of code (mostly) shared with aa-mergeprof
|
||||
|
||||
def selection_to_rule_obj(rule_obj, selection):
|
||||
rule_type = type(rule_obj)
|
||||
@@ -1726,6 +1783,39 @@ def delete_duplicates(profile, incname):
|
||||
|
||||
return deleted
|
||||
|
||||
def ask_conflict_mode(profile, hat, old_profile, merge_profile):
|
||||
'''ask user about conflicting exec rules'''
|
||||
for oldrule in old_profile['file'].rules:
|
||||
conflictingrules = merge_profile['file'].get_exec_conflict_rules(oldrule)
|
||||
|
||||
if conflictingrules.rules:
|
||||
q = aaui.PromptQuestion()
|
||||
q.headers = [_('Path'), oldrule.path.regex]
|
||||
q.headers += [_('Select the appropriate mode'), '']
|
||||
options = []
|
||||
options.append(oldrule.get_clean())
|
||||
for rule in conflictingrules.rules:
|
||||
options.append(rule.get_clean())
|
||||
q.options = options
|
||||
q.functions = ['CMD_ALLOW', 'CMD_ABORT']
|
||||
done = False
|
||||
while not done:
|
||||
ans, selected = q.promptUser()
|
||||
if ans == 'CMD_ALLOW':
|
||||
if selected == 0:
|
||||
pass # just keep the existing rule
|
||||
elif selected > 0:
|
||||
# replace existing rule with merged one
|
||||
old_profile['file'].delete(oldrule)
|
||||
old_profile['file'].add(conflictingrules.rules[selected - 1])
|
||||
else:
|
||||
raise AppArmorException(_('Unknown selection'))
|
||||
|
||||
for rule in conflictingrules.rules:
|
||||
merge_profile['file'].delete(rule) # make sure aa-mergeprof doesn't ask to add conflicting rules later
|
||||
|
||||
done = True
|
||||
|
||||
def match_includes(profile, rule_type, rule_obj):
|
||||
newincludes = []
|
||||
for incname in include.keys():
|
||||
@@ -1767,11 +1857,9 @@ def set_logfile(filename):
|
||||
elif os.path.isdir(logfile):
|
||||
raise AppArmorException(_('%s is a directory. Please specify a file as logfile') % logfile)
|
||||
|
||||
def do_logprof_pass(logmark='', passno=0, pid=pid):
|
||||
def do_logprof_pass(logmark='', passno=0, log_pid=log_pid):
|
||||
# set up variables for this pass
|
||||
# t = hasher()
|
||||
# transitions = hasher()
|
||||
# seen = hasher() # XXX global?
|
||||
global log
|
||||
log = []
|
||||
global existing_profiles
|
||||
@@ -1779,9 +1867,7 @@ def do_logprof_pass(logmark='', passno=0, pid=pid):
|
||||
# aa = hasher()
|
||||
# profile_changes = hasher()
|
||||
# prelog = hasher()
|
||||
# log_dict = hasher()
|
||||
# changed = dict()
|
||||
# skip = hasher() # XXX global?
|
||||
# filelist = hasher()
|
||||
|
||||
aaui.UI_Info(_('Reading log entries from %s.') % logfile)
|
||||
@@ -1799,7 +1885,7 @@ def do_logprof_pass(logmark='', passno=0, pid=pid):
|
||||
## if not repo_cfg['repository'].get('enabled', False) or repo_cfg['repository]['enabled'] not in ['yes', 'no']:
|
||||
## UI_ask_to_enable_repo()
|
||||
|
||||
log_reader = apparmor.logparser.ReadLog(pid, logfile, existing_profiles, profile_dir, log)
|
||||
log_reader = apparmor.logparser.ReadLog(log_pid, logfile, existing_profiles, profile_dir, log)
|
||||
log = log_reader.read_log(logmark)
|
||||
#read_log(logmark)
|
||||
|
||||
@@ -1811,9 +1897,9 @@ def do_logprof_pass(logmark='', passno=0, pid=pid):
|
||||
for pid in sorted(profile_changes.keys()):
|
||||
set_process(pid, profile_changes[pid])
|
||||
|
||||
collapse_log()
|
||||
log_dict = collapse_log()
|
||||
|
||||
ask_the_questions()
|
||||
ask_the_questions(log_dict)
|
||||
|
||||
if aaui.UI_mode == 'yast':
|
||||
# To-Do
|
||||
@@ -2019,6 +2105,7 @@ def set_process(pid, profile):
|
||||
process.close()
|
||||
|
||||
def collapse_log():
|
||||
log_dict = hasher()
|
||||
for aamode in prelog.keys():
|
||||
for profile in prelog[aamode].keys():
|
||||
for hat in prelog[aamode][profile].keys():
|
||||
@@ -2099,6 +2186,8 @@ def collapse_log():
|
||||
if not is_known_rule(aa[profile][hat], 'signal', signal_event):
|
||||
log_dict[aamode][profile][hat]['signal'].add(signal_event)
|
||||
|
||||
return log_dict
|
||||
|
||||
def is_skippable_file(path):
|
||||
"""Returns True if filename matches something to be skipped (rpm or dpkg backup files, hidden files etc.)
|
||||
The list of skippable files needs to be synced with apparmor initscript and libapparmor _aa_is_blacklisted()
|
||||
@@ -3652,24 +3741,36 @@ def logger_path():
|
||||
|
||||
######Initialisations######
|
||||
|
||||
conf = apparmor.config.Config('ini', CONFDIR)
|
||||
cfg = conf.read_config('logprof.conf')
|
||||
def init_aa(confdir="/etc/apparmor"):
|
||||
global CONFDIR
|
||||
global conf
|
||||
global cfg
|
||||
global profile_dir
|
||||
global extra_profile_dir
|
||||
global parser
|
||||
|
||||
# prevent various failures if logprof.conf doesn't exist
|
||||
if not cfg.sections():
|
||||
cfg.add_section('settings')
|
||||
cfg.add_section('required_hats')
|
||||
if CONFDIR:
|
||||
return # config already initialized (and possibly changed afterwards), so don't overwrite the config variables
|
||||
|
||||
if cfg['settings'].get('default_owner_prompt', False):
|
||||
cfg['settings']['default_owner_prompt'] = ''
|
||||
CONFDIR = confdir
|
||||
conf = apparmor.config.Config('ini', CONFDIR)
|
||||
cfg = conf.read_config('logprof.conf')
|
||||
|
||||
profile_dir = conf.find_first_dir(cfg['settings'].get('profiledir')) or '/etc/apparmor.d'
|
||||
if not os.path.isdir(profile_dir):
|
||||
raise AppArmorException('Can\'t find AppArmor profiles')
|
||||
# prevent various failures if logprof.conf doesn't exist
|
||||
if not cfg.sections():
|
||||
cfg.add_section('settings')
|
||||
cfg.add_section('required_hats')
|
||||
|
||||
extra_profile_dir = conf.find_first_dir(cfg['settings'].get('inactive_profiledir')) or '/usr/share/apparmor/extra-profiles/'
|
||||
if cfg['settings'].get('default_owner_prompt', False):
|
||||
cfg['settings']['default_owner_prompt'] = ''
|
||||
|
||||
parser = conf.find_first_file(cfg['settings'].get('parser')) or '/sbin/apparmor_parser'
|
||||
if not os.path.isfile(parser) or not os.access(parser, os.EX_OK):
|
||||
raise AppArmorException('Can\'t find apparmor_parser')
|
||||
profile_dir = conf.find_first_dir(cfg['settings'].get('profiledir')) or '/etc/apparmor.d'
|
||||
if not os.path.isdir(profile_dir):
|
||||
raise AppArmorException('Can\'t find AppArmor profiles in %s' % (profile_dir))
|
||||
|
||||
extra_profile_dir = conf.find_first_dir(cfg['settings'].get('inactive_profiledir')) or '/usr/share/apparmor/extra-profiles/'
|
||||
|
||||
parser = conf.find_first_file(cfg['settings'].get('parser')) or '/sbin/apparmor_parser'
|
||||
if not os.path.isfile(parser) or not os.access(parser, os.EX_OK):
|
||||
raise AppArmorException('Can\'t find apparmor_parser at %s' % (parser))
|
||||
|
||||
|
@@ -16,6 +16,7 @@ import apparmor.aa as apparmor
|
||||
|
||||
class Prof(object):
|
||||
def __init__(self, filename):
|
||||
apparmor.init_aa()
|
||||
self.aa = apparmor.aa
|
||||
self.filelist = apparmor.filelist
|
||||
self.include = apparmor.include
|
||||
|
@@ -259,14 +259,11 @@ def open_file_read(path):
|
||||
return orig
|
||||
|
||||
|
||||
def verify_policy(policy):
|
||||
def verify_policy(policy, exe, base=None, include=None):
|
||||
'''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
|
||||
if not exe:
|
||||
warn("Could not find apparmor_parser. Skipping verify")
|
||||
return True
|
||||
|
||||
fn = ""
|
||||
# if policy starts with '/' and is one line, assume it is a path
|
||||
@@ -279,7 +276,14 @@ def verify_policy(policy):
|
||||
os.write(f, policy)
|
||||
os.close(f)
|
||||
|
||||
rc, out = cmd([exe, '-QTK', fn])
|
||||
command = [exe, '-QTK']
|
||||
if base:
|
||||
command.extend(['-b', base])
|
||||
if include:
|
||||
command.extend(['-I', include])
|
||||
command.append(fn)
|
||||
|
||||
rc, out = cmd(command)
|
||||
os.unlink(fn)
|
||||
if rc == 0:
|
||||
return True
|
||||
@@ -302,6 +306,22 @@ class AppArmorEasyProfile:
|
||||
if os.path.isfile(self.conffile):
|
||||
self._get_defaults()
|
||||
|
||||
self.parser_path = '/sbin/apparmor_parser'
|
||||
if opt.parser_path:
|
||||
self.parser_path = opt.parser_path
|
||||
elif not os.path.exists(self.parser_path):
|
||||
rc, self.parser_path = cmd(['which', 'apparmor_parser'])
|
||||
if rc != 0:
|
||||
self.parser_path = None
|
||||
|
||||
self.parser_base = "/etc/apparmor.d"
|
||||
if opt.parser_base:
|
||||
self.parser_base = opt.parser_base
|
||||
|
||||
self.parser_include = None
|
||||
if opt.parser_include:
|
||||
self.parser_include = opt.parser_include
|
||||
|
||||
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 \
|
||||
@@ -350,8 +370,6 @@ class AppArmorEasyProfile:
|
||||
if not 'policygroups' in self.dirs:
|
||||
raise AppArmorException("Could not find policygroups directory")
|
||||
|
||||
self.aa_topdir = "/etc/apparmor.d"
|
||||
|
||||
self.binary = binary
|
||||
if binary:
|
||||
if not valid_binary_path(binary):
|
||||
@@ -506,9 +524,15 @@ class AppArmorEasyProfile:
|
||||
|
||||
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)
|
||||
base = os.path.join(self.parser_base, "abstractions", abstraction)
|
||||
if not os.path.exists(base):
|
||||
if not self.parser_include:
|
||||
raise AppArmorException("%s does not exist" % base)
|
||||
|
||||
include = os.path.join(self.parser_include, "abstractions", abstraction)
|
||||
if not os.path.exists(include):
|
||||
raise AppArmorException("Neither %s nor %s exist" % (base, include))
|
||||
|
||||
return "#include <abstractions/%s>" % abstraction
|
||||
|
||||
def gen_variable_declaration(self, dec):
|
||||
@@ -661,7 +685,7 @@ class AppArmorEasyProfile:
|
||||
|
||||
if no_verify:
|
||||
debug("Skipping policy verification")
|
||||
elif not verify_policy(policy):
|
||||
elif not verify_policy(policy, self.parser_path, self.parser_base, self.parser_include):
|
||||
msg("\n" + policy)
|
||||
raise AppArmorException("Invalid policy")
|
||||
|
||||
@@ -804,6 +828,10 @@ def check_for_manifest_arg_append(option, opt_str, value, parser):
|
||||
|
||||
def add_parser_policy_args(parser):
|
||||
'''Add parser arguments'''
|
||||
parser.add_option("--parser",
|
||||
dest="parser_path",
|
||||
help="The path to the profile parser used for verification",
|
||||
metavar="PATH")
|
||||
parser.add_option("-a", "--abstractions",
|
||||
action="callback",
|
||||
callback=check_for_manifest_arg,
|
||||
@@ -811,6 +839,14 @@ def add_parser_policy_args(parser):
|
||||
dest="abstractions",
|
||||
help="Comma-separated list of abstractions",
|
||||
metavar="ABSTRACTIONS")
|
||||
parser.add_option("-b", "--base",
|
||||
dest="parser_base",
|
||||
help="Set the base directory for resolving abstractions",
|
||||
metavar="DIR")
|
||||
parser.add_option("-I", "--Include",
|
||||
dest="parser_include",
|
||||
help="Add a directory to the search path when resolving abstractions",
|
||||
metavar="DIR")
|
||||
parser.add_option("--read-path",
|
||||
action="callback",
|
||||
callback=check_for_manifest_arg_append,
|
||||
|
@@ -243,6 +243,8 @@ class ReadLog:
|
||||
if e['operation'] == 'change_hat':
|
||||
if aamode != 'HINT' and aamode != 'PERMITTING':
|
||||
return None
|
||||
if e['error_code'] == 1 and e['info'] == 'unconfined can not change_hat':
|
||||
return None
|
||||
profile = e['name2']
|
||||
#hat = None
|
||||
if '//' in e['name2']:
|
||||
|
@@ -37,14 +37,16 @@ RE_ACCESS_KEYWORDS = ( joint_access_keyword + # one of the access_keyword or
|
||||
RE_FLAG = '(?P<%s>(\S+|"[^"]+"|\(\s*\S+\s*\)|\(\s*"[^"]+"\)\s*))' # string without spaces, or quoted string, optionally wrapped in (...). %s is the match group name
|
||||
# plaintext version: | * | "* " | ( * ) | ( " * " ) |
|
||||
|
||||
# XXX this regex will allow repeated parameters, last one wins
|
||||
# XXX (the parser will reject such rules)
|
||||
RE_DBUS_DETAILS = re.compile(
|
||||
'^' +
|
||||
'(\s+(?P<access>' + RE_ACCESS_KEYWORDS + '))?' + # optional access keyword(s)
|
||||
'(\s+(bus\s*=\s*' + RE_FLAG % 'bus' + '))?' + # optional bus= system | session | AARE, (...) optional
|
||||
'(\s+(path\s*=\s*' + RE_FLAG % 'path' + '))?' + # optional path=AARE, (...) optional
|
||||
'(\s+(name\s*=\s*' + RE_FLAG % 'name' + '))?' + # optional name=AARE, (...) optional
|
||||
'(\s+(interface\s*=\s*' + RE_FLAG % 'interface' + '))?' + # optional interface=AARE, (...) optional
|
||||
'(\s+(member\s*=\s*' + RE_FLAG % 'member' + '))?' + # optional member=AARE, (...) optional
|
||||
'((\s+(bus\s*=\s*' + RE_FLAG % 'bus' + '))?|' + # optional bus= system | session | AARE, (...) optional
|
||||
'(\s+(path\s*=\s*' + RE_FLAG % 'path' + '))?|' + # optional path=AARE, (...) optional
|
||||
'(\s+(name\s*=\s*' + RE_FLAG % 'name' + '))?|' + # optional name=AARE, (...) optional
|
||||
'(\s+(interface\s*=\s*' + RE_FLAG % 'interface' + '))?|' + # optional interface=AARE, (...) optional
|
||||
'(\s+(member\s*=\s*' + RE_FLAG % 'member' + '))?|' + # optional member=AARE, (...) optional
|
||||
'(\s+(peer\s*=\s*\((,|\s)*' + # optional peer=( name=AARE and/or label=AARE ), (...) required
|
||||
'(' +
|
||||
'(' + '(,|\s)*' + ')' + # empty peer=()
|
||||
@@ -57,7 +59,7 @@ RE_DBUS_DETAILS = re.compile(
|
||||
'|' # or
|
||||
'(' + 'label\s*=\s*' + RE_PROFILE_NAME % 'peerlabel3' + '(,|\s)+' + 'name\s*=\s*' + RE_PROFILE_NAME % 'peername3' + ')' + # peer label + name (match name peername3/peerlabel3)
|
||||
')'
|
||||
'(,|\s)*\)))?'
|
||||
'(,|\s)*\)))?){0,6}'
|
||||
'\s*$')
|
||||
|
||||
|
||||
|
@@ -24,6 +24,8 @@ _ = init_translation()
|
||||
|
||||
class aa_tools:
|
||||
def __init__(self, tool_name, args):
|
||||
apparmor.init_aa()
|
||||
|
||||
self.name = tool_name
|
||||
self.profiledir = args.dir
|
||||
self.profiling = args.program
|
||||
|
@@ -64,8 +64,8 @@ def get_translated_hotkey(translated, cmsg=''):
|
||||
msg = 'PromptUser: ' + _('Invalid hotkey for')
|
||||
|
||||
# Originally (\S) was used but with translations it would not work :(
|
||||
if re.search('\((\S+)\)', translated, re.LOCALE):
|
||||
return re.search('\((\S+)\)', translated, re.LOCALE).groups()[0]
|
||||
if re.search('\((\S+)\)', translated):
|
||||
return re.search('\((\S+)\)', translated).groups()[0]
|
||||
else:
|
||||
if cmsg:
|
||||
raise AppArmorException(cmsg)
|
||||
|
@@ -23,11 +23,17 @@ include $(COMMONDIR)/Make.rules
|
||||
ifdef USE_SYSTEM
|
||||
LD_LIBRARY_PATH=
|
||||
PYTHONPATH=
|
||||
CONFDIR=
|
||||
BASEDIR=
|
||||
PARSER=
|
||||
else
|
||||
# PYTHON_DIST_BUILD_PATH based on libapparmor/swig/python/test/Makefile.am
|
||||
PYTHON_DIST_BUILD_PATH = ../../libraries/libapparmor/swig/python/build/$$($(PYTHON) -c "import distutils.util; import platform; print(\"lib.%s-%s\" %(distutils.util.get_platform(), platform.python_version()[:3]))")
|
||||
LD_LIBRARY_PATH=../../libraries/libapparmor/src/.libs/
|
||||
PYTHONPATH=..:$(PYTHON_DIST_BUILD_PATH)
|
||||
CONFDIR=$(CURDIR)
|
||||
BASEDIR=../../profiles/apparmor.d
|
||||
PARSER=../../parser/apparmor_parser
|
||||
endif
|
||||
|
||||
.PHONY: __libapparmor
|
||||
@@ -62,10 +68,10 @@ clean:
|
||||
rm -rf __pycache__/ .coverage htmlcov
|
||||
|
||||
check: __libapparmor
|
||||
export PYTHONPATH=$(PYTHONPATH) ; export LD_LIBRARY_PATH=$(LD_LIBRARY_PATH) ; export LC_ALL=C; $(foreach test, $(wildcard test-*.py), echo ; echo === $(test) === ; $(call pyalldo, $(test)))
|
||||
export PYTHONPATH=$(PYTHONPATH) LD_LIBRARY_PATH=$(LD_LIBRARY_PATH) LC_ALL=C __AA_CONFDIR=$(CONFDIR) __AA_BASEDIR=$(BASEDIR) __AA_PARSER=$(PARSER) ; $(foreach test, $(wildcard test-*.py), echo ; echo === $(test) === ; $(call pyalldo, $(test)))
|
||||
|
||||
.coverage: $(wildcard ../aa-* ../apparmor/*.py test-*.py) __libapparmor
|
||||
export PYTHONPATH=$(PYTHONPATH) ; export LD_LIBRARY_PATH=$(LD_LIBRARY_PATH); export LC_ALL=C; $(COVERAGE_IGNORE_FAILURES_CMD) ; $(foreach test, $(wildcard test-*.py), echo ; echo === $(test) === ; $(PYTHON) -m coverage run --branch -p $(test); )
|
||||
export PYTHONPATH=$(PYTHONPATH) LD_LIBRARY_PATH=$(LD_LIBRARY_PATH) LC_ALL=C __AA_CONFDIR=$(CONFDIR) __AA_BASEDIR=$(BASEDIR) __AA_PARSER=$(PARSER) ; $(COVERAGE_IGNORE_FAILURES_CMD) ; $(foreach test, $(wildcard test-*.py), echo ; echo === $(test) === ; $(PYTHON) -m coverage run --branch -p $(test); )
|
||||
$(PYTHON) -m coverage combine
|
||||
|
||||
coverage: .coverage
|
||||
|
@@ -103,6 +103,17 @@ def setup_regex_tests(test_class):
|
||||
stub_test.__doc__ = "test '%s': %s" % (line, desc)
|
||||
setattr(test_class, 'test_%d' % (i), stub_test)
|
||||
|
||||
def setup_aa(aa):
|
||||
confdir = os.getenv('__AA_CONFDIR')
|
||||
try:
|
||||
if confdir:
|
||||
aa.init_aa(confdir=confdir)
|
||||
else:
|
||||
aa.init_aa()
|
||||
except AttributeError:
|
||||
# apparmor.aa module versions <= 2.11 do not have the init_aa() method
|
||||
pass
|
||||
|
||||
def write_file(directory, file, contents):
|
||||
'''construct path, write contents to it, and return the constructed path'''
|
||||
path = os.path.join(directory, file)
|
||||
|
@@ -10,11 +10,11 @@
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
[settings]
|
||||
profiledir = /etc/apparmor.d /etc/subdomain.d
|
||||
inactive_profiledir = /usr/share/doc/apparmor-profiles/extras
|
||||
profiledir = ../../profiles/apparmor.d
|
||||
inactive_profiledir = ../../profiles/apparmor/profiles/extra
|
||||
logfiles = /var/log/audit/audit.log /var/log/syslog /var/log/messages
|
||||
|
||||
parser = /sbin/apparmor_parser /sbin/subdomain_parser
|
||||
parser = ../../parser/apparmor_parser
|
||||
ldd = /usr/bin/ldd
|
||||
logger = /bin/logger /usr/bin/logger
|
||||
|
||||
|
@@ -16,7 +16,7 @@ import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import unittest
|
||||
from common_test import AATest, setup_all_loops
|
||||
from common_test import AATest, setup_all_loops, setup_aa
|
||||
|
||||
import apparmor.aa as apparmor
|
||||
from common_test import read_file
|
||||
@@ -156,6 +156,7 @@ class MinitoolsTest(AATest):
|
||||
self.assertEqual(exp_content, real_content, 'Failed to cleanup profile properly')
|
||||
|
||||
|
||||
setup_aa(apparmor)
|
||||
setup_all_loops(__name__)
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=2)
|
||||
|
@@ -18,6 +18,8 @@ import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
import apparmor.easyprof as easyprof
|
||||
|
||||
topdir = None
|
||||
debugging = False
|
||||
|
||||
@@ -163,6 +165,23 @@ TEMPLATES_DIR="%s/templates"
|
||||
self.binary = "/opt/bin/foo"
|
||||
self.full_args = ['-c', self.conffile, self.binary]
|
||||
|
||||
# Check __AA_BASEDIR, which may be set by the Makefile, to see if
|
||||
# we should use a non-default base directory path to find
|
||||
# abstraction files
|
||||
#
|
||||
# NOTE: Individual tests can append another --base path to the
|
||||
# args list and override a base path set here
|
||||
base = os.getenv('__AA_BASEDIR')
|
||||
if base:
|
||||
self.full_args.append('--base=%s' % base)
|
||||
|
||||
# Check __AA_PARSER, which may be set by the Makefile, to see if
|
||||
# we should use a non-default apparmor_parser path to verify
|
||||
# policy
|
||||
parser = os.getenv('__AA_PARSER')
|
||||
if parser:
|
||||
self.full_args.append('--parser=%s' % parser)
|
||||
|
||||
if debugging:
|
||||
self.full_args.append('-d')
|
||||
|
||||
@@ -913,6 +932,94 @@ POLICYGROUPS_DIR="%s/templates"
|
||||
raise
|
||||
raise Exception ("abstraction '%s' should be invalid" % s)
|
||||
|
||||
def _create_tmp_base_dir(self, prefix='', abstractions=[], tunables=[]):
|
||||
'''Create a temporary base dir layout'''
|
||||
base_name = 'apparmor.d'
|
||||
if prefix:
|
||||
base_name = '%s-%s' % (prefix, base_name)
|
||||
base_dir = os.path.join(self.tmpdir, base_name)
|
||||
abstractions_dir = os.path.join(base_dir, 'abstractions')
|
||||
tunables_dir = os.path.join(base_dir, 'tunables')
|
||||
|
||||
os.mkdir(base_dir)
|
||||
os.mkdir(abstractions_dir)
|
||||
os.mkdir(tunables_dir)
|
||||
|
||||
for f in abstractions:
|
||||
contents = '''
|
||||
# Abstraction file for testing
|
||||
/%s r,
|
||||
''' % (f)
|
||||
open(os.path.join(abstractions_dir, f), 'w').write(contents)
|
||||
|
||||
for f in tunables:
|
||||
contents = '''
|
||||
# Tunable file for testing
|
||||
@{AA_TEST_%s}=foo
|
||||
''' % (f)
|
||||
open(os.path.join(tunables_dir, f), 'w').write(contents)
|
||||
|
||||
return base_dir
|
||||
|
||||
def test_genpolicy_abstractions_custom_base(self):
|
||||
'''Test genpolicy (custom base dir)'''
|
||||
abstraction = "custom-base-dir-test-abstraction"
|
||||
# The default template #includes the base abstraction and global
|
||||
# tunable so we need to create placeholders
|
||||
base = self._create_tmp_base_dir(abstractions=['base', abstraction], tunables=['global'])
|
||||
args = ['--abstractions=%s' % abstraction, '--base=%s' % base]
|
||||
|
||||
p = self._gen_policy(extra_args=args)
|
||||
search = "#include <abstractions/%s>" % abstraction
|
||||
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_custom_base_bad(self):
|
||||
'''Test genpolicy (custom base dir - bad base dirs)'''
|
||||
abstraction = "custom-base-dir-test-abstraction"
|
||||
bad = [ None, '/etc/apparmor.d', '/' ]
|
||||
for base in bad:
|
||||
try:
|
||||
args = ['--abstractions=%s' % abstraction]
|
||||
if base:
|
||||
args.append('--base=%s' % base)
|
||||
self._gen_policy(extra_args=args)
|
||||
except easyprof.AppArmorException:
|
||||
continue
|
||||
except Exception:
|
||||
raise
|
||||
raise Exception ("abstraction '%s' should be invalid" % abstraction)
|
||||
|
||||
def test_genpolicy_abstractions_custom_include(self):
|
||||
'''Test genpolicy (custom include dir)'''
|
||||
abstraction = "custom-include-dir-test-abstraction"
|
||||
# No need to create placeholders for the base abstraction or global
|
||||
# tunable since we're not adjusting the base directory
|
||||
include = self._create_tmp_base_dir(abstractions=[abstraction])
|
||||
args = ['--abstractions=%s' % abstraction, '--Include=%s' % include]
|
||||
p = self._gen_policy(extra_args=args)
|
||||
search = "#include <abstractions/%s>" % abstraction
|
||||
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_custom_include_bad(self):
|
||||
'''Test genpolicy (custom include dir - bad include dirs)'''
|
||||
abstraction = "custom-include-dir-test-abstraction"
|
||||
bad = [ None, '/etc/apparmor.d', '/' ]
|
||||
for include in bad:
|
||||
try:
|
||||
args = ['--abstractions=%s' % abstraction]
|
||||
if include:
|
||||
args.append('--Include=%s' % include)
|
||||
self._gen_policy(extra_args=args)
|
||||
except easyprof.AppArmorException:
|
||||
continue
|
||||
except Exception:
|
||||
raise
|
||||
raise Exception ("abstraction '%s' should be invalid" % abstraction)
|
||||
|
||||
def test_genpolicy_profile_name_bad(self):
|
||||
'''Test genpolicy (profile name - bad values)'''
|
||||
bad = [
|
||||
@@ -2568,40 +2675,16 @@ POLICYGROUPS_DIR="%s/templates"
|
||||
# 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, 'a').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)
|
||||
|
@@ -10,7 +10,7 @@
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
import unittest
|
||||
from common_test import AATest, setup_all_loops
|
||||
from common_test import AATest, setup_all_loops, setup_aa
|
||||
from common_test import read_file, write_file
|
||||
|
||||
import os
|
||||
@@ -784,8 +784,8 @@ class AaTest_get_file_perms_2(AATest):
|
||||
('/dev/null', {'allow': {'all': {'r', 'w', 'k'}, 'owner': set() }, 'deny': {'all':set(), 'owner': set()}, 'paths': {'/dev/null'} }),
|
||||
('/foo/bar', {'allow': {'all': {'r', 'w'}, 'owner': set() }, 'deny': {'all':set(), 'owner': set()}, 'paths': {'/foo/bar'} }), # exec perms not included
|
||||
('/no/thing', {'allow': {'all': set(), 'owner': set() }, 'deny': {'all':set(), 'owner': set()}, 'paths': set() }),
|
||||
('/usr/lib/ispell/', {'allow': {'all': {'r'}, 'owner': set() }, 'deny': {'all':set(), 'owner': set()}, 'paths': {'/usr/lib/ispell/', '/usr/lib{,32,64}/**'} }), # from abstractions/enchant
|
||||
('/usr/lib/aspell/*.so', {'allow': {'all': {'m', 'r'}, 'owner': set() }, 'deny': {'all':set(), 'owner': set()}, 'paths': {'/usr/lib/aspell/*', '/usr/lib/aspell/*.so', '/usr/lib{,32,64}/**'} }), # from abstractions/aspell via abstractions/enchant
|
||||
('/usr/lib/ispell/', {'allow': {'all': {'r'}, 'owner': set() }, 'deny': {'all':set(), 'owner': set()}, 'paths': {'/usr/lib/ispell/', '/{usr/,}lib{,32,64}/**'} }), # from abstractions/enchant
|
||||
('/usr/lib/aspell/*.so', {'allow': {'all': {'m', 'r'}, 'owner': set() }, 'deny': {'all':set(), 'owner': set()}, 'paths': {'/usr/lib/aspell/*', '/usr/lib/aspell/*.so', '/{usr/,}lib{,32,64}/**'} }), # from abstractions/aspell via abstractions/enchant
|
||||
]
|
||||
|
||||
def _run_test(self, params, expected):
|
||||
@@ -820,8 +820,8 @@ class AaTest_propose_file_rules(AATest):
|
||||
(['/usr/share/common-licenses/foo/bar', 'w'], ['/usr/share/common*/foo/* rw,', '/usr/share/common-licenses/** rw,', '/usr/share/common-licenses/foo/bar rw,'] ),
|
||||
(['/dev/null', 'wk'], ['/dev/null rwk,'] ),
|
||||
(['/foo/bar', 'rw'], ['/foo/bar rw,'] ),
|
||||
(['/usr/lib/ispell/', 'w'], ['/usr/lib{,32,64}/** rw,', '/usr/lib/ispell/ rw,'] ),
|
||||
(['/usr/lib/aspell/some.so', 'k'], ['/usr/lib/aspell/* mrk,', '/usr/lib/aspell/*.so mrk,', '/usr/lib{,32,64}/** mrk,', '/usr/lib/aspell/some.so mrk,'] ),
|
||||
(['/usr/lib/ispell/', 'w'], ['/{usr/,}lib{,32,64}/** rw,', '/usr/lib/ispell/ rw,'] ),
|
||||
(['/usr/lib/aspell/some.so', 'k'], ['/usr/lib/aspell/* mrk,', '/usr/lib/aspell/*.so mrk,', '/{usr/,}lib{,32,64}/** mrk,', '/usr/lib/aspell/some.so mrk,'] ),
|
||||
]
|
||||
|
||||
def _run_test(self, params, expected):
|
||||
@@ -855,6 +855,7 @@ class AaTest_propose_file_rules(AATest):
|
||||
proposals = propose_file_rules(profile, rule_obj)
|
||||
self.assertEqual(proposals, expected)
|
||||
|
||||
setup_aa(apparmor.aa)
|
||||
setup_all_loops(__name__)
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=2)
|
||||
|
@@ -24,7 +24,7 @@ class Test(unittest.TestCase):
|
||||
conf = ini_config.read_config('logprof.conf')
|
||||
logprof_sections = ['settings', 'repository', 'qualifiers', 'required_hats', 'defaulthat', 'globs']
|
||||
logprof_sections_options = ['profiledir', 'inactive_profiledir', 'logfiles', 'parser', 'ldd', 'logger', 'default_owner_prompt', 'custom_includes']
|
||||
logprof_settings_parser = '/sbin/apparmor_parser /sbin/subdomain_parser'
|
||||
logprof_settings_parser = '../../parser/apparmor_parser'
|
||||
|
||||
self.assertEqual(conf.sections(), logprof_sections)
|
||||
self.assertEqual(conf.options('settings'), logprof_sections_options)
|
||||
|
@@ -89,6 +89,10 @@ class DbusTestParse(DbusTest):
|
||||
('dbus peer=(, label = bar, name = foo ),' , exp(False, False, False, '', None , True , None, True, None, True, None, True, None, True, None, True, 'foo', False, 'bar,', False)), # XXX peerlabel includes the comma
|
||||
('dbus peer=( label = bar , name = foo ),' , exp(False, False, False, '', None , True , None, True, None, True, None, True, None, True, None, True, 'foo', False, 'bar', False)),
|
||||
('dbus peer=( label = "bar" name = "foo" ),' , exp(False, False, False, '', None , True , None, True, None, True, None, True, None, True, None, True, 'foo', False, 'bar', False)),
|
||||
('dbus path=/foo/bar bus=session,' , exp(False, False, False, '', None , True , 'session', False, '/foo/bar', False, None, True, None, True, None, True, None, True, None, True)),
|
||||
('dbus bus=system path=/foo/bar bus=session,' , exp(False, False, False, '', None , True , 'session', False, '/foo/bar', False, None, True, None, True, None, True, None, True, None, True)), # XXX bus= specified twice, last one wins
|
||||
('dbus send peer=(label="foo") bus=session,' , exp(False, False, False, '', {'send'}, False, 'session', False, None, True, None, True, None, True, None, True, None, True, 'foo', False)),
|
||||
('dbus bus=1 bus=2 bus=3 bus=4 bus=5 bus=6,' , exp(False, False, False, '', None , True , '6', False, None, True, None, True, None, True, None, True, None, True, None, True)), # XXX bus= specified multiple times, last one wins
|
||||
]
|
||||
|
||||
def _run_test(self, rawrule, expected):
|
||||
@@ -108,6 +112,8 @@ class DbusTestParseInvalid(DbusTest):
|
||||
('dbus peer=(label=foo) path=,' , AppArmorException),
|
||||
('dbus (invalid),' , AppArmorException),
|
||||
('dbus peer=,' , AppArmorException),
|
||||
('dbus bus=session bind bus=system,', AppArmorException),
|
||||
('dbus bus=1 bus=2 bus=3 bus=4 bus=5 bus=6 bus=7,', AppArmorException),
|
||||
]
|
||||
|
||||
def _run_test(self, rawrule, expected):
|
||||
|
@@ -10,7 +10,7 @@
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
import unittest
|
||||
from common_test import AATest, setup_all_loops, read_file
|
||||
from common_test import AATest, setup_all_loops, setup_aa, read_file
|
||||
|
||||
import os
|
||||
from apparmor.common import open_file_read
|
||||
@@ -214,7 +214,6 @@ class TestLogToProfile(AATest):
|
||||
apparmor.aa.log = dict()
|
||||
apparmor.aa.aa = apparmor.aa.hasher()
|
||||
apparmor.aa.prelog = apparmor.aa.hasher()
|
||||
apparmor.aa.log_dict = apparmor.aa.hasher()
|
||||
|
||||
profile = parsed_event['profile']
|
||||
hat = profile
|
||||
@@ -229,12 +228,12 @@ class TestLogToProfile(AATest):
|
||||
for root in log:
|
||||
apparmor.aa.handle_children('', '', root) # interactive for exec events!
|
||||
|
||||
apparmor.aa.collapse_log()
|
||||
log_dict = apparmor.aa.collapse_log()
|
||||
|
||||
apparmor.aa.filelist = apparmor.aa.hasher()
|
||||
apparmor.aa.filelist[profile_dummy_file]['profiles'][profile] = True
|
||||
|
||||
new_profile = apparmor.aa.serialize_profile(apparmor.aa.log_dict[aamode][profile], profile, None)
|
||||
new_profile = apparmor.aa.serialize_profile(log_dict[aamode][profile], profile, None)
|
||||
|
||||
expected_profile = read_file('%s.profile' % params)
|
||||
|
||||
@@ -268,6 +267,7 @@ print('Testing libapparmor test_multi tests...')
|
||||
TestLibapparmorTestMulti.tests = find_test_multi('../../libraries/libapparmor/testsuite/test_multi/')
|
||||
TestLogToProfile.tests = find_test_multi('../../libraries/libapparmor/testsuite/test_multi/')
|
||||
|
||||
setup_aa(apparmor.aa)
|
||||
setup_all_loops(__name__)
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=1) # reduced verbosity due to the big number of tests
|
||||
|
@@ -11,7 +11,7 @@
|
||||
|
||||
import apparmor.aa as aa
|
||||
import unittest
|
||||
from common_test import AAParseTest, setup_regex_tests
|
||||
from common_test import AAParseTest, setup_regex_tests, setup_aa
|
||||
|
||||
class BaseAAParseMountTest(AAParseTest):
|
||||
def setUp(self):
|
||||
@@ -39,6 +39,7 @@ class AAParseUmountTest(BaseAAParseMountTest):
|
||||
('unmount /mnt/external,', 'unmount with mount point'),
|
||||
]
|
||||
|
||||
setup_aa(aa)
|
||||
if __name__ == '__main__':
|
||||
setup_regex_tests(AAParseMountTest)
|
||||
setup_regex_tests(AAParseRemountTest)
|
||||
|
@@ -10,7 +10,7 @@
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
import unittest
|
||||
from common_test import AATest, setup_all_loops
|
||||
from common_test import AATest, setup_all_loops, setup_aa
|
||||
import apparmor.aa as apparmor
|
||||
|
||||
import os
|
||||
@@ -30,9 +30,6 @@ skip_startswith = (
|
||||
'generated_x/ambiguous-',
|
||||
'generated_x/dominate-',
|
||||
|
||||
# permissions before path
|
||||
'generated_perms_leading/',
|
||||
|
||||
# 'safe' and 'unsafe' keywords
|
||||
'generated_perms_safe/',
|
||||
|
||||
@@ -52,6 +49,7 @@ exception_not_raised = [
|
||||
'change_profile/onx_conflict_unsafe1.sd',
|
||||
'change_profile/onx_conflict_unsafe2.sd',
|
||||
|
||||
'dbus/bad_modifier_2.sd',
|
||||
'dbus/bad_regex_01.sd',
|
||||
'dbus/bad_regex_02.sd',
|
||||
'dbus/bad_regex_03.sd',
|
||||
@@ -154,6 +152,8 @@ exception_not_raised = [
|
||||
'vars/vars_dbus_bad_01.sd',
|
||||
'vars/vars_dbus_bad_02.sd',
|
||||
'vars/vars_dbus_bad_03.sd',
|
||||
'vars/vars_dbus_bad_04.sd',
|
||||
'vars/vars_dbus_bad_05.sd',
|
||||
'vars/vars_dbus_bad_06.sd',
|
||||
'vars/vars_dbus_bad_07.sd',
|
||||
'vars/vars_file_evaluation_7.sd',
|
||||
@@ -256,16 +256,75 @@ syntax_failure = [
|
||||
'file/ok_5.sd', # Invalid mode UX
|
||||
'file/ok_2.sd', # Invalid mode RWM
|
||||
'file/ok_4.sd', # Invalid mode iX
|
||||
'file/ok_embedded_spaces_4.sd', # \-escaped space
|
||||
'file/file/ok_embedded_spaces_4.sd', # \-escaped space
|
||||
'file/ok_quoted_4.sd', # quoted string including \"
|
||||
'xtrans/simple_ok_pix_1.sd', # Invalid mode pIx
|
||||
'xtrans/simple_ok_pux_1.sd', # Invalid mode rPux
|
||||
|
||||
# dbus regex mismatch
|
||||
'vars/vars_dbus_4.sd',
|
||||
'vars/vars_dbus_9.sd',
|
||||
'vars/vars_dbus_2.sd',
|
||||
# unexpected uppercase vs. lowercase in *x rules - generated_perms_leading directory
|
||||
'generated_perms_leading/exact-re-Puxtarget.sd',
|
||||
'generated_perms_leading/dominate-ownerCuxtarget2.sd',
|
||||
'generated_perms_leading/ambiguous-Cux.sd',
|
||||
'generated_perms_leading/dominate-ownerPux.sd',
|
||||
'generated_perms_leading/exact-re-ownerPux.sd',
|
||||
'generated_perms_leading/overlap-ownerCuxtarget.sd',
|
||||
'generated_perms_leading/exact-re-ownerCuxtarget.sd',
|
||||
'generated_perms_leading/dominate-Puxtarget2.sd',
|
||||
'generated_perms_leading/dominate-ownerCuxtarget.sd',
|
||||
'generated_perms_leading/dominate-ownerPuxtarget.sd',
|
||||
'generated_perms_leading/ambiguous-Pux.sd',
|
||||
'generated_perms_leading/ambiguous-Cuxtarget2.sd',
|
||||
'generated_perms_leading/exact-Puxtarget2.sd',
|
||||
'generated_perms_leading/ambiguous-ownerCux.sd',
|
||||
'generated_perms_leading/exact-ownerPux.sd',
|
||||
'generated_perms_leading/ambiguous-ownerPuxtarget.sd',
|
||||
'generated_perms_leading/exact-re-ownerPuxtarget.sd',
|
||||
'generated_perms_leading/exact-re-Cuxtarget.sd',
|
||||
'generated_perms_leading/exact-re-Puxtarget2.sd',
|
||||
'generated_perms_leading/dominate-Cux.sd',
|
||||
'generated_perms_leading/exact-re-ownerCuxtarget2.sd',
|
||||
'generated_perms_leading/ambiguous-ownerCuxtarget.sd',
|
||||
'generated_perms_leading/exact-re-Cuxtarget2.sd',
|
||||
'generated_perms_leading/ambiguous-Puxtarget.sd',
|
||||
'generated_perms_leading/overlap-Puxtarget.sd',
|
||||
'generated_perms_leading/ambiguous-Puxtarget2.sd',
|
||||
'generated_perms_leading/overlap-Puxtarget2.sd',
|
||||
'generated_perms_leading/exact-Puxtarget.sd',
|
||||
'generated_perms_leading/overlap-ownerPuxtarget.sd',
|
||||
'generated_perms_leading/exact-ownerCuxtarget.sd',
|
||||
'generated_perms_leading/exact-re-ownerCux.sd',
|
||||
'generated_perms_leading/exact-ownerPuxtarget2.sd',
|
||||
'generated_perms_leading/exact-ownerCux.sd',
|
||||
'generated_perms_leading/overlap-Cuxtarget2.sd',
|
||||
'generated_perms_leading/ambiguous-Cuxtarget.sd',
|
||||
'generated_perms_leading/ambiguous-ownerPuxtarget2.sd',
|
||||
'generated_perms_leading/dominate-ownerCux.sd',
|
||||
'generated_perms_leading/exact-Pux.sd',
|
||||
'generated_perms_leading/exact-Cuxtarget.sd',
|
||||
'generated_perms_leading/overlap-ownerCuxtarget2.sd',
|
||||
'generated_perms_leading/overlap-Pux.sd',
|
||||
'generated_perms_leading/overlap-ownerPux.sd',
|
||||
'generated_perms_leading/ambiguous-ownerCuxtarget2.sd',
|
||||
'generated_perms_leading/exact-re-Cux.sd',
|
||||
'generated_perms_leading/exact-re-Pux.sd',
|
||||
'generated_perms_leading/overlap-Cuxtarget.sd',
|
||||
'generated_perms_leading/exact-re-ownerPuxtarget2.sd',
|
||||
'generated_perms_leading/exact-Cuxtarget2.sd',
|
||||
'generated_perms_leading/exact-Cux.sd',
|
||||
'generated_perms_leading/overlap-Cux.sd',
|
||||
'generated_perms_leading/overlap-ownerCux.sd',
|
||||
'generated_perms_leading/exact-ownerPuxtarget.sd',
|
||||
'generated_perms_leading/dominate-Pux.sd',
|
||||
'generated_perms_leading/exact-ownerCuxtarget2.sd',
|
||||
'generated_perms_leading/dominate-Puxtarget.sd',
|
||||
'generated_perms_leading/ambiguous-ownerPux.sd',
|
||||
'generated_perms_leading/overlap-ownerPuxtarget2.sd',
|
||||
'generated_perms_leading/dominate-Cuxtarget2.sd',
|
||||
'generated_perms_leading/dominate-Cuxtarget.sd',
|
||||
'generated_perms_leading/dominate-ownerPuxtarget2.sd',
|
||||
|
||||
# escaping with \
|
||||
'file/ok_embedded_spaces_4.sd', # \-escaped space
|
||||
'file/file/ok_embedded_spaces_4.sd', # \-escaped space
|
||||
'file/ok_quoted_4.sd', # quoted string including \"
|
||||
|
||||
# misc
|
||||
'vars/vars_dbus_8.sd', # Path doesn't start with / or variable: {/@{TLDS}/foo,/com/@{DOMAINS}}
|
||||
@@ -399,6 +458,7 @@ def find_and_setup_test_profiles(profile_dir):
|
||||
print('Running %s parser simple_tests...' % len(TestParseParserTests.tests))
|
||||
|
||||
|
||||
setup_aa(apparmor)
|
||||
find_and_setup_test_profiles('../../parser/tst/simple_tests/')
|
||||
|
||||
setup_all_loops(__name__)
|
||||
|
@@ -11,7 +11,7 @@
|
||||
|
||||
import apparmor.aa as aa
|
||||
import unittest
|
||||
from common_test import AAParseTest, setup_regex_tests
|
||||
from common_test import AAParseTest, setup_regex_tests, setup_aa
|
||||
|
||||
class AAParsePivotRootTest(AAParseTest):
|
||||
def setUp(self):
|
||||
@@ -24,6 +24,7 @@ class AAParsePivotRootTest(AAParseTest):
|
||||
('pivot_root /old /new -> /usr/bin/child,', 'pivot_root child rule'),
|
||||
]
|
||||
|
||||
setup_aa(aa)
|
||||
if __name__ == '__main__':
|
||||
setup_regex_tests(AAParsePivotRootTest)
|
||||
unittest.main(verbosity=2)
|
||||
|
@@ -11,7 +11,7 @@
|
||||
|
||||
import apparmor.aa as aa
|
||||
import unittest
|
||||
from common_test import AATest, setup_all_loops
|
||||
from common_test import AATest, setup_all_loops, setup_aa
|
||||
from apparmor.common import AppArmorBug, AppArmorException
|
||||
|
||||
from apparmor.regex import ( strip_parenthesis, strip_quotes, parse_profile_start_line, re_match_include,
|
||||
@@ -502,6 +502,7 @@ class TestStripQuotes(AATest):
|
||||
|
||||
|
||||
|
||||
setup_aa(aa)
|
||||
setup_all_loops(__name__)
|
||||
if __name__ == '__main__':
|
||||
# these two are not converted to a tests[] loop yet
|
||||
|
@@ -11,7 +11,7 @@
|
||||
|
||||
import apparmor.aa as aa
|
||||
import unittest
|
||||
from common_test import AAParseTest, setup_regex_tests
|
||||
from common_test import AAParseTest, setup_regex_tests, setup_aa
|
||||
|
||||
class AAParseUnixTest(AAParseTest):
|
||||
|
||||
@@ -34,6 +34,7 @@ class AAParseUnixTest(AAParseTest):
|
||||
'complex unix rule'),
|
||||
]
|
||||
|
||||
setup_aa(aa)
|
||||
if __name__ == '__main__':
|
||||
setup_regex_tests(AAParseUnixTest)
|
||||
unittest.main(verbosity=2)
|
||||
|
Reference in New Issue
Block a user