mirror of
https://gitlab.com/apparmor/apparmor
synced 2025-09-01 06:45:38 +00:00
Compare commits
40 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
af4808b5f6 | ||
|
f811fa9951 | ||
|
a68e6426f4 | ||
|
7356f51425 | ||
|
ef21e9ded7 | ||
|
1a0016ff17 | ||
|
3607865b18 | ||
|
597e17eb67 | ||
|
7dce58987f | ||
|
c044757de9 | ||
|
6249579842 | ||
|
37edb354ff | ||
|
b8dc8d1394 | ||
|
1f2eb0bbbf | ||
|
2296c30af5 | ||
|
aa328cb058 | ||
|
1d183660d5 | ||
|
7b07832459 | ||
|
b6c96f3933 | ||
|
ad236a59b8 | ||
|
f8b95d036d | ||
|
f4d7f8ae57 | ||
|
1b32d764ef | ||
|
dd4c2b05ea | ||
|
314617014a | ||
|
41ff006f3d | ||
|
7fc843d8d0 | ||
|
fc18647fba | ||
|
064521c236 | ||
|
566ad1fefa | ||
|
8def66134d | ||
|
8661ebcb79 | ||
|
3ee32c7ed7 | ||
|
9687b44842 | ||
|
fac1e427f1 | ||
|
37d176e72f | ||
|
6f70502ad1 | ||
|
37e64d99d1 | ||
|
e17d974330 | ||
|
1f884d7612 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -95,6 +95,8 @@ libraries/libapparmor/src/.deps
|
||||
libraries/libapparmor/src/.libs
|
||||
libraries/libapparmor/src/Makefile
|
||||
libraries/libapparmor/src/Makefile.in
|
||||
libraries/libapparmor/src/PMurHash.lo
|
||||
libraries/libapparmor/src/PMurHash.o
|
||||
libraries/libapparmor/src/af_protos.h
|
||||
libraries/libapparmor/src/change_hat.lo
|
||||
libraries/libapparmor/src/features.lo
|
||||
@@ -188,6 +190,7 @@ utils/*.tmp
|
||||
utils/po/*.mo
|
||||
utils/apparmor/*.pyc
|
||||
utils/apparmor/rule/*.pyc
|
||||
utils/test/common_test.pyc
|
||||
utils/test/.coverage
|
||||
utils/test/htmlcov/
|
||||
utils/vim/apparmor.vim
|
||||
|
@@ -1 +1 @@
|
||||
2.13.1
|
||||
2.13.2
|
||||
|
@@ -42,8 +42,8 @@ scanner.h: scanner.l
|
||||
|
||||
scanner.c: scanner.l
|
||||
|
||||
af_protos.h: /usr/include/netinet/in.h
|
||||
LC_ALL=C sed -n -e "/IPPROTO_MAX/d" -e "s/^\#define[ \\t]\\+IPPROTO_\\([A-Z0-9_]\\+\\)\\(.*\\)$$/AA_GEN_PROTO_ENT(\\UIPPROTO_\\1, \"\\L\\1\")/p" $< > $@
|
||||
af_protos.h:
|
||||
echo '#include <netinet/in.h>' | $(CC) -E -dM - | LC_ALL=C sed -n -e "/IPPROTO_MAX/d" -e "s/^\#define[ \\t]\\+IPPROTO_\\([A-Z0-9_]\\+\\)\\(.*\\)$$/AA_GEN_PROTO_ENT(\\UIPPROTO_\\1, \"\\L\\1\")/p" > $@
|
||||
|
||||
lib_LTLIBRARIES = libapparmor.la
|
||||
noinst_HEADERS = grammar.h parser.h scanner.h af_protos.h private.h PMurHash.h
|
||||
|
@@ -143,6 +143,56 @@ messages with the KERN facility. Thus, REJECTING and PERMITTING messages
|
||||
may go to either F</var/log/audit/audit.log> or F</var/log/messages>,
|
||||
depending upon local configuration.
|
||||
|
||||
=head1 DEBUGGING
|
||||
|
||||
AppArmor provides a few facilities to log more information,
|
||||
which can help debugging profiles.
|
||||
|
||||
=head2 Enable debug mode
|
||||
|
||||
When debug mode is enabled, AppArmor will log a few extra messages to
|
||||
dmesg (not via the audit subsystem). For example, the logs will tell
|
||||
whether environment scrubbing has been applied.
|
||||
|
||||
To enable debug mode, run:
|
||||
|
||||
echo 1 > /sys/module/apparmor/parameters/debug
|
||||
|
||||
=head2 Turn off deny audit quieting
|
||||
|
||||
By default, operations that trigger C<deny> rules are not logged.
|
||||
This is called I<deny audit quieting>.
|
||||
|
||||
To turn off deny audit quieting, run:
|
||||
|
||||
echo -n noquiet >/sys/module/apparmor/parameters/audit
|
||||
|
||||
=head2 Force audit mode
|
||||
|
||||
AppArmor can log a message for every operation that triggers a rule
|
||||
configured in the policy. This is called I<force audit mode>.
|
||||
|
||||
B<Warning!> Force audit mode can be extremely noisy even for a single profile,
|
||||
let alone when enabled globally.
|
||||
|
||||
To set a specific profile in force audit mode, add the C<audit> flag:
|
||||
|
||||
profile foo flags=(audit) { ... }
|
||||
|
||||
To enable force audit mode globally, run:
|
||||
|
||||
echo -n all > /sys/module/apparmor/parameters/audit
|
||||
|
||||
If auditd is not running, to avoid losing too many of the extra log
|
||||
messages, you will likely have to turn off rate limiting by doing:
|
||||
|
||||
echo 0 > /proc/sys/kernel/printk_ratelimit
|
||||
|
||||
But even then the kernel ring buffer may overflow and you might
|
||||
lose messages.
|
||||
|
||||
Else, if auditd is running, see auditd(8) and auditd.conf(5).
|
||||
|
||||
=head1 FILES
|
||||
|
||||
=over 4
|
||||
|
@@ -194,7 +194,8 @@ static void display_usage(const char *command)
|
||||
"-I n, --Include n Add n to the search path\n"
|
||||
"-f n, --subdomainfs n Set location of apparmor filesystem\n"
|
||||
"-m n, --match-string n Use only features n\n"
|
||||
"-M n, --features-file n Compile features set in file n\n"
|
||||
"-M n, --features-file n Set compile & kernel features to file n\n"
|
||||
"--compile-features n Compile features set in file n\n"
|
||||
"--kernel-features n Kernel features set in file n\n"
|
||||
"-n n, --namespace n Set Namespace for the profile\n"
|
||||
"-X, --readimpliesX Map profile read permissions to mr\n"
|
||||
@@ -535,14 +536,21 @@ static int process_arg(int c, char *optarg)
|
||||
}
|
||||
break;
|
||||
case 'M':
|
||||
if (compile_features)
|
||||
aa_features_unref(compile_features);
|
||||
if (kernel_features)
|
||||
aa_features_unref(kernel_features);
|
||||
if (aa_features_new(&compile_features, AT_FDCWD, optarg)) {
|
||||
fprintf(stderr,
|
||||
"Failed to load features from '%s': %m\n",
|
||||
optarg);
|
||||
exit(1);
|
||||
}
|
||||
kernel_features = aa_features_ref(compile_features);
|
||||
break;
|
||||
case 138:
|
||||
if (kernel_features)
|
||||
aa_features_unref(kernel_features);
|
||||
if (aa_features_new(&kernel_features, AT_FDCWD, optarg)) {
|
||||
fprintf(stderr,
|
||||
"Failed to load kernel features from '%s': %m\n",
|
||||
@@ -550,6 +558,16 @@ static int process_arg(int c, char *optarg)
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
case 139:
|
||||
if (compile_features)
|
||||
aa_features_unref(compile_features);
|
||||
if (aa_features_new(&compile_features, AT_FDCWD, optarg)) {
|
||||
fprintf(stderr,
|
||||
"Failed to load compile features from '%s': %m\n",
|
||||
optarg);
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
case 'q':
|
||||
conf_verbose = 0;
|
||||
conf_quiet = 1;
|
||||
|
@@ -113,6 +113,8 @@ skip_profile() {
|
||||
local profile=$1
|
||||
if [ "${profile%.rpmnew}" != "${profile}" -o \
|
||||
"${profile%.rpmsave}" != "${profile}" -o \
|
||||
"${profile%.orig}" != "${profile}" -o \
|
||||
"${profile%.rej}" != "${profile}" -o \
|
||||
-e "${PROFILE_DIR}/disable/`basename ${profile}`" -o \
|
||||
"${profile%\~}" != "${profile}" ] ; then
|
||||
return 1
|
||||
@@ -128,7 +130,7 @@ skip_profile() {
|
||||
return 2
|
||||
fi
|
||||
if echo "${profile}" | egrep -q '^.+\.new-[0-9\.]+_[0-9]+$'; then
|
||||
return 2 ;;
|
||||
return 2
|
||||
fi
|
||||
|
||||
return 0
|
||||
|
@@ -131,9 +131,13 @@ sub test_profile {
|
||||
} elsif ($coredump) {
|
||||
ok(0, "$profile: Produced core dump (signal $signal): $description");
|
||||
} elsif ($istodo) {
|
||||
TODO: {
|
||||
local $TODO = "Unfixed testcase.";
|
||||
ok($expass ? !$result : $result, "TODO: $profile: $description");
|
||||
if ($expass != $result) {
|
||||
fail("TODO passed unexpectedly: $profile: $description");
|
||||
} else {
|
||||
TODO: {
|
||||
local $TODO = "Unfixed testcase.";
|
||||
ok($expass ? !$result : $result, "TODO: $profile: $description");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ok($expass ? !$result : $result, "$profile: $description");
|
||||
|
@@ -2,6 +2,7 @@
|
||||
#=DESCRIPTION abi testing - abi path quotes in <> with spaces
|
||||
#=EXRESULT PASS
|
||||
#=TODO
|
||||
#=DISABLED - results in "superfluous TODO", but fails after removing TODO
|
||||
|
||||
abi < "abi/4.19">,
|
||||
|
||||
|
@@ -2,6 +2,7 @@
|
||||
#=DESCRIPTION abi testing - abi path quotes in <> with spaces
|
||||
#=EXRESULT PASS
|
||||
#=TODO
|
||||
#=DISABLED - results in "superfluous TODO", but fails after removing TODO
|
||||
|
||||
abi < "abi/4.19" >,
|
||||
|
||||
|
@@ -1,6 +1,5 @@
|
||||
#=DESCRIPTION reference variables in rules that also have alternations
|
||||
#=EXRESULT PASS
|
||||
#=TODO
|
||||
# This test needs check on @{FOO} attachment having leading / post var expansion
|
||||
|
||||
@{FOO}=/bar /baz
|
||||
|
@@ -1,6 +1,5 @@
|
||||
#=DESCRIPTION reference variables is null
|
||||
#=EXRESULT FAIL
|
||||
#=TODO
|
||||
#needs post var expansion check that variable contained a value
|
||||
|
||||
@{FOO}=
|
||||
|
@@ -1,6 +1,5 @@
|
||||
#=DESCRIPTION reference variables is null
|
||||
#=EXRESULT FAIL
|
||||
#=TODO
|
||||
#needs post var expansion check that variable contained a value
|
||||
|
||||
@{FOO}=
|
||||
|
@@ -1,7 +1,6 @@
|
||||
#
|
||||
#=DESCRIPTION test for conflict resolution in minimization phase of dfa gen
|
||||
#=EXRESULT PASS
|
||||
#=TODO
|
||||
#
|
||||
/usr/bin/foo {
|
||||
|
||||
|
@@ -1,7 +1,6 @@
|
||||
#
|
||||
#=DESCRIPTION test for conflict resolution in minimization phase of dfa gen
|
||||
#=EXRESULT FAIL
|
||||
#=TODO
|
||||
#
|
||||
/usr/bin/foo {
|
||||
/b* px,
|
||||
|
@@ -29,6 +29,7 @@ DESTDIR=/
|
||||
PROFILES_DEST=${DESTDIR}/etc/apparmor.d
|
||||
EXTRAS_DEST=${DESTDIR}/usr/share/apparmor/extra-profiles/
|
||||
PROFILES_SOURCE=./apparmor.d
|
||||
ABSTRACTIONS_SOURCE=./apparmor.d/abstractions
|
||||
EXTRAS_SOURCE=./apparmor/profiles/extras/
|
||||
|
||||
SUBDIRS=$(shell find ${PROFILES_SOURCE} -type d -print)
|
||||
@@ -84,6 +85,8 @@ docs:
|
||||
|
||||
IGNORE_FILES=${EXTRAS_SOURCE}/README
|
||||
CHECK_PROFILES=$(filter-out ${IGNORE_FILES} ${SUBDIRS}, $(wildcard ${PROFILES_SOURCE}/*) $(wildcard ${EXTRAS_SOURCE}/*))
|
||||
# use find because Make wildcard is not recursive:
|
||||
CHECK_ABSTRACTIONS=$(shell find ${ABSTRACTIONS_SOURCE} -type f -print)
|
||||
|
||||
.PHONY: check
|
||||
check: check-parser check-logprof
|
||||
@@ -96,6 +99,14 @@ check-parser: local
|
||||
${PARSER} --config-file=../parser/tst/parser.conf -S -b ${PWD}/apparmor.d $${profile} > /dev/null || exit 1; \
|
||||
done
|
||||
|
||||
@echo "*** Checking abstractions from ${ABSTRACTIONS_SOURCE} against apparmor_parser"
|
||||
$(Q)for abstraction in ${CHECK_ABSTRACTIONS} ; do \
|
||||
[ -n "${VERBOSE}" ] && echo "Testing $${abstraction}" ; \
|
||||
echo "#include <tunables/global> profile test { #include <$${abstraction}> }" \
|
||||
| ${PARSER} --config-file=../parser/tst/parser.conf -S -b ${PWD}/apparmor.d -I ${PWD} > /dev/null \
|
||||
|| exit 1; \
|
||||
done
|
||||
|
||||
.PHONY: check-logprof
|
||||
check-logprof: local
|
||||
@echo "*** Checking profiles from ${PROFILES_SOURCE} against logprof"
|
||||
|
@@ -7,9 +7,9 @@
|
||||
# Allow unconfined processes to send us signals by default
|
||||
signal (receive) peer=unconfined,
|
||||
# Allow apache to send us signals by default
|
||||
signal (receive) peer=/usr/{bin,sbin}/apache2,
|
||||
signal (receive) peer=apache2,
|
||||
# Allow other hats to signal by default
|
||||
signal peer=/usr/{bin,sbin}/apache2//*,
|
||||
signal peer=apache2//*,
|
||||
# Allow us to signal ourselves
|
||||
signal peer=@{profile_name},
|
||||
|
||||
|
@@ -90,8 +90,8 @@
|
||||
@{PROC}/meminfo r,
|
||||
@{PROC}/stat r,
|
||||
@{PROC}/cpuinfo r,
|
||||
/sys/devices/system/cpu/ r,
|
||||
/sys/devices/system/cpu/online r,
|
||||
@{sys}/devices/system/cpu/ r,
|
||||
@{sys}/devices/system/cpu/online r,
|
||||
|
||||
# glibc's *printf protections read the maps file
|
||||
@{PROC}/@{pid}/{maps,auxv,status} r,
|
||||
|
@@ -14,6 +14,6 @@
|
||||
deny capability block_suspend,
|
||||
|
||||
# dovecot's master can send us signals
|
||||
signal receive peer=/usr/{bin,sbin}/dovecot,
|
||||
signal receive peer=dovecot,
|
||||
|
||||
/{var/,}run/dovecot/config rw,
|
||||
|
@@ -4,6 +4,5 @@
|
||||
# needs to enumerate graphic devices (as with drmParsePciDeviceInfo() from
|
||||
# libdrm).
|
||||
|
||||
# TODO: use @{sys} after it's moved into tunables/kernelvars (LP: #1728551)
|
||||
/sys/devices/pci[0-9]*/**/{device,subsystem_device,subsystem_vendor,uevent,vendor} r,
|
||||
@{sys}/devices/pci[0-9]*/**/{device,subsystem_device,subsystem_vendor,uevent,vendor} r,
|
||||
|
||||
|
@@ -17,7 +17,7 @@
|
||||
@{PROC}/driver/nvidia/params r,
|
||||
@{PROC}/modules r,
|
||||
|
||||
/sys/devices/system/memory/block_size_bytes r,
|
||||
@{sys}/devices/system/memory/block_size_bytes r,
|
||||
|
||||
owner @{HOME}/.nv/ w,
|
||||
owner @{HOME}/.nv/GLCache/ rw,
|
||||
|
@@ -4,7 +4,7 @@
|
||||
# System files
|
||||
|
||||
/etc/OpenCL/** r,
|
||||
/sys/bus/pci/devices/ r, # libpocl.so -> libhwlock.so, libnvidia-opencl.so, beignet/libcl.so -> libdrm_intel.so
|
||||
/sys/devices/system/node/ r, # for clGetPlatformIDs() from libOpenCL.so
|
||||
/sys/devices/system/node/node[0-9]*/meminfo r, # for clGetPlatformIDs() from libOpenCL.so
|
||||
@{sys}/bus/pci/devices/ r, # libpocl.so -> libhwlock.so, libnvidia-opencl.so, beignet/libcl.so -> libdrm_intel.so
|
||||
@{sys}/devices/system/node/ r, # for clGetPlatformIDs() from libOpenCL.so
|
||||
@{sys}/devices/system/node/node[0-9]*/meminfo r, # for clGetPlatformIDs() from libOpenCL.so
|
||||
|
||||
|
@@ -12,6 +12,6 @@
|
||||
# System files
|
||||
|
||||
/dev/dri/card[0-9]* rw, # beignet/libcl.so
|
||||
/sys/devices/pci[0-9]*/**/{class,config,resource,revision} r, # libcl.so -> libdrm_intel.so -> libpciaccess.so (move to dri-enumerate ?)
|
||||
@{sys}/devices/pci[0-9]*/**/{class,config,resource,revision} r, # libcl.so -> libdrm_intel.so -> libpciaccess.so (move to dri-enumerate ?)
|
||||
/usr/lib/@{multiarch}/beignet/** r,
|
||||
|
||||
|
@@ -16,8 +16,8 @@
|
||||
# libnvidia-opencl.so rules:
|
||||
/dev/nvidia-uvm rw,
|
||||
/dev/nvidia-uvm-tools rw,
|
||||
/sys/devices/pci[0-9]*/**/config r,
|
||||
/sys/devices/system/memory/block_size_bytes r,
|
||||
@{sys}/devices/pci[0-9]*/**/config r,
|
||||
@{sys}/devices/system/memory/block_size_bytes r,
|
||||
/usr/share/nvidia/** r,
|
||||
@{PROC}/devices r,
|
||||
@{PROC}/sys/vm/mmap_min_addr r,
|
||||
|
@@ -11,22 +11,22 @@
|
||||
# System files
|
||||
|
||||
/ r, # libpocl.so -> libhwloc.so
|
||||
/sys/bus/pci/slots/ r, # libpocl.so -> hwloc_topology_load() from libhwloc.so
|
||||
/sys/bus/{cpu,node}/devices/ r, # libpocl.so -> libhwlock.so
|
||||
/sys/class/net/ r, # libpocl.so -> hwloc_pci_traverse_lookuposdevices_cb() from libhwloc.so
|
||||
/sys/devices/pci[0-9]*/**/ r, # for libpocl -> hwloc_linux_lookup_block_class() from libhwloc.so
|
||||
/sys/devices/pci[0-9]*/**/block/*/dev r, # libpocl.so -> hwloc_linux_lookup_host_block_class() from libhwloc.so
|
||||
/sys/devices/pci[0-9]*/**/{class,local_cpus} r, # libpocl.so -> libhwlock.so
|
||||
/sys/devices/pci[0-9]*/*/net/*/address r, # libpocl.so -> hwloc_pci_traverse_lookuposdevices_cb() from libhwloc.so
|
||||
/sys/devices/system/cpu/ r, # libpocl.so -> libnuma.so
|
||||
/sys/devices/system/cpu/cpu[0-9]*/cache/index[0-9]*/* r, # libpocl.so -> libhwloc.so
|
||||
/sys/devices/system/cpu/cpu[0-9]*/online r, # libpocl.so -> libhwlock.so
|
||||
/sys/devices/system/cpu/cpu[0-9]*/topology/* r, # *_siblings, physical_package_id and lot's of others, for libpocl.so -> libhwloc.so
|
||||
/sys/devices/system/cpu/cpufreq/policy[0-9]*/* r, # for clGetPlatformIDs() from libpocl.so
|
||||
/sys/devices/system/cpu/possible r, # libpocl.so -> libhwloc.so
|
||||
/sys/devices/virtual/dmi/id/{,*} r, # libpocl.so -> libhwloc.so
|
||||
/sys/fs/cgroup/cpuset/cpuset.{cpus,mems} r, # libpocl.so -> libhwloc.so
|
||||
/sys/kernel/mm/hugepages{/,/**} r, # libpocl.so -> libhwloc.so
|
||||
@{sys}/bus/pci/slots/ r, # libpocl.so -> hwloc_topology_load() from libhwloc.so
|
||||
@{sys}/bus/{cpu,node}/devices/ r, # libpocl.so -> libhwlock.so
|
||||
@{sys}/class/net/ r, # libpocl.so -> hwloc_pci_traverse_lookuposdevices_cb() from libhwloc.so
|
||||
@{sys}/devices/pci[0-9]*/**/ r, # for libpocl -> hwloc_linux_lookup_block_class() from libhwloc.so
|
||||
@{sys}/devices/pci[0-9]*/**/block/*/dev r, # libpocl.so -> hwloc_linux_lookup_host_block_class() from libhwloc.so
|
||||
@{sys}/devices/pci[0-9]*/**/{class,local_cpus} r, # libpocl.so -> libhwlock.so
|
||||
@{sys}/devices/pci[0-9]*/*/net/*/address r, # libpocl.so -> hwloc_pci_traverse_lookuposdevices_cb() from libhwloc.so
|
||||
@{sys}/devices/system/cpu/ r, # libpocl.so -> libnuma.so
|
||||
@{sys}/devices/system/cpu/cpu[0-9]*/cache/index[0-9]*/* r, # libpocl.so -> libhwloc.so
|
||||
@{sys}/devices/system/cpu/cpu[0-9]*/online r, # libpocl.so -> libhwlock.so
|
||||
@{sys}/devices/system/cpu/cpu[0-9]*/topology/* r, # *_siblings, physical_package_id and lot's of others, for libpocl.so -> libhwloc.so
|
||||
@{sys}/devices/system/cpu/cpufreq/policy[0-9]*/* r, # for clGetPlatformIDs() from libpocl.so
|
||||
@{sys}/devices/system/cpu/possible r, # libpocl.so -> libhwloc.so
|
||||
@{sys}/devices/virtual/dmi/id/{,*} r, # libpocl.so -> libhwloc.so
|
||||
@{sys}/fs/cgroup/cpuset/cpuset.{cpus,mems} r, # libpocl.so -> libhwloc.so
|
||||
@{sys}/kernel/mm/hugepages{/,/**} r, # libpocl.so -> libhwloc.so
|
||||
/usr/share/pocl/** r,
|
||||
/{,var/}run/udev/data/*:* r, # libpocl.so -> hwloc_linux_block_class_fillinfos() from libhwloc.so
|
||||
|
||||
|
@@ -6,6 +6,7 @@
|
||||
# lot of false positives when reading contents of directories)
|
||||
deny @{HOME}/.*history mrwkl,
|
||||
deny @{HOME}/.fetchmail* mrwkl,
|
||||
deny @{HOME}/.mutt** mrwkl,
|
||||
deny @{HOME}/.viminfo* mrwkl,
|
||||
deny @{HOME}/.*~ mrwkl,
|
||||
deny @{HOME}/.*.swp mrwkl,
|
||||
|
@@ -5,6 +5,7 @@
|
||||
#include <abstractions/private-files>
|
||||
|
||||
# potentially extremely sensitive files
|
||||
audit deny @{HOME}/.aws/{,**} mrwkl,
|
||||
audit deny @{HOME}/.gnupg/{,**} mrwkl,
|
||||
audit deny @{HOME}/.ssh/{,**} mrwkl,
|
||||
audit deny @{HOME}/.gnome2_private/{,**} mrwkl,
|
||||
|
@@ -32,3 +32,8 @@
|
||||
/etc/dehydrated/certs/*/cert-*.pem r,
|
||||
/etc/dehydrated/certs/*/chain-*.pem r,
|
||||
/etc/dehydrated/certs/*/fullchain-*.pem r,
|
||||
|
||||
# certbot
|
||||
/etc/letsencrypt/archive/*/cert*.pem r,
|
||||
/etc/letsencrypt/archive/*/chain*.pem r,
|
||||
/etc/letsencrypt/archive/*/fullchain*.pem r,
|
||||
|
@@ -23,3 +23,6 @@
|
||||
|
||||
# dehydrated
|
||||
/etc/dehydrated/certs/*/privkey-*.pem r,
|
||||
|
||||
# certbot / letsencrypt
|
||||
/etc/letsencrypt/archive/*/privkey*.pem r,
|
||||
|
@@ -41,8 +41,8 @@
|
||||
@{PROC}/@{pid}/ r,
|
||||
@{PROC}/@{pid}/fd/ r,
|
||||
@{PROC}/filesystems r,
|
||||
/sys/devices/system/cpu/ r,
|
||||
/sys/devices/system/cpu/** r,
|
||||
@{sys}/devices/system/cpu/ r,
|
||||
@{sys}/devices/system/cpu/** r,
|
||||
/usr/share/** r,
|
||||
/var/lib/dbus/machine-id r,
|
||||
|
||||
@@ -88,8 +88,8 @@
|
||||
@{PROC}/@{pid}/ r,
|
||||
@{PROC}/@{pid}/fd/ r,
|
||||
@{PROC}/filesystems r,
|
||||
/sys/devices/system/cpu/ r,
|
||||
/sys/devices/system/cpu/** r,
|
||||
@{sys}/devices/system/cpu/ r,
|
||||
@{sys}/devices/system/cpu/** r,
|
||||
/usr/share/** r,
|
||||
/var/lib/dbus/machine-id r,
|
||||
|
||||
|
@@ -2,5 +2,5 @@
|
||||
# video device access
|
||||
|
||||
# System devices
|
||||
/sys/class/video4linux r,
|
||||
/sys/class/video4linux/** r,
|
||||
@{sys}/class/video4linux r,
|
||||
@{sys}/class/video4linux/** r,
|
||||
|
14
profiles/apparmor.d/abstractions/vulkan
Normal file
14
profiles/apparmor.d/abstractions/vulkan
Normal file
@@ -0,0 +1,14 @@
|
||||
# vim:syntax=apparmor
|
||||
# Vulkan access requirements
|
||||
|
||||
# System files
|
||||
/dev/dri/ r, # libvulkan_radeon.so, libvulkan_intel.so (Mesa)
|
||||
/etc/vulkan/{explicit,implicit}_layer.d/{,*.json} r,
|
||||
# for drmGetMinorNameForFD() from libvulkan_intel.so (Mesa)
|
||||
@{sys}/devices/pci[0-9]*/*/drm/ r,
|
||||
/usr/share/vulkan/icd.d/{,*.json} r,
|
||||
/usr/share/vulkan/{explicit,implicit}_layer.d/{,*.json} r,
|
||||
|
||||
# User files
|
||||
owner @{HOME}/.local/share/vulkan/implicit_layer.d/{,*.json} r,
|
||||
|
@@ -20,13 +20,13 @@
|
||||
/etc/phpsysinfo/config.php r,
|
||||
/etc/udev/udev.conf r,
|
||||
@{PROC}/** r,
|
||||
/sys/bus/ r,
|
||||
/sys/bus/pci/devices/ r,
|
||||
/sys/bus/pci/slots/ r,
|
||||
/sys/bus/pci/slots/** r,
|
||||
/sys/bus/usb/devices/ r,
|
||||
/sys/class/ r,
|
||||
/sys/devices/** r,
|
||||
@{sys}/bus/ r,
|
||||
@{sys}/bus/pci/devices/ r,
|
||||
@{sys}/bus/pci/slots/ r,
|
||||
@{sys}/bus/pci/slots/** r,
|
||||
@{sys}/bus/usb/devices/ r,
|
||||
@{sys}/class/ r,
|
||||
@{sys}/devices/** r,
|
||||
/usr/bin/ r,
|
||||
/usr/bin/apt-cache ixr,
|
||||
/usr/bin/dpkg-query ixr,
|
||||
|
@@ -24,8 +24,8 @@ profile nvidia_modprobe {
|
||||
|
||||
/dev/nvidia-uvm w,
|
||||
/dev/nvidia-uvm-tools w,
|
||||
/sys/bus/pci/devices/ r,
|
||||
/sys/devices/pci[0-9]*/**/config r,
|
||||
@{sys}/bus/pci/devices/ r,
|
||||
@{sys}/devices/pci[0-9]*/**/config r,
|
||||
@{PROC}/devices r,
|
||||
@{PROC}/modules r,
|
||||
@{PROC}/sys/kernel/modprobe r,
|
||||
@@ -51,9 +51,9 @@ profile nvidia_modprobe {
|
||||
|
||||
/etc/modprobe.d/{,*.conf} r,
|
||||
/etc/nvidia/current/*.conf r,
|
||||
/sys/module/ipmi_devintf/initstate r,
|
||||
/sys/module/ipmi_msghandler/initstate r,
|
||||
/sys/module/nvidia/initstate r,
|
||||
@{sys}/module/ipmi_devintf/initstate r,
|
||||
@{sys}/module/ipmi_msghandler/initstate r,
|
||||
@{sys}/module/nvidia/initstate r,
|
||||
@{PROC}/cmdline r,
|
||||
}
|
||||
|
||||
|
@@ -47,7 +47,7 @@ profile syslog-ng /{usr/,}{bin,sbin}/syslog-ng {
|
||||
/etc/hosts.deny r,
|
||||
/etc/hosts.allow r,
|
||||
/{usr/,}{bin,sbin}/syslog-ng mr,
|
||||
/sys/devices/system/cpu/online r,
|
||||
@{sys}/devices/system/cpu/online r,
|
||||
/usr/share/syslog-ng/** r,
|
||||
/var/lib/syslog-ng/syslog-ng-?????.qf rw,
|
||||
# chrooted applications
|
||||
|
@@ -29,7 +29,7 @@
|
||||
/run/dovecot/auth-userdb rw,
|
||||
/usr/bin/doveconf mrix,
|
||||
/usr/lib/dovecot/dovecot-lda mrix,
|
||||
/usr/{bin,sbin}/sendmail Cx,
|
||||
/usr/{bin,sbin}/sendmail Cx -> sendmail,
|
||||
/usr/share/dovecot/protocols.d/ r,
|
||||
/usr/share/dovecot/protocols.d/** r,
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
#include <local/usr.lib.dovecot.dovecot-lda>
|
||||
|
||||
|
||||
profile /usr/{bin,sbin}/sendmail flags=(attach_disconnected) {
|
||||
profile sendmail /usr/{bin,sbin}/sendmail flags=(attach_disconnected) {
|
||||
# this profile is based on the usr.sbin.sendmail profile in extras
|
||||
# and should support both postfix' and sendmail's sendmail binary
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# Author: Marc Deslauriers <marc.deslauriers@ubuntu.com>
|
||||
|
||||
#include <tunables/global>
|
||||
/usr/{bin,sbin}/apache2 flags=(attach_disconnected) {
|
||||
profile apache2 /usr/{bin,sbin}/apache2 flags=(attach_disconnected) {
|
||||
|
||||
# This profile is completely permissive.
|
||||
# It is designed to target specific applications using mod_apparmor,
|
||||
|
@@ -1,5 +1,5 @@
|
||||
#include <tunables/global>
|
||||
/usr/{bin,sbin}/avahi-daemon {
|
||||
profile avahi-daemon /usr/{bin,sbin}/avahi-daemon {
|
||||
#include <abstractions/base>
|
||||
#include <abstractions/consoles>
|
||||
#include <abstractions/dbus>
|
||||
|
@@ -12,7 +12,7 @@
|
||||
@{TFTP_DIR}=/var/tftp /srv/tftpboot
|
||||
|
||||
#include <tunables/global>
|
||||
profile dnsmasq /usr/{bin,sbin}/dnsmasq flags=(attach_disconnected) {
|
||||
/usr/{bin,sbin}/dnsmasq flags=(attach_disconnected) {
|
||||
#include <abstractions/base>
|
||||
#include <abstractions/dbus>
|
||||
#include <abstractions/nameservice>
|
||||
@@ -45,7 +45,7 @@ profile dnsmasq /usr/{bin,sbin}/dnsmasq flags=(attach_disconnected) {
|
||||
|
||||
/usr/{bin,sbin}/dnsmasq mr,
|
||||
|
||||
/var/log/*dnsmasq.log w,
|
||||
/var/log/dnsmasq*.log w,
|
||||
|
||||
/usr/share/dnsmasq/ r,
|
||||
/usr/share/dnsmasq/* r,
|
||||
@@ -96,6 +96,7 @@ profile dnsmasq /usr/{bin,sbin}/dnsmasq flags=(attach_disconnected) {
|
||||
/{,var/}run/sendsigs.omit.d/*dnsmasq.pid w,
|
||||
/{,var/}run/NetworkManager/dnsmasq.conf r,
|
||||
/{,var/}run/NetworkManager/dnsmasq.pid w,
|
||||
/{,var/}run/NetworkManager/NetworkManager.pid w,
|
||||
|
||||
profile libvirt_leaseshelper {
|
||||
#include <abstractions/base>
|
||||
@@ -107,9 +108,9 @@ profile dnsmasq /usr/{bin,sbin}/dnsmasq flags=(attach_disconnected) {
|
||||
owner @{PROC}/@{pid}/net/psched r,
|
||||
owner @{PROC}/@{pid}/status r,
|
||||
|
||||
/sys/devices/system/cpu/ r,
|
||||
/sys/devices/system/node/ r,
|
||||
/sys/devices/system/node/*/meminfo r,
|
||||
@{sys}/devices/system/cpu/ r,
|
||||
@{sys}/devices/system/node/ r,
|
||||
@{sys}/devices/system/node/*/meminfo r,
|
||||
|
||||
# libvirt lease and status files for dnsmasq
|
||||
/var/lib/libvirt/dnsmasq/*.leases rw,
|
||||
|
@@ -12,7 +12,7 @@
|
||||
|
||||
#include <tunables/global>
|
||||
|
||||
/usr/{bin,sbin}/dovecot flags=(attach_disconnected) {
|
||||
profile dovecot /usr/{bin,sbin}/dovecot flags=(attach_disconnected) {
|
||||
#include <abstractions/authentication>
|
||||
#include <abstractions/base>
|
||||
#include <abstractions/dovecot-common>
|
||||
@@ -38,6 +38,7 @@
|
||||
/etc/lsb-release r,
|
||||
/etc/SuSE-release r,
|
||||
@{PROC}/@{pid}/mounts r,
|
||||
@{PROC}/sys/fs/suid_dumpable r,
|
||||
/usr/bin/doveconf rix,
|
||||
/usr/lib/dovecot/anvil mrPx,
|
||||
/usr/lib/dovecot/auth mrPx,
|
||||
|
@@ -11,7 +11,7 @@
|
||||
|
||||
#include <tunables/global>
|
||||
|
||||
/usr/{bin,sbin}/identd {
|
||||
profile identd /usr/{bin,sbin}/identd {
|
||||
#include <abstractions/base>
|
||||
#include <abstractions/nameservice>
|
||||
capability net_bind_service,
|
||||
|
@@ -11,7 +11,7 @@
|
||||
|
||||
#include <tunables/global>
|
||||
|
||||
/usr/{bin,sbin}/mdnsd {
|
||||
profile mdnsd /usr/{bin,sbin}/mdnsd {
|
||||
#include <abstractions/base>
|
||||
#include <abstractions/consoles>
|
||||
#include <abstractions/nameservice>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
#include <tunables/global>
|
||||
|
||||
/usr/{bin,sbin}/nmbd {
|
||||
profile nmbd /usr/{bin,sbin}/nmbd {
|
||||
#include <abstractions/base>
|
||||
#include <abstractions/nameservice>
|
||||
#include <abstractions/samba>
|
||||
|
@@ -10,7 +10,7 @@
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
#include <tunables/global>
|
||||
/usr/{bin,sbin}/nscd {
|
||||
profile nscd /usr/{bin,sbin}/nscd {
|
||||
#include <abstractions/base>
|
||||
#include <abstractions/consoles>
|
||||
#include <abstractions/nameservice>
|
||||
|
@@ -11,7 +11,7 @@
|
||||
|
||||
#include <tunables/global>
|
||||
#include <tunables/ntpd>
|
||||
/usr/{bin,sbin}/{,open}ntpd flags=(attach_disconnected) {
|
||||
profile ntpd /usr/{bin,sbin}/{,open}ntpd flags=(attach_disconnected) {
|
||||
#include <abstractions/base>
|
||||
#include <abstractions/nameservice>
|
||||
#include <abstractions/openssl>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
#include <tunables/global>
|
||||
|
||||
/usr/{bin,sbin}/smbd {
|
||||
profile smbd /usr/{bin,sbin}/smbd {
|
||||
#include <abstractions/authentication>
|
||||
#include <abstractions/base>
|
||||
#include <abstractions/consoles>
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# Last Modified: Tue Jan 3 00:17:40 2012
|
||||
#include <tunables/global>
|
||||
|
||||
/usr/{bin,sbin}/smbldap-useradd {
|
||||
profile smbldap-useradd /usr/{bin,sbin}/smbldap-useradd {
|
||||
#include <abstractions/base>
|
||||
#include <abstractions/bash>
|
||||
#include <abstractions/nameservice>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
#include <tunables/global>
|
||||
|
||||
/usr/{bin,sbin}/winbindd {
|
||||
profile winbindd /usr/{bin,sbin}/winbindd {
|
||||
#include <abstractions/base>
|
||||
#include <abstractions/nameservice>
|
||||
#include <abstractions/samba>
|
||||
|
@@ -17,7 +17,7 @@
|
||||
#include <abstractions/consoles>
|
||||
#include <abstractions/postfix-common>
|
||||
/etc/aliases r,
|
||||
/etc/aliases.db rwl,
|
||||
/etc/aliases.db rwlk,
|
||||
/etc/postfix r,
|
||||
/etc/postfix/main.cf r,
|
||||
/etc/postfix/aliases r,
|
||||
|
@@ -107,7 +107,7 @@ apparmor.check_qualifiers(program)
|
||||
|
||||
apparmor.loadincludes()
|
||||
|
||||
profile_filename = apparmor.get_profile_filename(program)
|
||||
profile_filename = apparmor.get_profile_filename_from_attachment(program, True)
|
||||
if os.path.exists(profile_filename):
|
||||
apparmor.helpers[program] = apparmor.get_profile_flags(profile_filename, program)
|
||||
else:
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#! /usr/bin/python3
|
||||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.com>
|
||||
# Copyright (C) 2014-2017 Christian Boltz <apparmor@cboltz.de>
|
||||
# Copyright (C) 2014-2018 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
|
||||
@@ -57,7 +57,7 @@ def reset_aa():
|
||||
apparmor.aa.aa = apparmor.aa.hasher()
|
||||
apparmor.aa.filelist = apparmor.aa.hasher()
|
||||
apparmor.aa.include = dict()
|
||||
apparmor.aa.existing_profiles = apparmor.aa.hasher()
|
||||
apparmor.aa.active_profiles = apparmor.aa.ProfileList()
|
||||
apparmor.aa.original_aa = apparmor.aa.hasher()
|
||||
|
||||
def find_profiles_from_files(files):
|
||||
@@ -75,7 +75,7 @@ def find_files_from_profiles(profiles):
|
||||
apparmor.aa.read_profiles()
|
||||
|
||||
for profile_name in profiles:
|
||||
profile_to_filename[profile_name] = apparmor.aa.get_profile_filename(profile_name)
|
||||
profile_to_filename[profile_name] = apparmor.aa.get_profile_filename_from_profile_name(profile_name, True)
|
||||
|
||||
reset_aa()
|
||||
|
||||
|
@@ -98,7 +98,8 @@ System-wide configuration for B<aa-notify> is done via
|
||||
# OPTIONAL - custom notification message footer
|
||||
message_footer="For more information visit https://foo.com"
|
||||
|
||||
Per-user configuration is done via ~/.apparmor/notify.conf:
|
||||
Per-user configuration is done via $XDG_CONFIG_HOME/apparmor/notify.conf (or
|
||||
the deprecated ~/.apparmor/notify.conf if it exists):
|
||||
|
||||
# set to 'yes' to enable AppArmor DENIED notifications
|
||||
show_notifications="yes"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.com>
|
||||
# Copyright (C) 2014-2017 Christian Boltz <apparmor@cboltz.de>
|
||||
# Copyright (C) 2014-2018 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
|
||||
@@ -49,6 +49,8 @@ from apparmor.regex import (RE_PROFILE_START, RE_PROFILE_END, RE_PROFILE_LINK,
|
||||
RE_PROFILE_UNIX, RE_RULE_HAS_COMMA, RE_HAS_COMMENT_SPLIT,
|
||||
strip_quotes, parse_profile_start_line, re_match_include )
|
||||
|
||||
from apparmor.profile_list import ProfileList
|
||||
|
||||
from apparmor.profile_storage import ProfileStorage, add_or_remove_flag, ruletypes, write_abi
|
||||
|
||||
import apparmor.rules as aarules
|
||||
@@ -88,7 +90,8 @@ extra_profile_dir = None
|
||||
# To keep track of previously included profile fragments
|
||||
include = dict()
|
||||
|
||||
existing_profiles = dict()
|
||||
active_profiles = ProfileList()
|
||||
extra_profiles = ProfileList()
|
||||
|
||||
# To store the globs entered by users so they can be provided again
|
||||
# format: user_globs['/foo*'] = AARE('/foo*')
|
||||
@@ -216,11 +219,29 @@ def find_executable(bin_path):
|
||||
return full_bin
|
||||
return None
|
||||
|
||||
def get_profile_filename(profile):
|
||||
"""Returns the full profile name"""
|
||||
if existing_profiles.get(profile, False):
|
||||
return existing_profiles[profile]
|
||||
elif profile.startswith('/'):
|
||||
def get_profile_filename_from_profile_name(profile, get_new=False):
|
||||
"""Returns the full profile name for the given profile name"""
|
||||
|
||||
filename = active_profiles.filename_from_profile_name(profile)
|
||||
if filename:
|
||||
return filename
|
||||
|
||||
if get_new:
|
||||
return get_new_profile_filename(profile)
|
||||
|
||||
def get_profile_filename_from_attachment(profile, get_new=False):
|
||||
"""Returns the full profile name for the given attachment"""
|
||||
|
||||
filename = active_profiles.filename_from_attachment(profile)
|
||||
if filename:
|
||||
return filename
|
||||
|
||||
if get_new:
|
||||
return get_new_profile_filename(profile)
|
||||
|
||||
def get_new_profile_filename(profile):
|
||||
'''Compose filename for a new profile'''
|
||||
if profile.startswith('/'):
|
||||
# Remove leading /
|
||||
profile = profile[1:]
|
||||
else:
|
||||
@@ -237,7 +258,7 @@ def name_to_prof_filename(prof_filename):
|
||||
else:
|
||||
bin_path = find_executable(prof_filename)
|
||||
if bin_path:
|
||||
prof_filename = get_profile_filename(bin_path)
|
||||
prof_filename = get_profile_filename_from_attachment(bin_path, True)
|
||||
if os.path.isfile(prof_filename):
|
||||
return (prof_filename, bin_path)
|
||||
|
||||
@@ -463,7 +484,7 @@ def create_new_profile(localfile, is_stub=False):
|
||||
|
||||
def delete_profile(local_prof):
|
||||
"""Deletes the specified file from the disk and remove it from our list"""
|
||||
profile_file = get_profile_filename(local_prof)
|
||||
profile_file = get_profile_filename_from_profile_name(local_prof, True)
|
||||
if os.path.isfile(profile_file):
|
||||
os.remove(profile_file)
|
||||
if aa.get(local_prof, False):
|
||||
@@ -497,13 +518,15 @@ def get_profile(prof_name):
|
||||
if inactive_profile:
|
||||
uname = 'Inactive local profile for %s' % prof_name
|
||||
inactive_profile[prof_name][prof_name]['flags'] = 'complain'
|
||||
orig_filename = inactive_profile[prof_name][prof_name]['filename'] # needed for CMD_VIEW_PROFILE
|
||||
inactive_profile[prof_name][prof_name]['filename'] = ''
|
||||
profile_hash[uname]['username'] = uname
|
||||
profile_hash[uname]['profile_type'] = 'INACTIVE_LOCAL'
|
||||
profile_hash[uname]['profile'] = serialize_profile(inactive_profile[prof_name], prof_name, None)
|
||||
profile_hash[uname]['profile_data'] = inactive_profile
|
||||
|
||||
existing_profiles.pop(prof_name) # remove profile filename from list to force storing in /etc/apparmor.d/ instead of extra_profile_dir
|
||||
# no longer necessary after splitting active and extra profiles
|
||||
# existing_profiles.pop(prof_name) # remove profile filename from list to force storing in /etc/apparmor.d/ instead of extra_profile_dir
|
||||
|
||||
# If no profiles in repo and no inactive profiles
|
||||
if not profile_hash.keys():
|
||||
@@ -537,11 +560,7 @@ def get_profile(prof_name):
|
||||
q.selected = options.index(options[arg])
|
||||
if ans == 'CMD_VIEW_PROFILE':
|
||||
pager = get_pager()
|
||||
proc = subprocess.Popen(pager, stdin=subprocess.PIPE)
|
||||
# proc.communicate('Profile submitted by %s:\n\n%s\n\n' %
|
||||
# (options[arg], p['profile']))
|
||||
proc.communicate(p['profile'].encode())
|
||||
proc.kill()
|
||||
subprocess.call([pager, orig_filename])
|
||||
elif ans == 'CMD_USE_PROFILE':
|
||||
if p['profile_type'] == 'INACTIVE_LOCAL':
|
||||
profile_data = p['profile_data']
|
||||
@@ -559,7 +578,7 @@ def activate_repo_profiles(url, profiles, complain):
|
||||
attach_profile_data(aa, profile_data)
|
||||
write_profile(pname)
|
||||
if complain:
|
||||
fname = get_profile_filename(pname)
|
||||
fname = get_profile_filename_from_profile_name(pname, True)
|
||||
change_profile_flags(profile_dir + fname, None, 'complain', True)
|
||||
aaui.UI_Info(_('Setting %s to complain mode.') % pname)
|
||||
except Exception as e:
|
||||
@@ -591,7 +610,7 @@ def autodep(bin_name, pname=''):
|
||||
# Create a new profile if no existing profile
|
||||
if not profile_data:
|
||||
profile_data = create_new_profile(pname)
|
||||
file = get_profile_filename(pname)
|
||||
file = get_profile_filename_from_profile_name(pname, True)
|
||||
profile_data[pname][pname]['filename'] = None # will be stored in /etc/apparmor.d when saving, so it shouldn't carry the extra_profile_dir filename
|
||||
attach_profile_data(aa, profile_data)
|
||||
attach_profile_data(original_aa, profile_data)
|
||||
@@ -691,15 +710,16 @@ def profile_exists(program):
|
||||
"""Returns True if profile exists, False otherwise"""
|
||||
# Check cache of profiles
|
||||
|
||||
if existing_profiles.get(program, False):
|
||||
if active_profiles.filename_from_attachment(program):
|
||||
return True
|
||||
# Check the disk for profile
|
||||
prof_path = get_profile_filename(program)
|
||||
prof_path = get_profile_filename_from_attachment(program, True)
|
||||
#print(prof_path)
|
||||
if os.path.isfile(prof_path):
|
||||
# Add to cache of profile
|
||||
existing_profiles[program] = prof_path
|
||||
return True
|
||||
raise AppArmorBug('Reached strange condition in profile_exists(), please open a bugreport!')
|
||||
# active_profiles[program] = prof_path
|
||||
# return True
|
||||
return False
|
||||
|
||||
def sync_profile():
|
||||
@@ -1088,9 +1108,9 @@ def handle_children(profile, hat, root):
|
||||
options += 'd'
|
||||
# Define the default option
|
||||
default = None
|
||||
if 'p' in options and os.path.exists(get_profile_filename(exec_target)):
|
||||
if 'p' in options and os.path.exists(get_profile_filename_from_attachment(exec_target, True)):
|
||||
default = 'CMD_px'
|
||||
sys.stdout.write(_('Target profile exists: %s\n') % get_profile_filename(exec_target))
|
||||
sys.stdout.write(_('Target profile exists: %s\n') % get_profile_filename_from_attachment(exec_target, True))
|
||||
elif 'i' in options:
|
||||
default = 'CMD_ix'
|
||||
elif 'c' in options:
|
||||
@@ -1104,7 +1124,7 @@ def handle_children(profile, hat, root):
|
||||
parent_uses_ld_xxx = check_for_LD_XXX(profile)
|
||||
|
||||
sev_db.unload_variables()
|
||||
sev_db.load_variables(get_profile_filename(profile))
|
||||
sev_db.load_variables(get_profile_filename_from_profile_name(profile, True))
|
||||
severity = sev_db.rank_path(exec_target, 'x')
|
||||
|
||||
# Prompt portion starts
|
||||
@@ -1228,7 +1248,7 @@ def handle_children(profile, hat, root):
|
||||
profile_changes[pid] = '%s' % profile
|
||||
|
||||
# Check profile exists for px
|
||||
if not os.path.exists(get_profile_filename(exec_target)):
|
||||
if not os.path.exists(get_profile_filename_from_attachment(exec_target, True)):
|
||||
ynans = 'y'
|
||||
if 'i' in exec_mode:
|
||||
ynans = aaui.UI_YesNo(_('A profile for %s does not exist.\nDo you want to create one?') % exec_target, 'n')
|
||||
@@ -1362,7 +1382,7 @@ def ask_the_questions(log_dict):
|
||||
UI_SelectUpdatedRepoProfile(profile, p)
|
||||
|
||||
sev_db.unload_variables()
|
||||
sev_db.load_variables(get_profile_filename(profile))
|
||||
sev_db.load_variables(get_profile_filename_from_profile_name(profile, True))
|
||||
|
||||
# Sorted list of hats with the profile name coming first
|
||||
hats = list(filter(lambda key: key != profile, sorted(log_dict[aamode][profile].keys())))
|
||||
@@ -1769,7 +1789,7 @@ def set_logfile(filename):
|
||||
def do_logprof_pass(logmark='', passno=0, log_pid=log_pid):
|
||||
# set up variables for this pass
|
||||
# transitions = hasher()
|
||||
global existing_profiles
|
||||
global active_profiles
|
||||
global sev_db
|
||||
# aa = hasher()
|
||||
# profile_changes = hasher()
|
||||
@@ -1786,13 +1806,13 @@ def do_logprof_pass(logmark='', passno=0, log_pid=log_pid):
|
||||
if not sev_db:
|
||||
sev_db = apparmor.severity.Severity(CONFDIR + '/severity.db', _('unknown'))
|
||||
#print(pid)
|
||||
#print(existing_profiles)
|
||||
#print(active_profiles)
|
||||
##if not repo_cf and cfg['repostory']['url']:
|
||||
## repo_cfg = read_config('repository.conf')
|
||||
## 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(log_pid, logfile, existing_profiles, profile_dir)
|
||||
log_reader = apparmor.logparser.ReadLog(log_pid, logfile, active_profiles, profile_dir)
|
||||
log = log_reader.read_log(logmark)
|
||||
#read_log(logmark)
|
||||
|
||||
@@ -1867,13 +1887,11 @@ def save_profiles():
|
||||
if aa[which][which].get('filename', False):
|
||||
oldprofile = aa[which][which]['filename']
|
||||
else:
|
||||
oldprofile = get_profile_filename(which)
|
||||
oldprofile = get_profile_filename_from_attachment(which, True)
|
||||
|
||||
try:
|
||||
newprofile = serialize_profile_from_old_profile(aa[which], which, '')
|
||||
except AttributeError:
|
||||
# see https://bugs.launchpad.net/ubuntu/+source/apparmor/+bug/1528139
|
||||
newprofile = "###\n###\n### Internal error while generating diff, please use '%s' instead\n###\n###\n" % _('View Changes b/w (C)lean profiles')
|
||||
serialize_options = {}
|
||||
serialize_options['METADATA'] = True
|
||||
newprofile = serialize_profile(aa[which], which, serialize_options)
|
||||
|
||||
aaui.UI_Changes(oldprofile, newprofile, comments=True)
|
||||
|
||||
@@ -2085,9 +2103,29 @@ def read_profile(file, active_profile):
|
||||
if profile_data and active_profile:
|
||||
attach_profile_data(aa, profile_data)
|
||||
attach_profile_data(original_aa, profile_data)
|
||||
|
||||
for profile in profile_data: # TODO: also honor hats
|
||||
name = profile_data[profile][profile]['name']
|
||||
attachment = profile_data[profile][profile]['attachment']
|
||||
filename = profile_data[profile][profile]['filename']
|
||||
|
||||
if not attachment and name.startswith('/'):
|
||||
active_profiles.add(filename, name, name) # use name as name and attachment
|
||||
else:
|
||||
active_profiles.add(filename, name, attachment)
|
||||
|
||||
elif profile_data:
|
||||
attach_profile_data(extras, profile_data)
|
||||
|
||||
for profile in profile_data: # TODO: also honor hats
|
||||
name = profile_data[profile][profile]['name']
|
||||
attachment = profile_data[profile][profile]['attachment']
|
||||
filename = profile_data[profile][profile]['filename']
|
||||
|
||||
if not attachment and name.startswith('/'):
|
||||
extra_profiles.add(filename, name, name) # use name as name and attachment
|
||||
else:
|
||||
extra_profiles.add(filename, name, attachment)
|
||||
|
||||
def attach_profile_data(profiles, profile_data):
|
||||
# Make deep copy of data to avoid changes to
|
||||
@@ -2179,9 +2217,6 @@ def parse_profile_data(data, file, do_include):
|
||||
if pps_set_hat_external:
|
||||
profile_data[profile][hat]['external'] = True
|
||||
|
||||
# Profile stored
|
||||
existing_profiles[profile] = file
|
||||
|
||||
# save profile name and filename
|
||||
profile_data[profile][hat]['name'] = profile
|
||||
profile_data[profile][hat]['filename'] = file
|
||||
@@ -2506,6 +2541,11 @@ def parse_profile_data(data, file, do_include):
|
||||
else:
|
||||
raise AppArmorException(_('Syntax Error: Unknown line found in file %(file)s line %(lineno)s:\n %(line)s') % { 'file': file, 'lineno': lineno + 1, 'line': line })
|
||||
|
||||
if lastline:
|
||||
# lastline gets merged into line (and reset to None) when reading the next line.
|
||||
# If it isn't empty, this means there's something unparseable at the end of the profile
|
||||
raise AppArmorException(_('Syntax Error: Unknown line found in file %(file)s line %(lineno)s:\n %(line)s') % { 'file': file, 'lineno': lineno + 1, 'line': lastline })
|
||||
|
||||
# Below is not required I'd say
|
||||
if not do_include:
|
||||
for hatglob in cfg['required_hats'].keys():
|
||||
@@ -2880,7 +2920,11 @@ def serialize_profile(profile_data, name, options):
|
||||
# comment.replace('\\n', '\n')
|
||||
# string += comment + '\n'
|
||||
|
||||
prof_filename = get_profile_filename(name)
|
||||
if options and options.get('is_attachment'):
|
||||
prof_filename = get_profile_filename_from_attachment(name, True)
|
||||
else:
|
||||
prof_filename = get_profile_filename_from_profile_name(name, True)
|
||||
|
||||
if filelist.get(prof_filename, False):
|
||||
data += write_abi(filelist[prof_filename], 0)
|
||||
data += write_alias(filelist[prof_filename], 0)
|
||||
@@ -2914,429 +2958,18 @@ def serialize_parse_profile_start(line, file, lineno, profile, hat, prof_data_pr
|
||||
|
||||
return (profile, hat, attachment, flags, in_contained_hat, correct)
|
||||
|
||||
def serialize_profile_from_old_profile(profile_data, name, options):
|
||||
data = []
|
||||
string = ''
|
||||
include_metadata = False
|
||||
include_flags = True
|
||||
prof_filename = get_profile_filename(name)
|
||||
|
||||
write_filelist = deepcopy(filelist[prof_filename])
|
||||
write_prof_data = deepcopy(profile_data)
|
||||
|
||||
# XXX profile_data / write_prof_data contain only one profile with its hats
|
||||
# XXX this will explode if a file contains multiple profiles, see https://bugs.launchpad.net/ubuntu/+source/apparmor/+bug/1528139
|
||||
# XXX fixing this needs lots of write_prof_data[hat] -> write_prof_data[profile][hat] changes (and of course also a change in the calling code)
|
||||
# XXX (the better option is a full rewrite of serialize_profile_from_old_profile())
|
||||
|
||||
if options: # and type(options) == dict:
|
||||
if options.get('METADATA', False):
|
||||
include_metadata = True
|
||||
if options.get('NO_FLAGS', False):
|
||||
include_flags = False
|
||||
|
||||
if include_metadata:
|
||||
string = '# Last Modified: %s\n' % time.asctime()
|
||||
|
||||
if (profile_data[name].get('repo', False) and
|
||||
profile_data[name]['repo']['url'] and
|
||||
profile_data[name]['repo']['user'] and
|
||||
profile_data[name]['repo']['id']):
|
||||
repo = profile_data[name]['repo']
|
||||
string += '# REPOSITORY: %s %s %s\n' % (repo['url'], repo['user'], repo['id'])
|
||||
elif profile_data[name]['repo']['neversubmit']:
|
||||
string += '# REPOSITORY: NEVERSUBMIT\n'
|
||||
|
||||
if not os.path.isfile(prof_filename):
|
||||
raise AppArmorException(_("Can't find existing profile to modify"))
|
||||
|
||||
# profiles_list = filelist[prof_filename].keys() # XXX
|
||||
|
||||
with open_file_read(prof_filename) as f_in:
|
||||
profile = None
|
||||
hat = None
|
||||
write_methods = {'abi': write_abi,
|
||||
'alias': write_alias,
|
||||
'lvar': write_list_vars,
|
||||
'include': write_includes,
|
||||
'rlimit': write_rlimits,
|
||||
'capability': write_capabilities,
|
||||
'network': write_netdomain,
|
||||
'dbus': write_dbus,
|
||||
'mount': write_mount,
|
||||
'signal': write_signal,
|
||||
'ptrace': write_ptrace,
|
||||
'pivot_root': write_pivot_root,
|
||||
'unix': write_unix,
|
||||
'link': write_links,
|
||||
'file': write_file,
|
||||
'change_profile': write_change_profile,
|
||||
}
|
||||
default_write_order = [ 'alias',
|
||||
'lvar',
|
||||
'include',
|
||||
'rlimit',
|
||||
'capability',
|
||||
'network',
|
||||
'dbus',
|
||||
'mount',
|
||||
'signal',
|
||||
'ptrace',
|
||||
'pivot_root',
|
||||
'unix',
|
||||
'link',
|
||||
'file',
|
||||
'change_profile',
|
||||
]
|
||||
# prof_correct = True # XXX correct?
|
||||
segments = {'alias': False,
|
||||
'lvar': False,
|
||||
'include': False,
|
||||
'rlimit': False,
|
||||
'capability': False,
|
||||
'network': False,
|
||||
'dbus': False,
|
||||
'mount': False,
|
||||
'signal': True, # not handled otherwise yet
|
||||
'ptrace': True, # not handled otherwise yet
|
||||
'pivot_root': False,
|
||||
'unix': False,
|
||||
'link': False,
|
||||
'file': False,
|
||||
'change_profile': False,
|
||||
'include_local_started': False, # unused
|
||||
}
|
||||
|
||||
def write_prior_segments(prof_data, segments, line):
|
||||
data = []
|
||||
for segs in list(filter(lambda x: segments[x], segments.keys())):
|
||||
depth = len(line) - len(line.lstrip())
|
||||
data += write_methods[segs](prof_data, int(depth / 2))
|
||||
segments[segs] = False
|
||||
# delete rules from prof_data to avoid duplication (they are in data now)
|
||||
if prof_data['allow'].get(segs, False):
|
||||
prof_data['allow'].pop(segs)
|
||||
if prof_data['deny'].get(segs, False):
|
||||
prof_data['deny'].pop(segs)
|
||||
if prof_data.get(segs, False):
|
||||
t = type(prof_data[segs])
|
||||
prof_data[segs] = t()
|
||||
return data
|
||||
|
||||
#data.append('reading prof')
|
||||
for line in f_in:
|
||||
correct = True
|
||||
line = line.rstrip('\n')
|
||||
#data.append(' ')#data.append('read: '+line)
|
||||
if RE_PROFILE_START.search(line):
|
||||
|
||||
(profile, hat, attachment, flags, in_contained_hat, correct) = serialize_parse_profile_start(
|
||||
line, prof_filename, None, profile, hat, write_prof_data[hat]['profile'], write_prof_data[hat]['external'], correct)
|
||||
|
||||
if not write_prof_data[hat]['name'] == profile:
|
||||
correct = False
|
||||
|
||||
if not write_filelist['profiles'][profile][hat] is True:
|
||||
correct = False
|
||||
|
||||
if not write_prof_data[hat]['flags'] == flags:
|
||||
correct = False
|
||||
|
||||
#Write the profile start
|
||||
if correct:
|
||||
if write_filelist:
|
||||
data += write_alias(write_filelist, 0)
|
||||
data += write_list_vars(write_filelist, 0)
|
||||
data += write_includes(write_filelist, 0)
|
||||
data.append(line)
|
||||
else:
|
||||
if write_prof_data[hat]['name'] == profile:
|
||||
depth = len(line) - len(line.lstrip())
|
||||
data += write_header(write_prof_data[name], int(depth / 2), name, False, include_flags)
|
||||
|
||||
elif RE_PROFILE_END.search(line):
|
||||
# DUMP REMAINDER OF PROFILE
|
||||
if profile:
|
||||
depth = int(len(line) - len(line.lstrip()) / 2) + 1
|
||||
|
||||
# first write sections that were modified
|
||||
#for segs in write_methods.keys():
|
||||
for segs in default_write_order:
|
||||
if segments[segs]:
|
||||
data += write_methods[segs](write_prof_data[name], depth)
|
||||
segments[segs] = False
|
||||
# delete rules from write_prof_data to avoid duplication (they are in data now)
|
||||
if write_prof_data[name]['allow'].get(segs, False):
|
||||
write_prof_data[name]['allow'].pop(segs)
|
||||
if write_prof_data[name]['deny'].get(segs, False):
|
||||
write_prof_data[name]['deny'].pop(segs)
|
||||
if write_prof_data[name].get(segs, False):
|
||||
t = type(write_prof_data[name][segs])
|
||||
write_prof_data[name][segs] = t()
|
||||
|
||||
# then write everything else
|
||||
for segs in default_write_order:
|
||||
data += write_methods[segs](write_prof_data[name], depth)
|
||||
|
||||
write_prof_data.pop(name)
|
||||
|
||||
#Append local includes
|
||||
data.append(line)
|
||||
|
||||
if not in_contained_hat:
|
||||
# Embedded hats
|
||||
depth = int((len(line) - len(line.lstrip())) / 2)
|
||||
pre2 = ' ' * (depth + 1)
|
||||
for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))):
|
||||
if not profile_data[hat]['external']:
|
||||
data.append('')
|
||||
if profile_data[hat]['profile']:
|
||||
data += list(map(str, write_header(profile_data[hat], depth + 1, hat, True, include_flags)))
|
||||
else:
|
||||
data += list(map(str, write_header(profile_data[hat], depth + 1, '^' + hat, True, include_flags)))
|
||||
|
||||
data += list(map(str, write_rules(profile_data[hat], depth + 2)))
|
||||
|
||||
data.append('%s}' % pre2)
|
||||
|
||||
# External hats
|
||||
for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))):
|
||||
if profile_data[hat].get('external', False):
|
||||
data.append('')
|
||||
data += list(map(lambda x: ' %s' % x, write_piece(profile_data, depth - 1, name, name, include_flags)))
|
||||
data.append(' }')
|
||||
|
||||
if in_contained_hat:
|
||||
#Hat processed, remove it
|
||||
hat = profile
|
||||
in_contained_hat = False
|
||||
else:
|
||||
profile = None
|
||||
|
||||
elif CapabilityRule.match(line):
|
||||
cap = CapabilityRule.parse(line)
|
||||
if write_prof_data[hat]['capability'].is_covered(cap, True, True):
|
||||
if not segments['capability'] and True in segments.values():
|
||||
data += write_prior_segments(write_prof_data[name], segments, line)
|
||||
segments['capability'] = True
|
||||
write_prof_data[hat]['capability'].delete(cap)
|
||||
data.append(line)
|
||||
else:
|
||||
# To-Do
|
||||
pass
|
||||
elif RE_PROFILE_LINK.search(line):
|
||||
matches = RE_PROFILE_LINK.search(line).groups()
|
||||
audit = False
|
||||
if matches[0]:
|
||||
audit = True
|
||||
allow = 'allow'
|
||||
if matches[1] and matches[1].strip() == 'deny':
|
||||
allow = 'deny'
|
||||
|
||||
subset = matches[3]
|
||||
link = strip_quotes(matches[6])
|
||||
value = strip_quotes(matches[7])
|
||||
if not write_prof_data[hat][allow]['link'][link]['to'] == value:
|
||||
correct = False
|
||||
if not write_prof_data[hat][allow]['link'][link]['mode'] & apparmor.aamode.AA_MAY_LINK:
|
||||
correct = False
|
||||
if subset and not write_prof_data[hat][allow]['link'][link]['mode'] & apparmor.aamode.AA_LINK_SUBSET:
|
||||
correct = False
|
||||
if audit and not write_prof_data[hat][allow]['link'][link]['audit'] & apparmor.aamode.AA_LINK_SUBSET:
|
||||
correct = False
|
||||
|
||||
if correct:
|
||||
if not segments['link'] and True in segments.values():
|
||||
data += write_prior_segments(write_prof_data[name], segments, line)
|
||||
segments['link'] = True
|
||||
write_prof_data[hat][allow]['link'].pop(link)
|
||||
data.append(line)
|
||||
else:
|
||||
# To-Do
|
||||
pass
|
||||
|
||||
elif ChangeProfileRule.match(line):
|
||||
change_profile_obj = ChangeProfileRule.parse(line)
|
||||
if write_prof_data[hat]['change_profile'].is_covered(change_profile_obj, True, True):
|
||||
if not segments['change_profile'] and True in segments.values():
|
||||
data += write_prior_segments(write_prof_data[name], segments, line)
|
||||
segments['change_profile'] = True
|
||||
write_prof_data[hat]['change_profile'].delete(change_profile_obj)
|
||||
data.append(line)
|
||||
|
||||
elif RE_PROFILE_ALIAS.search(line):
|
||||
matches = RE_PROFILE_ALIAS.search(line).groups()
|
||||
|
||||
from_name = strip_quotes(matches[0])
|
||||
to_name = strip_quotes(matches[1])
|
||||
|
||||
if profile:
|
||||
if not write_prof_data[hat]['alias'][from_name] == to_name:
|
||||
correct = False
|
||||
else:
|
||||
if not write_filelist['alias'][from_name] == to_name:
|
||||
correct = False
|
||||
|
||||
if correct:
|
||||
if not segments['alias'] and True in segments.values():
|
||||
data += write_prior_segments(write_prof_data[name], segments, line)
|
||||
segments['alias'] = True
|
||||
if profile:
|
||||
write_prof_data[hat]['alias'].pop(from_name)
|
||||
else:
|
||||
write_filelist['alias'].pop(from_name)
|
||||
data.append(line)
|
||||
else:
|
||||
#To-Do
|
||||
pass
|
||||
|
||||
elif RlimitRule.match(line):
|
||||
rlimit_obj = RlimitRule.parse(line)
|
||||
|
||||
if write_prof_data[hat]['rlimit'].is_covered(rlimit_obj, True, True):
|
||||
if not segments['rlimit'] and True in segments.values():
|
||||
data += write_prior_segments(write_prof_data[name], segments, line)
|
||||
segments['rlimit'] = True
|
||||
write_prof_data[hat]['rlimit'].delete(rlimit_obj)
|
||||
data.append(line)
|
||||
else:
|
||||
#To-Do
|
||||
pass
|
||||
|
||||
elif RE_PROFILE_BOOLEAN.search(line):
|
||||
matches = RE_PROFILE_BOOLEAN.search(line).groups()
|
||||
bool_var = matches[0]
|
||||
value = matches[1]
|
||||
|
||||
if not write_prof_data[hat]['lvar'][bool_var] == value:
|
||||
correct = False
|
||||
|
||||
if correct:
|
||||
if not segments['lvar'] and True in segments.values():
|
||||
data += write_prior_segments(write_prof_data[name], segments, line)
|
||||
segments['lvar'] = True
|
||||
write_prof_data[hat]['lvar'].pop(bool_var)
|
||||
data.append(line)
|
||||
else:
|
||||
#To-Do
|
||||
pass
|
||||
elif RE_PROFILE_VARIABLE.search(line):
|
||||
matches = RE_PROFILE_VARIABLE.search(line).groups()
|
||||
list_var = strip_quotes(matches[0])
|
||||
var_operation = matches[1]
|
||||
value = strip_quotes(matches[2])
|
||||
var_set = hasher()
|
||||
if var_operation == '+=':
|
||||
correct = False # adding proper support for "add to variable" needs big changes
|
||||
# (like storing a variable's "history" - where it was initially defined and what got added where)
|
||||
# so just skip any comparison and assume a non-match
|
||||
elif profile:
|
||||
store_list_var(var_set, list_var, value, var_operation, prof_filename)
|
||||
if not var_set[list_var] == write_prof_data['lvar'].get(list_var, False):
|
||||
correct = False
|
||||
else:
|
||||
store_list_var(var_set, list_var, value, var_operation, prof_filename)
|
||||
if not var_set[list_var] == write_filelist['lvar'].get(list_var, False):
|
||||
correct = False
|
||||
|
||||
if correct:
|
||||
if not segments['lvar'] and True in segments.values():
|
||||
data += write_prior_segments(write_prof_data[name], segments, line)
|
||||
segments['lvar'] = True
|
||||
if profile:
|
||||
write_prof_data[hat]['lvar'].pop(list_var)
|
||||
else:
|
||||
write_filelist['lvar'].pop(list_var)
|
||||
data.append(line)
|
||||
else:
|
||||
#To-Do
|
||||
pass
|
||||
|
||||
elif re_match_include(line):
|
||||
include_name = re_match_include(line)
|
||||
if profile:
|
||||
if write_prof_data[hat]['include'].get(include_name, False):
|
||||
if not segments['include'] and True in segments.values():
|
||||
data += write_prior_segments(write_prof_data[name], segments, line)
|
||||
segments['include'] = True
|
||||
write_prof_data[hat]['include'].pop(include_name)
|
||||
data.append(line)
|
||||
else:
|
||||
if write_filelist['include'].get(include_name, False):
|
||||
write_filelist['include'].pop(include_name)
|
||||
data.append(line)
|
||||
|
||||
elif NetworkRule.match(line):
|
||||
network_obj = NetworkRule.parse(line)
|
||||
if write_prof_data[hat]['network'].is_covered(network_obj, True, True):
|
||||
if not segments['network'] and True in segments.values():
|
||||
data += write_prior_segments(write_prof_data[name], segments, line)
|
||||
segments['network'] = True
|
||||
write_prof_data[hat]['network'].delete(network_obj)
|
||||
data.append(line)
|
||||
|
||||
elif RE_PROFILE_CHANGE_HAT.search(line):
|
||||
# "^hat," declarations are no longer supported, ignore them and don't write out the line
|
||||
# (parse_profile_data() already prints a warning about that)
|
||||
pass
|
||||
elif RE_PROFILE_HAT_DEF.search(line):
|
||||
matches = RE_PROFILE_HAT_DEF.search(line)
|
||||
in_contained_hat = True
|
||||
hat = matches.group('hat')
|
||||
hat = strip_quotes(hat)
|
||||
flags = matches.group('flags')
|
||||
|
||||
if not write_prof_data[hat]['flags'] == flags:
|
||||
correct = False
|
||||
if not write_filelist['profile'][profile][hat]:
|
||||
correct = False
|
||||
if correct:
|
||||
data.append(line)
|
||||
else:
|
||||
#To-Do
|
||||
pass
|
||||
elif FileRule.match(line):
|
||||
# leading permissions could look like a keyword, therefore handle file rules after everything else
|
||||
file_obj = FileRule.parse(line)
|
||||
|
||||
if write_prof_data[hat]['file'].is_covered(file_obj, True, True):
|
||||
if not segments['file'] and True in segments.values():
|
||||
data += write_prior_segments(write_prof_data[name], segments, line)
|
||||
segments['file'] = True
|
||||
write_prof_data[hat]['file'].delete(file_obj)
|
||||
data.append(line)
|
||||
else:
|
||||
#To-Do
|
||||
pass
|
||||
|
||||
else:
|
||||
if correct:
|
||||
data.append(line)
|
||||
else:
|
||||
#To-Do
|
||||
pass
|
||||
# data.append('prof done')
|
||||
# if write_filelist:
|
||||
# data += write_alias(write_filelist, 0)
|
||||
# data += write_list_vars(write_filelist, 0)
|
||||
# data += write_includes(write_filelist, 0)
|
||||
# data.append('from filelist over')
|
||||
# data += write_piece(write_prof_data, 0, name, name, include_flags)
|
||||
|
||||
string += '\n'.join(data)
|
||||
|
||||
return string + '\n'
|
||||
|
||||
def write_profile_ui_feedback(profile):
|
||||
def write_profile_ui_feedback(profile, is_attachment=False):
|
||||
aaui.UI_Info(_('Writing updated profile for %s.') % profile)
|
||||
write_profile(profile)
|
||||
write_profile(profile, is_attachment)
|
||||
|
||||
def write_profile(profile):
|
||||
def write_profile(profile, is_attachment=False):
|
||||
prof_filename = None
|
||||
if aa[profile][profile].get('filename', False):
|
||||
prof_filename = aa[profile][profile]['filename']
|
||||
elif is_attachment:
|
||||
prof_filename = get_profile_filename_from_attachment(profile, True)
|
||||
else:
|
||||
prof_filename = get_profile_filename(profile)
|
||||
prof_filename = get_profile_filename_from_profile_name(profile, True)
|
||||
|
||||
newprof = tempfile.NamedTemporaryFile('w', suffix='~', delete=False, dir=profile_dir)
|
||||
if os.path.exists(prof_filename):
|
||||
@@ -3346,8 +2979,7 @@ def write_profile(profile):
|
||||
#os.chmod(newprof.name, permission_600)
|
||||
pass
|
||||
|
||||
serialize_options = {}
|
||||
serialize_options['METADATA'] = True
|
||||
serialize_options = {'METADATA': True, 'is_attachment': is_attachment}
|
||||
|
||||
profile_string = serialize_profile(aa[profile], profile, serialize_options)
|
||||
newprof.write(profile_string)
|
||||
@@ -3470,7 +3102,7 @@ def reload_base(bin_path):
|
||||
if not check_for_apparmor():
|
||||
return None
|
||||
|
||||
prof_filename = get_profile_filename(bin_path)
|
||||
prof_filename = get_profile_filename_from_profile_name(bin_path, True)
|
||||
|
||||
# XXX use reload_profile() from tools.py instead (and don't hide output in /dev/null)
|
||||
subprocess.call("cat '%s' | %s -I%s -r >/dev/null 2>&1" % (prof_filename, parser, profile_dir), shell=True)
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.com>
|
||||
# Copyright (C) 2015-2016 Christian Boltz <apparmor@cboltz.de>
|
||||
# Copyright (C) 2015-2018 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
|
||||
@@ -44,11 +44,11 @@ class ReadLog:
|
||||
# used to pre-filter log lines so that we hand over only relevant lines to LibAppArmor parsing
|
||||
RE_LOG_ALL = re.compile('(' + '|'.join(RE_log_parts) + ')')
|
||||
|
||||
def __init__(self, pid, filename, existing_profiles, profile_dir):
|
||||
def __init__(self, pid, filename, active_profiles, profile_dir):
|
||||
self.filename = filename
|
||||
self.profile_dir = profile_dir
|
||||
self.pid = pid
|
||||
self.existing_profiles = existing_profiles
|
||||
self.active_profiles = active_profiles
|
||||
self.log = []
|
||||
self.debug_logger = DebugLogger('ReadLog')
|
||||
self.LOG = None
|
||||
@@ -447,15 +447,16 @@ class ReadLog:
|
||||
def profile_exists(self, program):
|
||||
"""Returns True if profile exists, False otherwise"""
|
||||
# Check cache of profiles
|
||||
if self.existing_profiles.get(program, False):
|
||||
if self.active_profiles.filename_from_profile_name(program):
|
||||
return True
|
||||
# Check the disk for profile
|
||||
prof_path = self.get_profile_filename(program)
|
||||
#print(prof_path)
|
||||
if os.path.isfile(prof_path):
|
||||
# Add to cache of profile
|
||||
self.existing_profiles[program] = prof_path
|
||||
return True
|
||||
raise AppArmorBug('This should never happen, please open a bugreport!')
|
||||
# self.active_profiles[program] = prof_path
|
||||
# return True
|
||||
return False
|
||||
|
||||
def get_profile_filename(self, profile):
|
||||
|
73
utils/apparmor/profile_list.py
Normal file
73
utils/apparmor/profile_list.py
Normal file
@@ -0,0 +1,73 @@
|
||||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2018 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
|
||||
# License as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
from apparmor.aare import AARE
|
||||
from apparmor.common import AppArmorBug, AppArmorException
|
||||
|
||||
# setup module translations
|
||||
from apparmor.translations import init_translation
|
||||
_ = init_translation()
|
||||
|
||||
|
||||
class ProfileList:
|
||||
''' Stores the list of profiles (both name and attachment) and in which files they live '''
|
||||
|
||||
def __init__(self):
|
||||
self.profile_names = {} # profile name -> filename
|
||||
self.attachments = {} # attachment -> filename
|
||||
self.attachments_AARE = {} # AARE(attachment) -> filename
|
||||
|
||||
def add(self, filename, profile_name, attachment):
|
||||
''' Add the given profile and attachment to the list '''
|
||||
|
||||
if not filename:
|
||||
raise AppArmorBug('Empty filename given to ProfileList')
|
||||
|
||||
if not profile_name and not attachment:
|
||||
raise AppArmorBug('Neither profile name or attachment given')
|
||||
|
||||
if profile_name in self.profile_names:
|
||||
raise AppArmorException(_('Profile %(profile_name)s exists in %(filename)s and %(filename2)s' % {'profile_name': profile_name, 'filename': filename, 'filename2': self.profile_names[profile_name]}))
|
||||
|
||||
if attachment in self.attachments:
|
||||
raise AppArmorException(_('Profile for %(profile_name)s exists in %(filename)s and %(filename2)s' % {'profile_name': attachment, 'filename': filename, 'filename2': self.attachments[attachment]}))
|
||||
|
||||
if profile_name:
|
||||
self.profile_names[profile_name] = filename
|
||||
|
||||
if attachment:
|
||||
self.attachments[attachment] = filename
|
||||
self.attachments_AARE[attachment] = AARE(attachment, True)
|
||||
|
||||
def filename_from_profile_name(self, name):
|
||||
''' Return profile filename for the given profile name, or None '''
|
||||
|
||||
return self.profile_names.get(name, None)
|
||||
|
||||
def filename_from_attachment(self, attachment):
|
||||
''' Return profile filename for the given attachment/executable path, or None '''
|
||||
|
||||
if not attachment.startswith( ('/', '@', '{') ):
|
||||
raise AppArmorBug('Called filename_from_attachment with non-path attachment: %s' % attachment)
|
||||
|
||||
# plain path
|
||||
if self.attachments.get(attachment):
|
||||
return self.attachments[attachment]
|
||||
|
||||
# try AARE matches to cover profile names with alternations and wildcards
|
||||
for path in self.attachments.keys():
|
||||
if self.attachments_AARE[path].match(attachment):
|
||||
return self.attachments[path] # XXX this returns the first match, not necessarily the best one
|
||||
|
||||
return None # nothing found
|
@@ -1,5 +1,6 @@
|
||||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.com>
|
||||
# Copyright (C) 2015-2018 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
|
||||
@@ -66,12 +67,12 @@ class aa_tools:
|
||||
profile = fq_path
|
||||
else:
|
||||
program = fq_path
|
||||
profile = apparmor.get_profile_filename(fq_path)
|
||||
profile = apparmor.get_profile_filename_from_attachment(fq_path, True)
|
||||
else:
|
||||
which = apparmor.which(p)
|
||||
if which is not None:
|
||||
program = apparmor.get_full_path(which)
|
||||
profile = apparmor.get_profile_filename(program)
|
||||
profile = apparmor.get_profile_filename_from_attachment(program, True)
|
||||
elif os.path.exists(os.path.join(apparmor.profile_dir, p)):
|
||||
program = None
|
||||
profile = apparmor.get_full_path(os.path.join(apparmor.profile_dir, p)).strip()
|
||||
@@ -190,7 +191,7 @@ class aa_tools:
|
||||
|
||||
apparmor.check_qualifiers(program)
|
||||
|
||||
if os.path.exists(apparmor.get_profile_filename(program)) and not self.force:
|
||||
if os.path.exists(apparmor.get_profile_filename_from_attachment(program, True)) and not self.force:
|
||||
aaui.UI_Info(_('Profile for %s already exists - skipping.') % program)
|
||||
else:
|
||||
apparmor.autodep(program)
|
||||
@@ -198,7 +199,7 @@ class aa_tools:
|
||||
apparmor.reload(program)
|
||||
|
||||
def clean_profile(self, program):
|
||||
filename = apparmor.get_profile_filename(program)
|
||||
filename = apparmor.get_profile_filename_from_attachment(program, True)
|
||||
import apparmor.cleanprofile as cleanprofile
|
||||
prof = cleanprofile.Prof(filename)
|
||||
cleanprof = cleanprofile.CleanProf(True, prof, prof)
|
||||
@@ -220,14 +221,14 @@ class aa_tools:
|
||||
while ans != 'CMD_SAVE_CHANGES':
|
||||
ans, arg = q.promptUser()
|
||||
if ans == 'CMD_SAVE_CHANGES':
|
||||
apparmor.write_profile_ui_feedback(program)
|
||||
apparmor.write_profile_ui_feedback(program, True)
|
||||
self.reload_profile(filename)
|
||||
elif ans == 'CMD_VIEW_CHANGES':
|
||||
#oldprofile = apparmor.serialize_profile(apparmor.original_aa[program], program, '')
|
||||
newprofile = apparmor.serialize_profile(apparmor.aa[program], program, '')
|
||||
newprofile = apparmor.serialize_profile(apparmor.aa[program], program, {'is_attachment': True})
|
||||
aaui.UI_Changes(filename, newprofile, comments=True)
|
||||
else:
|
||||
apparmor.write_profile_ui_feedback(program)
|
||||
apparmor.write_profile_ui_feedback(program, True)
|
||||
self.reload_profile(filename)
|
||||
else:
|
||||
raise apparmor.AppArmorException(_('The profile for %s does not exists. Nothing to clean.') % program)
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#! /usr/bin/python3
|
||||
# ------------------------------------------------------------------
|
||||
#
|
||||
# Copyright (C) 2015 Christian Boltz <apparmor@cboltz.de>
|
||||
# Copyright (C) 2015-2018 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
|
||||
@@ -17,6 +17,7 @@ from apparmor.common import open_file_read
|
||||
|
||||
import apparmor.aa
|
||||
from apparmor.logparser import ReadLog
|
||||
from apparmor.profile_list import ProfileList
|
||||
|
||||
class TestLibapparmorTestMulti(AATest):
|
||||
'''Parse all libraries/libapparmor/testsuite/test_multi tests and compare the result with the *.out files'''
|
||||
@@ -222,9 +223,15 @@ class TestLogToProfile(AATest):
|
||||
if '//' in profile:
|
||||
profile, hat = profile.split('//')
|
||||
|
||||
apparmor.aa.existing_profiles = {profile: profile_dummy_file}
|
||||
apparmor.aa.active_profiles = ProfileList()
|
||||
|
||||
log_reader = ReadLog(dict(), logfile, apparmor.aa.existing_profiles, '')
|
||||
# optional for now, might be needed one day
|
||||
# if profile.startswith('/'):
|
||||
# apparmor.aa.active_profiles.add(profile_dummy_file, profile, profile)
|
||||
# else:
|
||||
apparmor.aa.active_profiles.add(profile_dummy_file, profile, '')
|
||||
|
||||
log_reader = ReadLog(dict(), logfile, apparmor.aa.active_profiles, '')
|
||||
log = log_reader.read_log('')
|
||||
|
||||
for root in log:
|
||||
|
@@ -40,11 +40,6 @@ skip_startswith = (
|
||||
# testcases that should raise an exception, but don't
|
||||
exception_not_raised = [
|
||||
# most abi/bad_* aren't detected as bad by the basic implementation in the tools
|
||||
'abi/bad_1.sd',
|
||||
'abi/bad_2.sd',
|
||||
'abi/bad_3.sd',
|
||||
'abi/bad_4.sd',
|
||||
'abi/bad_5.sd',
|
||||
'abi/bad_10.sd',
|
||||
'abi/bad_11.sd',
|
||||
'abi/bad_12.sd',
|
||||
@@ -155,13 +150,9 @@ exception_not_raised = [
|
||||
'unix/bad_regex_04.sd',
|
||||
'unix/bad_shutdown_1.sd',
|
||||
'unix/bad_shutdown_2.sd',
|
||||
'vars/boolean/boolean_bad_1.sd',
|
||||
'vars/boolean/boolean_bad_2.sd',
|
||||
'vars/boolean/boolean_bad_3.sd',
|
||||
'vars/boolean/boolean_bad_4.sd',
|
||||
'vars/boolean/boolean_bad_6.sd',
|
||||
'vars/boolean/boolean_bad_7.sd',
|
||||
'vars/boolean/boolean_bad_8.sd',
|
||||
'vars/vars_bad_3.sd',
|
||||
'vars/vars_bad_4.sd',
|
||||
'vars/vars_bad_5.sd',
|
||||
@@ -200,7 +191,6 @@ exception_not_raised = [
|
||||
'vars/vars_recursion_2.sd',
|
||||
'vars/vars_recursion_3.sd',
|
||||
'vars/vars_recursion_4.sd',
|
||||
'vars/vars_simple_assignment_10.sd',
|
||||
'vars/vars_simple_assignment_3.sd',
|
||||
'vars/vars_simple_assignment_8.sd',
|
||||
'vars/vars_simple_assignment_9.sd',
|
||||
|
114
utils/test/test-profile-list.py
Normal file
114
utils/test/test-profile-list.py
Normal file
@@ -0,0 +1,114 @@
|
||||
#! /usr/bin/python3
|
||||
# ------------------------------------------------------------------
|
||||
#
|
||||
# Copyright (C) 2018 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
|
||||
# License published by the Free Software Foundation.
|
||||
#
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
import unittest
|
||||
from common_test import AATest, setup_all_loops
|
||||
|
||||
from apparmor.common import AppArmorBug, AppArmorException
|
||||
from apparmor.profile_list import ProfileList
|
||||
|
||||
class TestAdd(AATest):
|
||||
def AASetup(self):
|
||||
self.pl = ProfileList()
|
||||
|
||||
def testEmpty(self):
|
||||
self.assertEqual(self.pl.profile_names, {})
|
||||
self.assertEqual(self.pl.attachments, {})
|
||||
|
||||
def testAdd_1(self):
|
||||
self.pl.add('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo')
|
||||
self.assertEqual(self.pl.profile_names, {'foo': '/etc/apparmor.d/bin.foo'})
|
||||
self.assertEqual(self.pl.attachments, {'/bin/foo': '/etc/apparmor.d/bin.foo'})
|
||||
|
||||
def testAdd_2(self):
|
||||
self.pl.add('/etc/apparmor.d/bin.foo', None, '/bin/foo')
|
||||
self.assertEqual(self.pl.profile_names, {})
|
||||
self.assertEqual(self.pl.attachments, {'/bin/foo': '/etc/apparmor.d/bin.foo'})
|
||||
|
||||
def testAdd_3(self):
|
||||
self.pl.add('/etc/apparmor.d/bin.foo', 'foo', None)
|
||||
self.assertEqual(self.pl.profile_names, {'foo': '/etc/apparmor.d/bin.foo'})
|
||||
self.assertEqual(self.pl.attachments, {})
|
||||
|
||||
|
||||
def testAddError_1(self):
|
||||
with self.assertRaises(AppArmorBug):
|
||||
self.pl.add('', 'foo', '/bin/foo') # no filename
|
||||
|
||||
def testAddError_2(self):
|
||||
with self.assertRaises(AppArmorBug):
|
||||
self.pl.add('/etc/apparmor.d/bin.foo', None, None) # neither attachment or profile name
|
||||
|
||||
def testAddError_twice_1(self):
|
||||
self.pl.add('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo')
|
||||
with self.assertRaises(AppArmorException):
|
||||
self.pl.add('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo')
|
||||
|
||||
def testAddError_twice_2(self):
|
||||
self.pl.add('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo')
|
||||
with self.assertRaises(AppArmorException):
|
||||
self.pl.add('/etc/apparmor.d/bin.foo', 'foo', None)
|
||||
|
||||
def testAddError_twice_3(self):
|
||||
self.pl.add('/etc/apparmor.d/bin.foo', None, '/bin/foo')
|
||||
with self.assertRaises(AppArmorException):
|
||||
self.pl.add('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo')
|
||||
|
||||
def testAddError_twice_4(self):
|
||||
self.pl.add('/etc/apparmor.d/bin.foo', None, '/bin/foo')
|
||||
with self.assertRaises(AppArmorException):
|
||||
self.pl.add('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo')
|
||||
|
||||
def testAddError_twice_5(self):
|
||||
self.pl.add('/etc/apparmor.d/bin.foo', 'foo', None)
|
||||
with self.assertRaises(AppArmorException):
|
||||
self.pl.add('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo')
|
||||
|
||||
class TestFilename_from_profile_name(AATest):
|
||||
tests = [
|
||||
('foo', '/etc/apparmor.d/bin.foo'),
|
||||
('/bin/foo', None),
|
||||
('bar', None),
|
||||
]
|
||||
|
||||
def AASetup(self):
|
||||
self.pl = ProfileList()
|
||||
self.pl.add('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo')
|
||||
|
||||
def _run_test(self, params, expected):
|
||||
self.assertEqual(self.pl.filename_from_profile_name(params), expected)
|
||||
|
||||
class TestFilename_from_attachment(AATest):
|
||||
tests = [
|
||||
('/bin/foo', '/etc/apparmor.d/bin.foo'),
|
||||
('/bin/baz', '/etc/apparmor.d/bin.baz'),
|
||||
('/bin/foobar', '/etc/apparmor.d/bin.foobar'),
|
||||
('@{foo}', None), # XXX variables not supported yet (and @{foo} isn't defined in this test)
|
||||
('/bin/404', None),
|
||||
]
|
||||
|
||||
def AASetup(self):
|
||||
self.pl = ProfileList()
|
||||
self.pl.add('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo')
|
||||
self.pl.add('/etc/apparmor.d/bin.baz', 'baz', '/bin/ba*')
|
||||
self.pl.add('/etc/apparmor.d/bin.foobar', 'foobar', '/bin/foo{bar,baz}')
|
||||
|
||||
def _run_test(self, params, expected):
|
||||
self.assertEqual(self.pl.filename_from_attachment(params), expected)
|
||||
|
||||
def test_non_path_attachment(self):
|
||||
with self.assertRaises(AppArmorBug):
|
||||
self.pl.filename_from_attachment('foo')
|
||||
|
||||
|
||||
setup_all_loops(__name__)
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=1)
|
Reference in New Issue
Block a user