From 60c42713fe5943fd2b2b3201d1067d5ecb8d53f9 Mon Sep 17 00:00:00 2001 From: Apertis CI <devel@lists.apertis.org> Date: Wed, 26 Feb 2025 11:23:56 +0000 Subject: [PATCH 1/4] Import Upstream version 3.1.7 --- .gitignore | 21 + .gitlab-ci.yml | 142 +- .shellcheckrc | 10 + binutils/aa-enabled.1 | 2 +- binutils/aa-exec.1 | 2 +- binutils/aa-features-abi.1 | 2 +- binutils/aa-status.8 | 2 +- binutils/aa_status.c | 9 +- binutils/po/aa-enabled.pot | 66 + binutils/po/aa_enabled.pot | 4 +- binutils/po/aa_exec.pot | 2 +- binutils/po/aa_features_abi.pot | 2 +- changehat/mod_apparmor/mod_apparmor.8 | 2 +- changehat/mod_apparmor/mod_apparmor.c | 2 +- .../tomcat_5_0/README.tomcat_apparmor | 12 +- .../tomcat_5_5/README.tomcat_apparmor | 12 +- common/.stamp_rev | 2 +- common/Version | 2 +- common/list_af_names.sh | 2 +- libraries/libapparmor/autom4te.cache/output.0 | 2 +- libraries/libapparmor/autom4te.cache/output.1 | 2 +- libraries/libapparmor/autom4te.cache/output.2 | 2 +- libraries/libapparmor/autom4te.cache/requests | 488 +++--- libraries/libapparmor/autom4te.cache/traces.0 | 2 +- libraries/libapparmor/autom4te.cache/traces.1 | 4 +- libraries/libapparmor/autom4te.cache/traces.2 | 4 +- libraries/libapparmor/configure | 2 +- libraries/libapparmor/doc/aa_getcon.pod | 8 + libraries/libapparmor/doc/aa_policy_cache.pod | 2 +- .../libapparmor/doc/aa_stack_profile.pod | 5 +- libraries/libapparmor/include/aalogparse.h | 2 + libraries/libapparmor/src/Makefile.am | 16 +- libraries/libapparmor/src/Makefile.in | 15 +- libraries/libapparmor/src/features.c | 4 +- libraries/libapparmor/src/grammar.y | 7 +- libraries/libapparmor/src/kernel.c | 4 +- libraries/libapparmor/src/kernel_interface.c | 4 +- libraries/libapparmor/src/libaalogparse.c | 2 + libraries/libapparmor/src/private.c | 4 +- libraries/libapparmor/src/scanner.l | 8 + libraries/libapparmor/swig/python/__init__.py | 7 +- .../libapparmor/swig/python/test/buildpath.py | 1 + .../swig/python/test/test_python.py.in | 15 +- .../libaalogparse.test/multi_test.exp | 4 +- libraries/libapparmor/testsuite/test_multi.c | 2 + .../testsuite/test_multi/file_xm.err | 0 .../testsuite/test_multi/file_xm.in | 1 + .../testsuite/test_multi/file_xm.out | 16 + .../testsuite/test_multi/file_xm.profile | 4 + .../testsuite/test_multi/testcase_dbus_11.err | 0 .../testsuite/test_multi/testcase_dbus_11.in | 1 + .../testsuite/test_multi/testcase_dbus_11.out | 15 + .../test_multi/testcase_dbus_11.profile | 4 + parser/Makefile | 11 +- parser/aa-teardown.8 | 2 +- parser/af_rule.cc | 2 +- parser/af_unix.cc | 18 +- parser/af_unix.h | 3 - parser/apparmor.7 | 91 +- parser/apparmor.7.html | 58 +- parser/apparmor.d.5 | 22 +- parser/apparmor.d.5.html | 20 +- parser/apparmor.d.pod | 19 +- parser/apparmor.pod | 76 + parser/apparmor_parser.8 | 17 +- parser/apparmor_parser.8.html | 14 +- parser/apparmor_parser.pod | 21 +- parser/apparmor_xattrs.7 | 2 +- parser/capability.h | 4 + parser/lib.c | 2 +- parser/libapparmor_re/README | 6 +- parser/libapparmor_re/aare_rules.cc | 26 +- parser/libapparmor_re/chfa.cc | 14 +- parser/libapparmor_re/expr-tree.cc | 71 +- parser/libapparmor_re/expr-tree.h | 162 +- parser/libapparmor_re/hfa.cc | 21 +- parser/libapparmor_re/hfa.h | 2 +- parser/libapparmor_re/parse.y | 2 +- parser/mount.cc | 715 +++++--- parser/mount.h | 34 +- parser/parser.conf | 4 +- parser/parser.h | 19 +- parser/parser_include.c | 5 +- parser/parser_interface.c | 12 +- parser/parser_lex.l | 6 +- parser/parser_main.c | 163 +- parser/parser_regex.c | 13 +- parser/parser_symtab.c | 4 +- parser/parser_yacc.y | 2 +- parser/po/apparmor-parser.pot | 2 +- parser/policy_cache.c | 2 +- parser/policy_cache.h | 1 - parser/profile-load | 8 +- parser/rc.apparmor.debian | 117 -- parser/rc.apparmor.functions | 126 +- parser/rc.apparmor.redhat | 125 -- parser/signal.cc | 2 +- parser/techdoc.log | 8 +- parser/techdoc.pdf | Bin 248501 -> 248503 bytes parser/techdoc.tex | 4 +- parser/tst/Makefile | 4 +- parser/tst/README | 4 +- parser/tst/caching.py | 191 +- parser/tst/equality.sh | 12 +- parser/tst/errors.py | 35 +- parser/tst/gen-dbus.pl | 167 -- parser/tst/gen-dbus.py | 161 ++ parser/tst/gen-xtrans.pl | 235 --- parser/tst/gen-xtrans.py | 228 +++ parser/tst/minimize.sh | 12 +- parser/tst/mk_features_file.py | 9 +- parser/tst/simple_tests/abi/bad_13.sd | 8 + parser/tst/simple_tests/abi/bad_14.sd | 8 + parser/tst/simple_tests/abi/bad_15.sd | 8 + parser/tst/simple_tests/abi/bad_16.sd | 8 + parser/tst/simple_tests/abi/bad_17.sd | 8 + parser/tst/simple_tests/abi/bad_18.sd | 8 + parser/tst/simple_tests/abi/bad_19.sd | 8 + parser/tst/simple_tests/abi/bad_20.sd | 8 + parser/tst/simple_tests/capability/ok1.sd | 2 +- parser/tst/simple_tests/capability/ok2.sd | 2 +- parser/tst/simple_tests/capability/ok3.sd | 2 +- parser/tst/simple_tests/capability/set/ok1.sd | 2 +- .../tst/simple_tests/conditional/else_if_4.sd | 2 +- .../tst/simple_tests/file/allow/ok_mmap_2.sd | 2 +- .../tst/simple_tests/file/file/ok_mmap_2.sd | 2 +- parser/tst/simple_tests/file/ok_mmap_2.sd | 2 +- parser/tst/simple_tests/mount/bad_1.sd | 7 + parser/tst/simple_tests/mount/bad_2.sd | 7 + parser/tst/simple_tests/mount/bad_3.sd | 7 + .../simple_tests/mount/{ok_19.sd => bad_4.sd} | 4 +- parser/tst/simple_tests/mount/bad_opt_29.sd | 7 + parser/tst/simple_tests/mount/bad_opt_30.sd | 7 + parser/tst/simple_tests/mount/bad_opt_31.sd | 7 + parser/tst/simple_tests/mount/bad_opt_32.sd | 6 + parser/tst/simple_tests/mount/bad_opt_35.sd | 6 + parser/tst/simple_tests/mount/bad_opt_36.sd | 6 + parser/tst/simple_tests/mount/bad_opt_37.sd | 6 + parser/tst/simple_tests/mount/bad_opt_38.sd | 6 + parser/tst/simple_tests/mount/bad_opt_39.sd | 6 + parser/tst/simple_tests/mount/bad_opt_40.sd | 6 + parser/tst/simple_tests/mount/bad_opt_41.sd | 6 + parser/tst/simple_tests/mount/ok_16.sd | 2 +- parser/tst/simple_tests/mount/ok_17.sd | 7 - parser/tst/simple_tests/mount/ok_18.sd | 7 - parser/tst/simple_tests/mount/ok_opt_56.sd | 8 + parser/tst/simple_tests/mount/ok_opt_57.sd | 8 + parser/tst/simple_tests/mount/ok_opt_58.sd | 8 + parser/tst/simple_tests/mount/ok_opt_59.sd | 7 + parser/tst/simple_tests/mount/ok_opt_60.sd | 7 + parser/tst/simple_tests/mount/ok_opt_61.sd | 7 + parser/tst/simple_tests/mount/ok_opt_62.sd | 7 + parser/tst/simple_tests/mount/ok_opt_63.sd | 7 + parser/tst/simple_tests/mount/ok_opt_64.sd | 7 + parser/tst/simple_tests/mount/ok_opt_65.sd | 8 + parser/tst/simple_tests/mount/ok_opt_66.sd | 7 + parser/tst/simple_tests/mount/ok_opt_67.sd | 7 + parser/tst/simple_tests/mount/ok_opt_68.sd | 10 + parser/tst/simple_tests/mount/ok_opt_69.sd | 10 + parser/tst/simple_tests/mount/ok_opt_70.sd | 10 + parser/tst/simple_tests/mount/ok_opt_71.sd | 10 + parser/tst/simple_tests/mount/ok_opt_72.sd | 10 + parser/tst/simple_tests/mount/ok_opt_73.sd | 10 + parser/tst/simple_tests/mount/ok_opt_74.sd | 10 + parser/tst/simple_tests/mount/ok_opt_75.sd | 10 + parser/tst/simple_tests/mount/ok_opt_76.sd | 10 + parser/tst/simple_tests/mount/ok_opt_77.sd | 10 + parser/tst/simple_tests/mount/ok_opt_78.sd | 10 + parser/tst/simple_tests/mount/ok_opt_79.sd | 10 + parser/tst/simple_tests/mount/ok_opt_80.sd | 10 + parser/tst/simple_tests/mount/ok_opt_81.sd | 10 + parser/tst/simple_tests/mount/ok_opt_82.sd | 10 + parser/tst/simple_tests/mount/ok_opt_83.sd | 10 + parser/tst/simple_tests/mount/ok_opt_84.sd | 8 + .../simple_tests/network/tcp_client_error2.sd | 2 +- .../profile/profile_ns_named_ok1.sd | 2 +- .../tst/simple_tests/rlimits/ok_rlimit_10.sd | 2 +- parser/tst/testlib.py | 29 +- parser/tst/valgrind_simple.py | 29 +- profiles/Makefile | 16 +- profiles/apparmor.d/abstractions/audio | 3 + .../apparmor.d/abstractions/authentication | 5 + profiles/apparmor.d/abstractions/base | 11 +- profiles/apparmor.d/abstractions/crypto | 1 + profiles/apparmor.d/abstractions/exo-open | 4 +- profiles/apparmor.d/abstractions/fonts | 2 +- .../apparmor.d/abstractions/freedesktop.org | 2 +- profiles/apparmor.d/abstractions/groff | 67 + profiles/apparmor.d/abstractions/kde | 3 + profiles/apparmor.d/abstractions/kde-open5 | 4 +- .../apparmor.d/abstractions/kerberosclient | 5 + profiles/apparmor.d/abstractions/nameservice | 3 + profiles/apparmor.d/abstractions/nvidia | 4 + profiles/apparmor.d/abstractions/openssl | 3 +- profiles/apparmor.d/abstractions/samba | 3 +- .../apparmor.d/abstractions/snap_browsers | 1 + profiles/apparmor.d/abstractions/ssl_certs | 6 +- .../apparmor.d/abstractions/svn-repositories | 2 +- profiles/apparmor.d/abstractions/trash | 75 + .../abstractions/ubuntu-browsers.d/kde | 3 + .../apparmor.d/abstractions/ubuntu-helpers | 2 + profiles/apparmor.d/abstractions/video | 9 + profiles/apparmor.d/abstractions/wayland | 3 + profiles/apparmor.d/abstractions/wutmp | 3 + profiles/apparmor.d/abstractions/xdg-open | 2 +- profiles/apparmor.d/lsb_release | 2 + profiles/apparmor.d/nvidia_modprobe | 4 +- profiles/apparmor.d/samba-bgqd | 2 +- profiles/apparmor.d/samba-dcerpcd | 2 +- profiles/apparmor.d/samba-rpcd-spoolss | 2 +- profiles/apparmor.d/sbin.syslogd | 1 + profiles/apparmor.d/tunables/etc | 6 +- profiles/apparmor.d/tunables/home | 10 +- profiles/apparmor.d/usr.lib.dovecot.anvil | 4 +- profiles/apparmor.d/usr.lib.dovecot.auth | 4 +- profiles/apparmor.d/usr.lib.dovecot.config | 6 +- profiles/apparmor.d/usr.lib.dovecot.deliver | 4 +- profiles/apparmor.d/usr.lib.dovecot.dict | 4 +- profiles/apparmor.d/usr.lib.dovecot.director | 27 + .../apparmor.d/usr.lib.dovecot.doveadm-server | 22 + .../apparmor.d/usr.lib.dovecot.dovecot-auth | 4 +- .../apparmor.d/usr.lib.dovecot.dovecot-lda | 4 +- profiles/apparmor.d/usr.lib.dovecot.imap | 5 +- .../apparmor.d/usr.lib.dovecot.imap-login | 4 +- profiles/apparmor.d/usr.lib.dovecot.lmtp | 4 +- profiles/apparmor.d/usr.lib.dovecot.log | 4 +- .../apparmor.d/usr.lib.dovecot.managesieve | 4 +- .../usr.lib.dovecot.managesieve-login | 4 +- profiles/apparmor.d/usr.lib.dovecot.pop3 | 4 +- .../apparmor.d/usr.lib.dovecot.pop3-login | 4 +- .../apparmor.d/usr.lib.dovecot.replicator | 36 + .../apparmor.d/usr.lib.dovecot.script-login | 4 +- .../apparmor.d/usr.lib.dovecot.ssl-params | 4 +- profiles/apparmor.d/usr.lib.dovecot.stats | 4 +- profiles/apparmor.d/usr.sbin.avahi-daemon | 2 +- profiles/apparmor.d/usr.sbin.dnsmasq | 3 + profiles/apparmor.d/usr.sbin.dovecot | 41 +- profiles/apparmor.d/usr.sbin.nscd | 7 + profiles/apparmor.d/usr.sbin.smbd | 4 +- profiles/apparmor.d/usr.sbin.winbindd | 2 + profiles/apparmor.d/zgrep | 66 + profiles/apparmor/profiles/extras/bin.netstat | 3 + ....bin.chromium-browser => chromium_browser} | 0 .../profiles/extras/etc.cron.daily.logrotate | 3 + .../extras/etc.cron.daily.slocate.cron | 3 + .../profiles/extras/etc.cron.daily.tmpwatch | 3 + .../{usr.lib.firefox.firefox => firefox} | 5 +- ...{usr.lib.firefox.firefox.sh => firefox.sh} | 4 +- .../apparmor/profiles/extras/postfix-anvil | 3 + .../apparmor/profiles/extras/postfix-bounce | 3 + .../apparmor/profiles/extras/postfix-cleanup | 3 + .../apparmor/profiles/extras/postfix-discard | 3 + .../apparmor/profiles/extras/postfix-dnsblog | 3 + .../apparmor/profiles/extras/postfix-error | 2 + .../apparmor/profiles/extras/postfix-flush | 2 + .../apparmor/profiles/extras/postfix-lmtp | 2 + .../apparmor/profiles/extras/postfix-local | 3 + .../apparmor/profiles/extras/postfix-master | 3 + .../apparmor/profiles/extras/postfix-nqmgr | 3 + .../apparmor/profiles/extras/postfix-oqmgr | 3 + .../apparmor/profiles/extras/postfix-pickup | 3 + .../apparmor/profiles/extras/postfix-pipe | 2 + .../profiles/extras/postfix-postscreen | 3 + .../apparmor/profiles/extras/postfix-proxymap | 3 + .../apparmor/profiles/extras/postfix-qmgr | 3 + .../apparmor/profiles/extras/postfix-qmqpd | 3 + .../apparmor/profiles/extras/postfix-scache | 3 + .../apparmor/profiles/extras/postfix-showq | 3 + .../apparmor/profiles/extras/postfix-smtp | 3 + .../apparmor/profiles/extras/postfix-smtpd | 3 + .../apparmor/profiles/extras/postfix-spawn | 3 + .../apparmor/profiles/extras/postfix-tlsmgr | 4 + .../profiles/extras/postfix-trivial-rewrite | 3 + .../apparmor/profiles/extras/postfix-verify | 3 + .../apparmor/profiles/extras/postfix-virtual | 3 + .../apparmor/profiles/extras/sbin.dhclient | 1 + .../profiles/extras/sbin.dhclient-script | 1 + profiles/apparmor/profiles/extras/sbin.dhcpcd | 3 + .../apparmor/profiles/extras/sbin.portmap | 3 + .../apparmor/profiles/extras/sbin.resmgrd | 3 + .../apparmor/profiles/extras/sbin.rpc.lockd | 3 + .../apparmor/profiles/extras/sbin.rpc.statd | 3 + .../profiles/extras/usr.NX.bin.nxclient | 3 + .../apparmor/profiles/extras/usr.bin.acroread | 3 + .../apparmor/profiles/extras/usr.bin.apropos | 3 + .../apparmor/profiles/extras/usr.bin.dumpcap | 3 + .../profiles/extras/usr.bin.evolution-2.10 | 5 +- profiles/apparmor/profiles/extras/usr.bin.fam | 3 + .../profiles/extras/usr.bin.freshclam | 3 + .../apparmor/profiles/extras/usr.bin.gaim | 3 + profiles/apparmor/profiles/extras/usr.bin.man | 2 + .../profiles/extras/usr.bin.mlmmj-bounce | 3 + .../profiles/extras/usr.bin.mlmmj-maintd | 2 + .../profiles/extras/usr.bin.mlmmj-make-ml.sh | 5 +- .../profiles/extras/usr.bin.mlmmj-process | 2 + .../profiles/extras/usr.bin.mlmmj-receive | 3 + .../profiles/extras/usr.bin.mlmmj-recieve | 3 + .../profiles/extras/usr.bin.mlmmj-send | 2 + .../profiles/extras/usr.bin.mlmmj-sub | 2 + .../profiles/extras/usr.bin.mlmmj-unsub | 2 + .../apparmor/profiles/extras/usr.bin.opera | 3 + .../apparmor/profiles/extras/usr.bin.passwd | 3 + .../apparmor/profiles/extras/usr.bin.procmail | 3 + .../profiles/extras/usr.bin.pyzorsocket | 23 + .../profiles/extras/usr.bin.razorsocket | 21 + .../apparmor/profiles/extras/usr.bin.skype | 3 + .../apparmor/profiles/extras/usr.bin.spamc | 3 + .../apparmor/profiles/extras/usr.bin.svnserve | 3 + .../profiles/extras/usr.bin.wireshark | 3 + profiles/apparmor/profiles/extras/usr.bin.xfs | 3 + .../profiles/extras/usr.lib.GConf.2.gconfd-2 | 3 + .../extras/usr.lib.RealPlayer10.realplay | 3 + .../usr.lib.bonobo.bonobo-activation-server | 3 + ...ion-data-server.evolution-data-server-1.10 | 2 + .../usr.lib.firefox.mozilla-xremote-client | 3 + .../profiles/extras/usr.lib.man-db.man | 3 + .../extras/usr.lib64.GConf.2.gconfd-2 | 3 + .../apparmor/profiles/extras/usr.sbin.clamd | 30 + .../apparmor/profiles/extras/usr.sbin.cupsd | 3 + .../apparmor/profiles/extras/usr.sbin.dhcpd | 3 + .../apparmor/profiles/extras/usr.sbin.haproxy | 45 + .../profiles/extras/usr.sbin.httpd2-prefork | 7 +- .../apparmor/profiles/extras/usr.sbin.imapd | 3 + .../profiles/extras/usr.sbin.in.fingerd | 3 + .../apparmor/profiles/extras/usr.sbin.in.ftpd | 3 + .../profiles/extras/usr.sbin.in.ntalkd | 3 + .../apparmor/profiles/extras/usr.sbin.ipop2d | 3 + .../apparmor/profiles/extras/usr.sbin.ipop3d | 3 + .../profiles/extras/usr.sbin.lighttpd | 4 +- .../apparmor/profiles/extras/usr.sbin.mysqld | 2 + .../apparmor/profiles/extras/usr.sbin.oidentd | 3 + .../apparmor/profiles/extras/usr.sbin.popper | 3 + .../profiles/extras/usr.sbin.postalias | 3 + .../profiles/extras/usr.sbin.postdrop | 3 + .../apparmor/profiles/extras/usr.sbin.postmap | 3 + .../profiles/extras/usr.sbin.postqueue | 3 + .../profiles/extras/usr.sbin.sendmail | 3 + .../profiles/extras/usr.sbin.sendmail.postfix | 3 + .../extras/usr.sbin.sendmail.sendmail | 3 + .../apparmor/profiles/extras/usr.sbin.spamd | 3 + .../apparmor/profiles/extras/usr.sbin.squid | 2 + .../apparmor/profiles/extras/usr.sbin.useradd | 3 + .../apparmor/profiles/extras/usr.sbin.userdel | 3 + .../apparmor/profiles/extras/usr.sbin.vsftpd | 3 + .../apparmor/profiles/extras/usr.sbin.xinetd | 3 + tests/bin/shellcheck-tree | 38 + tests/checkstyle2junit.xslt | 44 + tests/regression/apparmor/Makefile | 20 +- tests/regression/apparmor/README | 6 +- tests/regression/apparmor/aa_exec_wrapper.sh | 2 +- tests/regression/apparmor/aa_policy_cache.sh | 2 +- tests/regression/apparmor/at_secure.sh | 2 +- .../regression/apparmor/attach_disconnected.c | 180 ++ .../apparmor/attach_disconnected.sh | 118 ++ tests/regression/apparmor/capabilities.sh | 76 +- tests/regression/apparmor/changehat_fork.sh | 2 +- tests/regression/apparmor/changeprofile.sh | 2 +- tests/regression/apparmor/clone.sh | 2 +- tests/regression/apparmor/coredump.sh | 2 +- tests/regression/apparmor/dbus.inc | 112 +- tests/regression/apparmor/dbus_eavesdrop.sh | 78 +- tests/regression/apparmor/dbus_message.sh | 193 ++- tests/regression/apparmor/dbus_service.c | 2 +- tests/regression/apparmor/dbus_service.sh | 119 +- .../apparmor/dbus_unrequested_reply.sh | 136 +- tests/regression/apparmor/deleted.c | 2 +- tests/regression/apparmor/deleted.sh | 10 +- tests/regression/apparmor/exec.c | 2 +- tests/regression/apparmor/exec_qual.sh | 6 +- tests/regression/apparmor/exec_stack.sh | 26 +- tests/regression/apparmor/link_subset.c | 2 +- tests/regression/apparmor/mkprofile.pl | 189 +- tests/regression/apparmor/mount.c | 160 +- tests/regression/apparmor/mount.sh | 256 ++- tests/regression/apparmor/named_pipe.sh | 4 +- tests/regression/apparmor/namespaces.sh | 12 +- tests/regression/apparmor/onexec.sh | 2 +- tests/regression/apparmor/pivot_root.sh | 7 +- tests/regression/apparmor/prologue.inc | 19 +- tests/regression/apparmor/ptrace.sh | 2 +- tests/regression/apparmor/query_label.c | 2 +- tests/regression/apparmor/query_label.sh | 14 +- tests/regression/apparmor/socketpair.c | 2 +- tests/regression/apparmor/socketpair.sh | 2 +- tests/regression/apparmor/swap.sh | 4 +- tests/regression/apparmor/syscall.sh | 10 +- tests/regression/apparmor/syscall_sysctl.sh | 2 +- tests/regression/apparmor/tcp.sh | 8 +- tests/regression/apparmor/unix_fd_client.c | 69 +- tests/regression/apparmor/unix_fd_common.c | 85 + tests/regression/apparmor/unix_fd_common.h | 17 + tests/regression/apparmor/unix_fd_server.c | 15 +- tests/regression/apparmor/unix_fd_server.sh | 34 +- tests/regression/apparmor/unix_socket.inc | 2 +- .../apparmor/unix_socket_pathname.sh | 17 +- tests/regression/apparmor/xattrs.sh | 6 +- utils/aa-audit | 11 +- utils/aa-audit.8 | 2 +- utils/aa-autodep | 10 +- utils/aa-autodep.8 | 2 +- utils/aa-cleanprof | 10 +- utils/aa-cleanprof.8 | 2 +- utils/aa-complain | 12 +- utils/aa-complain.8 | 2 +- utils/aa-decode | 12 +- utils/aa-decode.8 | 2 +- utils/aa-disable | 11 +- utils/aa-disable.8 | 2 +- utils/aa-easyprof | 17 +- utils/aa-easyprof.8 | 2 +- utils/aa-enforce | 10 +- utils/aa-enforce.8 | 2 +- utils/aa-genprof | 66 +- utils/aa-genprof.8 | 2 +- utils/aa-logprof | 16 +- utils/aa-logprof.8 | 4 +- utils/aa-logprof.8.html | 2 +- utils/aa-logprof.pod | 2 +- utils/aa-mergeprof | 35 +- utils/aa-mergeprof.8 | 2 +- utils/aa-notify | 83 +- utils/aa-notify.8 | 2 +- utils/aa-remove-unknown | 17 +- utils/aa-remove-unknown.8 | 2 +- utils/aa-sandbox | 8 +- utils/aa-unconfined | 44 +- utils/aa-unconfined.8 | 2 +- utils/apparmor/aa.py | 1544 +++++++++-------- utils/apparmor/aare.py | 87 +- utils/apparmor/cleanprofile.py | 20 +- utils/apparmor/common.py | 140 +- utils/apparmor/config.py | 82 +- utils/apparmor/easyprof.py | 389 ++--- utils/apparmor/fail.py | 32 +- utils/apparmor/logparser.py | 173 +- utils/apparmor/notify.py | 18 +- utils/apparmor/profile_list.py | 158 +- utils/apparmor/profile_storage.py | 214 ++- utils/apparmor/regex.py | 216 ++- utils/apparmor/rule/__init__.py | 193 ++- utils/apparmor/rule/abi.py | 28 +- utils/apparmor/rule/alias.py | 31 +- utils/apparmor/rule/boolean.py | 133 ++ utils/apparmor/rule/capability.py | 37 +- utils/apparmor/rule/change_profile.py | 71 +- utils/apparmor/rule/dbus.py | 123 +- utils/apparmor/rule/file.py | 141 +- utils/apparmor/rule/include.py | 48 +- utils/apparmor/rule/network.py | 65 +- utils/apparmor/rule/ptrace.py | 66 +- utils/apparmor/rule/rlimit.py | 75 +- utils/apparmor/rule/signal.py | 118 +- utils/apparmor/rule/variable.py | 61 +- utils/apparmor/rules.py | 6 +- utils/apparmor/sandbox.py | 264 ++- utils/apparmor/severity.py | 35 +- utils/apparmor/tools.py | 94 +- utils/apparmor/translations.py | 3 +- utils/apparmor/ui.py | 70 +- utils/logprof.conf | 2 +- utils/logprof.conf.5 | 2 +- utils/python-tools-setup.py | 47 +- utils/test/Makefile | 15 +- utils/test/README.md | 9 + utils/test/cleanprof_test.in | 15 +- utils/test/cleanprof_test.out | 15 + utils/test/common_test.py | 46 +- utils/test/logprof.conf | 6 +- utils/test/minitools_test.py | 162 -- utils/test/runtests-py2.sh | 5 - utils/test/severity.db | 463 ----- utils/test/test-aa-cli-bootstrap.py | 17 +- utils/test/test-aa-decode.py | 112 +- utils/test/test-aa-easyprof.py | 903 +++++----- utils/test/test-aa-notify.py | 61 +- utils/test/test-aa.py | 552 +++--- utils/test/test-aare.py | 558 +++--- utils/test/test-abi.py | 276 +-- utils/test/test-alias.py | 155 +- utils/test/test-baserule.py | 10 +- utils/test/test-boolean.py | 327 ++++ utils/test/test-capability.py | 234 +-- utils/test/test-change_profile.py | 336 ++-- utils/test/test-common.py | 43 +- utils/test/test-config.py | 22 +- utils/test/test-dbus.py | 833 ++++----- utils/test/test-example.py | 17 +- utils/test/test-file.py | 1032 +++++------ utils/test/test-include.py | 400 +++-- utils/test/test-libapparmor-test_multi.py | 108 +- utils/test/test-logparser.py | 17 +- utils/test/test-minitools.py | 217 +++ utils/test/test-mount_parse.py | 22 +- utils/test/test-network.py | 297 ++-- utils/test/test-notify.py | 36 +- utils/test/test-parser-simple-tests.py | 56 +- utils/test/test-pivot_root_parse.py | 17 +- utils/test/test-profile-list.py | 199 ++- utils/test/test-profile-storage.py | 216 ++- utils/test/test-profiles.py | 46 + utils/test/test-ptrace.py | 481 ++--- utils/test/test-regex_matches.py | 674 +++---- utils/test/test-rlimit.py | 386 +++-- utils/test/test-severity.py | 99 +- utils/test/test-signal.py | 570 +++--- utils/test/test-translations.py | 42 +- utils/test/test-unix_parse.py | 12 +- utils/test/test-variable.py | 263 +-- utils/vim/apparmor.vim.in | 4 +- utils/vim/create-apparmor.vim.py | 28 +- 510 files changed, 13467 insertions(+), 9802 deletions(-) create mode 100644 .shellcheckrc create mode 100644 binutils/po/aa-enabled.pot create mode 100644 libraries/libapparmor/testsuite/test_multi/file_xm.err create mode 100644 libraries/libapparmor/testsuite/test_multi/file_xm.in create mode 100644 libraries/libapparmor/testsuite/test_multi/file_xm.out create mode 100644 libraries/libapparmor/testsuite/test_multi/file_xm.profile create mode 100644 libraries/libapparmor/testsuite/test_multi/testcase_dbus_11.err create mode 100644 libraries/libapparmor/testsuite/test_multi/testcase_dbus_11.in create mode 100644 libraries/libapparmor/testsuite/test_multi/testcase_dbus_11.out create mode 100644 libraries/libapparmor/testsuite/test_multi/testcase_dbus_11.profile delete mode 100644 parser/rc.apparmor.debian delete mode 100644 parser/rc.apparmor.redhat delete mode 100755 parser/tst/gen-dbus.pl create mode 100755 parser/tst/gen-dbus.py delete mode 100755 parser/tst/gen-xtrans.pl create mode 100755 parser/tst/gen-xtrans.py create mode 100644 parser/tst/simple_tests/abi/bad_13.sd create mode 100644 parser/tst/simple_tests/abi/bad_14.sd create mode 100644 parser/tst/simple_tests/abi/bad_15.sd create mode 100644 parser/tst/simple_tests/abi/bad_16.sd create mode 100644 parser/tst/simple_tests/abi/bad_17.sd create mode 100644 parser/tst/simple_tests/abi/bad_18.sd create mode 100644 parser/tst/simple_tests/abi/bad_19.sd create mode 100644 parser/tst/simple_tests/abi/bad_20.sd create mode 100644 parser/tst/simple_tests/mount/bad_1.sd create mode 100644 parser/tst/simple_tests/mount/bad_2.sd create mode 100644 parser/tst/simple_tests/mount/bad_3.sd rename parser/tst/simple_tests/mount/{ok_19.sd => bad_4.sd} (50%) create mode 100644 parser/tst/simple_tests/mount/bad_opt_29.sd create mode 100644 parser/tst/simple_tests/mount/bad_opt_30.sd create mode 100644 parser/tst/simple_tests/mount/bad_opt_31.sd create mode 100644 parser/tst/simple_tests/mount/bad_opt_32.sd create mode 100644 parser/tst/simple_tests/mount/bad_opt_35.sd create mode 100644 parser/tst/simple_tests/mount/bad_opt_36.sd create mode 100644 parser/tst/simple_tests/mount/bad_opt_37.sd create mode 100644 parser/tst/simple_tests/mount/bad_opt_38.sd create mode 100644 parser/tst/simple_tests/mount/bad_opt_39.sd create mode 100644 parser/tst/simple_tests/mount/bad_opt_40.sd create mode 100644 parser/tst/simple_tests/mount/bad_opt_41.sd delete mode 100644 parser/tst/simple_tests/mount/ok_17.sd delete mode 100644 parser/tst/simple_tests/mount/ok_18.sd create mode 100644 parser/tst/simple_tests/mount/ok_opt_56.sd create mode 100644 parser/tst/simple_tests/mount/ok_opt_57.sd create mode 100644 parser/tst/simple_tests/mount/ok_opt_58.sd create mode 100644 parser/tst/simple_tests/mount/ok_opt_59.sd create mode 100644 parser/tst/simple_tests/mount/ok_opt_60.sd create mode 100644 parser/tst/simple_tests/mount/ok_opt_61.sd create mode 100644 parser/tst/simple_tests/mount/ok_opt_62.sd create mode 100644 parser/tst/simple_tests/mount/ok_opt_63.sd create mode 100644 parser/tst/simple_tests/mount/ok_opt_64.sd create mode 100644 parser/tst/simple_tests/mount/ok_opt_65.sd create mode 100644 parser/tst/simple_tests/mount/ok_opt_66.sd create mode 100644 parser/tst/simple_tests/mount/ok_opt_67.sd create mode 100644 parser/tst/simple_tests/mount/ok_opt_68.sd create mode 100644 parser/tst/simple_tests/mount/ok_opt_69.sd create mode 100644 parser/tst/simple_tests/mount/ok_opt_70.sd create mode 100644 parser/tst/simple_tests/mount/ok_opt_71.sd create mode 100644 parser/tst/simple_tests/mount/ok_opt_72.sd create mode 100644 parser/tst/simple_tests/mount/ok_opt_73.sd create mode 100644 parser/tst/simple_tests/mount/ok_opt_74.sd create mode 100644 parser/tst/simple_tests/mount/ok_opt_75.sd create mode 100644 parser/tst/simple_tests/mount/ok_opt_76.sd create mode 100644 parser/tst/simple_tests/mount/ok_opt_77.sd create mode 100644 parser/tst/simple_tests/mount/ok_opt_78.sd create mode 100644 parser/tst/simple_tests/mount/ok_opt_79.sd create mode 100644 parser/tst/simple_tests/mount/ok_opt_80.sd create mode 100644 parser/tst/simple_tests/mount/ok_opt_81.sd create mode 100644 parser/tst/simple_tests/mount/ok_opt_82.sd create mode 100644 parser/tst/simple_tests/mount/ok_opt_83.sd create mode 100644 parser/tst/simple_tests/mount/ok_opt_84.sd create mode 100644 profiles/apparmor.d/abstractions/groff create mode 100644 profiles/apparmor.d/abstractions/trash create mode 100644 profiles/apparmor.d/usr.lib.dovecot.director create mode 100644 profiles/apparmor.d/usr.lib.dovecot.doveadm-server create mode 100644 profiles/apparmor.d/usr.lib.dovecot.replicator create mode 100644 profiles/apparmor.d/zgrep rename profiles/apparmor/profiles/extras/{usr.bin.chromium-browser => chromium_browser} (100%) rename profiles/apparmor/profiles/extras/{usr.lib.firefox.firefox => firefox} (96%) rename profiles/apparmor/profiles/extras/{usr.lib.firefox.firefox.sh => firefox.sh} (70%) create mode 100644 profiles/apparmor/profiles/extras/usr.bin.pyzorsocket create mode 100644 profiles/apparmor/profiles/extras/usr.bin.razorsocket create mode 100644 profiles/apparmor/profiles/extras/usr.sbin.clamd create mode 100644 profiles/apparmor/profiles/extras/usr.sbin.haproxy create mode 100755 tests/bin/shellcheck-tree create mode 100644 tests/checkstyle2junit.xslt create mode 100644 tests/regression/apparmor/attach_disconnected.c create mode 100644 tests/regression/apparmor/attach_disconnected.sh mode change 100644 => 100755 tests/regression/apparmor/capabilities.sh create mode 100644 tests/regression/apparmor/unix_fd_common.c create mode 100644 tests/regression/apparmor/unix_fd_common.h mode change 100644 => 100755 utils/aa-remove-unknown create mode 100644 utils/apparmor/rule/boolean.py delete mode 100755 utils/test/minitools_test.py delete mode 100755 utils/test/runtests-py2.sh delete mode 100644 utils/test/severity.db create mode 100644 utils/test/test-boolean.py create mode 100755 utils/test/test-minitools.py create mode 100644 utils/test/test-profiles.py diff --git a/.gitignore b/.gitignore index bac706f2b..b294eef29 100644 --- a/.gitignore +++ b/.gitignore @@ -216,7 +216,11 @@ utils/vim/apparmor.vim utils/vim/apparmor.vim.5 utils/vim/apparmor.vim.5.html utils/vim/pod2htmd.tmp +tests/regression/apparmor/*.o +tests/regression/apparmor/aa_policy_cache tests/regression/apparmor/access +tests/regression/apparmor/at_secure +tests/regression/apparmor/attach_disconnected tests/regression/apparmor/changehat tests/regression/apparmor/changehat_fail tests/regression/apparmor/changehat_fork @@ -231,6 +235,10 @@ tests/regression/apparmor/chgrp tests/regression/apparmor/chmod tests/regression/apparmor/chown tests/regression/apparmor/clone +tests/regression/apparmor/dbus_eavesdrop +tests/regression/apparmor/dbus_message +tests/regression/apparmor/dbus_service +tests/regression/apparmor/dbus_unrequested_reply tests/regression/apparmor/deleted tests/regression/apparmor/env_check tests/regression/apparmor/environ @@ -241,7 +249,10 @@ tests/regression/apparmor/fchdir tests/regression/apparmor/fchgrp tests/regression/apparmor/fchmod tests/regression/apparmor/fchown +tests/regression/apparmor/fd_inheritance +tests/regression/apparmor/fd_inheritor tests/regression/apparmor/fork +tests/regression/apparmor/introspect tests/regression/apparmor/link tests/regression/apparmor/link_subset tests/regression/apparmor/mkdir @@ -252,15 +263,20 @@ tests/regression/apparmor/net_raw tests/regression/apparmor/open tests/regression/apparmor/openat tests/regression/apparmor/pipe +tests/regression/apparmor/pivot_root tests/regression/apparmor/ptrace tests/regression/apparmor/ptrace_helper tests/regression/apparmor/pwrite +tests/regression/apparmor/query_label tests/regression/apparmor/readdir tests/regression/apparmor/rename tests/regression/apparmor/rw +tests/regression/apparmor/socketpair tests/regression/apparmor/swap tests/regression/apparmor/symlink tests/regression/apparmor/syscall_chroot +tests/regression/apparmor/syscall_ioperm +tests/regression/apparmor/syscall_iopl tests/regression/apparmor/syscall_mknod tests/regression/apparmor/syscall_mlockall tests/regression/apparmor/syscall_ptrace @@ -272,10 +288,15 @@ tests/regression/apparmor/syscall_setscheduler tests/regression/apparmor/syscall_sysctl tests/regression/apparmor/sysctl_proc tests/regression/apparmor/tcp +tests/regression/apparmor/transition tests/regression/apparmor/unix_fd_client tests/regression/apparmor/unix_fd_server +tests/regression/apparmor/unix_socket +tests/regression/apparmor/unix_socket_client tests/regression/apparmor/unlink +tests/regression/apparmor/uservars.inc tests/regression/apparmor/xattrs +tests/regression/apparmor/xattrs_profile tests/regression/apparmor/coredump **/__pycache__/ *.orig diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 64d6a4cb0..91174d41e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,9 +1,5 @@ --- image: ubuntu:latest -before_script: - - export DEBIAN_FRONTEND=noninteractive && apt-get update -qq && apt-get install --no-install-recommends -y build-essential apache2-dev autoconf automake bison dejagnu flex libpam-dev libtool perl liblocale-gettext-perl pkg-config python-all-dev python3-all-dev pyflakes3 ruby-dev swig lsb-release python3-notify2 python3-psutil python3-setuptools zlib1g-dev - - lsb_release -a - - uname -a # XXX - add a deploy stage to publish man pages, docs, and coverage # reports @@ -12,44 +8,132 @@ stages: - build - test +.ubuntu-before_script: + before_script: + - export DEBIAN_FRONTEND=noninteractive + - apt-get update -qq + - apt-get install --no-install-recommends -y gcc perl liblocale-gettext-perl linux-libc-dev lsb-release make + - lsb_release -a + - uname -a + +.install-c-build-deps: &install-c-build-deps + - apt-get install --no-install-recommends -y build-essential apache2-dev autoconf automake bison dejagnu flex libpam-dev libtool pkg-config python3-all-dev python3-setuptools ruby-dev swig zlib1g-dev + build-all: stage: build + extends: + - .ubuntu-before_script artifacts: name: ${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA} expire_in: 30 days untracked: true paths: - - libraries/libapparmor/ - - parser/ - - binutils/ - - utils/ - - changehat/mod_apparmor/ - - changehat/pam_apparmor/ - - profiles/ - script: - - cd libraries/libapparmor && ./autogen.sh && ./configure --with-perl --with-python --prefix=/usr && make && cd ../.. || { cat config.log ; exit 1 ; } - - make -C parser - - make -C binutils - - make -C utils - - make -C changehat/mod_apparmor - - make -C changehat/pam_apparmor - - make -C profiles - -test-all: + - libraries/libapparmor/ + - parser/ + - binutils/ + - utils/ + - changehat/mod_apparmor/ + - changehat/pam_apparmor/ + - profiles/ + script: + - *install-c-build-deps + - cd libraries/libapparmor && ./autogen.sh && ./configure --with-perl --with-python --prefix=/usr && make && cd ../.. || { cat config.log ; exit 1 ; } + - make -C parser + - make -C binutils + - make -C utils + - make -C changehat/mod_apparmor + - make -C changehat/pam_apparmor + - make -C profiles + +test-libapparmor: + stage: test + needs: ["build-all"] + extends: + - .ubuntu-before_script + script: + - *install-c-build-deps + - make -C libraries/libapparmor check + +test-parser: stage: test + needs: ["build-all"] + extends: + - .ubuntu-before_script script: - - make -C libraries/libapparmor check - - make -C parser check - - make -C binutils check - - make -C utils check - - make -C changehat/mod_apparmor check - - make -C profiles check-parser - - make -C profiles check-abstractions.d + - *install-c-build-deps + - make -C parser check + +test-binutils: + stage: test + needs: ["build-all"] + extends: + - .ubuntu-before_script + script: + - make -C binutils check + +test-utils: + stage: test + needs: ["build-all"] + extends: + - .ubuntu-before_script + script: + - apt-get install --no-install-recommends -y libc6-dev libjs-jquery libjs-jquery-throttle-debounce libjs-jquery-isonscreen libjs-jquery-tablesorter pyflakes3 python3-coverage python3-notify2 python3-psutil python3-setuptools + # See apparmor/apparmor#221 + - make -C parser/tst gen_dbus + - make -C parser/tst gen_xtrans + - make -C utils check + - make -C utils/test coverage-regression + artifacts: + paths: + - utils/test/htmlcov/ + when: always + +test-mod-apparmor: + stage: test + needs: ["build-all"] + extends: + - .ubuntu-before_script + script: + - make -C changehat/mod_apparmor check + +test-profiles: + stage: test + needs: ["build-all"] + extends: + - .ubuntu-before_script + script: + - make -C profiles check-parser + - make -C profiles check-abstractions.d + - make -C profiles check-extras + +shellcheck: + stage: test + needs: [] + extends: + - .ubuntu-before_script + script: + - apt-get install --no-install-recommends -y file shellcheck xmlstarlet + - shellcheck --version + - './tests/bin/shellcheck-tree --format=checkstyle + | xmlstarlet tr tests/checkstyle2junit.xslt + > shellcheck.xml' + artifacts: + when: always + reports: + junit: shellcheck.xml # Disabled due to aa-logprof dependency on /sbin/apparmor_parser existing -# - make -C profiles check-profiles +# - make -C profiles check-profiles # test-pam_apparmor: # - stage: test # - script: # - cd changehat/pam_apparmor && make check + +include: + - template: SAST.gitlab-ci.yml + - template: Secret-Detection.gitlab-ci.yml + +variables: + SAST_EXCLUDED_ANALYZERS: "eslint,flawfinder,semgrep,spotbugs" + SAST_BANDIT_EXCLUDED_PATHS: "*/tst/*, */test/*" diff --git a/.shellcheckrc b/.shellcheckrc new file mode 100644 index 000000000..1dcfc1b86 --- /dev/null +++ b/.shellcheckrc @@ -0,0 +1,10 @@ +# Don't follow source'd scripts +disable=SC1090 +disable=SC1091 + +# dash supports 'local' +disable=SC2039 +disable=SC3043 + +# dash supports 'echo -n' +disable=SC3037 diff --git a/binutils/aa-enabled.1 b/binutils/aa-enabled.1 index c993335d5..2f804ac00 100644 --- a/binutils/aa-enabled.1 +++ b/binutils/aa-enabled.1 @@ -133,7 +133,7 @@ .\" ======================================================================== .\" .IX Title "AA-ENABLED 1" -.TH AA-ENABLED 1 "2022-11-22" "AppArmor 3.0.8" "AppArmor" +.TH AA-ENABLED 1 "2024-02-02" "AppArmor 3.1.7" "AppArmor" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l diff --git a/binutils/aa-exec.1 b/binutils/aa-exec.1 index 45e8b93ea..e34109359 100644 --- a/binutils/aa-exec.1 +++ b/binutils/aa-exec.1 @@ -133,7 +133,7 @@ .\" ======================================================================== .\" .IX Title "AA-EXEC 1" -.TH AA-EXEC 1 "2022-11-22" "AppArmor 3.0.8" "AppArmor" +.TH AA-EXEC 1 "2024-02-02" "AppArmor 3.1.7" "AppArmor" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l diff --git a/binutils/aa-features-abi.1 b/binutils/aa-features-abi.1 index 669d49f53..7354253b7 100644 --- a/binutils/aa-features-abi.1 +++ b/binutils/aa-features-abi.1 @@ -133,7 +133,7 @@ .\" ======================================================================== .\" .IX Title "AA-FEATURES-ABI 1" -.TH AA-FEATURES-ABI 1 "2022-11-22" "AppArmor 3.0.8" "AppArmor" +.TH AA-FEATURES-ABI 1 "2024-02-02" "AppArmor 3.1.7" "AppArmor" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l diff --git a/binutils/aa-status.8 b/binutils/aa-status.8 index e30bf27cd..385fdaf5a 100644 --- a/binutils/aa-status.8 +++ b/binutils/aa-status.8 @@ -133,7 +133,7 @@ .\" ======================================================================== .\" .IX Title "AA-STATUS 8" -.TH AA-STATUS 8 "2022-11-22" "AppArmor 3.0.8" "AppArmor" +.TH AA-STATUS 8 "2024-02-02" "AppArmor 3.1.7" "AppArmor" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l diff --git a/binutils/aa_status.c b/binutils/aa_status.c index 57610d53d..092bee55b 100644 --- a/binutils/aa_status.c +++ b/binutils/aa_status.c @@ -454,6 +454,7 @@ static int detailed_output(FILE *json) { const char *process_statuses[] = {"enforce", "complain", "unconfined", "mixed", "kill"}; int ret; size_t i; + int need_finish = 0; ret = get_profiles(&profiles, &nprofiles); if (ret != 0) { @@ -534,16 +535,20 @@ static int detailed_output(FILE *json) { } else { fprintf(json, "%s\"%s\": [{\"profile\": \"%s\", \"pid\": \"%s\", \"status\": \"%s\"}", // first element will be a unique executable - i == 0 && j == 0 ? "" : "], ", + j == 0 && !need_finish ? "" : "], ", filtered[j].exe, filtered[j].profile, filtered[j].pid, filtered[j].mode); } + need_finish = 1; } } free_processes(filtered, nfiltered); } if (json) { - fprintf(json, "%s}}\n", nprocesses > 0 ? "]" : ""); + if (need_finish > 0) { + fprintf(json, "]"); + } + fprintf(json, "}}\n"); } exit: diff --git a/binutils/po/aa-enabled.pot b/binutils/po/aa-enabled.pot new file mode 100644 index 000000000..bb2b69e78 --- /dev/null +++ b/binutils/po/aa-enabled.pot @@ -0,0 +1,66 @@ +# Copyright (C) 2015 Canonical Ltd +# This file is distributed under the same license as the AppArmor package. +# John Johansen <john.johansen@canonical.com>, 2015. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: apparmor@lists.ubuntu.com\n" +"POT-Creation-Date: 2015-11-28 10:23-0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: ../aa_enabled.c:26 +#, c-format +msgid "" +"%s: [options]\n" +" options:\n" +" -q | --quiet Don't print out any messages\n" +" -h | --help Print help\n" +msgstr "" + +#: ../aa_enabled.c:45 +#, c-format +msgid "unknown or incompatible options\n" +msgstr "" + +#: ../aa_enabled.c:55 +#, c-format +msgid "unknown option '%s'\n" +msgstr "" + +#: ../aa_enabled.c:64 +#, c-format +msgid "Yes\n" +msgstr "" + +#: ../aa_enabled.c:71 +#, c-format +msgid "No - not available on this system.\n" +msgstr "" + +#: ../aa_enabled.c:74 +#, c-format +msgid "No - disabled at boot.\n" +msgstr "" + +#: ../aa_enabled.c:77 +#, c-format +msgid "Maybe - policy interface not available.\n" +msgstr "" + +#: ../aa_enabled.c:81 +#, c-format +msgid "Maybe - insufficient permissions to determine availability.\n" +msgstr "" + +#: ../aa_enabled.c:84 +#, c-format +msgid "Error - '%s'\n" +msgstr "" diff --git a/binutils/po/aa_enabled.pot b/binutils/po/aa_enabled.pot index e9850bf49..8f3a23f7d 100644 --- a/binutils/po/aa_enabled.pot +++ b/binutils/po/aa_enabled.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: apparmor@lists.ubuntu.com\n" -"POT-Creation-Date: 2020-10-14 03:58-0700\n" +"POT-Creation-Date: 2020-10-14 03:52-0700\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" @@ -22,7 +22,7 @@ msgstr "" msgid "" "%s: [options]\n" " options:\n" -" -x | --exclusive Shared interfaces must be availabe\n" +" -x | --exclusive Shared interfaces must be available\n" " -q | --quiet Don't print out any messages\n" " -h | --help Print help\n" msgstr "" diff --git a/binutils/po/aa_exec.pot b/binutils/po/aa_exec.pot index bfaa2ffee..ff9d62f3a 100644 --- a/binutils/po/aa_exec.pot +++ b/binutils/po/aa_exec.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: apparmor@lists.ubuntu.com\n" -"POT-Creation-Date: 2020-10-14 03:58-0700\n" +"POT-Creation-Date: 2020-10-14 03:52-0700\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" diff --git a/binutils/po/aa_features_abi.pot b/binutils/po/aa_features_abi.pot index 12a686101..14daec230 100644 --- a/binutils/po/aa_features_abi.pot +++ b/binutils/po/aa_features_abi.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: apparmor@lists.ubuntu.com\n" -"POT-Creation-Date: 2020-10-14 03:58-0700\n" +"POT-Creation-Date: 2020-10-14 03:52-0700\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" diff --git a/changehat/mod_apparmor/mod_apparmor.8 b/changehat/mod_apparmor/mod_apparmor.8 index 96b68d819..142ad3877 100644 --- a/changehat/mod_apparmor/mod_apparmor.8 +++ b/changehat/mod_apparmor/mod_apparmor.8 @@ -133,7 +133,7 @@ .\" ======================================================================== .\" .IX Title "MOD_APPARMOR 8" -.TH MOD_APPARMOR 8 "2022-11-22" "AppArmor 3.0.8" "AppArmor" +.TH MOD_APPARMOR 8 "2024-02-02" "AppArmor 3.1.7" "AppArmor" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l diff --git a/changehat/mod_apparmor/mod_apparmor.c b/changehat/mod_apparmor/mod_apparmor.c index 71b2f3c91..5fb7af009 100644 --- a/changehat/mod_apparmor/mod_apparmor.c +++ b/changehat/mod_apparmor/mod_apparmor.c @@ -412,7 +412,7 @@ register_hooks(unused_ apr_pool_t *p) module AP_MODULE_DECLARE_DATA apparmor_module = { STANDARD20_MODULE_STUFF, - aa_create_dir_config, /* dir config creater */ + aa_create_dir_config, /* dir config creator */ NULL, /* dir merger --- default is to override */ /* immunix_merge_dir_config, */ /* dir merger --- default is to override */ aa_create_srv_config, /* server config */ diff --git a/changehat/tomcat_apparmor/tomcat_5_0/README.tomcat_apparmor b/changehat/tomcat_apparmor/tomcat_5_0/README.tomcat_apparmor index d32ca697d..dabef00bf 100644 --- a/changehat/tomcat_apparmor/tomcat_5_0/README.tomcat_apparmor +++ b/changehat/tomcat_apparmor/tomcat_5_0/README.tomcat_apparmor @@ -66,8 +66,8 @@ under src/jni_src. cp dist/libJNIChangeHat.so /usr/lib [Note: you must ensure that the target directory is passed to tomcat via the - java.library.path propert. This can be accomplished by setting the JAVA_OPTS - enviroment variable, export JAVA_OPTS=-Djava.library.path, or set via the + java.library.path property. This can be accomplished by setting the JAVA_OPTS + environment variable, export JAVA_OPTS=-Djava.library.path, or set via the env variable LD_LIBRARY_PATH to include this directory so that tomcat can find this library at startup] @@ -108,13 +108,13 @@ under src/jni_src. Once the installation steps above have been started you are ready to begin creating a profile for your application. The profile creation tool genprof will guide you through generating a profile and its support for change_hat will -prompt you create discrete hats as requested byt the changeHatValve during +prompt you create discrete hats as requested by the changeHatValve during tomcat execution. 1. Create a basic profile for the tomcat server. - Run the command "genprof PATH_TO_CATALINA.SH" - - In a seperate window start tomcat and then stop tomcat + - In a separate window start tomcat and then stop tomcat - In the genprof window press "S" to scan for events - Answer the questions about the initial profile for tomcat @@ -124,7 +124,7 @@ tomcat execution. - Stop the tomcat server - Deploy your WAR file or equivalent files under the container. - execute "genprof PATH_TO_CATALINA.SH" - - In a seperate window start tomcat and then exercise your web application + - In a separate window start tomcat and then exercise your web application - In the genprof window press "S" to scan for events During the prompting you will be asked questions similar to: @@ -180,7 +180,7 @@ all subsequent resource requests will be mediated in this hew hat (or security context). If you choose to use the default hat: genprof will mediate all resource requests in the default hat for the duration of processing this request. -When the request processng is complete the valve will change_hat back to the +When the request processing is complete the valve will change_hat back to the parent context. diff --git a/changehat/tomcat_apparmor/tomcat_5_5/README.tomcat_apparmor b/changehat/tomcat_apparmor/tomcat_5_5/README.tomcat_apparmor index d32ca697d..dabef00bf 100644 --- a/changehat/tomcat_apparmor/tomcat_5_5/README.tomcat_apparmor +++ b/changehat/tomcat_apparmor/tomcat_5_5/README.tomcat_apparmor @@ -66,8 +66,8 @@ under src/jni_src. cp dist/libJNIChangeHat.so /usr/lib [Note: you must ensure that the target directory is passed to tomcat via the - java.library.path propert. This can be accomplished by setting the JAVA_OPTS - enviroment variable, export JAVA_OPTS=-Djava.library.path, or set via the + java.library.path property. This can be accomplished by setting the JAVA_OPTS + environment variable, export JAVA_OPTS=-Djava.library.path, or set via the env variable LD_LIBRARY_PATH to include this directory so that tomcat can find this library at startup] @@ -108,13 +108,13 @@ under src/jni_src. Once the installation steps above have been started you are ready to begin creating a profile for your application. The profile creation tool genprof will guide you through generating a profile and its support for change_hat will -prompt you create discrete hats as requested byt the changeHatValve during +prompt you create discrete hats as requested by the changeHatValve during tomcat execution. 1. Create a basic profile for the tomcat server. - Run the command "genprof PATH_TO_CATALINA.SH" - - In a seperate window start tomcat and then stop tomcat + - In a separate window start tomcat and then stop tomcat - In the genprof window press "S" to scan for events - Answer the questions about the initial profile for tomcat @@ -124,7 +124,7 @@ tomcat execution. - Stop the tomcat server - Deploy your WAR file or equivalent files under the container. - execute "genprof PATH_TO_CATALINA.SH" - - In a seperate window start tomcat and then exercise your web application + - In a separate window start tomcat and then exercise your web application - In the genprof window press "S" to scan for events During the prompting you will be asked questions similar to: @@ -180,7 +180,7 @@ all subsequent resource requests will be mediated in this hew hat (or security context). If you choose to use the default hat: genprof will mediate all resource requests in the default hat for the duration of processing this request. -When the request processng is complete the valve will change_hat back to the +When the request processing is complete the valve will change_hat back to the parent context. diff --git a/common/.stamp_rev b/common/.stamp_rev index 892b6a123..0bb256752 100644 --- a/common/.stamp_rev +++ b/common/.stamp_rev @@ -1 +1 @@ -git@gitlab.com:apparmor/apparmor.git master v3.0.7-19-g474a12ebe86bb931 +git@gitlab.com:apparmor/apparmor.git master v3.1.7-0-g7279fae3d1e4e9e3 diff --git a/common/Version b/common/Version index 67786e246..23887f6eb 100644 --- a/common/Version +++ b/common/Version @@ -1 +1 @@ -3.0.8 +3.1.7 diff --git a/common/list_af_names.sh b/common/list_af_names.sh index d7987537a..a6f794ce3 100755 --- a/common/list_af_names.sh +++ b/common/list_af_names.sh @@ -6,7 +6,7 @@ # the source tree # ===================== -# It doesn't make sence for AppArmor to mediate PF_UNIX, filter it out. Search +# It doesn't make sense for AppArmor to mediate PF_UNIX, filter it out. Search # for "PF_" constants since that is what is required in bits/socket.h, but # rewrite as "AF_". diff --git a/libraries/libapparmor/autom4te.cache/output.0 b/libraries/libapparmor/autom4te.cache/output.0 index 74a71f778..a3a08e2ae 100644 --- a/libraries/libapparmor/autom4te.cache/output.0 +++ b/libraries/libapparmor/autom4te.cache/output.0 @@ -3092,7 +3092,7 @@ fi # Define the identity of the package. PACKAGE=libapparmor1 - VERSION=3.0.8 + VERSION=3.1.7 printf "%s\n" "@%:@define PACKAGE \"$PACKAGE\"" >>confdefs.h diff --git a/libraries/libapparmor/autom4te.cache/output.1 b/libraries/libapparmor/autom4te.cache/output.1 index 80d4f8723..f8d012c77 100644 --- a/libraries/libapparmor/autom4te.cache/output.1 +++ b/libraries/libapparmor/autom4te.cache/output.1 @@ -3092,7 +3092,7 @@ fi # Define the identity of the package. PACKAGE=libapparmor1 - VERSION=3.0.8 + VERSION=3.1.7 printf "%s\n" "@%:@define PACKAGE \"$PACKAGE\"" >>confdefs.h diff --git a/libraries/libapparmor/autom4te.cache/output.2 b/libraries/libapparmor/autom4te.cache/output.2 index ef52da37c..0902381ab 100644 --- a/libraries/libapparmor/autom4te.cache/output.2 +++ b/libraries/libapparmor/autom4te.cache/output.2 @@ -3092,7 +3092,7 @@ fi # Define the identity of the package. PACKAGE=libapparmor1 - VERSION=3.0.8 + VERSION=3.1.7 printf "%s\n" "@%:@define PACKAGE \"$PACKAGE\"" >>confdefs.h diff --git a/libraries/libapparmor/autom4te.cache/requests b/libraries/libapparmor/autom4te.cache/requests index 7c4346489..d75f86279 100644 --- a/libraries/libapparmor/autom4te.cache/requests +++ b/libraries/libapparmor/autom4te.cache/requests @@ -41,173 +41,173 @@ 'configure.ac' ], { - 'AC_PYTHON_DEVEL' => 1, - 'LTOBSOLETE_VERSION' => 1, + 'AU_DEFUN' => 1, + 'AC_LIBTOOL_GCJ' => 1, + 'LT_AC_PROG_GCJ' => 1, + 'AM_SANITY_CHECK' => 1, + 'AM_PROG_INSTALL_SH' => 1, + 'AM_AUX_DIR_EXPAND' => 1, + 'AC_LIBTOOL_CXX' => 1, 'AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH' => 1, + 'AC_LIBTOOL_LINKER_OPTION' => 1, + 'PROG_POD2MAN' => 1, + 'AM_PATH_PYTHON' => 1, + '_LT_AC_LANG_F77' => 1, + '_LT_CC_BASENAME' => 1, + '_AM_AUTOCONF_VERSION' => 1, '_LT_AC_PROG_CXXCPP' => 1, - 'AC_LIBTOOL_SYS_DYNAMIC_LINKER' => 1, - '_LT_AC_SHELL_INIT' => 1, - '_AM_DEPENDENCIES' => 1, - '_LT_PROG_F77' => 1, - 'AC_LIBTOOL_SYS_HARD_LINK_LOCKS' => 1, - '_LT_AC_PROG_ECHO_BACKSLASH' => 1, - 'AC_LIBTOOL_LANG_C_CONFIG' => 1, - '_AM_IF_OPTION' => 1, - '_LT_AC_LANG_CXX' => 1, - 'AC_LTDL_PREOPEN' => 1, - '_LT_PROG_LTMAIN' => 1, - 'AM_PROG_INSTALL_SH' => 1, - 'PKG_PROG_PKG_CONFIG' => 1, 'AM_SET_DEPDIR' => 1, - 'AC_CONFIG_MACRO_DIR' => 1, - 'AC_LIBTOOL_SYS_MAX_CMD_LEN' => 1, - '_LT_AC_LANG_F77' => 1, - 'AM_PATH_PYTHON' => 1, - '_AC_AM_CONFIG_HEADER_HOOK' => 1, - 'AM_PYTHON_CHECK_VERSION' => 1, - 'LT_AC_PROG_EGREP' => 1, + '_LT_AC_TAGCONFIG' => 1, + '_AM_SUBST_NOTMAKE' => 1, + '_LT_PROG_FC' => 1, + 'AC_DISABLE_FAST_INSTALL' => 1, + 'AC_LIBTOOL_PROG_LD_SHLIBS' => 1, + '_LT_PROG_CXX' => 1, + '_AM_CONFIG_MACRO_DIRS' => 1, + '_LT_AC_PROG_ECHO_BACKSLASH' => 1, + 'AC_LIBTOOL_LANG_F77_CONFIG' => 1, '_LT_LINKER_BOILERPLATE' => 1, - 'AC_PROG_LD_GNU' => 1, - '_AM_PROG_TAR' => 1, - 'AM_ENABLE_STATIC' => 1, - '_LT_COMPILER_BOILERPLATE' => 1, - '_LT_REQUIRED_DARWIN_CHECKS' => 1, + 'PKG_NOARCH_INSTALLDIR' => 1, + 'AC_LIBTOOL_SYS_MAX_CMD_LEN' => 1, + 'AC_LIBTOOL_PROG_CC_C_O' => 1, + 'LT_OUTPUT' => 1, + 'AM_ENABLE_SHARED' => 1, 'PKG_CHECK_MODULES' => 1, - 'AC_LIBTOOL_COMPILER_OPTION' => 1, - '_AM_SUBST_NOTMAKE' => 1, - 'AM_MAKE_INCLUDE' => 1, - 'AC_LIBTOOL_OBJDIR' => 1, + '_AM_SET_OPTION' => 1, + 'AM_SUBST_NOTMAKE' => 1, + 'AC_CONFIG_MACRO_DIR_TRACE' => 1, + '_LT_AC_TRY_DLOPEN_SELF' => 1, 'AM_PROG_INSTALL_STRIP' => 1, - 'LT_LANG' => 1, - 'LT_INIT' => 1, + '_LT_AC_CHECK_DLFCN' => 1, 'AC_LIBTOOL_LANG_CXX_CONFIG' => 1, - 'LTOPTIONS_VERSION' => 1, - '_LT_DLL_DEF_P' => 1, - 'AC_CONFIG_MACRO_DIR_TRACE' => 1, - 'PKG_INSTALLDIR' => 1, - '_LT_PROG_CXX' => 1, - 'm4_pattern_allow' => 1, - 'AC_DEFUN' => 1, - '_LT_AC_LOCK' => 1, - 'AC_LIBTOOL_PROG_CC_C_O' => 1, + '_AM_PROG_TAR' => 1, + 'PKG_CHECK_EXISTS' => 1, + '_LT_PROG_LTMAIN' => 1, + 'AC_LIBTOOL_POSTDEP_PREDEP' => 1, 'AC_LIBTOOL_LANG_RC_CONFIG' => 1, + '_LT_COMPILER_OPTION' => 1, + 'AC_PROG_LD_GNU' => 1, + 'AM_SET_LEADING_DOT' => 1, + '_AM_DEPENDENCIES' => 1, + '_LT_AC_LANG_F77_CONFIG' => 1, + 'LT_SUPPORTED_TAG' => 1, + '_LT_AC_LANG_C_CONFIG' => 1, + 'LTVERSION_VERSION' => 1, + 'AM_MISSING_HAS_RUN' => 1, + 'AM_RUN_LOG' => 1, + 'LT_PROG_RC' => 1, + 'AM_CONDITIONAL' => 1, + 'AM_AUTOMAKE_VERSION' => 1, '_LT_AC_TAGVAR' => 1, - 'AC_LIBTOOL_PICMODE' => 1, + '_AM_IF_OPTION' => 1, + 'AM_MAKE_INCLUDE' => 1, 'AM_SET_CURRENT_AUTOMAKE_VERSION' => 1, - '_LT_WITH_SYSROOT' => 1, - 'AC_LIBTOOL_SETUP' => 1, - 'LT_AC_PROG_GCJ' => 1, - 'AM_RUN_LOG' => 1, + 'AC_LIBTOOL_F77' => 1, + '_LT_AC_FILE_LTDLL_C' => 1, + 'AC_LTDL_ENABLE_INSTALL' => 1, + 'LTSUGAR_VERSION' => 1, + 'AC_LIBTOOL_SYS_LIB_STRIP' => 1, '_AC_PROG_LIBTOOL' => 1, - 'AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE' => 1, - '_LT_PREPARE_SED_QUOTE_VARS' => 1, - 'AC_LIBTOOL_FC' => 1, - 'AC_PATH_MAGIC' => 1, - 'AC_LIBTOOL_LINKER_OPTION' => 1, - 'AC_ENABLE_STATIC' => 1, - '_LT_AC_LANG_C_CONFIG' => 1, - 'AC_PROG_EGREP' => 1, - '_LT_AC_LANG_GCJ_CONFIG' => 1, '_AM_OUTPUT_DEPENDENCY_COMMANDS' => 1, - '_AM_PROG_CC_C_O' => 1, - '_LT_AC_CHECK_DLFCN' => 1, - '_LT_AC_TRY_DLOPEN_SELF' => 1, - 'AC_LIBTOOL_RC' => 1, - '_LT_PROG_ECHO_BACKSLASH' => 1, - '_PKG_SHORT_ERRORS_SUPPORTED' => 1, - 'AM_PROG_CC_C_O' => 1, - 'AC_LIBTOOL_GCJ' => 1, + 'AM_SILENT_RULES' => 1, + 'AC_PROG_LD_RELOAD_FLAG' => 1, + 'AC_ENABLE_SHARED' => 1, + 'AC_PYTHON_DEVEL' => 1, + 'AM_MISSING_PROG' => 1, '_LT_AC_SYS_COMPILER' => 1, - 'AM_SANITY_CHECK' => 1, - 'AM_INIT_AUTOMAKE' => 1, - 'LT_LIB_M' => 1, - '_AM_SET_OPTIONS' => 1, - 'LT_OUTPUT' => 1, - 'LT_AC_PROG_RC' => 1, - '_LT_AC_SYS_LIBPATH_AIX' => 1, - '_LT_AC_LANG_GCJ' => 1, - 'AC_DEFUN_ONCE' => 1, + 'AC_LTDL_PREOPEN' => 1, 'LT_PROG_GCJ' => 1, - 'AM_DISABLE_STATIC' => 1, - 'PKG_NOARCH_INSTALLDIR' => 1, + '_LT_AC_LANG_RC_CONFIG' => 1, + 'AC_LIBTOOL_SYS_OLD_ARCHIVE' => 1, + '_LT_AC_SYS_LIBPATH_AIX' => 1, + 'AC_LIBTOOL_WIN32_DLL' => 1, + 'AC_PATH_MAGIC' => 1, + 'AC_LIBTOOL_LANG_GCJ_CONFIG' => 1, + 'AC_CONFIG_MACRO_DIR' => 1, + 'LT_PATH_LD' => 1, + 'AM_INIT_AUTOMAKE' => 1, + 'AC_PROG_EGREP' => 1, + '_LT_DLL_DEF_P' => 1, + 'LT_PATH_NM' => 1, + 'AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE' => 1, '_m4_warn' => 1, + 'AM_PROG_LD' => 1, + '_LT_REQUIRED_DARWIN_CHECKS' => 1, + '_LT_PROG_F77' => 1, + '_AC_AM_CONFIG_HEADER_HOOK' => 1, + 'AM_PROG_NM' => 1, + '_AM_PROG_CC_C_O' => 1, + 'LTOPTIONS_VERSION' => 1, + 'AC_LIBTOOL_PROG_COMPILER_NO_RTTI' => 1, 'AC_PATH_TOOL_PREFIX' => 1, - 'AC_ENABLE_SHARED' => 1, - 'AC_LIBTOOL_CONFIG' => 1, - '_LT_LINKER_OPTION' => 1, - '_AM_AUTOCONF_VERSION' => 1, - 'AM_SUBST_NOTMAKE' => 1, - 'AM_PROG_LIBTOOL' => 1, - 'AC_LIBTOOL_DLOPEN_SELF' => 1, - 'LT_AC_PROG_SED' => 1, - 'LT_SYS_DLOPEN_SELF' => 1, - '_AM_SET_OPTION' => 1, - '_AM_MANGLE_OPTION' => 1, - 'AC_CHECK_LIBM' => 1, - 'LT_SUPPORTED_TAG' => 1, - 'AC_LIBTOOL_PROG_COMPILER_PIC' => 1, 'PKG_CHECK_VAR' => 1, - '_LT_AC_LANG_RC_CONFIG' => 1, - 'AU_DEFUN' => 1, - 'AM_PROG_LD' => 1, + 'AC_ENABLE_STATIC' => 1, + 'AC_PROG_LIBTOOL' => 1, + 'AC_LIBTOOL_SYS_HARD_LINK_LOCKS' => 1, + 'LT_PROG_GO' => 1, + 'AC_LIBTOOL_SETUP' => 1, + 'AC_LIBTOOL_COMPILER_OPTION' => 1, + 'AC_LIBTOOL_FC' => 1, + 'AC_ENABLE_FAST_INSTALL' => 1, + 'PKG_CHECK_MODULES_STATIC' => 1, 'AM_DISABLE_SHARED' => 1, - '_LT_COMPILER_OPTION' => 1, - 'AM_PROG_LEX' => 1, - 'PROG_POD2MAN' => 1, - '_AM_CONFIG_MACRO_DIRS' => 1, - 'AM_SET_LEADING_DOT' => 1, - 'AM_MISSING_HAS_RUN' => 1, - 'LT_CMD_MAX_LEN' => 1, - 'AM_PROG_NM' => 1, - '_LT_AC_LANG_CXX_CONFIG' => 1, - 'LT_PROG_RC' => 1, - 'LT_PATH_LD' => 1, - 'AC_LTDL_OBJDIR' => 1, - 'AC_LIBTOOL_LANG_GCJ_CONFIG' => 1, - 'AC_LTDL_ENABLE_INSTALL' => 1, - '_LT_PATH_TOOL_PREFIX' => 1, - '_LT_AC_LANG_F77_CONFIG' => 1, - 'AC_LIBTOOL_LANG_F77_CONFIG' => 1, - 'LTVERSION_VERSION' => 1, - 'AC_LIBTOOL_PROG_LD_SHLIBS' => 1, - 'AM_MISSING_PROG' => 1, - 'AC_DISABLE_FAST_INSTALL' => 1, - 'AM_SILENT_RULES' => 1, + '_LT_PREPARE_SED_QUOTE_VARS' => 1, + 'AC_DEFUN_ONCE' => 1, + 'AM_OUTPUT_DEPENDENCY_COMMANDS' => 1, + 'AC_LIBTOOL_PICMODE' => 1, 'AC_DISABLE_STATIC' => 1, - 'LT_PATH_NM' => 1, - 'PKG_CHECK_MODULES_STATIC' => 1, - '_LT_AC_FILE_LTDLL_C' => 1, + 'AC_LIBTOOL_CONFIG' => 1, + 'LT_SYS_DLOPEN_SELF' => 1, + '_AM_SET_OPTIONS' => 1, + '_LT_AC_LANG_GCJ_CONFIG' => 1, + 'AC_LTDL_OBJDIR' => 1, + 'PKG_INSTALLDIR' => 1, + 'AM_DEP_TRACK' => 1, 'AC_LIBTOOL_DLOPEN' => 1, - 'AC_LIBTOOL_CXX' => 1, - '_LT_PROG_FC' => 1, - 'AC_ENABLE_FAST_INSTALL' => 1, - 'AC_PROG_LD' => 1, - 'AC_LIBTOOL_POSTDEP_PREDEP' => 1, - '_LT_CC_BASENAME' => 1, - 'AC_DEPLIBS_CHECK_METHOD' => 1, + '_LT_AC_LANG_CXX' => 1, + 'AM_PROG_CC_C_O' => 1, + '_LT_WITH_SYSROOT' => 1, 'm4_include' => 1, + 'AC_DEFUN' => 1, + '_LT_PATH_TOOL_PREFIX' => 1, 'm4_pattern_forbid' => 1, - 'AM_DEP_TRACK' => 1, - 'PROG_PODCHECKER' => 1, + 'AM_PYTHON_CHECK_VERSION' => 1, 'include' => 1, - 'LTSUGAR_VERSION' => 1, - 'AM_AUTOMAKE_VERSION' => 1, - 'AM_ENABLE_SHARED' => 1, - 'LT_PROG_GO' => 1, - 'AC_PROG_LD_RELOAD_FLAG' => 1, - 'AM_OUTPUT_DEPENDENCY_COMMANDS' => 1, - 'AC_PROG_LIBTOOL' => 1, - 'AC_LIBTOOL_SYS_OLD_ARCHIVE' => 1, - 'AC_LIBTOOL_F77' => 1, - 'PKG_CHECK_EXISTS' => 1, + 'AC_LIBTOOL_SYS_DYNAMIC_LINKER' => 1, + 'AC_CHECK_LIBM' => 1, 'AC_PROG_NM' => 1, - 'AM_CONDITIONAL' => 1, - 'AM_AUX_DIR_EXPAND' => 1, - 'AC_LIBTOOL_SYS_LIB_STRIP' => 1, - 'AC_LIBTOOL_WIN32_DLL' => 1, + 'AC_PROG_LD' => 1, + 'AM_PROG_LEX' => 1, + 'LT_AC_PROG_EGREP' => 1, + 'PROG_PODCHECKER' => 1, + '_LT_AC_SHELL_INIT' => 1, + 'LT_LANG' => 1, + 'LT_AC_PROG_RC' => 1, + 'LT_AC_PROG_SED' => 1, + 'AM_ENABLE_STATIC' => 1, + 'AM_PROG_LIBTOOL' => 1, + 'AC_LIBTOOL_RC' => 1, + 'LT_CMD_MAX_LEN' => 1, + 'AC_LIBTOOL_DLOPEN_SELF' => 1, + 'AC_LIBTOOL_OBJDIR' => 1, + 'LT_INIT' => 1, + '_LT_AC_LANG_GCJ' => 1, + 'AC_DEPLIBS_CHECK_METHOD' => 1, + '_LT_AC_LANG_CXX_CONFIG' => 1, + '_LT_LINKER_OPTION' => 1, + '_AM_MANGLE_OPTION' => 1, + 'LTOBSOLETE_VERSION' => 1, + 'm4_pattern_allow' => 1, 'AC_DISABLE_SHARED' => 1, - '_LT_AC_TAGCONFIG' => 1, - 'AC_LIBTOOL_PROG_COMPILER_NO_RTTI' => 1 + '_LT_PROG_ECHO_BACKSLASH' => 1, + 'AM_DISABLE_STATIC' => 1, + 'PKG_PROG_PKG_CONFIG' => 1, + '_PKG_SHORT_ERRORS_SUPPORTED' => 1, + 'AC_LIBTOOL_LANG_C_CONFIG' => 1, + '_LT_COMPILER_BOILERPLATE' => 1, + 'AC_LIBTOOL_PROG_COMPILER_PIC' => 1, + 'LT_LIB_M' => 1, + '_LT_AC_LOCK' => 1 } ], 'Autom4te::Request' ), bless( [ @@ -223,69 +223,69 @@ 'configure.ac' ], { - 'AM_PROG_CC_C_O' => 1, - '_AM_MAKEFILE_INCLUDE' => 1, - 'm4_sinclude' => 1, - 'AM_INIT_AUTOMAKE' => 1, - 'AC_FC_SRCEXT' => 1, - 'AC_CONFIG_LINKS' => 1, - 'AC_CANONICAL_SYSTEM' => 1, - 'sinclude' => 1, - 'AC_FC_PP_SRCEXT' => 1, - 'AM_NLS' => 1, + 'AM_PROG_FC_C_O' => 1, 'AC_FC_PP_DEFINE' => 1, - 'AM_EXTRA_RECURSIVE_TARGETS' => 1, - 'AM_PATH_GUILE' => 1, - 'AC_SUBST' => 1, - 'AM_POT_TOOLS' => 1, - '_m4_warn' => 1, - 'AM_ENABLE_MULTILIB' => 1, 'AC_CANONICAL_BUILD' => 1, - 'AM_PROG_LIBTOOL' => 1, - 'AM_PROG_F77_C_O' => 1, - 'AM_PROG_MKDIR_P' => 1, - '_AM_COND_ENDIF' => 1, + 'include' => 1, 'LT_SUPPORTED_TAG' => 1, - 'AC_CANONICAL_TARGET' => 1, - 'AM_PROG_MOC' => 1, - 'AC_INIT' => 1, - 'AC_CONFIG_HEADERS' => 1, - 'AC_CANONICAL_HOST' => 1, + 'IT_PROG_INTLTOOL' => 1, + 'm4_pattern_forbid' => 1, + 'AC_CONFIG_LIBOBJ_DIR' => 1, + 'AC_SUBST_TRACE' => 1, + 'AM_PATH_GUILE' => 1, + 'AM_GNU_GETTEXT_INTL_SUBDIR' => 1, + 'AM_PROG_LIBTOOL' => 1, + 'AM_CONDITIONAL' => 1, + 'AM_AUTOMAKE_VERSION' => 1, + 'AC_CANONICAL_SYSTEM' => 1, + 'AC_REQUIRE_AUX_FILE' => 1, + 'AH_OUTPUT' => 1, + 'm4_pattern_allow' => 1, + 'AM_SILENT_RULES' => 1, + 'LT_INIT' => 1, + 'AM_ENABLE_MULTILIB' => 1, 'AC_CONFIG_FILES' => 1, + 'AM_INIT_AUTOMAKE' => 1, + 'sinclude' => 1, + 'm4_sinclude' => 1, + 'AM_PROG_MOC' => 1, 'AC_FC_FREEFORM' => 1, + '_AM_COND_ELSE' => 1, + 'AM_NLS' => 1, + 'AC_CONFIG_HEADERS' => 1, + '_m4_warn' => 1, + 'AC_LIBSOURCE' => 1, + 'AM_PROG_F77_C_O' => 1, + 'AM_GNU_GETTEXT' => 1, 'GTK_DOC_CHECK' => 1, - 'AM_MAKEFILE_INCLUDE' => 1, - 'AC_CONFIG_SUBDIRS' => 1, '_AM_SUBST_NOTMAKE' => 1, + 'AM_PROG_MKDIR_P' => 1, + '_LT_AC_TAGCONFIG' => 1, + 'AC_CONFIG_SUBDIRS' => 1, + 'AC_PROG_LIBTOOL' => 1, 'AM_PROG_CXX_C_O' => 1, - 'AH_OUTPUT' => 1, - 'AM_PROG_FC_C_O' => 1, - 'AC_LIBSOURCE' => 1, - 'AM_SILENT_RULES' => 1, + 'AM_MAKEFILE_INCLUDE' => 1, + 'AM_EXTRA_RECURSIVE_TARGETS' => 1, + '_AM_COND_IF' => 1, + 'AM_PROG_AR' => 1, + 'AC_SUBST' => 1, 'AM_XGETTEXT_OPTION' => 1, - '_AM_COND_ELSE' => 1, - 'LT_INIT' => 1, - 'AC_CONFIG_MACRO_DIR_TRACE' => 1, - 'AC_DEFINE_TRACE_LITERAL' => 1, - 'AC_SUBST_TRACE' => 1, - 'AM_GNU_GETTEXT_INTL_SUBDIR' => 1, - 'm4_pattern_allow' => 1, - 'm4_pattern_forbid' => 1, + 'LT_CONFIG_LTDL_DIR' => 1, + '_AM_MAKEFILE_INCLUDE' => 1, + 'AC_CANONICAL_TARGET' => 1, 'AC_CONFIG_AUX_DIR' => 1, 'm4_include' => 1, - 'AC_REQUIRE_AUX_FILE' => 1, - 'include' => 1, - 'AC_CONFIG_LIBOBJ_DIR' => 1, - 'AC_PROG_LIBTOOL' => 1, - '_AM_COND_IF' => 1, - 'AM_AUTOMAKE_VERSION' => 1, - 'IT_PROG_INTLTOOL' => 1, - 'AM_GNU_GETTEXT' => 1, - 'AM_CONDITIONAL' => 1, - 'LT_CONFIG_LTDL_DIR' => 1, - '_LT_AC_TAGCONFIG' => 1, + 'AM_PROG_CC_C_O' => 1, + 'AC_CONFIG_LINKS' => 1, + 'AC_DEFINE_TRACE_LITERAL' => 1, + 'AM_POT_TOOLS' => 1, 'AM_MAINTAINER_MODE' => 1, - 'AM_PROG_AR' => 1 + '_AM_COND_ENDIF' => 1, + 'AC_FC_PP_SRCEXT' => 1, + 'AC_INIT' => 1, + 'AC_CONFIG_MACRO_DIR_TRACE' => 1, + 'AC_CANONICAL_HOST' => 1, + 'AC_FC_SRCEXT' => 1 } ], 'Autom4te::Request' ), bless( [ @@ -300,69 +300,69 @@ 'configure.ac' ], { - 'm4_pattern_allow' => 1, - 'AM_GNU_GETTEXT_INTL_SUBDIR' => 1, - 'AC_SUBST_TRACE' => 1, + 'AC_FC_PP_SRCEXT' => 1, + 'AC_INIT' => 1, + 'AC_CONFIG_MACRO_DIR_TRACE' => 1, + 'AC_CANONICAL_HOST' => 1, + 'AC_FC_SRCEXT' => 1, 'm4_include' => 1, - 'm4_pattern_forbid' => 1, 'AC_CONFIG_AUX_DIR' => 1, - 'AC_CONFIG_LIBOBJ_DIR' => 1, - 'include' => 1, - 'AC_REQUIRE_AUX_FILE' => 1, - 'AM_AUTOMAKE_VERSION' => 1, + 'AC_CONFIG_LINKS' => 1, + 'AM_PROG_CC_C_O' => 1, + 'AC_DEFINE_TRACE_LITERAL' => 1, + 'AM_POT_TOOLS' => 1, + '_AM_COND_ENDIF' => 1, + 'AM_MAINTAINER_MODE' => 1, + '_AM_MAKEFILE_INCLUDE' => 1, + 'AC_CANONICAL_TARGET' => 1, + 'AM_PROG_AR' => 1, '_AM_COND_IF' => 1, - 'AC_PROG_LIBTOOL' => 1, - 'IT_PROG_INTLTOOL' => 1, + 'AC_SUBST' => 1, + 'AM_XGETTEXT_OPTION' => 1, 'LT_CONFIG_LTDL_DIR' => 1, - 'AM_CONDITIONAL' => 1, - 'AM_GNU_GETTEXT' => 1, - 'AM_PROG_AR' => 1, - 'AM_MAINTAINER_MODE' => 1, - '_LT_AC_TAGCONFIG' => 1, - '_AM_SUBST_NOTMAKE' => 1, - 'AM_PROG_FC_C_O' => 1, 'AM_PROG_CXX_C_O' => 1, - 'AH_OUTPUT' => 1, + 'AM_MAKEFILE_INCLUDE' => 1, + 'AM_EXTRA_RECURSIVE_TARGETS' => 1, + 'AM_PROG_MKDIR_P' => 1, + '_AM_SUBST_NOTMAKE' => 1, + '_LT_AC_TAGCONFIG' => 1, + 'AC_CONFIG_SUBDIRS' => 1, + 'AC_PROG_LIBTOOL' => 1, + 'AM_GNU_GETTEXT' => 1, + 'GTK_DOC_CHECK' => 1, + '_m4_warn' => 1, 'AC_LIBSOURCE' => 1, - 'AM_SILENT_RULES' => 1, - '_AM_COND_ELSE' => 1, - 'AM_XGETTEXT_OPTION' => 1, - 'LT_INIT' => 1, - 'AC_DEFINE_TRACE_LITERAL' => 1, - 'AC_CONFIG_MACRO_DIR_TRACE' => 1, 'AM_PROG_F77_C_O' => 1, - 'AM_PROG_LIBTOOL' => 1, - 'AM_PROG_MKDIR_P' => 1, - 'AM_PROG_MOC' => 1, - 'AC_CANONICAL_TARGET' => 1, - 'AC_INIT' => 1, - '_AM_COND_ENDIF' => 1, - 'LT_SUPPORTED_TAG' => 1, - 'AC_CANONICAL_HOST' => 1, - 'AC_CONFIG_FILES' => 1, - 'AC_CONFIG_HEADERS' => 1, 'AC_FC_FREEFORM' => 1, - 'GTK_DOC_CHECK' => 1, - 'AC_CONFIG_SUBDIRS' => 1, - 'AM_MAKEFILE_INCLUDE' => 1, - '_AM_MAKEFILE_INCLUDE' => 1, - 'AM_PROG_CC_C_O' => 1, - 'm4_sinclude' => 1, + '_AM_COND_ELSE' => 1, + 'AM_NLS' => 1, + 'AC_CONFIG_HEADERS' => 1, + 'AM_INIT_AUTOMAKE' => 1, 'sinclude' => 1, - 'AC_CONFIG_LINKS' => 1, + 'm4_sinclude' => 1, + 'AM_PROG_MOC' => 1, + 'LT_INIT' => 1, + 'AM_ENABLE_MULTILIB' => 1, + 'AC_CONFIG_FILES' => 1, + 'm4_pattern_allow' => 1, + 'AM_SILENT_RULES' => 1, 'AC_CANONICAL_SYSTEM' => 1, - 'AC_FC_SRCEXT' => 1, - 'AM_INIT_AUTOMAKE' => 1, - 'AC_FC_PP_DEFINE' => 1, - 'AM_NLS' => 1, - 'AC_FC_PP_SRCEXT' => 1, + 'AC_REQUIRE_AUX_FILE' => 1, + 'AH_OUTPUT' => 1, + 'AM_GNU_GETTEXT_INTL_SUBDIR' => 1, 'AM_PATH_GUILE' => 1, - 'AM_EXTRA_RECURSIVE_TARGETS' => 1, - 'AC_SUBST' => 1, + 'AM_PROG_LIBTOOL' => 1, + 'AM_CONDITIONAL' => 1, + 'AM_AUTOMAKE_VERSION' => 1, + 'IT_PROG_INTLTOOL' => 1, + 'm4_pattern_forbid' => 1, + 'AC_CONFIG_LIBOBJ_DIR' => 1, + 'AC_SUBST_TRACE' => 1, + 'AM_PROG_FC_C_O' => 1, + 'AC_FC_PP_DEFINE' => 1, + 'include' => 1, 'AC_CANONICAL_BUILD' => 1, - 'AM_ENABLE_MULTILIB' => 1, - '_m4_warn' => 1, - 'AM_POT_TOOLS' => 1 + 'LT_SUPPORTED_TAG' => 1 } ], 'Autom4te::Request' ) ); diff --git a/libraries/libapparmor/autom4te.cache/traces.0 b/libraries/libapparmor/autom4te.cache/traces.0 index 749ef1616..b2fc42af6 100644 --- a/libraries/libapparmor/autom4te.cache/traces.0 +++ b/libraries/libapparmor/autom4te.cache/traces.0 @@ -2234,7 +2234,7 @@ m4trace:configure.ac:6: -1- m4_pattern_allow([^LIBS$]) m4trace:configure.ac:6: -1- m4_pattern_allow([^build_alias$]) m4trace:configure.ac:6: -1- m4_pattern_allow([^host_alias$]) m4trace:configure.ac:6: -1- m4_pattern_allow([^target_alias$]) -m4trace:configure.ac:8: -1- AM_INIT_AUTOMAKE([libapparmor1], [3.0.8]) +m4trace:configure.ac:8: -1- AM_INIT_AUTOMAKE([libapparmor1], [3.1.7]) m4trace:configure.ac:8: -1- m4_pattern_allow([^AM_[A-Z]+FLAGS$]) m4trace:configure.ac:8: -1- AM_SET_CURRENT_AUTOMAKE_VERSION m4trace:configure.ac:8: -1- AM_AUTOMAKE_VERSION([1.16.5]) diff --git a/libraries/libapparmor/autom4te.cache/traces.1 b/libraries/libapparmor/autom4te.cache/traces.1 index bd5b24040..50db8200f 100644 --- a/libraries/libapparmor/autom4te.cache/traces.1 +++ b/libraries/libapparmor/autom4te.cache/traces.1 @@ -154,7 +154,7 @@ m4trace:configure.ac:6: -1- m4_pattern_allow([^host_alias$]) m4trace:configure.ac:6: -1- AC_SUBST([target_alias]) m4trace:configure.ac:6: -1- AC_SUBST_TRACE([target_alias]) m4trace:configure.ac:6: -1- m4_pattern_allow([^target_alias$]) -m4trace:configure.ac:8: -1- AM_INIT_AUTOMAKE([libapparmor1], [3.0.8]) +m4trace:configure.ac:8: -1- AM_INIT_AUTOMAKE([libapparmor1], [3.1.7]) m4trace:configure.ac:8: -1- m4_pattern_allow([^AM_[A-Z]+FLAGS$]) m4trace:configure.ac:8: -1- AM_AUTOMAKE_VERSION([1.16.5]) m4trace:configure.ac:8: -1- AC_REQUIRE_AUX_FILE([install-sh]) @@ -180,7 +180,7 @@ configure.ac:8: the top level]) m4trace:configure.ac:8: -1- AC_SUBST([PACKAGE], [libapparmor1]) m4trace:configure.ac:8: -1- AC_SUBST_TRACE([PACKAGE]) m4trace:configure.ac:8: -1- m4_pattern_allow([^PACKAGE$]) -m4trace:configure.ac:8: -1- AC_SUBST([VERSION], [3.0.8]) +m4trace:configure.ac:8: -1- AC_SUBST([VERSION], [3.1.7]) m4trace:configure.ac:8: -1- AC_SUBST_TRACE([VERSION]) m4trace:configure.ac:8: -1- m4_pattern_allow([^VERSION$]) m4trace:configure.ac:8: -1- AC_DEFINE_TRACE_LITERAL([PACKAGE]) diff --git a/libraries/libapparmor/autom4te.cache/traces.2 b/libraries/libapparmor/autom4te.cache/traces.2 index bd5b24040..50db8200f 100644 --- a/libraries/libapparmor/autom4te.cache/traces.2 +++ b/libraries/libapparmor/autom4te.cache/traces.2 @@ -154,7 +154,7 @@ m4trace:configure.ac:6: -1- m4_pattern_allow([^host_alias$]) m4trace:configure.ac:6: -1- AC_SUBST([target_alias]) m4trace:configure.ac:6: -1- AC_SUBST_TRACE([target_alias]) m4trace:configure.ac:6: -1- m4_pattern_allow([^target_alias$]) -m4trace:configure.ac:8: -1- AM_INIT_AUTOMAKE([libapparmor1], [3.0.8]) +m4trace:configure.ac:8: -1- AM_INIT_AUTOMAKE([libapparmor1], [3.1.7]) m4trace:configure.ac:8: -1- m4_pattern_allow([^AM_[A-Z]+FLAGS$]) m4trace:configure.ac:8: -1- AM_AUTOMAKE_VERSION([1.16.5]) m4trace:configure.ac:8: -1- AC_REQUIRE_AUX_FILE([install-sh]) @@ -180,7 +180,7 @@ configure.ac:8: the top level]) m4trace:configure.ac:8: -1- AC_SUBST([PACKAGE], [libapparmor1]) m4trace:configure.ac:8: -1- AC_SUBST_TRACE([PACKAGE]) m4trace:configure.ac:8: -1- m4_pattern_allow([^PACKAGE$]) -m4trace:configure.ac:8: -1- AC_SUBST([VERSION], [3.0.8]) +m4trace:configure.ac:8: -1- AC_SUBST([VERSION], [3.1.7]) m4trace:configure.ac:8: -1- AC_SUBST_TRACE([VERSION]) m4trace:configure.ac:8: -1- m4_pattern_allow([^VERSION$]) m4trace:configure.ac:8: -1- AC_DEFINE_TRACE_LITERAL([PACKAGE]) diff --git a/libraries/libapparmor/configure b/libraries/libapparmor/configure index 13ddd6edf..b83b543b7 100755 --- a/libraries/libapparmor/configure +++ b/libraries/libapparmor/configure @@ -3092,7 +3092,7 @@ fi # Define the identity of the package. PACKAGE=libapparmor1 - VERSION=3.0.8 + VERSION=3.1.7 printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h diff --git a/libraries/libapparmor/doc/aa_getcon.pod b/libraries/libapparmor/doc/aa_getcon.pod index 008f199e5..07a4abd16 100644 --- a/libraries/libapparmor/doc/aa_getcon.pod +++ b/libraries/libapparmor/doc/aa_getcon.pod @@ -116,6 +116,14 @@ The specified I<file/task> does not exist or is not visible. The confinement data is too large to fit in the supplied buffer. +=item B<ENOPROTOOPT> + +The kernel doesn't support the SO_PEERLABEL option in sockets. This happens +mainly when the kernel lacks 'fine grained unix mediation' support. It also +can happen on LSM stacking kernels where another LSM has claimed this +interface and decides to return this error, although this is really a +corner case. + =back =head1 NOTES diff --git a/libraries/libapparmor/doc/aa_policy_cache.pod b/libraries/libapparmor/doc/aa_policy_cache.pod index 592dcd2da..1c7de3692 100644 --- a/libraries/libapparmor/doc/aa_policy_cache.pod +++ b/libraries/libapparmor/doc/aa_policy_cache.pod @@ -125,7 +125,7 @@ layer. Binary policy cache files will be located in the directory returned by this function. The aa_policy_cache_dir_levels() function provides access to the number -of directories that are being overlayed to create the policy cache. +of directories that are being overlaid to create the policy cache. =head1 RETURN VALUE diff --git a/libraries/libapparmor/doc/aa_stack_profile.pod b/libraries/libapparmor/doc/aa_stack_profile.pod index afef0be45..140776437 100644 --- a/libraries/libapparmor/doc/aa_stack_profile.pod +++ b/libraries/libapparmor/doc/aa_stack_profile.pod @@ -109,12 +109,12 @@ To immediately stack a profile named "profile_a", as performed with aa_stack_profile("profile_a"), the equivalent of this shell command can be used: - $ echo -n "stackprofile profile_a" > /proc/self/attr/current + $ echo -n "stack profile_a" > /proc/self/attr/current To stack a profile named "profile_a" at the next exec, as performed with aa_stack_onexec("profile_a"), the equivalent of this shell command can be used: - $ echo -n "stackexec profile_a" > /proc/self/attr/exec + $ echo -n "stack profile_a" > /proc/self/attr/exec These raw AppArmor filesystem operations must only be used when using libapparmor is not a viable option. @@ -184,6 +184,7 @@ with apparmor_parser(8): /etc/passwd r, # Needed for aa_stack_profile() + change-profile -> &i_cant_be_trusted_anymore, /usr/lib/libapparmor*.so* mr, /proc/[0-9]*/attr/current w, } diff --git a/libraries/libapparmor/include/aalogparse.h b/libraries/libapparmor/include/aalogparse.h index 1eee33a68..aab0093ff 100644 --- a/libraries/libapparmor/include/aalogparse.h +++ b/libraries/libapparmor/include/aalogparse.h @@ -159,6 +159,8 @@ typedef struct char *fs_type; char *flags; char *src_name; + + char *class; } aa_log_record; /** diff --git a/libraries/libapparmor/src/Makefile.am b/libraries/libapparmor/src/Makefile.am index 4511bba82..58c5599e7 100644 --- a/libraries/libapparmor/src/Makefile.am +++ b/libraries/libapparmor/src/Makefile.am @@ -11,9 +11,13 @@ INCLUDES = $(all_includes) # 3. If any interfaces have been added, removed, or changed since the last # update, # - increment AA_LIB_CURRENT +# - by 1 if bugfix release +# - by 5 on larger releases. This gives room to fix library interface +# problems in the unlikely event where an interface has to break. # - set AA_LIB_REVISION to 0. # 4. If any interfaces have been added since the last public release, then -# - increment AA_LIB_AGE. +# - increment AA_LIB_AGE by the same amount that AA_LIB_CURRENT was +# incremented. # 5. If any interfaces have been removed or changed since the last public # release, then # - set AA_LIB_AGE to 0. @@ -26,10 +30,12 @@ INCLUDES = $(all_includes) # For more information, see: # http://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html # -AA_LIB_CURRENT = 9 -AA_LIB_REVISION = 4 -AA_LIB_AGE = 8 -EXPECTED_SO_NAME = libapparmor.so.1.8.3 +# After changing the AA_LIB_* variables, also update EXPECTED_SO_NAME. + +AA_LIB_CURRENT = 13 +AA_LIB_REVISION = 3 +AA_LIB_AGE = 12 +EXPECTED_SO_NAME = libapparmor.so.1.12.3 SUFFIXES = .pc.in .pc diff --git a/libraries/libapparmor/src/Makefile.in b/libraries/libapparmor/src/Makefile.in index 84fc1aff5..e182893ec 100644 --- a/libraries/libapparmor/src/Makefile.in +++ b/libraries/libapparmor/src/Makefile.in @@ -579,9 +579,13 @@ INCLUDES = $(all_includes) # 3. If any interfaces have been added, removed, or changed since the last # update, # - increment AA_LIB_CURRENT +# - by 1 if bugfix release +# - by 5 on larger releases. This gives room to fix library interface +# problems in the unlikely event where an interface has to break. # - set AA_LIB_REVISION to 0. # 4. If any interfaces have been added since the last public release, then -# - increment AA_LIB_AGE. +# - increment AA_LIB_AGE by the same amount that AA_LIB_CURRENT was +# incremented. # 5. If any interfaces have been removed or changed since the last public # release, then # - set AA_LIB_AGE to 0. @@ -594,10 +598,11 @@ INCLUDES = $(all_includes) # For more information, see: # http://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html # -AA_LIB_CURRENT = 9 -AA_LIB_REVISION = 4 -AA_LIB_AGE = 8 -EXPECTED_SO_NAME = libapparmor.so.1.8.3 +# After changing the AA_LIB_* variables, also update EXPECTED_SO_NAME. +AA_LIB_CURRENT = 13 +AA_LIB_REVISION = 3 +AA_LIB_AGE = 12 +EXPECTED_SO_NAME = libapparmor.so.1.12.3 SUFFIXES = .pc.in .pc COMMONDIR = $(top_srcdir)/../../common/ BUILT_SOURCES = grammar.h scanner.h af_protos.h diff --git a/libraries/libapparmor/src/features.c b/libraries/libapparmor/src/features.c index 86a0c39a8..2b16dcf58 100644 --- a/libraries/libapparmor/src/features.c +++ b/libraries/libapparmor/src/features.c @@ -666,7 +666,7 @@ static const char *features_lookup(aa_features *features, const char *str) /* Empty strings are not accepted. Neither are leading '/' chars. */ if (!str || str[0] == '/') - return false; + return NULL; /** * Break @str into an array of components. For example, @@ -679,7 +679,7 @@ static const char *features_lookup(aa_features *features, const char *str) /* At least one valid token is required */ if (!num_components) - return false; + return NULL; /* Ensure that all components are valid and found */ for (i = 0; i < num_components; i++) { diff --git a/libraries/libapparmor/src/grammar.y b/libraries/libapparmor/src/grammar.y index d986cf026..8afd0c39f 100644 --- a/libraries/libapparmor/src/grammar.y +++ b/libraries/libapparmor/src/grammar.y @@ -187,6 +187,7 @@ aa_record_event_type lookup_aa_event(unsigned int type) %token TOK_KEY_FSTYPE %token TOK_KEY_FLAGS %token TOK_KEY_SRCNAME +%token TOK_KEY_CLASS %token TOK_SOCKLOGD_KERNEL %token TOK_SYSLOG_KERNEL @@ -384,7 +385,7 @@ key: TOK_KEY_OPERATION TOK_EQUALS TOK_QUOTED_STRING | TOK_KEY_CAPABILITY TOK_EQUALS TOK_DIGITS { /* need to reverse map number to string, need to figure out * how to get auto generation of reverse mapping table into - * autotools Makefile. For now just drop assumming capname is + * autotools Makefile. For now just drop assuming capname is * present which it should be with current kernels */ } | TOK_KEY_CAPNAME TOK_EQUALS TOK_QUOTED_STRING @@ -392,7 +393,7 @@ key: TOK_KEY_OPERATION TOK_EQUALS TOK_QUOTED_STRING ret_record->name = $3; } | TOK_KEY_OFFSET TOK_EQUALS TOK_DIGITS - { /* offset is used for reporting where an error occured unpacking + { /* offset is used for reporting where an error occurred unpacking * loaded policy. We can just drop this currently */ } @@ -431,6 +432,8 @@ key: TOK_KEY_OPERATION TOK_EQUALS TOK_QUOTED_STRING ret_record->event = AA_RECORD_INVALID; ret_record->info = $1; } + | TOK_KEY_CLASS TOK_EQUALS TOK_QUOTED_STRING + { ret_record->class = $3; } ; apparmor_event: diff --git a/libraries/libapparmor/src/kernel.c b/libraries/libapparmor/src/kernel.c index 67f40d4e8..5fab36e56 100644 --- a/libraries/libapparmor/src/kernel.c +++ b/libraries/libapparmor/src/kernel.c @@ -1319,9 +1319,9 @@ int aa_query_link_path_len(const char *label, size_t label_len, query[pos] = 0; query[++pos] = AA_CLASS_FILE; memcpy(query + pos + 1, link, link_len); - /* The kernel does the query in two parts we could similate this + /* The kernel does the query in two parts; we could simulate this * doing the following, however as long as policy is compiled - * correctly this isn't requied, and it requires and extra round + * correctly this isn't required, and it requires an extra round * trip to the kernel and adds a race on policy replacement between * the two queries. * diff --git a/libraries/libapparmor/src/kernel_interface.c b/libraries/libapparmor/src/kernel_interface.c index 5942ddd27..a0235254d 100644 --- a/libraries/libapparmor/src/kernel_interface.c +++ b/libraries/libapparmor/src/kernel_interface.c @@ -90,7 +90,7 @@ static int write_buffer(int fd, const char *buffer, int size) /** * write_policy_buffer - load compiled policy into the kernel - * @fd: kernel iterface to write to + * @fd: kernel interface to write to * @atomic: whether to load all policy in buffer atomically (true) * @buffer: buffer of policy to load * @size: the size of the data in the buffer @@ -205,7 +205,7 @@ static int write_policy_file_to_iface(aa_kernel_interface *kernel_interface, * @apparmorfs: path to the apparmor directory of the mounted securityfs (can * be NULL and the path will be auto discovered) * - * Returns: 0 on success, -1 on error with errnot set and *@kernel_interface + * Returns: 0 on success, -1 on error with errno set and *@kernel_interface * pointing to NULL */ int aa_kernel_interface_new(aa_kernel_interface **kernel_interface, diff --git a/libraries/libapparmor/src/libaalogparse.c b/libraries/libapparmor/src/libaalogparse.c index 6e7c4b797..d4f0c1737 100644 --- a/libraries/libapparmor/src/libaalogparse.c +++ b/libraries/libapparmor/src/libaalogparse.c @@ -103,6 +103,8 @@ void free_record(aa_log_record *record) free(record->flags); if (record->src_name != NULL) free(record->src_name); + if (record->class != NULL) + free(record->class); free(record); } diff --git a/libraries/libapparmor/src/private.c b/libraries/libapparmor/src/private.c index 52c97910b..26b34c8d1 100644 --- a/libraries/libapparmor/src/private.c +++ b/libraries/libapparmor/src/private.c @@ -63,7 +63,7 @@ struct ignored_suffix_t { }; static struct ignored_suffix_t ignored_suffixes[] = { - /* Debian packging files, which are in flux during install + /* Debian packaging files, which are in flux during install should be silently ignored. */ { ".dpkg-new", 9, 1 }, { ".dpkg-old", 9, 1 }, @@ -147,7 +147,7 @@ int _aa_is_blacklisted(const char *name) return 0; } -/* automaticly free allocated variables tagged with autofree on fn exit */ +/* automatically free allocated variables tagged with autofree on fn exit */ void _aa_autofree(void *p) { void **_p = (void**)p; diff --git a/libraries/libapparmor/src/scanner.l b/libraries/libapparmor/src/scanner.l index 3c93f5d90..e663f787a 100644 --- a/libraries/libapparmor/src/scanner.l +++ b/libraries/libapparmor/src/scanner.l @@ -121,6 +121,8 @@ key_namespace "namespace" key_mask "mask" key_denied_mask "denied_mask" key_requested_mask "requested_mask" +key_denied "denied" +key_requested "requested" key_attribute "attribute" key_task "task" key_parent "parent" @@ -163,11 +165,13 @@ key_dest "dest" key_path "path" key_interface "interface" key_member "member" +key_method "method" key_signal "signal" key_peer "peer" key_fstype "fstype" key_flags "flags" key_srcname "srcname" +key_class "class" audit "audit" /* network addrs */ @@ -309,6 +313,8 @@ yy_flex_debug = 0; {key_mask} { return(TOK_KEY_MASK); } {key_denied_mask} { return(TOK_KEY_DENIED_MASK); } {key_requested_mask} { return(TOK_KEY_REQUESTED_MASK); } +{key_denied} { return(TOK_KEY_DENIED_MASK); } +{key_requested} { return(TOK_KEY_REQUESTED_MASK); } {key_attribute} { BEGIN(sub_id); return(TOK_KEY_ATTRIBUTE); } {key_task} { return(TOK_KEY_TASK); } {key_parent} { return(TOK_KEY_PARENT); } @@ -350,11 +356,13 @@ yy_flex_debug = 0; {key_path} { return(TOK_KEY_PATH); } {key_interface} { return(TOK_KEY_INTERFACE); } {key_member} { return(TOK_KEY_MEMBER); } +{key_method} { return(TOK_KEY_MEMBER); } {key_signal} { BEGIN(sub_id); return(TOK_KEY_SIGNAL); } {key_peer} { BEGIN(safe_string); return(TOK_KEY_PEER); } {key_fstype} { return(TOK_KEY_FSTYPE); } {key_flags} { BEGIN(safe_string); return(TOK_KEY_FLAGS); } {key_srcname} { BEGIN(safe_string); return(TOK_KEY_SRCNAME); } +{key_class} { BEGIN(safe_string); return(TOK_KEY_CLASS); } {socklogd_kernel} { BEGIN(dmesg_timestamp); return(TOK_SOCKLOGD_KERNEL); } {syslog_kernel} { BEGIN(dmesg_timestamp); return(TOK_SYSLOG_KERNEL); } diff --git a/libraries/libapparmor/swig/python/__init__.py b/libraries/libapparmor/swig/python/__init__.py index d3da9d223..7ea244534 100644 --- a/libraries/libapparmor/swig/python/__init__.py +++ b/libraries/libapparmor/swig/python/__init__.py @@ -1,6 +1 @@ -import sys - -if sys.version_info[0] >= 3: - from LibAppArmor.LibAppArmor import * -else: - from .LibAppArmor import * +from LibAppArmor.LibAppArmor import * diff --git a/libraries/libapparmor/swig/python/test/buildpath.py b/libraries/libapparmor/swig/python/test/buildpath.py index 94b63c2e4..cfa05c01f 100644 --- a/libraries/libapparmor/swig/python/test/buildpath.py +++ b/libraries/libapparmor/swig/python/test/buildpath.py @@ -3,6 +3,7 @@ # https://github.com/pypa/setuptools/commit/1c23f5e1e4b18b50081cbabb2dea22bf345f5894 import sys import sysconfig + import setuptools diff --git a/libraries/libapparmor/swig/python/test/test_python.py.in b/libraries/libapparmor/swig/python/test/test_python.py.in index 75c71415e..02b4c39ee 100644 --- a/libraries/libapparmor/swig/python/test/test_python.py.in +++ b/libraries/libapparmor/swig/python/test/test_python.py.in @@ -13,6 +13,7 @@ import ctypes import os import unittest + import LibAppArmor as libapparmor TESTDIR = "../../../testsuite/test_multi" @@ -34,6 +35,7 @@ OUTPUT_MAP = { 'Local port': 'net_local_port', 'Foreign port': 'net_foreign_port', 'Audit subid': 'audit_sub_id', + 'Class': '_class', } # FIXME: pull this automatically out of LibAppArmor, but swig @@ -75,11 +77,11 @@ class AAPythonBindingsTests(unittest.TestCase): expected = self.parse_output_file(outfile) self.assertEqual(expected, record, - "expected records did not match\n" + - "expected = %s\nactual = %s" % (expected, record)) + "expected records did not match\n" + "expected = %s\nactual = %s" % (expected, record)) def parse_output_file(self, outfile): - '''parse testcase .out file and return dict''' + """parse testcase .out file and return dict""" output = dict() with open(os.path.join(TESTDIR, outfile), 'r') as f: @@ -105,10 +107,10 @@ class AAPythonBindingsTests(unittest.TestCase): return output def create_record_dict(self, record): - '''parse the swig created record and construct a dict from it''' + """parse the swig created record and construct a dict from it""" new_record = dict() - for key in [x for x in dir(record) if not (x.startswith('_') or x == 'this')]: + for key in [x for x in dir(record) if not (x.startswith('__') or x == 'this')]: value = getattr(record, key) if key == "event" and value in EVENT_MAP: new_record[key] = EVENT_MAP[value] @@ -128,7 +130,7 @@ class AAPythonBindingsTests(unittest.TestCase): def find_testcases(testdir): - '''dig testcases out of passed directory''' + """dig testcases out of passed directory""" for f in os.listdir(testdir): if f.endswith(".in"): @@ -143,5 +145,6 @@ def main(): setattr(AAPythonBindingsTests, 'test_%s' % (f), stub_test) return unittest.main(verbosity=2) + if __name__ == "__main__": main() diff --git a/libraries/libapparmor/testsuite/libaalogparse.test/multi_test.exp b/libraries/libapparmor/testsuite/libaalogparse.test/multi_test.exp index 8dc626b87..b09fda2ac 100644 --- a/libraries/libapparmor/testsuite/libaalogparse.test/multi_test.exp +++ b/libraries/libapparmor/testsuite/libaalogparse.test/multi_test.exp @@ -1,5 +1,5 @@ -# Runs all tests with the extention "multi" for several times. -# Each testprogram <programname>.multi has an own subdirectory +# Runs all tests with the extension "multi" for several times. +# Each test program <programname>.multi has its own subdirectory # <programmname> in which several testcases are defined for this program # Each testcase has 3 files: # diff --git a/libraries/libapparmor/testsuite/test_multi.c b/libraries/libapparmor/testsuite/test_multi.c index 016077192..f4092a870 100644 --- a/libraries/libapparmor/testsuite/test_multi.c +++ b/libraries/libapparmor/testsuite/test_multi.c @@ -134,6 +134,8 @@ int print_results(aa_log_record *record) print_string("Flags", record->flags); print_string("Src name", record->src_name); + print_string("Class", record->class); + print_long("Epoch", record->epoch, 0); print_long("Audit subid", (long) record->audit_sub_id, 0); return(0); diff --git a/libraries/libapparmor/testsuite/test_multi/file_xm.err b/libraries/libapparmor/testsuite/test_multi/file_xm.err new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/libapparmor/testsuite/test_multi/file_xm.in b/libraries/libapparmor/testsuite/test_multi/file_xm.in new file mode 100644 index 000000000..4a360019a --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/file_xm.in @@ -0,0 +1 @@ +type=AVC msg=audit(1676978994.840:1493): apparmor="DENIED" operation="link" profile="cargo" name="/var/tmp/portage/dev-lang/rust-1.67.1/work/rustc-1.67.1-src/build/bootstrap/debug/libbootstrap.rlib" pid=12412 comm="cargo" requested_mask="xm" denied_mask="xm" fsuid=250 ouid=250 target="/var/tmp/portage/dev-lang/rust-1.67.1/work/rustc-1.67.1-src/build/bootstrap/debug/deps/libbootstrap-4542dd99e796257e.rlib"FSUID="portage" OUID="portage" diff --git a/libraries/libapparmor/testsuite/test_multi/file_xm.out b/libraries/libapparmor/testsuite/test_multi/file_xm.out new file mode 100644 index 000000000..9b9d1890d --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/file_xm.out @@ -0,0 +1,16 @@ +START +File: file_xm.in +Event type: AA_RECORD_DENIED +Audit ID: 1676978994.840:1493 +Operation: link +Mask: xm +Denied Mask: xm +fsuid: 250 +ouid: 250 +Profile: cargo +Name: /var/tmp/portage/dev-lang/rust-1.67.1/work/rustc-1.67.1-src/build/bootstrap/debug/libbootstrap.rlib +Command: cargo +Name2: /var/tmp/portage/dev-lang/rust-1.67.1/work/rustc-1.67.1-src/build/bootstrap/debug/deps/libbootstrap-4542dd99e796257e.rlib +PID: 12412 +Epoch: 1676978994 +Audit subid: 1493 diff --git a/libraries/libapparmor/testsuite/test_multi/file_xm.profile b/libraries/libapparmor/testsuite/test_multi/file_xm.profile new file mode 100644 index 000000000..3ad0162e9 --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/file_xm.profile @@ -0,0 +1,4 @@ +profile cargo { + owner /var/tmp/portage/dev-lang/rust-1.67.1/work/rustc-1.67.1-src/build/bootstrap/debug/libbootstrap.rlib m, + +} diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_dbus_11.err b/libraries/libapparmor/testsuite/test_multi/testcase_dbus_11.err new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_dbus_11.in b/libraries/libapparmor/testsuite/test_multi/testcase_dbus_11.in new file mode 100644 index 000000000..f6df2a4ba --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_dbus_11.in @@ -0,0 +1 @@ +Dec 15 17:32:17 kinetic kernel: [4835959.046111] audit: type=1107 audit(1671125537.724:209): pid=7308 uid=0 auid=4294967295 ses=4294967295 msg='apparmor="DENIED" operation="dbus_method_call" bus="session" path="/org/freedesktop/DBus" interface="org.freedesktop.DBus" method="Hello" mask="send" label="/tmp/apparmor/tests/regression/apparmor/dbus_message" peer_label="unconfined" exe="/usr/local/bin/dbus-broker" sauid=0 hostname=? addr=? terminal=?' diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_dbus_11.out b/libraries/libapparmor/testsuite/test_multi/testcase_dbus_11.out new file mode 100644 index 000000000..5aaf41784 --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_dbus_11.out @@ -0,0 +1,15 @@ +START +File: testcase_dbus_11.in +Event type: AA_RECORD_DENIED +Audit ID: 1671125537.724:209 +Operation: dbus_method_call +Denied Mask: send +Profile: /tmp/apparmor/tests/regression/apparmor/dbus_message +Peer profile: unconfined +Command: /usr/local/bin/dbus-broker +DBus bus: session +DBus path: /org/freedesktop/DBus +DBus interface: org.freedesktop.DBus +DBus member: Hello +Epoch: 1671125537 +Audit subid: 209 diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_dbus_11.profile b/libraries/libapparmor/testsuite/test_multi/testcase_dbus_11.profile new file mode 100644 index 000000000..5e7134b00 --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_dbus_11.profile @@ -0,0 +1,4 @@ +/tmp/apparmor/tests/regression/apparmor/dbus_message { + dbus send bus=session path=/org/freedesktop/DBus interface=org.freedesktop.DBus member=Hello peer=(label=unconfined), + +} diff --git a/parser/Makefile b/parser/Makefile index 15f9d975d..c39ed309f 100644 --- a/parser/Makefile +++ b/parser/Makefile @@ -352,13 +352,8 @@ tests: apparmor_parser ${TESTS} $(AAREOBJECT): FORCE $(MAKE) -C $(AAREDIR) CFLAGS="$(EXTRA_CXXFLAGS)" -.PHONY: install-rhel4 -install-rhel4: install-redhat - .PHONY: install-redhat -install-redhat: - install -m 755 -d $(DESTDIR)/etc/init.d - install -m 755 rc.apparmor.$(subst install-,,$@) $(DESTDIR)/etc/init.d/apparmor +install-redhat: install-systemd .PHONY: install-suse install-suse: install-systemd @@ -389,9 +384,9 @@ DISTRO=$(shell if [ -f /etc/slackware-version ] ; then \ if [ "$$(rpm --eval '0%{?suse_version}')" != "0" ] ; then \ echo suse ;\ elif [ "$$(rpm --eval '%{_host_vendor}')" = redhat ] ; then \ - echo rhel4 ;\ + echo redhat ;\ elif [ "$$(rpm --eval '0%{?fedora}')" != "0" ] ; then \ - echo rhel4 ;\ + echo redhat ;\ else \ echo unknown ;\ fi ;\ diff --git a/parser/aa-teardown.8 b/parser/aa-teardown.8 index e4080fd8a..6f375619e 100644 --- a/parser/aa-teardown.8 +++ b/parser/aa-teardown.8 @@ -133,7 +133,7 @@ .\" ======================================================================== .\" .IX Title "AA-TEARDOWN 8" -.TH AA-TEARDOWN 8 "2022-11-22" "AppArmor 3.0.8" "AppArmor" +.TH AA-TEARDOWN 8 "2024-02-02" "AppArmor 3.1.7" "AppArmor" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l diff --git a/parser/af_rule.cc b/parser/af_rule.cc index c617c609d..dc86c590b 100644 --- a/parser/af_rule.cc +++ b/parser/af_rule.cc @@ -37,7 +37,7 @@ static struct supported_cond supported_conds[] = { { "type", true, false, false, local_cond }, { "protocol", false, false, false, local_cond }, { "label", true, false, false, peer_cond }, - { NULL, false, false, false, local_cond }, /* eol sentinal */ + { NULL, false, false, false, local_cond }, /* eol sentinel */ }; bool af_rule::cond_check(struct supported_cond *conds, struct cond_entry *ent, diff --git a/parser/af_unix.cc b/parser/af_unix.cc index 674ab88c7..db3aa08ca 100644 --- a/parser/af_unix.cc +++ b/parser/af_unix.cc @@ -29,7 +29,7 @@ #include "profile.h" #include "af_unix.h" -/* See unix(7) for autobind address definiation */ +/* See unix(7) for autobind address definition */ #define autobind_address_pattern "\\x00[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]"; int parse_unix_mode(const char *str_mode, int *mode, int fail) @@ -40,7 +40,7 @@ int parse_unix_mode(const char *str_mode, int *mode, int fail) static struct supported_cond supported_conds[] = { { "addr", true, false, false, either_cond }, - { NULL, false, false, false, local_cond }, /* sentinal */ + { NULL, false, false, false, local_cond }, /* sentinel */ }; void unix_rule::move_conditionals(struct cond_entry *conds) @@ -111,8 +111,7 @@ unix_rule::unix_rule(unsigned int type_p, bool audit_p, bool denied): unix_rule::unix_rule(int mode_p, struct cond_entry *conds, struct cond_entry *peer_conds): - af_rule("unix"), addr(NULL), peer_addr(NULL), - audit(0), deny(0) + af_rule("unix"), addr(NULL), peer_addr(NULL) { move_conditionals(conds); move_peer_conditionals(peer_conds); @@ -136,7 +135,7 @@ ostream &unix_rule::dump_local(ostream &os) { af_rule::dump_local(os); if (addr) - os << "addr='" << addr << "'"; + os << " addr='" << addr << "'"; return os; } @@ -144,7 +143,7 @@ ostream &unix_rule::dump_peer(ostream &os) { af_rule::dump_peer(os); if (peer_addr) - os << "addr='" << peer_addr << "'"; + os << " addr='" << peer_addr << "'"; return os; } @@ -326,8 +325,9 @@ int unix_rule::gen_policy_re(Profile &prof) rule_t::warn_once(prof.name, "downgrading extended network unix socket rule to generic network rule\n"); /* TODO: add ability to abort instead of downgrade */ return RULE_OK; + } else { + warn_once(prof.name); } - warn_once(prof.name); return RULE_NOT_SUPPORTED; } @@ -355,7 +355,7 @@ int unix_rule::gen_policy_re(Profile &prof) /* local label option */ if (!write_label(tmp, label)) goto fail; - /* seperator */ + /* separator */ tmp << "\\x00"; buf = tmp.str(); @@ -376,7 +376,7 @@ int unix_rule::gen_policy_re(Profile &prof) /* local label option */ if (!write_label(buffer, label)) goto fail; - /* seperator */ + /* separator */ buffer << "\\x00"; /* create already masked off */ diff --git a/parser/af_unix.h b/parser/af_unix.h index 763ed166b..2d2d70e1f 100644 --- a/parser/af_unix.h +++ b/parser/af_unix.h @@ -36,9 +36,6 @@ class unix_rule: public af_rule { public: char *addr; char *peer_addr; - int mode; - int audit; - bool deny; unix_rule(unsigned int type_p, bool audit_p, bool denied); unix_rule(int mode, struct cond_entry *conds, diff --git a/parser/apparmor.7 b/parser/apparmor.7 index b5e7e4859..4e518c775 100644 --- a/parser/apparmor.7 +++ b/parser/apparmor.7 @@ -133,7 +133,7 @@ .\" ======================================================================== .\" .IX Title "APPARMOR 7" -.TH APPARMOR 7 "2022-11-22" "AppArmor 3.0.8" "AppArmor" +.TH APPARMOR 7 "2024-02-02" "AppArmor 3.1.7" "AppArmor" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l @@ -205,6 +205,69 @@ cannot call the following system calls: \& iopl(2) ptrace(2) reboot(2) setdomainname(2) \& sethostname(2) swapoff(2) swapon(2) sysctl(2) .Ve +.SS "Complain mode" +.IX Subsection "Complain mode" +Instead of denying access to resources the profile does not have a rule for +AppArmor can \*(L"allow\*(R" the access and log a message for the operation +that triggers it. This is called \fIcomplain mode\fR. It is important to +note that rules that are present in the profile are still applied, so +allow rules will still quiet or force audit messages, and deny rules +will still result in denials and quieting of denial messages (see +\&\fITurn off deny audit quieting\fR if this is a problem). +.PP +Complain mode can be used to develop profiles incrementally as an +application is exercised. The logged accesses can be added to the +profile and then can the application further excercised to discover further +additions that are needed. Because AppArmor allows the accesses the +application will behave as it would if AppArmor was not confining it. +.PP +\&\fBWarning\fR complain mode does not provide any security, only +auditing, while it is enabled. It should not be used in a hostile +environment or bad behaviors may be logged and added to the profile +as if they are resource accesses that should be used by the +application. +.PP +\&\fBNote\fR complain mode can be very noisy with new or empty profiles, +but with developed profiles might not log anything if the profile +covers the application behavior well. See \fIAudit Rate Limiting\fR if +complain mode is generating too many log messages. +.PP +To set a profile and any children or hat profiles the profile may contain +into complain mode use +.PP +.Vb 1 +\& aa\-complain /etc/apparmor.d/<the\-application> +.Ve +.PP +To manually set a specific profile in complain mode, add the +\&\f(CW\*(C`complain\*(C'\fR flag, and then manually reload the profile: +.PP +.Vb 1 +\& profile foo flags=(complain) { ... } +.Ve +.PP +Note that the \f(CW\*(C`complain\*(C'\fR flag must also be added manually to any +hats or children profiles of the profile or they will continue to +use the previous mode. +.PP +To enable complain mode globally, run: +.PP +.Vb 1 +\& echo \-n complain > /sys/module/apparmor/parameters/mode +.Ve +.PP +or to set it on boot add: +.PP +.Vb 1 +\& apparmor.mode=complain +.Ve +.PP +as a kernel boot paramenter. +.PP +\&\fBWarning\fR Setting complain mode gloabally disables all apparmor +security protections. It can be useful during debugging or profile +development, but setting it selectively on a per profile basis is +safer. .SH "ERRORS" .IX Header "ERRORS" When a confined process tries to access a file it does not have permission @@ -267,6 +330,14 @@ To enable debug mode, run: .Vb 1 \& echo 1 > /sys/module/apparmor/parameters/debug .Ve +.PP +or to set it on boot add: +.PP +.Vb 1 +\& apparmor.debug=1 +.Ve +.PP +as a kernel boot paramenter. .SS "Turn off deny audit quieting" .IX Subsection "Turn off deny audit quieting" By default, operations that trigger \f(CW\*(C`deny\*(C'\fR rules are not logged. @@ -277,6 +348,14 @@ To turn off deny audit quieting, run: .Vb 1 \& echo \-n noquiet >/sys/module/apparmor/parameters/audit .Ve +.PP +or to set it on boot add: +.PP +.Vb 1 +\& apparmor.audit=noquiet +.Ve +.PP +as a kernel boot paramenter. .SS "Force audit mode" .IX Subsection "Force audit mode" AppArmor can log a message for every operation that triggers a rule @@ -297,6 +376,16 @@ To enable force audit mode globally, run: \& echo \-n all > /sys/module/apparmor/parameters/audit .Ve .PP +or to set it on boot add: +.PP +.Vb 1 +\& apparmor.audit=all +.Ve +.PP +as a kernel boot paramenter. +.PP +\&\fBAudit Rate Limiting\fR +.PP 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: .PP diff --git a/parser/apparmor.7.html b/parser/apparmor.7.html index da1cf7e08..0c15736f5 100644 --- a/parser/apparmor.7.html +++ b/parser/apparmor.7.html @@ -19,7 +19,11 @@ <ul id="index"> <li><a href="#NAME">NAME</a></li> - <li><a href="#DESCRIPTION">DESCRIPTION</a></li> + <li><a href="#DESCRIPTION">DESCRIPTION</a> + <ul> + <li><a href="#Complain-mode">Complain mode</a></li> + </ul> + </li> <li><a href="#ERRORS">ERRORS</a></li> <li><a href="#DEBUGGING">DEBUGGING</a> <ul> @@ -78,6 +82,38 @@ $ cat /sys/kernel/security/apparmor/profiles iopl(2) ptrace(2) reboot(2) setdomainname(2) sethostname(2) swapoff(2) swapon(2) sysctl(2)</code></pre> +<h2 id="Complain-mode">Complain mode</h2> + +<p>Instead of denying access to resources the profile does not have a rule for AppArmor can "allow" the access and log a message for the operation that triggers it. This is called <i>complain mode</i>. It is important to note that rules that are present in the profile are still applied, so allow rules will still quiet or force audit messages, and deny rules will still result in denials and quieting of denial messages (see <i>Turn off deny audit quieting</i> if this is a problem).</p> + +<p>Complain mode can be used to develop profiles incrementally as an application is exercised. The logged accesses can be added to the profile and then can the application further excercised to discover further additions that are needed. Because AppArmor allows the accesses the application will behave as it would if AppArmor was not confining it.</p> + +<p><b>Warning</b> complain mode does not provide any security, only auditing, while it is enabled. It should not be used in a hostile environment or bad behaviors may be logged and added to the profile as if they are resource accesses that should be used by the application.</p> + +<p><b>Note</b> complain mode can be very noisy with new or empty profiles, but with developed profiles might not log anything if the profile covers the application behavior well. See <i>Audit Rate Limiting</i> if complain mode is generating too many log messages.</p> + +<p>To set a profile and any children or hat profiles the profile may contain into complain mode use</p> + +<pre><code>aa-complain /etc/apparmor.d/<the-application></code></pre> + +<p>To manually set a specific profile in complain mode, add the <code>complain</code> flag, and then manually reload the profile:</p> + +<pre><code>profile foo flags=(complain) { ... }</code></pre> + +<p>Note that the <code>complain</code> flag must also be added manually to any hats or children profiles of the profile or they will continue to use the previous mode.</p> + +<p>To enable complain mode globally, run:</p> + +<pre><code>echo -n complain > /sys/module/apparmor/parameters/mode</code></pre> + +<p>or to set it on boot add:</p> + +<pre><code>apparmor.mode=complain</code></pre> + +<p>as a kernel boot paramenter.</p> + +<p><b>Warning</b> Setting complain mode gloabally disables all apparmor security protections. It can be useful during debugging or profile development, but setting it selectively on a per profile basis is safer.</p> + <h1 id="ERRORS">ERRORS</h1> <p>When a confined process tries to access a file it does not have permission to access, the kernel will report a message through audit, similar to:</p> @@ -120,6 +156,12 @@ audit(1386512577.017:276): apparmor="ALLOWED" operation="open&quo <pre><code>echo 1 > /sys/module/apparmor/parameters/debug</code></pre> +<p>or to set it on boot add:</p> + +<pre><code>apparmor.debug=1</code></pre> + +<p>as a kernel boot paramenter.</p> + <h2 id="Turn-off-deny-audit-quieting">Turn off deny audit quieting</h2> <p>By default, operations that trigger <code>deny</code> rules are not logged. This is called <i>deny audit quieting</i>.</p> @@ -128,6 +170,12 @@ audit(1386512577.017:276): apparmor="ALLOWED" operation="open&quo <pre><code>echo -n noquiet >/sys/module/apparmor/parameters/audit</code></pre> +<p>or to set it on boot add:</p> + +<pre><code>apparmor.audit=noquiet</code></pre> + +<p>as a kernel boot paramenter.</p> + <h2 id="Force-audit-mode">Force audit mode</h2> <p>AppArmor can log a message for every operation that triggers a rule configured in the policy. This is called <i>force audit mode</i>.</p> @@ -142,6 +190,14 @@ audit(1386512577.017:276): apparmor="ALLOWED" operation="open&quo <pre><code>echo -n all > /sys/module/apparmor/parameters/audit</code></pre> +<p>or to set it on boot add:</p> + +<pre><code>apparmor.audit=all</code></pre> + +<p>as a kernel boot paramenter.</p> + +<p><b>Audit Rate Limiting</b></p> + <p>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:</p> <pre><code>echo 0 > /proc/sys/kernel/printk_ratelimit</code></pre> diff --git a/parser/apparmor.d.5 b/parser/apparmor.d.5 index a7d3c827d..c654e7201 100644 --- a/parser/apparmor.d.5 +++ b/parser/apparmor.d.5 @@ -133,7 +133,7 @@ .\" ======================================================================== .\" .IX Title "APPARMOR.D 5" -.TH APPARMOR.D 5 "2022-11-22" "AppArmor 3.0.8" "AppArmor" +.TH APPARMOR.D 5 "2024-02-02" "AppArmor 3.1.7" "AppArmor" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l @@ -284,7 +284,7 @@ to the policy; this behaviour is modelled after \fBcpp\fR\|(1). .Sp \&\fB\s-1MOUNT FLAGS LIST\s0\fR = Comma separated list of \fI\s-1MOUNT FLAGS\s0\fR. .Sp -\&\fB\s-1MOUNT FLAGS\s0\fR = ( 'ro' | 'rw' | 'nosuid' | 'suid' | 'nodev' | 'dev' | 'noexec' | 'exec' | 'sync' | 'async' | 'remount' | 'mand' | 'nomand' | 'dirsync' | 'noatime' | 'atime' | 'nodiratime' | 'diratime' | 'bind' | 'rbind' | 'move' | 'verbose' | 'silent' | 'loud' | 'acl' | 'noacl' | 'unbindable' | 'runbindable' | 'private' | 'rprivate' | 'slave' | 'rslave' | 'shared' | 'rshared' | 'relatime' | 'norelatime' | 'iversion' | 'noiversion' | 'strictatime' | 'nouser' | 'user' ) +\&\fB\s-1MOUNT FLAGS\s0\fR = ( 'ro' | 'rw' | 'nosuid' | 'suid' | 'nodev' | 'dev' | 'noexec' | 'exec' | 'sync' | 'async' | 'remount' | 'mand' | 'nomand' | 'dirsync' | 'noatime' | 'atime' | 'nodiratime' | 'diratime' | 'bind' | 'rbind' | 'move' | 'verbose' | 'silent' | 'loud' | 'acl' | 'noacl' | 'unbindable' | 'runbindable' | 'private' | 'rprivate' | 'slave' | 'rslave' | 'shared' | 'rshared' | 'relatime' | 'norelatime' | 'iversion' | 'noiversion' | 'strictatime' | 'nostrictatime' | 'lazytime' | 'nolazytime' | 'nouser' | 'user' | 'symfollow' | 'nosymfollow' ) .Sp \&\fB\s-1MOUNT EXPRESSION\s0\fR = ( \fI\s-1ALPHANUMERIC\s0\fR | \fI\s-1AARE\s0\fR ) ... .Sp @@ -353,9 +353,6 @@ to the policy; this behaviour is modelled after \fBcpp\fR\|(1). \&\fB\s-1DBUS ACCESS\s0\fR = ( 'send' | 'receive' | 'bind' | 'eavesdrop' | 'r' | 'read' | 'w' | 'write' | 'rw' ) Some accesses are incompatible with some rules; see below. .Sp -\&\fB\s-1AARE\s0\fR = \fB?*[]{}^\fR - See below for meanings. -.Sp \&\fB\s-1UNIX RULE\s0\fR = [ \fI\s-1QUALIFIERS\s0\fR ] 'unix' [ \fI\s-1UNIX ACCESS EXPR\s0\fR ] [ \fI\s-1UNIX RULE CONDS\s0\fR ] [ \fI\s-1UNIX LOCAL EXPR\s0\fR ] [ \fI\s-1UNIX PEER EXPR\s0\fR ] .Sp \&\fB\s-1UNIX ACCESS EXPR\s0\fR = ( \fI\s-1UNIX ACCESS\s0\fR | \fI\s-1UNIX ACCESS LIST\s0\fR ) @@ -412,6 +409,9 @@ to the policy; this behaviour is modelled after \fBcpp\fR\|(1). .Sp \&\fB\s-1UNQUOTED FILEGLOB\s0\fR = (must start with '/' (after variable expansion), \fB\s-1AARE\s0\fR have special meanings; see below. May include \fI\s-1VARIABLE\s0\fR. Rules with embedded spaces or tabs must be quoted. Rules must end with '/' to apply to directories.) .Sp +\&\fB\s-1AARE\s0\fR = \fB?*[]{}^\fR + See section \*(L"Globbing (\s-1AARE\s0)\*(R" below for meanings. +.Sp \&\fB\s-1ACCESS\s0\fR = ( 'r' | 'w' | 'a' | 'l' | 'k' | 'm' | \fI\s-1EXEC TRANSITION\s0\fR )+ (not all combinations are allowed; see below.) .Sp \&\fB\s-1EXEC TRANSITION\s0\fR = ( 'ix' | 'ux' | 'Ux' | 'px' | 'Px' | 'cx' | 'Cx' | 'pix' | 'Pix' | 'cix' | 'Cix' | 'pux' | 'PUx' | 'cux' | 'CUx' | 'x' ) @@ -1622,9 +1622,10 @@ preamble of the profile. System-wide aliases are found in \&\fI/etc/apparmor.d/tunables/alias\fR, which is included by \&\fI/etc/apparmor.d/tunables/global\fR. \fI/etc/apparmor.d/tunables/global\fR is typically included at the beginning of an AppArmor profile. -.SS "Globbing" -.IX Subsection "Globbing" -File resources may be specified with a globbing syntax similar to that +.SS "Globbing (\s-1AARE\s0)" +.IX Subsection "Globbing (AARE)" +File resources and other parameters accepting an \s-1AARE\s0 +may be specified with a globbing syntax similar to that used by popular shells, such as \fBcsh\fR\|(1), \fBbash\fR\|(1), \fBzsh\fR\|(1). .IP "\fB*\fR" 4 .IX Item "*" @@ -1647,6 +1648,11 @@ will substitute for any single character not matching a, b or c .IP "\fB{ab,cd}\fR" 4 .IX Item "{ab,cd}" will expand to one rule to match ab, one rule to match cd +.Sp +Can also include variables. +.IP "\fB@{variable}\fR" 4 +.IX Item "@{variable}" +will expand to all values assigned to the given variable. .PP When AppArmor looks up a directory the pathname being looked up will end with a slash (e.g., \fI/var/tmp/\fR); otherwise it will not end with a diff --git a/parser/apparmor.d.5.html b/parser/apparmor.d.5.html index 4bf9de443..f94bd2eca 100644 --- a/parser/apparmor.d.5.html +++ b/parser/apparmor.d.5.html @@ -64,7 +64,7 @@ <li><a href="#rlimit-rules">rlimit rules</a></li> <li><a href="#Variables">Variables</a></li> <li><a href="#Alias-rules">Alias rules</a></li> - <li><a href="#Globbing">Globbing</a></li> + <li><a href="#Globbing-AARE">Globbing (AARE)</a></li> <li><a href="#Rule-Qualifiers">Rule Qualifiers</a> <ul> <li><a href="#Qualifier-Blocks">Qualifier Blocks</a></li> @@ -206,7 +206,7 @@ <p><b>MOUNT FLAGS LIST</b> = Comma separated list of <i>MOUNT FLAGS</i>.</p> -<p><b>MOUNT FLAGS</b> = ( 'ro' | 'rw' | 'nosuid' | 'suid' | 'nodev' | 'dev' | 'noexec' | 'exec' | 'sync' | 'async' | 'remount' | 'mand' | 'nomand' | 'dirsync' | 'noatime' | 'atime' | 'nodiratime' | 'diratime' | 'bind' | 'rbind' | 'move' | 'verbose' | 'silent' | 'loud' | 'acl' | 'noacl' | 'unbindable' | 'runbindable' | 'private' | 'rprivate' | 'slave' | 'rslave' | 'shared' | 'rshared' | 'relatime' | 'norelatime' | 'iversion' | 'noiversion' | 'strictatime' | 'nouser' | 'user' )</p> +<p><b>MOUNT FLAGS</b> = ( 'ro' | 'rw' | 'nosuid' | 'suid' | 'nodev' | 'dev' | 'noexec' | 'exec' | 'sync' | 'async' | 'remount' | 'mand' | 'nomand' | 'dirsync' | 'noatime' | 'atime' | 'nodiratime' | 'diratime' | 'bind' | 'rbind' | 'move' | 'verbose' | 'silent' | 'loud' | 'acl' | 'noacl' | 'unbindable' | 'runbindable' | 'private' | 'rprivate' | 'slave' | 'rslave' | 'shared' | 'rshared' | 'relatime' | 'norelatime' | 'iversion' | 'noiversion' | 'strictatime' | 'nostrictatime' | 'lazytime' | 'nolazytime' | 'nouser' | 'user' | 'symfollow' | 'nosymfollow' )</p> <p><b>MOUNT EXPRESSION</b> = ( <i>ALPHANUMERIC</i> | <i>AARE</i> ) ...</p> @@ -274,8 +274,6 @@ <p><b>DBUS ACCESS</b> = ( 'send' | 'receive' | 'bind' | 'eavesdrop' | 'r' | 'read' | 'w' | 'write' | 'rw' ) Some accesses are incompatible with some rules; see below.</p> -<p><b>AARE</b> = <b>?*[]{}^</b> See below for meanings.</p> - <p><b>UNIX RULE</b> = [ <i>QUALIFIERS</i> ] 'unix' [ <i>UNIX ACCESS EXPR</i> ] [ <i>UNIX RULE CONDS</i> ] [ <i>UNIX LOCAL EXPR</i> ] [ <i>UNIX PEER EXPR</i> ]</p> <p><b>UNIX ACCESS EXPR</b> = ( <i>UNIX ACCESS</i> | <i>UNIX ACCESS LIST</i> )</p> @@ -324,6 +322,8 @@ <p><b>UNQUOTED FILEGLOB</b> = (must start with '/' (after variable expansion), <b>AARE</b> have special meanings; see below. May include <i>VARIABLE</i>. Rules with embedded spaces or tabs must be quoted. Rules must end with '/' to apply to directories.)</p> +<p><b>AARE</b> = <b>?*[]{}^</b> See section "Globbing (AARE)" below for meanings.</p> + <p><b>ACCESS</b> = ( 'r' | 'w' | 'a' | 'l' | 'k' | 'm' | <i>EXEC TRANSITION</i> )+ (not all combinations are allowed; see below.)</p> <p><b>EXEC TRANSITION</b> = ( 'ix' | 'ux' | 'Ux' | 'px' | 'Px' | 'cx' | 'Cx' | 'pix' | 'Pix' | 'cix' | 'Cix' | 'pux' | 'PUx' | 'cux' | 'CUx' | 'x' ) A bare 'x' is only allowed in rules with the deny qualifier, everything else only without the deny qualifier.</p> @@ -1315,9 +1315,9 @@ set rlimit nice <= 5,</code></pre> <p>AppArmor also provides alias rules for remapping paths for site-specific layouts. They are an alternative form of path rewriting to using variables, and are done after variable resolution. Alias rules must occur within the preamble of the profile. System-wide aliases are found in <i>/etc/apparmor.d/tunables/alias</i>, which is included by <i>/etc/apparmor.d/tunables/global</i>. <i>/etc/apparmor.d/tunables/global</i> is typically included at the beginning of an AppArmor profile.</p> -<h2 id="Globbing">Globbing</h2> +<h2 id="Globbing-AARE">Globbing (AARE)</h2> -<p>File resources may be specified with a globbing syntax similar to that used by popular shells, such as csh(1), bash(1), zsh(1).</p> +<p>File resources and other parameters accepting an AARE may be specified with a globbing syntax similar to that used by popular shells, such as csh(1), bash(1), zsh(1).</p> <dl> @@ -1362,6 +1362,14 @@ set rlimit nice <= 5,</code></pre> <p>will expand to one rule to match ab, one rule to match cd</p> +<p>Can also include variables.</p> + +</dd> +<dt id="variable"><b>@{variable}</b></dt> +<dd> + +<p>will expand to all values assigned to the given variable.</p> + </dd> </dl> diff --git a/parser/apparmor.d.pod b/parser/apparmor.d.pod index d6f492a39..e5c031738 100644 --- a/parser/apparmor.d.pod +++ b/parser/apparmor.d.pod @@ -172,7 +172,7 @@ B<MOUNT FLAGS EXPRESSION> = ( I<MOUNT FLAGS LIST> | I<MOUNT EXPRESSION> ) B<MOUNT FLAGS LIST> = Comma separated list of I<MOUNT FLAGS>. -B<MOUNT FLAGS> = ( 'ro' | 'rw' | 'nosuid' | 'suid' | 'nodev' | 'dev' | 'noexec' | 'exec' | 'sync' | 'async' | 'remount' | 'mand' | 'nomand' | 'dirsync' | 'noatime' | 'atime' | 'nodiratime' | 'diratime' | 'bind' | 'rbind' | 'move' | 'verbose' | 'silent' | 'loud' | 'acl' | 'noacl' | 'unbindable' | 'runbindable' | 'private' | 'rprivate' | 'slave' | 'rslave' | 'shared' | 'rshared' | 'relatime' | 'norelatime' | 'iversion' | 'noiversion' | 'strictatime' | 'nouser' | 'user' ) +B<MOUNT FLAGS> = ( 'ro' | 'rw' | 'nosuid' | 'suid' | 'nodev' | 'dev' | 'noexec' | 'exec' | 'sync' | 'async' | 'remount' | 'mand' | 'nomand' | 'dirsync' | 'noatime' | 'atime' | 'nodiratime' | 'diratime' | 'bind' | 'rbind' | 'move' | 'verbose' | 'silent' | 'loud' | 'acl' | 'noacl' | 'unbindable' | 'runbindable' | 'private' | 'rprivate' | 'slave' | 'rslave' | 'shared' | 'rshared' | 'relatime' | 'norelatime' | 'iversion' | 'noiversion' | 'strictatime' | 'nostrictatime' | 'lazytime' | 'nolazytime' | 'nouser' | 'user' | 'symfollow' | 'nosymfollow' ) B<MOUNT EXPRESSION> = ( I<ALPHANUMERIC> | I<AARE> ) ... @@ -241,9 +241,6 @@ B<DBUS ACCESS LIST> = Comma separated list of I<DBUS ACCESS> B<DBUS ACCESS> = ( 'send' | 'receive' | 'bind' | 'eavesdrop' | 'r' | 'read' | 'w' | 'write' | 'rw' ) Some accesses are incompatible with some rules; see below. -B<AARE> = B<?*[]{}^> - See below for meanings. - B<UNIX RULE> = [ I<QUALIFIERS> ] 'unix' [ I<UNIX ACCESS EXPR> ] [ I<UNIX RULE CONDS> ] [ I<UNIX LOCAL EXPR> ] [ I<UNIX PEER EXPR> ] B<UNIX ACCESS EXPR> = ( I<UNIX ACCESS> | I<UNIX ACCESS LIST> ) @@ -300,6 +297,9 @@ B<QUOTED FILEGLOB> = '"' I<UNQUOTED FILEGLOB> '"' B<UNQUOTED FILEGLOB> = (must start with '/' (after variable expansion), B<AARE> have special meanings; see below. May include I<VARIABLE>. Rules with embedded spaces or tabs must be quoted. Rules must end with '/' to apply to directories.) +B<AARE> = B<?*[]{}^> + See section "Globbing (AARE)" below for meanings. + B<ACCESS> = ( 'r' | 'w' | 'a' | 'l' | 'k' | 'm' | I<EXEC TRANSITION> )+ (not all combinations are allowed; see below.) B<EXEC TRANSITION> = ( 'ix' | 'ux' | 'Ux' | 'px' | 'Px' | 'cx' | 'Cx' | 'pix' | 'Pix' | 'cix' | 'Cix' | 'pux' | 'PUx' | 'cux' | 'CUx' | 'x' ) @@ -1513,9 +1513,10 @@ F</etc/apparmor.d/tunables/alias>, which is included by F</etc/apparmor.d/tunables/global>. F</etc/apparmor.d/tunables/global> is typically included at the beginning of an AppArmor profile. -=head2 Globbing +=head2 Globbing (AARE) -File resources may be specified with a globbing syntax similar to that +File resources and other parameters accepting an AARE +may be specified with a globbing syntax similar to that used by popular shells, such as csh(1), bash(1), zsh(1). =over 4 @@ -1548,6 +1549,12 @@ will substitute for any single character not matching a, b or c will expand to one rule to match ab, one rule to match cd +Can also include variables. + +=item B<@{variable}> + +will expand to all values assigned to the given variable. + =back When AppArmor looks up a directory the pathname being looked up will diff --git a/parser/apparmor.pod b/parser/apparmor.pod index 4d731f3cb..95c10d09d 100644 --- a/parser/apparmor.pod +++ b/parser/apparmor.pod @@ -98,6 +98,62 @@ cannot call the following system calls: iopl(2) ptrace(2) reboot(2) setdomainname(2) sethostname(2) swapoff(2) swapon(2) sysctl(2) +=head2 Complain mode + +Instead of denying access to resources the profile does not have a rule for +AppArmor can "allow" the access and log a message for the operation +that triggers it. This is called I<complain mode>. It is important to +note that rules that are present in the profile are still applied, so +allow rules will still quiet or force audit messages, and deny rules +will still result in denials and quieting of denial messages (see +I<Turn off deny audit quieting> if this is a problem). + +Complain mode can be used to develop profiles incrementally as an +application is exercised. The logged accesses can be added to the +profile and then can the application further excercised to discover further +additions that are needed. Because AppArmor allows the accesses the +application will behave as it would if AppArmor was not confining it. + +B<Warning> complain mode does not provide any security, only +auditing, while it is enabled. It should not be used in a hostile +environment or bad behaviors may be logged and added to the profile +as if they are resource accesses that should be used by the +application. + +B<Note> complain mode can be very noisy with new or empty profiles, +but with developed profiles might not log anything if the profile +covers the application behavior well. See I<Audit Rate Limiting> if +complain mode is generating too many log messages. + +To set a profile and any children or hat profiles the profile may contain +into complain mode use + + aa-complain /etc/apparmor.d/<the-application> + +To manually set a specific profile in complain mode, add the +C<complain> flag, and then manually reload the profile: + + profile foo flags=(complain) { ... } + +Note that the C<complain> flag must also be added manually to any +hats or children profiles of the profile or they will continue to +use the previous mode. + +To enable complain mode globally, run: + + echo -n complain > /sys/module/apparmor/parameters/mode + +or to set it on boot add: + + apparmor.mode=complain + +as a kernel boot paramenter. + +B<Warning> Setting complain mode gloabally disables all apparmor +security protections. It can be useful during debugging or profile +development, but setting it selectively on a per profile basis is +safer. + =head1 ERRORS When a confined process tries to access a file it does not have permission @@ -158,6 +214,12 @@ To enable debug mode, run: echo 1 > /sys/module/apparmor/parameters/debug +or to set it on boot add: + + apparmor.debug=1 + +as a kernel boot paramenter. + =head2 Turn off deny audit quieting By default, operations that trigger C<deny> rules are not logged. @@ -167,6 +229,12 @@ To turn off deny audit quieting, run: echo -n noquiet >/sys/module/apparmor/parameters/audit +or to set it on boot add: + + apparmor.audit=noquiet + +as a kernel boot paramenter. + =head2 Force audit mode AppArmor can log a message for every operation that triggers a rule @@ -183,6 +251,14 @@ To enable force audit mode globally, run: echo -n all > /sys/module/apparmor/parameters/audit +or to set it on boot add: + + apparmor.audit=all + +as a kernel boot paramenter. + +B<Audit Rate Limiting> + 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: diff --git a/parser/apparmor_parser.8 b/parser/apparmor_parser.8 index 462fbfe2a..00b22b949 100644 --- a/parser/apparmor_parser.8 +++ b/parser/apparmor_parser.8 @@ -133,7 +133,7 @@ .\" ======================================================================== .\" .IX Title "APPARMOR_PARSER 8" -.TH APPARMOR_PARSER 8 "2022-11-22" "AppArmor 3.0.8" "AppArmor" +.TH APPARMOR_PARSER 8 "2024-02-02" "AppArmor 3.1.7" "AppArmor" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l @@ -353,13 +353,13 @@ can be specified per \-\-warn option, but the \-\-warn flag can be passed multiple times. .Sp .Vb 1 -\& apparmor_parser \-\-warn=rules\-not\-enforced ... +\& apparmor_parser \-\-warn=rule\-not\-enforced ... .Ve .Sp A specific warning can be disabled by prepending \fIno\fR\- to the flag .Sp .Vb 1 -\& apparmor_parser \-\-warn=no\-rules\-not\-enforced ... +\& apparmor_parser \-\-warn=no\-rule\-not\-enforced ... .Ve .Sp Use \-\-help=warn to see a full list of which warn flags are supported. @@ -444,6 +444,17 @@ encountered will be used to set the exit code. This option tells the parser to not attempt to rebuild the cache on failure, instead the parser continues on with processing the remaining profiles. +.IP "\-\-estimated\-compile\-size Adjust the internal parameter used to estimate how agressive the parser can be when compiling policy. This may include changes to how or when caches are dropped or how many compile units (jobs) are launched. The value should slightly larger than the largest Resident Set Size (\s-1RSS\s0) encountered for the type of policy being compiled." 4 +.IX Item "--estimated-compile-size Adjust the internal parameter used to estimate how agressive the parser can be when compiling policy. This may include changes to how or when caches are dropped or how many compile units (jobs) are launched. The value should slightly larger than the largest Resident Set Size (RSS) encountered for the type of policy being compiled." +A value that is too small may result in the parser exhausting system +resources when compiling large policy. A value too large may slow +policy compiles down. +.Sp +The value specified may include a suffix of \fI\s-1KB\s0\fR, \fI\s-1MB\s0\fR, \fI\s-1GB\s0\fR, to +make it easier to adjust the size. +.Sp +Note: config-file and command line options will override values chosen +by tuning affected by the option. .IP "\-\-config\-file" 4 .IX Item "--config-file" Specify the config file to use instead of diff --git a/parser/apparmor_parser.8.html b/parser/apparmor_parser.8.html index f18b94057..05c23a5aa 100644 --- a/parser/apparmor_parser.8.html +++ b/parser/apparmor_parser.8.html @@ -323,11 +323,11 @@ <p>Enable various warnings during policy compilation. A single warn flag can be specified per --warn option, but the --warn flag can be passed multiple times.</p> -<pre><code>apparmor_parser --warn=rules-not-enforced ...</code></pre> +<pre><code>apparmor_parser --warn=rule-not-enforced ...</code></pre> <p>A specific warning can be disabled by prepending <i>no</i>- to the flag</p> -<pre><code>apparmor_parser --warn=no-rules-not-enforced ...</code></pre> +<pre><code>apparmor_parser --warn=no-rule-not-enforced ...</code></pre> <p>Use --help=warn to see a full list of which warn flags are supported.</p> @@ -400,6 +400,16 @@ x# - # * number of cpus</code></pre> <p>This option tells the parser to not attempt to rebuild the cache on failure, instead the parser continues on with processing the remaining profiles.</p> +</dd> +<dt id="estimated-compile-size-Adjust-the-internal-parameter-used-to-estimate-how-agressive-the-parser-can-be-when-compiling-policy.-This-may-include-changes-to-how-or-when-caches-are-dropped-or-how-many-compile-units-jobs-are-launched.-The-value-should-slightly-larger-than-the-largest-Resident-Set-Size-RSS-encountered-for-the-type-of-policy-being-compiled">--estimated-compile-size Adjust the internal parameter used to estimate how agressive the parser can be when compiling policy. This may include changes to how or when caches are dropped or how many compile units (jobs) are launched. The value should slightly larger than the largest Resident Set Size (RSS) encountered for the type of policy being compiled.</dt> +<dd> + +<p>A value that is too small may result in the parser exhausting system resources when compiling large policy. A value too large may slow policy compiles down.</p> + +<p>The value specified may include a suffix of <i>KB</i>, <i>MB</i>, <i>GB</i>, to make it easier to adjust the size.</p> + +<p>Note: config-file and command line options will override values chosen by tuning affected by the option.</p> + </dd> <dt id="config-file">--config-file</dt> <dd> diff --git a/parser/apparmor_parser.pod b/parser/apparmor_parser.pod index f670443dd..6c9d0f228 100644 --- a/parser/apparmor_parser.pod +++ b/parser/apparmor_parser.pod @@ -299,11 +299,11 @@ Enable various warnings during policy compilation. A single warn flag can be specified per --warn option, but the --warn flag can be passed multiple times. - apparmor_parser --warn=rules-not-enforced ... + apparmor_parser --warn=rule-not-enforced ... A specific warning can be disabled by prepending I<no>- to the flag - apparmor_parser --warn=no-rules-not-enforced ... + apparmor_parser --warn=no-rule-not-enforced ... Use --help=warn to see a full list of which warn flags are supported. @@ -396,6 +396,23 @@ This option tells the parser to not attempt to rebuild the cache on failure, instead the parser continues on with processing the remaining profiles. +=item --estimated-compile-size +Adjust the internal parameter used to estimate how agressive the parser +can be when compiling policy. This may include changes to how or when +caches are dropped or how many compile units (jobs) are launched. The +value should slightly larger than the largest Resident Set Size (RSS) +encountered for the type of policy being compiled. + +A value that is too small may result in the parser exhausting system +resources when compiling large policy. A value too large may slow +policy compiles down. + +The value specified may include a suffix of I<KB>, I<MB>, I<GB>, to +make it easier to adjust the size. + +Note: config-file and command line options will override values chosen +by tuning affected by the option. + =item --config-file Specify the config file to use instead of diff --git a/parser/apparmor_xattrs.7 b/parser/apparmor_xattrs.7 index 890dabd87..8cd3552e8 100644 --- a/parser/apparmor_xattrs.7 +++ b/parser/apparmor_xattrs.7 @@ -133,7 +133,7 @@ .\" ======================================================================== .\" .IX Title "APPARMOR_XATTRS 7" -.TH APPARMOR_XATTRS 7 "2022-11-22" "AppArmor 3.0.8" "AppArmor" +.TH APPARMOR_XATTRS 7 "2024-02-02" "AppArmor 3.1.7" "AppArmor" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l diff --git a/parser/capability.h b/parser/capability.h index eed8f2c2b..329affb3b 100644 --- a/parser/capability.h +++ b/parser/capability.h @@ -38,6 +38,10 @@ #define CAP_MAC_OVERRIDE 32 #endif +#ifndef CAP_AUDIT_READ +#define CAP_AUDIT_READ 37 +#endif + #ifndef CAP_PERFMON #define CAP_PERFMON 38 #endif diff --git a/parser/lib.c b/parser/lib.c index 11c221075..a105fa379 100644 --- a/parser/lib.c +++ b/parser/lib.c @@ -183,7 +183,7 @@ int strn_escseq(const char **pos, const char *chrs, size_t n) if (strchr(chrs, c)) return c; - /* unsupported escap sequence, backup to return that char */ + /* unsupported escape sequence, backup to return that char */ pos--; return -1; } diff --git a/parser/libapparmor_re/README b/parser/libapparmor_re/README index 41d02281c..a08ba9b44 100644 --- a/parser/libapparmor_re/README +++ b/parser/libapparmor_re/README @@ -8,7 +8,7 @@ chfa.{h,cc} - code to build a highly compressed runtime readonly version of an hfa. aare_rules.{h,cc} - code to that binds parse -> expr-tree -> hfa generation -> chfa generation into a basic interface for converting - rules to a runtime ready statemachine. + rules to a runtime ready state machine. Regular Expression Scanner Generator ==================================== @@ -19,12 +19,12 @@ Notes in the scanner File Format The file format used is based on the GNU flex table file format (--tables-file option; see Table File Format in the flex info pages and the flex sources for documentation). The magic number used in the header -is set to 0x1B5E783D insted of 0xF13C57B1 though, which is meant to +is set to 0x1B5E783D instead of 0xF13C57B1 though, which is meant to indicate that the file format logically is not the same: the YY_ID_CHK (check) and YY_ID_DEF (default) tables are used differently. Flex uses state compression to store only the differences between states -for states that are similar. The amount of compresion influences the parse +for states that are similar. The amount of compression influences the parse speed. The following two states could be stored as in the tables outlined diff --git a/parser/libapparmor_re/aare_rules.cc b/parser/libapparmor_re/aare_rules.cc index 124499044..b250e1013 100644 --- a/parser/libapparmor_re/aare_rules.cc +++ b/parser/libapparmor_re/aare_rules.cc @@ -61,12 +61,12 @@ void aare_rules::add_to_rules(Node *tree, Node *perms) expr_map[perms] = tree; } -static Node *cat_with_null_seperator(Node *l, Node *r) +static Node *cat_with_null_separator(Node *l, Node *r) { return new CatNode(new CatNode(l, new CharNode(0)), r); } -static Node *cat_with_oob_seperator(Node *l, Node *r) +static Node *cat_with_oob_separator(Node *l, Node *r) { return new CatNode(new CatNode(l, new CharNode(transchar(-1, true))), r); } @@ -85,9 +85,9 @@ bool aare_rules::add_rule_vec(int deny, uint32_t perms, uint32_t audit, if (regex_parse(&subtree, rulev[i])) goto err; if (oob) - tree = cat_with_oob_seperator(tree, subtree); + tree = cat_with_oob_separator(tree, subtree); else - tree = cat_with_null_seperator(tree, subtree); + tree = cat_with_null_separator(tree, subtree); } /* @@ -97,11 +97,11 @@ bool aare_rules::add_rule_vec(int deny, uint32_t perms, uint32_t audit, */ exact_match = 1; for (depth_first_traversal i(tree); i && exact_match; i++) { - if (dynamic_cast<StarNode *>(*i) || - dynamic_cast<PlusNode *>(*i) || - dynamic_cast<AnyCharNode *>(*i) || - dynamic_cast<CharSetNode *>(*i) || - dynamic_cast<NotCharSetNode *>(*i)) + if ((*i)->is_type(NODE_TYPE_STAR) || + (*i)->is_type(NODE_TYPE_PLUS) || + (*i)->is_type(NODE_TYPE_ANYCHAR) || + (*i)->is_type(NODE_TYPE_CHARSET) || + (*i)->is_type(NODE_TYPE_NOTCHARSET)) exact_match = 0; } @@ -111,15 +111,15 @@ bool aare_rules::add_rule_vec(int deny, uint32_t perms, uint32_t audit, accept = unique_perms.insert(deny, perms, audit, exact_match); if (flags & DFA_DUMP_RULE_EXPR) { - const char *seperator; + const char *separator; if (oob) - seperator = "\\-x01"; + separator = "\\-x01"; else - seperator = "\\x00"; + separator = "\\x00"; cerr << "rule: "; cerr << rulev[0]; for (int i = 1; i < count; i++) { - cerr << seperator; + cerr << separator; cerr << rulev[i]; } cerr << " -> "; diff --git a/parser/libapparmor_re/chfa.cc b/parser/libapparmor_re/chfa.cc index 55adfbf7d..235df335b 100644 --- a/parser/libapparmor_re/chfa.cc +++ b/parser/libapparmor_re/chfa.cc @@ -193,9 +193,8 @@ void CHFA::insert_state(vector<pair<size_t, size_t> > &free_list, State *default_state = dfa.nonmatching; ssize_t base = 0; int resize; - StateTrans &trans = from->trans; - ssize_t c = trans.begin()->first.c; + ssize_t c; ssize_t prev = 0; ssize_t x = first_free; @@ -204,6 +203,7 @@ void CHFA::insert_state(vector<pair<size_t, size_t> > &free_list, if (trans.empty()) goto do_insert; + c = trans.begin()->first.c; repeat: resize = 0; /* get the first free entry that won't underflow */ @@ -251,10 +251,18 @@ repeat: first_free = next; } -do_insert: + /* these flags will only be set on states that have transitions */ if (c < 0) { base |= MATCH_FLAG_OOB_TRANSITION; } +do_insert: + /* While a state without transitions could have the diff encode + * flag set, it would be pointless resulting in just an extra + * state transition in the encoding chain, and so it should be + * considered an error + * TODO: add check that state without transitions isn't being + * given a diffencode flag + */ if (from->flags & DiffEncodeFlag) base |= DiffEncodeBit32; default_base.push_back(make_pair(default_state, base)); diff --git a/parser/libapparmor_re/expr-tree.cc b/parser/libapparmor_re/expr-tree.cc index b3005f6fd..7dc18b041 100644 --- a/parser/libapparmor_re/expr-tree.cc +++ b/parser/libapparmor_re/expr-tree.cc @@ -23,7 +23,7 @@ * it can be factored so that the set of important nodes is smaller. * Having a reduced set of important nodes generally results in a dfa that * is closer to minimum (fewer redundant states are created). It also - * results in fewer important nodes in a the state set during subset + * results in fewer important nodes in the state set during subset * construction resulting in less memory used to create a dfa. * * Generally it is worth doing expression tree simplification before dfa @@ -150,7 +150,7 @@ void Node::dump_syntax_tree(ostream &os) } /* - * Normalize the regex parse tree for factoring and cancelations. Normalization + * Normalize the regex parse tree for factoring and cancellations. Normalization * reorganizes internal (alt and cat) nodes into a fixed "normalized" form that * simplifies factoring code, in that it produces a canonicalized form for * the direction being normalized so that the factoring code does not have @@ -172,10 +172,10 @@ void Node::dump_syntax_tree(ostream &os) * dir to !dir. Until no dir direction node meets the criterial. * Then recurse to the children (which will have a different node type) * to make sure they are normalized. - * Normalization of a child node is guarenteed to not affect the + * Normalization of a child node is guaranteed to not affect the * normalization of the parent. * - * For cat nodes the depth first traverse order is guarenteed to be + * For cat nodes the depth first traverse order is guaranteed to be * maintained. This is not necessary for altnodes. * * Eg. For left normalization @@ -210,7 +210,7 @@ int TwoChildNode::normalize_eps(int dir) // Test for E | (E | E) and E . (E . E) which will // result in an infinite loop Node *c = child[!dir]; - if (dynamic_cast<TwoChildNode *>(c) && + if (c->is_type(NODE_TYPE_TWOCHILD) && &epsnode == c->child[dir] && &epsnode == c->child[!dir]) { c->release(); @@ -229,7 +229,7 @@ void CatNode::normalize(int dir) for (;;) { if (normalize_eps(dir)) { continue; - } else if (dynamic_cast<CatNode *>(child[dir])) { + } else if (child[dir]->is_type(NODE_TYPE_CAT)) { // (ab)c -> a(bc) rotate_node(this, dir); } else { @@ -248,11 +248,11 @@ void AltNode::normalize(int dir) for (;;) { if (normalize_eps(dir)) { continue; - } else if (dynamic_cast<AltNode *>(child[dir])) { + } else if (child[dir]->is_type(NODE_TYPE_ALT)) { // (a | b) | c -> a | (b | c) rotate_node(this, dir); - } else if (dynamic_cast<CharSetNode *>(child[dir]) && - dynamic_cast<CharNode *>(child[!dir])) { + } else if (child[dir]->is_type(NODE_TYPE_CHARSET) && + child[!dir]->is_type(NODE_TYPE_CHAR)) { // [a] | b -> b | [a] Node *c = child[dir]; child[dir] = child[!dir]; @@ -344,7 +344,7 @@ static Node *alt_to_charsets(Node *t, int dir) static Node *basic_alt_factor(Node *t, int dir) { - if (!dynamic_cast<AltNode *>(t)) + if (!t->is_type(NODE_TYPE_ALT)) return t; if (t->child[dir]->eq(t->child[!dir])) { @@ -355,8 +355,8 @@ static Node *basic_alt_factor(Node *t, int dir) return tmp; } // (ab) | (ac) -> a(b|c) - if (dynamic_cast<CatNode *>(t->child[dir]) && - dynamic_cast<CatNode *>(t->child[!dir]) && + if (t->child[dir]->is_type(NODE_TYPE_CAT) && + t->child[!dir]->is_type(NODE_TYPE_CAT) && t->child[dir]->child[dir]->eq(t->child[!dir]->child[dir])) { // (ab) | (ac) -> a(b|c) Node *left = t->child[dir]; @@ -369,7 +369,7 @@ static Node *basic_alt_factor(Node *t, int dir) return left; } // a | (ab) -> a (E | b) -> a (b | E) - if (dynamic_cast<CatNode *>(t->child[!dir]) && + if (t->child[!dir]->is_type(NODE_TYPE_CAT) && t->child[dir]->eq(t->child[!dir]->child[dir])) { Node *c = t->child[!dir]; t->child[dir]->release(); @@ -379,7 +379,7 @@ static Node *basic_alt_factor(Node *t, int dir) return c; } // ab | (a) -> a (b | E) - if (dynamic_cast<CatNode *>(t->child[dir]) && + if (t->child[dir]->is_type(NODE_TYPE_CAT) && t->child[dir]->child[dir]->eq(t->child[!dir])) { Node *c = t->child[dir]; t->child[!dir]->release(); @@ -394,7 +394,7 @@ static Node *basic_alt_factor(Node *t, int dir) static Node *basic_simplify(Node *t, int dir) { - if (dynamic_cast<CatNode *>(t) && &epsnode == t->child[!dir]) { + if (t->is_type(NODE_TYPE_CAT) && &epsnode == t->child[!dir]) { // aE -> a Node *tmp = t->child[dir]; t->child[dir] = NULL; @@ -419,7 +419,7 @@ static Node *basic_simplify(Node *t, int dir) */ Node *simplify_tree_base(Node *t, int dir, bool &mod) { - if (dynamic_cast<ImportantNode *>(t)) + if (t->is_type(NODE_TYPE_IMPORTANT)) return t; for (int i = 0; i < 2; i++) { @@ -442,15 +442,15 @@ Node *simplify_tree_base(Node *t, int dir, bool &mod) } /* all tests after this must meet 2 alt node condition */ - if (!dynamic_cast<AltNode *>(t) || - !dynamic_cast<AltNode *>(t->child[!dir])) + if (!t->is_type(NODE_TYPE_ALT) || + !t->child[!dir]->is_type(NODE_TYPE_ALT)) break; // a | (a | b) -> (a | b) // a | (b | (c | a)) -> (b | (c | a)) Node *p = t; Node *i = t->child[!dir]; - for (; dynamic_cast<AltNode *>(i); p = i, i = i->child[!dir]) { + for (; i->is_type(NODE_TYPE_ALT); p = i, i = i->child[!dir]) { if (t->child[dir]->eq(i->child[dir])) { Node *tmp = t->child[!dir]; t->child[!dir] = NULL; @@ -475,19 +475,19 @@ Node *simplify_tree_base(Node *t, int dir, bool &mod) int count = 0; Node *subject = t->child[dir]; Node *a = subject; - if (dynamic_cast<CatNode *>(subject)) + if (subject->is_type(NODE_TYPE_CAT)) a = subject->child[dir]; for (pp = p = t, i = t->child[!dir]; - dynamic_cast<AltNode *>(i);) { - if ((dynamic_cast<CatNode *>(i->child[dir]) && + i->is_type(NODE_TYPE_ALT);) { + if ((i->child[dir]->is_type(NODE_TYPE_CAT) && a->eq(i->child[dir]->child[dir])) || (a->eq(i->child[dir]))) { // extract matching alt node p->child[!dir] = i->child[!dir]; i->child[!dir] = subject; subject = basic_simplify(i, dir); - if (dynamic_cast<CatNode *>(subject)) + if (subject->is_type(NODE_TYPE_CAT)) a = subject->child[dir]; else a = subject; @@ -502,7 +502,7 @@ Node *simplify_tree_base(Node *t, int dir, bool &mod) } // last altnode in chain check other dir as well - if ((dynamic_cast<CatNode *>(i) && + if ((i->is_type(NODE_TYPE_CAT) && a->eq(i->child[dir])) || (a->eq(i))) { count++; if (t == p) { @@ -528,7 +528,7 @@ int debug_tree(Node *t) { int nodes = 1; - if (!dynamic_cast<ImportantNode *>(t)) { + if (!t->is_type(NODE_TYPE_IMPORTANT)) { if (t->child[0]) nodes += debug_tree(t->child[0]); if (t->child[1]) @@ -539,30 +539,30 @@ int debug_tree(Node *t) static void count_tree_nodes(Node *t, struct node_counts *counts) { - if (dynamic_cast<AltNode *>(t)) { + if (t->is_type(NODE_TYPE_ALT)) { counts->alt++; count_tree_nodes(t->child[0], counts); count_tree_nodes(t->child[1], counts); - } else if (dynamic_cast<CatNode *>(t)) { + } else if (t->is_type(NODE_TYPE_CAT)) { counts->cat++; count_tree_nodes(t->child[0], counts); count_tree_nodes(t->child[1], counts); - } else if (dynamic_cast<PlusNode *>(t)) { + } else if (t->is_type(NODE_TYPE_PLUS)) { counts->plus++; count_tree_nodes(t->child[0], counts); - } else if (dynamic_cast<StarNode *>(t)) { + } else if (t->is_type(NODE_TYPE_STAR)) { counts->star++; count_tree_nodes(t->child[0], counts); - } else if (dynamic_cast<OptionalNode *>(t)) { + } else if (t->is_type(NODE_TYPE_OPTIONAL)) { counts->optional++; count_tree_nodes(t->child[0], counts); - } else if (dynamic_cast<CharNode *>(t)) { + } else if (t->is_type(NODE_TYPE_CHAR)) { counts->charnode++; - } else if (dynamic_cast<AnyCharNode *>(t)) { + } else if (t->is_type(NODE_TYPE_ANYCHAR)) { counts->any++; - } else if (dynamic_cast<CharSetNode *>(t)) { + } else if (t->is_type(NODE_TYPE_CHARSET)) { counts->charset++; - } else if (dynamic_cast<NotCharSetNode *>(t)) { + } else if (t->is_type(NODE_TYPE_NOTCHARSET)) { counts->notcharset++; } } @@ -635,7 +635,8 @@ Node *simplify_tree(Node *t, dfaflags_t flags) void flip_tree(Node *node) { for (depth_first_traversal i(node); i; i++) { - if (CatNode *cat = dynamic_cast<CatNode *>(*i)) { + if ((*i)->is_type(NODE_TYPE_CAT)) { + CatNode *cat = static_cast<CatNode *>(*i); swap(cat->child[0], cat->child[1]); } } diff --git a/parser/libapparmor_re/expr-tree.h b/parser/libapparmor_re/expr-tree.h index de73f854e..3530eb167 100644 --- a/parser/libapparmor_re/expr-tree.h +++ b/parser/libapparmor_re/expr-tree.h @@ -222,16 +222,43 @@ typedef struct Cases { ostream &operator<<(ostream &os, Node &node); +#define NODE_TYPE_NODE 0 +#define NODE_TYPE_INNER (1 << 0) +#define NODE_TYPE_ONECHILD (1 << 1) +#define NODE_TYPE_TWOCHILD (1 << 2) +#define NODE_TYPE_LEAF (1 << 3) +#define NODE_TYPE_EPS (1 << 4) +#define NODE_TYPE_IMPORTANT (1 << 5) +#define NODE_TYPE_C (1 << 6) +#define NODE_TYPE_CHAR (1 << 7) +#define NODE_TYPE_CHARSET (1 << 8) +#define NODE_TYPE_NOTCHARSET (1 << 9) +#define NODE_TYPE_ANYCHAR (1 << 10) +#define NODE_TYPE_STAR (1 << 11) +#define NODE_TYPE_OPTIONAL (1 << 12) +#define NODE_TYPE_PLUS (1 << 13) +#define NODE_TYPE_CAT (1 << 14) +#define NODE_TYPE_ALT (1 << 15) +#define NODE_TYPE_SHARED (1 << 16) +#define NODE_TYPE_ACCEPT (1 << 17) +#define NODE_TYPE_MATCHFLAG (1 << 18) +#define NODE_TYPE_EXACTMATCHFLAG (1 << 19) +#define NODE_TYPE_DENYMATCHFLAG (1 << 20) + /* An abstract node in the syntax tree. */ class Node { public: - Node(): nullable(false), label(0) { child[0] = child[1] = 0; } - Node(Node *left): nullable(false), label(0) + Node(): nullable(false), type_flags(NODE_TYPE_NODE), label(0) + { + child[0] = child[1] = 0; + } + Node(Node *left): nullable(false), type_flags(NODE_TYPE_NODE), label(0) { child[0] = left; child[1] = 0; } - Node(Node *left, Node *right): nullable(false), label(0) + Node(Node *left, Node *right): nullable(false), + type_flags(NODE_TYPE_NODE), label(0) { child[0] = left; child[1] = right; @@ -302,6 +329,13 @@ public: NodeSet firstpos, lastpos, followpos; /* child 0 is left, child 1 is right */ Node *child[2]; + /* + * Bitmap that stores supported pointer casts for the Node, composed + * by the NODE_TYPE_* flags. This is used by is_type() as a substitute + * of costly dynamic_cast calls. + */ + unsigned type_flags; + bool is_type(unsigned type) { return type_flags & type; } unsigned int label; /* unique number for debug etc */ /** @@ -315,25 +349,34 @@ public: class InnerNode: public Node { public: - InnerNode(): Node() { }; - InnerNode(Node *left): Node(left) { }; - InnerNode(Node *left, Node *right): Node(left, right) { }; + InnerNode(): Node() { type_flags |= NODE_TYPE_INNER; }; + InnerNode(Node *left): Node(left) { type_flags |= NODE_TYPE_INNER; }; + InnerNode(Node *left, Node *right): Node(left, right) + { + type_flags |= NODE_TYPE_INNER; + }; }; class OneChildNode: public InnerNode { public: - OneChildNode(Node *left): InnerNode(left) { }; + OneChildNode(Node *left): InnerNode(left) + { + type_flags |= NODE_TYPE_ONECHILD; + }; }; class TwoChildNode: public InnerNode { public: - TwoChildNode(Node *left, Node *right): InnerNode(left, right) { }; + TwoChildNode(Node *left, Node *right): InnerNode(left, right) + { + type_flags |= NODE_TYPE_TWOCHILD; + }; virtual int normalize_eps(int dir); }; class LeafNode: public Node { public: - LeafNode(): Node() { }; + LeafNode(): Node() { type_flags |= NODE_TYPE_LEAF; }; virtual void normalize(int dir __attribute__((unused))) { return; } }; @@ -342,6 +385,7 @@ class EpsNode: public LeafNode { public: EpsNode(): LeafNode() { + type_flags |= NODE_TYPE_EPS; nullable = true; label = 0; } @@ -356,7 +400,7 @@ public: void compute_lastpos() { } int eq(Node *other) { - if (dynamic_cast<EpsNode *>(other)) + if (other->is_type(NODE_TYPE_EPS)) return 1; return 0; } @@ -373,7 +417,7 @@ public: */ class ImportantNode: public LeafNode { public: - ImportantNode(): LeafNode() { } + ImportantNode(): LeafNode() { type_flags |= NODE_TYPE_IMPORTANT; } void compute_firstpos() { firstpos.insert(this); } void compute_lastpos() { lastpos.insert(this); } virtual void follow(Cases &cases) = 0; @@ -386,7 +430,7 @@ public: */ class CNode: public ImportantNode { public: - CNode(): ImportantNode() { } + CNode(): ImportantNode() { type_flags |= NODE_TYPE_C; } int is_accept(void) { return false; } int is_postprocess(void) { return false; } }; @@ -394,7 +438,7 @@ public: /* Match one specific character (/c/). */ class CharNode: public CNode { public: - CharNode(transchar c): c(c) { } + CharNode(transchar c): c(c) { type_flags |= NODE_TYPE_CHAR; } void follow(Cases &cases) { NodeSet **x = &cases.cases[c]; @@ -408,8 +452,8 @@ public: } int eq(Node *other) { - CharNode *o = dynamic_cast<CharNode *>(other); - if (o) { + if (other->is_type(NODE_TYPE_CHAR)) { + CharNode *o = static_cast<CharNode *>(other); return c == o->c; } return 0; @@ -439,7 +483,10 @@ public: /* Match a set of characters (/[abc]/). */ class CharSetNode: public CNode { public: - CharSetNode(Chars &chars): chars(chars) { } + CharSetNode(Chars &chars): chars(chars) + { + type_flags |= NODE_TYPE_CHARSET; + } void follow(Cases &cases) { for (Chars::iterator i = chars.begin(); i != chars.end(); i++) { @@ -455,8 +502,11 @@ public: } int eq(Node *other) { - CharSetNode *o = dynamic_cast<CharSetNode *>(other); - if (!o || chars.size() != o->chars.size()) + if (!other->is_type(NODE_TYPE_CHARSET)) + return 0; + + CharSetNode *o = static_cast<CharSetNode *>(other); + if (chars.size() != o->chars.size()) return 0; for (Chars::iterator i = chars.begin(), j = o->chars.begin(); @@ -498,7 +548,10 @@ public: /* Match all except one character (/[^abc]/). */ class NotCharSetNode: public CNode { public: - NotCharSetNode(Chars &chars): chars(chars) { } + NotCharSetNode(Chars &chars): chars(chars) + { + type_flags |= NODE_TYPE_NOTCHARSET; + } void follow(Cases &cases) { if (!cases.otherwise) @@ -522,8 +575,11 @@ public: } int eq(Node *other) { - NotCharSetNode *o = dynamic_cast<NotCharSetNode *>(other); - if (!o || chars.size() != o->chars.size()) + if (!other->is_type(NODE_TYPE_NOTCHARSET)) + return 0; + + NotCharSetNode *o = static_cast<NotCharSetNode *>(other); + if (chars.size() != o->chars.size()) return 0; for (Chars::iterator i = chars.begin(), j = o->chars.begin(); @@ -565,7 +621,7 @@ public: /* Match any character (/./). */ class AnyCharNode: public CNode { public: - AnyCharNode() { } + AnyCharNode() { type_flags |= NODE_TYPE_ANYCHAR; } void follow(Cases &cases) { if (!cases.otherwise) @@ -579,7 +635,7 @@ public: } int eq(Node *other) { - if (dynamic_cast<AnyCharNode *>(other)) + if (other->is_type(NODE_TYPE_ANYCHAR)) return 1; return 0; } @@ -589,7 +645,11 @@ public: /* Match a node zero or more times. (This is a unary operator.) */ class StarNode: public OneChildNode { public: - StarNode(Node *left): OneChildNode(left) { nullable = true; } + StarNode(Node *left): OneChildNode(left) + { + type_flags |= NODE_TYPE_STAR; + nullable = true; + } void compute_firstpos() { firstpos = child[0]->firstpos; } void compute_lastpos() { lastpos = child[0]->lastpos; } void compute_followpos() @@ -601,7 +661,7 @@ public: } int eq(Node *other) { - if (dynamic_cast<StarNode *>(other)) + if (other->is_type(NODE_TYPE_STAR)) return child[0]->eq(other->child[0]); return 0; } @@ -618,12 +678,16 @@ public: /* Match a node zero or one times. */ class OptionalNode: public OneChildNode { public: - OptionalNode(Node *left): OneChildNode(left) { nullable = true; } + OptionalNode(Node *left): OneChildNode(left) + { + type_flags |= NODE_TYPE_OPTIONAL; + nullable = true; + } void compute_firstpos() { firstpos = child[0]->firstpos; } void compute_lastpos() { lastpos = child[0]->lastpos; } int eq(Node *other) { - if (dynamic_cast<OptionalNode *>(other)) + if (other->is_type(NODE_TYPE_OPTIONAL)) return child[0]->eq(other->child[0]); return 0; } @@ -638,7 +702,9 @@ public: /* Match a node one or more times. (This is a unary operator.) */ class PlusNode: public OneChildNode { public: - PlusNode(Node *left): OneChildNode(left) { + PlusNode(Node *left): OneChildNode(left) + { + type_flags |= NODE_TYPE_PLUS; } void compute_nullable() { nullable = child[0]->nullable; } void compute_firstpos() { firstpos = child[0]->firstpos; } @@ -651,7 +717,7 @@ public: } } int eq(Node *other) { - if (dynamic_cast<PlusNode *>(other)) + if (other->is_type(NODE_TYPE_PLUS)) return child[0]->eq(other->child[0]); return 0; } @@ -667,7 +733,10 @@ public: /* Match a pair of consecutive nodes. */ class CatNode: public TwoChildNode { public: - CatNode(Node *left, Node *right): TwoChildNode(left, right) { } + CatNode(Node *left, Node *right): TwoChildNode(left, right) + { + type_flags |= NODE_TYPE_CAT; + } void compute_nullable() { nullable = child[0]->nullable && child[1]->nullable; @@ -695,7 +764,7 @@ public: } int eq(Node *other) { - if (dynamic_cast<CatNode *>(other)) { + if (other->is_type(NODE_TYPE_CAT)) { if (!child[0]->eq(other->child[0])) return 0; return child[1]->eq(other->child[1]); @@ -730,7 +799,10 @@ public: /* Match one of two alternative nodes. */ class AltNode: public TwoChildNode { public: - AltNode(Node *left, Node *right): TwoChildNode(left, right) { } + AltNode(Node *left, Node *right): TwoChildNode(left, right) + { + type_flags |= NODE_TYPE_ALT; + } void compute_nullable() { nullable = child[0]->nullable || child[1]->nullable; @@ -745,7 +817,7 @@ public: } int eq(Node *other) { - if (dynamic_cast<AltNode *>(other)) { + if (other->is_type(NODE_TYPE_ALT)) { if (!child[0]->eq(other->child[0])) return 0; return child[1]->eq(other->child[1]); @@ -780,7 +852,10 @@ public: class SharedNode: public ImportantNode { public: - SharedNode() { } + SharedNode() + { + type_flags |= NODE_TYPE_SHARED; + } void release(void) { /* don't delete SharedNodes via release as they are shared, and @@ -803,14 +878,17 @@ public: */ class AcceptNode: public SharedNode { public: - AcceptNode() { } + AcceptNode() { type_flags |= NODE_TYPE_ACCEPT; } int is_accept(void) { return true; } int is_postprocess(void) { return false; } }; class MatchFlag: public AcceptNode { public: - MatchFlag(uint32_t flag, uint32_t audit): flag(flag), audit(audit) { } + MatchFlag(uint32_t flag, uint32_t audit): flag(flag), audit(audit) + { + type_flags |= NODE_TYPE_MATCHFLAG; + } ostream &dump(ostream &os) { return os << "< 0x" << hex << flag << '>'; } uint32_t flag; @@ -819,12 +897,18 @@ public: class ExactMatchFlag: public MatchFlag { public: - ExactMatchFlag(uint32_t flag, uint32_t audit): MatchFlag(flag, audit) {} + ExactMatchFlag(uint32_t flag, uint32_t audit): MatchFlag(flag, audit) + { + type_flags |= NODE_TYPE_EXACTMATCHFLAG; + } }; class DenyMatchFlag: public MatchFlag { public: - DenyMatchFlag(uint32_t flag, uint32_t quiet): MatchFlag(flag, quiet) {} + DenyMatchFlag(uint32_t flag, uint32_t quiet): MatchFlag(flag, quiet) + { + type_flags |= NODE_TYPE_DENYMATCHFLAG; + } }; /* Traverse the syntax tree depth-first in an iterator-like manner. */ @@ -833,7 +917,7 @@ class depth_first_traversal { void push_left(Node *node) { pos.push(node); - while (dynamic_cast<InnerNode *>(node)) { + while (node->is_type(NODE_TYPE_INNER)) { pos.push(node->child[0]); node = node->child[0]; } diff --git a/parser/libapparmor_re/hfa.cc b/parser/libapparmor_re/hfa.cc index 6b0109133..e1ef1803b 100644 --- a/parser/libapparmor_re/hfa.cc +++ b/parser/libapparmor_re/hfa.cc @@ -651,13 +651,13 @@ void DFA::minimize(dfaflags_t flags) list<Partition *> partitions; /* Set up the initial partitions - * minimium of - 1 non accepting, and 1 accepting + * minimum of - 1 non accepting, and 1 accepting * if trans hashing is used the accepting and non-accepting partitions * can be further split based on the number and type of transitions * a state makes. * If permission hashing is enabled the accepting partitions can * be further divided by permissions. This can result in not - * obtaining a truely minimized dfa but comes close, and can speedup + * obtaining a truly minimized dfa but comes close, and can speedup * minimization. */ int accept_count = 0; @@ -753,7 +753,7 @@ void DFA::minimize(dfaflags_t flags) /* Remap the dfa so it uses the representative states * Use the first state of a partition as the representative state - * At this point all states with in a partion have transitions + * At this point all states with in a partition have transitions * to states within the same partitions, however this can slow * down compressed dfa compression as there are more states, */ @@ -813,7 +813,7 @@ void DFA::minimize(dfaflags_t flags) } /* Now that the states have been remapped, remove all states - * that are not the representive states for their partition, they + * that are not the representative states for their partition, they * will have a label == -1 */ for (Partition::iterator i = states.begin(); i != states.end();) { @@ -875,7 +875,7 @@ static int diff_partition(State *state, Partition &part, int max_range, int uppe /** * diff_encode - compress dfa by differentially encoding state transitions - * @dfa_flags: flags controling dfa creation + * @dfa_flags: flags controlling dfa creation * * This function reduces the number of transitions that need to be stored * by encoding transitions as the difference between the state and a @@ -889,7 +889,7 @@ static int diff_partition(State *state, Partition &part, int max_range, int uppe * - The number of state transitions needed to match an input of length * m will be 2m * - * To guarentee this the ordering and distance calculation is done in the + * To guarantee this the ordering and distance calculation is done in the * following manner. * - A DAG of the DFA is created starting with the start state(s). * - A state can only be relative (have a differential encoding) to @@ -1352,17 +1352,18 @@ int accept_perms(NodeSet *state, perms_t &perms, bool filedfa) return error; for (NodeSet::iterator i = state->begin(); i != state->end(); i++) { - MatchFlag *match; - if (!(match = dynamic_cast<MatchFlag *>(*i))) + if (!(*i)->is_type(NODE_TYPE_MATCHFLAG)) continue; - if (dynamic_cast<ExactMatchFlag *>(match)) { + + MatchFlag *match = static_cast<MatchFlag *>(*i); + if (match->is_type(NODE_TYPE_EXACTMATCHFLAG)) { /* exact match only ever happens with x */ if (filedfa && !is_merged_x_consistent(exact_match_allow, match->flag)) error = 1;; exact_match_allow |= match->flag; exact_audit |= match->audit; - } else if (dynamic_cast<DenyMatchFlag *>(match)) { + } else if (match->is_type(NODE_TYPE_DENYMATCHFLAG)) { perms.deny |= match->flag; perms.quiet |= match->audit; } else { diff --git a/parser/libapparmor_re/hfa.h b/parser/libapparmor_re/hfa.h index 3ad7aaaa4..451d74659 100644 --- a/parser/libapparmor_re/hfa.h +++ b/parser/libapparmor_re/hfa.h @@ -189,7 +189,7 @@ struct DiffDag { * accept: the accept permissions for the state * trans: set of transitions from this state * otherwise: the default state for transitions not in @trans - * parition: Is a temporary work variable used during dfa minimization. + * partition: Is a temporary work variable used during dfa minimization. * it can be replaced with a map, but that is slower and uses more * memory. * proto: Is a temporary work variable used during dfa creation. It can diff --git a/parser/libapparmor_re/parse.y b/parser/libapparmor_re/parse.y index 843a5090c..3006880b6 100644 --- a/parser/libapparmor_re/parse.y +++ b/parser/libapparmor_re/parse.y @@ -76,7 +76,7 @@ static inline Chars* insert_char_range(Chars* cset, transchar a, transchar b) %% /* FIXME: Does not parse "[--]", "[---]", "[^^-x]". I don't actually know - which precise grammer Perl regexs use, and rediscovering that + which precise grammar Perl regexs use, and rediscovering that is proving to be painful. */ regex : /* empty */ { *root = $$ = &epsnode; } diff --git a/parser/mount.cc b/parser/mount.cc index 6fdb213be..30de90e0d 100644 --- a/parser/mount.cc +++ b/parser/mount.cc @@ -98,6 +98,9 @@ * nomand * #define MS_DIRSYNC 128 Directory modifications are synchronous * dirsync + * #define MS_NOSYMFOLLOW 256 Do not follow symlinks + * symfollow + * nosymfollow * #define MS_NOATIME 1024 Do not update access times * noatime * atime @@ -139,6 +142,9 @@ * #define MS_STRICTATIME (1<<24) Always perform atime updates * strictatime * nostrictatime + * #define MS_LAZYTIME (1<<25) Update the on-disk [acm]times lazily + * lazytime + * nolazytime * #define MS_NOSEC (1<<28) * #define MS_BORN (1<<29) * #define MS_ACTIVE (1<<30) @@ -206,7 +212,7 @@ * AppArmor mount rule encoding * * TODO: - * add semantic checking of options against specified filesytem types + * add semantic checking of options against specified filesystem types * to catch mount options that can't be covered. * * @@ -246,6 +252,8 @@ static struct mnt_keyword_table mnt_opts_table[] = { {"mand", MS_MAND, 0}, {"nomand", 0, MS_MAND}, {"dirsync", MS_DIRSYNC, 0}, + {"symfollow", 0, MS_NOSYMFOLLOW}, + {"nosymfollow", MS_NOSYMFOLLOW, 0}, {"atime", 0, MS_NOATIME}, {"noatime", MS_NOATIME, 0}, {"diratime", 0, MS_NODIRATIME}, @@ -283,6 +291,9 @@ static struct mnt_keyword_table mnt_opts_table[] = { {"iversion", MS_IVERSION, 0}, {"noiversion", 0, MS_IVERSION}, {"strictatime", MS_STRICTATIME, 0}, + {"nostrictatime", 0, MS_STRICTATIME}, + {"lazytime", MS_LAZYTIME, 0}, + {"nolazytime", 0, MS_LAZYTIME}, {"user", 0, (unsigned int) MS_NOUSER}, {"nouser", (unsigned int) MS_NOUSER, 0}, @@ -298,6 +309,22 @@ static struct mnt_keyword_table mnt_conds_table[] = { {NULL, 0, 0} }; +static ostream &dump_flags(ostream &os, + pair <unsigned int, unsigned int> flags) +{ + for (int i = 0; mnt_opts_table[i].keyword; i++) { + if ((flags.first & mnt_opts_table[i].set) || + (flags.second & mnt_opts_table[i].clear)) + os << mnt_opts_table[i].keyword; + } + return os; +} + +ostream &operator<<(ostream &os, pair<unsigned int, unsigned int> flags) +{ + return dump_flags(os, flags); +} + static int find_mnt_keyword(struct mnt_keyword_table *table, const char *name) { int i; @@ -320,7 +347,7 @@ int is_valid_mnt_cond(const char *name, int src) static unsigned int extract_flags(struct value_list **list, unsigned int *inv) { - unsigned int flags = 0; + unsigned int flags = 0, invflags = 0; *inv = 0; struct value_list *entry, *tmp, *prev = NULL; @@ -329,11 +356,11 @@ static unsigned int extract_flags(struct value_list **list, unsigned int *inv) i = find_mnt_keyword(mnt_opts_table, entry->value); if (i != -1) { flags |= mnt_opts_table[i].set; - *inv |= mnt_opts_table[i].clear; + invflags |= mnt_opts_table[i].clear; PDEBUG(" extracting mount flag %s req: 0x%x inv: 0x%x" " => req: 0x%x inv: 0x%x\n", entry->value, mnt_opts_table[i].set, - mnt_opts_table[i].clear, flags, *inv); + mnt_opts_table[i].clear, flags, invflags); if (prev) prev->next = tmp; if (entry == *list) @@ -344,9 +371,27 @@ static unsigned int extract_flags(struct value_list **list, unsigned int *inv) prev = entry; } + if (inv) + *inv = invflags; + return flags; } +static bool conflicting_flags(unsigned int flags, unsigned int inv) +{ + if (flags & inv) { + for (int i = 0; i < 31; i++) { + unsigned int mask = 1 << i; + if ((flags & inv) & mask) { + cerr << "conflicting flag values = " + << flags << ", " << inv << "\n"; + } + } + return true; + } + return false; +} + static struct value_list *extract_fstype(struct cond_entry **conds) { struct value_list *list = NULL; @@ -369,22 +414,19 @@ static struct value_list *extract_fstype(struct cond_entry **conds) return list; } -static struct value_list *extract_options(struct cond_entry **conds, int eq) +static struct cond_entry *extract_options(struct cond_entry **conds, int eq) { - struct value_list *list = NULL; - - struct cond_entry *entry, *tmp, *prev = NULL; + struct cond_entry *list = NULL, *entry, *tmp, *prev = NULL; list_for_each_safe(*conds, entry, tmp) { if ((strcmp(entry->name, "options") == 0 || strcmp(entry->name, "option") == 0) && entry->eq == eq) { list_remove_at(*conds, prev, entry); - PDEBUG(" extracting option %s\n", entry->name); - list_append(entry->vals, list); - list = entry->vals; - entry->vals = NULL; - free_cond_entry(entry); + PDEBUG(" extracting %s %s\n", entry->name, entry->eq ? +"=" : "in"); + list_append(entry, list); + list = entry; } else prev = entry; } @@ -392,60 +434,129 @@ static struct value_list *extract_options(struct cond_entry **conds, int eq) return list; } +static void perror_conds(const char *rule, struct cond_entry *conds) +{ + struct cond_entry *entry; + + list_for_each(conds, entry) { + PERROR( "unsupported %s condition '%s%s(...)'\n", rule, entry->name, entry->eq ? "=" : " in "); + } +} + +static void perror_vals(const char *rule, struct value_list *vals) +{ + struct value_list *entry; + + list_for_each(vals, entry) { + PERROR( "unsupported %s value '%s'\n", rule, entry->value); + } +} + +static void process_one_option(struct cond_entry *&opts, unsigned int &flags, + unsigned int &inv_flags) +{ + struct cond_entry *entry; + struct value_list *vals; + + entry = list_pop(opts); + vals = entry->vals; + entry->vals = NULL; + /* fail if there are any unknown optional flags */ + if (opts) { + PERROR(" unsupported multiple 'mount options %s(...)'\n", entry->eq ? "=" : " in "); + exit(1); + } + free_cond_entry(entry); + + flags = extract_flags(&vals, &inv_flags); + if (vals) { + perror_vals("mount option", vals); + exit(1); + } +} + mnt_rule::mnt_rule(struct cond_entry *src_conds, char *device_p, struct cond_entry *dst_conds unused, char *mnt_point_p, int allow_p): mnt_point(mnt_point_p), device(device_p), trans(NULL), opts(NULL), - flags(0), inv_flags(0), audit(0), deny(0) + flagsv(0), opt_flagsv(0), audit(0), deny(0) { /* FIXME: dst_conds are ignored atm */ dev_type = extract_fstype(&src_conds); if (src_conds) { - struct value_list *list = extract_options(&src_conds, 0); - - opts = extract_options(&src_conds, 1); - if (opts) - flags = extract_flags(&opts, &inv_flags); - - if (list) { - unsigned int tmpflags, tmpinv_flags = 0; + /* move options in () to local list */ + struct cond_entry *opts_in = extract_options(&src_conds, 0); + + if (opts_in) { + unsigned int tmpflags = 0, tmpinv_flags = 0; + struct cond_entry *entry; + + while ((entry = list_pop(opts_in))) { + process_one_option(entry, tmpflags, + tmpinv_flags); + /* optional flags if set/clear mean the same + * thing and can be represented by a single + * bitset, also there is no need to check for + * conflicting flags when they are optional + */ + opt_flagsv.push_back(tmpflags | tmpinv_flags); + } + } - tmpflags = extract_flags(&list, &tmpinv_flags); - /* these flags are optional so set both */ - tmpflags |= tmpinv_flags; - tmpinv_flags |= tmpflags; + /* move options=() to opts list */ + struct cond_entry *opts_eq = extract_options(&src_conds, 1); + if (opts_eq) { + unsigned int tmpflags = 0, tmpinv_flags = 0; + struct cond_entry *entry; + + while ((entry = list_pop(opts_eq))) { + process_one_option(entry, tmpflags, + tmpinv_flags); + /* throw away tmpinv_flags, only needed in + * consistancy check + */ + if (allow_p & AA_DUMMY_REMOUNT) + tmpflags |= MS_REMOUNT; + + if (conflicting_flags(tmpflags, tmpinv_flags)) { + PERROR("conflicting flags in the rule\n"); + exit(1); + } + + flagsv.push_back(tmpflags); + } + } - flags |= tmpflags; - inv_flags |= tmpinv_flags; + if (src_conds) { + perror_conds("mount", src_conds); + exit(1); + } + } - if (opts) - list_append(opts, list); - else if (list) - opts = list; + if (!(flagsv.size() + opt_flagsv.size())) { + /* no flag options, and not remount, allow everything */ + if (allow_p & AA_DUMMY_REMOUNT) { + flagsv.push_back(MS_REMOUNT); + opt_flagsv.push_back(MS_REMOUNT_FLAGS & ~MS_REMOUNT); + } else { + flagsv.push_back(MS_ALL_FLAGS); + opt_flagsv.push_back(MS_ALL_FLAGS); } + } else if (!(flagsv.size())) { + /* no flags but opts set */ + if (allow_p & AA_DUMMY_REMOUNT) + flagsv.push_back(MS_REMOUNT); + else + flagsv.push_back(0); + } else if (!(opt_flagsv.size())) { + opt_flagsv.push_back(0); } if (allow_p & AA_DUMMY_REMOUNT) { allow_p = AA_MAY_MOUNT; - flags |= MS_REMOUNT; - inv_flags = 0; - } else if (!(flags | inv_flags)) { - /* no flag options, and not remount, allow everything */ - flags = MS_ALL_FLAGS; - inv_flags = MS_ALL_FLAGS; } - allow = allow_p; - - if (src_conds) { - PERROR(" unsupported mount conditions\n"); - exit(1); - } - if (opts) { - PERROR(" unsupported mount options\n"); - exit(1); - } } ostream &mnt_rule::dump(ostream &os) @@ -457,9 +568,13 @@ ostream &mnt_rule::dump(ostream &os) else if (allow & AA_MAY_PIVOTROOT) os << "pivotroot"; else - os << "error: unknonwn mount perm"; + os << "error: unknown mount perm"; + + for (unsigned int i = 0; i < flagsv.size(); i++) + os << " flags=(0x" << hex << flagsv[i] << ")"; + for (unsigned int i = 0; i < opt_flagsv.size(); i++) + os << " flags in (0x" << hex << opt_flagsv[i] << ")"; - os << " (0x" << hex << flags << " - 0x" << inv_flags << ") "; if (dev_type) { os << " type="; print_value_list(dev_type); @@ -515,7 +630,7 @@ int mnt_rule::expand_variables(void) } static int build_mnt_flags(char *buffer, int size, unsigned int flags, - unsigned int inv_flags) + unsigned int opt_flags) { char *p = buffer; int i, len = 0; @@ -528,7 +643,7 @@ static int build_mnt_flags(char *buffer, int size, unsigned int flags, return TRUE; } for (i = 0; i <= 31; ++i) { - if ((flags & inv_flags) & (1 << i)) + if ((opt_flags) & (1 << i)) len = snprintf(p, size, "(\\x%02x|)", i + 1); else if (flags & (1 << i)) len = snprintf(p, size, "\\x%02x", i + 1); @@ -583,7 +698,9 @@ void mnt_rule::warn_once(const char *name) rule_t::warn_once(name, "mount rules not enforce"); } -int mnt_rule::gen_policy_re(Profile &prof) + +int mnt_rule::gen_policy_remount(Profile &prof, int &count, + unsigned int flags, unsigned int opt_flags) { std::string mntbuf; std::string devbuf; @@ -592,215 +709,335 @@ int mnt_rule::gen_policy_re(Profile &prof) std::string optsbuf; char class_mount_hdr[64]; const char *vec[5]; - int count = 0; - unsigned int tmpflags, tmpinv_flags; - - if (!features_supports_mount) { - warn_once(prof.name); - return RULE_NOT_SUPPORTED; - } + int tmpallow; sprintf(class_mount_hdr, "\\x%02x", AA_CLASS_MOUNT); - /* a single mount rule may result in multiple matching rules being - * created in the backend to cover all the possible choices - */ - - if ((allow & AA_MAY_MOUNT) && (flags & MS_REMOUNT) - && !device && !dev_type) { - int tmpallow; - /* remount can't be conditional on device and type */ - /* rule class single byte header */ - mntbuf.assign(class_mount_hdr); - if (mnt_point) { - /* both device && mnt_point or just mnt_point */ - if (!convert_entry(mntbuf, mnt_point)) - goto fail; - vec[0] = mntbuf.c_str(); - } else { - if (!convert_entry(mntbuf, device)) - goto fail; - vec[0] = mntbuf.c_str(); - } - /* skip device */ - vec[1] = default_match_pattern; - /* skip type */ - vec[2] = default_match_pattern; - - tmpflags = flags; - tmpinv_flags = inv_flags; - if (tmpflags != MS_ALL_FLAGS) - tmpflags &= MS_REMOUNT_FLAGS; - if (tmpinv_flags != MS_ALL_FLAGS) - tmpflags &= MS_REMOUNT_FLAGS; - if (!build_mnt_flags(flagsbuf, PATH_MAX, tmpflags, tmpinv_flags)) - goto fail; - vec[3] = flagsbuf; - - if (opts) - tmpallow = AA_MATCH_CONT; - else - tmpallow = allow; - - /* rule for match without required data || data MATCH_CONT */ - if (!prof.policy.rules->add_rule_vec(deny, tmpallow, - audit | AA_AUDIT_MNT_DATA, 4, - vec, dfaflags, false)) - goto fail; - count++; - - if (opts) { - /* rule with data match required */ - optsbuf.clear(); - if (!build_mnt_opts(optsbuf, opts)) - goto fail; - vec[4] = optsbuf.c_str(); - if (!prof.policy.rules->add_rule_vec(deny, allow, - audit | AA_AUDIT_MNT_DATA, - 5, vec, dfaflags, false)) - goto fail; - count++; - } - } - if ((allow & AA_MAY_MOUNT) && (flags & MS_BIND) - && !dev_type && !opts) { - /* bind mount rules can't be conditional on dev_type or data */ - /* rule class single byte header */ - mntbuf.assign(class_mount_hdr); + /* remount can't be conditional on device and type */ + /* rule class single byte header */ + mntbuf.assign(class_mount_hdr); + if (mnt_point) { + /* both device && mnt_point or just mnt_point */ if (!convert_entry(mntbuf, mnt_point)) goto fail; vec[0] = mntbuf.c_str(); - if (!clear_and_convert_entry(devbuf, device)) - goto fail; - vec[1] = devbuf.c_str(); - /* skip type */ - vec[2] = default_match_pattern; - - tmpflags = flags; - tmpinv_flags = inv_flags; - if (tmpflags != MS_ALL_FLAGS) - tmpflags &= MS_BIND_FLAGS; - if (tmpinv_flags != MS_ALL_FLAGS) - tmpflags &= MS_BIND_FLAGS; - if (!build_mnt_flags(flagsbuf, PATH_MAX, tmpflags, tmpinv_flags)) - goto fail; - vec[3] = flagsbuf; - if (!prof.policy.rules->add_rule_vec(deny, allow, audit, 4, vec, - dfaflags, false)) - goto fail; - count++; - } - if ((allow & AA_MAY_MOUNT) && - (flags & (MS_UNBINDABLE | MS_PRIVATE | MS_SLAVE | MS_SHARED)) - && !device && !dev_type && !opts) { - /* change type base rules can not be conditional on device, - * device type or data - */ - /* rule class single byte header */ - mntbuf.assign(class_mount_hdr); - if (!convert_entry(mntbuf, mnt_point)) + } else { + if (!convert_entry(mntbuf, device)) goto fail; vec[0] = mntbuf.c_str(); - /* skip device and type */ - vec[1] = default_match_pattern; - vec[2] = default_match_pattern; - - tmpflags = flags; - tmpinv_flags = inv_flags; - if (tmpflags != MS_ALL_FLAGS) - tmpflags &= MS_MAKE_FLAGS; - if (tmpinv_flags != MS_ALL_FLAGS) - tmpflags &= MS_MAKE_FLAGS; - if (!build_mnt_flags(flagsbuf, PATH_MAX, tmpflags, tmpinv_flags)) + } + /* skip device */ + vec[1] = default_match_pattern; + /* skip type */ + vec[2] = default_match_pattern; + + if (!build_mnt_flags(flagsbuf, PATH_MAX, flags & MS_REMOUNT_FLAGS, + opt_flags & MS_REMOUNT_FLAGS)) + goto fail; + + vec[3] = flagsbuf; + + if (opts) + tmpallow = AA_MATCH_CONT; + else + tmpallow = allow; + + /* rule for match without required data || data MATCH_CONT */ + if (!prof.policy.rules->add_rule_vec(deny, tmpallow, + audit | AA_AUDIT_MNT_DATA, 4, + vec, dfaflags, false)) + goto fail; + count++; + + if (opts) { + /* rule with data match required */ + optsbuf.clear(); + if (!build_mnt_opts(optsbuf, opts)) goto fail; - vec[3] = flagsbuf; - if (!prof.policy.rules->add_rule_vec(deny, allow, audit, 4, vec, - dfaflags, false)) + vec[4] = optsbuf.c_str(); + if (!prof.policy.rules->add_rule_vec(deny, allow, + audit | AA_AUDIT_MNT_DATA, + 5, vec, dfaflags, false)) goto fail; count++; } - if ((allow & AA_MAY_MOUNT) && (flags & MS_MOVE) - && !dev_type && !opts) { - /* mount move rules can not be conditional on dev_type, - * or data - */ - /* rule class single byte header */ - mntbuf.assign(class_mount_hdr); - if (!convert_entry(mntbuf, mnt_point)) - goto fail; - vec[0] = mntbuf.c_str(); - if (!clear_and_convert_entry(devbuf, device)) - goto fail; - vec[1] = devbuf.c_str(); - /* skip type */ - vec[2] = default_match_pattern; - - tmpflags = flags; - tmpinv_flags = inv_flags; - if (tmpflags != MS_ALL_FLAGS) - tmpflags &= MS_MOVE_FLAGS; - if (tmpinv_flags != MS_ALL_FLAGS) - tmpflags &= MS_MOVE_FLAGS; - if (!build_mnt_flags(flagsbuf, PATH_MAX, tmpflags, tmpinv_flags)) + + return RULE_OK; + +fail: + return RULE_ERROR; +} + +int mnt_rule::gen_policy_bind_mount(Profile &prof, int &count, + unsigned int flags, unsigned int opt_flags) +{ + std::string mntbuf; + std::string devbuf; + std::string typebuf; + char flagsbuf[PATH_MAX + 3]; + std::string optsbuf; + char class_mount_hdr[64]; + const char *vec[5]; + + sprintf(class_mount_hdr, "\\x%02x", AA_CLASS_MOUNT); + + /* bind mount rules can't be conditional on dev_type or data */ + /* rule class single byte header */ + mntbuf.assign(class_mount_hdr); + if (!convert_entry(mntbuf, mnt_point)) + goto fail; + vec[0] = mntbuf.c_str(); + if (!clear_and_convert_entry(devbuf, device)) + goto fail; + vec[1] = devbuf.c_str(); + /* skip type */ + vec[2] = default_match_pattern; + + if (!build_mnt_flags(flagsbuf, PATH_MAX, flags & MS_BIND_FLAGS, + opt_flags & MS_BIND_FLAGS)) + goto fail; + vec[3] = flagsbuf; + if (!prof.policy.rules->add_rule_vec(deny, allow, audit, 4, vec, + dfaflags, false)) + goto fail; + count++; + + return RULE_OK; + +fail: + return RULE_ERROR; +} + +int mnt_rule::gen_policy_change_mount_type(Profile &prof, int &count, + unsigned int flags, + unsigned int opt_flags) +{ + std::string mntbuf; + std::string devbuf; + std::string typebuf; + char flagsbuf[PATH_MAX + 3]; + std::string optsbuf; + char class_mount_hdr[64]; + const char *vec[5]; + char *mountpoint = mnt_point; + + sprintf(class_mount_hdr, "\\x%02x", AA_CLASS_MOUNT); + + /* change type base rules can specify the mount point by using + * the parser token position reserved to device. that's why if + * the mount point is not specified, we use device in its + * place. this is a deprecated behavior. + * + * change type base rules can not be conditional on device + * (source), device type or data + */ + /* rule class single byte header */ + mntbuf.assign(class_mount_hdr); + if (flags && flags != MS_ALL_FLAGS && device && mnt_point) { + PERROR("source and mount point cannot be used at the " + "same time for propagation type flags"); + goto fail; + } else if (device && !mnt_point) { + mountpoint = device; + } + if (!convert_entry(mntbuf, mountpoint)) + goto fail; + vec[0] = mntbuf.c_str(); + /* skip device and type */ + vec[1] = default_match_pattern; + vec[2] = default_match_pattern; + + if (!build_mnt_flags(flagsbuf, PATH_MAX, flags & MS_MAKE_FLAGS, + opt_flags & MS_MAKE_FLAGS)) + goto fail; + vec[3] = flagsbuf; + if (!prof.policy.rules->add_rule_vec(deny, allow, audit, 4, vec, + dfaflags, false)) + goto fail; + count++; + + return RULE_OK; + +fail: + return RULE_ERROR; +} + +int mnt_rule::gen_policy_move_mount(Profile &prof, int &count, + unsigned int flags, unsigned int opt_flags) +{ + std::string mntbuf; + std::string devbuf; + std::string typebuf; + char flagsbuf[PATH_MAX + 3]; + std::string optsbuf; + char class_mount_hdr[64]; + const char *vec[5]; + + sprintf(class_mount_hdr, "\\x%02x", AA_CLASS_MOUNT); + + /* mount move rules can not be conditional on dev_type, + * or data + */ + /* rule class single byte header */ + mntbuf.assign(class_mount_hdr); + if (!convert_entry(mntbuf, mnt_point)) + goto fail; + vec[0] = mntbuf.c_str(); + if (!clear_and_convert_entry(devbuf, device)) + goto fail; + vec[1] = devbuf.c_str(); + /* skip type */ + vec[2] = default_match_pattern; + + if (!build_mnt_flags(flagsbuf, PATH_MAX, flags & MS_MOVE_FLAGS, + opt_flags & MS_MOVE_FLAGS)) + goto fail; + vec[3] = flagsbuf; + if (!prof.policy.rules->add_rule_vec(deny, allow, audit, 4, vec, + dfaflags, false)) + goto fail; + count++; + + return RULE_OK; + +fail: + return RULE_ERROR; +} + +int mnt_rule::gen_policy_new_mount(Profile &prof, int &count, + unsigned int flags, unsigned int opt_flags) +{ + std::string mntbuf; + std::string devbuf; + std::string typebuf; + char flagsbuf[PATH_MAX + 3]; + std::string optsbuf; + char class_mount_hdr[64]; + const char *vec[5]; + int tmpallow; + + sprintf(class_mount_hdr, "\\x%02x", AA_CLASS_MOUNT); + + /* rule class single byte header */ + mntbuf.assign(class_mount_hdr); + if (!convert_entry(mntbuf, mnt_point)) + goto fail; + vec[0] = mntbuf.c_str(); + if (!clear_and_convert_entry(devbuf, device)) + goto fail; + vec[1] = devbuf.c_str(); + typebuf.clear(); + if (!build_list_val_expr(typebuf, dev_type)) + goto fail; + vec[2] = typebuf.c_str(); + + if (!build_mnt_flags(flagsbuf, PATH_MAX, flags & MS_NEW_FLAGS, + opt_flags & MS_NEW_FLAGS)) + goto fail; + vec[3] = flagsbuf; + + if (opts) + tmpallow = AA_MATCH_CONT; + else + tmpallow = allow; + + /* rule for match without required data || data MATCH_CONT */ + if (!prof.policy.rules->add_rule_vec(deny, tmpallow, + audit | AA_AUDIT_MNT_DATA, 4, + vec, dfaflags, false)) + goto fail; + count++; + + if (opts) { + /* rule with data match required */ + optsbuf.clear(); + if (!build_mnt_opts(optsbuf, opts)) goto fail; - vec[3] = flagsbuf; - if (!prof.policy.rules->add_rule_vec(deny, allow, audit, 4, vec, - dfaflags, false)) + vec[4] = optsbuf.c_str(); + if (!prof.policy.rules->add_rule_vec(deny, allow, + audit | AA_AUDIT_MNT_DATA, + 5, vec, dfaflags, false)) goto fail; count++; } - if ((allow & AA_MAY_MOUNT) && - (flags | inv_flags) & ~MS_CMDS) { - int tmpallow; + + return RULE_OK; + +fail: + return RULE_ERROR; +} + +int mnt_rule::gen_flag_rules(Profile &prof, int &count, unsigned int flags, + unsigned int opt_flags) +{ + /* + * XXX: added !flags to cover cases like: + * mount options in (bind) /d -> /4, + */ + if ((allow & AA_MAY_MOUNT) && (!flags || flags == MS_ALL_FLAGS)) { + /* no mount flags specified, generate multiple rules */ + if (!device && !dev_type && + gen_policy_remount(prof, count, flags, opt_flags) == RULE_ERROR) + return RULE_ERROR; + if (!dev_type && !opts && + gen_policy_bind_mount(prof, count, flags, opt_flags) == RULE_ERROR) + return RULE_ERROR; + if ((!device || !mnt_point) && !dev_type && !opts && + gen_policy_change_mount_type(prof, count, flags, opt_flags) == RULE_ERROR) + return RULE_ERROR; + if (!dev_type && !opts && + gen_policy_move_mount(prof, count, flags, opt_flags) == RULE_ERROR) + return RULE_ERROR; + + return gen_policy_new_mount(prof, count, flags, opt_flags); + } else if ((allow & AA_MAY_MOUNT) && (flags & MS_REMOUNT) + && !device && !dev_type) { + return gen_policy_remount(prof, count, flags, opt_flags); + } else if ((allow & AA_MAY_MOUNT) && (flags & MS_BIND) + && !dev_type && !opts) { + return gen_policy_bind_mount(prof, count, flags, opt_flags); + } else if ((allow & AA_MAY_MOUNT) && + (flags & (MS_MAKE_CMDS)) + && (!device || !mnt_point) && !dev_type && !opts) { + return gen_policy_change_mount_type(prof, count, flags, opt_flags); + } else if ((allow & AA_MAY_MOUNT) && (flags & MS_MOVE) + && !dev_type && !opts) { + return gen_policy_move_mount(prof, count, flags, opt_flags); + } else if ((allow & AA_MAY_MOUNT) && + ((flags | opt_flags) & ~MS_CMDS)) { /* generic mount if flags are set that are not covered by * above commands */ - /* rule class single byte header */ - mntbuf.assign(class_mount_hdr); - if (!convert_entry(mntbuf, mnt_point)) - goto fail; - vec[0] = mntbuf.c_str(); - if (!clear_and_convert_entry(devbuf, device)) - goto fail; - vec[1] = devbuf.c_str(); - typebuf.clear(); - if (!build_list_val_expr(typebuf, dev_type)) - goto fail; - vec[2] = typebuf.c_str(); - - tmpflags = flags; - tmpinv_flags = inv_flags; - if (tmpflags != MS_ALL_FLAGS) - tmpflags &= ~MS_CMDS; - if (tmpinv_flags != MS_ALL_FLAGS) - tmpinv_flags &= ~MS_CMDS; - if (!build_mnt_flags(flagsbuf, PATH_MAX, tmpflags, tmpinv_flags)) - goto fail; - vec[3] = flagsbuf; + return gen_policy_new_mount(prof, count, flags, opt_flags); + } /* else must be RULE_OK for some rules */ - if (opts) - tmpallow = AA_MATCH_CONT; - else - tmpallow = allow; + return RULE_OK; +} - /* rule for match without required data || data MATCH_CONT */ - if (!prof.policy.rules->add_rule_vec(deny, tmpallow, - audit | AA_AUDIT_MNT_DATA, 4, - vec, dfaflags, false)) - goto fail; - count++; +int mnt_rule::gen_policy_re(Profile &prof) +{ + std::string mntbuf; + std::string devbuf; + std::string typebuf; + std::string optsbuf; + char class_mount_hdr[64]; + const char *vec[5]; + int count = 0; - if (opts) { - /* rule with data match required */ - optsbuf.clear(); - if (!build_mnt_opts(optsbuf, opts)) - goto fail; - vec[4] = optsbuf.c_str(); - if (!prof.policy.rules->add_rule_vec(deny, allow, - audit | AA_AUDIT_MNT_DATA, - 5, vec, dfaflags, false)) + if (!features_supports_mount) { + warn_once(prof.name); + return RULE_NOT_SUPPORTED; + } + + sprintf(class_mount_hdr, "\\x%02x", AA_CLASS_MOUNT); + + /* a single mount rule may result in multiple matching rules being + * created in the backend to cover all the possible choices + */ + for (size_t i = 0; i < flagsv.size(); i++) { + for (size_t j = 0; j < opt_flagsv.size(); j++) { + if (gen_flag_rules(prof, count, flagsv[i], opt_flagsv[j]) == RULE_ERROR) goto fail; - count++; } } if (allow & AA_MAY_UMOUNT) { diff --git a/parser/mount.h b/parser/mount.h index 9ec546cd7..40dd65c13 100644 --- a/parser/mount.h +++ b/parser/mount.h @@ -20,6 +20,7 @@ #define __AA_MOUNT_H #include <ostream> +#include <vector> #include "parser.h" #include "rule.h" @@ -39,6 +40,8 @@ #define MS_MAND (1 << 6) #define MS_NOMAND 0 #define MS_DIRSYNC (1 << 7) +#define MS_SYMFOLLOW 0 +#define MS_NOSYMFOLLOW (1 << 8) #define MS_NODIRSYNC 0 #define MS_NOATIME (1 << 10) #define MS_ATIME 0 @@ -61,6 +64,7 @@ #define MS_IVERSION (1 << 23) #define MS_NOIVERSION 0 #define MS_STRICTATIME (1 << 24) +#define MS_LAZYTIME (1 << 25) #define MS_NOUSER (1 << 31) #define MS_USER 0 @@ -74,12 +78,14 @@ #define MS_ALL_FLAGS (MS_RDONLY | MS_NOSUID | MS_NODEV | MS_NOEXEC | \ MS_SYNC | MS_REMOUNT | MS_MAND | MS_DIRSYNC | \ + MS_NOSYMFOLLOW | \ MS_NOATIME | MS_NODIRATIME | MS_BIND | MS_RBIND | \ MS_MOVE | MS_VERBOSE | MS_ACL | \ MS_UNBINDABLE | MS_RUNBINDABLE | \ MS_PRIVATE | MS_RPRIVATE | \ MS_SLAVE | MS_RSLAVE | MS_SHARED | MS_RSHARED | \ - MS_RELATIME | MS_IVERSION | MS_STRICTATIME | MS_USER) + MS_RELATIME | MS_IVERSION | MS_STRICTATIME | \ + MS_LAZYTIME | MS_USER) /* set of flags we don't use but define (but not with the kernel values) * for MNT_FLAGS @@ -94,16 +100,15 @@ MS_KERNMOUNT | MS_STRICTATIME) #define MS_BIND_FLAGS (MS_BIND | MS_RBIND) -#define MS_MAKE_FLAGS ((MS_UNBINDABLE | MS_RUNBINDABLE | \ +#define MS_MAKE_CMDS (MS_UNBINDABLE | MS_RUNBINDABLE | \ MS_PRIVATE | MS_RPRIVATE | \ - MS_SLAVE | MS_RSLAVE | MS_SHARED | MS_RSHARED) | \ - (MS_ALL_FLAGS & ~(MNT_FLAGS))) + MS_SLAVE | MS_RSLAVE | MS_SHARED | MS_RSHARED) +#define MS_MAKE_FLAGS (MS_ALL_FLAGS & ~(MNT_FLAGS)) #define MS_MOVE_FLAGS (MS_MOVE) -#define MS_CMDS (MS_MOVE | MS_REMOUNT | MS_BIND | MS_RBIND | \ - MS_UNBINDABLE | MS_RUNBINDABLE | MS_PRIVATE | MS_RPRIVATE | \ - MS_SLAVE | MS_RSLAVE | MS_SHARED | MS_RSHARED) +#define MS_CMDS (MS_MOVE | MS_REMOUNT | MS_BIND | MS_RBIND | MS_MAKE_CMDS) #define MS_REMOUNT_FLAGS (MS_ALL_FLAGS & ~(MS_CMDS & ~MS_REMOUNT & ~MS_BIND & ~MS_RBIND)) +#define MS_NEW_FLAGS (MS_ALL_FLAGS & ~MS_CMDS) #define MNT_SRC_OPT 1 #define MNT_DST_OPT 2 @@ -121,6 +126,19 @@ class mnt_rule: public rule_t { + int gen_policy_remount(Profile &prof, int &count, unsigned int flags, + unsigned int opt_flags); + int gen_policy_bind_mount(Profile &prof, int &count, unsigned int flags, + unsigned int opt_flags); + int gen_policy_change_mount_type(Profile &prof, int &count, + unsigned int flags, + unsigned int opt_flags); + int gen_policy_move_mount(Profile &prof, int &count, unsigned int flags, + unsigned int opt_flags); + int gen_policy_new_mount(Profile &prof, int &count, unsigned int flags, + unsigned int opt_flags); + int gen_flag_rules(Profile &prof, int &count, unsigned int flags, + unsigned int opt_flags); public: char *mnt_point; char *device; @@ -128,7 +146,7 @@ public: struct value_list *dev_type; struct value_list *opts; - unsigned int flags, inv_flags; + std::vector<unsigned int> flagsv, opt_flagsv; int allow, audit; int deny; diff --git a/parser/parser.conf b/parser/parser.conf index 1d1c0da21..3909aee49 100644 --- a/parser/parser.conf +++ b/parser/parser.conf @@ -1,7 +1,7 @@ # parser.conf is a global AppArmor config file for the apparmor_parser # # It can be used to specify the default options for the parser, which -# can then be overriden by options passed on the command line. +# can then be overridden by options passed on the command line. # # Leading whitespace is ignored and lines that begin with # are treated # as comments. @@ -43,7 +43,7 @@ #skip-read-cache -#### Set Optimizaions. Multiple Optimizations can be set, one per line #### +#### Set Optimizations. Multiple Optimizations can be set, one per line #### # For supported optimizations see # apparmor_parser --help=O diff --git a/parser/parser.h b/parser/parser.h index cd08afdd6..a7ead40f2 100644 --- a/parser/parser.h +++ b/parser/parser.h @@ -66,10 +66,12 @@ extern int parser_token; #define WARN_FORMAT 0x400 #define WARN_MISSING 0x800 #define WARN_OVERRIDE 0x1000 +#define WARN_INCLUDE 0x2000 #define WARN_DEV (WARN_RULE_NOT_ENFORCED | WARN_RULE_DOWNGRADED | WARN_ABI | \ WARN_DEPRECATED | WARN_DANGEROUS | WARN_UNEXPECTED | \ - WARN_FORMAT | WARN_MISSING | WARN_OVERRIDE | WARN_DEBUG_CACHE) + WARN_FORMAT | WARN_MISSING | WARN_OVERRIDE | \ + WARN_DEBUG_CACHE | WARN_INCLUDE) #define DEFAULT_WARNINGS (WARN_CONFIG | WARN_CACHE | WARN_JOBS | \ WARN_UNEXPECTED | WARN_OVERRIDE) @@ -77,7 +79,8 @@ extern int parser_token; #define WARN_ALL (WARN_RULE_NOT_ENFORCED | WARN_RULE_DOWNGRADED | WARN_ABI | \ WARN_DEPRECATED | WARN_CONFIG | WARN_CACHE | \ WARN_DEBUG_CACHE | WARN_JOBS | WARN_DANGEROUS | \ - WARN_UNEXPECTED | WARN_FORMAT | WARN_MISSING | WARN_OVERRIDE) + WARN_UNEXPECTED | WARN_FORMAT | WARN_MISSING | \ + WARN_OVERRIDE | WARN_INCLUDE) extern dfaflags_t warnflags; extern dfaflags_t werrflags; @@ -229,6 +232,7 @@ do { \ #endif +#define list_first(LIST) (LIST) #define list_for_each(LIST, ENTRY) \ for ((ENTRY) = (LIST); (ENTRY); (ENTRY) = (ENTRY)->next) #define list_for_each_safe(LIST, ENTRY, TMP) \ @@ -262,6 +266,16 @@ do { \ prev; \ }) +#define list_pop(LIST) \ +({ \ + typeof(LIST) _entry = (LIST); \ + if (LIST) { \ + (LIST) = (LIST)->next; \ + _entry->next = NULL; \ + } \ + _entry; \ +}) + #define list_remove_at(LIST, PREV, ENTRY) \ if (PREV) \ (PREV)->next = (ENTRY)->next; \ @@ -376,7 +390,6 @@ extern int skip_mode_force; extern int abort_on_error; extern int skip_bad_cache_rebuild; extern int mru_skip_cache; -extern int debug_cache; /* provided by parser_lex.l (cannot be used in tst builds) */ extern FILE *yyin; diff --git a/parser/parser_include.c b/parser/parser_include.c index d5672a9ac..d708cb8d7 100644 --- a/parser/parser_include.c +++ b/parser/parser_include.c @@ -23,7 +23,7 @@ We support 2 types of includes -#include <name> which searches for the first occurance of name in the +#include <name> which searches for the first occurrence of name in the apparmor directory path. #include "name" which will search for a relative or absolute pathed @@ -60,7 +60,7 @@ static char *path[MAX_PATH] = { NULL }; static int npath = 0; -/* default base directory is /etc/apparmor.d, it can be overriden +/* default base directory is /etc/apparmor.d, it can be overridden with the -b option. */ const char *basedir; @@ -165,6 +165,7 @@ FILE *search_path(char *filename, char **fullpath, bool *skip) if (g_includecache->find(buf)) { /* hit do not want to re-include */ *skip = true; + free(buf); return NULL; } diff --git a/parser/parser_interface.c b/parser/parser_interface.c index ab1077b4f..27948b57a 100644 --- a/parser/parser_interface.c +++ b/parser/parser_interface.c @@ -274,7 +274,7 @@ static inline void sd_write_aligned_blob(std::ostringstream &buf, void *b, int b buf.write((const char *) b, b_size); } -static void sd_write_strn(std::ostringstream &buf, char *b, int size, const char *name) +static void sd_write_strn(std::ostringstream &buf, const char *b, int size, const char *name) { sd_write_name(buf, name); sd_write8(buf, SD_STRING); @@ -282,7 +282,7 @@ static void sd_write_strn(std::ostringstream &buf, char *b, int size, const char buf.write(b, size); } -static inline void sd_write_string(std::ostringstream &buf, char *b, const char *name) +static inline void sd_write_string(std::ostringstream &buf, const char *b, const char *name) { sd_write_strn(buf, b, strlen(b) + 1, name); } @@ -359,7 +359,7 @@ void sd_serialize_xtable(std::ostringstream &buf, char **table) int len = strlen(table[i]) + 1; /* if its a namespace make sure the second : is overwritten - * with 0, so that the namespace and name are \0 seperated + * with 0, so that the namespace and name are \0 separated */ if (*table[i] == ':') { char *tmp = table[i] + 1; @@ -401,11 +401,7 @@ void sd_serialize_profile(std::ostringstream &buf, Profile *profile, sd_write_struct(buf, "profile"); if (flattened) { assert(profile->parent); - autofree char *name = (char *) malloc(3 + strlen(profile->name) + strlen(profile->parent->name)); - if (!name) - return; - sprintf(name, "%s//%s", profile->parent->name, profile->name); - sd_write_string(buf, name, NULL); + sd_write_string(buf, profile->get_name(false).c_str(), NULL); } else { sd_write_string(buf, profile->name, NULL); } diff --git a/parser/parser_lex.l b/parser/parser_lex.l index 7d99cc0f2..d37b33503 100644 --- a/parser/parser_lex.l +++ b/parser/parser_lex.l @@ -167,10 +167,10 @@ void include_filename(char *filename, int search, bool if_exists) include_file = search_path(filename, &fullpath, &cached); if (!include_file && cached) { goto skip; - } else if (preprocess_only) { - fprintf(yyout, "\n\n##included <%s>\n", filename); } else if (!include_file && preprocess_only) { fprintf(yyout, "\n\n##failed include <%s>\n", filename); + } else if (preprocess_only) { + fprintf(yyout, "\n\n##included <%s>\n", filename); } } else if (g_includecache->find(filename)) { @@ -613,6 +613,7 @@ GT > /* Don't use PUSH() macro here as we don't want #include echoed out. * It needs to be handled specially */ + pwarn(WARN_INCLUDE, _("deprecated use of '#include'\n")); yy_push_state(INCLUDE_EXISTS); } @@ -627,6 +628,7 @@ include{WS}+if{WS}+exists/{WS} { /* Don't use PUSH() macro here as we don't want #include echoed out. * It needs to be handled specially */ + pwarn(WARN_INCLUDE, _("deprecated use of '#include'\n")); yy_push_state(INCLUDE); } diff --git a/parser/parser_main.c b/parser/parser_main.c index 4ababf43b..e61456748 100644 --- a/parser/parser_main.c +++ b/parser/parser_main.c @@ -85,10 +85,13 @@ int mru_skip_cache = 1; /* for jobs_max and jobs * LONG_MAX : no limit * LONG_MIN : auto = detect system processing cores - * n : use that number of processes/threads to compile policy + * -n : multiply by the number of CPUs to compile policy */ #define JOBS_AUTO LONG_MIN -long jobs_max = -8; /* 8 * cpus */ +#define DEFAULT_JOBS_MAX -8 +#define DEFAULT_ESTIMATED_JOB_SIZE (50 * 1024 * 1024) +long estimated_job_size = DEFAULT_ESTIMATED_JOB_SIZE; +long jobs_max = DEFAULT_JOBS_MAX; /* 8 * cpus */ long jobs = JOBS_AUTO; /* default: number of processor cores */ long njobs = 0; long jobs_scale = 0; /* number of chance to resample online @@ -130,6 +133,7 @@ static const char *config_file = "/etc/apparmor/parser.conf"; #define ARG_OVERRIDE_POLICY_ABI 141 #define EARLY_ARG_CONFIG_FILE 142 #define ARG_WERROR 143 +#define ARG_ESTIMATED_COMPILE_SIZE 144 /* Make sure to update BOTH the short and long_options */ static const char *short_options = "ad::f:h::rRVvI:b:BCD:NSm:M:qQn:XKTWkL:O:po:j:"; @@ -184,6 +188,7 @@ struct option long_options[] = { {"print-config-file", 0, 0, ARG_PRINT_CONFIG_FILE}, /* no short option */ {"override-policy-abi", 1, 0, ARG_OVERRIDE_POLICY_ABI}, /* no short option */ {"config-file", 1, 0, EARLY_ARG_CONFIG_FILE}, /* early option, no short option */ + {"estimated-compile-size", 1, 0, ARG_ESTIMATED_COMPILE_SIZE}, /* no short option, not in help */ {NULL, 0, 0, 0}, }; @@ -264,6 +269,7 @@ optflag_table_t warnflag_table[] = { { 1, "missing", "warn when missing qualifier and a default is used", WARN_MISSING }, { 1, "override", "warn when overriding", WARN_OVERRIDE }, { 1, "dev", "turn on warnings that are useful for profile development", WARN_DEV }, + { 1, "pound-include", "warn when #include is used", WARN_INCLUDE }, { 1, "all", "turn on all warnings", WARN_ALL}, { 0, NULL, NULL, 0 }, }; @@ -414,6 +420,19 @@ static long process_jobs_arg(const char *arg, const char *val) { return n; } +static long str_to_size(const char *s) +{ + if (*s == '\0') + return 1; + else if (strcmp(s, "KB") == 0) + return 1024; + else if (strcmp(s, "MB") == 0) + return 1024*1024; + else if (strcmp(s, "GB") == 0) + return 1024*1024*1024; + return -1; +} + #define EARLY_ARG 1 #define LATE_ARG 2 #define TWOPASS_ARG (EARLY_ARG | LATE_ARG) @@ -434,7 +453,7 @@ int arg_pass(int c) { return LATE_ARG; } -/* process a single argment from getopt_long +/* process a single argument from getopt_long * Returns: 1 if an action arg, else 0 */ #define DUMP_HEADER " variables \tDump variables\n" \ @@ -751,6 +770,21 @@ static int process_arg(int c, char *optarg) case ARG_PRINT_CONFIG_FILE: printf("%s\n", config_file); break; + case ARG_ESTIMATED_COMPILE_SIZE: + /* used to auto tune parser on low resource systems */ + { + char *end; + long mult; + long long tmp = strtoll(optarg, &end, 0); + if (end == optarg || + (errno == ERANGE && (tmp == LLONG_MIN || tmp == LLONG_MAX)) || + (mult = str_to_size(end)) == -1) { + PERROR("%s: --estimated-compile-size invalid size '%s'", progname, optarg); + exit(1); + } + estimated_job_size = tmp * mult; + } + break; default: /* 'unrecognized option' error message gets printed by getopt_long() */ exit(1); @@ -1112,7 +1146,7 @@ int process_profile(int option, aa_kernel_interface *kernel_interface, retval = process_binary(option, kernel_interface, cachename); if (!retval || skip_bad_cache_rebuild) - return retval; + goto out; } } @@ -1175,7 +1209,8 @@ int process_profile(int option, aa_kernel_interface *kernel_interface, } } out: - + /* cleanup */ + reset_parser(profilename); return retval; } @@ -1263,7 +1298,7 @@ do { \ * from work_spawn and work_sync. We could throw a C++ exception, is it * worth doing it to avoid the exit here. * - * atm not all resources maybe cleanedup at exit + * atm not all resources may be cleaned up at exit */ int last_error = 0; void handle_work_result(int retval) @@ -1291,35 +1326,120 @@ static long compute_jobs(long n, long j) return j; } -static void setup_parallel_compile(void) +static void setup_parallel_compile(long ncpus, long maxcpus) { - /* jobs and paralell_max set by default, config or args */ - long n = sysconf(_SC_NPROCESSORS_ONLN); - long maxn = sysconf(_SC_NPROCESSORS_CONF); - if (n == -1) - /* unable to determine number of processors, default to 1 */ - n = 1; - if (maxn == -1) - /* unable to determine number of processors, default to 1 */ - maxn = 1; + /* jobs and parallel_max set by default, config or args */ if (jobs < 0 || jobs == JOBS_AUTO) jobs_scale = 1; - jobs = compute_jobs(n, jobs); - jobs_max = compute_jobs(maxn, jobs_max); + jobs = compute_jobs(ncpus, jobs); + jobs_max = compute_jobs(maxcpus, jobs_max); if (jobs > jobs_max) { - pwarn(WARN_JOBS, "%s: Warning capping number of jobs to %ld * # of cpus == '%ld'", + pwarn(WARN_JOBS, "%s: Capping number of jobs to %ld * # of cpus == '%ld'", progname, jobs_max, jobs); jobs = jobs_max; } else if (jobs_scale && jobs < jobs_max) /* the bigger the difference the more sample chances given */ - jobs_scale = jobs_max + 1 - n; + jobs_scale = jobs_max + 1 - ncpus; njobs = 0; if (debug_jobs) fprintf(stderr, "jobs: %ld\n", jobs); } + +/* + * Tune parameters to adjust the parser to adapt to low memory, low power + * systems. + * with a profile compile taking up to 10s of MB, launching a lot of + * parallel compiles is a bad idea on lauch 16 parallel compiles with + * only 50 MB free. + * + */ +#define PREFIX_TOTAL "MemTotal:" +#define PREFIX_FREE "MemFree:" +#define PREFIX_CACHE "Cached:" + +static bool get_memstat(long long &mem_total, long long &mem_free, + long long &mem_cache) +{ + char *line, buf[256]; + autofclose FILE *f = NULL; + + mem_total = mem_free = mem_cache = -1; + + /* parse /proc/meminfo to get a rough idea of available mem, + look into libstatgrab as alternative */ + f = fopen("/proc/meminfo", "r"); + if (f == NULL) { + PDEBUG("Failed to open /proc/meminfo"); + return false; + } + + while ((line = fgets(buf, sizeof(buf), f)) != NULL) { + long long value; + if (sscanf(buf, "%*s %lld kB", &value) != 1) + continue; + + if (strncmp(buf, PREFIX_FREE, strlen(PREFIX_FREE)) == 0) + mem_free = value * 1024; + else if (strncmp(buf, PREFIX_TOTAL, strlen(PREFIX_TOTAL)) == 0) + mem_total = value * 1024; + else if (strncmp(buf, PREFIX_CACHE, strlen(PREFIX_CACHE)) == 0) + mem_cache = value * 1024; + } + + if (mem_free == -1 || mem_total == -1 || mem_cache == -1) { + PDEBUG("Failed to parse mem value"); + return false; + } + mem_free += mem_cache; + + return true; +} + +static void auto_tune_parameters(void) +{ + long long mem_total, mem_free, mem_cache; + long ncpus = sysconf(_SC_NPROCESSORS_ONLN); + long maxcpus = sysconf(_SC_NPROCESSORS_CONF); + if (ncpus == -1) { + PDEBUG("Unable to determine number of processors, default to 1"); + ncpus = 1; + } + if (maxcpus == -1) { + PDEBUG("Unable to determine number of processors, default to 1"); + maxcpus = 1; + } + /* only override if config or param hasn't overridden */ + if (get_memstat(mem_total, mem_free, mem_cache) == true && + jobs == JOBS_AUTO) { + long estimated_jobs = (long) (mem_free / estimated_job_size); + + if (mem_free < 2) { + /* -j0 - no workers */ + jobs = jobs_max = 0; + PDEBUG("Auto tune: --jobs=0"); + } else if (estimated_jobs < ncpus) { + /* --jobs=estimate_jobs */ + jobs = estimated_jobs; + PDEBUG("Auto tune: --jobs=%ld", estimated_jobs); + } else { + long long n = estimated_jobs / ncpus; + + if (n < -DEFAULT_JOBS_MAX) { + /* --jobs=cpus*n */ + jobs = -n; + PDEBUG("Auto tune: --jobs=%ld", jobs); + } + } + } else { + PDEBUG("Unable to get meminfo, using defaults"); + } + + setup_parallel_compile(ncpus, maxcpus); +} + struct dir_cb_data { aa_kernel_interface *kernel_interface; const char *dirname; /* name of the parent dir */ @@ -1427,7 +1547,7 @@ int main(int argc, char *argv[]) process_config_file(config_file); optind = process_args(argc, argv); - setup_parallel_compile(); + auto_tune_parameters(); setlocale(LC_MESSAGES, ""); bindtextdomain(PACKAGE, LOCALEDIR); @@ -1577,6 +1697,7 @@ int main(int argc, char *argv[]) if (ofile) fclose(ofile); aa_policy_cache_unref(policy_cache); + aa_kernel_interface_unref(kernel_interface); return last_error; } diff --git a/parser/parser_regex.c b/parser/parser_regex.c index f257ecfb9..200c94767 100644 --- a/parser/parser_regex.c +++ b/parser/parser_regex.c @@ -486,13 +486,18 @@ static int process_profile_name_xmatch(Profile *prof) &prof->xmatch_len); if (ptype == ePatternBasic) prof->xmatch_len = strlen(name); - if (!prof->attachment) - free(name); if (ptype == ePatternInvalid) { PERROR(_("%s: Invalid profile name '%s' - bad regular expression\n"), progname, name); + if (!prof->attachment) + free(name); return FALSE; - } else if (ptype == ePatternBasic && !(prof->altnames || prof->attachment || prof->xattrs.list)) { + } + + if (!prof->attachment) + free(name); + + if (ptype == ePatternBasic && !(prof->altnames || prof->attachment || prof->xattrs.list)) { /* no regex so do not set xmatch */ prof->xmatch = NULL; prof->xmatch_len = 0; @@ -541,7 +546,7 @@ static int process_profile_name_xmatch(Profile *prof) int len; tbuf.clear(); /* prepend \x00 to every value. This is - * done to separate the existance of the + * done to separate the existence of the * xattr from a null value match. * * if an xattr exists, a single \x00 will diff --git a/parser/parser_symtab.c b/parser/parser_symtab.c index 7b8f211b7..89ae432e9 100644 --- a/parser/parser_symtab.c +++ b/parser/parser_symtab.c @@ -847,13 +847,13 @@ int main(void) MY_TEST(retval == 0, "get boolean variable 2"); retval = get_boolean_var("non_existant"); - MY_TEST(retval < 0, "get nonexistant boolean variable"); + MY_TEST(retval < 0, "get nonexistent boolean variable"); retval = get_boolean_var("stereopuff"); MY_TEST(retval < 0, "get boolean variable that's declared a set var"); retptr = get_set_var("daves_not_here_man"); - MY_TEST(retptr == NULL, "get non-existent set variable"); + MY_TEST(retptr == NULL, "get nonexistent set variable"); retptr = get_set_var("abuse"); MY_TEST(retptr == NULL, "get set variable that's declared a boolean"); diff --git a/parser/parser_yacc.y b/parser/parser_yacc.y index f317556e4..1ef55ab18 100644 --- a/parser/parser_yacc.y +++ b/parser/parser_yacc.y @@ -1773,7 +1773,7 @@ static int abi_features_base(struct aa_features **features, char *filename, bool { autofclose FILE *f = NULL; struct stat my_stat; - char *fullpath = NULL; + autofree char *fullpath = NULL; bool cached; if (search) { diff --git a/parser/po/apparmor-parser.pot b/parser/po/apparmor-parser.pot index df194e31b..250d6d656 100644 --- a/parser/po/apparmor-parser.pot +++ b/parser/po/apparmor-parser.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: apparmor@lists.ubuntu.com\n" -"POT-Creation-Date: 2020-10-14 04:04-0700\n" +"POT-Creation-Date: 2020-10-14 03:51-0700\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" diff --git a/parser/policy_cache.c b/parser/policy_cache.c index 341e0331f..2baf27b7e 100644 --- a/parser/policy_cache.c +++ b/parser/policy_cache.c @@ -179,7 +179,7 @@ void install_cache(const char *cachetmpname, const char *cachename) } if (rename(cachetmpname, cachename) < 0) { - pwarn(WARN_CACHE, "Warning failed to write cache: %s\n", cachename); + pwarn(WARN_CACHE, "Failed to write cache: %s\n", cachename); unlink(cachetmpname); } else if (show_cache) { diff --git a/parser/policy_cache.h b/parser/policy_cache.h index 7925f3677..3bee31ca2 100644 --- a/parser/policy_cache.h +++ b/parser/policy_cache.h @@ -36,7 +36,6 @@ extern int cond_clear_cache; /* only applies if write is set */ extern int force_clear_cache; /* force clearing regargless of state */ extern int create_cache_dir; /* create the cache dir if missing? */ extern int mru_skip_cache; -extern int debug_cache; void set_cache_tstamp(struct timespec t); void update_mru_tstamp(FILE *file, const char *path); diff --git a/parser/profile-load b/parser/profile-load index 2663c04d5..7591fb3e1 100755 --- a/parser/profile-load +++ b/parser/profile-load @@ -24,7 +24,11 @@ . /lib/apparmor/rc.apparmor.functions # do not load in a container -[ -x /usr/bin/systemd-detect-virt ] && systemd-detect-virt --quiet --container && ! is_container_with_internal_policy && exit 0 || true +if [ -x /usr/bin/systemd-detect-virt ] && \ + systemd-detect-virt --quiet --container && \ + ! is_container_with_internal_policy; then + exit 0 +fi [ -d /rofs/etc/apparmor.d ] && exit 0 # do not load if running liveCD @@ -42,7 +46,7 @@ aafs=/sys/kernel/security/apparmor params=$module/parameters [ -r $params/enabled ] || exit 0 # do not load if missing -read enabled < $params/enabled || exit 1 # if this fails, something went wrong +read -r enabled < $params/enabled || exit 1 # if this fails, something went wrong [ "$enabled" = "Y" ] || exit 0 # do not load if disabled /sbin/apparmor_parser -r -W "$profile" || exit 0 # LP: #1058356 diff --git a/parser/rc.apparmor.debian b/parser/rc.apparmor.debian deleted file mode 100644 index 8efd4400d..000000000 --- a/parser/rc.apparmor.debian +++ /dev/null @@ -1,117 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------- -# Copyright (c) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007 -# NOVELL (All rights reserved) -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of version 2 of the GNU General Public -# License published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, contact Novell, Inc. -# ---------------------------------------------------------------------- -# rc.apparmor by Steve Beattie -# -# /etc/init.d/apparmor -# -# chkconfig: 2345 01 99 -# description: AppArmor rc file. This rc script inserts the apparmor \ -# module and runs the parser on the /etc/apparmor.d/ \ -# directory. -# -### BEGIN INIT INFO -# Provides: apparmor -# Required-Start: -# Required-Stop: -# Default-Start: 3 4 5 -# Default-Stop: 0 1 2 6 -# Short-Description: AppArmor initialization -# Description: AppArmor rc file. This rc script inserts the apparmor -# module and runs the parser on the /etc/apparmor.d/ -# directory. -### END INIT INFO -APPARMOR_FUNCTIONS=/lib/apparmor/rc.apparmor.functions - -aa_action() { - STRING=$1 - shift - $* - rc=$? - if [ $rc -eq 0 ] ; then - aa_log_success_msg $"$STRING " - else - aa_log_failure_msg $"$STRING " - fi - return $rc -} - -aa_log_success_msg() { - [ -n "$1" ] && echo -n $1 - echo ": done." -} - -aa_log_warning_msg() { - [ -n "$1" ] && echo -n $1 - echo ": Warning." -} - -aa_log_failure_msg() { - [ -n "$1" ] && echo -n $1 - echo ": Failed." -} - -aa_log_skipped_msg() { - [ -n "$1" ] && echo -n $1 - echo ": Skipped." -} - -usage() { - echo "Usage: $0 {start|stop|restart|try-restart|reload|force-reload|status|kill}" -} - -# source apparmor function library -if [ -f "${APPARMOR_FUNCTIONS}" ]; then - . ${APPARMOR_FUNCTIONS} -else - aa_log_failure_msg "Unable to find AppArmor initscript functions" - exit 1 -fi - -test -x ${PARSER} || exit 0 # by debian policy - -case "$1" in - start) - apparmor_start - rc=$? - ;; - stop) - apparmor_stop - rc=$? - ;; - restart|reload|force-reload) - apparmor_restart - rc=$? - ;; - try-restart) - apparmor_try_restart - rc=$? - ;; - kill) - apparmor_kill - rc=$? - ;; - status) - apparmor_status - rc=$? - ;; - *) - usage - exit 1 - ;; -esac -exit $rc diff --git a/parser/rc.apparmor.functions b/parser/rc.apparmor.functions index c11a5a937..f66fea422 100644 --- a/parser/rc.apparmor.functions +++ b/parser/rc.apparmor.functions @@ -68,7 +68,7 @@ is_apparmor_present() { # something like `systemd-detect-virt --container`. # # The only known container environments capable of supporting internal policy -# are LXD and LXC environment. +# are LXD and LXC environments, and Windows Subsystem for Linux. # # Returns 0 if the container environment is capable of having its own internal # policy and non-zero otherwise. @@ -90,6 +90,12 @@ is_container_with_internal_policy() { local ns_stacked local ns_name + # WSL needs to be detected explicitly + if [ -x /usr/bin/systemd-detect-virt ] && \ + [ "$(systemd-detect-virt --container)" = "wsl" ]; then + return 0 + fi + if ! [ -f "$ns_stacked_path" ] || ! [ -f "$ns_name_path" ]; then return 1 fi @@ -99,11 +105,12 @@ is_container_with_internal_policy() { return 1 fi - # LXD and LXC set up AppArmor namespaces starting with "lxd-" and - # "lxc-", respectively. Return non-zero for all other namespace - # identifiers. + # LXD, Incus and LXC set up AppArmor namespaces starting with "lxd-", + # "incus-" and "lxc-", respectively. Return non-zero for all other + # namespace identifiers. read -r ns_name < "$ns_name_path" if [ "${ns_name#lxd-*}" = "$ns_name" ] && \ + [ "${ns_name#incus-*}" = "$ns_name" ] && \ [ "${ns_name#lxc-*}" = "$ns_name" ]; then return 1 fi @@ -111,37 +118,6 @@ is_container_with_internal_policy() { return 0 } -# This set of patterns to skip needs to be kept in sync with -# AppArmor.pm::isSkippableFile() -# returns 0 if profile should NOT be skipped -# returns 1 on verbose skip -# returns 2 on silent skip -skip_profile() { - local profile="$1" - if [ "${profile%.rpmnew}" != "$profile" ] || \ - [ "${profile%.rpmsave}" != "$profile" ] || \ - [ "${profile%.orig}" != "$profile" ] || \ - [ "${profile%.rej}" != "$profile" ] || \ - [ "${profile%\~}" != "$profile" ] ; then - return 1 - fi - # Silently ignore the dpkg, pacman, and xbps files - if [ "${profile%.dpkg-new}" != "$profile" ] || \ - [ "${profile%.dpkg-old}" != "$profile" ] || \ - [ "${profile%.dpkg-dist}" != "$profile" ] || \ - [ "${profile%.dpkg-bak}" != "$profile" ] || \ - [ "${profile%.dpkg-remove}" != "$profile" ] || \ - [ "${profile%.pacsave}" != "$profile" ] || \ - [ "${profile%.pacnew}" != "$profile" ] ; then - return 2 - fi - if echo "$profile" | grep -E -q '^.+\.new-[0-9\.]+_[0-9]+$'; then - return 2 - fi - - return 0 -} - __parse_profiles_dir() { local parser_cmd="$1" local profile_dir="$2" @@ -157,41 +133,11 @@ __parse_profiles_dir() { return 1 fi - # Note: the parser automatically skips files that match skip_profile() - # when we pass it a directory, but not when we pass it an individual - # profile. So we need to use skip_profile only in the latter case, - # as long as the parser is in sync' with skip_profile(). - "$PARSER" $PARSER_OPTS "$parser_cmd" -- "$profile_dir" || { - # FIXME: once the parser properly handles broken profiles - # (LP: #1377338), remove the following code and the - # skip_profile() function. For now, if the parser returns - # an error, just run it again separately on each profile. - for profile in "$profile_dir"/*; do - skip_profile "$profile" - skip=$? - if [ "$skip" -eq 2 ]; then - # Ignore skip status == 2 (silent skip) - continue - elif [ "$skip" -ne 0 ] ; then - aa_log_skipped_msg "$profile" - logger -t "AppArmor(init)" -p daemon.warn \ - "Skipping profile $profile" - continue - fi - if [ ! -f "$profile" ] ; then - continue - fi - printf "%s\0" "$profile" - done | \ - # Use xargs to parallelize calls to the parser over all CPUs - xargs -n1 -0r -P "$(getconf _NPROCESSORS_ONLN)" \ - "$PARSER" $PARSER_OPTS "$parser_cmd" -- - if [ $? -ne 0 ]; then - status=1 - aa_log_failure_msg "At least one profile failed to load" - fi - } - + # shellcheck disable=SC2086 + if ! "$PARSER" $PARSER_OPTS "$parser_cmd" -- "$profile_dir"; then + status=1 + aa_log_failure_msg "At least one profile failed to load" + fi return "$status" } @@ -215,7 +161,6 @@ parse_profiles() { # run the parser on all of the apparmor profiles if [ ! -f "$PARSER" ]; then aa_log_failure_msg "AppArmor parser not found" - aa_log_action_end 1 exit 1 fi @@ -227,41 +172,6 @@ parse_profiles() { return "$STATUS" } -profiles_names_list() { - # run the parser on all of the apparmor profiles - if [ ! -f "$PARSER" ]; then - aa_log_failure_msg "- AppArmor parser not found" - exit 1 - fi - - for profile_dir in $PROFILE_DIRS; do - if [ ! -d "$profile_dir" ]; then - aa_log_warning_msg "- Profile directory not found: $profile_dir" - continue - fi - - for profile in "$profile_dir"/*; do - if skip_profile "$profile" && [ -f "$profile" ] ; then - LIST_ADD=$("$PARSER" -N "$profile" ) - if [ $? -eq 0 ]; then - echo "$LIST_ADD" - fi - fi - done - done -} - -failstop_system() { - level=$(runlevel | cut -d" " -f2) - if [ "$level" -ne "1" ] ; then - aa_log_failure_msg "- could not start AppArmor. Changing to runlevel 1" - telinit 1; - return 255; - fi - aa_log_failure_msg "- could not start AppArmor." - return 255 -} - is_apparmor_loaded() { if ! is_securityfs_mounted ; then mount_securityfs @@ -309,7 +219,7 @@ apparmor_start() { fi # if there is anything in the profiles file don't load - if ! read -r line < "$SFS_MOUNTPOINT/profiles"; then + if ! read -r _ < "$SFS_MOUNTPOINT/profiles"; then parse_profiles load else aa_log_skipped_msg ": already loaded with profiles." @@ -357,7 +267,7 @@ remove_profiles() { } apparmor_stop() { - aa_log_daemon_msg "Unloading AppArmor profiles " + aa_log_daemon_msg "Unloading AppArmor profiles" remove_profiles rc=$? aa_log_end_msg "$rc" diff --git a/parser/rc.apparmor.redhat b/parser/rc.apparmor.redhat deleted file mode 100644 index 227699868..000000000 --- a/parser/rc.apparmor.redhat +++ /dev/null @@ -1,125 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------- -# Copyright (c) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007 -# NOVELL (All rights reserved) -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of version 2 of the GNU General Public -# License published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, contact Novell, Inc. -# ---------------------------------------------------------------------- -# rc.apparmor by Steve Beattie -# -# /etc/init.d/apparmor -# -# chkconfig: 2345 01 99 -# description: AppArmor rc file. This rc script inserts the apparmor \ -# module and runs the parser on the /etc/apparmor.d/ \ -# directory. -# -### BEGIN INIT INFO -# Provides: apparmor -# Required-Start: -# Required-Stop: -# Default-Start: 3 4 5 -# Default-Stop: 0 1 2 6 -# Short-Description: AppArmor initialization -# Description: AppArmor rc file. This rc script inserts the apparmor -# module and runs the parser on the /etc/apparmor.d/ -# directory. -### END INIT INFO -APPARMOR_FUNCTIONS=/lib/apparmor/rc.apparmor.functions - -# source function library -if [ -f /etc/init.d/functions ]; then - . /etc/init.d/functions -elif [ -f /etc/rc.d/init.d/functions ]; then - . /etc/rc.d/init.d/functions -elif [ -f /lib/lsb/init-functions ]; then - . /lib/lsb/init-functions -else - exit 0 -fi - -usage() { - echo "Usage: $0 {start|stop|restart|try-restart|reload|force-reload|status|kill}" -} - -aa_log_success_msg() { - echo -n "$*" - success - echo -} - -aa_log_warning_msg() { - echo -n "$*" - warning - echo -} - -aa_log_skipped_msg() { - echo -n "$*" - warning - echo -} - -aa_log_failure_msg() { - echo -n "$*" - failure - echo -} - -aa_action() { - STRING=$1 - shift - action "${STRING} " "$@" - return $? -} - -# source apparmor function library -if [ -f "${APPARMOR_FUNCTIONS}" ]; then - . ${APPARMOR_FUNCTIONS} -else - aa_log_failure_msg "Unable to find AppArmor initscript functions" - exit 1 -fi - -case "$1" in - start) - apparmor_start - rc=$? - ;; - stop) - apparmor_stop - rc=$? - ;; - restart|reload|force-reload) - apparmor_restart - rc=$? - ;; - try-restart) - apparmor_try_restart - rc=$? - ;; - kill) - apparmor_kill - rc=$? - ;; - status) - apparmor_status - rc=$? - ;; - *) - usage - exit 1 - ;; -esac -exit $rc - diff --git a/parser/signal.cc b/parser/signal.cc index a91ff23b6..c80dab6fb 100644 --- a/parser/signal.cc +++ b/parser/signal.cc @@ -112,7 +112,7 @@ static const char *const sig_names[MAXMAPPED_SIG + 1] = { "lost", "unused", - "exists", /* always last existance test mapped to MAXMAPPED_SIG */ + "exists", /* always last existence test mapped to MAXMAPPED_SIG */ }; diff --git a/parser/techdoc.log b/parser/techdoc.log index a9ee78c1b..0335ef705 100644 --- a/parser/techdoc.log +++ b/parser/techdoc.log @@ -1,8 +1,8 @@ -This is pdfTeX, Version 3.141592653-2.6-1.40.22 (TeX Live 2022/dev/Debian) (preloaded format=pdflatex 2022.7.2) 21 NOV 2022 16:55 +This is pdfTeX, Version 3.141592653-2.6-1.40.22 (TeX Live 2022/dev/Debian) (preloaded format=pdflatex 2023.5.30) 5 FEB 2024 13:49 entering extended mode restricted \write18 enabled. %&-line parsing enabled. -**\def\fixedpdfdate{20221122005432+0000}\input techdoc.tex +**\def\fixedpdfdate{20240202221305+0000}\input techdoc.tex (./techdoc.tex (/usr/share/texlive/texmf-dist/tex/latex/base/article.cls Document Class: article 2021/10/04 v1.4n Standard LaTeX document class @@ -250,7 +250,7 @@ Package rerunfilecheck Info: File `techdoc.out' has not changed. ) Here is how much of TeX's memory you used: 7473 strings out of 480247 - 118156 string characters out of 5896152 + 118156 string characters out of 5896151 416357 words of memory out of 5000000 25296 multiletter control sequences out of 15000+600000 475140 words of font info for 49 fonts, out of 8000000 for 9000 @@ -270,7 +270,7 @@ cmsy10.pfb></usr/share/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmti10 /usr/share/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmtt12.pfb></usr/s hare/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmtt8.pfb></usr/share/te xlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmtt9.pfb> -Output written on techdoc.pdf (20 pages, 248501 bytes). +Output written on techdoc.pdf (20 pages, 248503 bytes). PDF statistics: 375 PDF objects out of 1000 (max. 8388607) 318 compressed objects within 4 object streams diff --git a/parser/techdoc.pdf b/parser/techdoc.pdf index c8bee638fa4a59bcb153bec9ba6cb8027db69fe2..d53b1a30e123a86ea2c646fa6868d07c049d58be 100644 GIT binary patch delta 5697 zcmV-H7QX4Vl@GU-53uPk0W*_<B`SYQkK;xTzW1;2ZPmco(bJZ)hs6wb2gtsF#WavZ zl0&ssZWme)M<vaqf&BO-`LSfVM~%rL7pscJdXX&hBmLpw?z@k9vPqI%RTSyw;j~H8 zd{@Tl<`D09Nm^|l>djA4w#)CgNs<=PfBLDr&yr}o|K;JI-+fHuO|`4`>7IY5#+z+Y z?y^!R32RzLqwAjTw`r9}N7HHJK0QQd>Lt;WWl|9tj9iAE8VBOoCXx02FCO&HwANqD z(W2{gA=9+IAGJL^x^XbW3%Xx;Z^IkC_}%?Br$axSoh}!?hOZ$9X9?_aa5}?dW83>} zmhPiRIgUM=$y-p~UoP*5wjY1^Xx#rfPVz>^=oZItaP*IT$0rzvz5&7Mq>CBPe5bHU z5*aTu>vv2#M5e1T<7qGu<M!sP1<oq6&B~bN#~;YAjE*=0QO2`HV{lJrSxD)<PE@hE z<^_bHoF|><MqO#41MZ4|`>CnX_0uC0KJ#olgiToDE61i|j`lb@63>4&iw{w2k35-n zb-opCUFFOid%nhCPyJvqp|+i~9&Hjb>!7nW`mb#$@R4#L6>8J%SB?c)Wm(jU?GoG1 zaS@YpQu-#WnJkZ{HW)S!UPm(38MDxomUr*Dg_Y8o8_fR2k78uhKANT>Bjpn%`L4?I z+hcvaFSBSGC%$~TuULP^CCS*6_&g~63E!*CxYrtHd{GvXQD$`n^4gMZIT0Jv#8ykS zRhgms<h^vkAeMJ*WQkmu552uPwLYVHcb3R(#d_hT%Ba6ITTvHH@fUr1EJ#Ok<QYed z%__k3{vxyOVA!M}$=8mJRGl7nWmyK7R;WvR5TzfA=wEi|Y=eLHe?NXg<L}0{B$#h{ zJ6R=9ipbmJG`Lako+o@*IbF?bGY2b+=_97(B;$fy$Kd+O2ZB5zRWMU9N6*Cs=ffgq z#1Q<DlFgvUX|Q?!EPAXlI$hSR|1q37ts?j)Zn0nD@8l=bklynmjUJ~_2Tvp01~>H} z)S9=PF3N8WoC<%Bm$SoUUZ<<~U86fYdfEsZ%qJ%%>hf#`A6;g}fs-4l;gJ)G6#NzM z%CAw(iS5k6v@s0dV0lcUMINhNq{+g;r#DOUedUXx)^R*@E#Gu%!|umss>P!D41KH1 z_<k7IRKRM^szcCaa1qsB#c~K@89;@{p$YI@VJ4kJt|EUSiYIr$1i>T%rZdv-qrM!v z^qwBp4;?uE?ZERJYcR=ZeXMys9NLh_s*k6DS;KcfN)$#L@tR%m@{S&Zrtv)nRdBQK z4^(*eTKg;az<|i{I+Nq=pz=e%b_2zqet7xodK?G<;p##GTgng_mncMb;l@dvw4#lV zu2FIjlEQx!p_0$j6MIJz2;NLMjzjO2+uK2%0VWMr40$wUKWfx(#|_|}oE&v)jd&6X z$?{H#_~lI7D$Rlf_d=8!Zcm6#XI^M?6{0*##sF?SgUNs}zilGn+V9?u5XZ*p#aaJ3 z#TU`17)V|cG}Oios-iyc+D|%o77cv{5TZ5aurYu852hM02iV0bDk)svQo+h7b=5m= z;V#mBO7W|`Od}#^8l6ZLL*Jrf@Zo5$Mpq(U9u8_iuBib7m`|L>^>s4oV%&fhbR}%J zUeSkf8+L=(xm`=#{?aK6uf0!%b>&v`J7Bz)MvaA2<S59RKY^?mGL6D1zz@@4I>ql` z&W(Q#QHbwoX;DC#D%_lxN}~FxLBTS5BGZ<j!Yr_YW^|Wk^~`58)Q#(&*(~EyR+g_A zaZDC~*cC4#4N=)>_`xn%%bB%W3W5j72)cemd)7R6YI?0?Ym@V5#_B7_Q{-NLB^1Ut z5DJV{RIc&_m`xdlwP=<aUrNLmp6iu>m{xzOEQtWL!t2Sil>nASA2e%uBhKXcUL9SY zA8x?j*_s+uxeFLA8oy4~GsrTJB3R<l42fT87Xl9pp4x2pIe{oReP;G{oGu<q)jhrt zGz~sYCcs3y8rbn3vLuc|G*rPQ?24KOtyy1LvM@uoGQva94sl7OAqIZ@4AR9C#HD}o zXwW~mP4k8KLL~63d%zp))<n&Y;FHz7LK6(iXC?o*`^UrGuXhPei8lmR5BYAt-{f(+ zD~fV+Z0~;hCEnCDMvLyUs@!}L&UTaTD#D%drrCVD``_IM&f;&dX>vI1_IW8wWrZSM zO3=mua=qO*jjLUGIQ$tKr%Adi^$vgY#_)PpUi)(Z?3(*g+v(T-hSx&S$g|K2c9J{s zloCM>*)B<MWh-cfLanK5#|yqgSEU^5yC6ykXWSN_#ow0vOREnMiHO3*EM(0@0B+XA zfclhToCRgbK1SJ%u4z_+-4zZAIN`(wIdr@zcSvmGSWeI(XXrU7hnzMRgQ0&fWu_L1 z<v=$3qU%`-G$tg|dDQDZy`D7AUgg_)Fki`1B#*bYdov+U^D7?Uwmczj%%cPhMzkam zT@GF4w*!*YA`A^FC;_|C1;STP=p}R()pbG9sT_%mCh}%AL3@4pBZ?Dgxu+&f>zXH8 z3;@7zOKMa6Ra-KR_O)t&WtM*_G7;c27PSkI8~3ca1@{(7IrV;Db8Luxa1evKh-H0+ zMy?)*I${m{W`fr}^}`XeP=kIO6@HkDSyeeF97ddH#c_ZZIfbOcdBy*31b=IAGmmcL z#xbM<gbl;066}d|nRMDUeJF2VR2703ao2OI{l*LGYO&{iwyX{9^QC`U$jFMUh~C$s z0>gy^Qp`vy?*|Wlf#AH1sXsepDMXb+?=yrW8I^e8-BX7?J)$|s=KeQoNYB9S>N#lL zlUG&ZqJOYVvDY%ik7usCzMv|B8&G2m#fvK<<z7Iv9$kKEggH)A^S8Gv8CuBrk+K3i zSADV~7yMD=uS<(eLF9iQ$oN`+!^?Yb9}Q$n{W)c17I{$Ys>8FEwXPVv$l$`1x?1;C zRw*U58aFz-tO<P;wKLA1PIl1ts87gfIZj;z(uSj!r5GFBa4Q1I@&Iuf`#pn~;;Q%S zB&S<hC!Toa8iQ@fanFSajw$`C7zU86aY9TpQ((^VCd8jd-#35LsB451gjg3#E-t|_ zg=mRn8?Wqt<Pg&^=g%wGfU6pwQRy!a3p8Ek64VH-N<D=K;V#vh_(tp&;T{k3PRNGY zL-VfJwf9S;&J#q><yFs2I~#S_2K}jXgj-i@{j=HSdCGM$`r`iFSJaz|i#08hP(*KL zTU>#e7_{ii=|F#RCtWYBbNhyP$c?BvHOeYSmo0k`aNX5JBh<4Kk^;iR0>y(44JKa+ zU9c5{jY<PD^JqZEJRt1@YIh~q?zngJu#-hCC^_7mr!|+b(yEs<Fh(dwEt4>J-?^hy z8M(hSY7!l*;F`VXSCmA9i&}HKjFjn3I1g{bhZRK@?puF<uSor?zp2?~A#+TEZ{s+b zX%frh27`pmMt7VVGl0C5pVVnpX0qfcDU6U`88y>_^U~w2Wpz?SO{<uFp*BwV(z)@! ziqti|j%qC`z%^<t0te`V$EZaSmlP0iOeHQwnNEQe&{gX^m#$Ya*)kVc`^!V$<qz!O z802yM9)N$c=*_#3V2TPWl2ebm&y3is;Qfe%0uqtPqtKmdP_<?)O`UNY+46W%>C=rI zoL`R~OY?1<B7OiapTtrIj9INc!tjp}^FfS<nZHzi@&Geu__rx8W=zG51i-mqf<Icd zpmm=B!L&9k%htuiwVVG7BKnC-SE7e~wafO!W}6R{yS&PAnIhzi{|EL@_`kCYI}}O* zGLwNND1WtBUytN841eEGQ6VHc=-N)&blM{Ea3{e7LIT9{1PEo@%uclFADVXOR={`1 zj@_N<>@0Wpz*7_3aUA;>+v&U8o8((50wLlm&(q*`ABa@O<u(n9?JgE+72Gz#uc3_7 zO(aB`hqnj4iLxROov!E6Oq)!EPkTqS-?l>9g@6C#Cf#m+zx^)xHr)nQT<y{wZm<m^ zQN~%xs^1R7+p+D(O(ct5I6LiVwCGPu(vP(LMudxG-FLX%{XDA4^c@jZ*We%zr=AF- z9ySrE=*Ni&rrY;p>zBlvs4Rp(5m@Q%LO1NluG!7X!rEOHw*B0p9TV#^G$ZC74Z^{t z!GB0I{7t|c$pf<V*lC-b%Wldn1XE-x!e2V89WQx8W<Rs?#<*!Tck=`uz|)=^oRh(7 zo_ai3t*m{57-X=j_XeX&aS9*s-y52+ewx&(!+|^<l~-Jx2}O=(I($WOl|@iOTzAp~ zr;JO$OO$S7xfNv7=bI=8(4f|dhOE(R!+&}73Y|Q*^48N8FhyBhmN{%H;;brY(;vDT zF__Dj=I42MP>z^)<Qv`5^!envA%e#e4kizISgHo_{-6l0NNCZ&McB-iC2X%6-Jpl_ zijLqT6$u$vQm*6m3LFwO&=$!s_H?1pU|=1MwK%Fmcj!^}NOZCf2A2!S8X7ZZ`+wDv z>dc5YS24xNId0&X+HViDh1#m|fiPkAL?OK}SeXI>YGj7#=&SJ_*1FXvxJmM788$5I z-Tb^phE}VwGZ2IB{zCo}@mQ<rb(F0Fus3*w#WP#*45zJRc4Km9x}+cUs7Tvy+k9L1 z?O>IG+?0z~*I9Fy?9Hh6{rlw`yMMzcC~>&6bwh1Vi`b*Dt%>9KBk#|TOY9KbMQAbU zs>XEpZ_wK%=2WXVbVWEZ6*7nj8(K1VcC`WllS1-6!3bwWEna4FWcGVvLV1W@ze8L( zJV+L#S=TSwuVWxR8b!=?LSToi>mRr+E#jhju6!9+zB9LAefyqH_~8O~zJE?oiXhKq zV(rV)`!W+1rye1J4G;&PJ#KRFmn9AUq`;(Vq(>}h{+a{tj!KFj(`6Lvyandod~<vA z=Zye6w}A*`2KPkn0-4J=mswD^H^2VA4H{qqh5!|1aP*>Wkj51pY8%+#=bN8y-eK>s zI=GY~F0<UL${<w!_^_|jBY%Z36GgnuvULpS$=Ib7f@h)1k9~`yf=_@@;VT)jHcuQ> zjjJ9un40f3)X==hmIj5{1+sU@CL$F&P_30SmN(=|z?0e7X`y?guH?mW?mFYMP$eJ@ zK6qTy%qJ`OlI>M`Y33J>`TY=C{pj<rCCLU-K|S>&v;-1cf*nACi+>q#rvqj!zlCFY z0yt0zFud!PX6Dpn`Mk|(lgbtlPGt-3$f_}D)e;!sKy!AP=9h2)cj`~dt`~ui=^R#h zTGvqM{OGe}Y6r-8H2aIQ_c?8B-_YQn&*=L%eHQ5aaRTCh<-l^)Lr5rg^cl`>vE;5V zlj#}zepD?@sKtQ6d4G!`vr|2Wu`gpjfA&NmpMpjL6P08j<Yxf^u@)z@n`nj+c{MqD zjcEz);w*zR8TJEbS>RCfT1w7C9@0qXr4m6c!uM#bZyqOuS)PT?T!K@KE-g|-==70y z>{tZL)O7GoG0fPIE7xm<x;x)v>1PNsxcKUH>jtFjFQ7-$JAd@)(*j<_-EF5@R;%Ab zf9<ezeqtx_e;}0l;%Eull^PX2TePZFk}K8QPQg>azs#ssVKK$8qR*)!eah<$0(pL+ zaqbg>EWKi!I|F@-rg5WdtFWR!a&knXRo(pzNzgkY4fnHus*t#=;ekUJrXd=pQv6;f zwWT>_zr_vWDu4HlPXsjx;Nz>Qf4|{;pz2WUitybNk)Y?Gx=I+r+6!0&v*d2wN29QB zUUA?cIeX(I<$3%jdaAKyjINqU_9ghf6ZF)KQo!5Et?5}7MUI<?=NGFZwqg!D7soa1 z=cr~bB-7P)ZZs`vwXb~#)fv}bGi*uL@K*E<#*yE92Y)qET|={su9wu^^ZjDmtf`kG zB_Tt-<<)HAiwg*Wr<YRy!1}DAm&bA?jk|X7s#uDAeeV+LeCd+xt-5z#zv!6x2I8?% zgS=>-&o$tkyw+f2hi;oFjq}*`i>P^jT^JEy8xV|ZRbz-hsM*|o3F2SAkc1Lc2`)iN zqaTxDazzP?9j{A)qwA#AE&kYGed~=E!ACVF(6=O=tDD62^@E-+6|W3k$q&l8V{@+m zB5w#;##@QOs-T+ko+4ZN&3^%A{^<RK1B{0Qi~)xOi~_d<j03baC^RrKFfuYSF*7hV zD=;uHFexuhZ)8MabY&<qFfueSGB7eSGBGnSHMifv1CRt!I7TozI5tKzK{7ZoI5b2w zH8L|XK{+xtL^nk^LpV1!J|H|eMld-zHbyf+GB`0fG(<EtGBYtjIWjdwH$^x@I5##v zT?#KuWo~D5XdpQ=HkZK011o<^XjNAfhW9!rH|C|ujWLNaF%QlA9UqA%z7kD*rSbhX zCPofYD0EsnC<KJkrWA^Tw3H4MQCh+R!GVH;(n5<JRB+sh({>`*QQGeBH-j&0edpYB z&)Mrg`-WilUlKwREL6OV|AMEn&JvsfDlfpPO+f*!7{@1)94N$<M3;YGpj`!uaOJqG zw8fwVSAm<SEd^z`D%>3HYOn^k4tIsN98}<{akI2}P>I`syG*+lRN-21muT04YFr!c zB5e(*#kJ!u(5?q{xDMQT+6|x{*MmDp+W<D=dU0oI8$lDU4|j&P8MNTG<4)6V0-JHe zxKp&Ppba;IJ4xFPI&gnGa3^RxK^JZ>?l^5X=)vv79i#09eYhjI8QOj@fIEu&fOZfJ z;f~{`X}5r_xD&XewA;XT+)3OK+F>w)o5LNZ-2ryu=5dE;N5L4_1$KiyU@zDQ#=!*G z4-SAyFa-_*T}t1fFV|h^cJ&;3Ks~vZL~Er*)aq*4H4hC+6V!ibHFJeQDVehn%9UpY zX_iOO+G{Af-78evz*e9M>c$msa_@SDcvD<&#k_^w7jg+FS6aC9L@n=!6MiqWP`4yk z#3`(h^9DHa_A(2t*L8FaM~SzSU5}UQ|3e4l$gV4sB+-@FS_`9%Iz^!-dfs5+K%tz% z+RQ~+b|+lE*}{L!?>b5*oq)IkLVUc-!i54IC3Q|zTv1`jK49Tmv5snmM!`5xB6d>g zH6RSmiaUvLJueaPF$*6r>H>Q>PcN}<jW9S7a1!Cn+$Su2vaFL2b25=~lH=sXr*VYK ziGa^oxbgnhj&VTe5e6p=em=xMHbEyPPAr@V_-PAERr-I}MLwq6Wzs%t;nsu>C=nX> zf`#vQv5)41rOU5Xv2ErP3qK93WoON=0yzs0Kawj1+BS<UJo-(Joi#rR%76y1ov_rx z<D5>>2AT(}f%aH!j5VMZtOs>KyJJ0Q02@IgXab6WHp@*QM&N0;zN6K`%9yG)v+;AP z+RZ-vR#kt8*~_0*b(+1qq^ir3`bkyYmW;grY>y?UI#u;rvh=sAK1&{as;b|TKRZ+n zSX%k7szFOz7gY^eI`WsQEtY=P92a)+Y{ZSPB0hf+@#Q}eUw21*GZ%5|X2k7ZBfeXS z_~BK=9p-T_UL(E#HsZ%Oy51BxXzBBZw3&YM^dl>m0}Lf&P#Ldere8W{l$8R8lCkU= nlgU{7jCo{vOP}41-|<U%_#Y;|$p)8<$^#(?F$yImMNdWwU%u)p delta 5702 zcmV-M7P;xSl@GO*53uPkm%$|iDSxe5OONC>4!-xVsN1@Msfyoa?;aKz><*B90gE({ zLz08Ktm+cRkBVhy(m;OvlKd!_tK&v;*h^WWD2gH}zUt%C!&jg3WS1m|x+v1!(|MPq z`Jsx_-7zi?Nm}oon%(zNcE}(1Ns<=PzlXVh%#vt&{PF1@UwumBU45v_w14ES@ot|~ zhpf^~f|^#*<ocJ#eOl+y$@DsSOpnopW=Zs78B|0DE7x(L!I3cbNo2kMlLx#vo%L69 zvY0w0WZG`<la9w{H;ra|#q<lGZTO&9e|X&I#Prj}DY@`9d<|ioB~XHK;^Dcq-Qzw> z%jj9yu_rTo3&_{k>(^s9jDO4;&wq%Myp=V&#VIh3`Dy5xgJ~RE0Gw{Rn(4xK3Wp?- z@v^h|z@TGf`UX2*MguYK?|3c1tH?I1VwN9&AipX)fdzt07mLB@UM@mN;e&2evAOmY zfS{Zg-RCByw9)}jMZm+{HkkVPnE^lZZaaoUSmGOO6PuGgO^(E~&wt`$)Y%hnrc<5o zL|ZpGGe*hR80~o&EjBc^ch+M}LS`NGw!!?Z4Fx<>4x~b3y8Q-QkX4pNo!Bm+{SX&1 zDJP|G!kWqQXzqex^WtqKQ=KsiZRz;*fk&W}F4#c!uYM9Eqw&eKEg30ul;npx&+pm# z^jKxlJk5Ohw5(ai6@SUtllVL!!x`V3%y`xrMtoHil2K-L1@hXHZ8;Ge!^BoAwN=@n z`sA~eU=Yg(HnK!6%uMg^xYlR1oX!$itXMC+RvFFLW-IEVE&i%cj|J&Sj=bZ9wM7Nk zK3rwD9SxflB>C2{k*d?<p{lCj(h7BHPonf=5&hGSy=^i6&wr<HG5DLY9SP=ly`50W zlOpo=G>>l5yXOfrE8I0qn=x2f44<$iCm9#yIz~6lJ^<tqse&1TJ<1mYoDW3IgeCYP z1)EXXX>s`QS@hUob-r#{|8w9utt0p*9<g8I@9byOlHT(ojh^R87ta&h1`m}H8ZAf8 zSH-smPKDR2#eZQku2bqm-|ESZo;Jb;;|a$EUGrw}Q8F72oZLtw9+||-ks>Qfa!>O5 z9qbuv<jTb9lF5<JDUw)ip@35-lWTRlrpVWnFP1vjyN-#@T{!X7&W%_xpF4EA&ky6Y zC3aSARv&}@f?KFd6~!@#VgwXkhBm-kg`LE$<;zG|;(yJ3Fgqai$|L<g>uaG)m-Lvv z>4EVt2b^D6Ye`1*LG#8O#t_D)U#B41z+XR0<i!{<n%(aDjvkAq^#c}FaEreWP<Zo3 z=NtFHg2?f<lVj|t^25AwBgLFyeErL|?+;1A&21u8sX{bdXC87256<G4HN*JiS_KCo zX=)-+@_%#6v3Dea;K2mrG!9;|y&cs7VABXIoebFz87q(9j}^#qa&pd{HR3%a49nXj z;Fk*>t1t@?Tna%NxH<tk-Fc<MRfzHwIqtjZ0we>#@}3EVYkzn@Dx6xU7iazF6jMZ> zVjy`%>7WkoGAWw#sl%*`7tzqqNPdXM5-Jv-!GBN#<dAByiYf|`_fVj6X1eJS_wW{J znNrN^uJeRwnMP+)#W-}B7<@RwtI<`6k;kJNkZT)+e#Ud0_}*S8gRaJn7(rKpcJB#& zShwLch@Hoc#Og1-qA=|;5!8)aE$@JHw3K`td?H6a*8CY@%^TAwZ~=Un2E!?42Xk(9 ziGRX+PozZwRjMiGLMn-xlLi9I855bd1QJGp6||GPwwpX(%-FQ9|IB6?S3+67V#G07 z0OD1=iY^3Yqu~d;Lai3mdMyZEAS3993FAfcJZb2)lC90oUpPPCU{AC4>Ni5+oCZRH zv5Lx7o=9L*&b&GdD+CuQTy(EjfW$Egvws!<WGGBeo-JE<o<$$EQh6uN<aw!%F3*p5 z3Est;22i;R5G@8j&DL{bWu8T_grijvztBDe9u1jn_c<j_aQckw?KEFKNHqh#5Hu~m z%qFCUb~BjcJ!BM|Lo`&uCG3jY7NbR9S+X)iwld0rpdG@JNJ9wx_&G5bOAwdJtA9cN zJT~nYJ`0h+Z=L~faM%(xJA!Ym))HD&Pz@_J$HU*B9)5aAXiL1K1ofC7%5s;->7gj9 z-Kl%{{>OON&>9gvWOcRsBADGSJ=BzI#=Ca+?ZbZ`K62%LmyagL<DtweA(a)H<x){L zjgag8xoKP<s^jthad4WXhf43T9DfY2XXUkj4oSM!do*_XwZGuCP-^5^XazgTgLq1b zAjj;Gr1!FwY=uItX&T1_KVYg-j`dv-B!n{_i_hY3NB*VL2Z-V$QD#D=OaSD{S`5&f z%8H9*8FG$MbfYxQMzFi3Lz0|uF@w4}UbQ$Rws9<{)FJ28b5soBHWq`SFMm~|7RBU9 zHv6jdECm`93g|o<^qfICjk7m3b{@=EDif*Qt?l0}g46t#5Aax?5D%7BN(?59r1)Gf z-Q;&bCaH}VTGFHh=tc>IuX5-mbQblEpy*Us;zo(QSuM)mGJirtBCSdq!nAI+p~V6c z7#>MKioa@0rq#K23P5IE7Jm}}K4aCd0JwFZwY1>bst~8yuUn1{u@4SnR2Q-CsnE*J z<IqH`f!|E<y60g$K^AJz?`MS{mj+f{Ed_@W=UH<cAR^~bR5-8szng&H8Qjcc+PY~9 zr2yrIfvN<10$pd_b}JvM`xjM*;6>QYQeMCFg8D}6xy;u6pfX>pg@0?etcbpDLf3_x z1*DjXR6dLz_>zJ%8PojiP^A!5m@YGfBUzPr;N44)IX$AKuIBmg^o>3vZ&%MjbkAN@ ziHrWh)<mhTiSI7lYkfs00ym(+6dD${jFe{~qxG2bOQW3QJhy*+zk8w0i|?o^uuG>W zD{>UK`P+6PLlF5#GJn3#-{JDv`)32$(tIfy8AToxyXx?)h1M+xFEY3=g>Lpb6;;|t zoyLvcuKPg0iP{-gPiH&oc+w|iv<0WG0cFEUTTz@F++k}5lI6Fqz@IsJDQ<edZE|{) zb>fLPt})t{9QV?P;FvPJiD5{RHBN|WW}29Dyb1B=*$?eJDSwT?L5Ov+)Z!8xQ;3#8 zw)KksN0^w$rGDPH23*zn8QuNrxXPxhT!I>9tI|*5MYv0~CcY87MYzY~auTXx_RxIl zjdr=t)OmyGxw`3@VHcwg+h9KTj`G&c{{CWid7g4FjJ~)(&o%2!#l@NqMJS>-qphyM zNSw6j%jp4eXMd#^=-j^{UUHKczuhXTur5aq0N}ctg+}OMCnN=ghgB92I5e32M(C1T zak5csKxP~b$k>NW`-pyB#r-;--97AN)eB0Fcl>nb8YpdgNdseqV$?Dz=N@`@lFlN} z*G5gEV-?)0_xzS6(c-GVoUS8fdKb>a+wg&+$in^JUw<mn{~GT4v{|Seli=GpPG(xf z^18tyWoDB*&8-;$UdvB<vnn$oIY|j4;5SCiu;9Fuovo@)8mDO;voF-a8DHWX|D{M@ z)9b4CqC&bxgH_-FT=Fp*QN$$$ggmAem!e4L01DYv=RCKoH*>OeEwJ@BhrY{i*uim< z$MJhamVZTW-h~2FR9KUo2K0TFjJ*!tk3eW35r{kr-Khao`_wYj9ruwfk5`>OJ;=fN z4VbYs-_KLT55VP<SgMd?)?1G-{0qc*5aV&duN0p=z=9e6VTy|-r(#4(z`0?9KUcM( z^_U=o>1-g&*2T+>TmJtc_?cT*qKC3RWM#42XAjjOuX9|c2=(Is0Fi$7%CikS6iNX# zlYu2Df3;Z6kL<P$zwcjR1O@5|o<C&EW4k~P?*YxB2#^Hbo|2%@+L|3<<&RqS&ReAY z-$RP@&Ukh<$vfm^iKHls{E4Le>HaSHMv6d)xGst;xIY9UlW~=1L7DDjk=4O{8~hl` zINL=+WJP#?)VnAzi_qz2nasS)Mfh@XMEfZff6}h}7dPu}_w)TflW(##sN;H{?Qw%N zh(s0V6|4R@jvuFPn0ApY_u=BSqtU8At4Kf64m%O9lFiWLc8|-XW;66eRDFwsBAf>z zOnTf!pkkP2BAEU#Or2j6@1m*{{zPD<w+r2{C%YE6C<|-%dDsn0k9N$g%g~IN2Q&x= zf7b>h&G0t?ZzNC1(o?T(aw)s3@(@gsnF#;gTkUws3o?g=mAA&tlX+NX@Bp6n(%^y& zR?9r#!5U@l3&bFYRf9JeU5Zoqfd4+wg!R*;Q9Tai`KY|&>P#qdGV}3o6jxaW6~uKf zJ#ebH0=z_78p~9WO@G-%1%L*%&NO6=e_k8Tqj%`!v5mK0Zh$Gu<EkoPQxWHNNt=Gx zH;BPPzBRun!lQD;d?4TGo~ExS*DVn|mT)k8$iq@Kg7-&7XhlMY{w>3Hu`FQ+)#?^K zT-0;~AE`*lxR!DouXo^(sFAiv#%Z7ng$4ubWUR$e9lGOyvL~XGeK5FQK-SQhe=$33 zmedzUe7K1zM$Tym$JBm%n61=KO;3agb07-og~7%Y5Kt2{Oiy2p_ps5OKEq9tKdZ21 zS?}i8H8QkXO}&8_^p98aUx>#>&F`aZ6M%!kBdnfD!84q;lEqEQgXxoD)RQ7@Kcz+5 z4Bcpzf!tJxch_0-kQ~gU55wp6e;d2w7btPKvvp%*&WqTi@2!dB_z&Kn@7LHNxXaLD z(p9bLA3vbCE6k}@ap=l$W-4S55w^5s>Fs6(0w#mxdxjCth+4kQ<j5Qj#Dww?y?=+e za(IxeNV9HOvtQ3ZdNzuf+l0Ul+15XBTUN$p{aX2Qu6!46!TR<so$&Vyf86CdK`DYf zlZ&k{EAPu()SP;R1U5h%eD=I6z+aX$_>%&Ys*#?sp!sVKyay^NeoU89Z1WbFd-sp~ zyZ_z^urm!rAal4Uav#V-#)Zs-rn~#`=QL=62^a!YRKdxMc0m@`aHuq}!N2Z)xch{? z!{*>pinz)PuPTR7`QyXBe@;&n!d#Sbn&;aXF0-*~DFn|#m7j(VM<t&Cp~6=(Vr`x{ zss>j*?JzYzYN(+_nXe5BvrA;}kxfJ@bf8)*XDn~Xm4GLUvGYpzM%~DZ)6)0GXQ5g^ z8hrA&rkPJx@Fm;p?Apw~I_3{!Wc9Ppzm_ChNCnM2OwbZYYzg)Ne+4dPz@3knwfq*2 z<q6<GA;9piH=3DqljZX^r%ftbKsc8zxFeg!pi^sLfCJ6hb(&wp0o-{wE4y6;{z&Jr z$<w-lLgz<cC381I#-rIUoV_n;Q}=-e|Nn}<|D(?eo&TJH_**%!+Vl_-iXDB%i(4$Y z>&s<!!G4@nM-yr>e_(LgV#xelk74Y~n9rXB5y+>Yk-$VH83@HiKtQa+$>L_3VMN|d zj$UI{fx9@%;7o?Y$XOOR)Vx-b^N@!$(s`*wP>b+08ta?K*<hAup$nJb6r)Rv6cKuT z;vIVy!7?=;eNzlGHsr?j+Mw>i_gMNBf($OcI{mf*>4z)mf6?>-efqM3S8;dStB%#` z&(L2xES+E2N&FcIrM@^?gLb1vMXwfZDwX6)^|4p*6!0%Is#RD_@vG=_u1H_-IxEus z=0fA#Cj=ZQ`Zxo93zJ&iSOvA#P@ITpRsXm^5=2MHIy@}?sY2qefd>v<n3iamN{h63 zlhoGcl>HXBe~7EXH$D;6Ab^jrrvCkg^O34Uxi7;{FGPZ#gX$__2x~835zLbNZ6A%o zzInxggXE0H;TVm>Z=&Z0TgK?7iDX}b?>j+H%_sxBo!y#UWKra}Wqf_HI$|s4uyb+T zz<!Qu=1MYsW0zLbl1}^DcT~M`-7Uk`WDRdc-(Z~hf4z5vkWqIHW*ObAse9o2#kN_~ ztVK#fhWf~>*}^v$5L$P7DGg7o&zdKO1zkzwZe6@OmZI3+yM#Jlx+Djy9^K#Hbj*AM z@z|(AUNz5`8t_iuYOt|Gw{4WgMeK%E)O@}zj0mtT2*!<SFvP#B**ts=;$Od!gc4K> zE<s7FNS~8&b_t7}ZcBlqo21hn{@7rB+l^PjXEi0zw<Nu5+Qbdblb){?Zw%eYkIK1I zd#V2_ZwOh%sl;H_P)&JHk+1#cukonp;FB?x6oVFwhZc+hhZc+iw-$^8pfxBmF)=bS zFfcVVGcqeMFfcGFFHLV`L}7GgC^9fIGBGhSGB7YTG&3@{-oXQq1W`sdI5|c}GeR&% zH8nCdI7Ko!I72}-I5R~vMKne*HbXujJVrJ+IYve^LNG=(H8M3gMKU=!LqRn-Get8+ zG)6EsLq1&!FHB`_XLM*FIWset;g|y{e<hVkXcbu$hVQA$q=_ak8jUeg6V2$$;QTbv zXpF`=&Kif9v`x|UqUo(5DBTF6!G?x_5X6NjiXsQPaUr50l7+fy1h=l-+MA|vqw#*f zS$sL?t9tdS?)mG!5X@$h5Rzb_>TUcNJdJgh;0#cC5l(Fiig2@Vd?G0ZCAitqf91~8 zmVz=|CGH2>SztD<3O7z$4k~c-abvV|z+7An?i_6;sKPD4jnd{oHEuEPEbTlnAJ>FC zLt6tD;978}X%~W8Tr2Jr?IKWzYr~DuE(Z0uJnkfI16YFV#GRmB3YOuzaK~vIK@)C0 z?ilTIumZOMca*jnwBQQ3Beboce+{=0cbK*vbl|q)4$-azdE7SKLE28xh1-i8rtJnj zxP7<-w5z~s+#%e4+BIM;?l5j2?K-d?cLcYWb^|Ej#&CORH-b&Laold&UeE_NgDs#R zYz5oE0N4%&!49w!41rxhm(q9W%XL?}T|I{$P*1KU(OPK{wYpk%%|nCIe*`sJ&0Jwn zO2#aNO66HW8s!nR_8N+A_Z-zauoh^7x^cyu+`C>O-W1ncF>aywnq0!k)fVnNQ_K6| zgx^an)J@2haSAKsya7(Uy~0BCPdYl6qr}_EuE$Gt|DywPWY?8RlITiot%cqOouW_^ zJ#VnEqexC+ZRDaXyAv*7e_>(xjgFE@Cm^nX5FhWb5Z8=~)HzXcMTH@IkA)vgbyO?V z3kHA^v588r0by`f+)0G%d5M7cS@`v`F3`_;dWm&wgu#h`lL%+#zTLuwM>=^ACle_r zIZj@DKaOxY5%6IPSH9ocK@R9V!r+9#M?(B#6LeDI#KMVyAG0t~f1{6`=3}~DChemZ zCI@vuiO{&uTe!7_eKZ#=ef(Y(+h#7Y@Tj1coi$eqiY@&0i(CoNwpnK3$s0L#*4$1| z0W^5+gyj~Vmgp31pgAxHXphy#xDeEWMW7C7cdQ2uU<p_XmH|aTo8@v4Bk(-0?`XF0 zWwWXlvw;y+t!6(@f2nFSdpoVF-R%FfsyZyGA5yi_lEU}T<}Eqep{mo8iGNjfS@Phb zs%}d@w5#f|w0cI>DodMxSGC&G!Y5U0EWOke7xuT45m#<RTzwUB{d2@0`G`NqBPJ&! zZoZC~`WkWjUBsR0h<j<o{r@5!e$@4bz%EN){!N?dH%~uuD#d`IWDF|fmCW=@$BeRa sz)&)lJ!3K%Yo9TXENAJ9yYV}wtHL)SD#;+1j>-ce2r>#KB}Gq03QL9(WdHyG diff --git a/parser/techdoc.tex b/parser/techdoc.tex index 024e898e6..d7116450f 100644 --- a/parser/techdoc.tex +++ b/parser/techdoc.tex @@ -240,7 +240,7 @@ and may grant confined processes specific mount operations. The security model of the various versions of NFS is that files are looked up by name as usual, but after that lookup, each file is only -identified by a file handle in successive acesses. The file handle at a +identified by a file handle in successive accesses. The file handle at a minimum includes some sort of filesystem identifier and the file's inode number. In Linux, the file handles used by most filesystems also include the inode number of the parent directory; this may change in the @@ -816,7 +816,7 @@ one (this option may be used even if no profile by that name exists): \subsection{Anatomy of a Profile} -AppArmor profiles use a simple declaritive language, fully described in +AppArmor profiles use a simple declarative language, fully described in the apparmor.d(5) manual page. By convention, profiles are stored in /etc/{\H}apparmor.d/. The AppArmor parser supports a simple cpp-style include mechanism to allow sharing pieces of policy. A simple profile diff --git a/parser/tst/Makefile b/parser/tst/Makefile index e1bc3fffb..08d42f459 100644 --- a/parser/tst/Makefile +++ b/parser/tst/Makefile @@ -23,13 +23,13 @@ tests: error_output caching minimize equality dirtest parser_sanity GEN_TRANS_DIRS=simple_tests/generated_x/ simple_tests/generated_perms_leading/ simple_tests/generated_perms_safe/ simple_tests/generated_dbus gen_xtrans: $(GEN_TRANS_DIRS) - ./gen-xtrans.pl + ./gen-xtrans.py $(GEN_TRANS_DIRS): mkdir $@ gen_dbus: $(GEN_TRANS_DIRS) - ./gen-dbus.pl + ./gen-dbus.py error_output: $(PARSER) LANG=C ./errors.py -p "$(PARSER)" $(PYTEST_ARG) diff --git a/parser/tst/README b/parser/tst/README index 782b8fa91..0d9ff128a 100644 --- a/parser/tst/README +++ b/parser/tst/README @@ -10,7 +10,7 @@ against a different parser, or use a different set of profiles for the simple.pl test, you can change those settings in 'uservars.conf'. You can also override which parser is used through make by specifying -the PARSER veriable. For example, to run the tests on the system parser, +the PARSER variable. For example, to run the tests on the system parser, run 'make PARSER=/sbin/apparmor_parser'. Adding to the testsuite @@ -61,7 +61,7 @@ The simple script looks for a few special comments in the profile, expected parse result of PASS. - #=TODO -- marks the test as being for a future item to implement and - thus are expected testsuite failures and hsould be ignored. + thus are expected testsuite failures and should be ignored. - #=DISABLED -- skips the test, and marks it as a failed TODO task. Useful if the particular testcase causes the parser to infinite diff --git a/parser/tst/caching.py b/parser/tst/caching.py index 72e73e4e3..3d6f23c91 100755 --- a/parser/tst/caching.py +++ b/parser/tst/caching.py @@ -15,13 +15,11 @@ # - check cache not used if parser in $PATH is newer # - check cache used for force-complain, disable symlink, etc. -from argparse import ArgumentParser import os -import platform import shutil -import time import tempfile import unittest +from argparse import ArgumentParser import testlib @@ -51,7 +49,7 @@ class AAParserCachingCommon(testlib.AATestTemplate): do_cleanup = True def setUp(self): - '''setup for each test''' + """setup for each test""" global config # REPORT ALL THE OUTPUT @@ -73,13 +71,13 @@ class AAParserCachingCommon(testlib.AATestTemplate): self.cmd_prefix = [config.parser, '--config-file=./parser.conf', '--base', self.tmp_dir, '--skip-kernel-load'] if not self.is_apparmorfs_mounted(): - self.cmd_prefix += ['-M', './features_files/features.all'] + self.cmd_prefix.extend(('-M', './features_files/features.all')) # Otherwise get_cache_dir() will try to create /var/cache/apparmor # and will fail when the test suite is run as non-root. - self.cmd_prefix += [ + self.cmd_prefix.extend(( '--cache-loc', os.path.join(self.tmp_dir, 'cache') - ] + )) # create directory for cached blobs # NOTE: get_cache_dir() requires cmd_prefix to be fully initialized @@ -89,7 +87,7 @@ class AAParserCachingCommon(testlib.AATestTemplate): self.cache_file = os.path.join(self.cache_dir, PROFILE) def tearDown(self): - '''teardown for each test''' + """teardown for each test""" if not self.do_cleanup: print("\n===> Skipping cleanup, leaving testfiles behind in '%s'" % (self.tmp_dir)) @@ -98,7 +96,8 @@ class AAParserCachingCommon(testlib.AATestTemplate): shutil.rmtree(self.tmp_dir) def get_cache_dir(self, create=False): - cmd = [config.parser, '--print-cache-dir'] + self.cmd_prefix + cmd = [config.parser, '--print-cache-dir'] + cmd.extend(self.cmd_prefix) rc, report = self.run_cmd(cmd) if rc != 0: if "unrecognized option '--print-cache-dir'" not in report: @@ -114,7 +113,7 @@ class AAParserCachingCommon(testlib.AATestTemplate): return cache_dir def assert_path_exists(self, path, expected=True): - if expected is True: + if expected: self.assertTrue(os.path.exists(path), 'test did not create file %s, when it was expected to do so' % path) else: @@ -137,58 +136,57 @@ class AAParserCachingCommon(testlib.AATestTemplate): with open(features_path) as f: features = f.read() if expected: - self.assertEqual(expected_output, features, - "features contents differ, expected:\n%s\nresult:\n%s" % (expected_output, features)) + self.assertEqual( + expected_output, features, + "features contents differ, expected:\n%s\nresult:\n%s" % (expected_output, features)) else: - self.assertNotEqual(expected_output, features, - "features contents equal, expected:\n%s\nresult:\n%s" % (expected_output, features)) + self.assertNotEqual( + expected_output, features, + "features contents equal, expected:\n%s\nresult:\n%s" % (expected_output, features)) class AAParserBasicCachingTests(AAParserCachingCommon): - def setUp(self): - super(AAParserBasicCachingTests, self).setUp() - def test_no_cache_by_default(self): - '''test profiles are not cached by default''' + """test profiles are not cached by default""" cmd = list(self.cmd_prefix) - cmd.extend(['-q', '-r', self.profile]) + cmd.extend(('-q', '-r', self.profile)) self.run_cmd_check(cmd) self.assert_path_exists(os.path.join(self.cache_dir, PROFILE), expected=False) def test_no_cache_w_skip_cache(self): - '''test profiles are not cached with --skip-cache''' + """test profiles are not cached with --skip-cache""" cmd = list(self.cmd_prefix) - cmd.extend(['-q', '--write-cache', '--skip-cache', '-r', self.profile]) + cmd.extend(('-q', '--write-cache', '--skip-cache', '-r', self.profile)) self.run_cmd_check(cmd) self.assert_path_exists(os.path.join(self.cache_dir, PROFILE), expected=False) def test_cache_when_requested(self): - '''test profiles are cached when requested''' + """test profiles are cached when requested""" cmd = list(self.cmd_prefix) - cmd.extend(['-q', '--write-cache', '-r', self.profile]) + cmd.extend(('-q', '--write-cache', '-r', self.profile)) self.run_cmd_check(cmd) self.assert_path_exists(os.path.join(self.cache_dir, PROFILE)) def test_write_features_when_caching(self): - '''test features file is written when caching''' + """test features file is written when caching""" cmd = list(self.cmd_prefix) - cmd.extend(['-q', '--write-cache', '-r', self.profile]) + cmd.extend(('-q', '--write-cache', '-r', self.profile)) self.run_cmd_check(cmd) self.assert_path_exists(os.path.join(self.cache_dir, PROFILE)) self.assert_path_exists(os.path.join(self.cache_dir, '.features')) def test_features_match_when_caching(self): - '''test features file is written when caching''' + """test features file is written when caching""" self.require_apparmorfs() cmd = list(self.cmd_prefix) - cmd.extend(['-q', '--write-cache', '-r', self.profile]) + cmd.extend(('-q', '--write-cache', '-r', self.profile)) self.run_cmd_check(cmd) self.assert_path_exists(os.path.join(self.cache_dir, PROFILE)) self.assert_path_exists(os.path.join(self.cache_dir, '.features')) @@ -197,47 +195,47 @@ class AAParserBasicCachingTests(AAParserCachingCommon): class AAParserAltCacheBasicTests(AAParserBasicCachingTests): - '''Same tests as above, but with an alternate cache location specified on the command line''' + """Same tests as above, but with an alternate cache location specified on the command line""" def setUp(self): - super(AAParserAltCacheBasicTests, self).setUp() + super().setUp() alt_cache_loc = tempfile.mkdtemp(prefix='aa-alt-cache', dir=self.tmp_dir) os.chmod(alt_cache_loc, 0o755) self.unused_cache_loc = self.cache_dir - self.cmd_prefix.extend(['--cache-loc', alt_cache_loc]) + self.cmd_prefix.extend(('--cache-loc', alt_cache_loc)) self.cache_dir = self.get_cache_dir() def tearDown(self): - if len(os.listdir(self.unused_cache_loc)) > 0: - self.fail('original cache dir \'%s\' not empty' % self.unused_cache_loc) - super(AAParserAltCacheBasicTests, self).tearDown() + if os.listdir(self.unused_cache_loc): + self.fail("original cache dir '%s' not empty" % self.unused_cache_loc) + super().tearDown() class AAParserCreateCacheBasicTestsCacheExists(AAParserBasicCachingTests): - '''Same tests as above, but with create cache option on the command line and the cache already exists''' + """Same tests as above, but with create cache option on the command line and the cache already exists""" def setUp(self): - super(AAParserCreateCacheBasicTestsCacheExists, self).setUp() + super().setUp() self.cmd_prefix.append('--create-cache-dir') class AAParserCreateCacheBasicTestsCacheNotExist(AAParserBasicCachingTests): - '''Same tests as above, but with create cache option on the command line and cache dir removed''' + """Same tests as above, but with create cache option on the command line and cache dir removed""" def setUp(self): - super(AAParserCreateCacheBasicTestsCacheNotExist, self).setUp() + super().setUp() shutil.rmtree(self.cache_dir) self.cmd_prefix.append('--create-cache-dir') class AAParserCreateCacheAltCacheTestsCacheNotExist(AAParserBasicCachingTests): - '''Same tests as above, but with create cache option on the command line, - alt cache specified, and cache dir removed''' + """Same tests as above, but with create cache option on the command line, + alt cache specified, and cache dir removed""" def setUp(self): - super(AAParserCreateCacheAltCacheTestsCacheNotExist, self).setUp() + super().setUp() shutil.rmtree(self.cache_dir) self.cmd_prefix.append('--create-cache-dir') @@ -245,7 +243,7 @@ class AAParserCreateCacheAltCacheTestsCacheNotExist(AAParserBasicCachingTests): class AAParserCachingTests(AAParserCachingCommon): def setUp(self): - super(AAParserCachingTests, self).setUp() + super().setUp() r = testlib.filesystem_time_resolution() self.mtime_res = r[1] @@ -253,116 +251,102 @@ class AAParserCachingTests(AAParserCachingCommon): def _generate_cache_file(self): cmd = list(self.cmd_prefix) - cmd.extend(['-q', '--write-cache', '-r', self.profile]) + cmd.extend(('-q', '--write-cache', '-r', self.profile)) self.run_cmd_check(cmd) self.assert_path_exists(self.cache_file) - def _assertTimeStampEquals(self, time1, time2): - '''Compare two timestamps to ensure equality''' - - # python 3.2 and earlier don't support writing timestamps with - # nanosecond resolution, only microsecond. When comparing - # timestamps in such an environment, loosen the equality bounds - # to compensate - # Reference: https://bugs.python.org/issue12904 - (major, minor, _) = platform.python_version_tuple() - if (int(major) < 3) or ((int(major) == 3) and (int(minor) <= 2)): - self.assertAlmostEquals(time1, time2, places=5) - else: - self.assertEqual(time1, time2) - def _set_mtime(self, path, mtime): atime = os.stat(path).st_atime os.utime(path, (atime, mtime)) - self._assertTimeStampEquals(os.stat(path).st_mtime, mtime) + self.assertEqual(os.stat(path).st_mtime, mtime) def test_cache_loaded_when_exists(self): - '''test cache is loaded when it exists, is newer than profile, and features match''' + """test cache is loaded when it exists, is newer than profile, and features match""" self._generate_cache_file() cmd = list(self.cmd_prefix) - cmd.extend(['-v', '-r', self.profile]) + cmd.extend(('-v', '-r', self.profile)) self.run_cmd_check(cmd, expected_string='Cached reload succeeded') def test_cache_not_loaded_when_skip_arg(self): - '''test cache is not loaded when --skip-cache is passed''' + """test cache is not loaded when --skip-cache is passed""" self._generate_cache_file() cmd = list(self.cmd_prefix) - cmd.extend(['-v', '--skip-cache', '-r', self.profile]) + cmd.extend(('-v', '--skip-cache', '-r', self.profile)) self.run_cmd_check(cmd, expected_string='Replacement succeeded for') def test_cache_not_loaded_when_skip_read_arg(self): - '''test cache is not loaded when --skip-read-cache is passed''' + """test cache is not loaded when --skip-read-cache is passed""" self._generate_cache_file() cmd = list(self.cmd_prefix) - cmd.extend(['-v', '--skip-read-cache', '-r', self.profile]) + cmd.extend(('-v', '--skip-read-cache', '-r', self.profile)) self.run_cmd_check(cmd, expected_string='Replacement succeeded for') def test_cache_not_loaded_when_features_differ(self): - '''test cache is not loaded when features file differs''' + """test cache is not loaded when features file differs""" self._generate_cache_file() testlib.write_file(self.cache_dir, '.features', 'monkey\n') cmd = list(self.cmd_prefix) - cmd.extend(['-v', '-r', self.profile]) + cmd.extend(('-v', '-r', self.profile)) self.run_cmd_check(cmd, expected_string='Replacement succeeded for') def test_cache_writing_does_not_overwrite_features_when_features_differ(self): - '''test cache writing does not overwrite the features files when it differs and --skip-bad-cache is given''' + """test cache writing does not overwrite the features files when it differs and --skip-bad-cache is given""" self.require_apparmorfs() features_file = testlib.write_file(self.cache_dir, '.features', 'monkey\n') cmd = list(self.cmd_prefix) - cmd.extend(['-v', '--write-cache', '--skip-bad-cache', '-r', self.profile]) + cmd.extend(('-v', '--write-cache', '--skip-bad-cache', '-r', self.profile)) self.run_cmd_check(cmd, expected_string='Replacement succeeded for') self.assert_path_exists(features_file) # ensure that the features does *not* match the current features set self.compare_features_file(features_file, expected=False) def test_cache_writing_skipped_when_features_differ(self): - '''test cache writing is skipped when features file differs''' + """test cache writing is skipped when features file differs""" testlib.write_file(self.cache_dir, '.features', 'monkey\n') cmd = list(self.cmd_prefix) - cmd.extend(['-v', '--write-cache', '--skip-bad-cache', '-r', self.profile]) + cmd.extend(('-v', '--write-cache', '--skip-bad-cache', '-r', self.profile)) self.run_cmd_check(cmd, expected_string='Replacement succeeded for') self.assert_path_exists(self.cache_file, expected=False) def test_cache_writing_collision_of_features(self): - '''test cache writing collision of features''' + """test cache writing collision of features""" # cache dir with different features causes a collision resulting # in a new cache dir self.require_apparmorfs() features_file = testlib.write_file(self.cache_dir, '.features', 'monkey\n') new_file = self.get_cache_dir() - new_features_file = new_file + '/.features'; + new_features_file = new_file + '/.features' cmd = list(self.cmd_prefix) - cmd.extend(['-v', '--write-cache', '-r', self.profile]) + cmd.extend(('-v', '--write-cache', '-r', self.profile)) self.run_cmd_check(cmd, expected_string='Replacement succeeded for') self.assert_path_exists(features_file) self.assert_path_exists(new_features_file) self.compare_features_file(new_features_file) def test_cache_writing_updates_cache_file(self): - '''test cache writing updates cache file''' + """test cache writing updates cache file""" cache_file = testlib.write_file(self.cache_dir, PROFILE, 'monkey\n') orig_stat = os.stat(cache_file) cmd = list(self.cmd_prefix) - cmd.extend(['-v', '--write-cache', '-r', self.profile]) + cmd.extend(('-v', '--write-cache', '-r', self.profile)) self.run_cmd_check(cmd, expected_string='Replacement succeeded for') self.assert_path_exists(cache_file) stat = os.stat(cache_file) @@ -373,17 +357,17 @@ class AAParserCachingTests(AAParserCachingCommon): self.assertEqual(os.stat(self.profile).st_mtime, stat.st_mtime) def test_cache_writing_clears_all_files(self): - '''test cache writing clears all cache files''' + """test cache writing clears all cache files""" check_file = testlib.write_file(self.cache_dir, 'monkey', 'monkey\n') cmd = list(self.cmd_prefix) - cmd.extend(['-v', '--write-cache', '-r', self.profile]) + cmd.extend(('-v', '--write-cache', '-r', self.profile)) self.run_cmd_check(cmd, expected_string='Replacement succeeded for') self.assert_path_exists(check_file, expected=False) def test_profile_mtime_preserved(self): - '''test profile mtime is preserved when it is newest''' + """test profile mtime is preserved when it is newest""" expected = 1 self._set_mtime(self.abstraction, 0) self._set_mtime(self.profile, expected) @@ -391,7 +375,7 @@ class AAParserCachingTests(AAParserCachingCommon): self.assertEqual(expected, os.stat(self.cache_file).st_mtime) def test_abstraction_mtime_preserved(self): - '''test abstraction mtime is preserved when it is newest''' + """test abstraction mtime is preserved when it is newest""" expected = 1000 self._set_mtime(self.profile, 0) self._set_mtime(self.abstraction, expected) @@ -399,7 +383,7 @@ class AAParserCachingTests(AAParserCachingCommon): self.assertEqual(expected, os.stat(self.cache_file).st_mtime) def test_equal_mtimes_preserved(self): - '''test equal profile and abstraction mtimes are preserved''' + """test equal profile and abstraction mtimes are preserved""" expected = 10000 + self.mtime_res self._set_mtime(self.profile, expected) self._set_mtime(self.abstraction, expected) @@ -407,7 +391,7 @@ class AAParserCachingTests(AAParserCachingCommon): self.assertEqual(expected, os.stat(self.cache_file).st_mtime) def test_profile_newer_skips_cache(self): - '''test cache is skipped if profile is newer''' + """test cache is skipped if profile is newer""" self._generate_cache_file() profile_mtime = os.stat(self.cache_file).st_mtime + self.mtime_res @@ -416,7 +400,7 @@ class AAParserCachingTests(AAParserCachingCommon): orig_stat = os.stat(self.cache_file) cmd = list(self.cmd_prefix) - cmd.extend(['-v', '-r', self.profile]) + cmd.extend(('-v', '-r', self.profile)) self.run_cmd_check(cmd, expected_string='Replacement succeeded for') stat = os.stat(self.cache_file) @@ -425,7 +409,7 @@ class AAParserCachingTests(AAParserCachingCommon): self.assertEqual(orig_stat.st_mtime, stat.st_mtime) def test_abstraction_newer_skips_cache(self): - '''test cache is skipped if abstraction is newer''' + """test cache is skipped if abstraction is newer""" self._generate_cache_file() abstraction_mtime = os.stat(self.cache_file).st_mtime + self.mtime_res @@ -434,7 +418,7 @@ class AAParserCachingTests(AAParserCachingCommon): orig_stat = os.stat(self.cache_file) cmd = list(self.cmd_prefix) - cmd.extend(['-v', '-r', self.profile]) + cmd.extend(('-v', '-r', self.profile)) self.run_cmd_check(cmd, expected_string='Replacement succeeded for') stat = os.stat(self.cache_file) @@ -443,7 +427,7 @@ class AAParserCachingTests(AAParserCachingCommon): self.assertEqual(orig_stat.st_mtime, stat.st_mtime) def test_profile_newer_rewrites_cache(self): - '''test cache is rewritten if profile is newer''' + """test cache is rewritten if profile is newer""" self._generate_cache_file() profile_mtime = os.stat(self.cache_file).st_mtime + self.mtime_res @@ -452,15 +436,15 @@ class AAParserCachingTests(AAParserCachingCommon): orig_stat = os.stat(self.cache_file) cmd = list(self.cmd_prefix) - cmd.extend(['-v', '-r', '-W', self.profile]) + cmd.extend(('-v', '-r', '-W', self.profile)) self.run_cmd_check(cmd, expected_string='Replacement succeeded for') stat = os.stat(self.cache_file) self.assertNotEqual(orig_stat.st_ino, stat.st_ino) - self._assertTimeStampEquals(profile_mtime, stat.st_mtime) + self.assertEqual(profile_mtime, stat.st_mtime) def test_abstraction_newer_rewrites_cache(self): - '''test cache is rewritten if abstraction is newer''' + """test cache is rewritten if abstraction is newer""" self._generate_cache_file() abstraction_mtime = os.stat(self.cache_file).st_mtime + self.mtime_res @@ -469,15 +453,15 @@ class AAParserCachingTests(AAParserCachingCommon): orig_stat = os.stat(self.cache_file) cmd = list(self.cmd_prefix) - cmd.extend(['-v', '-r', '-W', self.profile]) + cmd.extend(('-v', '-r', '-W', self.profile)) self.run_cmd_check(cmd, expected_string='Replacement succeeded for') stat = os.stat(self.cache_file) self.assertNotEqual(orig_stat.st_ino, stat.st_ino) - self._assertTimeStampEquals(abstraction_mtime, stat.st_mtime) + self.assertEqual(abstraction_mtime, stat.st_mtime) def test_parser_newer_uses_cache(self): - '''test cache is not skipped if parser is newer''' + """test cache is not skipped if parser is newer""" self._generate_cache_file() @@ -489,7 +473,7 @@ class AAParserCachingTests(AAParserCachingCommon): cmd = list(self.cmd_prefix) cmd[0] = new_parser - cmd.extend(['-v', '-r', self.profile]) + cmd.extend(('-v', '-r', self.profile)) self.run_cmd_check(cmd, expected_string='Cached reload succeeded for') def _purge_cache_test(self, location): @@ -497,50 +481,50 @@ class AAParserCachingTests(AAParserCachingCommon): cache_file = testlib.write_file(self.cache_dir, location, 'monkey\n') cmd = list(self.cmd_prefix) - cmd.extend(['-v', '--purge-cache', '-r', self.profile]) + cmd.extend(('-v', '--purge-cache', '-r', self.profile)) self.run_cmd_check(cmd) # no message is output self.assert_path_exists(cache_file, expected=False) def test_cache_purge_removes_features_file(self): - '''test cache --purge-cache removes .features file''' + """test cache --purge-cache removes .features file""" self._purge_cache_test('.features') def test_cache_purge_removes_cache_file(self): - '''test cache --purge-cache removes profile cache file''' + """test cache --purge-cache removes profile cache file""" self._purge_cache_test(PROFILE) def test_cache_purge_removes_other_cache_files(self): - '''test cache --purge-cache removes other cache files''' + """test cache --purge-cache removes other cache files""" self._purge_cache_test('monkey') class AAParserAltCacheTests(AAParserCachingTests): - '''Same tests as above, but with an alternate cache location specified on the command line''' + """Same tests as above, but with an alternate cache location specified on the command line""" check_orig_cache = True def setUp(self): - super(AAParserAltCacheTests, self).setUp() + super().setUp() alt_cache_loc = tempfile.mkdtemp(prefix='aa-alt-cache', dir=self.tmp_dir) os.chmod(alt_cache_loc, 0o755) self.orig_cache_dir = self.cache_dir - self.cmd_prefix.extend(['--cache-loc', alt_cache_loc]) + self.cmd_prefix.extend(('--cache-loc', alt_cache_loc)) self.cache_dir = self.get_cache_dir(create=True) self.cache_file = os.path.join(self.cache_dir, PROFILE) def tearDown(self): - if self.check_orig_cache and len(os.listdir(self.orig_cache_dir)) > 0: - self.fail('original cache dir \'%s\' not empty' % self.orig_cache_dir) - super(AAParserAltCacheTests, self).tearDown() + if self.check_orig_cache and os.listdir(self.orig_cache_dir): + self.fail("original cache dir '%s' not empty" % self.orig_cache_dir) + super().tearDown() def test_cache_purge_leaves_original_cache_alone(self): - '''test cache purging only touches alt cache''' + """test cache purging only touches alt cache""" # skip tearDown check to ensure non-alt cache is empty self.check_orig_cache = False - filelist = [PROFILE, '.features', 'monkey'] + filelist = (PROFILE, '.features', 'monkey') for f in filelist: testlib.write_file(self.orig_cache_dir, f, 'monkey\n') @@ -582,6 +566,7 @@ def main(): return rc + if __name__ == "__main__": rc = main() exit(rc) diff --git a/parser/tst/equality.sh b/parser/tst/equality.sh index 5c5aa1d5e..a0badfa55 100755 --- a/parser/tst/equality.sh +++ b/parser/tst/equality.sh @@ -568,7 +568,7 @@ verify_binary_equality "set rlimit memlock <= 2GB" \ # Unfortunately we can not just compare an empty profile and hat to a # ie. "/t { ^test { /f r, }}" # to the second profile with the equivalent rule inserted manually -# because policy write permission "w" actually expands to mutiple permissions +# because policy write permission "w" actually expands to multiple permissions # under the hood, and the parser is not adding those permissions # to the rules it auto generates # So we insert the rule with "append" permissions, and rely on the parser @@ -643,6 +643,16 @@ verify_binary_equality "attachment slash filtering" \ @{FOO}=/foo /t @{BAR}/@{FOO} { }" +# This can potentially fail as ideally it requires a better dfa comparison +# routine as it can generates hormomorphic dfas. The enumeration of the +# dfas dumped will be different, even if the binary is the same +# Note: this test in the future will require -O filter-deny and +# -O minimize and -O remove-unreachable. +verify_binary_equality "mount specific deny doesn't affect non-overlapping" \ + "/t { mount options=bind /e/ -> /**, }" \ + "/t { audit deny mount /s/** -> /**, + mount options=bind /e/ -> /**, }" + if [ $fails -ne 0 ] || [ $errors -ne 0 ] then printf "ERRORS: %d\nFAILS: %d\n" $errors $fails 2>&1 diff --git a/parser/tst/errors.py b/parser/tst/errors.py index e0ed9a62a..3be714925 100755 --- a/parser/tst/errors.py +++ b/parser/tst/errors.py @@ -13,20 +13,21 @@ # # ------------------------------------------------------------------ -from argparse import ArgumentParser -import os -import unittest import subprocess +import unittest +from argparse import ArgumentParser + import testlib config = None + class AAErrorTests(testlib.AATestTemplate): def setUp(self): self.maxDiff = None self.cmd_prefix = [config.parser, '--config-file=./parser.conf', '-S', '-I', 'errors'] - def _run_test(self, profile, message=None, is_error=True): + def _run_test(self, profile, message='', is_error=True): cmd = self.cmd_prefix + [profile] (rc, out, outerr) = self._run_cmd(cmd, stdout=subprocess.DEVNULL) @@ -36,8 +37,14 @@ class AAErrorTests(testlib.AATestTemplate): else: self.assertEqual(rc, 0, report) - if message: - self.assertIn(message, outerr, report) + ignore_messages = ( + 'Cache read/write disabled: interface file missing. (Kernel needs AppArmor 2.4 compatibility patch.)\n', + ) + for ign in ignore_messages: + if ign in outerr: + outerr = outerr.replace(ign, '') + + self.assertEqual(message, outerr, report) def test_okay(self): self._run_test('errors/okay.sd', is_error=False) @@ -45,40 +52,40 @@ class AAErrorTests(testlib.AATestTemplate): def test_single(self): self._run_test( 'errors/single.sd', - "AppArmor parser error for errors/single.sd in profile errors/single.sd at line 3: Could not open 'failure'", + "AppArmor parser error for errors/single.sd in profile errors/single.sd at line 3: Could not open 'failure'\n", ) def test_double(self): self._run_test( 'errors/double.sd', - "AppArmor parser error for errors/double.sd in profile errors/includes/busted at line 66: Could not open 'does-not-exist'", + "AppArmor parser error for errors/double.sd in profile errors/includes/busted at line 66: Could not open 'does-not-exist'\n", ) def test_modefail(self): self._run_test( 'errors/modefail.sd', - "AppArmor parser error for errors/modefail.sd in profile errors/modefail.sd at line 6: syntax error, unexpected TOK_ID, expecting TOK_MODE", + "AppArmor parser error for errors/modefail.sd in profile errors/modefail.sd at line 6: syntax error, unexpected TOK_ID, expecting TOK_MODE\n", ) def test_multi_include(self): self._run_test( 'errors/multi_include.sd', - "AppArmor parser error for errors/multi_include.sd in profile errors/multi_include.sd at line 12: Could not open 'failure'", + "AppArmor parser error for errors/multi_include.sd in profile errors/multi_include.sd at line 12: Could not open 'failure'\n", ) def test_deprecation1(self): - self.cmd_prefix.extend(['--warn=deprecated']) + self.cmd_prefix.append('--warn=deprecated') self._run_test( 'errors/deprecation1.sd', - "Warning from errors/deprecation1.sd (errors/deprecation1.sd line 6): The use of file paths as profile names is deprecated. See man apparmor.d for more information", + "Warning from errors/deprecation1.sd (errors/deprecation1.sd line 6): The use of file paths as profile names is deprecated. See man apparmor.d for more information\n", is_error=False ) def test_deprecation2(self): - self.cmd_prefix.extend(['--warn=deprecated']) + self.cmd_prefix.append('--warn=deprecated') self._run_test( 'errors/deprecation2.sd', - "Warning from errors/deprecation2.sd (errors/deprecation2.sd line 6): The use of file paths as profile names is deprecated. See man apparmor.d for more information", + "Warning from errors/deprecation2.sd (errors/deprecation2.sd line 6): The use of file paths as profile names is deprecated. See man apparmor.d for more information\n", is_error=False ) diff --git a/parser/tst/gen-dbus.pl b/parser/tst/gen-dbus.pl deleted file mode 100755 index 1fe581081..000000000 --- a/parser/tst/gen-dbus.pl +++ /dev/null @@ -1,167 +0,0 @@ -#!/usr/bin/perl -# -# Copyright (c) 2013 -# Canonical, Ltd. (All rights reserved) -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of version 2 of the GNU General Public -# License published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, contact Canonical Ltd. -# - -use strict; -use Locale::gettext; -use POSIX; - -setlocale(LC_MESSAGES, ""); - -my $count=0; - -my $prefix="simple_tests/generated_dbus"; - -my @quantifier = ("", "deny", "audit"); -my @session = ("", "bus=session", "bus=system", "bus=accessibility"); -my @path = ("", "path=/foo/bar", "path=\"/foo/bar\""); -my @interface = ("", "interface=com.baz", "interface=\"com.baz\""); -my @member = ("", "member=bar", "member=\"bar\""); - -my @name = ("", "name=com.foo", "name=\"com.foo\""); -my @peer = map { "peer=($_)" } (@name, "label=/usr/bin/app", - "label=\"/usr/bin/app\"", - "name=com.foo label=/usr/bin/app", - "name=\"com.foo\" label=\"/usr/bin/app\""); - -# @msg_perms are the permissions that are related to sending and receiving -# messages. @svc_perms are the permissions related to services. -my @base_msg_perms = ("r", "w", "rw", "read", "receive", "write", "send"); -my @msg_perms = ("", @base_msg_perms, (map { "($_)" } @base_msg_perms), - "(write, read)", "(send receive)", "(send read)", - "(receive write)"); - -gen_files("message-rules", "PASS", \@quantifier, \@msg_perms, \@session, - [""], \@path, \@interface, \@member, \@peer); -gen_files("service-rules", "PASS", \@quantifier, ["bind"], \@session, - \@name, [""], [""], [""], [""]); -gen_files("eavesdrop-rules", "PASS", \@quantifier, ["eavesdrop"], \@session, - [""], [""], [""], [""], [""]); -gen_file("sloppy-formatting", "PASS", "", "(send , receive )", "bus=session", - "", "path =\"/foo/bar\"", "interface = com.foo", " member=bar", - "peer =( label= /usr/bin/app name =\"com.foo\")"); -gen_file("sloppy-formatting", "PASS", "", "bind", "bus =session", - "name= com.foo", "", "", "", ""); -gen_file("sloppy-formatting", "PASS", "", "eavesdrop", "bus = system", - "", "", "", "", ""); - -# Don't use the first element, which is empty, from each array since all empty -# conditionals would PASS but we want all FAILs -shift @msg_perms; -shift @name; -shift @path; -shift @interface; -shift @member; -shift @peer; -gen_files("message-incompat", "FAIL", \@quantifier, \@msg_perms, \@session, - \@name, [""], [""], [""], [""]); -gen_files("service-incompat", "FAIL", \@quantifier, ["bind"], \@session, - \@name, \@path, [""], [""], [""]); -gen_files("service-incompat", "FAIL", \@quantifier, ["bind"], \@session, - \@name, [""], \@interface, [""], [""]); -gen_files("service-incompat", "FAIL", \@quantifier, ["bind"], \@session, - \@name, [""], [""], \@member, [""]); -gen_files("service-incompat", "FAIL", \@quantifier, ["bind"], \@session, - \@name, [""], [""], [""], \@peer); -gen_files("eavesdrop-incompat", "FAIL", \@quantifier, ["eavesdrop"], \@session, - \@name, \@path, \@interface, \@member, \@peer); - -gen_files("pairing-unsupported", "FAIL", \@quantifier, ["send", "bind"], - \@session, ["name=sn", "label=sl"], [""], [""], [""], - ["peer=(name=pn)", "peer=(label=pl)"]); - -# missing bus= prefix -gen_file("bad-formatting", "FAIL", "", "send", "session", "", "", "", "", ""); -# incorrectly formatted permissions -gen_files("bad-perms", "FAIL", [""], ["send receive", "(send", "send)"], - ["bus=session"], [""], [""], [""], [""], [""]); -# invalid permissions -gen_files("bad-perms", "FAIL", [""], - ["a", "x", "Ux", "ix", "m", "k", "l", "(a)", "(x)"], [""], [""], - [""], [""], [""], [""]); - -gen_file("duplicated-conditionals", "FAIL", "", "bus=1 bus=2"); -gen_file("duplicated-conditionals", "FAIL", "", "name=1 name=2"); -gen_file("duplicated-conditionals", "FAIL", "", "path=1 path=2"); -gen_file("duplicated-conditionals", "FAIL", "", "interface=1 interface=2"); -gen_file("duplicated-conditionals", "FAIL", "", "member=1 member=2"); -gen_file("duplicated-conditionals", "FAIL", "", "peer=(name=1) peer=(name=2)"); -gen_file("duplicated-conditionals", "FAIL", "", "peer=(label=1) peer=(label=2)"); -gen_file("duplicated-conditionals", "FAIL", "", "peer=(name=1) peer=(label=2)"); - -print "Generated $count dbus tests\n"; - -sub print_rule($$$$$$$$$) { - my ($file, $quantifier, $perms, $session, $name, $path, $interface, $member, $peer) = @_; - - print $file " "; - print $file " ${quantifier}" if ${quantifier}; - print $file " dbus"; - print $file " ${perms}" if ${perms}; - print $file " ${session}" if ${session}; - print $file " ${name}" if ${name}; - print $file " ${path}" if ${path}; - print $file " ${interface}" if ${interface}; - print $file " ${member}" if ${member}; - print $file " ${peer}" if ${peer}; - print $file ",\n"; -} - -sub gen_file($$$$$$$$$$) { - my ($test, $xres, $quantifier, $perms, $session, $name, $path, $interface, $member, $peer) = @_; - - my $file; - unless (open $file, ">${prefix}/$test-$count.sd") { - print("couldn't open $test\n"); - exit 1; - } - - print $file "#\n"; - print $file "#=DESCRIPTION ${test}\n"; - print $file "#=EXRESULT ${xres}\n"; - print $file "#\n"; - print $file "/usr/bin/foo {\n"; - print_rule($file, $quantifier, $perms, $session, $name, $path, $interface, - $member, $peer); - print $file "}\n"; - close($file); - - $count++; -} - -sub gen_files($$$$$$$$$$) { - my ($test, $xres, $quantifiers, $perms, $sessions, $names, $paths, $interfaces, $members, $peers) = @_; - - foreach my $quantifier (@{$quantifiers}) { - foreach my $perm (@{$perms}) { - foreach my $session (@{$sessions}) { - foreach my $name (@{$names}) { - foreach my $path (@{$paths}) { - foreach my $interface (@{$interfaces}) { - foreach my $member (@{$members}) { - foreach my $peer (@{$peers}) { - gen_file($test, $xres, $quantifier, $perm, $session, $name, - $path, $interface, $member, $peer); - } - } - } - } - } - } - } - } -} diff --git a/parser/tst/gen-dbus.py b/parser/tst/gen-dbus.py new file mode 100755 index 000000000..544d0f64a --- /dev/null +++ b/parser/tst/gen-dbus.py @@ -0,0 +1,161 @@ +#!/usr/bin/python3 +# +# Copyright (c) 2013 Canonical, Ltd. (All rights reserved) +# Copyright (c) 2021 Christian Boltz +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of version 2 of the GNU General Public +# License published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, contact Canonical Ltd. +# + +from testlib import write_file + + +def get_rule(quantifier, perms, session, name, path, interface, member, peer): + + result = ' ' + + for part in (quantifier, 'dbus', perms, session, name, path, interface, member, peer): + if part: + result += ' %s' % part + + result += ',\n' + + return result + + +def gen_file(test, xres, quantifier, perms, session, name, path, interface, member, peer): + global count + + content = '' + content += '#\n' + content += '#=DESCRIPTION %s\n' % test + content += '#=EXRESULT %s\n' % xres + content += '#\n' + content += '/usr/bin/foo {\n' + content += get_rule(quantifier, perms, session, name, path, interface, member, peer) + content += '}\n' + + write_file('simple_tests/generated_dbus', '%s-%s.sd' % (test, count), content) + + count += 1 + + +def gen_files(test, xres, quantifiers, perms, sessions, names, paths, interfaces, members, peers): + for quantifier in quantifiers: + for perm in perms: + for session in sessions: + for name in names: + for path in paths: + for interface in interfaces: + for member in members: + for peer in peers: + gen_file(test, xres, quantifier, perm, session, name, path, interface, member, peer) + + +count = 0 + +quantifier = ('', 'deny', 'audit') +session = ('', 'bus=session', 'bus=system', 'bus=accessibility') +path = ['', 'path=/foo/bar', 'path="/foo/bar"'] +interface = ['', 'interface=com.baz', 'interface="com.baz"'] +member = ['', 'member=bar', 'member="bar"'] + +name = ['', 'name=com.foo', 'name="com.foo"'] +peer = [ + 'peer=()', + 'peer=(name=com.foo)', + 'peer=(name="com.foo")', + 'peer=(label=/usr/bin/app)', + 'peer=(label="/usr/bin/app")', + 'peer=(name=com.foo label=/usr/bin/app)', + 'peer=(name="com.foo" label="/usr/bin/app")', +] + +# msg_perms are the permissions that are related to sending and receiving +# messages. +msg_perms = [ + '', + 'r', + 'w', + 'rw', + 'read', + 'receive', + 'write', + 'send', + '(r)', + '(w)', + '(rw)', + '(read)', + '(receive)', + '(write)', + '(send)', + '(write, read)', + '(send receive)', + '(send read)', + '(receive write)', +] + +empty_tup = ('',) + +gen_files('message-rules', 'PASS', quantifier, msg_perms, session, + empty_tup, path, interface, member, peer) +gen_files('service-rules', 'PASS', quantifier, ['bind'], session, + name, empty_tup, empty_tup, empty_tup, empty_tup) +gen_files('eavesdrop-rules', 'PASS', quantifier, ['eavesdrop'], session, + empty_tup, empty_tup, empty_tup, empty_tup, empty_tup) +gen_file('sloppy-formatting', 'PASS', '', '(send , receive )', 'bus=session', + '', 'path ="/foo/bar"', 'interface = com.foo', ' member=bar', + 'peer =( label= /usr/bin/app name ="com.foo")') +gen_file('sloppy-formatting', 'PASS', '', 'bind', 'bus =session', + 'name= com.foo', '', '', '', '') +gen_file('sloppy-formatting', 'PASS', '', 'eavesdrop', 'bus = system', + '', '', '', '', '') + +# Don't use the empty element from each array since all empty conditionals would PASS but we want all FAILs +msg_perms.remove('') +name.remove('') +path.remove('') +interface.remove('') +member.remove('') +peer.remove('peer=()') + +gen_files('message-incompat', 'FAIL', quantifier, msg_perms, session, name, empty_tup, empty_tup, empty_tup, empty_tup) +gen_files('service-incompat', 'FAIL', quantifier, ('bind',), session, name, path, empty_tup, empty_tup, empty_tup) +gen_files('service-incompat', 'FAIL', quantifier, ('bind',), session, name, empty_tup, interface, empty_tup, empty_tup) +gen_files('service-incompat', 'FAIL', quantifier, ('bind',), session, name, empty_tup, empty_tup, member, empty_tup) +gen_files('service-incompat', 'FAIL', quantifier, ('bind',), session, name, empty_tup, empty_tup, empty_tup, peer) +gen_files('eavesdrop-incompat', 'FAIL', quantifier, ('eavesdrop',), session, name, path, interface, member, peer) + +gen_files('pairing-unsupported', 'FAIL', quantifier, ('send', 'bind'), + session, ('name=sn', 'label=sl'), empty_tup, empty_tup, empty_tup, + ('peer=(name=pn)', 'peer=(label=pl)')) + +# missing bus= prefix +gen_file('bad-formatting', 'FAIL', '', 'send', 'session', '', '', '', '', '') +# incorrectly formatted permissions +gen_files('bad-perms', 'FAIL', empty_tup, ('send receive', '(send', 'send)'), + ('bus=session',), empty_tup, empty_tup, empty_tup, empty_tup, empty_tup) +# invalid permissions +gen_files('bad-perms', 'FAIL', empty_tup, + ('a', 'x', 'Ux', 'ix', 'm', 'k', 'l', '(a)', '(x)'), empty_tup, empty_tup, + empty_tup, empty_tup, empty_tup, empty_tup) + +gen_file('duplicated-conditionals', 'FAIL', '', 'bus=1 bus=2', '', '', '', '', '', '') +gen_file('duplicated-conditionals', 'FAIL', '', 'name=1 name=2', '', '', '', '', '', '') +gen_file('duplicated-conditionals', 'FAIL', '', 'path=1 path=2', '', '', '', '', '', '') +gen_file('duplicated-conditionals', 'FAIL', '', 'interface=1 interface=2', '', '', '', '', '', '') +gen_file('duplicated-conditionals', 'FAIL', '', 'member=1 member=2', '', '', '', '', '', '') +gen_file('duplicated-conditionals', 'FAIL', '', 'peer=(name=1) peer=(name=2)', '', '', '', '', '', '') +gen_file('duplicated-conditionals', 'FAIL', '', 'peer=(label=1) peer=(label=2)', '', '', '', '', '', '') +gen_file('duplicated-conditionals', 'FAIL', '', 'peer=(name=1) peer=(label=2)', '', '', '', '', '', '') + +print('Generated %s dbus tests' % count) diff --git a/parser/tst/gen-xtrans.pl b/parser/tst/gen-xtrans.pl deleted file mode 100755 index 8cf077f4b..000000000 --- a/parser/tst/gen-xtrans.pl +++ /dev/null @@ -1,235 +0,0 @@ -#!/usr/bin/perl - -use strict; -use Locale::gettext; -use POSIX; - -setlocale(LC_MESSAGES, ""); - -my $prefix="simple_tests/generated_x"; -my $prefix_leading="simple_tests/generated_perms_leading"; -my $prefix_safe="simple_tests/generated_perms_safe"; - -my @trans_types = ("p", "P", "c", "C", "u", "i"); -my @modifiers = ("i", "u"); -my %trans_modifiers = ( - "p" => \@modifiers, - "P" => \@modifiers, - "c" => \@modifiers, - "C" => \@modifiers, - ); - -my @targets = ("", "target", "target2"); -my @null_target = (""); - -my %named_trans = ( - "p" => \@targets, - "P" => \@targets, - "c" => \@targets, - "C" => \@targets, - "u" => \@null_target, - "i" => \@null_target, - ); - -my %safe_map = ( - "p" => "unsafe", - "P" => "safe", - "c" => "unsafe", - "C" => "safe", - "u" => "", - "i" => "", - ); - -my %invert_safe = ( - "safe" => "unsafe", - "unsafe" => "safe", - ); - -# audit qualifier disabled for now it really shouldn't affect the conflict -# test but it may be worth checking every once in awhile -#my @qualifiers = ("", "owner", "audit", "audit owner"); -my @qualifiers = ("", "owner"); - -my $count = 0; - -gen_conflicting_x(); -gen_overlap_re_exact(); -gen_dominate_re_re(); -gen_ambiguous_re_re(); -gen_leading_perms("exact", "/bin/cat", "/bin/cat"); -gen_leading_perms("exact-re", "/bin/*", "/bin/*"); -gen_leading_perms("overlap", "/*", "/bin/cat"); -gen_leading_perms("dominate", "/**", "/*"); -gen_leading_perms("ambiguous", "/a*", "/*b"); -gen_safe_perms("exact", "PASS", "", "/bin/cat", "/bin/cat"); -gen_safe_perms("exact-re", "PASS", "", "/bin/*", "/bin/*"); -gen_safe_perms("overlap", "PASS", "", "/*", "/bin/cat"); -gen_safe_perms("dominate", "PASS", "", "/**", "/*"); -gen_safe_perms("ambiguous", "PASS", "", "/a*", "/*b"); -gen_safe_perms("exact", "FAIL", "inv", "/bin/cat", "/bin/cat"); -gen_safe_perms("exact-re", "FAIL", "inv", "/bin/*", "/bin/*"); -gen_safe_perms("overlap", "PASS", "inv", "/*", "/bin/cat"); -gen_safe_perms("dominate", "FAIL", "inv", "/**", "/*"); -gen_safe_perms("ambiguous", "FAIL", "inv", "/a*", "/*b"); - -print "Generated $count xtransition interaction tests\n"; - -sub gen_list { - my @output; - foreach my $trans (@trans_types) { - if ($trans_modifiers{$trans}) { - foreach my $mod (@{$trans_modifiers{$trans}}) { - push @output, "${trans}${mod}x"; - } - } - push @output, "${trans}x"; - } - return @output; -} - -sub print_rule($$$$$$) { - my ($file, $leading, $qual, $name, $perm, $target) = @_; - if ($leading) { - print $file "\t${qual} ${perm} ${name}"; - } else { - print $file "\t${qual} ${name} ${perm}"; - } - if ($target ne "") { - print $file " -> $target"; - } - print $file ",\n"; -} - -sub gen_file($$$$$$$$$$$$) { - my ($name, $xres, $leading1, $qual1, $rule1, $perm1, $target1, $leading2, $qual2, $rule2, $perm2, $target2) = @_; - -# print "$xres $rule1 $perm1 $target1 $rule2 $perm2 $target2\n"; - - my $file; - unless (open $file, ">$name") { - print("couldn't open $name\n"); - exit 1; - } - - print $file "#\n"; - print $file "#=DESCRIPTION ${name}\n"; - print $file "#=EXRESULT ${xres}\n"; - print $file "#\n"; - print $file "/usr/bin/foo {\n"; - print_rule($file, $leading1, $qual1, $rule1, $perm1, $target1); - print_rule($file, $leading2, $qual2, $rule2, $perm2, $target2); - print $file "}\n"; - close($file); - - $count++; -} - -#NOTE: currently we don't do px to cx, or cx to px conversion -# so -# /foo { -# /* px -> /foo//bar, -# /* cx -> bar, -# -# will conflict -# -#NOTE: conflict tests don't tests leading permissions or using unsafe keywords -# It is assumed that there are extra tests to verify 1 to 1 coorispondance -sub gen_files($$$$) { - my ($name, $rule1, $rule2, $default) = @_; - - my @perms = gen_list(); - -# print "@perms\n"; - - foreach my $i (@perms) { - foreach my $t (@{$named_trans{substr($i, 0, 1)}}) { - foreach my $q (@qualifiers) { - foreach my $j (@perms) { - foreach my $u (@{$named_trans{substr($j, 0, 1)}}) { - foreach my $r (@qualifiers) { - my $file="${prefix}/${name}-$q$i$t-$r$j$u.sd"; -# print "$file\n"; - - #override failures when transitions are the same - my $xres = ${default}; - if ($i eq $j && $t eq $u) { - $xres = "PASS"; - } - - -# print "foo $xres $rule1 $i $t $rule2 $j $u\n"; - gen_file($file, $xres, 0, $q, $rule1, $i, $t, 0, $r, $rule2, $j, $u); - } - } - } - } - } - } - -} - -sub gen_conflicting_x { - gen_files("conflict", "/bin/cat", "/bin/cat", "FAIL"); -} - -sub gen_overlap_re_exact { - - gen_files("exact", "/bin/cat", "/bin/*", "PASS"); -} - -# we currently don't support this, once supported change to "PASS" -sub gen_dominate_re_re { - gen_files("dominate", "/bin/*", "/bin/**", "FAIL"); -} - -sub gen_ambiguous_re_re { - gen_files("ambiguous", "/bin/a*", "/bin/*b", "FAIL"); -} - - -# test that rules that lead with permissions don't conflict with -# the same rule using trailing permissions. -sub gen_leading_perms($$$) { - my ($name, $rule1, $rule2) = @_; - - my @perms = gen_list(); - - foreach my $i (@perms) { - foreach my $t (@{$named_trans{substr($i, 0, 1)}}) { - foreach my $q (@qualifiers) { - my $file="${prefix_leading}/${name}-$q$i$t.sd"; -# print "$file\n"; - - gen_file($file, "PASS", 0, $q, $rule1, $i, $t, 1, $q, $rule2, $i, $t); - } - } - } -} - -# test for rules with leading safe or unsafe keywords. -# check they are equivalent to their counter part, -# or if $invert that they properly conflict with their counterpart -sub gen_safe_perms($$$$$) { - my ($name, $xres, $invert, $rule1, $rule2) = @_; - - my @perms = gen_list(); - - foreach my $i (@perms) { - foreach my $t (@{$named_trans{substr($i, 0, 1)}}) { - foreach my $q (@qualifiers) { - my $qual = $safe_map{substr($i, 0, 1)}; - if ($invert) { - $qual = $invert_safe{$qual}; - } - if (! $invert || $qual) { - my $file="${prefix_safe}/${name}-$invert-$q${qual}-rule-$i$t.sd"; -# print "$file\n"; - gen_file($file, $xres, 0, "$q $qual", $rule1, $i, $t, 1, $q, $rule2, $i, $t); - $file="${prefix_safe}/${name}-$invert-$q$qual${i}-rule-$t.sd"; - gen_file($file, $xres, 0, $q, $rule1, $i, $t, 1, "$q $qual", $rule2, $i, $t); - } - - } - } - } -} diff --git a/parser/tst/gen-xtrans.py b/parser/tst/gen-xtrans.py new file mode 100755 index 000000000..6a915f05f --- /dev/null +++ b/parser/tst/gen-xtrans.py @@ -0,0 +1,228 @@ +#!/usr/bin/python3 +# ------------------------------------------------------------------ +# +# Copyright (C) 2010-2011 Canonical Ltd. +# Copyright (C) 2020 Christian Boltz +# +# 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. +# +# ------------------------------------------------------------------ + +from testlib import write_file + +prefix = "simple_tests/generated_x" +prefix_leading = "simple_tests/generated_perms_leading" +prefix_safe = "simple_tests/generated_perms_safe" + +trans_types = ("p", "P", "c", "C", "u", "i") +modifiers = ("i", "u") +trans_modifiers = { + "p": modifiers, + "P": modifiers, + "c": modifiers, + "C": modifiers, +} + +targets = ("", "target", "target2") +# null_target uses "_" instead of "" because "" gets skipped in some for loops. Replace with "" when actually using the value. +null_target = ("_",) + +named_trans = { + "p": targets, + "P": targets, + "c": targets, + "C": targets, + "u": null_target, + "i": null_target, +} + +safe_map = { + "p": "unsafe", + "P": "safe", + "c": "unsafe", + "C": "safe", + "u": "", + "i": "", +} + +invert_safe = { + "safe": "unsafe", + "unsafe": "safe", + '': '', +} + +# audit qualifier disabled for now it really shouldn't affect the conflict +# test but it may be worth checking every once in awhile +# qualifiers = ("", "owner", "audit", "audit owner") +qualifiers = ("", "owner") + +count = 0 + + +def gen_list(): + output = [] + for trans in trans_types: + if trans in trans_modifiers: + for mod in trans_modifiers[trans]: + output.append("%s%sx" % (trans, mod)) + + output.append("%sx" % trans) + + return output + + +def test_gen_list(): + """test if gen_list returns the expected output""" + + expected = "pix pux px Pix Pux Px cix cux cx Cix Cux Cx ux ix".split() + actual = gen_list() + + if actual != expected: + raise Exception("gen_list produced unexpected result, expected %s, got %s" % (expected, actual)) + + +def build_rule(leading, qual, name, perm, target): + rule = '' + + if leading: + rule += "\t%s %s %s" % (qual, perm, name) + else: + rule += "\t%s %s %s" % (qual, name, perm) + + if target: + rule += " -> %s" % target + + rule += ",\n" + + return rule + + +def gen_file(name, xres, leading1, qual1, rule1, perm1, target1, leading2, qual2, rule2, perm2, target2): + global count + count += 1 + + content = '' + content += "#\n" + content += "#=DESCRIPTION %s\n" % name + content += "#=EXRESULT %s\n" % xres + content += "#\n" + content += "/usr/bin/foo {\n" + content += build_rule(leading1, qual1, rule1, perm1, target1) + content += build_rule(leading2, qual2, rule2, perm2, target2) + content += "}\n" + + write_file('', name, content) + + +# NOTE: currently we don't do px to cx, or cx to px conversion +# so +# /foo { +# /* px -> /foo//bar, +# /* cx -> bar, +# +# will conflict +# +# NOTE: conflict tests don't test leading permissions or using unsafe keywords +# It is assumed that there are extra tests to verify 1 to 1 correspondance +def gen_files(name, rule1, rule2, default): + perms = gen_list() + + for i in perms: + for t in named_trans[i[0]]: + if t == '_': + t = '' + for q in qualifiers: + for j in perms: + for u in named_trans[j[0]]: + if u == '_': + u = '' + for r in qualifiers: + file = prefix + '/' + name + '-' + q + i + t + '-' + r + j + u + '.sd' + + # override failures when transitions are the same + xres = default + if (i == j and t == u): + xres = "PASS" + + gen_file(file, xres, 0, q, rule1, i, t, 0, r, rule2, j, u) + + +def gen_conflicting_x(): + gen_files("conflict", "/bin/cat", "/bin/cat", "FAIL") + + +def gen_overlap_re_exact(): + gen_files("exact", "/bin/cat", "/bin/*", "PASS") + + +# we currently don't support this, once supported change to "PASS" +def gen_dominate_re_re(): + gen_files("dominate", "/bin/*", "/bin/**", "FAIL") + + +def gen_ambiguous_re_re(): + gen_files("ambiguous", "/bin/a*", "/bin/*b", "FAIL") + + +# test that rules that lead with permissions don't conflict with +# the same rule using trailing permissions. +def gen_leading_perms(name, rule1, rule2): + perms = gen_list() + + for i in perms: + for t in named_trans[i[0]]: + if t == '_': + t = '' + for q in qualifiers: + file = prefix_leading + '/' + name + '-' + q + i + t + ".sd" + gen_file(file, "PASS", 0, q, rule1, i, t, 1, q, rule2, i, t) + + +# test for rules with leading safe or unsafe keywords. +# check they are equivalent to their counterpart, +# or if $invert that they properly conflict with their counterpart +def gen_safe_perms(name, xres, invert, rule1, rule2): + perms = gen_list() + + for i in perms: + for t in named_trans[i[0]]: + if t == '_': + t = '' + for q in qualifiers: + qual = safe_map[i[0]] + if invert: + qual = invert_safe[qual] + + if (not invert or qual): + file = prefix_safe + '/' + name + '-' + invert + '-' + q + qual + '-' + 'rule-' + i + t + '.sd' + gen_file(file, xres, 0, '%s %s' % (q, qual), rule1, i, t, 1, q, rule2, i, t) + + file = prefix_safe + '/' + name + '-' + invert + '-' + q + qual + i + '-' + 'rule-' + t + '.sd' + gen_file(file, xres, 0, q, rule1, i, t, 1, '%s %s' % (q, qual), rule2, i, t) + + +test_gen_list() + +gen_conflicting_x() +gen_overlap_re_exact() +gen_dominate_re_re() +gen_ambiguous_re_re() +gen_leading_perms("exact", "/bin/cat", "/bin/cat") +gen_leading_perms("exact-re", "/bin/*", "/bin/*") +gen_leading_perms("overlap", "/*", "/bin/cat") +gen_leading_perms("dominate", "/**", "/*") +gen_leading_perms("ambiguous", "/a*", "/*b") +gen_safe_perms("exact", "PASS", "", "/bin/cat", "/bin/cat") +gen_safe_perms("exact-re", "PASS", "", "/bin/*", "/bin/*") +gen_safe_perms("overlap", "PASS", "", "/*", "/bin/cat") +gen_safe_perms("dominate", "PASS", "", "/**", "/*") +gen_safe_perms("ambiguous", "PASS", "", "/a*", "/*b") +gen_safe_perms("exact", "FAIL", "inv", "/bin/cat", "/bin/cat") +gen_safe_perms("exact-re", "FAIL", "inv", "/bin/*", "/bin/*") +gen_safe_perms("overlap", "PASS", "inv", "/*", "/bin/cat") +gen_safe_perms("dominate", "FAIL", "inv", "/**", "/*") +gen_safe_perms("ambiguous", "FAIL", "inv", "/a*", "/*b") + +print("Generated %s xtransition interaction tests" % count) diff --git a/parser/tst/minimize.sh b/parser/tst/minimize.sh index b653be38e..3c71d9189 100755 --- a/parser/tst/minimize.sh +++ b/parser/tst/minimize.sh @@ -5,7 +5,7 @@ APPARMOR_PARSER="${APPARMOR_PARSER:-../apparmor_parser}" # Format of -D dfa-states # dfa-states output is split into 2 parts: -# the accept state infomation +# the accept state information # {state} (allow deny audit XXX) ignore XXX for now # followed by the transition table information # {Y} -> {Z}: 0xXX Char #0xXX is the hex dump of Char @@ -43,7 +43,7 @@ APPARMOR_PARSER="${APPARMOR_PARSER:-../apparmor_parser}" # These tests currently only look at the accept state permissions # # To view any of these DFAs as graphs replace --D dfa-states with -D dfa-graph -# strip of the test stuff around the parser command and use the the dot +# strip of the test stuff around the parser command and use the dot # command to convert # Eg. # echo "/t { /a r, /b w, /c a, /d l, /e k, /f m, deny /** w, }" | ./apparmor_parser -QT -O minimize -D dfa-graph --quiet 2>min.graph @@ -100,7 +100,7 @@ fi echo "ok" # same test as above except with deny 'w' perm added to /**, this does not -# elimnates the states with 'w' and 'a' because the quiet information is +# eliminates the states with 'w' and 'a' because the quiet information is # being carried # # {1} <== (allow/deny/audit/quiet) @@ -119,7 +119,7 @@ fi echo "ok" # same test as above except with audit deny 'w' perm added to /**, with the -# parameter this elimnates the states with 'w' and 'a' because +# parameter this eliminates the states with 'w' and 'a' because # the quiet information is NOT being carried # # {1} <== (allow/deny/audit/quiet) @@ -139,7 +139,7 @@ echo "ok" # The x transition test profile is setup so that there are 3 conflicting x # permissions, two are on paths that won't collide during dfa creation. The -# 3rd is a generic permission that should be overriden during dfa creation. +# 3rd is a generic permission that should be overridden during dfa creation. # # This should result in a dfa that specifies transitions on 'a' and 'b' to # unique states that store the alternate accept information. However @@ -190,7 +190,7 @@ fi echo "ok" # now try audit + denying x and make sure perms are cleared -# notice that the deny info is being carried, by an artifical trap state +# notice that the deny info is being carried, by an artificial trap state # {1} <== (allow/deny/audit/quiet) # {3} (0x 0/fe17f85/0/0) diff --git a/parser/tst/mk_features_file.py b/parser/tst/mk_features_file.py index f94bf1ce4..bf80d0bc8 100755 --- a/parser/tst/mk_features_file.py +++ b/parser/tst/mk_features_file.py @@ -10,12 +10,14 @@ # # ------------------------------------------------------------------ -from testlib import read_features_dir -from argparse import ArgumentParser import os +from argparse import ArgumentParser from sys import stderr, exit -DEFAULT_FEATURES_DIR='/sys/kernel/security/apparmor/features' +from testlib import read_features_dir + +DEFAULT_FEATURES_DIR = '/sys/kernel/security/apparmor/features' + def main(): p = ArgumentParser() @@ -33,5 +35,6 @@ def main(): return 0 + if __name__ == "__main__": exit(main()) diff --git a/parser/tst/simple_tests/abi/bad_13.sd b/parser/tst/simple_tests/abi/bad_13.sd new file mode 100644 index 000000000..f220541f8 --- /dev/null +++ b/parser/tst/simple_tests/abi/bad_13.sd @@ -0,0 +1,8 @@ +# +#=DESCRIPTION abi testing - empty/cut-off rule +#=EXRESULT FAIL + +abi " + +/does/not/exist { +} diff --git a/parser/tst/simple_tests/abi/bad_14.sd b/parser/tst/simple_tests/abi/bad_14.sd new file mode 100644 index 000000000..087eba607 --- /dev/null +++ b/parser/tst/simple_tests/abi/bad_14.sd @@ -0,0 +1,8 @@ +# +#=DESCRIPTION abi testing - empty/cut-off rule +#=EXRESULT FAIL + +abi ", + +/does/not/exist { +} diff --git a/parser/tst/simple_tests/abi/bad_15.sd b/parser/tst/simple_tests/abi/bad_15.sd new file mode 100644 index 000000000..ed0f0b71b --- /dev/null +++ b/parser/tst/simple_tests/abi/bad_15.sd @@ -0,0 +1,8 @@ +# +#=DESCRIPTION abi testing - empty/cut-off rule +#=EXRESULT FAIL + +abi "" + +/does/not/exist { +} diff --git a/parser/tst/simple_tests/abi/bad_16.sd b/parser/tst/simple_tests/abi/bad_16.sd new file mode 100644 index 000000000..9e870c427 --- /dev/null +++ b/parser/tst/simple_tests/abi/bad_16.sd @@ -0,0 +1,8 @@ +# +#=DESCRIPTION abi testing - empty/cut-off rule +#=EXRESULT FAIL + +abi "", + +/does/not/exist { +} diff --git a/parser/tst/simple_tests/abi/bad_17.sd b/parser/tst/simple_tests/abi/bad_17.sd new file mode 100644 index 000000000..2511d4e6f --- /dev/null +++ b/parser/tst/simple_tests/abi/bad_17.sd @@ -0,0 +1,8 @@ +# +#=DESCRIPTION abi testing - empty/cut-off rule +#=EXRESULT FAIL + +abi < + +/does/not/exist { +} diff --git a/parser/tst/simple_tests/abi/bad_18.sd b/parser/tst/simple_tests/abi/bad_18.sd new file mode 100644 index 000000000..c2017a747 --- /dev/null +++ b/parser/tst/simple_tests/abi/bad_18.sd @@ -0,0 +1,8 @@ +# +#=DESCRIPTION abi testing - empty/cut-off rule +#=EXRESULT FAIL + +abi <, + +/does/not/exist { +} diff --git a/parser/tst/simple_tests/abi/bad_19.sd b/parser/tst/simple_tests/abi/bad_19.sd new file mode 100644 index 000000000..9f5237776 --- /dev/null +++ b/parser/tst/simple_tests/abi/bad_19.sd @@ -0,0 +1,8 @@ +# +#=DESCRIPTION abi testing - empty/cut-off rule +#=EXRESULT FAIL + +abi <> + +/does/not/exist { +} diff --git a/parser/tst/simple_tests/abi/bad_20.sd b/parser/tst/simple_tests/abi/bad_20.sd new file mode 100644 index 000000000..b947396a2 --- /dev/null +++ b/parser/tst/simple_tests/abi/bad_20.sd @@ -0,0 +1,8 @@ +# +#=DESCRIPTION abi testing - empty/cut-off rule +#=EXRESULT FAIL + +abi <>, + +/does/not/exist { +} diff --git a/parser/tst/simple_tests/capability/ok1.sd b/parser/tst/simple_tests/capability/ok1.sd index 080c15c16..3bafb79d2 100644 --- a/parser/tst/simple_tests/capability/ok1.sd +++ b/parser/tst/simple_tests/capability/ok1.sd @@ -1,5 +1,5 @@ # -#=DESCRIPTION validate some uses of capabilties. +#=DESCRIPTION validate some uses of capabilities. #=EXRESULT PASS # vim:syntax=subdomain # Last Modified: Sun Apr 17 19:44:44 2005 diff --git a/parser/tst/simple_tests/capability/ok2.sd b/parser/tst/simple_tests/capability/ok2.sd index 2bbb1607d..70a3a42fe 100644 --- a/parser/tst/simple_tests/capability/ok2.sd +++ b/parser/tst/simple_tests/capability/ok2.sd @@ -1,5 +1,5 @@ # -#=DESCRIPTION validate some uses of capabilties. +#=DESCRIPTION validate some uses of capabilities. #=EXRESULT PASS # vim:syntax=subdomain # Last Modified: Sun Apr 17 19:44:44 2005 diff --git a/parser/tst/simple_tests/capability/ok3.sd b/parser/tst/simple_tests/capability/ok3.sd index 454b96cb0..e62b50566 100644 --- a/parser/tst/simple_tests/capability/ok3.sd +++ b/parser/tst/simple_tests/capability/ok3.sd @@ -1,5 +1,5 @@ # -#=DESCRIPTION validate some uses of capabilties. +#=DESCRIPTION validate some uses of capabilities. #=EXRESULT PASS # vim:syntax=subdomain # Last Modified: Sun Apr 17 19:44:44 2005 diff --git a/parser/tst/simple_tests/capability/set/ok1.sd b/parser/tst/simple_tests/capability/set/ok1.sd index eed247080..9354fcb33 100644 --- a/parser/tst/simple_tests/capability/set/ok1.sd +++ b/parser/tst/simple_tests/capability/set/ok1.sd @@ -1,5 +1,5 @@ # -#=DESCRIPTION validate some uses of capabilties. +#=DESCRIPTION validate some uses of capabilities. #=EXRESULT FAIL # vim:syntax=subdomain # Last Modified: Sun Apr 17 19:44:44 2005 diff --git a/parser/tst/simple_tests/conditional/else_if_4.sd b/parser/tst/simple_tests/conditional/else_if_4.sd index b655667bc..04e7cee0d 100644 --- a/parser/tst/simple_tests/conditional/else_if_4.sd +++ b/parser/tst/simple_tests/conditional/else_if_4.sd @@ -1,4 +1,4 @@ -#=DESCRIPTION conditional else in invlaid locations +#=DESCRIPTION conditional else in invalid locations #=EXRESULT FAIL $BAR = false diff --git a/parser/tst/simple_tests/file/allow/ok_mmap_2.sd b/parser/tst/simple_tests/file/allow/ok_mmap_2.sd index bbcc62acf..49ffa25ca 100644 --- a/parser/tst/simple_tests/file/allow/ok_mmap_2.sd +++ b/parser/tst/simple_tests/file/allow/ok_mmap_2.sd @@ -1,5 +1,5 @@ # -#=DESCRIPTION m and [upi]x do not conflict, seperate rules +#=DESCRIPTION m and [upi]x do not conflict, separate rules #=EXRESULT PASS # vim:syntax=apparmor # diff --git a/parser/tst/simple_tests/file/file/ok_mmap_2.sd b/parser/tst/simple_tests/file/file/ok_mmap_2.sd index eef6f5cb2..1c07a3c64 100644 --- a/parser/tst/simple_tests/file/file/ok_mmap_2.sd +++ b/parser/tst/simple_tests/file/file/ok_mmap_2.sd @@ -1,5 +1,5 @@ # -#=DESCRIPTION m and [upi]x do not conflict, seperate rules +#=DESCRIPTION m and [upi]x do not conflict, separate rules #=EXRESULT PASS # /usr/bin/foo { diff --git a/parser/tst/simple_tests/file/ok_mmap_2.sd b/parser/tst/simple_tests/file/ok_mmap_2.sd index 711d42d02..f357df343 100644 --- a/parser/tst/simple_tests/file/ok_mmap_2.sd +++ b/parser/tst/simple_tests/file/ok_mmap_2.sd @@ -1,5 +1,5 @@ # -#=DESCRIPTION m and [upi]x do not conflict, seperate rules +#=DESCRIPTION m and [upi]x do not conflict, separate rules #=EXRESULT PASS # /usr/bin/foo { diff --git a/parser/tst/simple_tests/mount/bad_1.sd b/parser/tst/simple_tests/mount/bad_1.sd new file mode 100644 index 000000000..dbb40d00c --- /dev/null +++ b/parser/tst/simple_tests/mount/bad_1.sd @@ -0,0 +1,7 @@ +# +#=Description basic mount rule with incompatible options +#=EXRESULT FAIL +# +/usr/bin/foo { + mount options=(rw, ro) -> /foo, +} diff --git a/parser/tst/simple_tests/mount/bad_2.sd b/parser/tst/simple_tests/mount/bad_2.sd new file mode 100644 index 000000000..1034f9207 --- /dev/null +++ b/parser/tst/simple_tests/mount/bad_2.sd @@ -0,0 +1,7 @@ +# +#=Description basic mount rule with incompatible options +#=EXRESULT FAIL +# +/usr/bin/foo { + mount options=(rw ro) -> /foo, +} diff --git a/parser/tst/simple_tests/mount/bad_3.sd b/parser/tst/simple_tests/mount/bad_3.sd new file mode 100644 index 000000000..ba9baec0b --- /dev/null +++ b/parser/tst/simple_tests/mount/bad_3.sd @@ -0,0 +1,7 @@ +# +#=Description basic mount rule with incompatible options +#=EXRESULT FAIL +# +/usr/bin/foo { + mount options=(rw ro) fstype=procfs -> /foo, +} diff --git a/parser/tst/simple_tests/mount/ok_19.sd b/parser/tst/simple_tests/mount/bad_4.sd similarity index 50% rename from parser/tst/simple_tests/mount/ok_19.sd rename to parser/tst/simple_tests/mount/bad_4.sd index c93984938..c6de45b1e 100644 --- a/parser/tst/simple_tests/mount/ok_19.sd +++ b/parser/tst/simple_tests/mount/bad_4.sd @@ -1,6 +1,6 @@ # -#=Description basic mount rule -#=EXRESULT PASS +#=Description basic mount rule with incompatible options +#=EXRESULT FAIL # /usr/bin/foo { mount options=(rw ro) fstype=(procfs) none -> /foo, diff --git a/parser/tst/simple_tests/mount/bad_opt_29.sd b/parser/tst/simple_tests/mount/bad_opt_29.sd new file mode 100644 index 000000000..41d3f01c3 --- /dev/null +++ b/parser/tst/simple_tests/mount/bad_opt_29.sd @@ -0,0 +1,7 @@ +# +#=Description basic mount rule conflicting = options +#=EXRESULT FAIL +# +/usr/bin/foo { + mount options=(strictatime, nostrictatime) -> /foo, +} diff --git a/parser/tst/simple_tests/mount/bad_opt_30.sd b/parser/tst/simple_tests/mount/bad_opt_30.sd new file mode 100644 index 000000000..8dcd89da7 --- /dev/null +++ b/parser/tst/simple_tests/mount/bad_opt_30.sd @@ -0,0 +1,7 @@ +# +#=Description basic mount rule conflicting = options +#=EXRESULT FAIL +# +/usr/bin/foo { + mount options=(lazytime, nolazytime) -> /foo, +} diff --git a/parser/tst/simple_tests/mount/bad_opt_31.sd b/parser/tst/simple_tests/mount/bad_opt_31.sd new file mode 100644 index 000000000..c322f04e4 --- /dev/null +++ b/parser/tst/simple_tests/mount/bad_opt_31.sd @@ -0,0 +1,7 @@ +# +#=Description basic mount rule conflicting = options +#=EXRESULT FAIL +# +/usr/bin/foo { + mount options=(symfollow, nosymfollow) -> /foo, +} diff --git a/parser/tst/simple_tests/mount/bad_opt_32.sd b/parser/tst/simple_tests/mount/bad_opt_32.sd new file mode 100644 index 000000000..a02915b43 --- /dev/null +++ b/parser/tst/simple_tests/mount/bad_opt_32.sd @@ -0,0 +1,6 @@ +# +#=Description test we fail make rules with source and mntpnt associated with MR 1054 +#=EXRESULT FAIL +/usr/bin/foo { + mount options=(slave) /snap/bin/** -> /**, +} diff --git a/parser/tst/simple_tests/mount/bad_opt_35.sd b/parser/tst/simple_tests/mount/bad_opt_35.sd new file mode 100644 index 000000000..02d4114aa --- /dev/null +++ b/parser/tst/simple_tests/mount/bad_opt_35.sd @@ -0,0 +1,6 @@ +# +#=Description test we fail make rules with source and mntpnt associated with MR 1054 +#=EXRESULT FAIL +/usr/bin/foo { + mount options=(rslave) /snap/bin/** -> /**, +} diff --git a/parser/tst/simple_tests/mount/bad_opt_36.sd b/parser/tst/simple_tests/mount/bad_opt_36.sd new file mode 100644 index 000000000..17cc47766 --- /dev/null +++ b/parser/tst/simple_tests/mount/bad_opt_36.sd @@ -0,0 +1,6 @@ +# +#=Description test we fail make rules with source and mntpnt associated with MR 1054 +#=EXRESULT FAIL +/usr/bin/foo { + mount options=(unbindable) /snap/bin/** -> /**, +} diff --git a/parser/tst/simple_tests/mount/bad_opt_37.sd b/parser/tst/simple_tests/mount/bad_opt_37.sd new file mode 100644 index 000000000..a3b194b03 --- /dev/null +++ b/parser/tst/simple_tests/mount/bad_opt_37.sd @@ -0,0 +1,6 @@ +# +#=Description test we fail make rules with source and mntpnt associated with MR 1054 +#=EXRESULT FAIL +/usr/bin/foo { + mount options=(runbindable) /snap/bin/** -> /**, +} diff --git a/parser/tst/simple_tests/mount/bad_opt_38.sd b/parser/tst/simple_tests/mount/bad_opt_38.sd new file mode 100644 index 000000000..6f1c924ca --- /dev/null +++ b/parser/tst/simple_tests/mount/bad_opt_38.sd @@ -0,0 +1,6 @@ +# +#=Description test we fail make rules with source and mntpnt associated with MR 1054 +#=EXRESULT FAIL +/usr/bin/foo { + mount options=(private) /snap/bin/** -> /**, +} diff --git a/parser/tst/simple_tests/mount/bad_opt_39.sd b/parser/tst/simple_tests/mount/bad_opt_39.sd new file mode 100644 index 000000000..d013554f7 --- /dev/null +++ b/parser/tst/simple_tests/mount/bad_opt_39.sd @@ -0,0 +1,6 @@ +# +#=Description test we fail make rules with source and mntpnt associated with MR 1054 +#=EXRESULT FAIL +/usr/bin/foo { + mount options=(rprivate) /snap/bin/** -> /**, +} diff --git a/parser/tst/simple_tests/mount/bad_opt_40.sd b/parser/tst/simple_tests/mount/bad_opt_40.sd new file mode 100644 index 000000000..3ce9fad69 --- /dev/null +++ b/parser/tst/simple_tests/mount/bad_opt_40.sd @@ -0,0 +1,6 @@ +# +#=Description test we fail make rules with source and mntpnt associated with MR 1054 +#=EXRESULT FAIL +/usr/bin/foo { + mount options=(shared) /snap/bin/** -> /**, +} diff --git a/parser/tst/simple_tests/mount/bad_opt_41.sd b/parser/tst/simple_tests/mount/bad_opt_41.sd new file mode 100644 index 000000000..51a336ba2 --- /dev/null +++ b/parser/tst/simple_tests/mount/bad_opt_41.sd @@ -0,0 +1,6 @@ +# +#=Description test we fail make rules with source and mntpnt associated with MR 1054 +#=EXRESULT FAIL +/usr/bin/foo { + mount options=(rshared) /snap/bin/** -> /**, +} diff --git a/parser/tst/simple_tests/mount/ok_16.sd b/parser/tst/simple_tests/mount/ok_16.sd index 12c21aa65..e1c2d86c4 100644 --- a/parser/tst/simple_tests/mount/ok_16.sd +++ b/parser/tst/simple_tests/mount/ok_16.sd @@ -3,5 +3,5 @@ #=EXRESULT PASS # /usr/bin/foo { - mount options=(rw, ro) -> /foo, + mount options=(rw nosuid) -> /foo, } diff --git a/parser/tst/simple_tests/mount/ok_17.sd b/parser/tst/simple_tests/mount/ok_17.sd deleted file mode 100644 index 08aa1bb83..000000000 --- a/parser/tst/simple_tests/mount/ok_17.sd +++ /dev/null @@ -1,7 +0,0 @@ -# -#=Description basic mount rule -#=EXRESULT PASS -# -/usr/bin/foo { - mount options=(rw ro) -> /foo, -} diff --git a/parser/tst/simple_tests/mount/ok_18.sd b/parser/tst/simple_tests/mount/ok_18.sd deleted file mode 100644 index 96a93a261..000000000 --- a/parser/tst/simple_tests/mount/ok_18.sd +++ /dev/null @@ -1,7 +0,0 @@ -# -#=Description basic mount rule -#=EXRESULT PASS -# -/usr/bin/foo { - mount options=(rw ro) fstype=procfs -> /foo, -} diff --git a/parser/tst/simple_tests/mount/ok_opt_56.sd b/parser/tst/simple_tests/mount/ok_opt_56.sd new file mode 100644 index 000000000..80b9af379 --- /dev/null +++ b/parser/tst/simple_tests/mount/ok_opt_56.sd @@ -0,0 +1,8 @@ +# +#=Description basic rules to test the "nostrictatime" mount option +#=EXRESULT PASS +/usr/bin/foo { + mount options=nostrictatime /a -> /1, + mount options=(nostrictatime) /b -> /2, + mount options in (nostrictatime) /d -> /4, +} diff --git a/parser/tst/simple_tests/mount/ok_opt_57.sd b/parser/tst/simple_tests/mount/ok_opt_57.sd new file mode 100644 index 000000000..0cd537bec --- /dev/null +++ b/parser/tst/simple_tests/mount/ok_opt_57.sd @@ -0,0 +1,8 @@ +# +#=Description basic rules to test the "lazytime" mount option +#=EXRESULT PASS +/usr/bin/foo { + mount options=lazytime /a -> /1, + mount options=(lazytime) /b -> /2, + mount options in (lazytime) /d -> /4, +} diff --git a/parser/tst/simple_tests/mount/ok_opt_58.sd b/parser/tst/simple_tests/mount/ok_opt_58.sd new file mode 100644 index 000000000..849fbedb0 --- /dev/null +++ b/parser/tst/simple_tests/mount/ok_opt_58.sd @@ -0,0 +1,8 @@ +# +#=Description basic rules to test the "nolazytime" mount option +#=EXRESULT PASS +/usr/bin/foo { + mount options=nolazytime /a -> /1, + mount options=(nolazytime) /b -> /2, + mount options in (nolazytime) /d -> /4, +} diff --git a/parser/tst/simple_tests/mount/ok_opt_59.sd b/parser/tst/simple_tests/mount/ok_opt_59.sd new file mode 100644 index 000000000..0b60620a6 --- /dev/null +++ b/parser/tst/simple_tests/mount/ok_opt_59.sd @@ -0,0 +1,7 @@ +# +#=Description basic rules to test the "strictatime" mount option in combination +#=EXRESULT PASS +/usr/bin/foo { + mount options=(rw,strictatime) /c -> /3, + mount options in (ro,strictatime) /e -> /5, +} diff --git a/parser/tst/simple_tests/mount/ok_opt_60.sd b/parser/tst/simple_tests/mount/ok_opt_60.sd new file mode 100644 index 000000000..01a6c01a3 --- /dev/null +++ b/parser/tst/simple_tests/mount/ok_opt_60.sd @@ -0,0 +1,7 @@ +# +#=Description basic rules to test the "nostrictatime" mount option in combination +#=EXRESULT PASS +/usr/bin/foo { + mount options=(rw,nostrictatime) /c -> /3, + mount options in (ro,nostrictatime) /e -> /5, +} diff --git a/parser/tst/simple_tests/mount/ok_opt_61.sd b/parser/tst/simple_tests/mount/ok_opt_61.sd new file mode 100644 index 000000000..694ba956e --- /dev/null +++ b/parser/tst/simple_tests/mount/ok_opt_61.sd @@ -0,0 +1,7 @@ +# +#=Description basic rules to test the "lazytime" mount option in combination +#=EXRESULT PASS +/usr/bin/foo { + mount options=(rw,lazytime) /c -> /3, + mount options in (ro,lazytime) /e -> /5, +} diff --git a/parser/tst/simple_tests/mount/ok_opt_62.sd b/parser/tst/simple_tests/mount/ok_opt_62.sd new file mode 100644 index 000000000..641fd9c97 --- /dev/null +++ b/parser/tst/simple_tests/mount/ok_opt_62.sd @@ -0,0 +1,7 @@ +# +#=Description basic rules to test the "nolazytime" mount option in combination +#=EXRESULT PASS +/usr/bin/foo { + mount options=(rw,nolazytime) /c -> /3, + mount options in (ro,nolazytime) /e -> /5, +} diff --git a/parser/tst/simple_tests/mount/ok_opt_63.sd b/parser/tst/simple_tests/mount/ok_opt_63.sd new file mode 100644 index 000000000..4b374962e --- /dev/null +++ b/parser/tst/simple_tests/mount/ok_opt_63.sd @@ -0,0 +1,7 @@ +# +#=Description basic mount rule conflicting options with in +#=EXRESULT PASS +# +/usr/bin/foo { + mount options in (strictatime, nostrictatime) -> /foo, +} diff --git a/parser/tst/simple_tests/mount/ok_opt_64.sd b/parser/tst/simple_tests/mount/ok_opt_64.sd new file mode 100644 index 000000000..83fdba52d --- /dev/null +++ b/parser/tst/simple_tests/mount/ok_opt_64.sd @@ -0,0 +1,7 @@ +# +#=Description basic mount rule conflicting options with in +#=EXRESULT PASS +# +/usr/bin/foo { + mount options in (lazytime, nolazytime) -> /foo, +} diff --git a/parser/tst/simple_tests/mount/ok_opt_65.sd b/parser/tst/simple_tests/mount/ok_opt_65.sd new file mode 100644 index 000000000..8b5c2e600 --- /dev/null +++ b/parser/tst/simple_tests/mount/ok_opt_65.sd @@ -0,0 +1,8 @@ +# +#=Description basic rules to test the "nosymfollow" mount option +#=EXRESULT PASS +/usr/bin/foo { + mount options=nosymfollow /a -> /1, + mount options=(nosymfollow) /b -> /2, + mount options in (nosymfollow) /d -> /4, +} diff --git a/parser/tst/simple_tests/mount/ok_opt_66.sd b/parser/tst/simple_tests/mount/ok_opt_66.sd new file mode 100644 index 000000000..b27d7bb6b --- /dev/null +++ b/parser/tst/simple_tests/mount/ok_opt_66.sd @@ -0,0 +1,7 @@ +# +#=Description basic rules to test the "symfollow" mount option in combination +#=EXRESULT PASS +/usr/bin/foo { + mount options=(rw,symfollow) /c -> /3, + mount options in (ro,symfollow) /e -> /5, +} diff --git a/parser/tst/simple_tests/mount/ok_opt_67.sd b/parser/tst/simple_tests/mount/ok_opt_67.sd new file mode 100644 index 000000000..4ff29bcd4 --- /dev/null +++ b/parser/tst/simple_tests/mount/ok_opt_67.sd @@ -0,0 +1,7 @@ +# +#=Description basic mount rule conflicting options with in +#=EXRESULT PASS +# +/usr/bin/foo { + mount options in (symfollow, nosymfollow) -> /foo, +} diff --git a/parser/tst/simple_tests/mount/ok_opt_68.sd b/parser/tst/simple_tests/mount/ok_opt_68.sd new file mode 100644 index 000000000..ba4551084 --- /dev/null +++ b/parser/tst/simple_tests/mount/ok_opt_68.sd @@ -0,0 +1,10 @@ +# +#=Description basic rules to test the "unbindable" mount option passing mount point as source (should emit a deprecation warning) +#=EXRESULT PASS +/usr/bin/foo { + mount options=unbindable /1, + mount options=(unbindable) /2, + mount options=(rw,unbindable) /3, + mount options in (unbindable) /4, + mount options in (ro,unbindable) /5, +} diff --git a/parser/tst/simple_tests/mount/ok_opt_69.sd b/parser/tst/simple_tests/mount/ok_opt_69.sd new file mode 100644 index 000000000..deddeb8f0 --- /dev/null +++ b/parser/tst/simple_tests/mount/ok_opt_69.sd @@ -0,0 +1,10 @@ +# +#=Description basic rules to test the "runbindable" mount option passing mount point as source (should emit a deprecation warning) +#=EXRESULT PASS +/usr/bin/foo { + mount options=runbindable /1, + mount options=(runbindable) /2, + mount options=(rw,runbindable) /3, + mount options in (runbindable) /4, + mount options in (ro,runbindable) /5, +} diff --git a/parser/tst/simple_tests/mount/ok_opt_70.sd b/parser/tst/simple_tests/mount/ok_opt_70.sd new file mode 100644 index 000000000..9cc60472b --- /dev/null +++ b/parser/tst/simple_tests/mount/ok_opt_70.sd @@ -0,0 +1,10 @@ +# +#=Description basic rules to test the "rprivate" mount option passing mount point as source (should emit a deprecation warning) +#=EXRESULT PASS +/usr/bin/foo { + mount options=rprivate /1, + mount options=(rprivate) /2, + mount options=(rw,rprivate) /3, + mount options in (rprivate) /4, + mount options in (ro,rprivate) /5, +} diff --git a/parser/tst/simple_tests/mount/ok_opt_71.sd b/parser/tst/simple_tests/mount/ok_opt_71.sd new file mode 100644 index 000000000..bfdde5a94 --- /dev/null +++ b/parser/tst/simple_tests/mount/ok_opt_71.sd @@ -0,0 +1,10 @@ +# +#=Description basic rules to test the "private" mount option passing mount point as source (should emit a deprecation warning) +#=EXRESULT PASS +/usr/bin/foo { + mount options=private /1, + mount options=(private) /2, + mount options=(rw,private) /3, + mount options in (private) /4, + mount options in (ro,private) /5, +} diff --git a/parser/tst/simple_tests/mount/ok_opt_72.sd b/parser/tst/simple_tests/mount/ok_opt_72.sd new file mode 100644 index 000000000..da610aa2e --- /dev/null +++ b/parser/tst/simple_tests/mount/ok_opt_72.sd @@ -0,0 +1,10 @@ +# +#=Description basic rules to test the "slave" mount option passing mount point as source (should emit a deprecation warning) +#=EXRESULT PASS +/usr/bin/foo { + mount options=slave /1, + mount options=(slave) /2, + mount options=(rw,slave) /3, + mount options in (slave) /4, + mount options in (ro,slave) /5, +} diff --git a/parser/tst/simple_tests/mount/ok_opt_73.sd b/parser/tst/simple_tests/mount/ok_opt_73.sd new file mode 100644 index 000000000..6f465f050 --- /dev/null +++ b/parser/tst/simple_tests/mount/ok_opt_73.sd @@ -0,0 +1,10 @@ +# +#=Description basic rules to test the "rslave" mount option passing mount point as source (should emit a deprecation warning) +#=EXRESULT PASS +/usr/bin/foo { + mount options=rslave /1, + mount options=(rslave) /2, + mount options=(rw,rslave) /3, + mount options in (rslave) /4, + mount options in (ro,rslave) /5, +} diff --git a/parser/tst/simple_tests/mount/ok_opt_74.sd b/parser/tst/simple_tests/mount/ok_opt_74.sd new file mode 100644 index 000000000..463e40841 --- /dev/null +++ b/parser/tst/simple_tests/mount/ok_opt_74.sd @@ -0,0 +1,10 @@ +# +#=Description basic rules to test the "shared" mount option passing mount point as source (should emit a deprecation warning) +#=EXRESULT PASS +/usr/bin/foo { + mount options=shared /1, + mount options=(shared) /2, + mount options=(rw,shared) /3, + mount options in (shared) /4, + mount options in (ro,shared) /5, +} diff --git a/parser/tst/simple_tests/mount/ok_opt_75.sd b/parser/tst/simple_tests/mount/ok_opt_75.sd new file mode 100644 index 000000000..64018c079 --- /dev/null +++ b/parser/tst/simple_tests/mount/ok_opt_75.sd @@ -0,0 +1,10 @@ +# +#=Description basic rules to test the "rshared" mount option passing mount point as source (should emit a deprecation warning) +#=EXRESULT PASS +/usr/bin/foo { + mount options=rshared /1, + mount options=(rshared) /2, + mount options=(rw,rshared) /3, + mount options in (rshared) /4, + mount options in (ro,rshared) /5, +} diff --git a/parser/tst/simple_tests/mount/ok_opt_76.sd b/parser/tst/simple_tests/mount/ok_opt_76.sd new file mode 100644 index 000000000..ddd0e53b3 --- /dev/null +++ b/parser/tst/simple_tests/mount/ok_opt_76.sd @@ -0,0 +1,10 @@ +# +#=Description basic rules to test the "make-unbindable" mount option passing mount point as source (should emit a deprecation warning) +#=EXRESULT PASS +/usr/bin/foo { + mount options=make-unbindable /1, + mount options=(make-unbindable) /2, + mount options=(rw,make-unbindable) /3, + mount options in (make-unbindable) /4, + mount options in (ro,make-unbindable) /5, +} diff --git a/parser/tst/simple_tests/mount/ok_opt_77.sd b/parser/tst/simple_tests/mount/ok_opt_77.sd new file mode 100644 index 000000000..8604ecd40 --- /dev/null +++ b/parser/tst/simple_tests/mount/ok_opt_77.sd @@ -0,0 +1,10 @@ +# +#=Description basic rules to test the "make-runbindable" mount option passing mount point as source (should emit a deprecation warning) +#=EXRESULT PASS +/usr/bin/foo { + mount options=make-runbindable /1, + mount options=(make-runbindable) /2, + mount options=(rw,make-runbindable) /3, + mount options in (make-runbindable) /4, + mount options in (ro,make-runbindable) /5, +} diff --git a/parser/tst/simple_tests/mount/ok_opt_78.sd b/parser/tst/simple_tests/mount/ok_opt_78.sd new file mode 100644 index 000000000..61e02fa43 --- /dev/null +++ b/parser/tst/simple_tests/mount/ok_opt_78.sd @@ -0,0 +1,10 @@ +# +#=Description basic rules to test the "make-private" mount option passing mount point as source (should emit a deprecation warning) +#=EXRESULT PASS +/usr/bin/foo { + mount options=make-private /1, + mount options=(make-private) /2, + mount options=(rw,make-private) /3, + mount options in (make-private) /4, + mount options in (ro,make-private) /5, +} diff --git a/parser/tst/simple_tests/mount/ok_opt_79.sd b/parser/tst/simple_tests/mount/ok_opt_79.sd new file mode 100644 index 000000000..d8ef7a92d --- /dev/null +++ b/parser/tst/simple_tests/mount/ok_opt_79.sd @@ -0,0 +1,10 @@ +# +#=Description basic rules to test the "make-rprivate" mount option passing mount point as source (should emit a deprecation warning) +#=EXRESULT PASS +/usr/bin/foo { + mount options=make-rprivate /1, + mount options=(make-rprivate) /2, + mount options=(rw,make-rprivate) /3, + mount options in (make-rprivate) /4, + mount options in (ro,make-rprivate) /5, +} diff --git a/parser/tst/simple_tests/mount/ok_opt_80.sd b/parser/tst/simple_tests/mount/ok_opt_80.sd new file mode 100644 index 000000000..7b10eefaf --- /dev/null +++ b/parser/tst/simple_tests/mount/ok_opt_80.sd @@ -0,0 +1,10 @@ +# +#=Description basic rules to test the "make-slave" mount option passing mount point as source (should emit a deprecation warning) +#=EXRESULT PASS +/usr/bin/foo { + mount options=make-slave /1, + mount options=(make-slave) /2, + mount options=(rw,make-slave) /3, + mount options in (make-slave) /4, + mount options in (ro,make-slave) /5, +} diff --git a/parser/tst/simple_tests/mount/ok_opt_81.sd b/parser/tst/simple_tests/mount/ok_opt_81.sd new file mode 100644 index 000000000..06c498258 --- /dev/null +++ b/parser/tst/simple_tests/mount/ok_opt_81.sd @@ -0,0 +1,10 @@ +# +#=Description basic rules to test the "make-shared" mount option passing mount point as source (should emit a deprecation warning) +#=EXRESULT PASS +/usr/bin/foo { + mount options=make-shared /1, + mount options=(make-shared) /2, + mount options=(rw,make-shared) /3, + mount options in (make-shared) /4, + mount options in (ro,make-shared) /5, +} diff --git a/parser/tst/simple_tests/mount/ok_opt_82.sd b/parser/tst/simple_tests/mount/ok_opt_82.sd new file mode 100644 index 000000000..d3972c4e0 --- /dev/null +++ b/parser/tst/simple_tests/mount/ok_opt_82.sd @@ -0,0 +1,10 @@ +# +#=Description basic rules to test the "make-rslave" mount option passing mount point as source (should emit a deprecation warning) +#=EXRESULT PASS +/usr/bin/foo { + mount options=make-rslave /1, + mount options=(make-rslave) /2, + mount options=(rw,make-rslave) /3, + mount options in (make-rslave) /4, + mount options in (ro,make-rslave) /5, +} diff --git a/parser/tst/simple_tests/mount/ok_opt_83.sd b/parser/tst/simple_tests/mount/ok_opt_83.sd new file mode 100644 index 000000000..28a985bd4 --- /dev/null +++ b/parser/tst/simple_tests/mount/ok_opt_83.sd @@ -0,0 +1,10 @@ +# +#=Description basic rules to test the "make-rshared" mount option passing mount point as source (should emit a deprecation warning) +#=EXRESULT PASS +/usr/bin/foo { + mount options=make-rshared /1, + mount options=(make-rshared) /2, + mount options=(rw,make-rshared) /3, + mount options in (make-rshared) /4, + mount options in (ro,make-rshared) /5, +} diff --git a/parser/tst/simple_tests/mount/ok_opt_84.sd b/parser/tst/simple_tests/mount/ok_opt_84.sd new file mode 100644 index 000000000..9dfcc5241 --- /dev/null +++ b/parser/tst/simple_tests/mount/ok_opt_84.sd @@ -0,0 +1,8 @@ +# +#=Description test we can parse rules associated with MR 1054 +#=EXRESULT PASS +/usr/bin/foo { + mount options=(slave) /**, + mount options=(slave) -> /**, + mount /snap/bin/** -> /**, +} diff --git a/parser/tst/simple_tests/network/tcp_client_error2.sd b/parser/tst/simple_tests/network/tcp_client_error2.sd index 1624e68e3..32fecb1d2 100644 --- a/parser/tst/simple_tests/network/tcp_client_error2.sd +++ b/parser/tst/simple_tests/network/tcp_client_error2.sd @@ -1,5 +1,5 @@ # -#=DESCRIPTION netdomain tcp connect w/multiple from statments +#=DESCRIPTION netdomain tcp connect w/multiple from statements #=EXRESULT FAIL /tmp/tcp/tcp_client { tcp_connect from 10.0.0.17/16:50-100 from 127.0.0.1 via eth1, diff --git a/parser/tst/simple_tests/profile/profile_ns_named_ok1.sd b/parser/tst/simple_tests/profile/profile_ns_named_ok1.sd index 0a21e7e52..2330d0fa7 100644 --- a/parser/tst/simple_tests/profile/profile_ns_named_ok1.sd +++ b/parser/tst/simple_tests/profile/profile_ns_named_ok1.sd @@ -1,6 +1,6 @@ # # $Id$ -#=DESCRIPTION Basic namespace test wit named profile, duplicate mode bits +#=DESCRIPTION Basic namespace test with named profile, duplicate mode bits #=EXRESULT PASS # vim:syntax=subdomain # Last Modified: Sun Apr 17 19:44:44 2005 diff --git a/parser/tst/simple_tests/rlimits/ok_rlimit_10.sd b/parser/tst/simple_tests/rlimits/ok_rlimit_10.sd index f6f0c0e5d..7ab6f4ea0 100644 --- a/parser/tst/simple_tests/rlimits/ok_rlimit_10.sd +++ b/parser/tst/simple_tests/rlimits/ok_rlimit_10.sd @@ -1,5 +1,5 @@ # -#=DESCRIPTION simple max virtual memory szie rlimit test +#=DESCRIPTION simple max virtual memory size rlimit test #=EXRESULT PASS profile rlimit { diff --git a/parser/tst/testlib.py b/parser/tst/testlib.py index 6b9318ef9..30b127896 100644 --- a/parser/tst/testlib.py +++ b/parser/tst/testlib.py @@ -41,9 +41,9 @@ class AANoCleanupMetaClass(type): @classmethod def keep_on_fail(cls, unittest_func): - '''wrapping function for unittest testcases to detect failure + """wrapping function for unittest testcases to detect failure and leave behind test files in tearDown(); to be used as - a decorator''' + a decorator""" def new_unittest_func(self): try: @@ -58,17 +58,17 @@ class AANoCleanupMetaClass(type): class AATestTemplate(unittest.TestCase, metaclass=AANoCleanupMetaClass): - '''Stub class for use by test scripts''' + """Stub class for use by test scripts""" debug = False do_cleanup = True def run_cmd_check(self, command, input=None, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, stdin=None, timeout=120, expected_rc=0, expected_string=None): - '''Wrapper around run_cmd that checks the rc code against + """Wrapper around run_cmd that checks the rc code against expected_rc and for expected strings in the output if passed. The valgrind tests generally don't care what the rc is as long as it's not a specific set of return codes, - so can't push the check directly into run_cmd().''' + so can't push the check directly into run_cmd().""" rc, report = self.run_cmd(command, input, stderr, stdout, stdin, timeout) self.assertEqual(rc, expected_rc, "Got return code %d, expected %d\nCommand run: %s\nOutput: %s" % (rc, expected_rc, (' '.join(command)), report)) if expected_string: @@ -77,26 +77,26 @@ class AATestTemplate(unittest.TestCase, metaclass=AANoCleanupMetaClass): def run_cmd(self, command, input=None, stderr=subprocess.PIPE, stdout=subprocess.PIPE, stdin=None, timeout=120): - '''Try to execute given command (array) and return its stdout, or - return a textual error if it failed.''' + """Try to execute given command (array) and return its stdout, or + return a textual error if it failed.""" if self.debug: - print('\n===> Running command: \'%s\'' % (' '.join(command))) + print("\n===> Running command: '%s'" % (' '.join(command))) (rc, out, outerr) = self._run_cmd(command, input, stderr, stdout, stdin, timeout) report = out + outerr - return [rc, report] + return rc, report def _run_cmd(self, command, input=None, stderr=subprocess.PIPE, stdout=subprocess.PIPE, stdin=None, timeout=120): - '''Try to execute given command (array) and return its rc, stdout, and stderr as a tuple''' + """Try to execute given command (array) and return its rc, stdout, and stderr as a tuple""" try: sp = subprocess.Popen(command, stdin=stdin, stdout=stdout, stderr=stderr, close_fds=True, preexec_fn=subprocess_setup, universal_newlines=True) except OSError as e: - return [127, str(e), ''] + return 127, str(e), '' timeout_communicate = TimeoutFunction(sp.communicate, timeout) out, outerr = (None, None) @@ -115,13 +115,12 @@ class AATestTemplate(unittest.TestCase, metaclass=AANoCleanupMetaClass): if outerr is None: outerr = '' - return (rc, out, outerr) + return rc, out, outerr # Timeout handler using alarm() from John P. Speno's Pythonic Avocado class TimeoutFunctionException(Exception): """Exception to raise on a timeout""" - pass class TimeoutFunction: @@ -144,7 +143,7 @@ class TimeoutFunction: def filesystem_time_resolution(): - '''detect whether the filesystem stores subsecond timestamps''' + """detect whether the filesystem stores subsecond timestamps""" default_diff = 0.1 result = (True, default_diff) @@ -199,7 +198,7 @@ def touch(path): def write_file(directory, file, contents): - '''construct path, write contents to it, and return the constructed path''' + """construct path, write contents to it, and return the constructed path""" path = os.path.join(directory, file) with open(path, 'w+') as f: f.write(contents) diff --git a/parser/tst/valgrind_simple.py b/parser/tst/valgrind_simple.py index 4cc603fa2..92865a896 100755 --- a/parser/tst/valgrind_simple.py +++ b/parser/tst/valgrind_simple.py @@ -13,11 +13,12 @@ # TODO # - finish adding suppressions for valgrind false positives -from argparse import ArgumentParser # requires python 2.7 or newer import os import sys -import tempfile import unittest +from argparse import ArgumentParser +from tempfile import NamedTemporaryFile + import testlib DEFAULT_TESTDIR = "./simple_tests/vars" @@ -42,20 +43,22 @@ class AAParserValgrindTests(testlib.AATestTemplate): self.maxDiff = None def _runtest(self, testname, config): - parser_args = ['-Q', '-I', config.testdir, '-M', './features_files/features.all'] - failure_rc = [VALGRIND_ERROR_CODE, testlib.TIMEOUT_ERROR_CODE] + parser_args = ('-Q', '-I', config.testdir, '-M', './features_files/features.all') + failure_rc = (VALGRIND_ERROR_CODE, testlib.TIMEOUT_ERROR_CODE) command = [config.valgrind] command.extend(VALGRIND_ARGS) command.append(config.parser) command.extend(parser_args) command.append(testname) rc, output = self.run_cmd(command, timeout=120) - self.assertNotIn(rc, failure_rc, - "valgrind returned error code %d, gave the following output\n%s\ncommand run: %s" % (rc, output, " ".join(command))) + self.assertNotIn( + rc, failure_rc, + "valgrind returned error code %d, gave the following output\n%s\ncommand run: %s" + % (rc, output, " ".join(command))) def find_testcases(testdir): - '''dig testcases out of passed directory''' + """dig testcases out of passed directory""" for (fdir, direntries, files) in os.walk(testdir): for f in files: @@ -64,14 +67,11 @@ def find_testcases(testdir): def create_suppressions(): - '''generate valgrind suppressions file''' + """generate valgrind suppressions file""" + with NamedTemporaryFile("w+", suffix='.suppressions', prefix='aa-parser-valgrind', delete=False) as temp_file: + temp_file.write(VALGRIND_SUPPRESSIONS) + return temp_file.name - handle, name = tempfile.mkstemp(suffix='.suppressions', prefix='aa-parser-valgrind') - os.close(handle) - handle = open(name,"w+") - handle.write(VALGRIND_SUPPRESSIONS) - handle.close() - return name def main(): rc = 0 @@ -125,6 +125,7 @@ def main(): return rc + if __name__ == "__main__": rc = main() exit(rc) diff --git a/profiles/Makefile b/profiles/Makefile index f8fa10be3..b68c2ac4b 100644 --- a/profiles/Makefile +++ b/profiles/Makefile @@ -47,10 +47,10 @@ else PYTHONPATH=../utils/:$(PYTHON_DIST_BUILD_PATH) PARSER?=../parser/apparmor_parser # use ../utils logprof - LOGPROF?=LD_LIBRARY_PATH=$(LD_LIBRARY_PATH) PYTHONPATH=$(PYTHONPATH) $(PYTHON) ../utils/aa-logprof + LOGPROF?=LD_LIBRARY_PATH=$(LD_LIBRARY_PATH) PYTHONPATH=$(PYTHONPATH) $(PYTHON) ../utils/aa-logprof --configdir ../utils/ endif -# $(PWD) is wrong when using "make -C profiles" - explicitely set it here to get the right value +# $(PWD) is wrong when using "make -C profiles" - explicitly set it here to get the right value PWD=$(shell pwd) .PHONY: test-dependencies @@ -83,7 +83,7 @@ local: fn=$$(basename $$profile); \ echo "# Site-specific additions and overrides for '$$fn'" > ${PROFILES_SOURCE}/local/$$fn; \ grep "include[[:space:]]\\+if[[:space:]]\\+exists[[:space:]]\\+<local/$$fn>" "$$profile" >/dev/null || { echo "$$profile doesn't contain include if exists <local/$$fn>" ; exit 1; } ; \ - done; \ + done .PHONY: install install: local @@ -119,7 +119,7 @@ CHECK_PROFILES=$(filter-out ${IGNORE_FILES} ${SUBDIRS}, $(wildcard ${PROFILES_SO CHECK_ABSTRACTIONS=$(shell find ${ABSTRACTIONS_SOURCE} -type f -print) .PHONY: check -check: check-parser check-logprof check-abstractions.d +check: check-parser check-logprof check-abstractions.d check-extras .PHONY: check-parser check-parser: test-dependencies local @@ -151,3 +151,11 @@ check-abstractions.d: test "$$file" = 'ubuntu-helpers' && continue ; \ grep -q "^ include if exists <abstractions/$${file}.d>$$" $$file || { echo "$$file does not contain 'include if exists <abstractions/$${file}.d>'"; exit 1; } ; \ done + +.PHONY: check-extras +check-extras: + @echo "*** Checking if all extra profiles contain include if exists <local/*>" + $(Q)cd ${EXTRAS_SOURCE} && for file in * ; do \ + test "$$file" = 'README' && continue ; \ + grep -q "^ include if exists <local/$${file}>$$" $$file || { echo "$$file does not contain 'include if exists <local/$${file}>'"; exit 1; } ; \ + done diff --git a/profiles/apparmor.d/abstractions/audio b/profiles/apparmor.d/abstractions/audio index 01493260d..89d3965c6 100644 --- a/profiles/apparmor.d/abstractions/audio +++ b/profiles/apparmor.d/abstractions/audio @@ -85,5 +85,8 @@ owner @{HOME}/.local/share/openal/hrtf/{,**} r, # wildmidi /etc/wildmidi/wildmidi.cfg r, +# pipewire +/usr/share/pipewire/client{,-rt}.conf r, + # Include additions to the abstraction include if exists <abstractions/audio.d> diff --git a/profiles/apparmor.d/abstractions/authentication b/profiles/apparmor.d/abstractions/authentication index d5dbd83ad..65cd0d72f 100644 --- a/profiles/apparmor.d/abstractions/authentication +++ b/profiles/apparmor.d/abstractions/authentication @@ -31,6 +31,11 @@ /{usr/,}lib/@{multiarch}/security/pam_*.so mr, /{usr/,}lib/@{multiarch}/security/ r, + # gssapi + @{etc_ro}/gss/mech r, + @{etc_ro}/gss/mech.d/ r, + @{etc_ro}/gss/mech.d/*.conf r, + # kerberos include <abstractions/kerberosclient> # SuSE's pwdutils are different: diff --git a/profiles/apparmor.d/abstractions/base b/profiles/apparmor.d/abstractions/base index f36a5f86d..0bd7fda30 100644 --- a/profiles/apparmor.d/abstractions/base +++ b/profiles/apparmor.d/abstractions/base @@ -36,8 +36,8 @@ /usr/share/locale-langpack/** r, /usr/share/locale/** r, /usr/share/**/locale/** r, - /usr/share/zoneinfo/ r, - /usr/share/zoneinfo/** r, + /usr/share/zoneinfo{,-icu}/ r, + /usr/share/zoneinfo{,-icu}/** r, /usr/share/X11/locale/** r, @{run}/systemd/journal/dev-log w, # systemd native journal API (see sd_journal_print(4)) @@ -62,6 +62,7 @@ @{etc_ro}/ld.so.conf r, @{etc_ro}/ld.so.conf.d/{,*.conf} r, @{etc_ro}/ld.so.preload r, + @{etc_ro}/ld-musl-*.path r, /{usr/,}lib{,32,64}/ld{,32,64}-*.so mr, /{usr/,}lib/@{multiarch}/ld{,32,64}-*.so mr, /{usr/,}lib/tls/i686/{cmov,nosegneg}/ld-*.so mr, @@ -103,12 +104,12 @@ @{sys}/devices/system/cpu/online r, @{sys}/devices/system/cpu/possible r, + # transparent hugepage support + @{sys}/kernel/mm/transparent_hugepage/hpage_pmd_size r, + # glibc's *printf protections read the maps file @{PROC}/@{pid}/{maps,auxv,status} r, - # libgcrypt reads some flags from /proc - @{PROC}/sys/crypto/* r, - # some applications will display license information /usr/share/common-licenses/** r, diff --git a/profiles/apparmor.d/abstractions/crypto b/profiles/apparmor.d/abstractions/crypto index 83676003d..50852e8af 100644 --- a/profiles/apparmor.d/abstractions/crypto +++ b/profiles/apparmor.d/abstractions/crypto @@ -13,6 +13,7 @@ abi <abi/3.0>, + @{etc_ro}/gcrypt/hwf.deny r, @{etc_ro}/gcrypt/random.conf r, @{PROC}/sys/crypto/fips_enabled r, diff --git a/profiles/apparmor.d/abstractions/exo-open b/profiles/apparmor.d/abstractions/exo-open index 2ce38e5f0..0927f2d5f 100644 --- a/profiles/apparmor.d/abstractions/exo-open +++ b/profiles/apparmor.d/abstractions/exo-open @@ -29,8 +29,8 @@ # include <abstractions/ubuntu-browsers> # include <abstractions/ubuntu-email> # -# # Add if accesibility access is considered as required -# # (for message boxe in case exo-open fails) +# # Add if accessibility access is considered as required +# # (for message box in case exo-open fails) # include <abstractions/dbus-accessibility> # # # < add additional allowed applications here > diff --git a/profiles/apparmor.d/abstractions/fonts b/profiles/apparmor.d/abstractions/fonts index 46324dbb5..7070a2b86 100644 --- a/profiles/apparmor.d/abstractions/fonts +++ b/profiles/apparmor.d/abstractions/fonts @@ -47,7 +47,7 @@ owner @{HOME}/.local/share/fonts/** r, owner @{HOME}/.fonts.cache-2 mr, owner @{HOME}/.{,cache/}fontconfig/ rw, - owner @{HOME}/.{,cache/}fontconfig/** mrl, + owner @{HOME}/.{,cache/}fontconfig/** mrwkl, owner @{HOME}/.fonts.conf.d/ r, owner @{HOME}/.fonts.conf.d/** r, owner @{HOME}/.config/fontconfig/ r, diff --git a/profiles/apparmor.d/abstractions/freedesktop.org b/profiles/apparmor.d/abstractions/freedesktop.org index a3c9672fe..a8580fde1 100644 --- a/profiles/apparmor.d/abstractions/freedesktop.org +++ b/profiles/apparmor.d/abstractions/freedesktop.org @@ -20,7 +20,7 @@ @{system_share_dirs}/mime/** r, # per-user configurations - owner @{HOME}/.icons/ r, + owner @{HOME}/.icons/{,**} r, owner @{HOME}/.recently-used.xbel* rw, owner @{HOME}/.local/share/recently-used.xbel* rw, owner @{HOME}/.config/user-dirs.dirs r, diff --git a/profiles/apparmor.d/abstractions/groff b/profiles/apparmor.d/abstractions/groff new file mode 100644 index 000000000..874fbb7ab --- /dev/null +++ b/profiles/apparmor.d/abstractions/groff @@ -0,0 +1,67 @@ +# ------------------------------------------------------------------ +# +# Copyright (C) 2002-2009 Novell/SUSE +# Copyright (C) 2009 Canonical Ltd. +# Copyright (C) 2023 SUSE LLC +# +# 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. +# +# ------------------------------------------------------------------ + + # Note: executing groff and nroff themself is not included in this abstraction + # so that you can choose to ix, Px or Cx them in your profile + + # groff/nroff helpers, preprocessors, and postprocessors + /usr/bin/addftinfo mrix, + /usr/bin/afmtodit mrix, + /usr/bin/chem mrix, + /usr/bin/eqn mrix, + /usr/bin/eqn2graph mrix, + /usr/bin/gdiffmk mrix, + /usr/bin/geqn mrix, + /usr/bin/grap2graph mrix, + /usr/bin/grn mrix, + /usr/bin/grodvi mrix, + /usr/bin/groffer mrix, + /usr/bin/grog mrix, + /usr/bin/grolbp mrix, + /usr/bin/grolj4 mrix, + /usr/bin/gropdf mrix, + /usr/bin/grops mrix, + /usr/bin/grotty mrix, + /usr/bin/gtbl mrix, + /usr/bin/hpftodit mrix, + /usr/bin/indxbib mrix, + /usr/bin/lkbib mrix, + /usr/bin/lookbib mrix, + /usr/bin/mmroff mrix, + /usr/bin/neqn mrix, + /usr/bin/pdfmom mrix, + /usr/bin/pdfroff mrix, + /usr/bin/pfbtops mrix, + /usr/bin/pic mrix, + /usr/bin/pic2graph mrix, + /usr/bin/post-grohtml mrix, + /usr/bin/pre-grohtml mrix, + /usr/bin/preconv mrix, + /usr/bin/refer mrix, + /usr/bin/roff2dvi mrix, + /usr/bin/roff2html mrix, + /usr/bin/roff2pdf mrix, + /usr/bin/roff2ps mrix, + /usr/bin/roff2text mrix, + /usr/bin/roff2x mrix, + /usr/bin/soelim mrix, + /usr/bin/tbl mrix, + /usr/bin/tfmtodit mrix, + /usr/bin/troff mrix, + /usr/bin/xtotroff mrix, + + # at least its macros and fonts + /usr/libexec/groff/** r, + /usr/share/groff/** r, + + # Include additions to the abstraction + include if exists <abstractions/groff.d> diff --git a/profiles/apparmor.d/abstractions/kde b/profiles/apparmor.d/abstractions/kde index 5514e6326..f8cb31401 100644 --- a/profiles/apparmor.d/abstractions/kde +++ b/profiles/apparmor.d/abstractions/kde @@ -27,6 +27,9 @@ include <abstractions/qt5> /etc/kde4rc r, /etc/xdg/kdeglobals r, /etc/xdg/Trolltech.conf r, +/usr/share/desktop-base/kf5-settings/baloofilerc r, +/usr/share/desktop-base/kf5-settings/kdeglobals r, +/usr/share/desktop-base/kf5-settings/kscreenlockerrc r, /usr/share/knotifications5/*.notifyrc r, # KNotification::sendEvent() /usr/share/kubuntu-default-settings/kf5-settings/* r, diff --git a/profiles/apparmor.d/abstractions/kde-open5 b/profiles/apparmor.d/abstractions/kde-open5 index 5f4e0f753..d3adae298 100644 --- a/profiles/apparmor.d/abstractions/kde-open5 +++ b/profiles/apparmor.d/abstractions/kde-open5 @@ -29,8 +29,8 @@ # include <abstractions/ubuntu-browsers> # include <abstractions/ubuntu-email> # -# # Add if accesibility access is considered as required -# # (for message boxe in case exo-open fails) +# # Add if accessibility access is considered as required +# # (for message box in case exo-open fails) # include <abstractions/dbus-accessibility> # # # Add if audio support for message box is diff --git a/profiles/apparmor.d/abstractions/kerberosclient b/profiles/apparmor.d/abstractions/kerberosclient index 386e8c118..a4452c21e 100644 --- a/profiles/apparmor.d/abstractions/kerberosclient +++ b/profiles/apparmor.d/abstractions/kerberosclient @@ -22,6 +22,11 @@ /usr/lib/@{multiarch}/krb5/plugins/preauth/ r, /usr/lib/@{multiarch}/krb5/plugins/preauth/* mr, + /usr/lib{,32,64}/krb5/plugins/authdata/ r, + /usr/lib{,32,64}/krb5/plugins/authdata/* mr, + /usr/lib/@{multiarch}/krb5/plugins/authdata/ r, + /usr/lib/@{multiarch}/krb5/plugins/authdata/* mr, + /etc/krb5.keytab rk, /etc/krb5.conf r, /etc/krb5.conf.d/ r, diff --git a/profiles/apparmor.d/abstractions/nameservice b/profiles/apparmor.d/abstractions/nameservice index 7f53f2eb6..12b560211 100644 --- a/profiles/apparmor.d/abstractions/nameservice +++ b/profiles/apparmor.d/abstractions/nameservice @@ -23,6 +23,9 @@ @{etc_ro}/passwd r, @{etc_ro}/protocols r, + # On systems with authselect installed, /etc/nsswitch.conf is a symlink to /etc/authselect/nsswitch.conf + @{etc_ro}/authselect/nsswitch.conf r, + # libtirpc (used for NIS/YP login) needs this @{etc_ro}/netconfig r, diff --git a/profiles/apparmor.d/abstractions/nvidia b/profiles/apparmor.d/abstractions/nvidia index b2d475f16..13d56d3d2 100644 --- a/profiles/apparmor.d/abstractions/nvidia +++ b/profiles/apparmor.d/abstractions/nvidia @@ -23,9 +23,13 @@ @{sys}/devices/system/memory/block_size_bytes r, + owner @{HOME}/.cache/nvidia/ w, + owner @{HOME}/.cache/nvidia/GLCache/ rw, + owner @{HOME}/.cache/nvidia/GLCache/** rwk, owner @{HOME}/.nv/ w, owner @{HOME}/.nv/GLCache/ rw, owner @{HOME}/.nv/GLCache/** rwk, + owner @{PROC}/@{pid}/comm r, # somehwere in libnvidia-glcore.so unix (send, receive) type=dgram peer=(addr="@nvidia[0-9a-f]*"), diff --git a/profiles/apparmor.d/abstractions/openssl b/profiles/apparmor.d/abstractions/openssl index 02eba3915..65939ae44 100644 --- a/profiles/apparmor.d/abstractions/openssl +++ b/profiles/apparmor.d/abstractions/openssl @@ -11,11 +11,10 @@ abi <abi/3.0>, /etc/ssl/openssl.cnf r, + /etc/ssl/openssl-*.cnf r, /etc/ssl/{engdef,engines}.d/ r, /etc/ssl/{engdef,engines}.d/*.cnf r, /usr/share/ssl/openssl.cnf r, - @{PROC}/sys/crypto/fips_enabled r, - # Include additions to the abstraction include if exists <abstractions/openssl.d> diff --git a/profiles/apparmor.d/abstractions/samba b/profiles/apparmor.d/abstractions/samba index b5e167064..a17e31a18 100644 --- a/profiles/apparmor.d/abstractions/samba +++ b/profiles/apparmor.d/abstractions/samba @@ -23,11 +23,12 @@ /var/lib/samba/** rwk, /var/log/samba/cores/ rw, /var/log/samba/cores/** rw, - /var/log/samba/* w, + /var/log/samba/* rw, @{run}/{,lock/}samba/ w, @{run}/{,lock/}samba/*.tdb rwk, @{run}/{,lock/}samba/msg.{lock,sock}/ rwk, @{run}/{,lock/}samba/msg.{lock,sock}/[0-9]* rwk, + /var/cache/samba/*.tdb rwk, /var/cache/samba/msg.lock/ rwk, /var/cache/samba/msg.lock/[0-9]* rwk, diff --git a/profiles/apparmor.d/abstractions/snap_browsers b/profiles/apparmor.d/abstractions/snap_browsers index 06ca911a0..98fdeed29 100644 --- a/profiles/apparmor.d/abstractions/snap_browsers +++ b/profiles/apparmor.d/abstractions/snap_browsers @@ -38,5 +38,6 @@ profile snap_browsers { /snap/opera/[0-9]*/meta/{snap.yaml,hooks/} r, /var/lib/snapd/sequence/{chromium,firefox,opera}.json r, + /var/lib/snapd/inhibit/{chromium,firefox,opera}.lock rk, # add other browsers here } diff --git a/profiles/apparmor.d/abstractions/ssl_certs b/profiles/apparmor.d/abstractions/ssl_certs index 56ab53c7b..edb39646b 100644 --- a/profiles/apparmor.d/abstractions/ssl_certs +++ b/profiles/apparmor.d/abstractions/ssl_certs @@ -17,7 +17,7 @@ /etc/{,libre}ssl/certs/{,**} r, /{etc,usr/share}/pki/bl[ao]cklist/{,*} r, /{etc,usr/share}/pki/trust/{,*} r, - /{etc,usr/share}/pki/trust/anchors/{,**} r, + /{etc,usr/share}/pki/trust/{bl[oa]cklist,anchors}/{,**} r, /usr/share/ca-certificates/{,**} r, /usr/share/ssl/certs/ca-bundle.crt r, /usr/local/share/ca-certificates/{,**} r, @@ -42,9 +42,5 @@ /etc/certbot/archive/*/chain*.pem r, /etc/certbot/archive/*/fullchain*.pem r, - # crypto policies used by various libraries - /etc/crypto-policies/*/*.txt r, - /usr/share/crypto-policies/*/*.txt r, - # Include additions to the abstraction include if exists <abstractions/ssl_certs.d> diff --git a/profiles/apparmor.d/abstractions/svn-repositories b/profiles/apparmor.d/abstractions/svn-repositories index d518f1d0b..ca9f990c2 100644 --- a/profiles/apparmor.d/abstractions/svn-repositories +++ b/profiles/apparmor.d/abstractions/svn-repositories @@ -14,7 +14,7 @@ # it is intended to be included in profiles for svnserve/apache2 and maybe # some repository viewers like trac/viewvc - # no hooks exec by default; please define whatever you need explicitely. + # no hooks exec by default; please define whatever you need explicitly. /srv/svn/**/conf/* r, /srv/svn/**/format r, diff --git a/profiles/apparmor.d/abstractions/trash b/profiles/apparmor.d/abstractions/trash new file mode 100644 index 000000000..68a43c067 --- /dev/null +++ b/profiles/apparmor.d/abstractions/trash @@ -0,0 +1,75 @@ +abi <abi/3.0>, + +# requires <tunables/home> + + owner @{HOME}/.config/trashrc rw, + owner @{HOME}/.config/trashrc.lock rwk, + owner @{HOME}/.config/#[0-9]*[0-9] rwk, + owner @{HOME}/.config/trashrc.* rwl -> @{HOME}/.config/#[0-9]*[0-9], + + owner @{run}/user/@{uid}/#[0-9]*[0-9] rw, + owner @{run}/user/@{uid}/trash.so*.[0-9].slave-socket rwl -> @{run}/user/@{uid}/#[0-9]*[0-9], + + # Home trash location + owner @{HOME}/.local/share/Trash/ rw, + owner @{HOME}/.local/share/Trash/#[0-9]*[0-9] rw, + owner @{HOME}/.local/share/Trash/directorysizes{,.*} rwl -> @{HOME}/.local/share/Trash/#[0-9]*[0-9], + owner @{HOME}/.local/share/Trash/files/{,**} rw, + owner @{HOME}/.local/share/Trash/info/ rw, + owner @{HOME}/.local/share/Trash/info/*.trashinfo{,.*} rw, + owner @{HOME}/.local/share/Trash/expunged/ rw, + owner @{HOME}/.local/share/Trash/expunged/[0-9]* rw, + owner @{HOME}/.local/share/Trash/expunged/[0-9]*/ rw, + owner @{HOME}/.local/share/Trash/expunged/[0-9]*/** rw, + + # Partitions' trash location when the admin creates the .Trash/ folder in the top lvl dir + owner /media/*/.Trash/ rw, + owner /media/*/.Trash/@{uid}/ rw, + owner /media/*/.Trash/@{uid}/#[0-9]*[0-9] rw, + owner /media/*/.Trash/@{uid}/directorysizes{,.*} rwl -> /media/*/.Trash/@{uid}/#[0-9]*[0-9], + owner /media/*/.Trash/@{uid}/files/{,**} rw, + owner /media/*/.Trash/@{uid}/info/ rw, + owner /media/*/.Trash/@{uid}/info/*.trashinfo{,.*} rw, + owner /media/*/.Trash/@{uid}/expunged/ rw, + owner /media/*/.Trash/@{uid}/expunged/[0-9]* rw, + owner /media/*/.Trash/@{uid}/expunged/[0-9]*/ rw, + owner /media/*/.Trash/@{uid}/expunged/[0-9]*/** rw, + + # Partitions' trash location when the admin doesn't create the .Trash/ folder in the top lvl dir + owner /media/*/.Trash-@{uid}/ rw, + owner /media/*/.Trash-@{uid}/#[0-9]*[0-9] rw, + owner /media/*/.Trash-@{uid}/directorysizes{,.*} rwl -> /media/*/.Trash-@{uid}/#[0-9]*[0-9], + owner /media/*/.Trash-@{uid}/files/{,**} rw, + owner /media/*/.Trash-@{uid}/info/ rw, + owner /media/*/.Trash-@{uid}/info/*.trashinfo{,.*} rw, + owner /media/*/.Trash-@{uid}/expunged/ rw, + owner /media/*/.Trash-@{uid}/expunged/[0-9]* rw, + owner /media/*/.Trash-@{uid}/expunged/[0-9]*/ rw, + owner /media/*/.Trash-@{uid}/expunged/[0-9]*/** rw, + + # Removable media's trash location when the admin creates the .Trash/ folder in the top lvl dir + owner /media/*/*/.Trash/ rw, + owner /media/*/*/.Trash/@{uid}/ rw, + owner /media/*/*/.Trash/@{uid}/#[0-9]*[0-9] rw, + owner /media/*/*/.Trash/@{uid}/directorysizes{,.*} rwl -> /media/*/*/.Trash/@{uid}/#[0-9]*[0-9], + owner /media/*/*/.Trash/@{uid}/files/{,**} rw, + owner /media/*/*/.Trash/@{uid}/info/ rw, + owner /media/*/*/.Trash/@{uid}/info/*.trashinfo{,.*} rw, + owner /media/*/*/.Trash/@{uid}/expunged/ rw, + owner /media/*/*/.Trash/@{uid}/expunged/[0-9]* rw, + owner /media/*/*/.Trash/@{uid}/expunged/[0-9]*/ rw, + owner /media/*/*/.Trash/@{uid}/expunged/[0-9]*/** rw, + + # Removable media's trash location when the admin doesn't create the .Trash/ folder in the top lvl dir + owner /media/*/*/.Trash-@{uid}/ rw, + owner /media/*/*/.Trash-@{uid}/#[0-9]*[0-9] rw, + owner /media/*/*/.Trash-@{uid}/directorysizes{,.*} rwl -> /media/*/*/.Trash-@{uid}/#[0-9]*[0-9], + owner /media/*/*/.Trash-@{uid}/files/{,**} rw, + owner /media/*/*/.Trash-@{uid}/info/ rw, + owner /media/*/*/.Trash-@{uid}/info/*.trashinfo{,.*} rw, + owner /media/*/*/.Trash-@{uid}/expunged/ rw, + owner /media/*/*/.Trash-@{uid}/expunged/[0-9]* rw, + owner /media/*/*/.Trash-@{uid}/expunged/[0-9]*/ rw, + owner /media/*/*/.Trash-@{uid}/expunged/[0-9]*/** rw, + + include if exists <abstractions/trash.d> diff --git a/profiles/apparmor.d/abstractions/ubuntu-browsers.d/kde b/profiles/apparmor.d/abstractions/ubuntu-browsers.d/kde index bdac331e3..62db4f399 100644 --- a/profiles/apparmor.d/abstractions/ubuntu-browsers.d/kde +++ b/profiles/apparmor.d/abstractions/ubuntu-browsers.d/kde @@ -7,3 +7,6 @@ include <abstractions/kde> /usr/bin/kde4-config Cx -> sanitized_helper, + + # https://bugs.kde.org/show_bug.cgi?id=397399 + /usr/bin/plasma-browser-integration-host Cx -> sanitized_helper, diff --git a/profiles/apparmor.d/abstractions/ubuntu-helpers b/profiles/apparmor.d/abstractions/ubuntu-helpers index b9a3b1c34..8484a411b 100644 --- a/profiles/apparmor.d/abstractions/ubuntu-helpers +++ b/profiles/apparmor.d/abstractions/ubuntu-helpers @@ -36,6 +36,7 @@ profile sanitized_helper { include <abstractions/base> include <abstractions/X> + include if exists <local/ubuntu-helpers> # Allow all networking network inet, @@ -79,6 +80,7 @@ profile sanitized_helper { /opt/brave.com/brave{,-beta,-dev,-nightly}/chrome-sandbox PUxr, /opt/brave.com/brave{,-beta,-dev,-nightly}/brave-browser{,-beta,-dev,-nightly} Pixr, /opt/brave.com/brave{,-beta,-dev,-nightly}/brave Pixr, + /opt/brave.com/brave{,-beta,-dev,-nightly}/chrome_crashpad_handler Pixr, /opt/brave.com/brave{,-beta,-dev,-nightly}/{,**/}lib*.so{,.*} m, # Full access diff --git a/profiles/apparmor.d/abstractions/video b/profiles/apparmor.d/abstractions/video index e3cc30cc5..2b5634824 100644 --- a/profiles/apparmor.d/abstractions/video +++ b/profiles/apparmor.d/abstractions/video @@ -7,5 +7,14 @@ @{sys}/class/video4linux/ r, @{sys}/class/video4linux/** r, + owner /dev/shm/libv4l-* rw, + /dev/video[0-9]* rw, + @{sys}/devices/pci[0-9]*/**/usb[0-9]/**/video4linux/video[0-9]*/dev r, + @{sys}/devices/pci[0-9]*/**/usb[0-9]/**/{modalias,speed} r, + + @{sys}/devices/virtual/dmi/id/sys_vendor r, + @{sys}/devices/virtual/dmi/id/product_{name,version} r, + @{sys}/devices/virtual/dmi/id/board_{vendor,name,version} r, + # Include additions to the abstraction include if exists <abstractions/video.d> diff --git a/profiles/apparmor.d/abstractions/wayland b/profiles/apparmor.d/abstractions/wayland index 2b73925a2..368e2f77c 100644 --- a/profiles/apparmor.d/abstractions/wayland +++ b/profiles/apparmor.d/abstractions/wayland @@ -14,5 +14,8 @@ owner @{run}/user/*/wayland-[0-9]* rw, owner @{run}/user/*/{mesa,mutter,sdl,wayland-cursor,weston,xwayland}-shared-* rw, + #For compositors based on wlroots + owner /dev/shm/wlroots-* rw, + # Include additions to the abstraction include if exists <abstractions/wayland.d> diff --git a/profiles/apparmor.d/abstractions/wutmp b/profiles/apparmor.d/abstractions/wutmp index 46d33f795..a085c3c91 100644 --- a/profiles/apparmor.d/abstractions/wutmp +++ b/profiles/apparmor.d/abstractions/wutmp @@ -18,5 +18,8 @@ /var/log/btmp rwk, @{run}/utmp rwk, + # Some read the list of sessions from systemd + /run/systemd/sessions/ r, + # Include additions to the abstraction include if exists <abstractions/wutmp.d> diff --git a/profiles/apparmor.d/abstractions/xdg-open b/profiles/apparmor.d/abstractions/xdg-open index aed207104..11cf41053 100644 --- a/profiles/apparmor.d/abstractions/xdg-open +++ b/profiles/apparmor.d/abstractions/xdg-open @@ -41,7 +41,7 @@ include <abstractions/base> - # for openin with `exo-open` + # for opening with `exo-open` include <abstractions/exo-open> # for opening with `gio open <uri>` diff --git a/profiles/apparmor.d/lsb_release b/profiles/apparmor.d/lsb_release index ad8b998fc..421a52f7e 100644 --- a/profiles/apparmor.d/lsb_release +++ b/profiles/apparmor.d/lsb_release @@ -30,6 +30,8 @@ profile lsb_release { /{usr/,}bin/dash ixr, /usr/bin/basename ixr, /usr/bin/dpkg-query ixr, + /usr/bin/cat ixr, + /usr/bin/cut ixr, /usr/bin/getopt ixr, /usr/bin/sed ixr, /usr/bin/tr ixr, diff --git a/profiles/apparmor.d/nvidia_modprobe b/profiles/apparmor.d/nvidia_modprobe index 2502c49d4..558f48207 100644 --- a/profiles/apparmor.d/nvidia_modprobe +++ b/profiles/apparmor.d/nvidia_modprobe @@ -54,10 +54,10 @@ profile nvidia_modprobe { # System files /etc/modprobe.d/{,*.conf} r, - /etc/nvidia/current/*.conf r, + /etc/nvidia/{current,legacy*,tesla*}/*.conf r, @{sys}/module/ipmi_devintf/initstate r, @{sys}/module/ipmi_msghandler/initstate r, - @{sys}/module/nvidia/initstate r, + @{sys}/module/{drm,nvidia}/initstate r, @{PROC}/cmdline r, } diff --git a/profiles/apparmor.d/samba-bgqd b/profiles/apparmor.d/samba-bgqd index c205c2614..a63021328 100644 --- a/profiles/apparmor.d/samba-bgqd +++ b/profiles/apparmor.d/samba-bgqd @@ -14,7 +14,7 @@ profile samba-bgqd /usr/lib*/samba/{,samba/}samba-bgqd { @{PROC}/sys/kernel/core_pattern r, owner @{PROC}/@{pid}/fd/ r, - @{run}/samba/samba-bgqd.pid wk, + @{run}/{,samba/}samba-bgqd.pid rwk, /usr/lib*/samba/{,samba/}samba-bgqd mr, /var/cache/samba/printing/*.tdb rwk, diff --git a/profiles/apparmor.d/samba-dcerpcd b/profiles/apparmor.d/samba-dcerpcd index c186441ee..12ea0f557 100644 --- a/profiles/apparmor.d/samba-dcerpcd +++ b/profiles/apparmor.d/samba-dcerpcd @@ -16,7 +16,7 @@ include <tunables/global> profile samba-dcerpcd /usr/lib*/samba/{,samba/}samba-dcerpcd { include <abstractions/samba-rpcd> - @{run}/samba/samba-dcerpcd.pid wk, + @{run}/{,samba/}samba-dcerpcd.pid rwk, /usr/lib*/samba/{,samba/}samba-dcerpcd mr, diff --git a/profiles/apparmor.d/samba-rpcd-spoolss b/profiles/apparmor.d/samba-rpcd-spoolss index a86873dd3..904fa0196 100644 --- a/profiles/apparmor.d/samba-rpcd-spoolss +++ b/profiles/apparmor.d/samba-rpcd-spoolss @@ -20,7 +20,7 @@ profile samba-rpcd-spoolss /usr/lib*/samba/{,samba/}rpcd_spoolss { /usr/lib*/samba/{,samba/}samba-bgqd Px -> samba-bgqd, /var/cache/samba/printing/ w, /var/cache/samba/printing/*.tdb rwk, - @{run}/samba/samba-bgqd.pid rk, + @{run}/{,samba/}samba-bgqd.pid rk, /dev/urandom rw, diff --git a/profiles/apparmor.d/sbin.syslogd b/profiles/apparmor.d/sbin.syslogd index eec1ba571..33c0e7ce4 100644 --- a/profiles/apparmor.d/sbin.syslogd +++ b/profiles/apparmor.d/sbin.syslogd @@ -30,6 +30,7 @@ profile syslogd /{usr/,}{bin,sbin}/syslogd { /dev/log wl, /var/lib/*/dev/log wl, + /dev/kmsg r, /proc/kmsg r, /dev/tty* w, diff --git a/profiles/apparmor.d/tunables/etc b/profiles/apparmor.d/tunables/etc index c144621df..142874af8 100644 --- a/profiles/apparmor.d/tunables/etc +++ b/profiles/apparmor.d/tunables/etc @@ -13,11 +13,15 @@ # with the goal of having only user-modified config files in /etc/, directories # like /usr/etc/ get introduced for storing the default config. -# @{etc_ro} contains read-only directories with configuration files. +# @{etc_ro} contains directories with configuration files, including read-only directories. # Do not use @{etc_ro} in rules that allow write access. @{etc_ro}=/etc/ /usr/etc/ # @{etc_rw} contains directories where writing to configuration files is allowed. +# @{etc_rw} should always be a subset of @{etc_ro}. +# +# Only use @{etc_rw} if the profile allows writing to a configuration file. +# For rules that only allows read access, use @{etc_ro}. @{etc_rw}=/etc/ # Also, include files in tunables/etc.d/ for site-specific adjustments to diff --git a/profiles/apparmor.d/tunables/home b/profiles/apparmor.d/tunables/home index 4df34b55f..b99cd491c 100644 --- a/profiles/apparmor.d/tunables/home +++ b/profiles/apparmor.d/tunables/home @@ -9,17 +9,17 @@ # # ------------------------------------------------------------------ +# @{HOMEDIRS} is a space-separated list of where user home directories +# are stored, for programs that must enumerate all home directories on a +# system. +@{HOMEDIRS}=/home/ + # @{HOME} is a space-separated list of all user home directories. While # it doesn't refer to a specific home directory (AppArmor doesn't # enforce discretionary access controls) it can be used as if it did # refer to a specific home directory @{HOME}=@{HOMEDIRS}/*/ /root/ -# @{HOMEDIRS} is a space-separated list of where user home directories -# are stored, for programs that must enumerate all home directories on a -# system. -@{HOMEDIRS}=/home/ - # Also, include files in tunables/home.d for site-specific adjustments to # @{HOMEDIRS}. include <tunables/home.d> diff --git a/profiles/apparmor.d/usr.lib.dovecot.anvil b/profiles/apparmor.d/usr.lib.dovecot.anvil index ac78bc9da..e5f3d16ac 100644 --- a/profiles/apparmor.d/usr.lib.dovecot.anvil +++ b/profiles/apparmor.d/usr.lib.dovecot.anvil @@ -13,7 +13,7 @@ abi <abi/3.0>, include <tunables/global> -profile dovecot-anvil /usr/lib/dovecot/anvil { +profile dovecot-anvil /usr/lib*/dovecot/anvil { include <abstractions/base> include <abstractions/dovecot-common> @@ -24,7 +24,7 @@ profile dovecot-anvil /usr/lib/dovecot/anvil { @{run}/dovecot/anvil rw, @{run}/dovecot/anvil-auth-penalty rw, - /usr/lib/dovecot/anvil mr, + /usr/lib*/dovecot/anvil mr, # Site-specific additions and overrides. See local/README for details. include if exists <local/usr.lib.dovecot.anvil> diff --git a/profiles/apparmor.d/usr.lib.dovecot.auth b/profiles/apparmor.d/usr.lib.dovecot.auth index 9a53247b3..1b49f72c0 100644 --- a/profiles/apparmor.d/usr.lib.dovecot.auth +++ b/profiles/apparmor.d/usr.lib.dovecot.auth @@ -14,7 +14,7 @@ abi <abi/3.0>, include <tunables/global> -profile dovecot-auth /usr/lib/dovecot/auth { +profile dovecot-auth /usr/lib*/dovecot/auth { include <abstractions/authentication> include <abstractions/base> include <abstractions/mysql> @@ -34,7 +34,7 @@ profile dovecot-auth /usr/lib/dovecot/auth { /etc/my.cnf.d/*.cnf r, /etc/dovecot/* r, - /usr/lib/dovecot/auth mr, + /usr/lib*/dovecot/auth mr, /var/lib/dovecot/auth-chroot/* r, # kerberos replay cache diff --git a/profiles/apparmor.d/usr.lib.dovecot.config b/profiles/apparmor.d/usr.lib.dovecot.config index 94ddfdb18..c9beadc69 100644 --- a/profiles/apparmor.d/usr.lib.dovecot.config +++ b/profiles/apparmor.d/usr.lib.dovecot.config @@ -13,7 +13,7 @@ abi <abi/3.0>, include <tunables/global> -profile dovecot-config /usr/lib/dovecot/config { +profile dovecot-config /usr/lib*/dovecot/config { include <abstractions/base> include <abstractions/nameservice> include <abstractions/dovecot-common> @@ -24,8 +24,8 @@ profile dovecot-config /usr/lib/dovecot/config { /etc/dovecot/** r, /usr/bin/doveconf rix, - /usr/lib/dovecot/config mr, - /usr/lib/dovecot/managesieve Px, + /usr/lib*/dovecot/config mr, + /usr/lib*/dovecot/managesieve Px, /usr/share/dovecot/** r, /var/lib/dovecot/ssl-parameters.dat r, diff --git a/profiles/apparmor.d/usr.lib.dovecot.deliver b/profiles/apparmor.d/usr.lib.dovecot.deliver index 2df23a0f8..f801b9be4 100644 --- a/profiles/apparmor.d/usr.lib.dovecot.deliver +++ b/profiles/apparmor.d/usr.lib.dovecot.deliver @@ -16,7 +16,7 @@ abi <abi/3.0>, include <tunables/global> include <tunables/dovecot> -profile dovecot-deliver /usr/lib/dovecot/deliver { +profile dovecot-deliver /usr/lib*/dovecot/deliver { include <abstractions/base> include <abstractions/nameservice> include <abstractions/dovecot-common> @@ -32,7 +32,7 @@ profile dovecot-deliver /usr/lib/dovecot/deliver { /etc/dovecot/dovecot-postfix.conf r, # ??? @{HOME} r, # ??? - /usr/lib/dovecot/deliver mr, + /usr/lib*/dovecot/deliver mr, # Site-specific additions and overrides. See local/README for details. include if exists <local/usr.lib.dovecot.deliver> diff --git a/profiles/apparmor.d/usr.lib.dovecot.dict b/profiles/apparmor.d/usr.lib.dovecot.dict index d0edd88e0..5c88f08c9 100644 --- a/profiles/apparmor.d/usr.lib.dovecot.dict +++ b/profiles/apparmor.d/usr.lib.dovecot.dict @@ -13,7 +13,7 @@ abi <abi/3.0>, include <tunables/global> -profile dovecot-dict /usr/lib/dovecot/dict { +profile dovecot-dict /usr/lib*/dovecot/dict { include <abstractions/base> include <abstractions/mysql> include <abstractions/nameservice> @@ -27,7 +27,7 @@ profile dovecot-dict /usr/lib/dovecot/dict { /etc/dovecot/dovecot-database.conf.ext r, /etc/dovecot/dovecot-dict-sql.conf.ext r, /etc/my.cnf r, - /usr/lib/dovecot/dict mr, + /usr/lib*/dovecot/dict mr, # Site-specific additions and overrides. See local/README for details. include if exists <local/usr.lib.dovecot.dict> diff --git a/profiles/apparmor.d/usr.lib.dovecot.director b/profiles/apparmor.d/usr.lib.dovecot.director new file mode 100644 index 000000000..ec2295eaf --- /dev/null +++ b/profiles/apparmor.d/usr.lib.dovecot.director @@ -0,0 +1,27 @@ +# ------------------------------------------------------------------ +# +# Copyright (C) 2020 SUSE LLC +# +# 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. +# +# ------------------------------------------------------------------ +# vim: ft=apparmor + +include <tunables/global> + +profile dovecot-director /usr/lib*/dovecot/director flags=(attach_disconnected) { + include <abstractions/base> + include <abstractions/dovecot-common> + include <abstractions/nameservice> + + capability setuid, + capability sys_chroot, + + /run/dovecot/login/proxy-notify rw, + /usr/lib*/dovecot/director mr, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.lib.dovecot.director> +} diff --git a/profiles/apparmor.d/usr.lib.dovecot.doveadm-server b/profiles/apparmor.d/usr.lib.dovecot.doveadm-server new file mode 100644 index 000000000..bd2cdb1fe --- /dev/null +++ b/profiles/apparmor.d/usr.lib.dovecot.doveadm-server @@ -0,0 +1,22 @@ +# ------------------------------------------------------------------ +# +# Copyright (C) 2020 SUSE LLC +# +# 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. +# +# ------------------------------------------------------------------ +# vim: ft=apparmor + +include <tunables/global> + +profile dovecot-doveadm-server /usr/lib*/dovecot/doveadm-server flags=(attach_disconnected) { + include <abstractions/base> + include <abstractions/dovecot-common> + + /usr/lib*/dovecot/doveadm-server mr, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.lib.dovecot.doveadm-server> +} diff --git a/profiles/apparmor.d/usr.lib.dovecot.dovecot-auth b/profiles/apparmor.d/usr.lib.dovecot.dovecot-auth index 779bcce23..65a14b37e 100644 --- a/profiles/apparmor.d/usr.lib.dovecot.dovecot-auth +++ b/profiles/apparmor.d/usr.lib.dovecot.dovecot-auth @@ -14,7 +14,7 @@ abi <abi/3.0>, include <tunables/global> -profile dovecot-dovecot-auth /usr/lib/dovecot/dovecot-auth { +profile dovecot-dovecot-auth /usr/lib*/dovecot/dovecot-auth { include <abstractions/authentication> include <abstractions/base> include <abstractions/nameservice> @@ -25,7 +25,7 @@ profile dovecot-dovecot-auth /usr/lib/dovecot/dovecot-auth { capability dac_override, @{PROC}/@{pid}/mounts r, - /usr/lib/dovecot/dovecot-auth mr, + /usr/lib*/dovecot/dovecot-auth mr, @{run}/dovecot/** rw, # required for postfix+dovecot integration /var/spool/postfix/private/dovecot-auth w, diff --git a/profiles/apparmor.d/usr.lib.dovecot.dovecot-lda b/profiles/apparmor.d/usr.lib.dovecot.dovecot-lda index c9d3fe4b1..fa208f0b8 100644 --- a/profiles/apparmor.d/usr.lib.dovecot.dovecot-lda +++ b/profiles/apparmor.d/usr.lib.dovecot.dovecot-lda @@ -14,7 +14,7 @@ abi <abi/3.0>, include <tunables/global> include <tunables/dovecot> -profile dovecot-dovecot-lda /usr/lib/dovecot/dovecot-lda flags=(attach_disconnected) { +profile dovecot-dovecot-lda /usr/lib*/dovecot/dovecot-lda flags=(attach_disconnected) { include <abstractions/base> include <abstractions/nameservice> include <abstractions/dovecot-common> @@ -30,7 +30,7 @@ profile dovecot-dovecot-lda /usr/lib/dovecot/dovecot-lda flags=(attach_disconnec @{run}/dovecot/mounts r, @{run}/dovecot/auth-userdb rw, /usr/bin/doveconf mrix, - /usr/lib/dovecot/dovecot-lda mrix, + /usr/lib*/dovecot/dovecot-lda mrix, /usr/{bin,sbin}/sendmail Cx -> sendmail, /usr/share/dovecot/protocols.d/ r, /usr/share/dovecot/protocols.d/** r, diff --git a/profiles/apparmor.d/usr.lib.dovecot.imap b/profiles/apparmor.d/usr.lib.dovecot.imap index 90b55bd41..cc5d2e14a 100644 --- a/profiles/apparmor.d/usr.lib.dovecot.imap +++ b/profiles/apparmor.d/usr.lib.dovecot.imap @@ -15,13 +15,12 @@ abi <abi/3.0>, include <tunables/global> include <tunables/dovecot> -profile dovecot-imap /usr/lib/dovecot/imap { +profile dovecot-imap /usr/lib*/dovecot/imap { include <abstractions/base> include <abstractions/nameservice> include <abstractions/dovecot-common> capability setuid, - deny capability block_suspend, network unix stream, @@ -38,7 +37,7 @@ profile dovecot-imap /usr/lib/dovecot/imap { @{PROC}/@{pid}/attr/{apparmor/,}current rw, @{PROC}/@{pid}/stat r, /usr/bin/doveconf rix, - /usr/lib/dovecot/imap mrix, + /usr/lib*/dovecot/imap mrix, /usr/share/dovecot/** r, @{run}/dovecot/login/imap rw, @{run}/dovecot/auth-master rw, diff --git a/profiles/apparmor.d/usr.lib.dovecot.imap-login b/profiles/apparmor.d/usr.lib.dovecot.imap-login index fccde842e..96fce2a3e 100644 --- a/profiles/apparmor.d/usr.lib.dovecot.imap-login +++ b/profiles/apparmor.d/usr.lib.dovecot.imap-login @@ -14,7 +14,7 @@ abi <abi/3.0>, include <tunables/global> -profile dovecot-imap-login /usr/lib/dovecot/imap-login { +profile dovecot-imap-login /usr/lib*/dovecot/imap-login { include <abstractions/base> include <abstractions/dovecot-common> include <abstractions/openssl> @@ -26,7 +26,7 @@ profile dovecot-imap-login /usr/lib/dovecot/imap-login { network inet6 stream, network unix stream, - /usr/lib/dovecot/imap-login mr, + /usr/lib*/dovecot/imap-login mr, @{run}/dovecot/anvil rw, @{run}/dovecot/login-master-notify* rw, @{run}/dovecot/login/ r, diff --git a/profiles/apparmor.d/usr.lib.dovecot.lmtp b/profiles/apparmor.d/usr.lib.dovecot.lmtp index ad26eff3e..462d5e6b7 100644 --- a/profiles/apparmor.d/usr.lib.dovecot.lmtp +++ b/profiles/apparmor.d/usr.lib.dovecot.lmtp @@ -14,7 +14,7 @@ abi <abi/3.0>, include <tunables/global> include <tunables/dovecot> -profile dovecot-lmtp /usr/lib/dovecot/lmtp { +profile dovecot-lmtp /usr/lib*/dovecot/lmtp { include <abstractions/base> include <abstractions/nameservice> include <abstractions/dovecot-common> @@ -35,7 +35,7 @@ profile dovecot-lmtp /usr/lib/dovecot/lmtp { owner @{PROC}/@{pid}/stat r, @{PROC}/*/mounts r, /tmp/dovecot.lmtp.* rw, - /usr/lib/dovecot/lmtp mr, + /usr/lib*/dovecot/lmtp mr, @{run}/dovecot/mounts r, # Site-specific additions and overrides. See local/README for details. diff --git a/profiles/apparmor.d/usr.lib.dovecot.log b/profiles/apparmor.d/usr.lib.dovecot.log index 1cc1224b2..e527f290d 100644 --- a/profiles/apparmor.d/usr.lib.dovecot.log +++ b/profiles/apparmor.d/usr.lib.dovecot.log @@ -13,11 +13,11 @@ abi <abi/3.0>, include <tunables/global> -profile dovecot-log /usr/lib/dovecot/log flags=(attach_disconnected) { +profile dovecot-log /usr/lib*/dovecot/log flags=(attach_disconnected) { include <abstractions/base> include <abstractions/dovecot-common> - /usr/lib/dovecot/log mr, + /usr/lib*/dovecot/log mr, # Site-specific additions and overrides. See local/README for details. include if exists <local/usr.lib.dovecot.log> diff --git a/profiles/apparmor.d/usr.lib.dovecot.managesieve b/profiles/apparmor.d/usr.lib.dovecot.managesieve index beaa66edf..83d4926d0 100644 --- a/profiles/apparmor.d/usr.lib.dovecot.managesieve +++ b/profiles/apparmor.d/usr.lib.dovecot.managesieve @@ -15,7 +15,7 @@ abi <abi/3.0>, include <tunables/global> include <tunables/dovecot> -profile dovecot-managesieve /usr/lib/dovecot/managesieve { +profile dovecot-managesieve /usr/lib*/dovecot/managesieve { include <abstractions/base> include <abstractions/dovecot-common> @@ -29,7 +29,7 @@ profile dovecot-managesieve /usr/lib/dovecot/managesieve { /etc/dovecot/** r, /usr/bin/doveconf rix, - /usr/lib/dovecot/managesieve mrix, + /usr/lib*/dovecot/managesieve mrix, # Site-specific additions and overrides. See local/README for details. include if exists <local/usr.lib.dovecot.managesieve> diff --git a/profiles/apparmor.d/usr.lib.dovecot.managesieve-login b/profiles/apparmor.d/usr.lib.dovecot.managesieve-login index 518157814..b5ca9053a 100644 --- a/profiles/apparmor.d/usr.lib.dovecot.managesieve-login +++ b/profiles/apparmor.d/usr.lib.dovecot.managesieve-login @@ -16,7 +16,7 @@ abi <abi/3.0>, include <tunables/global> -profile dovecot-managesieve-login /usr/lib/dovecot/managesieve-login { +profile dovecot-managesieve-login /usr/lib*/dovecot/managesieve-login { include <abstractions/base> include <abstractions/dovecot-common> include <abstractions/openssl> @@ -28,7 +28,7 @@ profile dovecot-managesieve-login /usr/lib/dovecot/managesieve-login { network inet6 stream, network unix stream, - /usr/lib/dovecot/managesieve-login mr, + /usr/lib*/dovecot/managesieve-login mr, @{run}/dovecot/login-master-notify* rw, @{run}/dovecot/login/ r, @{run}/dovecot/login/* rw, diff --git a/profiles/apparmor.d/usr.lib.dovecot.pop3 b/profiles/apparmor.d/usr.lib.dovecot.pop3 index ed010ddaf..bef8d923a 100644 --- a/profiles/apparmor.d/usr.lib.dovecot.pop3 +++ b/profiles/apparmor.d/usr.lib.dovecot.pop3 @@ -15,7 +15,7 @@ abi <abi/3.0>, include <tunables/global> include <tunables/dovecot> -profile dovecot-pop3 /usr/lib/dovecot/pop3 { +profile dovecot-pop3 /usr/lib*/dovecot/pop3 { include <abstractions/base> include <abstractions/nameservice> include <abstractions/dovecot-common> @@ -27,7 +27,7 @@ profile dovecot-pop3 /usr/lib/dovecot/pop3 { @{HOME} r, # ??? @{PROC}/@{pid}/stat r, - /usr/lib/dovecot/pop3 mr, + /usr/lib*/dovecot/pop3 mr, # Site-specific additions and overrides. See local/README for details. include if exists <local/usr.lib.dovecot.pop3> diff --git a/profiles/apparmor.d/usr.lib.dovecot.pop3-login b/profiles/apparmor.d/usr.lib.dovecot.pop3-login index 2bcc3fec4..457a0f637 100644 --- a/profiles/apparmor.d/usr.lib.dovecot.pop3-login +++ b/profiles/apparmor.d/usr.lib.dovecot.pop3-login @@ -14,7 +14,7 @@ abi <abi/3.0>, include <tunables/global> -profile dovecot-pop3-login /usr/lib/dovecot/pop3-login { +profile dovecot-pop3-login /usr/lib*/dovecot/pop3-login { include <abstractions/base> include <abstractions/dovecot-common> include <abstractions/openssl> @@ -26,7 +26,7 @@ profile dovecot-pop3-login /usr/lib/dovecot/pop3-login { network inet6 stream, network unix stream, - /usr/lib/dovecot/pop3-login mr, + /usr/lib*/dovecot/pop3-login mr, @{run}/dovecot/anvil rw, @{run}/dovecot/login-master-notify* rw, @{run}/dovecot/login/ r, diff --git a/profiles/apparmor.d/usr.lib.dovecot.replicator b/profiles/apparmor.d/usr.lib.dovecot.replicator new file mode 100644 index 000000000..743d43fb4 --- /dev/null +++ b/profiles/apparmor.d/usr.lib.dovecot.replicator @@ -0,0 +1,36 @@ +# ------------------------------------------------------------------ +# +# Copyright (C) 2020 SUSE LLC +# Copyright (C) 2009-2010 Canonical Ltd. +# Copyright (C) 2011-2013 Christian Boltz +# +# 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. +# +# ------------------------------------------------------------------ +# vim: ft=apparmor +# for https://wiki.dovecot.org/Replication + +include <tunables/dovecot> +include <tunables/global> + +profile dovecot-replicator /usr/lib*/dovecot/replicator { + include <abstractions/base> + include <abstractions/dovecot-common> + include <abstractions/nameservice> + + network unix stream, + + /etc/dovecot/conf.d/ r, + /etc/dovecot/conf.d/** r, + /etc/dovecot/dovecot.conf r, + /usr/lib*/dovecot/replicator mr, + /usr/share/dovecot/** r, + /{,var/}run/dovecot/auth-master rw, + @{DOVECOT_MAILSTORE}/ rw, + @{DOVECOT_MAILSTORE}/** rwlk, + /var/lib/dovecot/replicator.db rw, + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.lib.dovecot.replicator> +} diff --git a/profiles/apparmor.d/usr.lib.dovecot.script-login b/profiles/apparmor.d/usr.lib.dovecot.script-login index aca227581..4b5c37a12 100644 --- a/profiles/apparmor.d/usr.lib.dovecot.script-login +++ b/profiles/apparmor.d/usr.lib.dovecot.script-login @@ -14,14 +14,14 @@ abi <abi/3.0>, include <tunables/global> -profile dovecot-script-login /usr/lib/dovecot/script-login { +profile dovecot-script-login /usr/lib*/dovecot/script-login { include <abstractions/base> include <abstractions/dovecot-common> include <abstractions/nameservice> capability setuid, - /usr/lib/dovecot/script-login mrPx, + /usr/lib*/dovecot/script-login mrPx, # NOTE: You'll need to allow execution of your actual login script. # The recommended way is to add a rule for it in local/usr.lib.dovecot.script-login diff --git a/profiles/apparmor.d/usr.lib.dovecot.ssl-params b/profiles/apparmor.d/usr.lib.dovecot.ssl-params index 64a1eba80..f31b9f37e 100644 --- a/profiles/apparmor.d/usr.lib.dovecot.ssl-params +++ b/profiles/apparmor.d/usr.lib.dovecot.ssl-params @@ -13,13 +13,13 @@ abi <abi/3.0>, include <tunables/global> -profile dovecot-ssl-params /usr/lib/dovecot/ssl-params { +profile dovecot-ssl-params /usr/lib*/dovecot/ssl-params { include <abstractions/base> include <abstractions/dovecot-common> @{run}/dovecot/ssl-params rw, @{run}/dovecot/login/ssl-params rw, - /usr/lib/dovecot/ssl-params mr, + /usr/lib*/dovecot/ssl-params mr, /var/lib/dovecot/ssl-parameters.dat rw, /var/lib/dovecot/ssl-parameters.dat.tmp rwk, diff --git a/profiles/apparmor.d/usr.lib.dovecot.stats b/profiles/apparmor.d/usr.lib.dovecot.stats index 37e774065..c4e9fec70 100644 --- a/profiles/apparmor.d/usr.lib.dovecot.stats +++ b/profiles/apparmor.d/usr.lib.dovecot.stats @@ -13,7 +13,7 @@ abi <abi/3.0>, include <tunables/global> -profile dovecot-stats /usr/lib/dovecot/stats { +profile dovecot-stats /usr/lib*/dovecot/stats { include <abstractions/base> include <abstractions/dovecot-common> @@ -24,7 +24,7 @@ profile dovecot-stats /usr/lib/dovecot/stats { network inet stream, network inet6 stream, - /usr/lib/dovecot/stats mr, + /usr/lib*/dovecot/stats mr, # Site-specific additions and overrides. See local/README for details. include if exists <local/usr.lib.dovecot.stats> diff --git a/profiles/apparmor.d/usr.sbin.avahi-daemon b/profiles/apparmor.d/usr.sbin.avahi-daemon index 010b3858c..af31aec49 100644 --- a/profiles/apparmor.d/usr.sbin.avahi-daemon +++ b/profiles/apparmor.d/usr.sbin.avahi-daemon @@ -1,7 +1,7 @@ abi <abi/3.0>, include <tunables/global> -profile avahi-daemon /usr/{bin,sbin}/avahi-daemon { +profile avahi-daemon /usr/{bin,sbin}/avahi-daemon flags=(attach_disconnected) { include <abstractions/base> include <abstractions/consoles> include <abstractions/dbus> diff --git a/profiles/apparmor.d/usr.sbin.dnsmasq b/profiles/apparmor.d/usr.sbin.dnsmasq index 9a5ca0b78..6c4f20904 100644 --- a/profiles/apparmor.d/usr.sbin.dnsmasq +++ b/profiles/apparmor.d/usr.sbin.dnsmasq @@ -115,6 +115,9 @@ profile dnsmasq /usr/{bin,sbin}/dnsmasq flags=(attach_disconnected) { owner @{run}/user/*/containers/cni/dnsname/*/addnhosts r, owner @{run}/user/*/containers/cni/dnsname/*/pidfile rw, + # waydroid lxc-net pid file + @{run}/waydroid-lxc/dnsmasq.pid rw, + profile libvirt_leaseshelper { include <abstractions/base> diff --git a/profiles/apparmor.d/usr.sbin.dovecot b/profiles/apparmor.d/usr.sbin.dovecot index daa95ba5d..bb15a7b7f 100644 --- a/profiles/apparmor.d/usr.sbin.dovecot +++ b/profiles/apparmor.d/usr.sbin.dovecot @@ -33,10 +33,10 @@ profile dovecot /usr/{bin,sbin}/dovecot flags=(attach_disconnected) { capability sys_chroot, capability sys_resource, - signal send peer=/usr/lib/dovecot/*, + signal send peer=/usr/lib*/dovecot/*, signal send peer=dovecot-*, - unix (receive, send) type=stream peer=(label=/usr/lib/dovecot/anvil), + unix (receive, send) type=stream peer=(label=/usr/lib*/dovecot/anvil), unix (receive, send) type=stream peer=(label=dovecot-anvil), /etc/dovecot/** r, @@ -46,23 +46,26 @@ profile dovecot /usr/{bin,sbin}/dovecot flags=(attach_disconnected) { @{PROC}/@{pid}/mounts r, @{PROC}/sys/fs/suid_dumpable r, /usr/bin/doveconf rix, - /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 mrPx, - /usr/lib/dovecot/log mrPx, - /usr/lib/dovecot/managesieve mrPx, - /usr/lib/dovecot/managesieve-login Pxmr, - /usr/lib/dovecot/pop3 mrPx, - /usr/lib/dovecot/pop3-login Pxmr, - /usr/lib/dovecot/script-login Px, - /usr/lib/dovecot/ssl-build-param rix, - /usr/lib/dovecot/ssl-params mrPx, - /usr/lib/dovecot/stats Px, + /usr/lib*/dovecot/anvil mrPx, + /usr/lib*/dovecot/auth mrPx, + /usr/lib*/dovecot/config mrPx, + /usr/lib*/dovecot/dict mrPx, + /usr/lib*/dovecot/director mrPx, + /usr/lib*/dovecot/doveadm-server mrPx, + /usr/lib*/dovecot/dovecot-auth Pxmr, + /usr/lib*/dovecot/imap Pxmr, + /usr/lib*/dovecot/imap-login Pxmr, + /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 mrPx, + /usr/lib*/dovecot/pop3-login Pxmr, + /usr/lib*/dovecot/replicator mrPx, + /usr/lib*/dovecot/script-login Px, + /usr/lib*/dovecot/ssl-build-param rix, + /usr/lib*/dovecot/ssl-params mrPx, + /usr/lib*/dovecot/stats Px, /usr/{bin,sbin}/dovecot mrix, /usr/share/dovecot/dh.pem r, /usr/share/dovecot/protocols.d/ r, diff --git a/profiles/apparmor.d/usr.sbin.nscd b/profiles/apparmor.d/usr.sbin.nscd index 80f970988..4b7b3b9a2 100644 --- a/profiles/apparmor.d/usr.sbin.nscd +++ b/profiles/apparmor.d/usr.sbin.nscd @@ -41,6 +41,13 @@ profile nscd /usr/{bin,sbin}/nscd { @{PROC}/@{pid}/fd/* r, @{PROC}/@{pid}/mounts r, + # systemd-userdb + /{etc,run,run/host,/usr/lib}/userdb/ r, + /{etc,run,run/host,/usr/lib}/userdb/*.{user,user-privileged,group,group-privileged} r, + + # needed by unscd + @{run}/systemd/notify w, + # Site-specific additions and overrides. See local/README for details. include if exists <local/usr.sbin.nscd> } diff --git a/profiles/apparmor.d/usr.sbin.smbd b/profiles/apparmor.d/usr.sbin.smbd index be516275e..c4e6d70c7 100644 --- a/profiles/apparmor.d/usr.sbin.smbd +++ b/profiles/apparmor.d/usr.sbin.smbd @@ -49,14 +49,14 @@ profile smbd /usr/{bin,sbin}/smbd { /usr/{bin,sbin}/smbldap-useradd Px, /var/cache/samba/** rwk, /var/{cache,lib}/samba/printing/printers.tdb mrw, + /var/lib/nscd/netgroup r, /var/lib/samba/** rwk, /var/lib/sss/pubconf/kdcinfo.* r, @{run}/dbus/system_bus_socket rw, - @{run}/smbd.pid rwk, + @{run}/{,samba/}smbd.pid rwk, @{run}/samba/** rk, @{run}/samba/ncalrpc/ rw, @{run}/samba/ncalrpc/** rw, - @{run}/samba/smbd.pid rw, /var/spool/samba/** rw, @{HOMEDIRS}/** lrwk, diff --git a/profiles/apparmor.d/usr.sbin.winbindd b/profiles/apparmor.d/usr.sbin.winbindd index adc3a010d..b33d754d4 100644 --- a/profiles/apparmor.d/usr.sbin.winbindd +++ b/profiles/apparmor.d/usr.sbin.winbindd @@ -6,6 +6,7 @@ profile winbindd /usr/{bin,sbin}/winbindd { include <abstractions/base> include <abstractions/nameservice> include <abstractions/samba> + include <abstractions/kerberosclient> deny capability block_suspend, @@ -30,6 +31,7 @@ profile winbindd /usr/{bin,sbin}/winbindd { /usr/{bin,sbin}/winbindd mr, /var/cache/krb5rcache/* rwk, /var/cache/samba/*.tdb rwk, + /var/lib/sss/pubconf/kdcinfo.* r, /var/log/samba/log.winbindd rw, @{run}/{samba/,}winbindd.pid rwk, @{run}/samba/winbindd/ rw, diff --git a/profiles/apparmor.d/zgrep b/profiles/apparmor.d/zgrep new file mode 100644 index 000000000..ed342b20d --- /dev/null +++ b/profiles/apparmor.d/zgrep @@ -0,0 +1,66 @@ +# ------------------------------------------------------------------ +# +# Copyright (C) 2022 Christian Boltz +# +# 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. +# +# ------------------------------------------------------------------ + +abi <abi/3.0>, + +include <tunables/global> + +profile zgrep /usr/bin/{x,}zgrep { + include <abstractions/base> + include <abstractions/bash> + + /dev/tty rw, + /usr/bin/{ba,da,}sh ix, + /usr/bin/bzip2 Cx -> helper, + /usr/bin/cat ix, + /usr/bin/egrep Cx -> helper, + /usr/bin/expr ix, + /usr/bin/fgrep Cx -> helper, + /usr/bin/grep Cx -> helper, + /usr/bin/gzip Cx -> helper, + /usr/bin/mktemp ix, + /usr/bin/rm ix, + /usr/bin/sed Cx -> sed, + /usr/bin/xz Cx -> helper, + /usr/bin/xzgrep r, + /usr/bin/zgrep Cx -> helper, + /usr/bin/zstd Cx -> helper, + owner /tmp/zgrep* rw, + /usr/bin/zgrep r, + + include if exists <local/zgrep> + + profile helper { + include <abstractions/base> + + capability dac_override, + capability dac_read_search, + + /dev/tty w, + + /usr/bin/{ba,da,}sh ix, + /usr/bin/bzip2 mr, + /usr/bin/grep mrix, + /usr/bin/gzip mr, + /usr/bin/xz mr, + /usr/bin/zstd mr, + /{,**} r, + + } + + profile sed { + include <abstractions/base> + + /dev/tty rw, + /usr/bin/{ba,da,}sh ix, + /usr/bin/sed mr, + + } +} diff --git a/profiles/apparmor/profiles/extras/bin.netstat b/profiles/apparmor/profiles/extras/bin.netstat index fd8d15606..41ab5e6c9 100644 --- a/profiles/apparmor/profiles/extras/bin.netstat +++ b/profiles/apparmor/profiles/extras/bin.netstat @@ -46,4 +46,7 @@ profile netstat /{usr/,}bin/netstat { @{PROC}/@{pid}/net/udplite r, @{PROC}/@{pid}/net/udplit6 r, @{PROC}/@{pid}/net/unix r, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/bin.netstat> } diff --git a/profiles/apparmor/profiles/extras/usr.bin.chromium-browser b/profiles/apparmor/profiles/extras/chromium_browser similarity index 100% rename from profiles/apparmor/profiles/extras/usr.bin.chromium-browser rename to profiles/apparmor/profiles/extras/chromium_browser diff --git a/profiles/apparmor/profiles/extras/etc.cron.daily.logrotate b/profiles/apparmor/profiles/extras/etc.cron.daily.logrotate index 7ba4b38ea..079c8c11a 100644 --- a/profiles/apparmor/profiles/extras/etc.cron.daily.logrotate +++ b/profiles/apparmor/profiles/extras/etc.cron.daily.logrotate @@ -74,4 +74,7 @@ include <tunables/global> /var/spool/slrnpull/ wr, /var/spool/slrnpull/log* wrl, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/etc.cron.daily.logrotate> } diff --git a/profiles/apparmor/profiles/extras/etc.cron.daily.slocate.cron b/profiles/apparmor/profiles/extras/etc.cron.daily.slocate.cron index 2b551dfdf..de731c44f 100644 --- a/profiles/apparmor/profiles/extras/etc.cron.daily.slocate.cron +++ b/profiles/apparmor/profiles/extras/etc.cron.daily.slocate.cron @@ -25,4 +25,7 @@ include <tunables/global> /usr/bin/slocate mixr, /usr/bin/renice mixr, /** r , + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/etc.cron.daily.slocate.cron> } diff --git a/profiles/apparmor/profiles/extras/etc.cron.daily.tmpwatch b/profiles/apparmor/profiles/extras/etc.cron.daily.tmpwatch index fa0b95617..ee4e5c673 100644 --- a/profiles/apparmor/profiles/extras/etc.cron.daily.tmpwatch +++ b/profiles/apparmor/profiles/extras/etc.cron.daily.tmpwatch @@ -22,4 +22,7 @@ include <tunables/global> /var/cache/man*/** r, /var/tmp r, /var/tmp/** rwl, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/etc.cron.daily.tmpwatch> } diff --git a/profiles/apparmor/profiles/extras/usr.lib.firefox.firefox b/profiles/apparmor/profiles/extras/firefox similarity index 96% rename from profiles/apparmor/profiles/extras/usr.lib.firefox.firefox rename to profiles/apparmor/profiles/extras/firefox index 6d869d674..051a4b45d 100644 --- a/profiles/apparmor/profiles/extras/usr.lib.firefox.firefox +++ b/profiles/apparmor/profiles/extras/firefox @@ -18,7 +18,7 @@ include <tunables/global> # /usr/lib/firefox-4.0b8/firefox # but not: # /usr/lib/firefox-4.0b8/firefox.sh -/usr/lib/firefox{,-[0-9]*}/firefox{,*[^s][^h]} { +profile firefox /usr/lib/firefox{,-[0-9]*}/firefox{,*[^s][^h]} { include <abstractions/audio> include <abstractions/cups-client> include <abstractions/dbus-session> @@ -128,7 +128,6 @@ include <tunables/global> @{HOME}/.mozilla/firefox/*/gmp-widevinecdm/*/lib*so m, # Site-specific additions and overrides. See local/README for details. - # Local path is disabled, we only enable them for profiles we promote - # out of extras. include if exists <local/usr.bin.firefox> + include if exists <local/firefox> } diff --git a/profiles/apparmor/profiles/extras/usr.lib.firefox.firefox.sh b/profiles/apparmor/profiles/extras/firefox.sh similarity index 70% rename from profiles/apparmor/profiles/extras/usr.lib.firefox.firefox.sh rename to profiles/apparmor/profiles/extras/firefox.sh index 95a7a7de4..352fa36cb 100644 --- a/profiles/apparmor/profiles/extras/usr.lib.firefox.firefox.sh +++ b/profiles/apparmor/profiles/extras/firefox.sh @@ -4,7 +4,7 @@ abi <abi/3.0>, include <tunables/global> -/usr/lib/firefox/firefox.sh { +profile firefox.sh /usr/lib/firefox/firefox.sh { include <abstractions/base> include <abstractions/bash> include <abstractions/consoles> @@ -19,4 +19,6 @@ include <tunables/global> /usr/lib/firefox/firefox px, /usr/share/misc/magic.mgc r, + # Site-specific additions and overrides. See local/README for details. + include if exists <local/firefox.sh> } diff --git a/profiles/apparmor/profiles/extras/postfix-anvil b/profiles/apparmor/profiles/extras/postfix-anvil index 2aec87286..b0b165da3 100644 --- a/profiles/apparmor/profiles/extras/postfix-anvil +++ b/profiles/apparmor/profiles/extras/postfix-anvil @@ -23,4 +23,7 @@ profile postfix-anvil /usr/lib/postfix/{bin/,sbin/,}anvil { /etc/postfix/main.cf r, /{var/spool/postfix/,}private/anvil rw, /{var/spool/postfix/,}pid/unix.anvil rwk, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/postfix-anvil> } diff --git a/profiles/apparmor/profiles/extras/postfix-bounce b/profiles/apparmor/profiles/extras/postfix-bounce index f57b3ac36..ffe69461b 100644 --- a/profiles/apparmor/profiles/extras/postfix-bounce +++ b/profiles/apparmor/profiles/extras/postfix-bounce @@ -47,4 +47,7 @@ profile postfix-bounce /usr/lib/postfix/{bin/,sbin/,}bounce { /{var/spool/postfix/,}pid/unix.bounce rwk, /{var/spool/postfix/,}pid/unix.defer rwk, /{var/spool/postfix/,}pid/unix.trace rwk, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/postfix-bounce> } diff --git a/profiles/apparmor/profiles/extras/postfix-cleanup b/profiles/apparmor/profiles/extras/postfix-cleanup index e277f14cc..6789ae149 100644 --- a/profiles/apparmor/profiles/extras/postfix-cleanup +++ b/profiles/apparmor/profiles/extras/postfix-cleanup @@ -38,4 +38,7 @@ profile postfix-cleanup /usr/lib/postfix/{bin/,sbin/,}cleanup { /etc/{m,fs}tab r, /etc/postfix/* r, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/postfix-cleanup> } diff --git a/profiles/apparmor/profiles/extras/postfix-discard b/profiles/apparmor/profiles/extras/postfix-discard index fbfe784f8..c236c386a 100644 --- a/profiles/apparmor/profiles/extras/postfix-discard +++ b/profiles/apparmor/profiles/extras/postfix-discard @@ -18,4 +18,7 @@ profile postfix-discard /usr/lib/postfix/{bin/,sbin/,}discard { include <abstractions/base> /usr/lib/postfix/{bin/,sbin/,}discard mrix, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/postfix-discard> } diff --git a/profiles/apparmor/profiles/extras/postfix-dnsblog b/profiles/apparmor/profiles/extras/postfix-dnsblog index f8ce329b1..05c6a5f95 100644 --- a/profiles/apparmor/profiles/extras/postfix-dnsblog +++ b/profiles/apparmor/profiles/extras/postfix-dnsblog @@ -19,4 +19,7 @@ profile postfix-dnsblog /usr/lib/postfix/{bin/,sbin/,}dnsblog { /usr/lib/postfix/{bin/,sbin/,}dnsblog mrix, /var/spool/postfix/private/dnsblog rw, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/postfix-dnsblog> } diff --git a/profiles/apparmor/profiles/extras/postfix-error b/profiles/apparmor/profiles/extras/postfix-error index 4719f8973..709875515 100644 --- a/profiles/apparmor/profiles/extras/postfix-error +++ b/profiles/apparmor/profiles/extras/postfix-error @@ -26,4 +26,6 @@ profile postfix-error /usr/lib/postfix/{bin/,sbin/,}error { /var/spool/postfix/pid/unix.retry rwk, owner /var/spool/postfix/private/defer w, + # Site-specific additions and overrides. See local/README for details. + include if exists <local/postfix-error> } diff --git a/profiles/apparmor/profiles/extras/postfix-flush b/profiles/apparmor/profiles/extras/postfix-flush index f8395519d..c51478e32 100644 --- a/profiles/apparmor/profiles/extras/postfix-flush +++ b/profiles/apparmor/profiles/extras/postfix-flush @@ -40,4 +40,6 @@ profile postfix-flush /usr/lib/postfix/{bin/,sbin/,}flush { @{HOME}/.forward r, + # Site-specific additions and overrides. See local/README for details. + include if exists <local/postfix-flush> } diff --git a/profiles/apparmor/profiles/extras/postfix-lmtp b/profiles/apparmor/profiles/extras/postfix-lmtp index d133d6459..9ffbc74bd 100644 --- a/profiles/apparmor/profiles/extras/postfix-lmtp +++ b/profiles/apparmor/profiles/extras/postfix-lmtp @@ -24,4 +24,6 @@ profile postfix-lmtp /usr/lib/postfix/{bin/,sbin/,}lmtp { /var/spool/postfix/active/* rwk, /var/spool/postfix/pid/unix.lmtp rwk, + # Site-specific additions and overrides. See local/README for details. + include if exists <local/postfix-lmtp> } diff --git a/profiles/apparmor/profiles/extras/postfix-local b/profiles/apparmor/profiles/extras/postfix-local index 292e617af..72b7a5742 100644 --- a/profiles/apparmor/profiles/extras/postfix-local +++ b/profiles/apparmor/profiles/extras/postfix-local @@ -44,4 +44,7 @@ profile postfix-local /usr/lib/postfix/{bin/,sbin/,}local { /{var/spool/postfix/,}public/{cleanup,flush} rw, # deliver mail /var/mail/* wk, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/postfix-local> } diff --git a/profiles/apparmor/profiles/extras/postfix-master b/profiles/apparmor/profiles/extras/postfix-master index 59a227e04..e06c45506 100644 --- a/profiles/apparmor/profiles/extras/postfix-master +++ b/profiles/apparmor/profiles/extras/postfix-master @@ -58,4 +58,7 @@ profile postfix-master /usr/lib/postfix/{bin/,sbin/,}master { /usr/lib/postfix/{bin/,sbin/,}trivial-rewrite Px, owner /var/lib/postfix/master.lock rwk, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/postfix-master> } diff --git a/profiles/apparmor/profiles/extras/postfix-nqmgr b/profiles/apparmor/profiles/extras/postfix-nqmgr index 717c9add6..0a2939176 100644 --- a/profiles/apparmor/profiles/extras/postfix-nqmgr +++ b/profiles/apparmor/profiles/extras/postfix-nqmgr @@ -45,4 +45,7 @@ profile postfix-nqmgr /usr/lib/postfix/{bin/,sbin/,}nqmgr { /{var/spool/postfix/,}private/local w, /{var/spool/postfix/,}public/flush w, /{var/spool/postfix/,}public/qmgr r, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/postfix-nqmgr> } diff --git a/profiles/apparmor/profiles/extras/postfix-oqmgr b/profiles/apparmor/profiles/extras/postfix-oqmgr index 625e92966..443243c37 100644 --- a/profiles/apparmor/profiles/extras/postfix-oqmgr +++ b/profiles/apparmor/profiles/extras/postfix-oqmgr @@ -20,4 +20,7 @@ profile postfix-oqmgr /usr/lib/postfix/{bin/,sbin/,}oqmgr { include <abstractions/postfix-common> /usr/lib/postfix/{bin/,sbin/,}oqmgr mrix, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/postfix-oqmgr> } diff --git a/profiles/apparmor/profiles/extras/postfix-pickup b/profiles/apparmor/profiles/extras/postfix-pickup index 33b1e5ca0..6bd5af916 100644 --- a/profiles/apparmor/profiles/extras/postfix-pickup +++ b/profiles/apparmor/profiles/extras/postfix-pickup @@ -24,4 +24,7 @@ profile postfix-pickup /usr/lib/postfix/{bin/,sbin/,}pickup { /{var/spool/postfix/,}public/pickup r, /{var/spool/postfix/,}maildrop/ r, /{var/spool/postfix/,}maildrop/* rwl, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/postfix-pickup> } diff --git a/profiles/apparmor/profiles/extras/postfix-pipe b/profiles/apparmor/profiles/extras/postfix-pipe index dbc0867f5..6567568bc 100644 --- a/profiles/apparmor/profiles/extras/postfix-pipe +++ b/profiles/apparmor/profiles/extras/postfix-pipe @@ -27,4 +27,6 @@ profile postfix-pipe /usr/lib/postfix/{bin/,sbin/,}pipe { /var/spool/postfix/private/rewrite w, /var/spool/postfix/private/trace w, + # Site-specific additions and overrides. See local/README for details. + include if exists <local/postfix-pipe> } diff --git a/profiles/apparmor/profiles/extras/postfix-postscreen b/profiles/apparmor/profiles/extras/postfix-postscreen index 46d9c0558..ace8edb6c 100644 --- a/profiles/apparmor/profiles/extras/postfix-postscreen +++ b/profiles/apparmor/profiles/extras/postfix-postscreen @@ -16,4 +16,7 @@ profile postfix-postscreen /usr/lib/postfix/{bin/,sbin/,}postscreen { include <abstractions/base> /usr/lib/postfix/{bin/,sbin/,}postscreen mrix, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/postfix-postscreen> } diff --git a/profiles/apparmor/profiles/extras/postfix-proxymap b/profiles/apparmor/profiles/extras/postfix-proxymap index b3b44e076..18f0f73de 100644 --- a/profiles/apparmor/profiles/extras/postfix-proxymap +++ b/profiles/apparmor/profiles/extras/postfix-proxymap @@ -23,4 +23,7 @@ profile postfix-proxymap /usr/lib/postfix/{bin/,sbin/,}proxymap { /etc/my.cnf r, /usr/lib/postfix/{bin/,sbin/,}proxymap mrix, /{var/spool/postfix/,}private/proxymap rw, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/postfix-proxymap> } diff --git a/profiles/apparmor/profiles/extras/postfix-qmgr b/profiles/apparmor/profiles/extras/postfix-qmgr index e02d3a1d9..93f74e70b 100644 --- a/profiles/apparmor/profiles/extras/postfix-qmgr +++ b/profiles/apparmor/profiles/extras/postfix-qmgr @@ -51,4 +51,7 @@ profile postfix-qmgr /usr/lib/postfix/{bin/,sbin/,}qmgr { /{var/spool/postfix/,}private/smtp w, /{var/spool/postfix/,}private/trace w, /{var/spool/postfix/,}private/uucp w, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/postfix-qmgr> } diff --git a/profiles/apparmor/profiles/extras/postfix-qmqpd b/profiles/apparmor/profiles/extras/postfix-qmqpd index ecd64cab5..fbcdd9aee 100644 --- a/profiles/apparmor/profiles/extras/postfix-qmqpd +++ b/profiles/apparmor/profiles/extras/postfix-qmqpd @@ -19,4 +19,7 @@ profile postfix-qmqpd /usr/lib/postfix/{bin/,sbin/,}qmqpd { include <abstractions/postfix-common> /usr/lib/postfix/{bin/,sbin/,}qmqpd mrix, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/postfix-qmqpd> } diff --git a/profiles/apparmor/profiles/extras/postfix-scache b/profiles/apparmor/profiles/extras/postfix-scache index a584f837e..070171cab 100644 --- a/profiles/apparmor/profiles/extras/postfix-scache +++ b/profiles/apparmor/profiles/extras/postfix-scache @@ -21,4 +21,7 @@ profile postfix-scache /usr/lib/postfix/{bin/,sbin/,}scache { include <abstractions/postfix-common> /usr/lib/postfix/{bin/,sbin/,}scache mrix, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/postfix-scache> } diff --git a/profiles/apparmor/profiles/extras/postfix-showq b/profiles/apparmor/profiles/extras/postfix-showq index 473ccdcd3..be2ed2fa7 100644 --- a/profiles/apparmor/profiles/extras/postfix-showq +++ b/profiles/apparmor/profiles/extras/postfix-showq @@ -48,4 +48,7 @@ profile postfix-showq /usr/lib/postfix/{bin/,sbin/,}showq { /{var/spool/postfix/,}pid/unix.showq rwk, owner /{var/spool/postfix,}/defer/[0-9A-F]/[0-9A-F]* r, owner /{var/spool/postfix,}/deferred/[0-9A-F]/[0-9A-F]* r, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/postfix-showq> } diff --git a/profiles/apparmor/profiles/extras/postfix-smtp b/profiles/apparmor/profiles/extras/postfix-smtp index a0ca40210..bf26529d4 100644 --- a/profiles/apparmor/profiles/extras/postfix-smtp +++ b/profiles/apparmor/profiles/extras/postfix-smtp @@ -45,4 +45,7 @@ profile postfix-smtp /usr/lib/postfix/{bin/,sbin/,}smtp { /etc/postfix/prng_exch rw, /usr/share/ssl/certs/ca-bundle.crt r, /etc/mtab r, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/postfix-smtp> } diff --git a/profiles/apparmor/profiles/extras/postfix-smtpd b/profiles/apparmor/profiles/extras/postfix-smtpd index 1676d2ab9..8b397f32b 100644 --- a/profiles/apparmor/profiles/extras/postfix-smtpd +++ b/profiles/apparmor/profiles/extras/postfix-smtpd @@ -52,4 +52,7 @@ profile postfix-smtpd /usr/lib/postfix/{bin/,sbin/,}smtpd { /{var/spool/postfix/,}public/cleanup rw, /{,var/}run/sasl2/mux w, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/postfix-smtpd> } diff --git a/profiles/apparmor/profiles/extras/postfix-spawn b/profiles/apparmor/profiles/extras/postfix-spawn index 86db87f21..721849ad5 100644 --- a/profiles/apparmor/profiles/extras/postfix-spawn +++ b/profiles/apparmor/profiles/extras/postfix-spawn @@ -19,4 +19,7 @@ profile postfix-spawn /usr/lib/postfix/{bin/,sbin/,}spawn { include <abstractions/postfix-common> /usr/lib/postfix/{bin/,sbin/,}spawn mrix, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/postfix-spawn> } diff --git a/profiles/apparmor/profiles/extras/postfix-tlsmgr b/profiles/apparmor/profiles/extras/postfix-tlsmgr index 743391e2c..f74c39333 100644 --- a/profiles/apparmor/profiles/extras/postfix-tlsmgr +++ b/profiles/apparmor/profiles/extras/postfix-tlsmgr @@ -17,6 +17,7 @@ include <tunables/global> profile postfix-tlsmgr /usr/lib/postfix/{bin/,sbin/,}tlsmgr { include <abstractions/base> include <abstractions/nameservice> + include <abstractions/openssl> include <abstractions/postfix-common> /usr/lib/postfix/{bin/,sbin/,}tlsmgr mrix, @@ -28,4 +29,7 @@ profile postfix-tlsmgr /usr/lib/postfix/{bin/,sbin/,}tlsmgr { /{,var/}run/smtpd_tls_session_cache.db rw, /var/lib/postfix/smtpd_scache.db rwk, /var/lib/postfix/smtp_scache.db rwk, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/postfix-tlsmgr> } diff --git a/profiles/apparmor/profiles/extras/postfix-trivial-rewrite b/profiles/apparmor/profiles/extras/postfix-trivial-rewrite index 27c12c831..59fc6b4d7 100644 --- a/profiles/apparmor/profiles/extras/postfix-trivial-rewrite +++ b/profiles/apparmor/profiles/extras/postfix-trivial-rewrite @@ -26,4 +26,7 @@ profile postfix-trivial-rewrite /usr/lib/postfix/{bin/,sbin/,}trivial-rewrite { /etc/{m,fs}tab r, /var/spool/postfix/pid/unix.rewrite rw, /{var/spool/postfix/,}private/rewrite rw, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/postfix-trivial-rewrite> } diff --git a/profiles/apparmor/profiles/extras/postfix-verify b/profiles/apparmor/profiles/extras/postfix-verify index d7d629d1d..b2f52d950 100644 --- a/profiles/apparmor/profiles/extras/postfix-verify +++ b/profiles/apparmor/profiles/extras/postfix-verify @@ -19,4 +19,7 @@ profile postfix-verify /usr/lib/postfix/{bin/,sbin/,}verify { include <abstractions/postfix-common> /usr/lib/postfix/{bin/,sbin/,}verify mrix, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/postfix-verify> } diff --git a/profiles/apparmor/profiles/extras/postfix-virtual b/profiles/apparmor/profiles/extras/postfix-virtual index d477f1d4e..89b2f59d4 100644 --- a/profiles/apparmor/profiles/extras/postfix-virtual +++ b/profiles/apparmor/profiles/extras/postfix-virtual @@ -23,4 +23,7 @@ profile postfix-virtual /usr/lib/postfix/{bin/,sbin/,}virtual { /var/spool/postfix/active/* rw, /var/spool/postfix/pid/unix.virtual rw, /var/spool/postfix/private/bounce w, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/postfix-virtual> } diff --git a/profiles/apparmor/profiles/extras/sbin.dhclient b/profiles/apparmor/profiles/extras/sbin.dhclient index 02d67dd31..095615a97 100644 --- a/profiles/apparmor/profiles/extras/sbin.dhclient +++ b/profiles/apparmor/profiles/extras/sbin.dhclient @@ -87,5 +87,6 @@ profile dhclient /{usr/,}sbin/dhclient { /var/lib/dhcp/* rw, /{,var/}run/nm-dhclient-*.conf r, + # Site-specific additions and overrides. See local/README for details. include if exists <local/sbin.dhclient> } diff --git a/profiles/apparmor/profiles/extras/sbin.dhclient-script b/profiles/apparmor/profiles/extras/sbin.dhclient-script index d972b6093..16a9a5e8f 100644 --- a/profiles/apparmor/profiles/extras/sbin.dhclient-script +++ b/profiles/apparmor/profiles/extras/sbin.dhclient-script @@ -27,5 +27,6 @@ profile dhclient-script /{usr/,}sbin/dhclient-script { /{usr/,}sbin/ip rix, /{usr/,}sbin/resolvconf rPUx, + # Site-specific additions and overrides. See local/README for details. include if exists <local/sbin.dhclient-script> } diff --git a/profiles/apparmor/profiles/extras/sbin.dhcpcd b/profiles/apparmor/profiles/extras/sbin.dhcpcd index 53b3b3567..60745d309 100644 --- a/profiles/apparmor/profiles/extras/sbin.dhcpcd +++ b/profiles/apparmor/profiles/extras/sbin.dhcpcd @@ -44,4 +44,7 @@ profile dhcpcd /{usr/,}sbin/dhcpcd { /var/lib/dhcpcd/dhcpcd-*.info rw, /var/lib/dhcpcd/dhcpcd-*.info.old rw, /{,var/}run/dhcpcd-*.pid rwl, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/sbin.dhcpcd> } diff --git a/profiles/apparmor/profiles/extras/sbin.portmap b/profiles/apparmor/profiles/extras/sbin.portmap index 0d5b23936..c1beff557 100644 --- a/profiles/apparmor/profiles/extras/sbin.portmap +++ b/profiles/apparmor/profiles/extras/sbin.portmap @@ -23,4 +23,7 @@ profile portmap /{usr/,}sbin/portmap { /etc/bindresvport.blacklist r, /{usr/,}sbin/portmap rmix, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/sbin.portmap> } diff --git a/profiles/apparmor/profiles/extras/sbin.resmgrd b/profiles/apparmor/profiles/extras/sbin.resmgrd index c794cacae..aee87796f 100644 --- a/profiles/apparmor/profiles/extras/sbin.resmgrd +++ b/profiles/apparmor/profiles/extras/sbin.resmgrd @@ -31,4 +31,7 @@ profile resmgrd /{usr/,}sbin/resmgrd { /{,var/}run/fence* lrw, /{,var/}run/resmgr/classes/** wl, /{run,var}/lock/LCK* lrw, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/sbin.resmgrd> } diff --git a/profiles/apparmor/profiles/extras/sbin.rpc.lockd b/profiles/apparmor/profiles/extras/sbin.rpc.lockd index 8a198a279..dab9dfc07 100644 --- a/profiles/apparmor/profiles/extras/sbin.rpc.lockd +++ b/profiles/apparmor/profiles/extras/sbin.rpc.lockd @@ -15,4 +15,7 @@ include <tunables/global> profile rpc.lockd /{usr/,}sbin/rpc.lockd { include <abstractions/base> /{usr/,}sbin/rpc.lockd rmix, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/sbin.rpc.lockd> } diff --git a/profiles/apparmor/profiles/extras/sbin.rpc.statd b/profiles/apparmor/profiles/extras/sbin.rpc.statd index 58300d1d4..ec0d85705 100644 --- a/profiles/apparmor/profiles/extras/sbin.rpc.statd +++ b/profiles/apparmor/profiles/extras/sbin.rpc.statd @@ -53,4 +53,7 @@ profile rpc.statd /{usr/,}sbin/rpc.statd { @{run}/rpc.statd.pid w, @{run}/rpcbind.sock rw, @{run}/sm-notify.pid w, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/sbin.rpc.statd> } diff --git a/profiles/apparmor/profiles/extras/usr.NX.bin.nxclient b/profiles/apparmor/profiles/extras/usr.NX.bin.nxclient index 1173b8d06..d1244f39a 100644 --- a/profiles/apparmor/profiles/extras/usr.NX.bin.nxclient +++ b/profiles/apparmor/profiles/extras/usr.NX.bin.nxclient @@ -36,4 +36,7 @@ include <tunables/global> @{HOME}/.Xauthority-l rwl, @{HOME}/.ssh/config r, @{HOME}/.ssh/known_hosts rw, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.NX.bin.nxclient> } diff --git a/profiles/apparmor/profiles/extras/usr.bin.acroread b/profiles/apparmor/profiles/extras/usr.bin.acroread index f24f0a64a..d88aaadf7 100644 --- a/profiles/apparmor/profiles/extras/usr.bin.acroread +++ b/profiles/apparmor/profiles/extras/usr.bin.acroread @@ -59,4 +59,7 @@ include <tunables/global> /usr/lib/jvm/java-*/jre/lib/fonts/** r, /usr/lib/ooo-*/share/fonts/** r, /usr/share/icons r, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.bin.acroread> } diff --git a/profiles/apparmor/profiles/extras/usr.bin.apropos b/profiles/apparmor/profiles/extras/usr.bin.apropos index 292cd6de9..0ac126cc1 100644 --- a/profiles/apparmor/profiles/extras/usr.bin.apropos +++ b/profiles/apparmor/profiles/extras/usr.bin.apropos @@ -25,4 +25,7 @@ include <tunables/global> /usr/bin/tr mixr, /var/cache/man/whatis r, /var/cache/man/** r, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.bin.apropos> } diff --git a/profiles/apparmor/profiles/extras/usr.bin.dumpcap b/profiles/apparmor/profiles/extras/usr.bin.dumpcap index 556f3d9f1..c23c378c6 100644 --- a/profiles/apparmor/profiles/extras/usr.bin.dumpcap +++ b/profiles/apparmor/profiles/extras/usr.bin.dumpcap @@ -38,4 +38,7 @@ include <tunables/global> owner /tmp/*pcap{,ng} rw, owner @{HOME}/**pcap{,ng} rw, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.bin.dumpcap> } diff --git a/profiles/apparmor/profiles/extras/usr.bin.evolution-2.10 b/profiles/apparmor/profiles/extras/usr.bin.evolution-2.10 index 48c11bf95..50e8e64c4 100644 --- a/profiles/apparmor/profiles/extras/usr.bin.evolution-2.10 +++ b/profiles/apparmor/profiles/extras/usr.bin.evolution-2.10 @@ -20,7 +20,7 @@ # Mark mail as junk mail # Print mail message with lpr local # Print mail message with cups remote -# View pdf attachements +# View pdf attachments # Decrypt using gpg # # Send Mail: @@ -155,4 +155,7 @@ include <tunables/global> /usr/X11R6/lib/Acrobat7/Resource/Font r, /usr/X11R6/lib/Acrobat7/Resource/Font/** r, /var/tmp r, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.bin.evolution-2.10> } diff --git a/profiles/apparmor/profiles/extras/usr.bin.fam b/profiles/apparmor/profiles/extras/usr.bin.fam index 8b8385ac0..3981ef420 100644 --- a/profiles/apparmor/profiles/extras/usr.bin.fam +++ b/profiles/apparmor/profiles/extras/usr.bin.fam @@ -21,4 +21,7 @@ include <tunables/global> # it makes some level of sense for FAM to read all files on the # filesystem, even if this is a little unfortunate. /** r, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.bin.fam> } diff --git a/profiles/apparmor/profiles/extras/usr.bin.freshclam b/profiles/apparmor/profiles/extras/usr.bin.freshclam index d0bf30b74..69ce3a56a 100644 --- a/profiles/apparmor/profiles/extras/usr.bin.freshclam +++ b/profiles/apparmor/profiles/extras/usr.bin.freshclam @@ -17,6 +17,7 @@ include <tunables/global> include <abstractions/base> include <abstractions/consoles> include <abstractions/nameservice> + include <abstractions/openssl> capability setgid, capability setuid, @@ -27,4 +28,6 @@ include <tunables/global> /var/lib/clamav/** rw, owner /run/clamav/freshclam.pid w, + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.bin.freshclam> } diff --git a/profiles/apparmor/profiles/extras/usr.bin.gaim b/profiles/apparmor/profiles/extras/usr.bin.gaim index e5ae3ef11..0ed2fb8e0 100644 --- a/profiles/apparmor/profiles/extras/usr.bin.gaim +++ b/profiles/apparmor/profiles/extras/usr.bin.gaim @@ -66,4 +66,7 @@ include <tunables/global> /usr/share/icons r, /usr/share/tcl/tcl*/encoding/* r, /{,var/}run/.resmgr_socket w, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.bin.gaim> } diff --git a/profiles/apparmor/profiles/extras/usr.bin.man b/profiles/apparmor/profiles/extras/usr.bin.man index 4dcc19c2c..ffc3adf07 100644 --- a/profiles/apparmor/profiles/extras/usr.bin.man +++ b/profiles/apparmor/profiles/extras/usr.bin.man @@ -26,4 +26,6 @@ include <tunables/global> /usr/bin/man r, /usr/lib/man-db/man Px, + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.bin.man> } diff --git a/profiles/apparmor/profiles/extras/usr.bin.mlmmj-bounce b/profiles/apparmor/profiles/extras/usr.bin.mlmmj-bounce index a562dfe23..6eaa4aba7 100644 --- a/profiles/apparmor/profiles/extras/usr.bin.mlmmj-bounce +++ b/profiles/apparmor/profiles/extras/usr.bin.mlmmj-bounce @@ -39,4 +39,7 @@ include <tunables/global> /usr/share/mlmmj/text.skel/*/* r, /var/spool/mlmmj/*/control/* r, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.bin.mlmmj-bounce> } diff --git a/profiles/apparmor/profiles/extras/usr.bin.mlmmj-maintd b/profiles/apparmor/profiles/extras/usr.bin.mlmmj-maintd index 366d074f8..bf300e676 100644 --- a/profiles/apparmor/profiles/extras/usr.bin.mlmmj-maintd +++ b/profiles/apparmor/profiles/extras/usr.bin.mlmmj-maintd @@ -51,4 +51,6 @@ include <tunables/global> /usr/share/mlmmj/text.skel/*/digest r, /var/spool/mlmmj/*/mlmmj.operation.log rwk, + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.bin.mlmmj-maintd> } diff --git a/profiles/apparmor/profiles/extras/usr.bin.mlmmj-make-ml.sh b/profiles/apparmor/profiles/extras/usr.bin.mlmmj-make-ml.sh index bbdd0e721..3ca7b9abf 100644 --- a/profiles/apparmor/profiles/extras/usr.bin.mlmmj-make-ml.sh +++ b/profiles/apparmor/profiles/extras/usr.bin.mlmmj-make-ml.sh @@ -31,7 +31,7 @@ include <tunables/global> /{usr/,}bin/mkdir mixr, /{usr/,}bin/touch mixr, /usr/bin/which mixr, - # if mkdir cant read the current work directory it jumps into / + # if mkdir can't read the current working directory it jumps into / # allow reading that dir. / r, @@ -43,4 +43,7 @@ include <tunables/global> /var/spool r, /var/spool/mlmmj rw, /var/spool/mlmmj/** w, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.bin.mlmmj-make-ml.sh> } diff --git a/profiles/apparmor/profiles/extras/usr.bin.mlmmj-process b/profiles/apparmor/profiles/extras/usr.bin.mlmmj-process index 7a9a6ff1c..abeb56ada 100644 --- a/profiles/apparmor/profiles/extras/usr.bin.mlmmj-process +++ b/profiles/apparmor/profiles/extras/usr.bin.mlmmj-process @@ -45,4 +45,6 @@ include <tunables/global> /var/spool/mlmmj/*/moderation/* rw, /etc/mlmmj/text/*/* r, + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.bin.mlmmj-process> } diff --git a/profiles/apparmor/profiles/extras/usr.bin.mlmmj-receive b/profiles/apparmor/profiles/extras/usr.bin.mlmmj-receive index a0742b476..78f11b6bb 100644 --- a/profiles/apparmor/profiles/extras/usr.bin.mlmmj-receive +++ b/profiles/apparmor/profiles/extras/usr.bin.mlmmj-receive @@ -21,4 +21,7 @@ include <tunables/global> /usr/bin/mlmmj-receive mr, /var/spool/mlmmj/*/incoming/ rw, /var/spool/mlmmj/*/incoming/* rw, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.bin.mlmmj-receive> } diff --git a/profiles/apparmor/profiles/extras/usr.bin.mlmmj-recieve b/profiles/apparmor/profiles/extras/usr.bin.mlmmj-recieve index ebce17d77..5337b256d 100644 --- a/profiles/apparmor/profiles/extras/usr.bin.mlmmj-recieve +++ b/profiles/apparmor/profiles/extras/usr.bin.mlmmj-recieve @@ -23,4 +23,7 @@ include <tunables/global> /usr/bin/mlmmj-process Px, /usr/bin/mlmmj-recieve mr, /var/spool/mlmmj/*/incoming/* w, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.bin.mlmmj-recieve> } diff --git a/profiles/apparmor/profiles/extras/usr.bin.mlmmj-send b/profiles/apparmor/profiles/extras/usr.bin.mlmmj-send index 4ffb9d715..f399af930 100644 --- a/profiles/apparmor/profiles/extras/usr.bin.mlmmj-send +++ b/profiles/apparmor/profiles/extras/usr.bin.mlmmj-send @@ -31,4 +31,6 @@ include <tunables/global> /var/spool/mlmmj/*/moderation/* rwk, + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.bin.mlmmj-send> } diff --git a/profiles/apparmor/profiles/extras/usr.bin.mlmmj-sub b/profiles/apparmor/profiles/extras/usr.bin.mlmmj-sub index ed6a64f94..248a14a6f 100644 --- a/profiles/apparmor/profiles/extras/usr.bin.mlmmj-sub +++ b/profiles/apparmor/profiles/extras/usr.bin.mlmmj-sub @@ -41,4 +41,6 @@ include <tunables/global> /var/spool/mlmmj/*/digesters.d/ rw, /var/spool/mlmmj/*/digesters.d/* rwk, + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.bin.mlmmj-sub> } diff --git a/profiles/apparmor/profiles/extras/usr.bin.mlmmj-unsub b/profiles/apparmor/profiles/extras/usr.bin.mlmmj-unsub index 88fa6b152..f6a4d5e70 100644 --- a/profiles/apparmor/profiles/extras/usr.bin.mlmmj-unsub +++ b/profiles/apparmor/profiles/extras/usr.bin.mlmmj-unsub @@ -40,4 +40,6 @@ include <tunables/global> /usr/share/mlmmj/text.skel/*/* r, /etc/mlmmj/text/*/finish r, + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.bin.mlmmj-unsub> } diff --git a/profiles/apparmor/profiles/extras/usr.bin.opera b/profiles/apparmor/profiles/extras/usr.bin.opera index 324bc8d30..1bda4ba3d 100644 --- a/profiles/apparmor/profiles/extras/usr.bin.opera +++ b/profiles/apparmor/profiles/extras/usr.bin.opera @@ -74,4 +74,7 @@ include <tunables/global> /usr/lib/jvm/java-1.5.0-sun-1.5.0_update12/jre/lib/i386/client/*.so mr, /usr/lib/opera/*/opera ix, /usr/lib/opera/*/works ixr, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.bin.opera> } diff --git a/profiles/apparmor/profiles/extras/usr.bin.passwd b/profiles/apparmor/profiles/extras/usr.bin.passwd index d28d8be0e..80ca9c180 100644 --- a/profiles/apparmor/profiles/extras/usr.bin.passwd +++ b/profiles/apparmor/profiles/extras/usr.bin.passwd @@ -38,4 +38,7 @@ include <tunables/global> /usr/share/cracklib/pw_dict.hwm r, /usr/share/cracklib/pw_dict.pwd r, /usr/share/cracklib/pw_dict.pwi r, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.bin.passwd> } diff --git a/profiles/apparmor/profiles/extras/usr.bin.procmail b/profiles/apparmor/profiles/extras/usr.bin.procmail index a9219682c..c3a758a55 100644 --- a/profiles/apparmor/profiles/extras/usr.bin.procmail +++ b/profiles/apparmor/profiles/extras/usr.bin.procmail @@ -36,4 +36,7 @@ include <tunables/global> /usr/bin/procmail rmix, /usr/bin/spamc Px, /usr/sbin/sendmail rPx, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.bin.procmail> } diff --git a/profiles/apparmor/profiles/extras/usr.bin.pyzorsocket b/profiles/apparmor/profiles/extras/usr.bin.pyzorsocket new file mode 100644 index 000000000..6ec9ede5b --- /dev/null +++ b/profiles/apparmor/profiles/extras/usr.bin.pyzorsocket @@ -0,0 +1,23 @@ +# ------------------------------------------------------------------ +# +# Copyright (C) 2020 SUSE LLC +# +# 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. +# +# ------------------------------------------------------------------ + +include <tunables/global> + +profile pyzorsocket /usr/bin/pyzorsocket { + include <abstractions/base> + include <abstractions/python> + + /usr/bin/ r, + /usr/bin/python[2-9]* ix, + /usr/bin/pyzorsocket r, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.bin.pyzorsocket> +} diff --git a/profiles/apparmor/profiles/extras/usr.bin.razorsocket b/profiles/apparmor/profiles/extras/usr.bin.razorsocket new file mode 100644 index 000000000..9748ebce4 --- /dev/null +++ b/profiles/apparmor/profiles/extras/usr.bin.razorsocket @@ -0,0 +1,21 @@ +# ------------------------------------------------------------------ +# +# Copyright (C) 2020 SUSE LLC +# +# 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. +# +# ------------------------------------------------------------------ + +include <tunables/global> + +profile razorsocket /usr/bin/razorsocket { + include <abstractions/base> + include <abstractions/perl> + + /usr/bin/razorsocket r, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.bin.razorsocket> +} diff --git a/profiles/apparmor/profiles/extras/usr.bin.skype b/profiles/apparmor/profiles/extras/usr.bin.skype index dce23e344..776f6c59c 100644 --- a/profiles/apparmor/profiles/extras/usr.bin.skype +++ b/profiles/apparmor/profiles/extras/usr.bin.skype @@ -81,5 +81,8 @@ include <tunables/global> deny /var/cache/fontconfig/ w, deny owner @{HOME}/.fontconfig/ w, deny owner @{HOME}/.fontconfig/*.cache-*.TMP* w, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.bin.skype> } diff --git a/profiles/apparmor/profiles/extras/usr.bin.spamc b/profiles/apparmor/profiles/extras/usr.bin.spamc index e51ba8e2a..00189384e 100644 --- a/profiles/apparmor/profiles/extras/usr.bin.spamc +++ b/profiles/apparmor/profiles/extras/usr.bin.spamc @@ -19,4 +19,7 @@ include <tunables/global> include <abstractions/nameservice> /usr/bin/spamc r, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.bin.spamc> } diff --git a/profiles/apparmor/profiles/extras/usr.bin.svnserve b/profiles/apparmor/profiles/extras/usr.bin.svnserve index 9aa7868d3..e5d96861a 100644 --- a/profiles/apparmor/profiles/extras/usr.bin.svnserve +++ b/profiles/apparmor/profiles/extras/usr.bin.svnserve @@ -32,4 +32,7 @@ include <tunables/global> /tmp/apr* rwl, /var/tmp/apr* rwl, /tmp/report*.tmp rwl, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.bin.svnserve> } diff --git a/profiles/apparmor/profiles/extras/usr.bin.wireshark b/profiles/apparmor/profiles/extras/usr.bin.wireshark index a835afb34..261ca763c 100644 --- a/profiles/apparmor/profiles/extras/usr.bin.wireshark +++ b/profiles/apparmor/profiles/extras/usr.bin.wireshark @@ -99,4 +99,7 @@ include <tunables/global> # reading/writing pcaps /**pcap{,ng}{,.gz} r, owner /**pcap{,ng}{,.gz} rw, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.bin.wireshark> } diff --git a/profiles/apparmor/profiles/extras/usr.bin.xfs b/profiles/apparmor/profiles/extras/usr.bin.xfs index 17b9d06ba..4cebe7043 100644 --- a/profiles/apparmor/profiles/extras/usr.bin.xfs +++ b/profiles/apparmor/profiles/extras/usr.bin.xfs @@ -23,4 +23,7 @@ include <tunables/global> /tmp/.font-unix/fs710[0-9] wl, /usr/bin/xfs rmix, /{,var/}run/xfs.pid rw, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.bin.xfs> } diff --git a/profiles/apparmor/profiles/extras/usr.lib.GConf.2.gconfd-2 b/profiles/apparmor/profiles/extras/usr.lib.GConf.2.gconfd-2 index 02ffdb4be..d9bee8e35 100644 --- a/profiles/apparmor/profiles/extras/usr.lib.GConf.2.gconfd-2 +++ b/profiles/apparmor/profiles/extras/usr.lib.GConf.2.gconfd-2 @@ -33,4 +33,7 @@ include <tunables/global> /usr/lib/GConf/2/libgconfbackend-xml.so mr, /usr/lib64/GConf/2/libgconfbackend-xml.so mr, /usr/share/locale/** r, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.lib.GConf.2.gconfd-2> } diff --git a/profiles/apparmor/profiles/extras/usr.lib.RealPlayer10.realplay b/profiles/apparmor/profiles/extras/usr.lib.RealPlayer10.realplay index a2de723a4..b0b5b9024 100644 --- a/profiles/apparmor/profiles/extras/usr.lib.RealPlayer10.realplay +++ b/profiles/apparmor/profiles/extras/usr.lib.RealPlayer10.realplay @@ -49,4 +49,7 @@ include <tunables/global> /usr/share/icons/** r, /usr/share/pixmaps r, /usr/share/pixmaps/** r, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.lib.RealPlayer10.realplay> } diff --git a/profiles/apparmor/profiles/extras/usr.lib.bonobo.bonobo-activation-server b/profiles/apparmor/profiles/extras/usr.lib.bonobo.bonobo-activation-server index e09c0b945..156cb2fa4 100644 --- a/profiles/apparmor/profiles/extras/usr.lib.bonobo.bonobo-activation-server +++ b/profiles/apparmor/profiles/extras/usr.lib.bonobo.bonobo-activation-server @@ -24,4 +24,7 @@ include <tunables/global> /usr/lib/bonobo/servers r, /usr/lib/bonobo/servers/*.server r, /usr/lib/evolution-data-server-*/evolution-data-server-* Px, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.lib.bonobo.bonobo-activation-server> } diff --git a/profiles/apparmor/profiles/extras/usr.lib.evolution-data-server.evolution-data-server-1.10 b/profiles/apparmor/profiles/extras/usr.lib.evolution-data-server.evolution-data-server-1.10 index a649fe531..c0f80a076 100644 --- a/profiles/apparmor/profiles/extras/usr.lib.evolution-data-server.evolution-data-server-1.10 +++ b/profiles/apparmor/profiles/extras/usr.lib.evolution-data-server.evolution-data-server-1.10 @@ -39,4 +39,6 @@ include <tunables/global> /usr/lib/gnome-vfs** mr, /usr/share/evolution-data-server*/** mr, + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.lib.evolution-data-server.evolution-data-server-1.10> } diff --git a/profiles/apparmor/profiles/extras/usr.lib.firefox.mozilla-xremote-client b/profiles/apparmor/profiles/extras/usr.lib.firefox.mozilla-xremote-client index bb8ca311f..2d9a50d6c 100644 --- a/profiles/apparmor/profiles/extras/usr.lib.firefox.mozilla-xremote-client +++ b/profiles/apparmor/profiles/extras/usr.lib.firefox.mozilla-xremote-client @@ -20,4 +20,7 @@ include <tunables/global> /usr/lib/mozilla/lib*so* mr, /usr/lib/firefox/mozilla-xremote-client rmix, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.lib.firefox.mozilla-xremote-client> } diff --git a/profiles/apparmor/profiles/extras/usr.lib.man-db.man b/profiles/apparmor/profiles/extras/usr.lib.man-db.man index 1770359f7..8fcf46604 100644 --- a/profiles/apparmor/profiles/extras/usr.lib.man-db.man +++ b/profiles/apparmor/profiles/extras/usr.lib.man-db.man @@ -68,4 +68,7 @@ include <tunables/global> /var/cache/man/** rk, owner @{HOME}/.lesshst rw, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.lib.man-db.man> } diff --git a/profiles/apparmor/profiles/extras/usr.lib64.GConf.2.gconfd-2 b/profiles/apparmor/profiles/extras/usr.lib64.GConf.2.gconfd-2 index 89925b059..0c4b367de 100644 --- a/profiles/apparmor/profiles/extras/usr.lib64.GConf.2.gconfd-2 +++ b/profiles/apparmor/profiles/extras/usr.lib64.GConf.2.gconfd-2 @@ -33,4 +33,7 @@ include <tunables/global> /usr/lib/GConf/2/libgconfbackend-xml.so mr, /usr/lib64/GConf/2/libgconfbackend-xml.so mr, /usr/share/locale/** r, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.lib64.GConf.2.gconfd-2> } diff --git a/profiles/apparmor/profiles/extras/usr.sbin.clamd b/profiles/apparmor/profiles/extras/usr.sbin.clamd new file mode 100644 index 000000000..512a211b4 --- /dev/null +++ b/profiles/apparmor/profiles/extras/usr.sbin.clamd @@ -0,0 +1,30 @@ +# ------------------------------------------------------------------ +# +# Copyright (C) 2020 SUSE LLC +# +# 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. +# +# ------------------------------------------------------------------ + +include <tunables/global> + +profile clamd /usr/sbin/clamd { + include <abstractions/base> + include <abstractions/nameservice> + include <abstractions/openssl> + + capability setgid, + capability setuid, + + /etc/clamd.conf r, + /usr/sbin/clamd mr, + /var/lib/clamav/ r, + /var/lib/clamav/** r, + owner /run/clamav/clamd.pid w, + owner /run/clamav/clamd-socket rw, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.sbin.clamd> +} diff --git a/profiles/apparmor/profiles/extras/usr.sbin.cupsd b/profiles/apparmor/profiles/extras/usr.sbin.cupsd index 24f521e00..d059ec97a 100644 --- a/profiles/apparmor/profiles/extras/usr.sbin.cupsd +++ b/profiles/apparmor/profiles/extras/usr.sbin.cupsd @@ -64,4 +64,7 @@ include <tunables/global> /{,var/}run/cups/** rw, /var/cache/cups/ rw, /var/cache/cups/** rw, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.sbin.cupsd> } diff --git a/profiles/apparmor/profiles/extras/usr.sbin.dhcpd b/profiles/apparmor/profiles/extras/usr.sbin.dhcpd index 5d534dd73..19e511d14 100644 --- a/profiles/apparmor/profiles/extras/usr.sbin.dhcpd +++ b/profiles/apparmor/profiles/extras/usr.sbin.dhcpd @@ -36,4 +36,7 @@ include <tunables/global> /var/lib/dhcp/{db/,}dhcpd{6,}.leases* rwl, /var/lib/dhcp/etc/dhcpd.conf r, /{,var/}run/dhcpd.pid wl, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.sbin.dhcpd> } diff --git a/profiles/apparmor/profiles/extras/usr.sbin.haproxy b/profiles/apparmor/profiles/extras/usr.sbin.haproxy new file mode 100644 index 000000000..99a92696f --- /dev/null +++ b/profiles/apparmor/profiles/extras/usr.sbin.haproxy @@ -0,0 +1,45 @@ +# ------------------------------------------------------------------ +# +# Copyright (C) 2020 SUSE LLC +# +# 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. +# +# ------------------------------------------------------------------ + +include <tunables/global> + +profile haproxy /usr/sbin/haproxy { + include <abstractions/base> + include <abstractions/nameservice> + include <abstractions/openssl> + capability net_admin, + capability net_bind_service, + capability setgid, + capability setuid, + capability kill, + capability sys_resource, + capability sys_chroot, + + # those are needed for the stats socket creation + capability chown, + capability fowner, + capability fsetid, + + network inet tcp, + network inet6 tcp, + + /etc/haproxy/* r, + + /usr/sbin/haproxy rmix, + + /var/lib/haproxy/stats rwl, + /var/lib/haproxy/stats.*.bak rwl, + /var/lib/haproxy/stats.*.tmp rwl, + /{,var/}run/haproxy.pid rw, + /{,var/}run/haproxy-master.sock* rwlk, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.sbin.haproxy> +} diff --git a/profiles/apparmor/profiles/extras/usr.sbin.httpd2-prefork b/profiles/apparmor/profiles/extras/usr.sbin.httpd2-prefork index ada4f5634..b9ff89a35 100644 --- a/profiles/apparmor/profiles/extras/usr.sbin.httpd2-prefork +++ b/profiles/apparmor/profiles/extras/usr.sbin.httpd2-prefork @@ -106,7 +106,7 @@ include <tunables/global> # profile: # /home/*/public_html/** mixr, # (note that if you are using mod_change_hat, you have a choice of - # providing neccesary access in this file OR in URI-specific hats, or + # providing necessary access in this file OR in URI-specific hats, or # hats in the <VHost>, <Location>, or <Directory> directives. Please # see the user's guide or mod_apparmor(5) for more information. @@ -154,7 +154,7 @@ include <tunables/global> # profile: # /home/*/public_html/** mixr, # (note that if you are using mod_change_hat, you have a choice of - # providing neccesary access in this file OR in URI-specific hats, or + # providing necessary access in this file OR in URI-specific hats, or # hats in the <VHost>, <Location>, or <Directory> directives. Please # see the user's guide or mod_apparmor(5) for more information. @@ -178,4 +178,7 @@ include <tunables/global> # php session state /var/lib/php/sess_* rwl, } + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.sbin.httpd2-prefork> } diff --git a/profiles/apparmor/profiles/extras/usr.sbin.imapd b/profiles/apparmor/profiles/extras/usr.sbin.imapd index 0d21823d2..c1277bbb7 100644 --- a/profiles/apparmor/profiles/extras/usr.sbin.imapd +++ b/profiles/apparmor/profiles/extras/usr.sbin.imapd @@ -23,4 +23,7 @@ include <tunables/global> /tmp/* rwl, /usr/sbin/imapd r, /usr/share/ssl/certs/imapd.pem r, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.sbin.imapd> } diff --git a/profiles/apparmor/profiles/extras/usr.sbin.in.fingerd b/profiles/apparmor/profiles/extras/usr.sbin.in.fingerd index 1bcb43b4b..685a123c0 100644 --- a/profiles/apparmor/profiles/extras/usr.sbin.in.fingerd +++ b/profiles/apparmor/profiles/extras/usr.sbin.in.fingerd @@ -22,4 +22,7 @@ include <tunables/global> /usr/bin/finger mix, /var/log/lastlog r, /{,var/}run/utmp rk, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.sbin.in.fingerd> } diff --git a/profiles/apparmor/profiles/extras/usr.sbin.in.ftpd b/profiles/apparmor/profiles/extras/usr.sbin.in.ftpd index e39356dd2..d28986cca 100644 --- a/profiles/apparmor/profiles/extras/usr.sbin.in.ftpd +++ b/profiles/apparmor/profiles/extras/usr.sbin.in.ftpd @@ -37,4 +37,7 @@ include <tunables/global> /var/log/xferlog w, /{,var/}run wr, /{,var/}run/ftp.{pids,rips}-all wr, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.sbin.in.ftpd> } diff --git a/profiles/apparmor/profiles/extras/usr.sbin.in.ntalkd b/profiles/apparmor/profiles/extras/usr.sbin.in.ntalkd index 7b454a76a..f33033df7 100644 --- a/profiles/apparmor/profiles/extras/usr.sbin.in.ntalkd +++ b/profiles/apparmor/profiles/extras/usr.sbin.in.ntalkd @@ -19,4 +19,7 @@ include <tunables/global> /usr/sbin/in.ntalkd r, /{,var/}run/utmp r, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.sbin.in.ntalkd> } diff --git a/profiles/apparmor/profiles/extras/usr.sbin.ipop2d b/profiles/apparmor/profiles/extras/usr.sbin.ipop2d index c65c9c958..ca0c4c770 100644 --- a/profiles/apparmor/profiles/extras/usr.sbin.ipop2d +++ b/profiles/apparmor/profiles/extras/usr.sbin.ipop2d @@ -23,4 +23,7 @@ include <tunables/global> /tmp/.* rwl , /usr/sbin/ipop2d rmix, /usr/share/ssl/certs/ipop2d.pem r , + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.sbin.ipop2d> } diff --git a/profiles/apparmor/profiles/extras/usr.sbin.ipop3d b/profiles/apparmor/profiles/extras/usr.sbin.ipop3d index ca6348f5c..e94ffc5cc 100644 --- a/profiles/apparmor/profiles/extras/usr.sbin.ipop3d +++ b/profiles/apparmor/profiles/extras/usr.sbin.ipop3d @@ -23,4 +23,7 @@ include <tunables/global> /tmp/.* rwl , /usr/sbin/ipop3d rmix, /usr/share/ssl/certs/ipop3d.pem r , + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.sbin.ipop3d> } diff --git a/profiles/apparmor/profiles/extras/usr.sbin.lighttpd b/profiles/apparmor/profiles/extras/usr.sbin.lighttpd index af11fb5a9..b331509c6 100644 --- a/profiles/apparmor/profiles/extras/usr.sbin.lighttpd +++ b/profiles/apparmor/profiles/extras/usr.sbin.lighttpd @@ -67,5 +67,7 @@ include <tunables/global> /etc/lighttpd/conf-available/*.conf r, /etc/lighttpd/conf-enabled/ r, /etc/lighttpd/conf-enabled/*.conf r, -} + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.sbin.lighttpd> +} diff --git a/profiles/apparmor/profiles/extras/usr.sbin.mysqld b/profiles/apparmor/profiles/extras/usr.sbin.mysqld index 8410467b1..6c93b1582 100644 --- a/profiles/apparmor/profiles/extras/usr.sbin.mysqld +++ b/profiles/apparmor/profiles/extras/usr.sbin.mysqld @@ -44,4 +44,6 @@ include <tunables/global> /var/log/mysql/mysqld.log-20* w, /{,var/}run/mysql{,d}/mysqld.pid w, + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.sbin.mysqld> } diff --git a/profiles/apparmor/profiles/extras/usr.sbin.oidentd b/profiles/apparmor/profiles/extras/usr.sbin.oidentd index 999cefefa..6353b6b5e 100644 --- a/profiles/apparmor/profiles/extras/usr.sbin.oidentd +++ b/profiles/apparmor/profiles/extras/usr.sbin.oidentd @@ -29,4 +29,7 @@ include <tunables/global> # spoofing feature of oidentd @{HOME}/.ispoof r, @{HOME}/.oidentd.conf r, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.sbin.oidentd> } diff --git a/profiles/apparmor/profiles/extras/usr.sbin.popper b/profiles/apparmor/profiles/extras/usr.sbin.popper index 3b2ef4c6e..0c6eb5d5f 100644 --- a/profiles/apparmor/profiles/extras/usr.sbin.popper +++ b/profiles/apparmor/profiles/extras/usr.sbin.popper @@ -25,4 +25,7 @@ include <tunables/global> /usr/sbin/popper mr, /var/spool/mail/* rw, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.sbin.popper> } diff --git a/profiles/apparmor/profiles/extras/usr.sbin.postalias b/profiles/apparmor/profiles/extras/usr.sbin.postalias index 832a7a5fe..c78c35f7f 100644 --- a/profiles/apparmor/profiles/extras/usr.sbin.postalias +++ b/profiles/apparmor/profiles/extras/usr.sbin.postalias @@ -35,4 +35,7 @@ include <tunables/global> /var/lib/mailman/data/aliases.{lm,}db rwl, /var/spool/postfix r, /var/spool/postfix/pid r, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.sbin.postalias> } diff --git a/profiles/apparmor/profiles/extras/usr.sbin.postdrop b/profiles/apparmor/profiles/extras/usr.sbin.postdrop index 3b1706799..8ec184399 100644 --- a/profiles/apparmor/profiles/extras/usr.sbin.postdrop +++ b/profiles/apparmor/profiles/extras/usr.sbin.postdrop @@ -34,4 +34,7 @@ include <tunables/global> /var/spool/postfix/maildrop/* rwl, /var/spool/postfix/pid r, /var/spool/postfix/public/pickup rw, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.sbin.postdrop> } diff --git a/profiles/apparmor/profiles/extras/usr.sbin.postmap b/profiles/apparmor/profiles/extras/usr.sbin.postmap index 11bc606e0..656f10233 100644 --- a/profiles/apparmor/profiles/extras/usr.sbin.postmap +++ b/profiles/apparmor/profiles/extras/usr.sbin.postmap @@ -27,4 +27,7 @@ include <tunables/global> @{PROC}/net/if_inet6 r, /usr/share/icu/[0-9]*.[0-9]*/*.dat r, /usr/sbin/postmap rmix, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.sbin.postmap> } diff --git a/profiles/apparmor/profiles/extras/usr.sbin.postqueue b/profiles/apparmor/profiles/extras/usr.sbin.postqueue index 4ca429c38..9e1355f31 100644 --- a/profiles/apparmor/profiles/extras/usr.sbin.postqueue +++ b/profiles/apparmor/profiles/extras/usr.sbin.postqueue @@ -33,4 +33,7 @@ include <tunables/global> /var/spool/postfix/public/showq w, /var/spool/postfix/public/qmgr w, /var/spool/postfix/public/pickup w, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.sbin.postqueue> } diff --git a/profiles/apparmor/profiles/extras/usr.sbin.sendmail b/profiles/apparmor/profiles/extras/usr.sbin.sendmail index f1326d8de..75e903018 100644 --- a/profiles/apparmor/profiles/extras/usr.sbin.sendmail +++ b/profiles/apparmor/profiles/extras/usr.sbin.sendmail @@ -89,4 +89,7 @@ include <tunables/global> /var/spool/postfix/public/showq w, /var/spool/postfix r, /var/spool/postfix/saved r, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.sbin.sendmail> } diff --git a/profiles/apparmor/profiles/extras/usr.sbin.sendmail.postfix b/profiles/apparmor/profiles/extras/usr.sbin.sendmail.postfix index ed7fa7e4d..413e1bea6 100644 --- a/profiles/apparmor/profiles/extras/usr.sbin.sendmail.postfix +++ b/profiles/apparmor/profiles/extras/usr.sbin.sendmail.postfix @@ -50,4 +50,7 @@ include <tunables/global> /var/spool/postfix/public/showq w, /var/spool/postfix/public/qmgr w, /var/spool/postfix/saved r, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.sbin.sendmail.postfix> } diff --git a/profiles/apparmor/profiles/extras/usr.sbin.sendmail.sendmail b/profiles/apparmor/profiles/extras/usr.sbin.sendmail.sendmail index 4bce297d8..bf923f83e 100644 --- a/profiles/apparmor/profiles/extras/usr.sbin.sendmail.sendmail +++ b/profiles/apparmor/profiles/extras/usr.sbin.sendmail.sendmail @@ -46,4 +46,7 @@ include <tunables/global> /var/spool/mail/* rwl, /var/spool/mqueue rwl, /var/spool/mqueue/* rwl, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.sbin.sendmail.sendmail> } diff --git a/profiles/apparmor/profiles/extras/usr.sbin.spamd b/profiles/apparmor/profiles/extras/usr.sbin.spamd index 6ee9f97a2..84b485eb5 100644 --- a/profiles/apparmor/profiles/extras/usr.sbin.spamd +++ b/profiles/apparmor/profiles/extras/usr.sbin.spamd @@ -39,4 +39,7 @@ include <tunables/global> /usr/share/spamassassin/*.cf r, /usr/share/spamassassin/*.template r, /usr/share/spamassassin/*.txt r, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.sbin.spamd> } diff --git a/profiles/apparmor/profiles/extras/usr.sbin.squid b/profiles/apparmor/profiles/extras/usr.sbin.squid index a94eb3e71..15f8252d8 100644 --- a/profiles/apparmor/profiles/extras/usr.sbin.squid +++ b/profiles/apparmor/profiles/extras/usr.sbin.squid @@ -62,4 +62,6 @@ include <tunables/global> /usr/sbin/wbinfo_group.pl rmix, /usr/sbin/yp_auth rmix, + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.sbin.squid> } diff --git a/profiles/apparmor/profiles/extras/usr.sbin.useradd b/profiles/apparmor/profiles/extras/usr.sbin.useradd index 1b38a0e42..2f59f6d6f 100644 --- a/profiles/apparmor/profiles/extras/usr.sbin.useradd +++ b/profiles/apparmor/profiles/extras/usr.sbin.useradd @@ -72,4 +72,7 @@ include <tunables/global> /var/log/tallylog rw, } + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.sbin.useradd> } diff --git a/profiles/apparmor/profiles/extras/usr.sbin.userdel b/profiles/apparmor/profiles/extras/usr.sbin.userdel index 138a5b1eb..5014c9c7f 100644 --- a/profiles/apparmor/profiles/extras/usr.sbin.userdel +++ b/profiles/apparmor/profiles/extras/usr.sbin.userdel @@ -49,4 +49,7 @@ include <tunables/global> # XXX /{,var/}run/nscd.pid r, /var/spool/mail/* wl, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.sbin.userdel> } diff --git a/profiles/apparmor/profiles/extras/usr.sbin.vsftpd b/profiles/apparmor/profiles/extras/usr.sbin.vsftpd index 7d4862dfb..994fad61c 100644 --- a/profiles/apparmor/profiles/extras/usr.sbin.vsftpd +++ b/profiles/apparmor/profiles/extras/usr.sbin.vsftpd @@ -38,4 +38,7 @@ include <tunables/global> /pub/** r, @{HOMEDIRS} r, @{HOME}/** rwl, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.sbin.vsftpd> } diff --git a/profiles/apparmor/profiles/extras/usr.sbin.xinetd b/profiles/apparmor/profiles/extras/usr.sbin.xinetd index d5fb26a37..844f156cc 100644 --- a/profiles/apparmor/profiles/extras/usr.sbin.xinetd +++ b/profiles/apparmor/profiles/extras/usr.sbin.xinetd @@ -69,4 +69,7 @@ include <tunables/global> /usr/sbin/vsftpd Px, /usr/X11R6/bin/vnc_inetd_httpd Px, /usr/X11R6/bin/Xvnc Px, + + # Site-specific additions and overrides. See local/README for details. + include if exists <local/usr.sbin.xinetd> } diff --git a/tests/bin/shellcheck-tree b/tests/bin/shellcheck-tree new file mode 100755 index 000000000..46828cdce --- /dev/null +++ b/tests/bin/shellcheck-tree @@ -0,0 +1,38 @@ +#!/usr/bin/python3 + +import glob +import re +import subprocess +import sys +from pathlib import Path + + +def is_excluded(f): + return (re.match( + # skip test scripts and the rc.apparmor.slackware initscript + r"^([.]git/|parser/tst/|tests/|utils/test/|parser/rc[.]apparmor[.]slackware)" + + "|" + # skip several files generated during libapparmor build + + r"^libraries/libapparmor/(compile|config[.]guess|config[.]status|config[.]sub|configure|depcomp|install-sh|libtool|ltmain[.]sh|missing|test-driver|ylwrap|testsuite/test_multi[.]multi)", + f, + ) or Path(f).is_dir()) + + +def mimetype(f): + return subprocess.run(['file', '--brief', '--mime-type', f], + stdout=subprocess.PIPE, + universal_newlines=True, + check=True).stdout.rstrip() + + +def is_shell_script(f): + return mimetype(f) == "text/x-shellscript" + + +shell_scripts = [ + f for f in glob.glob("**/*", recursive=True) + if not is_excluded(f) and is_shell_script(f) +] + +sys.exit( + subprocess.run(['shellcheck'] + sys.argv[1:] + shell_scripts).returncode) diff --git a/tests/checkstyle2junit.xslt b/tests/checkstyle2junit.xslt new file mode 100644 index 000000000..0fea4528c --- /dev/null +++ b/tests/checkstyle2junit.xslt @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> + <xsl:output encoding="UTF-8" method="xml"></xsl:output> + + <xsl:template match="/"> + <testsuite> + <xsl:attribute name="tests"> + <xsl:value-of select="count(.//file)" /> + </xsl:attribute> + <xsl:attribute name="failures"> + <xsl:value-of select="count(.//error)" /> + </xsl:attribute> + <xsl:for-each select="//checkstyle"> + <xsl:apply-templates /> + </xsl:for-each> + </testsuite> + </xsl:template> + + <xsl:template match="file"> + <testcase> + <xsl:attribute name="classname"> + <xsl:value-of select="@name" /> + </xsl:attribute> + <xsl:attribute name="name"> + <xsl:value-of select="@name" /> + </xsl:attribute> + <xsl:apply-templates select="node()" /> + </testcase> + </xsl:template> + + <xsl:template match="error"> + <failure> + <xsl:attribute name="type"> + <xsl:value-of select="@source" /> + </xsl:attribute> + <xsl:text>Line </xsl:text> + <xsl:value-of select="@line" /> + <xsl:text>: </xsl:text> + <xsl:value-of select="@message" /> + <xsl:text> See https://www.shellcheck.net/wiki/</xsl:text> + <xsl:value-of select="substring(@source, '12')" /> + </failure> + </xsl:template> +</xsl:stylesheet> diff --git a/tests/regression/apparmor/Makefile b/tests/regression/apparmor/Makefile index 5c275a3cd..871afd576 100644 --- a/tests/regression/apparmor/Makefile +++ b/tests/regression/apparmor/Makefile @@ -67,13 +67,14 @@ system aa-exec by adding USE_SYSTEM=1 to your make command.${nl}\ LDLIBS += -Wl,-Bstatic -lapparmor -Wl,-Bdynamic -lpthread endif # USE_SYSTEM -+SYSCTL_INCLUDE="\#include <sys/sysctl.h>" -+USE_SYSCTL:=$(shell echo $(SYSCTL_INCLUDE) | cpp -dM >/dev/null 2>/dev/null && echo true) +SYSCTL_INCLUDE="\#include <sys/sysctl.h>" +USE_SYSCTL:=$(shell echo $(SYSCTL_INCLUDE) | cpp -dM >/dev/null 2>/dev/null && echo true) CFLAGS += -g -O0 $(EXTRA_WARNINGS) SRC=access.c \ at_secure.c \ + attach_disconnected.c \ introspect.c \ changeprofile.c \ changehat.c \ @@ -200,6 +201,7 @@ EXEC=$(SRC:%.c=%) TESTS=aa_exec \ access \ + attach_disconnected \ at_secure \ introspect \ capabilities \ @@ -317,6 +319,18 @@ unix_socket_client: unix_socket_client.c unix_socket_common.o unix_socket: unix_socket.c unix_socket_common.o unix_socket_client ${CC} ${CFLAGS} ${LDFLAGS} $(filter-out unix_socket_client, $^) -o $@ ${LDLIBS} +unix_fd_common.o: unix_fd_common.c unix_fd_common.h + ${CC} ${CFLAGS} ${LDFLAGS} $< -c ${LDLIBS} + +unix_fd_client: unix_fd_client.c unix_fd_common.o + ${CC} ${CFLAGS} ${LDFLAGS} $^ -o $@ ${LDLIBS} + +attach_disconnected: attach_disconnected.c unix_fd_common.o + ${CC} ${CFLAGS} ${LDFLAGS} $^ -o $@ ${LDLIBS} + +mount: mount.c + ${CC} ${CFLAGS} -std=gnu99 ${LDFLAGS} $^ -o $@ ${LDLIBS} + build-dep: @if [ `whoami` = "root" ] ;\ then \ @@ -377,6 +391,6 @@ alltests: all fi clean: - rm -f $(EXEC) dbus_common.o unix_socket_common.o uservars.inc + rm -f $(EXEC) dbus_common.o unix_socket_common.o uservars.inc unix_fd_common.o regex.sh: open exec diff --git a/tests/regression/apparmor/README b/tests/regression/apparmor/README index d6f285ad3..5d2670251 100644 --- a/tests/regression/apparmor/README +++ b/tests/regression/apparmor/README @@ -112,8 +112,8 @@ argument or the end of the argument list will be included within this hat. Support for multiple profiles within a single load (for example for test that want to domain tansition to another profile) is supported by -the "image' argument to genprofile. This keyword preceeded by a '--' -seperator terminates the previous profile and creates a new profile for +the "image' argument to genprofile. This keyword preceded by a '--' +separator terminates the previous profile and creates a new profile for the specified executable image. Together, 'image' and 'hat:' allow complex profiles including subhats and @@ -184,7 +184,7 @@ requiring signal passing) <check it's output, it is expected to FAIL> runchecktest "EXEC no x" fail $file - <Thats it. Exit status $rc is automatically returned by epilogue.inc> + <That's it. Exit status $rc is automatically returned by epilogue.inc> Supporting files ================ diff --git a/tests/regression/apparmor/aa_exec_wrapper.sh b/tests/regression/apparmor/aa_exec_wrapper.sh index a27c5666e..3169b7fb7 100755 --- a/tests/regression/apparmor/aa_exec_wrapper.sh +++ b/tests/regression/apparmor/aa_exec_wrapper.sh @@ -16,7 +16,7 @@ fi out=$($1 -- cat /proc/self/attr/current 2>&1) rc=$? -if [ $rc -eq 0 ] && [ "$out" == "$2" ]; then +if [ $rc -eq 0 ] && [ "$out" = "$2" ]; then echo PASS exit 0 elif [ $rc -ne 0 ]; then diff --git a/tests/regression/apparmor/aa_policy_cache.sh b/tests/regression/apparmor/aa_policy_cache.sh index 6fe97e470..1bac452e6 100755 --- a/tests/regression/apparmor/aa_policy_cache.sh +++ b/tests/regression/apparmor/aa_policy_cache.sh @@ -56,7 +56,7 @@ create_cache_files() do cachefile="${cachedir}/${policy}" - echo "profile $policy { /f r, }" | ${subdomain} "${parser_config}" -qS > "$cachefile" + echo "profile $policy { /f r, }" | ${subdomain} ${parser_config} -qS > "$cachefile" done } diff --git a/tests/regression/apparmor/at_secure.sh b/tests/regression/apparmor/at_secure.sh index 77fe0a701..452114e8c 100755 --- a/tests/regression/apparmor/at_secure.sh +++ b/tests/regression/apparmor/at_secure.sh @@ -8,7 +8,7 @@ #=NAME at_secure #=DESCRIPTION -# Verifies the AT_SECURE flag in the auxillary vector after an exec transition +# Verifies the AT_SECURE flag in the auxiliary vector after an exec transition #=END pwd=`dirname $0` diff --git a/tests/regression/apparmor/attach_disconnected.c b/tests/regression/apparmor/attach_disconnected.c new file mode 100644 index 000000000..afefa547b --- /dev/null +++ b/tests/regression/apparmor/attach_disconnected.c @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2021 Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, contact Canonical Ltd. + */ + +#define _GNU_SOURCE + +#include <alloca.h> +#include <errno.h> +#include <sched.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/apparmor.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/mount.h> +#include <limits.h> +#include <libgen.h> +#include "unix_fd_common.h" + +struct clone_arg { + const char *socket; + const char *disk_img; + const char *new_root; + const char *put_old; +}; + +static int _pivot_root(const char *new_root, const char *put_old) +{ +#ifdef __NR_pivot_root + return syscall(__NR_pivot_root, new_root, put_old); +#else + errno = ENOSYS; + return -1; +#endif +} + +static int pivot_and_get_unix_clientfd(void *arg) +{ + int rc; + const char *socket = ((struct clone_arg *)arg)->socket; + const char *disk_img = ((struct clone_arg *)arg)->disk_img; + const char *new_root = ((struct clone_arg *)arg)->new_root; + const char *put_old = ((struct clone_arg *)arg)->put_old; + + char *tmp = strdup(put_old); + char *put_old_bname = basename(tmp); // don't free + + char *socket_put_old; + rc = asprintf(&socket_put_old, "/%s/%s", put_old_bname, socket); + if (rc < 0) { + perror("FAIL - asprintf socket_put_old"); + rc = errno; + socket_put_old = NULL; + goto out; + } + + rc = mkdir(new_root, 0777); + if (rc < 0 && errno != EEXIST) { + perror("FAIL - mkdir new_root"); + rc = 100; + goto out; + } + + rc = mount(disk_img, new_root, "ext2", 0, NULL); + if (rc < 0) { + perror("FAIL - mount disk_img"); + rc = 101; + goto out; + } + + rc = chdir(new_root); + if (rc < 0) { + perror("FAIL - chdir"); + rc = 102; + goto out; + } + + rc = mkdir(put_old, 0777); + if (rc < 0 && errno != EEXIST) { + perror("FAIL - mkdir put_old"); + rc = 103; + goto out; + } + + rc = _pivot_root(new_root, put_old); + if (rc < 0) { + perror("FAIL - pivot_root"); + rc = 104; + goto out; + } + + /* Actual test - it tries to open the socket which is detached. + * Only allowed when there's the flag attach_disconnected and/or + * attach_disconnected.path is defined. + */ + rc = get_unix_clientfd(socket_put_old); + +out: + free(tmp); + free(socket_put_old); + + exit(rc); +} + +static pid_t _clone(int (*fn)(void *), void *arg) +{ + size_t stack_size = sysconf(_SC_PAGESIZE); + void *stack = alloca(stack_size); + +#ifdef __ia64__ + return __clone2(fn, stack, stack_size, + CLONE_NEWNS | SIGCHLD, arg); +#else + return clone(fn, stack + stack_size, + CLONE_NEWNS | SIGCHLD, arg); +#endif +} + +int main(int argc, char **argv) +{ + struct clone_arg arg; + pid_t child; + int child_status, rc; + + if (argc != 5) { + fprintf(stderr, + "FAIL - usage: %s <UNIX_SOCKET_PATH> <DISK_IMG> <NEW_ROOT> <PUT_OLD>\n\n" + " <UNIX_SOCKET_PATH>\tThe path of the unix socket the server will connect to\n" + " <DISK_IMG>\t\tThe loop device pointing to the disk image\n" + " <NEW_ROOT>\t\tThe new_root param of pivot_root()\n" + " <PUT_OLD>\t\tThe put_old param of pivot_root()\n\n" + "This program clones itself in a new mount namespace, \n" + "does a pivot and then connects to the <UNIX_SOCKET_PATH>.\n" + "The test fails if the program does not have attach_disconnected\n" + "permission to access the unix_socket which is disconnected.\n", argv[0]); + exit(1); + } + + arg.socket = argv[1]; + arg.disk_img = argv[2]; + arg.new_root = argv[3]; + arg.put_old = argv[4]; + + child = _clone(pivot_and_get_unix_clientfd, &arg); + if (child < 0) { + perror("FAIL - clone"); + exit(2); + } + + rc = waitpid(child, &child_status, 0); + if (rc < 0) { + perror("FAIL - waitpid"); + exit(3); + } else if (!WIFEXITED(child_status)) { + fprintf(stderr, "FAIL - child didn't exit\n"); + exit(4); + } else if (WEXITSTATUS(child_status)) { + /* The child has already printed a FAIL message */ + exit(WEXITSTATUS(child_status)); + } + + printf("PASS\n"); + exit(0); +} diff --git a/tests/regression/apparmor/attach_disconnected.sh b/tests/regression/apparmor/attach_disconnected.sh new file mode 100644 index 000000000..be0977c0a --- /dev/null +++ b/tests/regression/apparmor/attach_disconnected.sh @@ -0,0 +1,118 @@ +#! /bin/bash +# Copyright (C) 2021 Canonical, Ltd. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation, version 2 of the +# License. + +#=NAME attach_disconnected +#=DESCRIPTION +# This test verifies that the attached_disconnected flag is indeed restricting +# access to disconnected paths. +#=END + +pwd=`dirname $0` +pwd=`cd $pwd ; /bin/pwd` + +bin=$pwd + +. $bin/prologue.inc + +settest unix_fd_server +disk_img=$tmpdir/disk_img +new_root=$tmpdir/new_root/ +put_old=${new_root}put_old/ +root_was_shared="no" +fstype="ext2" +file=$tmpdir/file +socket=$tmpdir/unix_fd_test +att_dis_client=$pwd/attach_disconnected + +attach_disconnected_cleanup() { + if [ ! -z "$loop_device" ]; then + losetup -d $loop_device + fi + + mountpoint -q "$new_root" + if [ $? -eq 0 ] ; then + umount "$new_root" + fi + + if [ "$root_was_shared" = "yes" ] ; then + [ -n "$VERBOSE" ] && echo 'notice: re-mounting / as shared' + mount --make-shared / + fi +} +do_onexit="attach_disconnected_cleanup" + +if [ ! -b /dev/loop0 ] ; then + modprobe loop +fi + +# systemd mounts / and everything under it MS_SHARED. This breaks +# pivot_root entirely, so attempt to detect it, and remount / +# MS_PRIVATE temporarily. +FINDMNT=/bin/findmnt +if [ -x "${FINDMNT}" ] && ${FINDMNT} -no PROPAGATION / > /dev/null 2>&1 ; then + if [ "$(${FINDMNT} -no PROPAGATION /)" = "shared" ] ; then + root_was_shared="yes" + fi +elif [ "$(ps hp1 -ocomm)" = "systemd" ] ; then + # no findmnt or findmnt doesn't know the PROPAGATION column, + # but init is systemd so assume rootfs is shared + root_was_shared="yes" +fi +if [ "${root_was_shared}" = "yes" ] ; then + [ -n "$VERBOSE" ] && echo 'notice: re-mounting / as private' + mount --make-private / +fi + +dd if=/dev/zero of="$disk_img" bs=1024 count=512 2> /dev/null +/sbin/mkfs -t "$fstype" -F "$disk_img" > /dev/null 2> /dev/null +# mounting will be done by the test binary +loop_device=$(losetup -fP --show "${disk_img}") + +# content generated with: +# dd if=/dev/urandom bs=32 count=4 2> /dev/null | od -x | head -8 | sed -e 's/^[[:xdigit:]]\{7\}//g' -e 's/ //g' +# required by unix_fd_server which this test is based on +cat > ${file} << EOM +4bcd0f741e97195c57f1ff72dbdf2dd9 +8284a4cd56699628c185f6f647805a1c +8bdee094b7e73f9834ada004c570ad49 +a9a92856edb4a206f271b537fe73081f +ac62547499fffd79021898cc8653e36b +c943fd5f8f4cfa4690a08505e44b0906 +7532527375fb6dc0ddadfcb2f1bcdd82 +150223d965fefe996f8a6c602cc1b514 +EOM + +do_test() +{ + local desc="ATTACH_DISCONNECTED ($1)" + shift + runchecktest "$desc" "$@" +} + +# Needed for clone(CLONE_NEWNS) and pivot_root() +cap=capability:sys_admin +file_perm="$file:rw /put_old/$file:rw" +create_dir="$new_root:w $put_old:w" + +# Ensure everything works as expected when unconfined +do_test "attach_disconnected" pass $file $att_dis_client $socket $loop_device $new_root $put_old + +genprofile $file_perm unix:create $socket:rw $att_dis_client:px -- image=$att_dis_client $file_perm unix:create $socket:rw $create_dir $cap "pivot_root:ALL" "mount:ALL" flag:attach_disconnected + +do_test "attach_disconnected" pass $file $att_dis_client $socket $loop_device $new_root $put_old + +genprofile $file_perm unix:create $socket:rw $att_dis_client:px -- image=$att_dis_client $file_perm unix:create $socket:rw $create_dir $cap "pivot_root:ALL" "mount:ALL" flag:no_attach_disconnected + +do_test "no_attach_disconnected" fail $file $att_dis_client $socket $loop_device $new_root $put_old + +# Ensure default is no_attach_disconnected - no flags set +genprofile $file_perm unix:create $socket:rw $att_dis_client:px -- image=$att_dis_client $file_perm unix:create $socket:rw $create_dir $cap "pivot_root:ALL" "mount:ALL" + +do_test "no_attach_disconnected" fail $file $att_dis_client $socket $loop_device $new_root $put_old + +# TODO: Add .path to the attach_disconnected flag diff --git a/tests/regression/apparmor/capabilities.sh b/tests/regression/apparmor/capabilities.sh old mode 100644 new mode 100755 index 74a3c9024..446a28372 --- a/tests/regression/apparmor/capabilities.sh +++ b/tests/regression/apparmor/capabilities.sh @@ -13,7 +13,7 @@ # capability processing for confined processes) and no others allows successful # access. For every syscall in the test, we iterate over each capability # individually (plus no capabilities) in order to verify that only the expected -# capability grants access to the priviledged operation. The same is repeated +# capability grants access to the privileged operation. The same is repeated # for capabilities within hats. #=END @@ -49,14 +49,20 @@ CAPABILITIES="chown dac_override dac_read_search fowner fsetid kill \ sys_admin sys_boot sys_nice sys_resource sys_time \ sys_tty_config mknod lease audit_write audit_control" +# lockdown thwarts both ioperm and iopl +notlockeddown=TRUE +if [ -f /sys/kernel/security/lockdown ] && ! grep -q "\[none\]" /sys/kernel/security/lockdown; then + notlockeddown=FALSE +fi + # defines which test+capability pairs should succeed. syscall_reboot_sys_boot=TRUE syscall_sethostname_sys_admin=TRUE syscall_setdomainname_sys_admin=TRUE syscall_setpriority_sys_nice=TRUE syscall_setscheduler_sys_nice=TRUE -syscall_ioperm_sys_rawio=TRUE -syscall_iopl_sys_rawio=TRUE +syscall_ioperm_sys_rawio=$notlockeddown +syscall_iopl_sys_rawio=$notlockeddown syscall_chroot_sys_chroot=TRUE syscall_mlockall_ipc_lock=TRUE syscall_sysctl_sys_admin=TRUE @@ -93,11 +99,17 @@ for TEST in ${TESTS} ; do settest ${TEST} # base case, unconfined - runchecktest "${TEST} -- unconfined" pass ${my_arg} + if [ "${TEST}" = "syscall_ioperm" -a "$notlockeddown" = "FALSE" ] || + [ "${TEST}" = "syscall_iopl" -a "$notlockeddown" = "FALSE" ]; then + expected=fail + else + expected=pass + fi + runchecktest "${TEST} -- unconfined" ${expected} ${my_arg} # no capabilities allowed genprofile ${my_entries} - if [ "${TEST}" == "syscall_ptrace" -a "$(kernel_features ptrace)" == "true" ] ; then + if [ "${TEST}" = "syscall_ptrace" -a "$(kernel_features ptrace)" = "true" ] ; then # ptrace between profiles confining tasks of same pid is controlled by the ptrace rule # capability + ptrace rule needed between pids runchecktest "${TEST} -- no caps" pass ${my_arg} @@ -107,13 +119,15 @@ for TEST in ${TESTS} ; do # all capabilities allowed genprofile cap:ALL ${my_entries} - runchecktest "${TEST} -- all caps" pass ${my_arg} + runchecktest "${TEST} -- all caps" ${expected} ${my_arg} # iterate through each of the capabilities for cap in ${CAPABILITIES} ; do - if [ "X$(eval echo \${${TEST}_${cap}})" == "XTRUE" ] ; then + if [ ${expected} = "fail" ]; then + expected_result=fail + elif [ "X$(eval echo \${${TEST}_${cap}})" = "XTRUE" ] ; then expected_result=pass - elif [ "${TEST}" == "syscall_ptrace" -a "$(kernel_features ptrace)" == "true" ]; then + elif [ "${TEST}" = "syscall_ptrace" -a "$(kernel_features ptrace)" = "true" ]; then expected_result=pass else expected_result=fail @@ -126,7 +140,7 @@ for TEST in ${TESTS} ; do # a subprofile. settest ${testwrapper} genprofile hat:$bin/${TEST} addimage:${bin}/${TEST} ${my_entries} - if [ "${TEST}" == "syscall_ptrace" -a "$(kernel_features ptrace)" == "true" ] ; then + if [ "${TEST}" = "syscall_ptrace" -a "$(kernel_features ptrace)" = "true" ] ; then # ptrace between profiles confining tasks of same pid is controlled by the ptrace rule # capability + ptrace rule needed between pids runchecktest "${TEST} changehat -- no caps" pass $bin/${TEST} ${my_arg} @@ -136,12 +150,14 @@ for TEST in ${TESTS} ; do # all capabilities allowed genprofile hat:$bin/${TEST} addimage:${bin}/${TEST} cap:ALL ${my_entries} - runchecktest "${TEST} changehat -- all caps" pass $bin/${TEST} ${my_arg} + runchecktest "${TEST} changehat -- all caps" ${expected} $bin/${TEST} ${my_arg} for cap in ${CAPABILITIES} ; do - if [ "X$(eval echo \${${TEST}_${cap}})" == "XTRUE" ] ; then + if [ ${expected} = "fail" ]; then + expected_result=fail + elif [ "X$(eval echo \${${TEST}_${cap}})" = "XTRUE" ] ; then expected_result=pass - elif [ "${TEST}" == "syscall_ptrace" -a "$(kernel_features ptrace)" == "true" ]; then + elif [ "${TEST}" = "syscall_ptrace" -a "$(kernel_features ptrace)" = "true" ]; then expected_result=pass else expected_result=fail @@ -156,75 +172,75 @@ cap=sys_chroot settest syscall_chroot # test deny keyword works -genprofile cap:${cap}:deny ${syscall_chroot_extra_entries} +genprofile qual=deny:cap:${cap} ${syscall_chroot_extra_entries} runchecktest "syscall_chroot -- capability ${cap}, deny keyword" fail ${syscall_chroot_args} # test allow keyword works -genprofile cap:${cap}:allow ${syscall_chroot_extra_entries} +genprofile qual=allow:cap:${cap} ${syscall_chroot_extra_entries} runchecktest "syscall_chroot -- capability ${cap}, allow keyword" pass ${syscall_chroot_args} ### allow/deny overlap tests ### # test allow & deny keyword behavior, allow first -genprofile cap:${cap}:allow cap:${cap}:deny ${syscall_chroot_extra_entries} +genprofile qual=allow:cap:${cap} qual=deny:cap:${cap} ${syscall_chroot_extra_entries} runchecktest "syscall_chroot -- capability ${cap}, allow & deny keyword, allow first" fail ${syscall_chroot_args} # test implicit allow & deny keyword behavior, allow first -genprofile cap:${cap} cap:${cap}:deny ${syscall_chroot_extra_entries} +genprofile cap:${cap} qual=deny:cap:${cap} ${syscall_chroot_extra_entries} runchecktest "syscall_chroot -- capability ${cap}, implicit allow & deny keyword, allow first" fail ${syscall_chroot_args} # test allow & deny keyword behavior, deny first -genprofile cap:${cap}:deny cap:${cap}:allow ${syscall_chroot_extra_entries} +genprofile qual=deny:cap:${cap} qual=allow:cap:${cap} ${syscall_chroot_extra_entries} runchecktest "syscall_chroot -- capability ${cap}, allow & deny keyword, deny first" fail ${syscall_chroot_args} # test implicit allow & deny keyword behavior, deny first -genprofile cap:${cap}:deny cap:${cap} ${syscall_chroot_extra_entries} +genprofile qual=deny:cap:${cap} cap:${cap} ${syscall_chroot_extra_entries} runchecktest "syscall_chroot -- capability ${cap}, implicit allow & deny keyword, deny first" fail ${syscall_chroot_args} # test allow all & deny all capability keyword behavior, allow first -genprofile cap:ALL:allow cap:ALL:deny ${syscall_chroot_extra_entries} +genprofile qual=allow:cap:ALL qual=deny:cap:ALL ${syscall_chroot_extra_entries} runchecktest "syscall_chroot -- capability ${cap}, allow & deny all caps keyword, allow first" fail ${syscall_chroot_args} # test implicit allow all & deny all capability keyword behavior, allow first -genprofile cap:ALL cap:ALL:deny ${syscall_chroot_extra_entries} +genprofile cap:ALL qual=deny:cap:ALL ${syscall_chroot_extra_entries} runchecktest "syscall_chroot -- capability ${cap}, implicit allow all & deny all caps keyword, allow first" fail ${syscall_chroot_args} # test allow all & deny all capability keyword behavior, deny first -genprofile cap:ALL:deny cap:ALL:allow ${syscall_chroot_extra_entries} +genprofile qual=deny:cap:ALL qual=allow:cap:ALL ${syscall_chroot_extra_entries} runchecktest "syscall_chroot -- capability ${cap}, allow & deny all caps keyword, deny first" fail ${syscall_chroot_args} # test implicit allow all & deny all capability keyword behavior, deny first -genprofile cap:ALL:deny cap:ALL ${syscall_chroot_extra_entries} +genprofile qual=deny:cap:ALL cap:ALL ${syscall_chroot_extra_entries} runchecktest "syscall_chroot -- capability ${cap}, implicit allow & deny all caps keyword, deny first" fail ${syscall_chroot_args} # test allow all & deny keywords behavior, allow first -genprofile cap:ALL:allow cap:${cap}:deny ${syscall_chroot_extra_entries} +genprofile qual=allow:cap:ALL qual=deny:cap:${cap} ${syscall_chroot_extra_entries} runchecktest "syscall_chroot -- capability ${cap}, allow all & deny keyword, allow first" fail ${syscall_chroot_args} # test implicit allow all & deny keywords behavior, allow first -genprofile cap:ALL cap:${cap}:deny ${syscall_chroot_extra_entries} +genprofile cap:ALL qual=deny:cap:${cap} ${syscall_chroot_extra_entries} runchecktest "syscall_chroot -- capability ${cap}, implicit allow all & deny keyword, allow first" fail ${syscall_chroot_args} # test allow all & deny keywords behavior, deny first -genprofile cap:${cap}:deny cap:ALL:allow ${syscall_chroot_extra_entries} +genprofile qual=deny:cap:${cap} qual=allow:cap:ALL ${syscall_chroot_extra_entries} runchecktest "syscall_chroot -- capability ${cap}, allow all & deny keyword, deny first" fail ${syscall_chroot_args} # test implicit allow all & deny keywords behavior, deny first -genprofile cap:${cap}:deny cap:ALL ${syscall_chroot_extra_entries} +genprofile qual=deny:cap:${cap} cap:ALL ${syscall_chroot_extra_entries} runchecktest "syscall_chroot -- capability ${cap}, implicit allow all & deny keyword, deny first" fail ${syscall_chroot_args} # test allow & deny all keywords behavior, allow first -genprofile cap:${cap}:allow cap:ALL:deny ${syscall_chroot_extra_entries} +genprofile qual=allow:cap:${cap} qual=deny:cap:ALL ${syscall_chroot_extra_entries} runchecktest "syscall_chroot -- capability ${cap}, allow & deny all keyword, allow first" fail ${syscall_chroot_args} # test implicit allow & deny all keywords behavior, allow first -genprofile cap:${cap} cap:ALL:deny ${syscall_chroot_extra_entries} +genprofile cap:${cap} qual=deny:cap:ALL ${syscall_chroot_extra_entries} runchecktest "syscall_chroot -- capability ${cap}, implicit allow & deny all keyword, allow first" fail ${syscall_chroot_args} # test allow & deny all keywords behavior, deny first -genprofile cap:ALL:deny cap:${cap}:allow ${syscall_chroot_extra_entries} +genprofile qual=deny:cap:ALL qual=allow:cap:${cap} ${syscall_chroot_extra_entries} runchecktest "syscall_chroot -- capability ${cap}, allow & deny all keyword, deny first" fail ${syscall_chroot_args} # test implicit allow & deny all keywords behavior, deny first -genprofile cap:ALL:deny cap:${cap} ${syscall_chroot_extra_entries} +genprofile qual=deny:cap:ALL cap:${cap} ${syscall_chroot_extra_entries} runchecktest "syscall_chroot -- capability ${cap}, implicit allow & deny all keyword, deny first" fail ${syscall_chroot_args} diff --git a/tests/regression/apparmor/changehat_fork.sh b/tests/regression/apparmor/changehat_fork.sh index 27376e7a4..ecf0151de 100755 --- a/tests/regression/apparmor/changehat_fork.sh +++ b/tests/regression/apparmor/changehat_fork.sh @@ -61,7 +61,7 @@ echo -n "${testexec}//${subtest3}" >/sys/kernel/security/apparmor/.remove # Should put us into a null-profile # NOTE: As of AppArmor 2.1 (opensuse 10.3) this test now passes as -# the change_hat failes but it no longer entires the null profile +# the change_hat fails but it no longer enters the null profile genprofile $file:$okperm hat:$subtest $subfile:$okperm hat:$subtest2 $subfile:$okperm runchecktest "CHANGEHAT (access parent file 3)" pass $subtest3 $file diff --git a/tests/regression/apparmor/changeprofile.sh b/tests/regression/apparmor/changeprofile.sh index 5d5cff741..2c57e210f 100755 --- a/tests/regression/apparmor/changeprofile.sh +++ b/tests/regression/apparmor/changeprofile.sh @@ -47,7 +47,7 @@ runchecktest "NO CHANGEPROFILE (access parent file)" pass nochange $file runchecktest "NO CHANGEPROFILE (access sub file)" fail nochange $subfile errno=EACCES -if [ "$(kernel_features domain/stack)" == "true" ]; then +if [ "$(kernel_features domain/stack)" = "true" ]; then # The returned errno changed in the set of kernel patches that # introduced AppArmor profile stacking errno=ENOENT diff --git a/tests/regression/apparmor/clone.sh b/tests/regression/apparmor/clone.sh index 5d924de0f..feec8a154 100644 --- a/tests/regression/apparmor/clone.sh +++ b/tests/regression/apparmor/clone.sh @@ -9,7 +9,7 @@ #=NAME clone #=DESCRIPTION # Verifies that clone is allowed under AppArmor, but that CLONE_NEWNS is -# restriced. +# restricted. #=END pwd=`dirname $0` diff --git a/tests/regression/apparmor/coredump.sh b/tests/regression/apparmor/coredump.sh index a5d43094e..acfd89c99 100644 --- a/tests/regression/apparmor/coredump.sh +++ b/tests/regression/apparmor/coredump.sh @@ -18,7 +18,7 @@ cleancorefile() checkcorefile() { # global _testdesc _pfmode _known outfile - if [ ${1:0:1} == "x" ] ; then + if [ ${1:0:1} = "x" ] ; then requirement=${1#x} _known=" (known problem)" else diff --git a/tests/regression/apparmor/dbus.inc b/tests/regression/apparmor/dbus.inc index b234a348f..d30113235 100755 --- a/tests/regression/apparmor/dbus.inc +++ b/tests/regression/apparmor/dbus.inc @@ -13,8 +13,9 @@ gendbusprofile() ${__dbus_var_decl} $test { @{gen $test} + $outfile w, unix, - $@ + $* signal receive peer=unconfined, } EOF @@ -30,12 +31,109 @@ set_dbus_var() __dbus_var_decl=$@ } -start_bus() +cleanup_dbus_broker() { - out=$(dbus-daemon --fork --print-pid --print-address --config-file=dbus.conf) + rm -f /etc/systemd/system/dbus-apparmor-test.socket + rm -f /etc/systemd/system/dbus-apparmor-test.service + # don't stop test execution if systemctl is not available + systemctl daemon-reload 2>/dev/null || true +} + +kill_dbus_broker() +{ + if [ $(systemctl is-active dbus-apparmor-test.service) == "active" ] + then + if ! systemctl -q stop dbus-apparmor-test.service + then + echo "Failed to stop DBus broker service" + fi + fi + + if [ $(systemctl is-active dbus-apparmor-test.socket) == "active" ] + then + if ! systemctl -q stop dbus-apparmor-test.socket + then + echo "Failed to stop DBus broker socket" + fi + fi + + cleanup_dbus_broker +} + +start_dbus_broker() +{ + # TODO: remove systemd dependency from DBus Broker tests + if [ ! -d /run/systemd/system/ ] + then + echo "Error: DBus Broker tests require systemd" + return 1 + fi + + if [ $(which dbus-broker-launch > /dev/null; echo $?) -ne 0 ] + then + echo "Error: dbus-broker-launch not available" + return 1 + fi + + bus_addr=$(mktemp --dry-run /tmp/dbus-XXXXXX) + + dbus_test_socket=" +[Unit] +Description=AppArmor D-Bus Broker Test Socket + +[Socket] +ListenStream=@$bus_addr +" + dbus_test_service=" +[Unit] +Description=AppArmor D-Bus Broker Test Service +After=dbus-apparmor-test.socket +Requires=dbus-apparmor-test.socket + +[Service] +Sockets=dbus-apparmor-test.socket +StartLimitBurst=0 +ExecStart=dbus-broker-launch --scope system --audit --config-file=$(pwd)/dbus.conf + +[Install] +WantedBy=default.target +" + echo "$dbus_test_socket" > /etc/systemd/system/dbus-apparmor-test.socket + echo "$dbus_test_service" > /etc/systemd/system/dbus-apparmor-test.service + + systemctl daemon-reload + + if ! systemctl -q start dbus-apparmor-test + then + echo "Error: Failed to start DBus broker launcher" + return 1 + fi + + do_onexit="kill_dbus_broker" + + export DBUS_SESSION_BUS_ADDRESS="unix:abstract=$bus_addr" + return 0 +} + +kill_dbus_daemon() +{ + kill $bus_pid >/dev/null 2>&1 || true +} + +start_dbus_daemon() +{ + if [ $(which dbus-daemon > /dev/null; echo $?) -ne 0 ] + then + echo "Error: dbus-daemon not available" + return 1 + fi + + bus_addr=$(mktemp --dry-run /tmp/dbus-XXXXXX) + out=$(dbus-daemon --fork --print-pid --print-address --address="unix:abstract=$bus_addr" --config-file=dbus.conf) if [ $? -ne 0 ] then - fatalerror "Failed to start DBus daemon" + echo "Failed to start DBus daemon" + return 1 fi bus_addr=$(echo $out | cut -d\ -f 1) @@ -48,11 +146,13 @@ start_bus() kill -0 $bus_pid 2>/dev/null if [ $? -ne 0 ] then - fatalerror "DBus daemon unexpectedly stopped" + echo "DBus daemon unexpectedly stopped" + return 1 fi - do_onexit="kill $bus_pid" + do_onexit="kill_dbus_daemon" export DBUS_SESSION_BUS_ADDRESS=$bus_addr + return 0 } bus="session" diff --git a/tests/regression/apparmor/dbus_eavesdrop.sh b/tests/regression/apparmor/dbus_eavesdrop.sh index a7f21552f..32746a9b1 100755 --- a/tests/regression/apparmor/dbus_eavesdrop.sh +++ b/tests/regression/apparmor/dbus_eavesdrop.sh @@ -24,55 +24,77 @@ requires_parser_support "dbus," args="--session" -start_bus - -# Make sure we can eavesdrop unconfined - settest dbus_eavesdrop -runchecktest "eavesdrop (unconfined)" pass $args +run_tests() +{ + # Make sure we can eavesdrop unconfined + + runchecktest "eavesdrop (unconfined)" pass $args -# Make sure we get denials when confined but not allowed + # Make sure we get denials when confined but not allowed -genprofile -runchecktest "eavesdrop (confined w/o dbus perms)" fail $args + gendbusprofile + runchecktest "eavesdrop (confined w/o dbus perms)" fail $args -gendbusprofile "dbus send," -runchecktest "eavesdrop (confined w/ only send allowed)" fail $args + gendbusprofile "dbus send," + runchecktest "eavesdrop (confined w/ only send allowed)" fail $args -gendbusprofile "dbus eavesdrop," -runchecktest "eavesdrop (confined w/ only eavesdrop allowed)" fail $args + gendbusprofile "dbus eavesdrop," + runchecktest "eavesdrop (confined w/ only eavesdrop allowed)" fail $args -# Make sure we're okay when confined with appropriate permissions + # Make sure we're okay when confined with appropriate permissions -gendbusprofile "dbus," -runchecktest "eavesdrop (dbus allowed)" pass $args + gendbusprofile "dbus," + runchecktest "eavesdrop (dbus allowed)" pass $args -gendbusprofile "dbus (send eavesdrop)," -runchecktest "eavesdrop (send, eavesdrop allowed)" pass $args + gendbusprofile "dbus (send eavesdrop)," + runchecktest "eavesdrop (send, eavesdrop allowed)" pass $args -gendbusprofile "dbus (send eavesdrop) bus=session," -runchecktest "eavesdrop (send, eavesdrop allowed w/ bus conditional)" pass $args + gendbusprofile "dbus (send eavesdrop) bus=session," + runchecktest "eavesdrop (send, eavesdrop allowed w/ bus conditional)" pass $args -gendbusprofile "dbus send bus=session path=/org/freedesktop/DBus \ + gendbusprofile "dbus send bus=session path=/org/freedesktop/DBus \ interface=org.freedesktop.DBus \ member=Hello, \ dbus send bus=session path=/org/freedesktop/DBus \ interface=org.freedesktop.DBus \ member=AddMatch, \ dbus eavesdrop bus=session," -runchecktest "eavesdrop (send, eavesdrop allowed w/ bus and send member conditionals)" pass $args + runchecktest "eavesdrop (send, eavesdrop allowed w/ bus and send member conditionals)" pass $args -gendbusprofile "dbus send, \ + gendbusprofile "dbus send, \ audit dbus eavesdrop," -runchecktest "eavesdrop (send allowed, eavesdrop audited)" pass $args + runchecktest "eavesdrop (send allowed, eavesdrop audited)" pass $args -# Make sure we're denied when confined without appropriate conditionals + # Make sure we're denied when confined without appropriate conditionals -gendbusprofile "dbus send bus=session, \ + gendbusprofile "dbus send bus=session, \ dbus eavesdrop bus=system," -runchecktest "eavesdrop (wrong bus)" fail $args + runchecktest "eavesdrop (wrong bus)" fail $args -gendbusprofile "dbus send, \ + gendbusprofile "dbus send, \ deny dbus eavesdrop," -runchecktest "eavesdrop (send allowed, eavesdrop denied)" fail $args + runchecktest "eavesdrop (send allowed, eavesdrop denied)" fail $args + + # don't forget to remove the profile so the test can run again + removeprofile +} + +if start_dbus_daemon +then + run_tests + kill_dbus_daemon +else + echo "Starting DBus Daemon failed. Skipping tests..." +fi + +# Eavesdropping is deprecated in DBus Broker +# from https://github.com/bus1/dbus-broker/wiki/Deviations +# +# "The concept of eavesdropping has been deprecated in favor of +# monitoring upstream ... For the time being eavesdropping is not +# implemented in dbus-broker." +# +# TODO: add tests for the "BecomeMonitor" method +echo "DBus Broker does not support eavesdrop. Skipping tests..." diff --git a/tests/regression/apparmor/dbus_message.sh b/tests/regression/apparmor/dbus_message.sh index 27807c429..14bd1e522 100755 --- a/tests/regression/apparmor/dbus_message.sh +++ b/tests/regression/apparmor/dbus_message.sh @@ -33,123 +33,144 @@ confined_args="--log=$confined_log $listnames" message_gendbusprofile() { gendbusprofile "${confined_log} w, - $@" + $*" } -start_bus - settest dbus_message -# Make sure can send unconfined +run_tests() +{ + # Make sure can send unconfined + + runchecktest "message (unconfined)" pass $unconfined_args -runchecktest "message (unconfined)" pass $unconfined_args + # Make sure send is denied when confined but not allowed -# Make sure send is denied when confined but not allowed + message_gendbusprofile + runchecktest "message (confined w/o dbus allowed)" fail $confined_args -message_gendbusprofile -runchecktest "message (confined w/o dbus allowed)" fail $confined_args + message_gendbusprofile "dbus receive," + runchecktest "message (receive allowed)" fail $confined_args -message_gendbusprofile "dbus receive," -runchecktest "message (receive allowed)" fail $confined_args + message_gendbusprofile "dbus bind," + runchecktest "message (bind allowed)" fail $confined_args -message_gendbusprofile "dbus bind," -runchecktest "message (bind allowed)" fail $confined_args + message_gendbusprofile "dbus (receive, bind)," + runchecktest "message (receive bind allowed)" fail $confined_args -message_gendbusprofile "dbus (receive, bind)," -runchecktest "message (receive bind allowed)" fail $confined_args + # Make sure send is allowed when confined with appropriate permissions -# Make sure send is allowed when confined with appropriate permissions + message_gendbusprofile "dbus," + runtestfg "message (dbus allowed)" pass $confined_args + checktestfg "compare_logs $unconfined_log eq $confined_log" -message_gendbusprofile "dbus," -runtestfg "message (dbus allowed)" pass $confined_args -checktestfg "compare_logs $unconfined_log eq $confined_log" + message_gendbusprofile "dbus send," + runtestfg "message (send allowed)" pass $confined_args + checktestfg "compare_logs $unconfined_log eq $confined_log" -message_gendbusprofile "dbus send," -runtestfg "message (send allowed)" pass $confined_args -checktestfg "compare_logs $unconfined_log eq $confined_log" + message_gendbusprofile "dbus (send, receive)," + runtestfg "message (send receive allowed)" pass $confined_args + checktestfg "compare_logs $unconfined_log eq $confined_log" -message_gendbusprofile "dbus (send, receive)," -runtestfg "message (send receive allowed)" pass $confined_args -checktestfg "compare_logs $unconfined_log eq $confined_log" + message_gendbusprofile "dbus (send, bind)," + runtestfg "message (send bind allowed)" pass $confined_args + checktestfg "compare_logs $unconfined_log eq $confined_log" -message_gendbusprofile "dbus (send, bind)," -runtestfg "message (send bind allowed)" pass $confined_args -checktestfg "compare_logs $unconfined_log eq $confined_log" + message_gendbusprofile "dbus (send, receive, bind)," + runtestfg "message (send receive bind allowed)" pass $confined_args + checktestfg "compare_logs $unconfined_log eq $confined_log" -message_gendbusprofile "dbus (send, receive, bind)," -runtestfg "message (send receive bind allowed)" pass $confined_args -checktestfg "compare_logs $unconfined_log eq $confined_log" + # Make sure send is allowed when confined with appropriate permissions along + # with conditionals -# Make sure send is allowed when confined with appropriate permissions along -# with conditionals + message_gendbusprofile "dbus send bus=session," + runtestfg "message (send allowed w/ bus)" pass $confined_args + checktestfg "compare_logs $unconfined_log eq $confined_log" -message_gendbusprofile "dbus send bus=session," -runtestfg "message (send allowed w/ bus)" pass $confined_args -checktestfg "compare_logs $unconfined_log eq $confined_log" + message_gendbusprofile "dbus send bus=session peer=(name=org.freedesktop.DBus)," + runtestfg "message (send allowed w/ bus, dest)" pass $confined_args + checktestfg "compare_logs $unconfined_log eq $confined_log" -message_gendbusprofile "dbus send bus=session peer=(name=org.freedesktop.DBus)," -runtestfg "message (send allowed w/ bus, dest)" pass $confined_args -checktestfg "compare_logs $unconfined_log eq $confined_log" + message_gendbusprofile "dbus send bus=session path=/org/freedesktop/DBus peer=(name=org.freedesktop.DBus)," + runchecktest "message (send allowed w/ bus, dest, path)" pass $confined_args + checktestfg "compare_logs $unconfined_log eq $confined_log" -message_gendbusprofile "dbus send bus=session path=/org/freedesktop/DBus peer=(name=org.freedesktop.DBus)," -runchecktest "message (send allowed w/ bus, dest, path)" pass $confined_args -checktestfg "compare_logs $unconfined_log eq $confined_log" + message_gendbusprofile "dbus send bus=session path=/org/freedesktop/DBus interface=org.freedesktop.DBus peer=(name=org.freedesktop.DBus)," + runtestfg "message (send allowed w/ bus, dest, path, interface)" pass $confined_args + checktestfg "compare_logs $unconfined_log eq $confined_log" -message_gendbusprofile "dbus send bus=session path=/org/freedesktop/DBus interface=org.freedesktop.DBus peer=(name=org.freedesktop.DBus)," -runtestfg "message (send allowed w/ bus, dest, path, interface)" pass $confined_args -checktestfg "compare_logs $unconfined_log eq $confined_log" + message_gendbusprofile "dbus send bus=session path=/org/freedesktop/DBus interface=org.freedesktop.DBus member={Hello,ListNames} peer=(name=org.freedesktop.DBus)," + runtestfg "message (send allowed w/ bus, dest, path, interface, method)" pass $confined_args + checktestfg "compare_logs $unconfined_log eq $confined_log" -message_gendbusprofile "dbus send bus=session path=/org/freedesktop/DBus interface=org.freedesktop.DBus member={Hello,ListNames} peer=(name=org.freedesktop.DBus)," -runtestfg "message (send allowed w/ bus, dest, path, interface, method)" pass $confined_args -checktestfg "compare_logs $unconfined_log eq $confined_log" + # Make sure send is allowed when confined with appropriate permissions along + # with conditionals and variables (same tests as above, with vars) -# Make sure send is allowed when confined with appropriate permissions along -# with conditionals and variables (same tests as above, with vars) + set_dbus_var "@{BUSES}=session system" + message_gendbusprofile "dbus send bus=@{BUSES}," + runtestfg "message (send allowed w/ bus)" pass $confined_args + checktestfg "compare_logs $unconfined_log eq $confined_log" -set_dbus_var "@{BUSES}=session system" -message_gendbusprofile "dbus send bus=@{BUSES}," -runtestfg "message (send allowed w/ bus)" pass $confined_args -checktestfg "compare_logs $unconfined_log eq $confined_log" + set_dbus_var "@{PEERNAMES}=com.ubuntu.what net.apparmor.wiki org.freedesktop.DBus" + message_gendbusprofile "dbus send bus=session peer=(name=@{PEERNAMES})," + runtestfg "message (send allowed w/ bus, dest)" pass $confined_args + checktestfg "compare_logs $unconfined_log eq $confined_log" -set_dbus_var "@{PEERNAMES}=com.ubuntu.what net.apparmor.wiki org.freedesktop.DBus" -message_gendbusprofile "dbus send bus=session peer=(name=@{PEERNAMES})," -runtestfg "message (send allowed w/ bus, dest)" pass $confined_args -checktestfg "compare_logs $unconfined_log eq $confined_log" + set_dbus_var "@{PATHNAMES}=DBus spork spoon spork" + message_gendbusprofile "dbus send bus=session path=/org/freedesktop/@{PATHNAMES} peer=(name=org.freedesktop.DBus)," + runchecktest "message (send allowed w/ bus, dest, path)" pass $confined_args + checktestfg "compare_logs $unconfined_log eq $confined_log" -set_dbus_var "@{PATHNAMES}=DBus spork spoon spork" -message_gendbusprofile "dbus send bus=session path=/org/freedesktop/@{PATHNAMES} peer=(name=org.freedesktop.DBus)," -runchecktest "message (send allowed w/ bus, dest, path)" pass $confined_args -checktestfg "compare_logs $unconfined_log eq $confined_log" + set_dbus_var "@{INTERFACE_NAMES}=DBus spork spoon spork" + message_gendbusprofile "dbus send bus=session path=/org/freedesktop/DBus interface=org.freedesktop.@{INTERFACE_NAMES} peer=(name=org.freedesktop.DBus)," + runtestfg "message (send allowed w/ bus, dest, path, interface)" pass $confined_args + checktestfg "compare_logs $unconfined_log eq $confined_log" -set_dbus_var "@{INTERFACE_NAMES}=DBus spork spoon spork" -message_gendbusprofile "dbus send bus=session path=/org/freedesktop/DBus interface=org.freedesktop.@{INTERFACE_NAMES} peer=(name=org.freedesktop.DBus)," -runtestfg "message (send allowed w/ bus, dest, path, interface)" pass $confined_args -checktestfg "compare_logs $unconfined_log eq $confined_log" + set_dbus_var "@{MEMBERS}=Hello ListNames Spork Spoon" + message_gendbusprofile "dbus send bus=session path=/org/freedesktop/DBus interface=org.freedesktop.DBus member=@{MEMBERS} peer=(name=org.freedesktop.DBus)," + runtestfg "message (send allowed w/ bus, dest, path, interface, method)" pass $confined_args + checktestfg "compare_logs $unconfined_log eq $confined_log" -set_dbus_var "@{MEMBERS}=Hello ListNames Spork Spoon" -message_gendbusprofile "dbus send bus=session path=/org/freedesktop/DBus interface=org.freedesktop.DBus member=@{MEMBERS} peer=(name=org.freedesktop.DBus)," -runtestfg "message (send allowed w/ bus, dest, path, interface, method)" pass $confined_args -checktestfg "compare_logs $unconfined_log eq $confined_log" + # Make sure send is denied when confined with appropriate permissions along + # with incorrect conditionals -# Make sure send is denied when confined with appropriate permissions along -# with incorrect conditionals + message_gendbusprofile "dbus send bus=system," + runtestfg "message (send allowed w/ wrong bus)" fail $confined_args + checktestfg "compare_logs $unconfined_log ne $confined_log" -message_gendbusprofile "dbus send bus=system," -runtestfg "message (send allowed w/ wrong bus)" fail $confined_args -checktestfg "compare_logs $unconfined_log ne $confined_log" + message_gendbusprofile "dbus send bus=session peer=(name=com.freedesktop.DBus)," + runtestfg "message (send allowed w/ wrong dest)" fail $confined_args + checktestfg "compare_logs $unconfined_log ne $confined_log" -message_gendbusprofile "dbus send bus=session peer=(name=com.freedesktop.DBus)," -runtestfg "message (send allowed w/ wrong dest)" fail $confined_args -checktestfg "compare_logs $unconfined_log ne $confined_log" + message_gendbusprofile "dbus send bus=session path=/bad/freedesktop/DBus peer=(name=bad.freedesktop.DBus)," + runtestfg "message (send allowed w/ wrong path)" fail $confined_args + checktestfg "compare_logs $unconfined_log ne $confined_log" -message_gendbusprofile "dbus send bus=session path=/bad/freedesktop/DBus peer=(name=bad.freedesktop.DBus)," -runtestfg "message (send allowed w/ wrong path)" fail $confined_args -checktestfg "compare_logs $unconfined_log ne $confined_log" + message_gendbusprofile "dbus send bus=session path=/org/freedesktop/DBus interface=bad.freedesktop.DBus peer=(name=bad.freedesktop.DBus)," + runtestfg "message (send allowed w/ wrong interface)" fail $confined_args + checktestfg "compare_logs $unconfined_log ne $confined_log" -message_gendbusprofile "dbus send bus=session path=/org/freedesktop/DBus interface=bad.freedesktop.DBus peer=(name=bad.freedesktop.DBus)," -runtestfg "message (send allowed w/ wrong interface)" fail $confined_args -checktestfg "compare_logs $unconfined_log ne $confined_log" + message_gendbusprofile "dbus send bus=session path=/org/freedesktop/DBus interface=com.freedesktop.DBus member=Hello peer=(name=bad.freedesktop.DBus)," + runtestfg "message (send allowed w/ wrong method)" fail $confined_args + checktestfg "compare_logs $unconfined_log ne $confined_log" + + # don't forget to remove the profile so the test can run again + removeprofile +} -message_gendbusprofile "dbus send bus=session path=/org/freedesktop/DBus interface=com.freedesktop.DBus member=Hello peer=(name=bad.freedesktop.DBus)," -runtestfg "message (send allowed w/ wrong method)" fail $confined_args -checktestfg "compare_logs $unconfined_log ne $confined_log" +if start_dbus_daemon +then + run_tests + kill_dbus_daemon +else + echo "Starting DBus Daemon failed. Skipping tests..." +fi + +if start_dbus_broker +then + run_tests + kill_dbus_broker +else + echo "Starting DBus Broker failed. Skipping tests..." + cleanup_dbus_broker +fi diff --git a/tests/regression/apparmor/dbus_service.c b/tests/regression/apparmor/dbus_service.c index b4c52b1b9..cc1737b12 100644 --- a/tests/regression/apparmor/dbus_service.c +++ b/tests/regression/apparmor/dbus_service.c @@ -68,7 +68,7 @@ static int handle_messages(void) DBusMessage *message; if (!dbus_connection_read_write(connection, 250)) { - fprintf(stderr, "FAIL: Connecion is closed\n"); + fprintf(stderr, "FAIL: Connection is closed\n"); return -1; } diff --git a/tests/regression/apparmor/dbus_service.sh b/tests/regression/apparmor/dbus_service.sh index 5cd698a28..10b61484a 100755 --- a/tests/regression/apparmor/dbus_service.sh +++ b/tests/regression/apparmor/dbus_service.sh @@ -62,78 +62,99 @@ service_runchecktest() service_gendbusprofile() { gendbusprofile "$unconfined_log w, - $@" + $*" } -start_bus - -# Make sure we can bind a bus name and receive a message unconfined - settest dbus_service -service_runtestbg "service (unconfined)" pass $confined_log -sendmethod -sendsignal -service_checktestbg +run_tests() +{ + # Make sure we can bind a bus name and receive a message unconfined + + service_runtestbg "service (unconfined)" pass $confined_log + sendmethod + sendsignal + service_checktestbg -# Make sure we get denials when confined but not allowed + # Make sure we get denials when confined but not allowed -genprofile -service_runchecktest "service (confined w/o dbus perms)" fail + genprofile + service_runchecktest "service (confined w/o dbus perms)" fail -service_gendbusprofile "dbus send," -service_runchecktest "service (send allowed)" fail + service_gendbusprofile "dbus send," + service_runchecktest "service (send allowed)" fail -service_gendbusprofile "dbus receive," -service_runchecktest "service (receive allowed)" fail + service_gendbusprofile "dbus receive," + service_runchecktest "service (receive allowed)" fail -service_gendbusprofile "dbus bind," -service_runchecktest "service (bind allowed)" fail + service_gendbusprofile "dbus bind," + service_runchecktest "service (bind allowed)" fail -# Make sure we're okay when confined with appropriate permissions + # Make sure we're okay when confined with appropriate permissions -service_gendbusprofile "dbus," -service_runtestbg "service (dbus allowed)" pass $unconfined_log -sendmethod -sendsignal -service_checktestbg "compare_logs $unconfined_log eq $confined_log" + service_gendbusprofile "dbus," + service_runtestbg "service (dbus allowed)" pass $unconfined_log + sendmethod + sendsignal + service_checktestbg "compare_logs $unconfined_log eq $confined_log" -service_gendbusprofile "dbus (send, receive, bind)," -service_runtestbg "service (send receive bind allowed)" pass $unconfined_log -sendmethod -sendsignal -service_checktestbg "compare_logs $unconfined_log eq $confined_log" + service_gendbusprofile "dbus (send, receive, bind)," + service_runtestbg "service (send receive bind allowed)" pass $unconfined_log + sendmethod + sendsignal + service_checktestbg "compare_logs $unconfined_log eq $confined_log" -service_gendbusprofile "dbus (send receive bind) bus=session," -service_runtestbg "service (send receive bind w/ bus)" pass $unconfined_log -sendmethod -sendsignal -service_checktestbg "compare_logs $unconfined_log eq $confined_log" + service_gendbusprofile "dbus (send receive bind) bus=session," + service_runtestbg "service (send receive bind w/ bus)" pass $unconfined_log + sendmethod + sendsignal + service_checktestbg "compare_logs $unconfined_log eq $confined_log" -service_gendbusprofile "dbus bind bus=session name=$dest, \ + service_gendbusprofile "dbus bind bus=session name=$dest, \ dbus receive bus=session, \ dbus send bus=session peer=(name=org.freedesktop.DBus)," -service_runtestbg "service (receive bind w/ bus, dest)" pass $unconfined_log -sendmethod -sendsignal -service_checktestbg "compare_logs $unconfined_log eq $confined_log" + service_runtestbg "service (receive bind w/ bus, dest)" pass $unconfined_log + sendmethod + sendsignal + service_checktestbg "compare_logs $unconfined_log eq $confined_log" -service_gendbusprofile "dbus bind bus=session name=$dest, \ + service_gendbusprofile "dbus bind bus=session name=$dest, \ dbus receive bus=session, \ dbus send bus=session peer=(name=org.freedesktop.DBus)," -service_runtestbg "service (receive bind w/ bus, dest)" pass $unconfined_log -sendmethod -sendsignal -service_checktestbg "compare_logs $unconfined_log eq $confined_log" + service_runtestbg "service (receive bind w/ bus, dest)" pass $unconfined_log + sendmethod + sendsignal + service_checktestbg "compare_logs $unconfined_log eq $confined_log" -# Make sure we're denied when confined without appropriate conditionals + # Make sure we're denied when confined without appropriate conditionals -service_gendbusprofile "dbus bind bus=system name=$dest, \ + service_gendbusprofile "dbus bind bus=system name=$dest, \ dbus receive bus=system, \ dbus send bus=session peer=(name=org.freedesktop.DBus)," -service_runchecktest "service (receive bind w/ wrong bus)" fail + service_runchecktest "service (receive bind w/ wrong bus)" fail -service_gendbusprofile "dbus bind bus=session name=${dest}.BAD, \ + service_gendbusprofile "dbus bind bus=session name=${dest}.BAD, \ dbus receive bus=session, \ dbus send bus=session peer=(name=org.freedesktop.DBus)," -service_runchecktest "service (receive bind w/ wrong dest)" fail + service_runchecktest "service (receive bind w/ wrong dest)" fail + + # don't forget to remove the profile so the test can run again + removeprofile +} + +if start_dbus_daemon +then + run_tests + kill_dbus_daemon +else + echo "Starting DBus Daemon failed. Skipping tests..." +fi + +if start_dbus_broker +then + run_tests + kill_dbus_broker +else + echo "Starting DBus Broker failed. Skipping tests..." + cleanup_dbus_broker +fi diff --git a/tests/regression/apparmor/dbus_unrequested_reply.sh b/tests/regression/apparmor/dbus_unrequested_reply.sh index e69c8b458..626554f3d 100644 --- a/tests/regression/apparmor/dbus_unrequested_reply.sh +++ b/tests/regression/apparmor/dbus_unrequested_reply.sh @@ -63,65 +63,85 @@ ur_gendbusprofile() { gendbusprofile "$confined_log w, dbus bind bus=$bus name=$dest, - $@" + $*" } -start_bus - settest dbus_service -# Start a dbus service and send unrequested method_return and error messages to -# the service. The service should always start and stop just fine. The test -# results hinge on comparing the message log from confined services to the -# message log from the initial unconfined run. - -# Do an unconfined run to get an "expected" log for comparisons -ur_runtestbg "unrequested_reply (method_return, unconfined)" pass $unconfined_log -sendmethodreturn -ur_checktestbg - -# All dbus perms are granted so the logs should be equal -ur_gendbusprofile "dbus," -ur_runtestbg "unrequested_reply (method_return, dbus allowed)" pass $confined_log -sendmethodreturn -ur_checktestbg "compare_logs $unconfined_log eq $confined_log" - -# Only send perm is granted so the confined service should not be able to -# receive unrequested replies from the client -ur_gendbusprofile "dbus send," -ur_runtestbg "unrequested_reply (method_return, send allowed)" pass $confined_log -sendmethodreturn -ur_checktestbg "compare_logs $unconfined_log ne $confined_log" - -# Send and receive perms are granted so the logs should be equal -ur_gendbusprofile "dbus (send receive)," -ur_runtestbg "unrequested_reply (method_return, send receive allowed)" pass $confined_log -sendmethodreturn -ur_checktestbg "compare_logs $unconfined_log eq $confined_log" - -# Now test unrequested error replies - -# Do an unconfined run to get an "expected" log for comparisons -removeprofile -ur_runtestbg "unrequested_reply (error, unconfined)" pass $unconfined_log -senderror -ur_checktestbg - -# All dbus perms are granted so the logs should be equal -ur_gendbusprofile "dbus," -ur_runtestbg "unrequested_reply (error, dbus allowed)" pass $confined_log -senderror -ur_checktestbg "compare_logs $unconfined_log eq $confined_log" - -# Only send perm is granted so the confined service should not be able to -# receive unrequested replies from the client -ur_gendbusprofile "dbus send," -ur_runtestbg "unrequested_reply (error, send allowed)" pass $confined_log -senderror -ur_checktestbg "compare_logs $unconfined_log ne $confined_log" - -# Send and receive perms are granted so the logs should be equal -ur_gendbusprofile "dbus (send receive)," -ur_runtestbg "unrequested_reply (error, send receive allowed)" pass $confined_log -senderror -ur_checktestbg "compare_logs $unconfined_log eq $confined_log" +run_tests() +{ + # Start a dbus service and send unrequested method_return and error messages to + # the service. The service should always start and stop just fine. The test + # results hinge on comparing the message log from confined services to the + # message log from the initial unconfined run. + + # Do an unconfined run to get an "expected" log for comparisons + ur_runtestbg "unrequested_reply (method_return, unconfined)" pass $unconfined_log + sendmethodreturn + ur_checktestbg + + # All dbus perms are granted so the logs should be equal + ur_gendbusprofile "dbus," + ur_runtestbg "unrequested_reply (method_return, dbus allowed)" pass $confined_log + sendmethodreturn + ur_checktestbg "compare_logs $unconfined_log eq $confined_log" + + # Only send perm is granted so the confined service should not be able to + # receive unrequested replies from the client + ur_gendbusprofile "dbus send," + ur_runtestbg "unrequested_reply (method_return, send allowed)" pass $confined_log + sendmethodreturn + ur_checktestbg "compare_logs $unconfined_log ne $confined_log" + + # Send and receive perms are granted so the logs should be equal + ur_gendbusprofile "dbus (send receive)," + ur_runtestbg "unrequested_reply (method_return, send receive allowed)" pass $confined_log + sendmethodreturn + ur_checktestbg "compare_logs $unconfined_log eq $confined_log" + + # Now test unrequested error replies + + # Do an unconfined run to get an "expected" log for comparisons + removeprofile + ur_runtestbg "unrequested_reply (error, unconfined)" pass $unconfined_log + senderror + ur_checktestbg + + # All dbus perms are granted so the logs should be equal + ur_gendbusprofile "dbus," + ur_runtestbg "unrequested_reply (error, dbus allowed)" pass $confined_log + senderror + ur_checktestbg "compare_logs $unconfined_log eq $confined_log" + + # Only send perm is granted so the confined service should not be able to + # receive unrequested replies from the client + ur_gendbusprofile "dbus send," + ur_runtestbg "unrequested_reply (error, send allowed)" pass $confined_log + senderror + ur_checktestbg "compare_logs $unconfined_log ne $confined_log" + + # Send and receive perms are granted so the logs should be equal + ur_gendbusprofile "dbus (send receive)," + ur_runtestbg "unrequested_reply (error, send receive allowed)" pass $confined_log + senderror + ur_checktestbg "compare_logs $unconfined_log eq $confined_log" + + # don't forget to remove the profile so the test can run again + removeprofile +} + +if start_dbus_daemon +then + run_tests + kill_dbus_daemon +else + echo "Starting DBus Daemon failed. Skipping tests..." +fi + +# Unrequested replies are not supported by DBus Broker +# from https://github.com/bus1/dbus-broker/wiki/Deviations +# +# "... dbus-broker only allows expected replies, and those are allowed +# unconditionally. Unexpected-replies and Reply-filtering have no +# known users (nor use-cases), hence support has been dropped..." +echo "DBus Broker does not support unrequested replies. Skipping tests..." diff --git a/tests/regression/apparmor/deleted.c b/tests/regression/apparmor/deleted.c index b866f29e6..be22342d1 100644 --- a/tests/regression/apparmor/deleted.c +++ b/tests/regression/apparmor/deleted.c @@ -21,7 +21,7 @@ /* A test to validate that we are properly handling the kernel appending * (deleted) in d_path lookup. - * To acheive this the file is opened (the read/write of the file is just to + * To achieve this the file is opened (the read/write of the file is just to * make sure everything is working as expected), deleted without closing the * file reference, and doing a changehat. * The file is then used inside of the changehat. This forces the file diff --git a/tests/regression/apparmor/deleted.sh b/tests/regression/apparmor/deleted.sh index 9ca937f6d..01bb035a2 100755 --- a/tests/regression/apparmor/deleted.sh +++ b/tests/regression/apparmor/deleted.sh @@ -65,7 +65,9 @@ okperm=rwl badperm=wl af_unix="" -if [ "$(kernel_features network/af_unix)" == "true" -a "$(parser_supports 'unix,')" == "true" ]; then +if [ "$(kernel_features network_v8)" = "true" -a "$(parser_supports 'unix,')" = "true" ]; then + af_unix="unix:create" +elif [ "$(kernel_features network/af_unix)" = "true" -a "$(parser_supports 'unix,')" = "true" ]; then af_unix="unix:create" fi @@ -89,7 +91,7 @@ rm -f ${socket} genprofile $af_unix $file:$okperm $socket:rw $fd_client:ux -runchecktest "fd passing; unconfined client" pass $file $socket $fd_client "delete_file" +runchecktest "fd passing; unconfined client" pass $file $fd_client $socket "delete_file" sleep 1 cat > ${file} << EOM @@ -106,7 +108,7 @@ rm -f ${socket} # PASS - confined client, rw access to the file genprofile $af_unix $file:$okperm $socket:rw $fd_client:px -- image=$fd_client $af_unix $file:$okperm $socket:rw -runchecktest "fd passing; confined client w/ rw" pass $file $socket $fd_client "delete_file" +runchecktest "fd passing; confined client w/ rw" pass $file $fd_client $socket "delete_file" sleep 1 cat > ${file} << EOM @@ -123,7 +125,7 @@ rm -f ${socket} # FAIL - confined client, w access to the file genprofile $af_unix $file:$okperm $socket:rw $fd_client:px -- image=$fd_client $af_unix $file:$badperm $socket:rw -runchecktest "fd passing; confined client w/ w only" fail $file $socket $fd_client "delete_file" +runchecktest "fd passing; confined client w/ w only" fail $file $fd_client $socket "delete_file" sleep 1 rm -f ${socket} diff --git a/tests/regression/apparmor/exec.c b/tests/regression/apparmor/exec.c index 9bdca5484..0676f3b6e 100644 --- a/tests/regression/apparmor/exec.c +++ b/tests/regression/apparmor/exec.c @@ -42,7 +42,7 @@ extern char **environ; (void)execve(argv[1], &argv[1], environ); - /* exec failed, kill outselves to flag parent */ + /* exec failed, kill ourselves to flag parent */ (void)kill(getpid(), SIGKILL); } diff --git a/tests/regression/apparmor/exec_qual.sh b/tests/regression/apparmor/exec_qual.sh index f64ac5c70..5f80735eb 100755 --- a/tests/regression/apparmor/exec_qual.sh +++ b/tests/regression/apparmor/exec_qual.sh @@ -57,7 +57,7 @@ local_runchecktest() checktestbg - if [ "$teststatus" == "pass" -a -n "$actual_confinement" -a "$actual_confinement" != "$expected_confinement" ] + if [ "$teststatus" = "pass" -a -n "$actual_confinement" -a "$actual_confinement" != "$expected_confinement" ] then echo "Error: ${testname} failed. Test '${_testdesc}' actual confinement '$actual_confinement' differed from expected confinement '$expected_confinement'" testfailed @@ -119,7 +119,7 @@ genprofile $test2:rix signal:receive:peer=unconfined -- image=$test2 $file:$file local_runchecktest "enforce ix case3" fail $test1 $test2 $file # case 4: parent profile grants access -# missing child profile (irrelvant) +# missing child profile (irrelevant) # expected behaviour: child should be able to access resource genprofile $test2:rix $file:$fileperm signal:receive:peer=unconfined @@ -139,7 +139,7 @@ genprofile $test2:ux signal:receive:peer=unconfined local_runchecktest "enforce ux case1" pass "unconfined" $test2 $file # confined parent, exec child with conflicting exec qualifiers -# that overlap in such away that px is prefered (ix is glob, px is exact +# that overlap in such away that px is preferred (ix is glob, px is exact # match). Other overlap tests should be in the parser. # case 1: # expected behaviour: exec of child passes diff --git a/tests/regression/apparmor/exec_stack.sh b/tests/regression/apparmor/exec_stack.sh index 8914ce64f..21f639ab0 100755 --- a/tests/regression/apparmor/exec_stack.sh +++ b/tests/regression/apparmor/exec_stack.sh @@ -43,11 +43,19 @@ stackthirdok="change_profile->:&$thirdtest" touch $file $otherfile $sharedfile $thirdfile -if [ "$(kernel_features domain/fix_binfmt_elf_mmap)" == "true" ]; then - elfmmap="m" -else - elfmmap="" -fi +# We used to do a conditional test (below) for mmap permissions to +# address the change introduced by +# 9f834ec18defc369d73ccf9e87a2790bfa05bf46 but there are too many +# kernels in the wild with a backport/cherrypick of that commit that +# skipped cherry-picking 34c426acb75cc21bdf84685e106db0c1a3565057 +# meaning the below conditional check has the wrong results for those +# kernels. Since this test is not about testing mmap just always add +# the mmap perm +#if [ "$(kernel_features domain/fix_binfmt_elf_mmap)" = "true" ]; then +# elfmmap="m" +#else +# elfmmap="" +#fi # Verify file access and contexts by an unconfined process runchecktest "EXEC_STACK (unconfined - file)" pass -f $file @@ -72,7 +80,7 @@ runchecktest "EXEC_STACK (not stacked - bad mode)" fail -l "$test" -m complain # Verify file access and contexts by 2 stacked profiles genprofile -I $fileok $sharedok $getcon $test:"ix -> &$othertest" -- \ - image=$othertest addimage:$test $otherok $sharedok $getcon $test:r$elfmmap + image=$othertest addimage:$test $otherok $sharedok $getcon $test:rm runchecktest_errno EACCES "EXEC_STACK (2 stacked - file)" fail -- $test -f $file runchecktest_errno EACCES "EXEC_STACK (2 stacked - otherfile)" fail -- $test -f $otherfile runchecktest_errno EACCES "EXEC_STACK (2 stacked - thirdfile)" fail -- $test -f $thirdfile @@ -85,7 +93,7 @@ runchecktest "EXEC_STACK (2 stacked - bad mode)" fail -- $test -l "${test}//&${t # Verify file access and contexts by 3 stacked profiles genprofile -I $fileok $sharedok $getcon $test:"ix -> &$othertest" -- \ image=$othertest addimage:$test $otherok $sharedok $getcon $test:"rix -> &$thirdtest" -- \ - image=$thirdtest addimage:$test $thirdok $sharedok $getcon $test:r$elfmmap + image=$thirdtest addimage:$test $thirdok $sharedok $getcon $test:rm runchecktest_errno EACCES "EXEC_STACK (3 stacked - file)" fail -- $test -- $test -f $file runchecktest_errno EACCES "EXEC_STACK (3 stacked - otherfile)" fail -- $test -- $test -f $otherfile runchecktest_errno EACCES "EXEC_STACK (3 stacked - thirdfile)" fail -- $test -- $test -f $thirdfile @@ -95,7 +103,7 @@ runchecktest "EXEC_STACK (3 stacked - okcon)" pass -- $test -- $test -l "${third genprofile -I $sharedok $stackotherok $stackthirdok $test:"rix -> &$othertest" -- \ image=$othertest addimage:$test $sharedok $stackthirdok $test:"rix -> &$thirdtest" -- \ - image=$thirdtest addimage:$test $sharedok $stackthirdok $test:r$elfmmap + image=$thirdtest addimage:$test $sharedok $stackthirdok $test:rm # Triggered an AppArmor WARN in the initial stacking patch set runchecktest "EXEC_STACK (3 stacked - old AA WARN)" pass -p $othertest -- $test -p $thirdtest -f $sharedfile @@ -126,7 +134,7 @@ runchecktest "EXEC_STACK (stacked with namespaced profile - okcon)" pass -- $tes # Verify file access and contexts in mixed mode genprofile -I $fileok $sharedok $getcon $test:"ix -> &$othertest" -- \ - image=$othertest flag:complain addimage:$test $otherok $sharedok $getcon $test:r$elfmmap + image=$othertest flag:complain addimage:$test $otherok $sharedok $getcon $test:rm runchecktest "EXEC_STACK (mixed mode - file)" pass -- $test -f $file runchecktest_errno EACCES "EXEC_STACK (mixed mode - otherfile)" fail -- $test -f $otherfile runchecktest "EXEC_STACK (mixed mode - sharedfile)" pass -- $test -f $sharedfile diff --git a/tests/regression/apparmor/link_subset.c b/tests/regression/apparmor/link_subset.c index e908487cb..40951650d 100644 --- a/tests/regression/apparmor/link_subset.c +++ b/tests/regression/apparmor/link_subset.c @@ -50,7 +50,7 @@ #define MAX_PERM_LEN 10 -/* Set up permission subset test as a seperate binary to reduce the time +/* Set up permission subset test as a separate binary to reduce the time * as the shell based versions takes for ever */ diff --git a/tests/regression/apparmor/mkprofile.pl b/tests/regression/apparmor/mkprofile.pl index 201d53cb5..5e9132619 100755 --- a/tests/regression/apparmor/mkprofile.pl +++ b/tests/regression/apparmor/mkprofile.pl @@ -37,10 +37,17 @@ sub usage { print STDERR "Usage $0 [--nowarn|--escape] execname [rules]\n"; print STDERR " $0 --help\n"; print STDERR " $0 --stdin\n"; + print STDERR "Options:\n"; print STDERR " nowarn: don't warn if execname does not exist\n"; print STDERR " nodefault: don't include default rules/ldd output\n"; print STDERR " escape: escape stuff that would be treated as regexs\n"; print STDERR " help: print this message\n"; + print STDERR "Rule Qualifiers:\n"; + print STDERR " qualifiers can optionally be added to a rule with 'qual='\n"; + print STDERR " Examples:\n"; + print STDERR " /path/to/file:rw\n"; + print STDERR " qual=audit:/path/to/file:rw\n"; + print STDERR " qual=audit,deny:/path/to/file:rw\n"; } # genprofile passes in $bin:w as default rule atm @@ -146,185 +153,183 @@ sub gen_binary($) { } } -sub gen_netdomain($) { - my $rule = shift; +sub gen_netdomain($@) { + my ($rule, $qualifier) = @_; # only split on single ':'s my @rules = split (/(?<!:):(?!:)/, $rule); # convert '::' to ':' -- for port designations foreach (@rules) { s/::/:/g; } - push (@{$output_rules{$hat}}, " @rules,\n"); + push (@{$output_rules{$hat}}, " ${qualifier}@rules,\n"); } -sub gen_network($) { - my $rule = shift; +sub gen_network($@) { + my ($rule, $qualifier) = @_; my @rules = split (/:/, $rule); - push (@{$output_rules{$hat}}, " @rules,\n"); + push (@{$output_rules{$hat}}, " ${qualifier}@rules,\n"); } -sub gen_unix($) { - my $rule = shift; +sub gen_unix($@) { + my ($rule, $qualifier) = @_; if ($rule =~ /^unix:ALL$/) { push (@{$output_rules{$hat}}, " unix,\n"); } else { $rule =~ s/:/ /g; - push(@{$output_rules{$hat}}, " " . $rule . ",\n"); + push(@{$output_rules{$hat}}, " " . $qualifier . $rule . ",\n"); } } -sub gen_cap($) { - my $rule = shift; +sub gen_cap($@) { + my ($rule, $qualifier) = @_; my @rules = split (/:/, $rule); if (@rules == 2) { if ($rules[1] =~ /^ALL$/) { - push (@{$output_rules{$hat}}, " capability,\n"); - } else { - push (@{$output_rules{$hat}}, " capability $rules[1],\n"); - } - } elsif (@rules == 3) { - if ($rules[1] =~ /^ALL$/) { - push (@{$output_rules{$hat}}, " $rules[2] capability,\n"); + push (@{$output_rules{$hat}}, " ${qualifier}capability,\n"); } else { - push (@{$output_rules{$hat}}, " $rules[2] capability $rules[1],\n"); + push (@{$output_rules{$hat}}, " ${qualifier}capability $rules[1],\n"); } } else { (!$nowarn) && print STDERR "Warning: invalid capability description '$rule', ignored\n"; } } -sub gen_ptrace($) { - my $rule = shift; +sub gen_ptrace($@) { + my ($rule, $qualifier) = @_; my @rules = split (/:/, $rule); if (@rules == 2) { if ($rules[1] =~ /^ALL$/) { - push (@{$output_rules{$hat}}, " ptrace,\n"); + push (@{$output_rules{$hat}}, " ${qualifier}ptrace,\n"); } else { - push (@{$output_rules{$hat}}, " ptrace $rules[1],\n"); + push (@{$output_rules{$hat}}, " ${qualifier}ptrace $rules[1],\n"); } } elsif (@rules == 3) { - push (@{$output_rules{$hat}}, " ptrace $rules[1] $rules[2],\n"); + push (@{$output_rules{$hat}}, " ${qualifier}ptrace $rules[1] $rules[2],\n"); } else { (!$nowarn) && print STDERR "Warning: invalid ptrace description '$rule', ignored\n"; } } -sub gen_signal($) { - my $rule = shift; +sub gen_signal($@) { + my ($rule, $qualifier) = @_; my @rules = split (/:/, $rule); if (@rules == 2) { if ($rules[1] =~ /^ALL$/) { - push (@{$output_rules{$hat}}, " signal,\n"); + push (@{$output_rules{$hat}}, " ${qualifier}signal,\n"); } else { - push (@{$output_rules{$hat}}, " signal $rules[1],\n"); + push (@{$output_rules{$hat}}, " ${qualifier}signal $rules[1],\n"); } } elsif (@rules == 3) { - push (@{$output_rules{$hat}}, " signal $rules[1] $rules[2],\n"); + push (@{$output_rules{$hat}}, " ${qualifier}signal $rules[1] $rules[2],\n"); } else { (!$nowarn) && print STDERR "Warning: invalid signal description '$rule', ignored\n"; } } -sub gen_mount($) { - my $rule = shift; +sub gen_mount($@) { + my ($rule, $qualifier) = @_; my @rules = split (/:/, $rule); if (@rules == 2) { if ($rules[1] =~ /^ALL$/) { - push (@{$output_rules{$hat}}, " mount,\n"); + push (@{$output_rules{$hat}}, " ${qualifier}mount,\n"); } else { - push (@{$output_rules{$hat}}, " mount $rules[1],\n"); + push (@{$output_rules{$hat}}, " ${qualifier}mount $rules[1],\n"); } } elsif (@rules == 3) { - push (@{$output_rules{$hat}}, " mount $rules[1] $rules[2],\n"); + push (@{$output_rules{$hat}}, " ${qualifier}mount $rules[1] $rules[2],\n"); } elsif (@rules == 4) { - push (@{$output_rules{$hat}}, " mount $rules[1] $rules[2] $rules[3],\n"); + push (@{$output_rules{$hat}}, " ${qualifier}mount $rules[1] $rules[2] $rules[3],\n"); } elsif (@rules == 5) { - push (@{$output_rules{$hat}}, " mount $rules[1] $rules[2] $rules[3] $rules[4],\n"); + push (@{$output_rules{$hat}}, " ${qualifier}mount $rules[1] $rules[2] $rules[3] $rules[4],\n"); } elsif (@rules == 6) { - push (@{$output_rules{$hat}}, " mount $rules[1] $rules[2] $rules[3] $rules[4] $rules[5],\n"); + push (@{$output_rules{$hat}}, " ${qualifier}mount $rules[1] $rules[2] $rules[3] $rules[4] $rules[5],\n"); } elsif (@rules == 7) { - push (@{$output_rules{$hat}}, " mount $rules[1] $rules[2] $rules[3] $rules[4] $rules[5] $rules[6],\n"); + push (@{$output_rules{$hat}}, " ${qualifier}mount $rules[1] $rules[2] $rules[3] $rules[4] $rules[5] $rules[6],\n"); } else { (!$nowarn) && print STDERR "Warning: invalid mount description '$rule', ignored\n"; } } -sub gen_remount($) { - my $rule = shift; +sub gen_remount($@) { + my ($rule, $qualifier) = @_; my @rules = split (/:/, $rule); if (@rules == 2) { if ($rules[1] =~ /^ALL$/) { - push (@{$output_rules{$hat}}, " remount,\n"); + push (@{$output_rules{$hat}}, " ${qualifier}remount,\n"); } else { - push (@{$output_rules{$hat}}, " remount $rules[1],\n"); + push (@{$output_rules{$hat}}, " ${qualifier}remount $rules[1],\n"); } } elsif (@rules == 3) { - push (@{$output_rules{$hat}}, " remount $rules[1] $rules[2],\n"); + push (@{$output_rules{$hat}}, " ${qualifier}remount $rules[1] $rules[2],\n"); } elsif (@rules == 4) { - push (@{$output_rules{$hat}}, " remount $rules[1] $rules[2] $rules[3],\n"); + push (@{$output_rules{$hat}}, " ${qualifier}remount $rules[1] $rules[2] $rules[3],\n"); } elsif (@rules == 5) { - push (@{$output_rules{$hat}}, " remount $rules[1] $rules[2] $rules[3] $rules[4],\n"); + push (@{$output_rules{$hat}}, " ${qualifier}remount $rules[1] $rules[2] $rules[3] $rules[4],\n"); } elsif (@rules == 6) { - push (@{$output_rules{$hat}}, " remount $rules[1] $rules[2] $rules[3] $rules[4] $rules[5],\n"); + push (@{$output_rules{$hat}}, " ${qualifier}remount $rules[1] $rules[2] $rules[3] $rules[4] $rules[5],\n"); } elsif (@rules == 7) { - push (@{$output_rules{$hat}}, " remount $rules[1] $rules[2] $rules[3] $rules[4] $rules[5] $rules[6],\n"); + push (@{$output_rules{$hat}}, " ${qualifier}remount $rules[1] $rules[2] $rules[3] $rules[4] $rules[5] $rules[6],\n"); } else { (!$nowarn) && print STDERR "Warning: invalid remount description '$rule', ignored\n"; } } -sub gen_umount($) { - my $rule = shift; +sub gen_umount($@) { + my ($rule, $qualifier) = @_; my @rules = split (/:/, $rule); if (@rules == 2) { if ($rules[1] =~ /^ALL$/) { - push (@{$output_rules{$hat}}, " umount,\n"); + push (@{$output_rules{$hat}}, " ${qualifier}umount,\n"); } else { - push (@{$output_rules{$hat}}, " umount $rules[1],\n"); + push (@{$output_rules{$hat}}, " ${qualifier}umount $rules[1],\n"); } } elsif (@rules == 3) { - push (@{$output_rules{$hat}}, " umount $rules[1] $rules[2],\n"); + push (@{$output_rules{$hat}}, " ${qualifier}umount $rules[1] $rules[2],\n"); } elsif (@rules == 4) { - push (@{$output_rules{$hat}}, " umount $rules[1] $rules[2] $rules[3],\n"); + push (@{$output_rules{$hat}}, " ${qualifier}umount $rules[1] $rules[2] $rules[3],\n"); } elsif (@rules == 5) { - push (@{$output_rules{$hat}}, " umount $rules[1] $rules[2] $rules[3] $rules[4],\n"); + push (@{$output_rules{$hat}}, " ${qualifier}umount $rules[1] $rules[2] $rules[3] $rules[4],\n"); } elsif (@rules == 6) { - push (@{$output_rules{$hat}}, " umount $rules[1] $rules[2] $rules[3] $rules[4] $rules[5],\n"); + push (@{$output_rules{$hat}}, " ${qualifier}umount $rules[1] $rules[2] $rules[3] $rules[4] $rules[5],\n"); } elsif (@rules == 7) { - push (@{$output_rules{$hat}}, " umount $rules[1] $rules[2] $rules[3] $rules[4] $rules[5] $rules[6],\n"); + push (@{$output_rules{$hat}}, " ${qualifier}umount $rules[1] $rules[2] $rules[3] $rules[4] $rules[5] $rules[6],\n"); } else { (!$nowarn) && print STDERR "Warning: invalid umount description '$rule', ignored\n"; } } -sub gen_pivot_root($) { - my $rule = shift; +sub gen_pivot_root($@) { + my ($rule, $qualifier) = @_; my @rules = split (/:/, $rule); if (@rules == 2) { if ($rules[1] =~ /^ALL$/) { - push (@{$output_rules{$hat}}, " pivot_root,\n"); + push (@{$output_rules{$hat}}, " ${qualifier}pivot_root,\n"); } else { - push (@{$output_rules{$hat}}, " pivot_root $rules[1],\n"); + push (@{$output_rules{$hat}}, " ${qualifier}pivot_root $rules[1],\n"); } } else { (!$nowarn) && print STDERR "Warning: invalid pivot_root description '$rule', ignored\n"; } } -sub gen_file($) { - my $rule = shift; +sub gen_file($@) { + my ($rule, $qualifier) = @_; + if (!$qualifier) { + $qualifier = ""; + } + my @rules = split (/:/, $rule); # default: file rules if (@rules == 1) { # support raw rules - push (@{$output_rules{$hat}}, " $rules[0],\n"); + push (@{$output_rules{$hat}}, " ${qualifier}$rules[0],\n"); } elsif (@rules == 2) { if ($escape) { $rules[0]=~ s/(["[\]{}\:])/\\$1/g; $rules[0]=~ s/(\#)/\\043/g; } if ($rules[0]=~ /[\s\!\"\^]/) { - push (@{$output_rules{$hat}}, " \"$rules[0]\" $rules[1],\n"); + push (@{$output_rules{$hat}}, " ${qualifier}\"$rules[0]\" $rules[1],\n"); } else { - push (@{$output_rules{$hat}}, " $rules[0] $rules[1],\n"); + push (@{$output_rules{$hat}}, " ${qualifier}$rules[0] $rules[1],\n"); } } else { (!$nowarn) && print STDERR "Warning: invalid file access '$rule', ignored\n"; @@ -341,34 +346,34 @@ sub gen_flag($) { } } -sub gen_change_profile($) { - my $rule = shift; +sub gen_change_profile($@) { + my ($rule, $qualifier) = @_; my @rules = split (/:/, $rule); if (@rules == 2) { if ($rules[1] =~ /^ALL$/) { - push (@{$output_rules{$hat}}, " change_profile,\n",); + push (@{$output_rules{$hat}}, " ${qualifier}change_profile,\n",); } else { - push (@{$output_rules{$hat}}, " change_profile -> $rules[1],\n",); + push (@{$output_rules{$hat}}, " ${qualifier}change_profile -> $rules[1],\n",); } } elsif (@rules == 3) { - push (@{$output_rules{$hat}}, " change_profile $rules[1] -> $rules[2],\n",); + push (@{$output_rules{$hat}}, " ${qualifier}change_profile $rules[1] -> $rules[2],\n",); } elsif (@rules == 4) { - push (@{$output_rules{$hat}}, " change_profile $rules[1] $rules[2] -> $rules[3],\n",); + push (@{$output_rules{$hat}}, " ${qualifier}change_profile $rules[1] $rules[2] -> $rules[3],\n",); } else { (!$nowarn) && print STDERR "Warning: invalid change_profile description '$rule', ignored\n"; } } -sub gen_hat($) { - my $rule = shift; +sub gen_hat($@) { + my ($rule, $qualifier) = @_; my @rules = split (/:/, $rule); if (@rules != 2) { (!$nowarn) && print STDERR "Warning: invalid hat description '$rule', ignored\n"; } else { $hat = $rules[1]; # give every profile/hat access to change_hat - @{$output_rules{$hat}} = ( " /proc/*/attr/current w,\n",); - push(@{$output_rules{$hat}}, " /proc/*/attr/apparmor/current w,\n"); + @{$output_rules{$hat}} = ( " ${qualifier}/proc/*/attr/current w,\n",); + push(@{$output_rules{$hat}}, " ${qualifier}/proc/*/attr/apparmor/current w,\n"); } } @@ -428,34 +433,42 @@ sub gen_from_args() { } for my $rule (@ARGV) { + my $qualifier = ""; + if ($rule =~ /^qual=([^:]*):(.*)/) { + # Strip qualifiers from rule to pass as separate argument + $qualifier = "$1 "; + $rule = $2; + $qualifier =~ s/,/ /g; + } + #($fn, @rules) = split (/:/, $rule); if ($rule =~ /^(tcp|udp)/) { # netdomain rules - gen_netdomain($rule); + gen_netdomain($rule, $qualifier); } elsif ($rule =~ /^network:/) { - gen_network($rule); + gen_network($rule, $qualifier); } elsif ($rule =~ /^unix:/) { - gen_unix($rule); + gen_unix($rule, $qualifier); } elsif ($rule =~ /^cap:/) { - gen_cap($rule); + gen_cap($rule, $qualifier); } elsif ($rule =~ /^ptrace:/) { - gen_ptrace($rule); + gen_ptrace($rule, $qualifier); } elsif ($rule =~ /^signal:/) { - gen_signal($rule); + gen_signal($rule, $qualifier); } elsif ($rule =~ /^mount:/) { - gen_mount($rule); + gen_mount($rule, $qualifier); } elsif ($rule =~ /^remount:/) { - gen_remount($rule); + gen_remount($rule, $qualifier); } elsif ($rule =~ /^umount:/) { - gen_umount($rule); + gen_umount($rule, $qualifier); } elsif ($rule =~ /^pivot_root:/) { - gen_pivot_root($rule); + gen_pivot_root($rule, $qualifier); } elsif ($rule =~ /^flag:/) { gen_flag($rule); } elsif ($rule =~ /^hat:/) { - gen_hat($rule); + gen_hat($rule, $qualifier); } elsif ($rule =~ /^change_profile:/) { - gen_change_profile($rule); + gen_change_profile($rule, $qualifier); } elsif ($rule =~ /^addimage:/) { gen_addimage($rule); $addimage = 1; @@ -464,7 +477,7 @@ sub gen_from_args() { } elsif ($rule =~ /^path:/) { gen_path($rule); } else { - gen_file($rule); + gen_file($rule, $qualifier); } } diff --git a/tests/regression/apparmor/mount.c b/tests/regression/apparmor/mount.c index a250aa8b2..73e771eec 100644 --- a/tests/regression/apparmor/mount.c +++ b/tests/regression/apparmor/mount.c @@ -14,27 +14,163 @@ #include <sys/stat.h> #include <sys/mount.h> #include <string.h> +#include <stdlib.h> + +struct mnt_keyword_table { + const char *keyword; + unsigned long set; + unsigned long clear; +}; + +static struct mnt_keyword_table mnt_opts_table[] = { + { "rw", 0, MS_RDONLY }, /* read-write */ + { "ro", MS_RDONLY, 0 }, /* read-only */ + + { "exec", 0, MS_NOEXEC }, /* permit execution of binaries */ + { "noexec", MS_NOEXEC, 0 }, /* don't execute binaries */ + + { "suid", 0, MS_NOSUID }, /* honor suid executables */ + { "nosuid", MS_NOSUID, 0 }, /* don't honor suid executables */ + + { "dev", 0, MS_NODEV }, /* interpret device files */ + { "nodev", MS_NODEV, 0 }, /* don't interpret devices */ + + { "async", 0, MS_SYNCHRONOUS }, /* asynchronous I/O */ + { "sync", MS_SYNCHRONOUS, 0 }, /* synchronous I/O */ + + { "loud", 0, MS_SILENT }, /* print out messages. */ + { "silent", MS_SILENT, 0 }, /* be quiet */ + + { "nomand", 0, MS_MANDLOCK }, /* forbid mandatory locks on this FS */ + { "mand", MS_MANDLOCK, 0 }, /* allow mandatory locks on this FS */ + + { "atime", 0, MS_NOATIME }, /* update access time */ + { "noatime", MS_NOATIME, 0 }, /* do not update access time */ + + { "noiversion", 0, MS_I_VERSION }, /* don't update inode I_version time */ + { "iversion", MS_I_VERSION, 0 }, /* update inode I_version time */ + + { "diratime", 0, MS_NODIRATIME }, /* update dir access times */ + { "nodiratime", MS_NODIRATIME, 0 }, /* do not update dir access times */ + + { "nostrictatime", 0, MS_STRICTATIME }, /* kernel default atime */ + { "strictatime", MS_STRICTATIME, 0 }, /* strict atime semantics */ + +/* MS_LAZYTIME added in 4.0 kernel */ +#ifdef MS_LAZYTIME + { "nolazytime", 0, MS_LAZYTIME }, + { "lazytime", MS_LAZYTIME, 0 }, /* update {a,m,c}time on the in-memory inode only */ +#endif + + { "acl", MS_POSIXACL, 0 }, + { "noacl", 0, MS_POSIXACL }, + + { "norelatime", 0, MS_RELATIME }, + { "relatime", MS_RELATIME, 0 }, + + { "dirsync", MS_DIRSYNC, 0 }, /* synchronous directory modifications */ + { "nodirsync", 0, MS_DIRSYNC }, + +/* MS_NOSYMFOLLOW added in 5.10 kernel */ +#ifdef MS_NOSYMFOLLOW + { "nosymfollow", MS_NOSYMFOLLOW, 0 }, + { "symfollow", 0, MS_NOSYMFOLLOW }, +#endif + + { "bind", MS_BIND, 0 }, /* remount part of the tree elsewhere */ + { "rbind", MS_BIND | MS_REC, 0 }, /* idem, plus mounted subtrees */ + { "unbindable", MS_UNBINDABLE, 0 }, /* unbindable */ + { "runbindable", MS_UNBINDABLE | MS_REC, 0 }, + { "private", MS_PRIVATE, 0 }, /* private */ + { "rprivate", MS_PRIVATE | MS_REC, 0 }, + { "slave", MS_SLAVE, 0 }, /* slave */ + { "rslave", MS_SLAVE | MS_REC, 0 }, + { "shared", MS_SHARED, 0 }, /* shared */ + { "rshared", MS_SHARED | MS_REC, 0 }, + + { "move", MS_MOVE, 0 }, + + { "remount", MS_REMOUNT, 0 }, +}; + +const unsigned int mnt_opts_table_size = + sizeof(mnt_opts_table) / sizeof(struct mnt_keyword_table); + + +unsigned long get_mnt_opt_bit(char *key) +{ + for (unsigned int i = 0; i < mnt_opts_table_size; i++) { + if (strcmp(mnt_opts_table[i].keyword, key) == 0) { + return mnt_opts_table[i].set; + } + } + fprintf(stderr, "FAIL: invalid option\n"); + exit(1); +} + +static void usage(char *prog_name) +{ + fprintf(stderr, "Usage: %s mount|umount <source> <target> [options]\n", prog_name); + fprintf(stderr, "Options are:\n"); + fprintf(stderr, "-o flags sent to the mount syscall\n"); + fprintf(stderr, "-d data sent to the mount syscall\n"); + exit(1); +} int main(int argc, char *argv[]) { - if (argc != 4) { - fprintf(stderr, "usage: %s [mount|umount] loopdev mountpoint\n", - argv[0]); - return 1; + char *options = NULL; + char *data = NULL; + int index; + int c; + char *op, *source, *target, *token; + unsigned long flags = 0; + + while ((c = getopt (argc, argv, "o:d:h")) != -1) { + switch (c) + { + case 'o': + options = optarg; + break; + case 'd': + data = optarg; + break; + case 'h': + usage(argv[0]); + break; + default: + break; + } + } + + index = optind; + if (argc - optind < 3) { + fprintf(stderr, "FAIL: missing positional arguments\n"); + usage(argv[0]); + } + + op = argv[index++]; + source = argv[index++]; + target = argv[index++]; + + if (options) { + token = strtok(options, ","); + while (token) { + flags |= get_mnt_opt_bit(token); + token = strtok(NULL, ","); + } } - if (strcmp(argv[1], "mount") == 0) { - if (mount(argv[2], argv[3], "ext2", 0xc0ed0000 | MS_NODEV, NULL ) == -1) { + if (strcmp(op, "mount") == 0) { + if (mount(source, target, "ext2", flags, data) == -1) { fprintf(stderr, "FAIL: mount %s on %s failed - %s\n", - argv[2], argv[3], - strerror(errno)); + source, target, strerror(errno)); return errno; } - } else if (strcmp(argv[1], "umount") == 0) { - if (umount(argv[3]) == -1) { + } else if (strcmp(op, "umount") == 0) { + if (umount(target) == -1) { fprintf(stderr, "FAIL: umount %s failed - %s\n", - argv[3], - strerror(errno)); + target, strerror(errno)); return errno; } } else { diff --git a/tests/regression/apparmor/mount.sh b/tests/regression/apparmor/mount.sh index bfd2905b5..eef8e57dc 100755 --- a/tests/regression/apparmor/mount.sh +++ b/tests/regression/apparmor/mount.sh @@ -12,7 +12,7 @@ # processes. #=END -# I made this a seperate test script because of the need to make a +# I made this a separate test script because of the need to make a # loopfile before the tests run. pwd=`dirname $0` @@ -28,9 +28,11 @@ bin=$pwd mount_file=$tmpdir/mountfile mount_point=$tmpdir/mountpoint +mount_point2=$tmpdir/mountpoint2 mount_bad=$tmpdir/mountbad loop_device="unset" fstype="ext2" +root_was_shared="no" setup_mnt() { /bin/mount -n -t${fstype} ${loop_device} ${mount_point} @@ -41,6 +43,10 @@ remove_mnt() { if [ $? -eq 0 ] ; then /bin/umount -t${fstype} ${mount_point} fi + mountpoint -q "${mount_point2}" + if [ $? -eq 0 ] ; then + /bin/umount -t${fstype} ${mount_point2} + fi mountpoint -q "${mount_bad}" if [ $? -eq 0 ] ; then /bin/umount -t${fstype} ${mount_bad} @@ -53,12 +59,16 @@ mount_cleanup() { then /sbin/losetup -d ${loop_device} &> /dev/null fi + if [ "${root_was_shared}" = "yes" ] ; then + mount --make-shared / + fi } do_onexit="mount_cleanup" dd if=/dev/zero of=${mount_file} bs=1024 count=512 2> /dev/null /sbin/mkfs -t${fstype} -F ${mount_file} > /dev/null 2> /dev/null /bin/mkdir ${mount_point} +/bin/mkdir ${mount_point2} /bin/mkdir ${mount_bad} # in a modular udev world, the devices won't exist until the loopback @@ -71,6 +81,199 @@ fi loop_device=$(losetup -f) || fatalerror 'Unable to find a free loop device' /sbin/losetup "$loop_device" ${mount_file} > /dev/null 2> /dev/null +# systemd mounts / and everything under it MS_SHARED which does +# not work with "move", so attempt to detect it, and remount / +# MS_PRIVATE temporarily. snippet from pivot_root.sh +FINDMNT=/bin/findmnt +if [ -x "${FINDMNT}" ] && ${FINDMNT} -no PROPAGATION / > /dev/null 2>&1 ; then + if [ "$(${FINDMNT} -no PROPAGATION /)" == "shared" ] ; then + root_was_shared="yes" + fi +elif [ "$(ps hp1 -ocomm)" = "systemd" ] ; then + # no findmnt or findmnt doesn't know the PROPAGATION column, + # but init is systemd so assume rootfs is shared + root_was_shared="yes" +fi +if [ "${root_was_shared}" = "yes" ] ; then + mount --make-private / +fi + +options=( + # default and non-default options + "rw,ro" + "exec,noexec" + "suid,nosuid" + "dev,nodev" + "async,sync" + "loud,silent" + "nomand,mand" + "atime,noatime" + "noiversion,iversion" + "diratime,nodiratime" + "nostrictatime,strictatime" + "norelatime,relatime" + "nodirsync,dirsync" + "noacl,acl" +) + +# Options added in newer kernels +new_options=( + "nolazytime,lazytime" + "symfollow,nosymfollow" +) + +prop_options=( + "unbindable" + "runbindable" + "private" + "rprivate" + "slave" + "rslave" + "shared" + "rshared" +) + +combinations=() + +setup_all_combinations() { + n=${#options[@]} + for (( i = 1; i < (1 << n); i++ )); do + list=() + for (( j = 0; j < n; j++ )); do + if (( (1 << j) & i )); then + current_options="${options[j]}" + nondefault=${current_options#*,} + list+=("$nondefault") + fi + done + combination=$(IFS=,; printf "%s" "${list[*]}") + combinations+=($combination) + done +} + +run_all_combinations_test() { + for combination in "${combinations[@]}"; do + if [ "$(parser_supports "mount options=($combination),")" = "true" ] ; then + genprofile cap:sys_admin "mount:options=($combination)" + runchecktest "MOUNT (confined cap mount combination pass test $combination)" pass mount ${loop_device} ${mount_point} -o $combination + remove_mnt + + genprofile cap:sys_admin "mount:ALL" "qual=deny:mount:options=($combination)" + runchecktest "MOUNT (confined cap mount combination deny test $combination)" fail mount ${loop_device} ${mount_point} -o $combination + remove_mnt + else + echo " not supported by parser - skipping mount option=($combination)," + fi + + genprofile cap:sys_admin "mount:options=(rw)" + runchecktest "MOUNT (confined cap mount combination fail test $combination)" fail mount ${loop_device} ${mount_point} -o $combination + remove_mnt + done +} + +test_nonfs_options() { + if [ "$(parser_supports "mount options=($1),")" != "true" ] ; then + echo " not supported by parser - skipping mount options=($1)," + return + fi + + genprofile cap:sys_admin "mount:options=($1)" + runchecktest "MOUNT (confined cap mount $1)" pass mount ${loop_device} ${mount_point} -o $1 + remove_mnt + + genprofile cap:sys_admin "mount:ALL" "qual=deny:mount:options=($1)" + runchecktest "MOUNT (confined cap mount deny $1)" fail mount ${loop_device} ${mount_point} -o $1 + remove_mnt + + genprofile cap:sys_admin "mount:options=($1)" + runchecktest "MOUNT (confined cap mount bad option $2)" fail mount ${loop_device} ${mount_point} -o $2 + remove_mnt +} + +test_dir_options() { + if [ "$(parser_supports "mount options=($1),")" != "true" ] ; then + echo " not supported by parser - skipping mount option=($1)," + return + fi + + genprofile cap:sys_admin "mount:ALL" + runchecktest "MOUNT (confined cap mount dir setup $1)" pass mount ${loop_device} ${mount_point} + genprofile cap:sys_admin "mount:options=($1)" + runchecktest "MOUNT (confined cap mount dir $1)" pass mount ${mount_point} ${mount_point2} -o $1 + remove_mnt + + genprofile cap:sys_admin "mount:ALL" "qual=deny:mount:options=($1)" + runchecktest "MOUNT (confined cap mount dir setup 2 $1)" pass mount ${loop_device} ${mount_point} + runchecktest "MOUNT (confined cap mount dir deny $1)" fail mount ${mount_point} ${mount_point2} -o $1 + remove_mnt +} + +test_propagation_options() { + if [ "$(parser_supports "mount options=($1),")" != "true" ] ; then + echo " not supported by parser - skipping mount option=($1)," + return + fi + + genprofile cap:sys_admin "mount:ALL" + runchecktest "MOUNT (confined cap mount propagation setup $1)" pass mount ${loop_device} ${mount_point} + genprofile cap:sys_admin "mount:options=($1)" + runchecktest "MOUNT (confined cap mount propagation $1)" pass mount none ${mount_point} -o $1 + genprofile cap:sys_admin "mount:options=($1):-> ${mount_point}/" + runchecktest "MOUNT (confined cap mount propagation $1 mountpoint)" pass mount none ${mount_point} -o $1 + genprofile cap:sys_admin "mount:options=($1):${mount_point}/" + runchecktest "MOUNT (confined cap mount propagation $1 source as mountpoint - deprecated)" pass mount none ${mount_point} -o $1 + remove_mnt + + genprofile cap:sys_admin "mount:ALL" "qual=deny:mount:options=($1)" + runchecktest "MOUNT (confined cap mount propagation deny setup 2 $1)" pass mount ${loop_device} ${mount_point} + runchecktest "MOUNT (confined cap mount propagation deny $1)" fail mount none ${mount_point} -o $1 + remove_mnt +} + +test_remount() { + # setup by mounting first + genprofile cap:sys_admin "mount:ALL" + runchecktest "MOUNT (confined cap mount remount setup)" pass mount ${loop_device} ${mount_point} + + genprofile cap:sys_admin "mount:options=(remount)" + runchecktest "MOUNT (confined cap mount remount option)" pass mount ${loop_device} ${mount_point} -o remount + + genprofile cap:sys_admin "remount:ALL" + runchecktest "MOUNT (confined cap mount remount)" pass mount ${loop_device} ${mount_point} -o remount + + genprofile cap:sys_admin "mount:ALL" "qual=deny:mount:options=(remount)" + runchecktest "MOUNT (confined cap mount remount deny option)" fail mount ${loop_device} ${mount_point} -o remount + + genprofile cap:sys_admin "qual=deny:remount:ALL" + runchecktest "MOUNT (confined cap mount remount deny)" fail mount ${loop_device} ${mount_point} -o remount + + # TODO: add test for remount options + remove_mnt +} + +test_options() { + for i in "${options[@]}"; do + default="${i%,*}" + nondefault="${i#*,}" + + test_nonfs_options $default $nondefault + test_nonfs_options $nondefault $default + done + + for i in "bind" "rbind" "move"; do + test_dir_options $i + done + + for i in "${prop_options[@]}"; do + test_propagation_options $i + done + + test_remount + + # the following combinations tests take a long time to complete + # setup_all_combinations + # run_all_combinations_test +} # TEST 1. Make sure can mount and umount unconfined runchecktest "MOUNT (unconfined)" pass mount ${loop_device} ${mount_point} @@ -80,6 +283,43 @@ setup_mnt runchecktest "UMOUNT (unconfined)" pass umount ${loop_device} ${mount_point} remove_mnt +# Check mount options that may not be available on this kernel +for i in "${new_options[@]}"; do + default="${i%,*}" + if "$bin/mount" mount ${loop_device} ${mount_point} -o $default > /dev/null 2>&1; then + remove_mnt + options+=($i) + else + echo " not supported by kernel - skipping mount options=($i)," + fi +done + +for i in "${options[@]}"; do + default="${i%,*}" + nondefault="${i#*,}" + + runchecktest "MOUNT (unconfined mount $default)" pass mount ${loop_device} ${mount_point} -o $default + remove_mnt + runchecktest "MOUNT (unconfined mount $nondefault)" pass mount ${loop_device} ${mount_point} -o $nondefault + remove_mnt +done + +for i in "bind" "rbind" "move"; do + runchecktest "MOUNT (unconfined mount setup $i)" pass mount ${loop_device} ${mount_point} + runchecktest "MOUNT (unconfined mount $i)" pass mount ${mount_point} ${mount_point2} -o $i + remove_mnt +done + +for i in "${prop_options[@]}"; do + runchecktest "MOUNT (unconfined mount dir setup $i)" pass mount ${loop_device} ${mount_point} + runchecktest "MOUNT (unconfined mount dir $i)" pass mount none ${mount_point} -o $i + remove_mnt +done + +runchecktest "MOUNT (unconfined mount remount setup)" pass mount ${loop_device} ${mount_point} +runchecktest "MOUNT (unconfined mount remount)" pass mount ${loop_device} ${mount_point} -o remount +remove_mnt + # TEST A2. confine MOUNT no perms genprofile runchecktest "MOUNT (confined no perm)" fail mount ${loop_device} ${mount_point} @@ -91,6 +331,7 @@ remove_mnt if [ "$(kernel_features mount)" != "true" -o "$(parser_supports 'mount,')" != "true" ] ; then + echo " mount rules not supported, using capability check ..." genprofile capability:sys_admin runchecktest "MOUNT (confined cap)" pass mount ${loop_device} ${mount_point} remove_mnt @@ -157,6 +398,17 @@ else runchecktest "UMOUNT (confined cap umount:ALL)" pass umount ${loop_device} ${mount_point} remove_mnt + # MR:https://gitlab.com/apparmor/apparmor/-/merge_requests/1054 + # https://bugs.launchpad.net/apparmor/+bug/2023814 + # https://bugzilla.opensuse.org/show_bug.cgi?id=1211989 + # based on rules from profile in bug that triggered issue + genprofile cap:sys_admin "qual=deny:mount:/snap/bin/:-> /**" \ + "mount:options=(rw,bind):-> ${mount_point}/" + + runchecktest "MOUNT (confined cap bind mount with deny mount that doesn't overlap)" pass mount ${mount_point2} ${mount_point} -o bind + remove_mnt + + test_options fi -#need tests for move mount, remount, bind mount, chroot +#need tests for chroot diff --git a/tests/regression/apparmor/named_pipe.sh b/tests/regression/apparmor/named_pipe.sh index 72bc7361f..ded5f1990 100755 --- a/tests/regression/apparmor/named_pipe.sh +++ b/tests/regression/apparmor/named_pipe.sh @@ -10,7 +10,7 @@ #=DESCRIPTION # This test verifies that subdomain file access checks function correctly # for named piped (nodes in the filesystem created with mknod). The test -# creates a parent/child process relationship which attempt to rendevous via +# creates a parent/child process relationship which attempt to rendezvous via # the named pipe. The tests are attempted for unconfined and confined # processes and also for subhats. #=END @@ -38,7 +38,7 @@ badchild=r # Add genprofile params that are common to all hats here common="" -if [ "$(kernel_features signal)" == "true" -a "$(parser_supports 'signal,')" == "true" ] ; then +if [ "$(kernel_features signal)" = "true" -a "$(parser_supports 'signal,')" = "true" ] ; then # Allow send/receive of all signals common="${common} signal:ALL" fi diff --git a/tests/regression/apparmor/namespaces.sh b/tests/regression/apparmor/namespaces.sh index a82ae01c6..9cd2bc3dd 100755 --- a/tests/regression/apparmor/namespaces.sh +++ b/tests/regression/apparmor/namespaces.sh @@ -58,8 +58,16 @@ genprofile_ns_and_verify() { [ -d /sys/kernel/security/apparmor/policy/namespaces/${ns} ] local dir_created=$? - [ -d /sys/kernel/security/apparmor/policy/namespaces/${ns}/profiles/${prof}* ] - local prof_created=$? + local prof_created=1 + for d in /sys/kernel/security/apparmor/policy/namespaces/${ns}/profiles/${prof}*; do + if [ -d "$d" ]; then + prof_created=0 + fi + # Either $d exists and we've set prof_created to 0. Or it does + # not, because its value is the unexpanded pattern above. + # Either way, this is all we needed to know. + break + done removeprofile if [ $dir_created -ne 0 ]; then echo "Error: ${testname} failed. Test '${desc}' did not create the expected namespace directory in apparmorfs: policy/namespaces/${ns}" diff --git a/tests/regression/apparmor/onexec.sh b/tests/regression/apparmor/onexec.sh index 922ea2550..a1223b317 100644 --- a/tests/regression/apparmor/onexec.sh +++ b/tests/regression/apparmor/onexec.sh @@ -44,7 +44,7 @@ do_test() shift 4 desc="ONEXEC $desc ($prof -> $target_prof)" - if [ "$target_prof" == "nochange" ] ; then + if [ "$target_prof" = "nochange" ] ; then runchecktest "$desc" $res -l "$prof" -- "$@" else runchecktest "$desc" $res -O "$target_prof" -l "$prof" -L "$target_prof" -- "$@" diff --git a/tests/regression/apparmor/pivot_root.sh b/tests/regression/apparmor/pivot_root.sh index 960216cf0..dd7104edc 100755 --- a/tests/regression/apparmor/pivot_root.sh +++ b/tests/regression/apparmor/pivot_root.sh @@ -45,12 +45,17 @@ pivot_root_cleanup() { } do_onexit="pivot_root_cleanup" +# loopback module must be loaded for mount -o loop to work +if [ ! -b /dev/loop0 ] ; then + modprobe loop +fi + # systemd mounts / and everything under it MS_SHARED. This breaks # pivot_root entirely, so attempt to detect it, and remount / # MS_PRIVATE temporarily. FINDMNT=/bin/findmnt if [ -x "${FINDMNT}" ] && ${FINDMNT} -no PROPAGATION / > /dev/null 2>&1 ; then - if [ "$(${FINDMNT} -no PROPAGATION /)" == "shared" ] ; then + if [ "$(${FINDMNT} -no PROPAGATION /)" = "shared" ] ; then root_was_shared="yes" fi elif [ "$(ps hp1 -ocomm)" = "systemd" ] ; then diff --git a/tests/regression/apparmor/prologue.inc b/tests/regression/apparmor/prologue.inc index c3ed1ad62..1135b9c10 100755 --- a/tests/regression/apparmor/prologue.inc +++ b/tests/regression/apparmor/prologue.inc @@ -11,17 +11,17 @@ # # This file should be included by each test case # It does a lot of hidden 'magic', Downside is that -# this magic makes debugging fauling tests more difficult. +# this magic makes debugging failing tests more difficult. # Running the test with the '-r' option can help. # -# Userchangeable variables (tmpdir etc) should be specified in +# User changeable variables (tmpdir etc) should be specified in # uservars.inc # # Cleanup is automatically performed by epilogue.inc # # For this file, functions are first, entry point code is at end, see "MAIN" -#use $() to retreive the failure message or "true" if success +#use $() to retrieve the failure message or "true" if success # kernel_features_istrue() - test whether boolean files are true # $@: path(s) to test if true @@ -86,6 +86,19 @@ requires_kernel_features() fi } +requires_any_of_kernel_features() +{ + while [ $# -gt 0 ]; do + local res=$(kernel_features "$1") + if [ "$res" = "true" ] ; then + return 0; + fi + shift + done + echo "$res. Skipping tests ..." + exit 0 +} + # requires_namespace_interface() - exit if namespace interface is not available requires_namespace_interface() { diff --git a/tests/regression/apparmor/ptrace.sh b/tests/regression/apparmor/ptrace.sh index 320d65e81..ab025c846 100755 --- a/tests/regression/apparmor/ptrace.sh +++ b/tests/regression/apparmor/ptrace.sh @@ -55,7 +55,7 @@ runchecktest "test 2 -h prog" pass -h -n 100 $helper ${bin_true} runchecktest "test 2 -hc prog" pass -h -c -n 100 $helper ${bin_true} -if [ "$(kernel_features ptrace)" == "true" -a "$(parser_supports 'ptrace,')" == "true" ] ; then +if [ "$(kernel_features ptrace)" = "true" -a "$(parser_supports 'ptrace,')" = "true" ] ; then . $bin/ptrace_v6.inc else . $bin/ptrace_v5.inc diff --git a/tests/regression/apparmor/query_label.c b/tests/regression/apparmor/query_label.c index e84d7f2a0..e2281ae0d 100644 --- a/tests/regression/apparmor/query_label.c +++ b/tests/regression/apparmor/query_label.c @@ -87,7 +87,7 @@ #define AA_MAY_LINK 0x40000 #endif -#ifndef AA_LINK_SUBSET /* overlayed perm in pair */ +#ifndef AA_LINK_SUBSET /* overlaid perm in pair */ #define AA_LINK_SUBSET AA_MAY_LOCK #endif diff --git a/tests/regression/apparmor/query_label.sh b/tests/regression/apparmor/query_label.sh index 802376ab9..654772735 100755 --- a/tests/regression/apparmor/query_label.sh +++ b/tests/regression/apparmor/query_label.sh @@ -93,7 +93,7 @@ querytest() runchecktest "$desc" "$pf" "$expect" "$label" "$perms" $* } -if [ "$(kernel_features dbus)" == "true" ]; then +if [ "$(kernel_features dbus)" = "true" ]; then # Check querying of a label that the kernel doesn't know about # aa_query_label() should return an error expect anything @@ -214,10 +214,20 @@ else echo " required feature dbus missing, skipping dbus queries ..." fi +CURRENT_VERSION=$(uname -r | cut -d'.' -f-2) +if (( $(echo $CURRENT_VERSION 4.4 | awk '{if ($1 < $2) print 1;}') )); then + # The AppArmor query interface did not originally support queries for file + # rules. That support was added on version 4.4 but there is no feature file + # to examine in apparmorfs to determine if the current kernel supports queries + # for file rules. + echo " WARNING: kernel does not support file queries, skipping tests ..." + exit +fi + genqueryprofile "file," expect allow perms file exec,write,read,append,create,delete,setattr,getattr,chmod,chown,link,linksubset,lock,exec_mmap -if [ "$(kernel_features query/label/multi_transaction)" == "true" ] ; then +if [ "$(kernel_features query/label/multi_transaction)" = "true" ] ; then querytest "QUERY file (all base perms #1)" pass /anything querytest "QUERY file (all base perms #2)" pass /everything else diff --git a/tests/regression/apparmor/socketpair.c b/tests/regression/apparmor/socketpair.c index 491907f2a..0c12da9f5 100644 --- a/tests/regression/apparmor/socketpair.c +++ b/tests/regression/apparmor/socketpair.c @@ -111,7 +111,7 @@ static int reexec(int pair[2], int argc, char **argv) return 0; /** - * Save off the first <CHANGE_ONEXEC> arg and then shift all preceeding + * Save off the first <CHANGE_ONEXEC> arg and then shift all preceding * args by one to effectively pop off the first <CHANGE_ONEXEC> */ new_profile = argv[3]; diff --git a/tests/regression/apparmor/socketpair.sh b/tests/regression/apparmor/socketpair.sh index 2f5559476..5d94f4c1e 100755 --- a/tests/regression/apparmor/socketpair.sh +++ b/tests/regression/apparmor/socketpair.sh @@ -37,7 +37,7 @@ af_unix_create_label="" af_unix_inherit="" aa_enabled="/sys/module/apparmor/parameters/enabled:r" -if [ "$(kernel_features network/af_unix)" == "true" -a "$(parser_supports 'unix,')" == "true" ]; then +if [ "$(kernel_features network/af_unix)" = "true" -a "$(parser_supports 'unix,')" = "true" ]; then # AppArmor requires that the process inheriting the sock file # descriptors have send,receive perms in its profile af_unix_create="unix:(create,getopt)" diff --git a/tests/regression/apparmor/swap.sh b/tests/regression/apparmor/swap.sh index 9078d26e0..87b5c5c0b 100755 --- a/tests/regression/apparmor/swap.sh +++ b/tests/regression/apparmor/swap.sh @@ -13,7 +13,7 @@ # unconfined processes can call these syscalls but confined processes cannot. #=END -# I made this a seperate test script because of the need to make a +# I made this a separate test script because of the need to make a # swapfile before the tests run. pwd=`dirname $0` @@ -29,7 +29,7 @@ bin=$pwd # check if we can run the test at all fstype=$(stat -f --format '%T' "${tmpdir}") -if [ "${fstype}" == "tmpfs" ] ; then +if [ "${fstype}" = "tmpfs" ] ; then echo "ERROR: tmpdir '${tmpdir}' is of type tmpfs; can't mount a swapfile on it" 1>&2 echo "ERROR: skipping swap tests" 1>&2 num_testfailures=1 diff --git a/tests/regression/apparmor/syscall.sh b/tests/regression/apparmor/syscall.sh index 8d98b40a4..b9d68d471 100755 --- a/tests/regression/apparmor/syscall.sh +++ b/tests/regression/apparmor/syscall.sh @@ -150,13 +150,19 @@ i386 | i486 | i586 | i686 | x86 | x86_64) # But don't run them on xen kernels if [ ! -d /proc/xen ] ; then +# lockdown thwarts both ioperm and iopl +expected=pass +if [ -f /sys/kernel/security/lockdown ] && ! grep -q "\[none\]" /sys/kernel/security/lockdown; then + expected=fail +fi + ## ## F. IOPERM ## settest syscall_ioperm # TEST F1 -runchecktest "IOPERM (no confinement)" pass 0 0x3ff +runchecktest "IOPERM (no confinement)" $expected 0 0x3ff # TEST F2. ioperm will fail genprofile @@ -169,7 +175,7 @@ runchecktest "IOPERM (confinement)" fail 0 0x3ff settest syscall_iopl # TEST G1 -runchecktest "IOPL (no confinement)" pass 3 +runchecktest "IOPL (no confinement)" $expected 3 # TEST G2. iopl will fail genprofile diff --git a/tests/regression/apparmor/syscall_sysctl.sh b/tests/regression/apparmor/syscall_sysctl.sh index 5f8569847..647df403f 100644 --- a/tests/regression/apparmor/syscall_sysctl.sh +++ b/tests/regression/apparmor/syscall_sysctl.sh @@ -148,7 +148,7 @@ test_sysctl_proc() # check if the kernel supports CONFIG_SYSCTL_SYSCALL # generally we want to encourage kernels to disable it, but if it's # enabled we want to test against it -# In addition test that sysctl exists in the kernel headers, if it does't +# In addition test that sysctl exists in the kernel headers, if it doesn't # then we can't even built the syscall_sysctl test if echo "#include <sys/sysctl.h>" | cpp -dM >/dev/null 2>/dev/null ; then settest syscall_sysctl diff --git a/tests/regression/apparmor/tcp.sh b/tests/regression/apparmor/tcp.sh index 703f1c55f..4d27b7bb4 100755 --- a/tests/regression/apparmor/tcp.sh +++ b/tests/regression/apparmor/tcp.sh @@ -14,8 +14,14 @@ pwd=`cd $pwd ; /bin/pwd` bin=$pwd +# TODO: +# need to update so we can run the test for ech supported +# need to be able to modify the compile features to choose the +# kernel feature supported +# need to be able to query the parser if it supports the +# kernel feature . $bin/prologue.inc -requires_kernel_features network +requires_any_of_kernel_features network network_v8 port=34567 ip="127.0.0.1" diff --git a/tests/regression/apparmor/unix_fd_client.c b/tests/regression/apparmor/unix_fd_client.c index 77b284d1e..4b2e32798 100644 --- a/tests/regression/apparmor/unix_fd_client.c +++ b/tests/regression/apparmor/unix_fd_client.c @@ -9,74 +9,9 @@ * License. */ -#include <stdio.h> -#include <unistd.h> -#include <sys/types.h> -#include <string.h> -#include <stdlib.h> -#include <sys/socket.h> -#include <alloca.h> -#include <fcntl.h> -#include <sys/uio.h> -#include <sys/un.h> -#include <sys/wait.h> -#include <errno.h> #include <stdlib.h> +#include "unix_fd_common.h" int main(int argc, char *argv[]) { - int sock, fd, len; - struct sockaddr_un remote; - char read_buffer[17], f_buf[255]; - struct iovec vect; - struct msghdr mesg; - struct cmsghdr *ctrl_mesg; - - if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { - fprintf(stderr, "FAIL CLIENT - sock %s\n", - strerror(errno)); - exit(1); - } - - remote.sun_family = AF_UNIX; - strcpy(remote.sun_path, argv[1]); - len = strlen(remote.sun_path) + sizeof(remote.sun_family); - if (connect(sock, (struct sockaddr *)&remote, len) == -1) { - fprintf(stderr, "FAIL CLIENT - connect %s\n", - strerror(errno)); - exit(1); - } - - vect.iov_base = f_buf; - vect.iov_len = 255; - - mesg.msg_name = NULL; - mesg.msg_namelen=0; - mesg.msg_iov = &vect; - mesg.msg_iovlen = 1; - - ctrl_mesg = alloca(sizeof (struct cmsghdr) + sizeof(fd)); - ctrl_mesg->cmsg_len = sizeof(struct cmsghdr) + sizeof(fd); - mesg.msg_control = ctrl_mesg; - mesg.msg_controllen = ctrl_mesg->cmsg_len; - - if (!recvmsg(sock, &mesg,0 )) { - fprintf(stderr, "FAIL CLIENT - recvmsg\n"); - exit(1); - } - - /* get mr. file descriptor */ - - memcpy(&fd, CMSG_DATA(ctrl_mesg), sizeof(fd)); - - if (pread(fd, read_buffer, 16, 0) <= 0) { - /* Failure */ - fprintf(stderr, "FAIL CLIENT - could not read\n"); - send(sock, "FAILFAILFAILFAIL", 16, 0); - exit(1); - } else { - send(sock, read_buffer, strlen(read_buffer),0); - } - - /* looks like it worked */ - exit(0); + exit(get_unix_clientfd(argv[1])); } diff --git a/tests/regression/apparmor/unix_fd_common.c b/tests/regression/apparmor/unix_fd_common.c new file mode 100644 index 000000000..72405078d --- /dev/null +++ b/tests/regression/apparmor/unix_fd_common.c @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2021 Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, contact Canonical Ltd. + */ + +#include <stdio.h> +#include <unistd.h> +#include <sys/types.h> +#include <string.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <alloca.h> +#include <fcntl.h> +#include <sys/uio.h> +#include <sys/un.h> +#include <sys/wait.h> +#include <errno.h> +#include <stdlib.h> + +int get_unix_clientfd(char *sun_path) { + int sock, fd, len; + struct sockaddr_un remote; + char read_buffer[17], f_buf[255]; + struct iovec vect; + struct msghdr mesg; + struct cmsghdr *ctrl_mesg; + + if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + fprintf(stderr, "FAIL CLIENT - sock %s\n", + strerror(errno)); + return -1; + } + + remote.sun_family = AF_UNIX; + strcpy(remote.sun_path, sun_path); + len = strlen(remote.sun_path) + sizeof(remote.sun_family); + if (connect(sock, (struct sockaddr *)&remote, len) == -1) { + fprintf(stderr, "FAIL CLIENT - connect %s\n", + strerror(errno)); + return -1; + } + + vect.iov_base = f_buf; + vect.iov_len = 255; + + mesg.msg_name = NULL; + mesg.msg_namelen=0; + mesg.msg_iov = &vect; + mesg.msg_iovlen = 1; + + ctrl_mesg = alloca(sizeof (struct cmsghdr) + sizeof(fd)); + ctrl_mesg->cmsg_len = sizeof(struct cmsghdr) + sizeof(fd); + mesg.msg_control = ctrl_mesg; + mesg.msg_controllen = ctrl_mesg->cmsg_len; + + if (!recvmsg(sock, &mesg,0 )) { + fprintf(stderr, "FAIL CLIENT - recvmsg\n"); + return -1; + } + + /* get mr. file descriptor */ + + memcpy(&fd, CMSG_DATA(ctrl_mesg), sizeof(fd)); + + if (pread(fd, read_buffer, 16, 0) <= 0) { + /* Failure */ + fprintf(stderr, "FAIL CLIENT - could not read\n"); + send(sock, "FAILFAILFAILFAIL", 16, 0); + return -1; + } else { + send(sock, read_buffer, strlen(read_buffer),0); + } + return 0; +} diff --git a/tests/regression/apparmor/unix_fd_common.h b/tests/regression/apparmor/unix_fd_common.h new file mode 100644 index 000000000..cbc802416 --- /dev/null +++ b/tests/regression/apparmor/unix_fd_common.h @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2021 Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, contact Canonical Ltd. + */ + +int get_unix_clientfd(char *sun_path); diff --git a/tests/regression/apparmor/unix_fd_server.c b/tests/regression/apparmor/unix_fd_server.c index 290e555e4..a3fe0ee4b 100644 --- a/tests/regression/apparmor/unix_fd_server.c +++ b/tests/regression/apparmor/unix_fd_server.c @@ -40,8 +40,9 @@ int main (int argc, char * argv[]) { struct cmsghdr *ctrl_mesg; struct pollfd pfd; - if (argc < 4 || argc > 5 || (argc == 5 && (strcmp(argv[4], "delete_file") != 0))) { - fprintf(stderr, "Usage: %s <file>\n", argv[0]); + /* The server forwards the client's arguments */ + if (argc < 4) { + fprintf(stderr, "Usage: %s <file> <client> <unix_socket> [client args ...]\n", argv[0]); return(1); } @@ -57,7 +58,7 @@ int main (int argc, char * argv[]) { return(1); } - if (argc == 5) { + if (argc == 5 && strcmp(argv[4], "delete_file") == 0) { if (unlink(argv[1]) == -1){ fprintf(stderr, "FAIL: unlink before passing fd - %s\n", strerror(errno)); @@ -73,7 +74,7 @@ int main (int argc, char * argv[]) { } local.sun_family = AF_UNIX; - strcpy(local.sun_path, argv[2]); + strcpy(local.sun_path, argv[3]); unlink(local.sun_path); len = strlen(local.sun_path) + sizeof(local.sun_family); @@ -92,7 +93,7 @@ int main (int argc, char * argv[]) { /* exec the client */ int pid = fork(); if (!pid) { - execlp(argv[3], argv[3], argv[2], NULL); + execvp(argv[2], &(argv[2])); exit(0); } @@ -108,8 +109,8 @@ int main (int argc, char * argv[]) { exit(1); } - vect.iov_base = argv[2]; - vect.iov_len = strlen(argv[2]) + 1; + vect.iov_base = argv[3]; + vect.iov_len = strlen(argv[3]) + 1; mesg.msg_name = NULL; mesg.msg_namelen = 0; diff --git a/tests/regression/apparmor/unix_fd_server.sh b/tests/regression/apparmor/unix_fd_server.sh index 0538feec6..6bf966c09 100755 --- a/tests/regression/apparmor/unix_fd_server.sh +++ b/tests/regression/apparmor/unix_fd_server.sh @@ -27,7 +27,9 @@ okperm=rw badperm=w af_unix="" -if [ "$(kernel_features network/af_unix)" == "true" -a "$(parser_supports 'unix,')" == "true" ]; then +if [ "$(kernel_features network_v8)" = "true" -a "$(parser_supports 'unix,')" = "true" ]; then + af_unix="unix:create" +elif [ "$(kernel_features network/af_unix)" = "true" -a "$(parser_supports 'unix,')" = "true" ]; then af_unix="unix:create" fi @@ -49,7 +51,7 @@ rm -f ${socket} # PASS - unconfined -> unconfined -runchecktest "fd passing; unconfined -> unconfined" pass $file $socket $fd_client +runchecktest "fd passing; unconfined -> unconfined" pass $file $fd_client $socket sleep 1 rm -f ${socket} @@ -58,7 +60,7 @@ rm -f ${socket} genprofile $file:$okperm $af_unix $socket:rw $fd_client:ux -runchecktest "fd passing; confined -> unconfined" pass $file $socket $fd_client +runchecktest "fd passing; confined -> unconfined" pass $file $fd_client $socket sleep 1 rm -f ${socket} @@ -67,7 +69,7 @@ rm -f ${socket} genprofile $file:$badperm $af_unix $socket:rw $fd_client:ux -runchecktest "fd passing; confined (bad perm) -> unconfined" fail $file $socket $fd_client +runchecktest "fd passing; confined (bad perm) -> unconfined" fail $file $fd_client $socket sleep 1 rm -f ${socket} @@ -76,7 +78,7 @@ rm -f ${socket} genprofile $af_unix $socket:rw $fd_client:ux -runchecktest "fd passing; confined (no perm) -> unconfined" fail $file $socket $fd_client +runchecktest "fd passing; confined (no perm) -> unconfined" fail $file $fd_client $socket sleep 1 rm -f ${socket} @@ -84,7 +86,7 @@ rm -f ${socket} # PASS (due to delegation) - unconfined -> confined genprofile image=$fd_client $file:$okperm $af_unix $socket:rw -runchecktest "fd passing; unconfined -> confined" pass $file $socket $fd_client +runchecktest "fd passing; unconfined -> confined" pass $file $fd_client $socket sleep 1 rm -f ${socket} @@ -92,23 +94,23 @@ rm -f ${socket} # PASS (due to delegation) - unconfined -> confined (no perm) genprofile image=$fd_client $af_unix $socket:rw -runchecktest "fd passing; unconfined -> confined (no perm)" pass $file $socket $fd_client +runchecktest "fd passing; unconfined -> confined (no perm)" pass $file $fd_client $socket sleep 1 rm -f ${socket} # PASS - confined -> confined - +echo "PASS-----------------------------------------" genprofile $file:$okperm $af_unix $socket:rw $fd_client:px -- image=$fd_client $file:$okperm $af_unix $socket:rw -runchecktest "fd passing; confined -> confined" pass $file $socket $fd_client +runchecktest "fd passing; confined -> confined" pass $file $fd_client $socket sleep 1 rm -f ${socket} - +exit # FAIL - confined (bad perm) -> confined genprofile $file:$badperm $af_unix $socket:rw $fd_client:px -- image=$fd_client $file:$okperm $af_unix $socket:rw -runchecktest "fd passing; confined (bad perm) -> confined" fail $file $socket $fd_client +runchecktest "fd passing; confined (bad perm) -> confined" fail $file $fd_client $socket sleep 1 rm -f ${socket} @@ -116,7 +118,7 @@ rm -f ${socket} # FAIL - confined (no perm) -> confined genprofile $af_unix $socket:rw $fd_client:px -- image=$fd_client $file:$okperm $af_unix $socket:rw -runchecktest "fd passing; confined (no perm) -> confined" fail $file $socket $fd_client +runchecktest "fd passing; confined (no perm) -> confined" fail $file $fd_client $socket sleep 1 rm -f ${socket} @@ -124,7 +126,7 @@ rm -f ${socket} # FAIL - confined -> confined (bad perm) genprofile $file:$okperm $af_unix $socket:rw $fd_client:px -- image=$fd_client $file:$badperm $af_unix $socket:rw -runchecktest "fd passing; confined -> confined (bad perm)" fail $file $socket $fd_client +runchecktest "fd passing; confined -> confined (bad perm)" fail $file $fd_client $socket sleep 1 rm -f ${socket} @@ -132,16 +134,16 @@ rm -f ${socket} # FAIL - confined -> confined (no perm) genprofile $file:$okperm $af_unix $socket:rw $fd_client:px -- image=$fd_client $af_unix $socket:rw -runchecktest "fd passing; confined -> confined (no perm)" fail $file $socket $fd_client +runchecktest "fd passing; confined -> confined (no perm)" fail $file $fd_client $socket sleep 1 rm -f ${socket} -if [ "$(kernel_features policy/network/af_unix)" == "true" -a "$(parser_supports 'unix,')" == "true" ] ; then +if [ "$(kernel_features policy/network/af_unix)" = "true" -a "$(parser_supports 'unix,')" = "true" ] ; then # FAIL - confined client, no access to the socket file genprofile $file:$okperm $af_unix $socket:rw $fd_client:px -- image=$fd_client $file:$okperm $af_unix - runchecktest "fd passing; confined client w/o socket access" fail $file $socket $fd_client + runchecktest "fd passing; confined client w/o socket access" fail $file $fd_client $socket sleep 1 rm -f ${socket} diff --git a/tests/regression/apparmor/unix_socket.inc b/tests/regression/apparmor/unix_socket.inc index a8bacb667..905c99c6f 100644 --- a/tests/regression/apparmor/unix_socket.inc +++ b/tests/regression/apparmor/unix_socket.inc @@ -33,7 +33,7 @@ do_test() local bad_p_addr="${13}" # optional local desc="AF_UNIX $addr_type socket ($type);" - local l_access # combind local perms: local bound and local unbound + local l_access # combined local perms: local bound and local unbound local c_access # combined perms: local bound, local unbound, and peer local access # used as an iterator local u_rule # rule for pre-bind accesses diff --git a/tests/regression/apparmor/unix_socket_pathname.sh b/tests/regression/apparmor/unix_socket_pathname.sh index 595e887a3..1566ec136 100755 --- a/tests/regression/apparmor/unix_socket_pathname.sh +++ b/tests/regression/apparmor/unix_socket_pathname.sh @@ -29,7 +29,7 @@ bin=$pwd . $bin/prologue.inc requires_kernel_features policy/versions/v6 #af_mask for downgrade test af_unix for full test -requires_kernel_features network/af_mask +requires_any_of_kernel_features network/af_mask network_v8/af_mask settest unix_socket @@ -43,9 +43,9 @@ message=4a0c83d87aaa7afa2baab5df3ee4df630f0046d5bfb7a3080c550b721f401b3b\ okserver=w badserver1=r badserver2= -if [ "$(kernel_features policy/versions/v7)" == "true" ] ; then +if [ "$(kernel_features policy/versions/v7)" = "true" ] ; then okserver=rw - badserver2=w +# badserver2=w fi # af_unix support requires 'unix create' to call socket() @@ -54,9 +54,16 @@ fi # af_unix support requires 'unix getattr' to call getsockname() af_unix_okserver= af_unix_okclient= -if [ "$(kernel_features network/af_unix)" == "true" -a "$(parser_supports 'unix,')" == "true" ] ; then +if ( [ "$(kernel_features network_v8/af_unix)" = "true" ] || + [ "$(kernel_features network/af_unix)" = "true" ] ) && + [ "$(parser_supports 'unix,')" = "true" ] ; then af_unix_okserver="create,setopt" af_unix_okclient="create,getopt,setopt,getattr" +elif [ "$(kernel_features network_v8)" = "true" ] ; then +# af_unix_okserver="create,setopt" +# af_unix_okclient="create,getopt,setopt,getattr" + af_unix_okserver="create" + af_unix_okclient="create" fi okclient=rw @@ -88,7 +95,7 @@ testsocktype() # https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1373176 # get resolved local ex_result="pass" - if [ "${socktype}" == "dgram" ] ; then + if [ "${socktype}" = "dgram" ] ; then ex_result="xpass" fi diff --git a/tests/regression/apparmor/xattrs.sh b/tests/regression/apparmor/xattrs.sh index f61fde87d..341695aa3 100755 --- a/tests/regression/apparmor/xattrs.sh +++ b/tests/regression/apparmor/xattrs.sh @@ -14,8 +14,8 @@ # security: get r, set w + CAP_SYS_ADMIN # system: (acl's etc.) fs and kernel dependent (CAP_SYS_ADMIN) # trusted: CAP_SYS_ADMIN -# user: for subdomain the relevent file must be in the profile, with r perm -# to get xattr, w perm to set or remove xattr. The appriate cap must be +# user: for subdomain the relevant file must be in the profile, with r perm +# to get xattr, w perm to set or remove xattr. The appropriate cap must be # present in the profile as well #=END @@ -58,7 +58,7 @@ mkdir $dir add_attrs() { - #set the xattr for thos that passed above again so we can test removing it + #set the xattr for those that passed above again so we can test removing it setfattr -h -n security.sdtest -v hello "$1" setfattr -h -n trusted.sdtest -v hello "$1" if [ "$1" != $link ] ; then diff --git a/utils/aa-audit b/utils/aa-audit index eee559a22..f76cdef85 100755 --- a/utils/aa-audit +++ b/utils/aa-audit @@ -15,23 +15,20 @@ import argparse import apparmor.tools - -# setup exception handling from apparmor.fail import enable_aa_exception_handler -enable_aa_exception_handler() - -# setup module translations from apparmor.translations import init_translation -_ = init_translation() + +enable_aa_exception_handler() # setup exception handling +_ = init_translation() # setup module translations parser = argparse.ArgumentParser(description=_('Switch the given programs to audit mode')) parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) parser.add_argument('-r', '--remove', action='store_true', help=_('remove audit mode')) parser.add_argument('program', type=str, nargs='+', help=_('name of program')) parser.add_argument('--no-reload', dest='do_reload', action='store_false', default=True, help=_('Do not reload the profile after modifying it')) +parser.add_argument('--configdir', type=str, help=argparse.SUPPRESS) args = parser.parse_args() tool = apparmor.tools.aa_tools('audit', args) tool.cmd_audit() - diff --git a/utils/aa-audit.8 b/utils/aa-audit.8 index c7981fc25..5cce8aaf1 100644 --- a/utils/aa-audit.8 +++ b/utils/aa-audit.8 @@ -133,7 +133,7 @@ .\" ======================================================================== .\" .IX Title "AA-AUDIT 8" -.TH AA-AUDIT 8 "2022-11-22" "AppArmor 3.0.8" "AppArmor" +.TH AA-AUDIT 8 "2024-02-02" "AppArmor 3.1.7" "AppArmor" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l diff --git a/utils/aa-autodep b/utils/aa-autodep index 6126acce7..fa76094fd 100755 --- a/utils/aa-autodep +++ b/utils/aa-autodep @@ -15,20 +15,18 @@ import argparse import apparmor.tools - -# setup exception handling from apparmor.fail import enable_aa_exception_handler -enable_aa_exception_handler() - -# setup module translations from apparmor.translations import init_translation -_ = init_translation() + +enable_aa_exception_handler() # setup exception handling +_ = init_translation() # setup module translations parser = argparse.ArgumentParser(description=_('Generate a basic AppArmor profile by guessing requirements')) parser.add_argument('--force', action='store_true', default=False, help=_('overwrite existing profile')) parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) parser.add_argument('program', type=str, nargs='+', help=_('name of program')) parser.add_argument('--no-reload', dest='do_reload', action='store_false', default=True, help=_('Do not reload the profile after modifying it')) +parser.add_argument('--configdir', type=str, help=argparse.SUPPRESS) args = parser.parse_args() tool = apparmor.tools.aa_tools('autodep', args) diff --git a/utils/aa-autodep.8 b/utils/aa-autodep.8 index 58db599c2..c7dff9929 100644 --- a/utils/aa-autodep.8 +++ b/utils/aa-autodep.8 @@ -133,7 +133,7 @@ .\" ======================================================================== .\" .IX Title "AA-AUTODEP 8" -.TH AA-AUTODEP 8 "2022-11-22" "AppArmor 3.0.8" "AppArmor" +.TH AA-AUTODEP 8 "2024-02-02" "AppArmor 3.1.7" "AppArmor" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l diff --git a/utils/aa-cleanprof b/utils/aa-cleanprof index acc617380..09ae2c1c4 100755 --- a/utils/aa-cleanprof +++ b/utils/aa-cleanprof @@ -15,20 +15,18 @@ import argparse import apparmor.tools - -# setup exception handling from apparmor.fail import enable_aa_exception_handler -enable_aa_exception_handler() - -# setup module translations from apparmor.translations import init_translation -_ = init_translation() + +enable_aa_exception_handler() # setup exception handling +_ = init_translation() # setup module translations parser = argparse.ArgumentParser(description=_('Cleanup the profiles for the given programs')) parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) parser.add_argument('program', type=str, nargs='+', help=_('name of program')) parser.add_argument('-s', '--silent', action='store_true', help=_('Silently overwrite with a clean profile')) parser.add_argument('--no-reload', dest='do_reload', action='store_false', default=True, help=_('Do not reload the profile after modifying it')) +parser.add_argument('--configdir', type=str, help=argparse.SUPPRESS) args = parser.parse_args() clean = apparmor.tools.aa_tools('cleanprof', args) diff --git a/utils/aa-cleanprof.8 b/utils/aa-cleanprof.8 index 8ad47f2d1..3d28a09fa 100644 --- a/utils/aa-cleanprof.8 +++ b/utils/aa-cleanprof.8 @@ -133,7 +133,7 @@ .\" ======================================================================== .\" .IX Title "AA-CLEANPROF 8" -.TH AA-CLEANPROF 8 "2022-11-22" "AppArmor 3.0.8" "AppArmor" +.TH AA-CLEANPROF 8 "2024-02-02" "AppArmor 3.1.7" "AppArmor" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l diff --git a/utils/aa-complain b/utils/aa-complain index c0abd1919..d4631b1c4 100755 --- a/utils/aa-complain +++ b/utils/aa-complain @@ -15,21 +15,19 @@ import argparse import apparmor.tools - -# setup exception handling from apparmor.fail import enable_aa_exception_handler -enable_aa_exception_handler() - -# setup module translations from apparmor.translations import init_translation -_ = init_translation() + +enable_aa_exception_handler() # setup exception handling +_ = init_translation() # setup module translations parser = argparse.ArgumentParser(description=_('Switch the given program to complain mode')) parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) parser.add_argument('program', type=str, nargs='+', help=_('name of program')) parser.add_argument('--no-reload', dest='do_reload', action='store_false', default=True, help=_('Do not reload the profile after modifying it')) +parser.add_argument('--configdir', type=str, help=argparse.SUPPRESS) args = parser.parse_args() tool = apparmor.tools.aa_tools('complain', args) -#print(args) +# print(args) tool.cmd_complain() diff --git a/utils/aa-complain.8 b/utils/aa-complain.8 index 4e5151853..16c975a4e 100644 --- a/utils/aa-complain.8 +++ b/utils/aa-complain.8 @@ -133,7 +133,7 @@ .\" ======================================================================== .\" .IX Title "AA-COMPLAIN 8" -.TH AA-COMPLAIN 8 "2022-11-22" "AppArmor 3.0.8" "AppArmor" +.TH AA-COMPLAIN 8 "2024-02-02" "AppArmor 3.1.7" "AppArmor" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l diff --git a/utils/aa-decode b/utils/aa-decode index 16f23b55b..f02f362c7 100755 --- a/utils/aa-decode +++ b/utils/aa-decode @@ -37,7 +37,7 @@ EOM } decode() { - if echo "$1" | egrep -q "^[0-9A-Fa-f]+$" ; then + if echo "$1" | grep -E -q "^[0-9A-Fa-f]+$" ; then python3 -c "import binascii; print(bytes.decode(binascii.unhexlify('$1'), errors='strict'));" else echo "" @@ -53,12 +53,12 @@ fi # if have an argument, then use it, otherwise process stdin if [ -n "$1" ]; then e="$1" - if ! echo "$e" | egrep -q "^[0-9A-Fa-f]+$" ; then + if ! echo "$e" | grep -E -q "^[0-9A-Fa-f]+$" ; then echo "String should only contain hex characters (0-9, a-f, A-F)" exit 1 fi - d=`decode $e` + d=$(decode "$e") if [ -z "$d" ]; then echo "Could not decode string" exit 1 @@ -71,6 +71,12 @@ fi # For now just look at 'name=...' and 'profile=...', # so validate input against this and output based on it. # TODO: better handle other cases too +# This loop relies on subtle shell quoting/escaping behavior, and +# non-trivial surgery would be required to make shellcheck happy. +# shellcheck disable=SC2162 +# shellcheck disable=SC2086 +# shellcheck disable=SC2001 +# shellcheck disable=SC2006 while read line ; do # check if line contains encoded name= or profile= diff --git a/utils/aa-decode.8 b/utils/aa-decode.8 index 626d7616d..e9a94e386 100644 --- a/utils/aa-decode.8 +++ b/utils/aa-decode.8 @@ -133,7 +133,7 @@ .\" ======================================================================== .\" .IX Title "AA-DECODE 8" -.TH AA-DECODE 8 "2022-11-22" "AppArmor 3.0.8" "AppArmor" +.TH AA-DECODE 8 "2024-02-02" "AppArmor 3.1.7" "AppArmor" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l diff --git a/utils/aa-disable b/utils/aa-disable index 647a723dd..8da8c58c9 100755 --- a/utils/aa-disable +++ b/utils/aa-disable @@ -15,22 +15,19 @@ import argparse import apparmor.tools - -# setup exception handling from apparmor.fail import enable_aa_exception_handler -enable_aa_exception_handler() - -# setup module translations from apparmor.translations import init_translation -_ = init_translation() + +enable_aa_exception_handler() # setup exception handling +_ = init_translation() # setup module translations parser = argparse.ArgumentParser(description=_('Disable the profile for the given programs')) parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) parser.add_argument('program', type=str, nargs='+', help=_('name of program')) parser.add_argument('--no-reload', dest='do_reload', action='store_false', default=True, help=_('Do not unload the profile after modifying it')) +parser.add_argument('--configdir', type=str, help=argparse.SUPPRESS) args = parser.parse_args() tool = apparmor.tools.aa_tools('disable', args) tool.cmd_disable() - diff --git a/utils/aa-disable.8 b/utils/aa-disable.8 index 4d6943b66..61a689179 100644 --- a/utils/aa-disable.8 +++ b/utils/aa-disable.8 @@ -133,7 +133,7 @@ .\" ======================================================================== .\" .IX Title "AA-DISABLE 8" -.TH AA-DISABLE 8 "2022-11-22" "AppArmor 3.0.8" "AppArmor" +.TH AA-DISABLE 8 "2024-02-02" "AppArmor 3.1.7" "AppArmor" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l diff --git a/utils/aa-easyprof b/utils/aa-easyprof index 9c59359ac..3fa1df72b 100755 --- a/utils/aa-easyprof +++ b/utils/aa-easyprof @@ -9,18 +9,18 @@ # # ------------------------------------------------------------------ -import apparmor.easyprof -from apparmor.easyprof import error import os import sys -# setup exception handling +import apparmor.easyprof +from apparmor.easyprof import error from apparmor.fail import enable_aa_exception_handler -enable_aa_exception_handler() + +enable_aa_exception_handler() # setup exception handling if __name__ == "__main__": def usage(): - '''Return usage information''' + """Return usage information""" return 'USAGE: %s [options] <path to binary>' % \ os.path.basename(sys.argv[0]) @@ -54,8 +54,8 @@ if __name__ == "__main__": os.strerror(e.errno), e.errno)) profiles = apparmor.easyprof.parse_manifest(manifest, opt) - else: # fake up a tuple list when processing command line args - profiles.append( (binary, opt) ) + else: # fake up a tuple list when processing command line args + profiles.append((binary, opt)) count = 0 for (binary, options) in profiles: @@ -100,8 +100,7 @@ if __name__ == "__main__": apparmor.easyprof.print_files(files) sys.exit(0) - elif binary == None and not options.profile_name and \ - not options.manifest: + elif binary is None and not options.profile_name and not options.manifest: error("Must specify binary and/or profile name\n%s" % m) params = apparmor.easyprof.gen_policy_params(binary, options) diff --git a/utils/aa-easyprof.8 b/utils/aa-easyprof.8 index ffa41d53c..c5310ff11 100644 --- a/utils/aa-easyprof.8 +++ b/utils/aa-easyprof.8 @@ -133,7 +133,7 @@ .\" ======================================================================== .\" .IX Title "AA-EASYPROF 8" -.TH AA-EASYPROF 8 "2022-11-22" "AppArmor 3.0.8" "AppArmor" +.TH AA-EASYPROF 8 "2024-02-02" "AppArmor 3.1.7" "AppArmor" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l diff --git a/utils/aa-enforce b/utils/aa-enforce index 7f79854f6..a2e62649e 100755 --- a/utils/aa-enforce +++ b/utils/aa-enforce @@ -15,19 +15,17 @@ import argparse import apparmor.tools - -# setup exception handling from apparmor.fail import enable_aa_exception_handler -enable_aa_exception_handler() - -# setup module translations from apparmor.translations import init_translation -_ = init_translation() + +enable_aa_exception_handler() # setup exception handling +_ = init_translation() # setup module translations parser = argparse.ArgumentParser(description=_('Switch the given program to enforce mode')) parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) parser.add_argument('program', type=str, nargs='+', help=_('name of program')) parser.add_argument('--no-reload', dest='do_reload', action='store_false', default=True, help=_('Do not reload the profile after modifying it')) +parser.add_argument('--configdir', type=str, help=argparse.SUPPRESS) args = parser.parse_args() tool = apparmor.tools.aa_tools('enforce', args) diff --git a/utils/aa-enforce.8 b/utils/aa-enforce.8 index a259d538a..0232dafb3 100644 --- a/utils/aa-enforce.8 +++ b/utils/aa-enforce.8 @@ -133,7 +133,7 @@ .\" ======================================================================== .\" .IX Title "AA-ENFORCE 8" -.TH AA-ENFORCE 8 "2022-11-22" "AppArmor 3.0.8" "AppArmor" +.TH AA-ENFORCE 8 "2024-02-02" "AppArmor 3.1.7" "AppArmor" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l diff --git a/utils/aa-genprof b/utils/aa-genprof index bf5c5ee60..21988cdeb 100755 --- a/utils/aa-genprof +++ b/utils/aa-genprof @@ -18,18 +18,17 @@ import os import re import subprocess import sys +import time import apparmor.aa as apparmor import apparmor.ui as aaui -from apparmor.common import warn - -# setup exception handling +from apparmor.common import AppArmorException, warn from apparmor.fail import enable_aa_exception_handler -enable_aa_exception_handler() - -# setup module translations from apparmor.translations import init_translation -_ = init_translation() + +enable_aa_exception_handler() # setup exception handling +_ = init_translation() # setup module translations + def sysctl_read(path): value = None @@ -37,23 +36,26 @@ def sysctl_read(path): value = int(f_in.readline()) return value + def sysctl_write(path, value): if value is None: - warn('Not writing invalid value "None" to %s'%path) + warn('Not writing invalid value "None" to %s' % path) return with open(path, 'w') as f_out: f_out.write(str(value)) + def last_audit_entry_time(): out = subprocess.check_output(['tail', '-1', apparmor.logfile]) logmark = None out = out.decode('ascii') - if re.search('^.*msg\=audit\((\d+\.\d+\:\d+).*\).*$', out): - logmark = re.search('^.*msg\=audit\((\d+\.\d+\:\d+).*\).*$', out).groups()[0] + if re.search(r'^.*msg=audit\((\d+\.\d+:\d+).*\).*$', out): + logmark = re.search(r'^.*msg=audit\((\d+\.\d+:\d+).*\).*$', out).groups()[0] else: logmark = '' return logmark + def restore_ratelimit(): try: sysctl_write(ratelimit_sysctl, ratelimit_saved) @@ -61,11 +63,13 @@ def restore_ratelimit(): if ratelimit_saved != sysctl_read(ratelimit_sysctl): raise # happens only if a) running under lxd and b) something changed the ratelimit since starting aa-genprof + parser = argparse.ArgumentParser(description=_('Generate profile for the given program')) parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) parser.add_argument('-f', '--file', type=str, help=_('path to logfile')) parser.add_argument('program', type=str, help=_('name of program to profile')) parser.add_argument('-j', '--json', action="store_true", help=_('Input and Output in JSON')) +parser.add_argument('--configdir', type=str, help=argparse.SUPPRESS) args = parser.parse_args() if args.json: @@ -73,15 +77,15 @@ if args.json: profiling = args.program -apparmor.init_aa(profiledir=args.dir) +apparmor.init_aa(confdir=args.configdir, profiledir=args.dir) apparmor.set_logfile(args.file) aa_mountpoint = apparmor.check_for_apparmor() if not aa_mountpoint: - raise apparmor.AppArmorException(_('It seems AppArmor was not started. Please enable AppArmor and try again.')) + raise AppArmorException(_('It seems AppArmor was not started. Please enable AppArmor and try again.')) program = None -#if os.path.exists(apparmor.which(profiling.strip())): +# if os.path.exists(apparmor.which(profiling.strip())): if os.path.exists(profiling): program = apparmor.get_full_path(profiling) else: @@ -92,9 +96,14 @@ else: if not program or not os.path.exists(program): if '/' not in profiling: - raise apparmor.AppArmorException(_("Can't find %(profiling)s in the system path list. If the name of the application\nis correct, please run 'which %(profiling)s' as a user with correct PATH\nenvironment set up in order to find the fully-qualified path and\nuse the full path as parameter.") % { 'profiling': profiling }) + raise AppArmorException( + _("Can't find %(profiling)s in the system path list. If the name of the application\n" + "is correct, please run 'which %(profiling)s' as a user with correct PATH\n" + "environment set up in order to find the fully-qualified path and\n" + "use the full path as parameter.") + % {'profiling': profiling}) else: - raise apparmor.AppArmorException(_('%s does not exists, please double-check the path.') %profiling) + raise AppArmorException(_('%s does not exists, please double-check the path.') % profiling) # Check if the program has been marked as not allowed to have a profile apparmor.check_qualifiers(program) @@ -128,7 +137,11 @@ except PermissionError: # will fail in lxd atexit.register(restore_ratelimit) -aaui.UI_Info(_('\nBefore you begin, you may wish to check if a\nprofile already exists for the application you\nwish to confine. See the following wiki page for\nmore information:')+'\nhttps://gitlab.com/apparmor/apparmor/wikis/Profiles') +aaui.UI_Info( + _('\nBefore you begin, you may wish to check if a\n' + 'profile already exists for the application you\n' + 'wish to confine. See the following wiki page for\n' + 'more information:') + '\nhttps://gitlab.com/apparmor/apparmor/wikis/Profiles') syslog = True logmark = '' @@ -139,10 +152,8 @@ if os.path.exists('/var/log/audit/audit.log'): while not done_profiling: if syslog: - logmark = subprocess.check_output(['date | md5sum'], shell=True) - logmark = logmark.decode('ascii').strip() - logmark = re.search('^([0-9a-f]+)', logmark).groups()[0] - t=subprocess.call("%s -p kern.warn 'GenProf: %s'"%(apparmor.logger_path(), logmark), shell=True) + logmark = 'logmark-%s' % str(int(time.time())) # unix timestamp, seconds only + t = subprocess.call([apparmor.logger_path(), '-p', 'kern.warn', 'GenProf: %s' % logmark]) else: logmark = last_audit_entry_time() @@ -151,7 +162,14 @@ while not done_profiling: q.headers = [_('Profiling'), program] q.functions = ['CMD_SCAN', 'CMD_FINISHED'] q.default = 'CMD_SCAN' - q.explanation = _('Please start the application to be profiled in\nanother window and exercise its functionality now.\n\nOnce completed, select the "Scan" option below in \norder to scan the system logs for AppArmor events. \n\nFor each AppArmor event, you will be given the \nopportunity to choose whether the access should be \nallowed or denied.') + q.explanation = \ + _('Please start the application to be profiled in\n' + 'another window and exercise its functionality now.\n\n' + 'Once completed, select the "Scan" option below in \n' + 'order to scan the system logs for AppArmor events. \n\n' + 'For each AppArmor event, you will be given the \n' + 'opportunity to choose whether the access should be \n' + 'allowed or denied.') ans, arg = q.promptUser('noexit') if ans == 'CMD_SCAN': @@ -165,6 +183,8 @@ for p in sorted(apparmor.helpers.keys()): apparmor.reload(p) aaui.UI_Info(_('\nReloaded AppArmor profiles in enforce mode.')) -aaui.UI_Info(_('\nPlease consider contributing your new profile!\nSee the following wiki page for more information:')+'\nhttps://gitlab.com/apparmor/apparmor/wikis/Profiles\n') -aaui.UI_Info(_('Finished generating profile for %s.')%program) +aaui.UI_Info(_('\nPlease consider contributing your new profile!\n' + 'See the following wiki page for more information:') + + '\nhttps://gitlab.com/apparmor/apparmor/wikis/Profiles\n') +aaui.UI_Info(_('Finished generating profile for %s.') % program) sys.exit(0) diff --git a/utils/aa-genprof.8 b/utils/aa-genprof.8 index 3a9f80fbf..f3d7e8c12 100644 --- a/utils/aa-genprof.8 +++ b/utils/aa-genprof.8 @@ -133,7 +133,7 @@ .\" ======================================================================== .\" .IX Title "AA-GENPROF 8" -.TH AA-GENPROF 8 "2022-11-22" "AppArmor 3.0.8" "AppArmor" +.TH AA-GENPROF 8 "2024-02-02" "AppArmor 3.1.7" "AppArmor" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l diff --git a/utils/aa-logprof b/utils/aa-logprof index b56d4e643..4513e35fd 100755 --- a/utils/aa-logprof +++ b/utils/aa-logprof @@ -16,20 +16,19 @@ import argparse import apparmor.aa as apparmor import apparmor.ui as aaui - -# setup exception handling +from apparmor.common import AppArmorException from apparmor.fail import enable_aa_exception_handler -enable_aa_exception_handler() - -# setup module translations from apparmor.translations import init_translation -_ = init_translation() + +enable_aa_exception_handler() # setup exception handling +_ = init_translation() # setup module translations parser = argparse.ArgumentParser(description=_('Process log entries to generate profiles')) parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) parser.add_argument('-f', '--file', type=str, help=_('path to logfile')) parser.add_argument('-m', '--mark', type=str, help=_('mark in the log to start processing after')) parser.add_argument('-j', '--json', action='store_true', help=_('Input and Output in JSON')) +parser.add_argument('--configdir', type=str, help=argparse.SUPPRESS) args = parser.parse_args() if args.json: @@ -37,16 +36,15 @@ if args.json: logmark = args.mark or '' -apparmor.init_aa(profiledir=args.dir) +apparmor.init_aa(confdir=args.configdir, profiledir=args.dir) apparmor.set_logfile(args.file) aa_mountpoint = apparmor.check_for_apparmor() if not aa_mountpoint: - raise apparmor.AppArmorException(_('It seems AppArmor was not started. Please enable AppArmor and try again.')) + raise AppArmorException(_('It seems AppArmor was not started. Please enable AppArmor and try again.')) apparmor.loadincludes() apparmor.read_profiles(True) apparmor.do_logprof_pass(logmark) - diff --git a/utils/aa-logprof.8 b/utils/aa-logprof.8 index 1af782205..544a4f458 100644 --- a/utils/aa-logprof.8 +++ b/utils/aa-logprof.8 @@ -133,7 +133,7 @@ .\" ======================================================================== .\" .IX Title "AA-LOGPROF 8" -.TH AA-LOGPROF 8 "2022-11-22" "AppArmor 3.0.8" "AppArmor" +.TH AA-LOGPROF 8 "2024-02-02" "AppArmor 3.1.7" "AppArmor" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l @@ -186,7 +186,7 @@ those processes are set to run under their proper profiles. .SS "Responding to AppArmor Events" .IX Subsection "Responding to AppArmor Events" \&\fBaa-logprof\fR will generate a list of suggested profile changes that -the user can choose from, or they can create their own, to modifiy the +the user can choose from, or they can create their own, to modify the permission set of the profile so that the generated access violation will not re-occur. .PP diff --git a/utils/aa-logprof.8.html b/utils/aa-logprof.8.html index 79fbe84c7..ec1a72631 100644 --- a/utils/aa-logprof.8.html +++ b/utils/aa-logprof.8.html @@ -73,7 +73,7 @@ be surrounded with quotes to work correctly.</code></pre> <h2 id="Responding-to-AppArmor-Events">Responding to AppArmor Events</h2> -<p><b>aa-logprof</b> will generate a list of suggested profile changes that the user can choose from, or they can create their own, to modifiy the permission set of the profile so that the generated access violation will not re-occur.</p> +<p><b>aa-logprof</b> will generate a list of suggested profile changes that the user can choose from, or they can create their own, to modify the permission set of the profile so that the generated access violation will not re-occur.</p> <p>The user is then presented with info about the access including profile, path, old mode if there was a previous entry in the profile for this path, new mode, the suggestion list, and given these options:</p> diff --git a/utils/aa-logprof.pod b/utils/aa-logprof.pod index d5ef64356..4398acd45 100644 --- a/utils/aa-logprof.pod +++ b/utils/aa-logprof.pod @@ -67,7 +67,7 @@ those processes are set to run under their proper profiles. =head2 Responding to AppArmor Events B<aa-logprof> will generate a list of suggested profile changes that -the user can choose from, or they can create their own, to modifiy the +the user can choose from, or they can create their own, to modify the permission set of the profile so that the generated access violation will not re-occur. diff --git a/utils/aa-mergeprof b/utils/aa-mergeprof index 4b67719ea..2091bff93 100755 --- a/utils/aa-mergeprof +++ b/utils/aa-mergeprof @@ -16,33 +16,29 @@ import argparse import apparmor.aa - -import apparmor.severity import apparmor.cleanprofile as cleanprofile +import apparmor.severity import apparmor.ui as aaui - - - -# setup exception handling from apparmor.fail import enable_aa_exception_handler -enable_aa_exception_handler() - -# setup module translations from apparmor.translations import init_translation -_ = init_translation() + +enable_aa_exception_handler() # setup exception handling +_ = init_translation() # setup module translations 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('-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')) +# parser.add_argument('-a', '--auto', action='store_true', help=_('Automatically merge profiles, exits incase of *x conflicts')) +parser.add_argument('--configdir', type=str, help=argparse.SUPPRESS) args = parser.parse_args() args.other = None -apparmor.aa.init_aa(profiledir=args.dir) +apparmor.aa.init_aa(confdir=args.configdir, profiledir=args.dir) profiles = args.files + def find_profiles_from_files(files): profile_to_filename = dict() for file_name in files: @@ -53,6 +49,7 @@ def find_profiles_from_files(files): return profile_to_filename + def find_files_from_profiles(profiles): profile_to_filename = dict() apparmor.aa.read_profiles() @@ -64,6 +61,7 @@ def find_files_from_profiles(profiles): return profile_to_filename + def main(): base_profile_to_file = find_profiles_from_files(profiles) @@ -80,9 +78,10 @@ def main(): apparmor.aa.reset_aa() + def act(user_file, base_file, merging_profile): mergeprofiles = Merge(user_file, base_file) - #Get rid of common/superfluous stuff + # Get rid of common/superfluous stuff mergeprofiles.clear_common() mergeprofiles.ask_merge_questions() @@ -90,28 +89,29 @@ def act(user_file, base_file, merging_profile): apparmor.aa.changed[merging_profile] = True # force asking to save the profile apparmor.aa.save_profiles(True) + class Merge(object): def __init__(self, user, base): - #Read and parse base profile and save profile data, include data from it and reset them + # Read and parse base profile and save profile data, include data from it and reset them apparmor.aa.read_profile(base, True) self.base = cleanprofile.Prof(base) apparmor.aa.reset_aa() - #Read and parse user profile + # Read and parse user profile apparmor.aa.read_profile(user, True) self.user = cleanprofile.Prof(user) def clear_common(self): deleted = 0 - #Remove off the parts in base profile which are common/superfluous from user profile + # 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() def ask_merge_questions(self): other = self.base - log_dict = {'merge': other.aa} + log_dict = {'merge': apparmor.aa.split_to_merged(other.aa)} apparmor.aa.loadincludes() @@ -128,5 +128,6 @@ class Merge(object): apparmor.aa.ask_the_questions(log_dict) + if __name__ == '__main__': main() diff --git a/utils/aa-mergeprof.8 b/utils/aa-mergeprof.8 index 38c9172ca..c7d74e0c8 100644 --- a/utils/aa-mergeprof.8 +++ b/utils/aa-mergeprof.8 @@ -133,7 +133,7 @@ .\" ======================================================================== .\" .IX Title "AA-MERGEPROF 8" -.TH AA-MERGEPROF 8 "2022-11-22" "AppArmor 3.0.8" "AppArmor" +.TH AA-MERGEPROF 8 "2024-02-02" "AppArmor 3.1.7" "AppArmor" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l diff --git a/utils/aa-notify b/utils/aa-notify index 4bb65abad..a4db7c9e2 100755 --- a/utils/aa-notify +++ b/utils/aa-notify @@ -30,28 +30,29 @@ import argparse import atexit +import grp import os +import pwd import re import sys import time + import notify2 import psutil -import pwd -import grp import apparmor.aa as aa import apparmor.ui as aaui import apparmor.config as aaconfig +import LibAppArmor # C-library to parse one log line from apparmor.common import DebugLogger, open_file_read from apparmor.fail import enable_aa_exception_handler from apparmor.notify import get_last_login_timestamp from apparmor.translations import init_translation -import LibAppArmor # C-library to parse one log line def get_user_login(): - '''Portable function to get username. Should not trigger any - "OSError: [Errno 25] Inappropriate ioctl for device" errors in Giltab-CI''' + """Portable function to get username. Should not trigger any + "OSError: [Errno 25] Inappropriate ioctl for device" errors in Giltab-CI""" if os.name == "posix": username = pwd.getpwuid(os.geteuid()).pw_name else: @@ -61,7 +62,6 @@ def get_user_login(): return username - def format_event(event, logsource): output = [] @@ -143,7 +143,7 @@ def show_entries_since_last_login(logfile, username=get_user_login()): if args.verbose: print(_('Showing entries since {} logged in').format(username)) - print() # Newline + print() # Newline epoch_since = get_last_login_timestamp(username) if epoch_since == 0: print(_('ERROR: Could not find last login'), file=sys.stderr) @@ -159,10 +159,10 @@ def show_entries_since_days(logfile, since_days): def follow_apparmor_events(logfile, wait=0): - '''Follow AppArmor events and yield relevant entries until process stops''' + """Follow AppArmor events and yield relevant entries until process stops""" # If wait was given as argument but was type None (from ArgumentParser) - # ensure it type int and zero + # ensure it's type int and zero if not wait: wait = 0 @@ -211,7 +211,7 @@ def reopen_logfile_if_needed(logfile, logdata, log_inode, log_size): while retry: try: - # Reopen file if inode has chaneged, e.g. rename by logrotate + # Reopen file if inode has changed, e.g. rename by logrotate if os.stat(logfile).st_ino != log_inode: debug_logger.debug('Logfile was renamed, reload to read the new file.') logdata = open(logfile, 'r') @@ -239,7 +239,7 @@ def reopen_logfile_if_needed(logfile, logdata, log_inode, log_size): def get_apparmor_events(logfile, since=0): - '''Read audit events from log source and yield all relevant events''' + """Read audit events from log source and yield all relevant events""" # Get logdata from file # @TODO Implement more log sources in addition to just the logfile @@ -253,29 +253,29 @@ def get_apparmor_events(logfile, since=0): def parse_logdata(logsource): - '''Traverse any iterable log source and extract relevant AppArmor events''' - - RE_audit_time_id = '(msg=)?audit\([\d\.\:]+\):\s+' # 'audit(1282626827.320:411): ' - RE_kernel_time = '\[[\d\.\s]+\]' # '[ 1612.746129]' - RE_type_num = '1[45][0-9][0-9]' # 1400..1599 - RE_aa_or_op = '(apparmor=|operation=)' - - RE_log_parts = [ - 'kernel:\s+(' + RE_kernel_time + '\s+)?(audit:\s+)?type=' + RE_type_num + '\s+' + RE_audit_time_id + RE_aa_or_op, # v2_6 syslog - 'kernel:\s+(' + RE_kernel_time + '\s+)?' + RE_audit_time_id + 'type=' + RE_type_num + '\s+' + RE_aa_or_op, - 'type=(AVC|APPARMOR[_A-Z]*|' + RE_type_num + ')\s+' + RE_audit_time_id + '(type=' + RE_type_num + '\s+)?' + RE_aa_or_op, # v2_6 audit and dmesg - 'type=USER_AVC\s+' + RE_audit_time_id + '.*apparmor=', # dbus - 'type=UNKNOWN\[' + RE_type_num + '\]\s+' + RE_audit_time_id + RE_aa_or_op, - 'dbus\[[0-9]+\]:\s+apparmor=', # dbus + """Traverse any iterable log source and extract relevant AppArmor events""" + + re_audit_time_id = r'(msg=)?audit\([\d\.\:]+\):\s+' # 'audit(1282626827.320:411): ' + re_kernel_time = r'\[[\d\.\s]+\]' # '[ 1612.746129]' + re_type_num = '1[45][0-9][0-9]' # 1400..1599 + re_aa_or_op = '(apparmor=|operation=)' + + re_log_parts = [ + r'kernel:\s+(' + re_kernel_time + r'\s+)?(audit:\s+)?type=' + re_type_num + r'\s+' + re_audit_time_id + re_aa_or_op, # v2_6 syslog + r'kernel:\s+(' + re_kernel_time + r'\s+)?' + re_audit_time_id + 'type=' + re_type_num + r'\s+' + re_aa_or_op, + 'type=(AVC|APPARMOR[_A-Z]*|' + re_type_num + r')\s+' + re_audit_time_id + '(type=' + re_type_num + r'\s+)?' + re_aa_or_op, # v2_6 audit and dmesg + r'type=USER_AVC\s+' + re_audit_time_id + '.*apparmor=', # dbus + r'type=UNKNOWN\[' + re_type_num + r'\]\s+' + re_audit_time_id + re_aa_or_op, + r'dbus\[[0-9]+\]:\s+apparmor=', # dbus ] # Pre-filter log lines so that we hand over only relevant lines to LibAppArmor parsing - RE_LOG_ALL = re.compile('(' + '|'.join(RE_log_parts) + ')') + re_log_all = re.compile('(' + '|'.join(re_log_parts) + ')') for entry in logsource: # Check the start of the log line and only process lines from AppArmor - apparmor_entry = RE_LOG_ALL.search(entry) + apparmor_entry = re_log_all.search(entry) if apparmor_entry: # Parse the line using LibAppArmor (C library) # See aalogparse.h for data structure @@ -287,7 +287,7 @@ def parse_logdata(logsource): def drop_privileges(): - '''If running as root, drop privileges to USER if known, or fall-back to nobody_user/group''' + """If running as root, drop privileges to USER if known, or fall-back to nobody_user/group""" if os.geteuid() == 0: @@ -313,8 +313,9 @@ def drop_privileges(): os.setegid(int(next_gid)) os.seteuid(int(next_uid)) + def raise_privileges(): - '''If was running as user with saved user ID 0, raise back to root privileges''' + """If was running as user with saved user ID 0, raise back to root privileges""" if os.geteuid() != 0 and original_effective_user == 0: @@ -323,6 +324,7 @@ def raise_privileges(): # os.setgid(int(next_gid)) os.seteuid(original_effective_user) + def read_notify_conf(path, shell_config): try: shell_config.CONF_DIR = path @@ -332,11 +334,12 @@ def read_notify_conf(path, shell_config): except FileNotFoundError: return {} + def main(): - ''' + """ Main function of aa-notify that parses command line arguments and starts the requested operations. - ''' + """ global _, debug_logger, config, args global debug_docs_url, nobody_user, original_effective_user, timeformat @@ -370,6 +373,7 @@ def main(): parser.add_argument('-u', '--user', type=str, help=_('user to drop privileges to when not using sudo')) parser.add_argument('-w', '--wait', type=int, metavar=('NUM'), help=_('wait NUM seconds before displaying notifications (with -p)')) parser.add_argument('--debug', action='store_true', help=_('debug mode')) + parser.add_argument('--configdir', type=str, help=argparse.SUPPRESS) # If a TTY then assume running in test mode and fix output width if not sys.stdout.isatty(): @@ -396,11 +400,12 @@ def main(): # conf = None logfile = None - confdir = os.getenv('__AA_CONFDIR') - if confdir: - aa.init_aa(confdir) - else: - aa.init_aa() + if args.configdir: # prefer --configdir if given + confdir = args.configdir + else: # fallback to env variable (or None if not set) + confdir = os.getenv('__AA_CONFDIR') + + aa.init_aa(confdir=confdir) # Initialize aa.logfile aa.set_logfile(args.file) @@ -449,7 +454,7 @@ def main(): found_config_keys = config[''].keys() unknown_keys = [item for item in found_config_keys if item not in allowed_config_keys] for item in unknown_keys: - print(_('Warning! Configration item "{}" is unknown!').format(item)) + print(_('Warning! Configuration item "{}" is unknown!').format(item)) # Warn if use_group is defined and current group does not match defined if 'use_group' in config['']: @@ -516,7 +521,7 @@ def main(): os.environ['DBUS_SESSION_BUS_ADDRESS'] = 'unix:path=/run/user/{}/bus'.format(os.geteuid()) # Before use, notify2 must be initialized and the DBUS channel - # should be opened using the non-root user. This this step needs to + # should be opened using the non-root user. This step needs to # be executed after the drop_privileges(). notify2.init('AppArmor') @@ -528,7 +533,7 @@ def main(): n.show() # When notification is sent, raise privileged back to root if the - # original effective user id was zero (to be ableo to read AppArmor logs) + # original effective user id was zero (to be able to read AppArmor logs) raise_privileges() elif args.since_last: diff --git a/utils/aa-notify.8 b/utils/aa-notify.8 index ed7e3e834..49fc6b0dd 100644 --- a/utils/aa-notify.8 +++ b/utils/aa-notify.8 @@ -133,7 +133,7 @@ .\" ======================================================================== .\" .IX Title "AA-NOTIFY 8" -.TH AA-NOTIFY 8 "2022-11-22" "AppArmor 3.0.8" "AppArmor" +.TH AA-NOTIFY 8 "2024-02-02" "AppArmor 3.1.7" "AppArmor" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l diff --git a/utils/aa-remove-unknown b/utils/aa-remove-unknown old mode 100644 new mode 100755 index 81adfc1d9..0e00d6a03 --- a/utils/aa-remove-unknown +++ b/utils/aa-remove-unknown @@ -25,8 +25,8 @@ DRY_RUN=0 . $APPARMOR_FUNCTIONS usage() { - local progname="$1" - local rc="$2" + local progname="$0" + local rc="$1" local msg="usage: ${progname} [options] Remove profiles unknown to the system @@ -45,14 +45,14 @@ Options: } if [ "$#" -gt 1 ] ; then - usage "$0" 1 + usage 1 elif [ "$#" -eq 1 ] ; then - if [ "$1" = "-h" -o "$1" = "--help" ] ; then - usage "$0" 0 + if [ "$1" = "-h" ] || [ "$1" = "--help" ] ; then + usage 0 elif [ "$1" = "-n" ] ; then DRY_RUN=1 else - usage "$0" 1 + usage 1 fi fi @@ -63,7 +63,7 @@ fi # 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 +if ! IFS= read -r _ < "$PROFILES" ; then echo "ERROR: Unable to read apparmorfs profiles file" 1>&2 exit 1 elif [ ! -w "$REMOVE" ] ; then @@ -79,6 +79,7 @@ fi # on removing the parent profile when the profile has had its # child profile names changed. +# shellcheck disable=SC2086 LOADED_PROFILES=$("$PARSER" -N $PROFILE_DIRS) || { ret=$? echo 'apparmor_parser exited with failure, aborting.' >&2 @@ -103,7 +104,7 @@ END { } } ' | LC_COLLATE=C sort -r | \ - while IFS= read profile ; do + while IFS= read -r profile ; do if [ "$DRY_RUN" -ne 0 ]; then echo "Would remove '${profile}'" else diff --git a/utils/aa-remove-unknown.8 b/utils/aa-remove-unknown.8 index 0883f2975..b045e06b8 100644 --- a/utils/aa-remove-unknown.8 +++ b/utils/aa-remove-unknown.8 @@ -133,7 +133,7 @@ .\" ======================================================================== .\" .IX Title "AA-REMOVE-UNKNOWN 8" -.TH AA-REMOVE-UNKNOWN 8 "2022-11-22" "AppArmor 3.0.8" "AppArmor" +.TH AA-REMOVE-UNKNOWN 8 "2024-02-02" "AppArmor 3.1.7" "AppArmor" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l diff --git a/utils/aa-sandbox b/utils/aa-sandbox index 8a7e0e82b..e8aec1054 100755 --- a/utils/aa-sandbox +++ b/utils/aa-sandbox @@ -9,14 +9,14 @@ # # ------------------------------------------------------------------ -import apparmor.sandbox -from apparmor.common import error import optparse import sys -# setup exception handling +import apparmor.sandbox +from apparmor.common import error from apparmor.fail import enable_aa_exception_handler -enable_aa_exception_handler() + +enable_aa_exception_handler() # setup exception handling if __name__ == "__main__": argv = sys.argv diff --git a/utils/aa-unconfined b/utils/aa-unconfined index 3cb0b52b7..2660c5826 100755 --- a/utils/aa-unconfined +++ b/utils/aa-unconfined @@ -21,18 +21,16 @@ import sys import apparmor.aa as aa import apparmor.ui as ui -import apparmor.common - -# setup exception handling +from apparmor.common import AppArmorException, open_file_read from apparmor.fail import enable_aa_exception_handler -enable_aa_exception_handler() - -# setup module translations from apparmor.translations import init_translation -_ = init_translation() + +enable_aa_exception_handler() # setup exception handling +_ = init_translation() # setup module translations parser = argparse.ArgumentParser(description=_("Lists unconfined processes having tcp or udp ports")) parser.add_argument("--paranoid", action="store_true", help=_("scan all processes from /proc")) +parser.add_argument('--configdir', type=str, help=argparse.SUPPRESS) bin_group = parser.add_mutually_exclusive_group() bin_group.add_argument("--with-ss", action='store_true', help=_("use ss(8) to find listening processes (default)")) bin_group.add_argument("--with-netstat", action='store_true', help=_("use netstat(8) to find listening processes")) @@ -40,19 +38,20 @@ args = parser.parse_args() paranoid = args.paranoid -aa.init_aa() +aa.init_aa(confdir=args.configdir) + 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.")) + raise AppArmorException(_("It seems AppArmor was not started. Please enable AppArmor and try again.")) def get_all_pids(): - '''Return a set of all pids via walking /proc''' + """Return a set of all pids via walking /proc""" return set(filter(lambda x: re.search(r"^\d+$", x), aa.get_subdirectories("/proc"))) def get_pids_ss(ss='ss'): - '''Get a set of pids listening on network sockets via ss(8)''' + """Get a set of pids listening on network sockets via ss(8)""" regex_lines = re.compile(r"^(tcp|udp|raw|p_dgr)\s.+\s+users:(?P<users>\(\(.*\)\))$") regex_users_pids = re.compile(r'(\("[^"]+",(pid=)?(\d+),[^)]+\))') @@ -78,8 +77,8 @@ def get_pids_ss(ss='ss'): def get_pids_netstat(netstat='netstat'): - '''Get a set of pids listening on network sockets via netstat(8)''' - regex_tcp_udp = re.compile(r"^(tcp|udp|raw)6?\s+\d+\s+\d+\s+\S+\:(\d+)\s+\S+\:(\*|\d+)\s+(LISTEN|\d+|\s+)\s+(?P<pid>\d+)\/(\S+)") + """Get a set of pids listening on network sockets via netstat(8)""" + regex_tcp_udp = re.compile(r"^(tcp|udp|raw)6?\s+\d+\s+\d+\s+\S+:(\d+)\s+\S+:(\*|\d+)\s+(LISTEN|\d+|\s+)\s+(?P<pid>\d+)/(\S+)") cmd = [netstat, '-nlp', '--protocol', 'inet,inet6'] my_env = os.environ.copy() @@ -103,7 +102,7 @@ def read_proc_current(filename): attr = None if os.path.exists(filename): - with apparmor.common.open_file_read(filename) as current: + with open_file_read(filename) as current: for line in current: line = line.strip() if line.endswith(' (complain)', 1) or line.endswith(' (enforce)', 1) or line.endswith(' (kill)', 1): # enforce at least one char as profile name @@ -113,10 +112,19 @@ def read_proc_current(filename): return attr +def escape_special_chars(data): + """escape special characters in program names so that they can't mess up the terminal""" + data = repr(data) + if len(data) > 1 and data.startswith("'") and data.endswith("'"): + return data[1:-1] + else: + return data + + pids = set() if paranoid: pids = get_all_pids() -elif args.with_ss or (not args.with_netstat and (os.path.exists('/bin/ss') or os.path.exists('/usr/bin/ss'))): +elif args.with_ss or (not args.with_netstat and (aa.which("ss") is not None)): pids = get_pids_ss() else: pids = get_pids_netstat() @@ -124,6 +132,7 @@ else: for pid in sorted(map(int, pids)): try: prog = os.readlink("/proc/%s/exe" % pid) + prog = escape_special_chars(prog) except OSError: continue @@ -135,11 +144,12 @@ for pid in sorted(map(int, pids)): pname = None cmdline = None - with apparmor.common.open_file_read("/proc/%s/cmdline" % pid) as cmd: + with open_file_read("/proc/%s/cmdline" % pid) as cmd: cmdline = cmd.readlines()[0] pname = cmdline.split("\0")[0] if '/' in pname and pname != prog: pname = "(%s)" % pname + pname = escape_special_chars(pname) else: pname = "" regex_interpreter = re.compile(r"^(/usr)?/bin/(python|perl|bash|dash|sh)$") @@ -147,6 +157,7 @@ for pid in sorted(map(int, pids)): if regex_interpreter.search(prog): cmdline = re.sub(r"\x00", " ", cmdline) cmdline = re.sub(r"\s+$", "", cmdline).strip() + cmdline = escape_special_chars(cmdline) ui.UI_Info(_("%(pid)s %(program)s (%(commandline)s) not confined") % {'pid': pid, 'program': prog, 'commandline': cmdline}) else: @@ -157,6 +168,7 @@ for pid in sorted(map(int, pids)): if regex_interpreter.search(prog): cmdline = re.sub(r"\0", " ", cmdline) cmdline = re.sub(r"\s+$", "", cmdline).strip() + cmdline = escape_special_chars(cmdline) ui.UI_Info(_("%(pid)s %(program)s (%(commandline)s) confined by '%(attribute)s'") % {'pid': pid, 'program': prog, 'commandline': cmdline, 'attribute': attr}) else: if pname and pname[-1] == ')': diff --git a/utils/aa-unconfined.8 b/utils/aa-unconfined.8 index 7392e9e3c..1260dd759 100644 --- a/utils/aa-unconfined.8 +++ b/utils/aa-unconfined.8 @@ -133,7 +133,7 @@ .\" ======================================================================== .\" .IX Title "AA-UNCONFINED 8" -.TH AA-UNCONFINED 8 "2022-11-22" "AppArmor 3.0.8" "AppArmor" +.TH AA-UNCONFINED 8 "2024-02-02" "AppArmor 3.1.7" "AppArmor" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l diff --git a/utils/apparmor/aa.py b/utils/apparmor/aa.py index 4ba484dad..2dfeb3541 100644 --- a/utils/apparmor/aa.py +++ b/utils/apparmor/aa.py @@ -1,6 +1,6 @@ # ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.com> -# Copyright (C) 2014-2019 Christian Boltz <apparmor@cboltz.de> +# Copyright (C) 2014-2021 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 @@ -13,64 +13,48 @@ # # ---------------------------------------------------------------------- # No old version logs, only 2.6 + supported -from __future__ import division, with_statement +import atexit import os import re import shutil -import subprocess +import subprocess # nosec import sys import time import traceback -import atexit -import tempfile +from copy import deepcopy +from shutil import which +from tempfile import NamedTemporaryFile import apparmor.config import apparmor.logparser +import apparmor.rules as aarules import apparmor.severity - -from copy import deepcopy - -from apparmor.aare import AARE - -from apparmor.common import (AppArmorException, AppArmorBug, is_skippable_file, open_file_read, valid_path, hasher, - split_name, type_is_str, open_file_write, DebugLogger) - import apparmor.ui as aaui - -from apparmor.regex import (RE_PROFILE_START, RE_PROFILE_END, - RE_PROFILE_BOOLEAN, RE_PROFILE_CONDITIONAL, - RE_PROFILE_CONDITIONAL_VARIABLE, RE_PROFILE_CONDITIONAL_BOOLEAN, - RE_PROFILE_CHANGE_HAT, - RE_PROFILE_HAT_DEF, RE_PROFILE_MOUNT, - RE_PROFILE_PIVOT_ROOT, - 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.aare import AARE +from apparmor.common import ( + AppArmorBug, AppArmorException, DebugLogger, cmd, combine_profname, hasher, + is_skippable_file, open_file_read, open_file_write, split_name, valid_path) +from apparmor.profile_list import ProfileList, preamble_ruletypes from apparmor.profile_storage import ProfileStorage, add_or_remove_flag, ruletypes - -import apparmor.rules as aarules - -from apparmor.rule.abi import AbiRule -from apparmor.rule.alias import AliasRule -from apparmor.rule.capability import CapabilityRule -from apparmor.rule.change_profile import ChangeProfileRule -from apparmor.rule.dbus import DbusRule -from apparmor.rule.file import FileRule -from apparmor.rule.include import IncludeRule -from apparmor.rule.network import NetworkRule -from apparmor.rule.ptrace import PtraceRule -from apparmor.rule.rlimit import RlimitRule -from apparmor.rule.signal import SignalRule -from apparmor.rule.variable import VariableRule -from apparmor.rule import quote_if_needed - -# setup module translations +from apparmor.regex import ( + RE_HAS_COMMENT_SPLIT, RE_PROFILE_CHANGE_HAT, RE_PROFILE_CONDITIONAL, + RE_PROFILE_CONDITIONAL_BOOLEAN, RE_PROFILE_CONDITIONAL_VARIABLE, RE_PROFILE_END, + RE_PROFILE_HAT_DEF, RE_PROFILE_MOUNT, RE_PROFILE_PIVOT_ROOT, RE_PROFILE_START, + RE_PROFILE_UNIX, RE_RULE_HAS_COMMA, parse_profile_start_line, re_match_include) +from apparmor.rule.abi import AbiRule +from apparmor.rule.capability import CapabilityRule +from apparmor.rule.change_profile import ChangeProfileRule +from apparmor.rule.dbus import DbusRule +from apparmor.rule.file import FileRule +from apparmor.rule.include import IncludeRule +from apparmor.rule.network import NetworkRule +from apparmor.rule.ptrace import PtraceRule +from apparmor.rule.signal import SignalRule from apparmor.translations import init_translation + _ = init_translation() -# Setup logging incase of debugging is enabled +# Setup logging in case debugging is enabled debug_logger = DebugLogger('aa') # The database for severity @@ -97,12 +81,11 @@ extra_profiles = ProfileList() # format: user_globs['/foo*'] = AARE('/foo*') user_globs = {} -## Variables used under logprof -transitions = hasher() +# let ask_addhat() remember answers for already-seen change_hat events +transitions = {} -aa = hasher() # Profiles originally in sd, replace by aa +aa = {} # Profiles originally in sd, replace by aa original_aa = hasher() -extras = hasher() # Inactive profiles from extras ### end our changed = dict() @@ -110,27 +93,31 @@ created = [] helpers = dict() # Preserve this between passes # was our ### logprof ends + def reset_aa(): - ''' Reset the most important global variables + """Reset the most important global variables - Used by aa-mergeprof and some tests. - ''' + Used by aa-mergeprof and some tests. + """ global aa, include, active_profiles, original_aa - aa = hasher() + aa = {} include = dict() active_profiles = ProfileList() original_aa = hasher() + def on_exit(): """Shutdowns the logger and records exit if debugging enabled""" debug_logger.debug('Exiting..') debug_logger.shutdown() + # Register the on_exit method with atexit atexit.register(on_exit) + def check_for_LD_XXX(file): """Returns True if specified program contains references to LD_PRELOAD or LD_LIBRARY_PATH to give the Px/Ux code better suggestions""" @@ -146,6 +133,7 @@ def check_for_LD_XXX(file): return True return False + def fatal_error(message): # Get the traceback to the message tb_stack = traceback.format_list(traceback.extract_stack()) @@ -158,6 +146,7 @@ def fatal_error(message): aaui.UI_Important(message) sys.exit(1) + def check_for_apparmor(filesystem='/proc/filesystems', mounts='/proc/mounts'): """Finds and returns the mountpoint for apparmor None otherwise""" support_securityfs = False @@ -181,17 +170,6 @@ def check_for_apparmor(filesystem='/proc/filesystems', mounts='/proc/mounts'): break return aa_mountpoint -def which(file): - """Returns the executable fullpath for the file, None otherwise""" - if sys.version_info >= (3, 3): - return shutil.which(file) - env_dirs = os.getenv('PATH').split(':') - for env_dir in env_dirs: - env_path = os.path.join(env_dir, file) - # Test if the path is executable or not - if os.access(env_path, os.X_OK): - return env_path - return None def get_full_path(original_path): """Return the full path after resolving any symlinks""" @@ -213,6 +191,7 @@ def get_full_path(original_path): path = os.path.join(direc, link) return os.path.realpath(path) + def find_executable(bin_path): """Returns the full executable path for the given executable, None otherwise""" full_bin = None @@ -227,6 +206,7 @@ def find_executable(bin_path): return full_bin return None + def get_profile_filename_from_profile_name(profile, get_new=False): """Returns the full profile name for the given profile name""" @@ -237,6 +217,7 @@ def get_profile_filename_from_profile_name(profile, get_new=False): 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""" @@ -247,16 +228,18 @@ def get_profile_filename_from_attachment(profile, get_new=False): if get_new: return get_new_profile_filename(profile) + def get_new_profile_filename(profile): - '''Compose filename for a new profile''' + """Compose filename for a new profile""" if profile.startswith('/'): # Remove leading / - profile = profile[1:] + filename = profile[1:] else: - profile = "profile_" + profile - profile = profile.replace('/', '.') - full_profilename = os.path.join(profile_dir, profile) - return full_profilename + filename = "profile_" + profile + filename = filename.replace('/', '.') + filename = os.path.join(profile_dir, filename) + return filename + def name_to_prof_filename(prof_filename): """Returns the profile""" @@ -272,6 +255,7 @@ def name_to_prof_filename(prof_filename): return None, None + def complain(path): """Sets the profile to complain mode if it exists""" prof_filename, name = name_to_prof_filename(path) @@ -279,6 +263,7 @@ def complain(path): fatal_error(_("Can't find %s") % path) set_complain(prof_filename, name) + def enforce(path): """Sets the profile to enforce mode if it exists""" prof_filename, name = name_to_prof_filename(path) @@ -286,6 +271,7 @@ def enforce(path): fatal_error(_("Can't find %s") % path) set_enforce(prof_filename, name) + def set_complain(filename, program): """Sets the profile to complain mode""" aaui.UI_Info(_('Setting %s to complain mode.') % (filename if program is None else program)) @@ -295,6 +281,7 @@ def set_complain(filename, program): change_profile_flags(filename, program, ['enforce', 'kill', 'unconfined', 'prompt'], False) # remove conflicting mode flags change_profile_flags(filename, program, 'complain', True) + def set_enforce(filename, program): """Sets the profile to enforce mode""" aaui.UI_Info(_('Setting %s to enforce mode.') % (filename if program is None else program)) @@ -302,22 +289,24 @@ def set_enforce(filename, program): delete_symlink('disable', filename) change_profile_flags(filename, program, ['complain', 'kill', 'unconfined', 'prompt'], False) # remove conflicting and complain mode flags + def delete_symlink(subdir, filename): path = filename link = re.sub('^%s' % profile_dir, '%s/%s' % (profile_dir, subdir), path) if link != path and os.path.islink(link): os.remove(link) + def create_symlink(subdir, filename): path = filename bname = os.path.basename(filename) if not bname: raise AppArmorException(_('Unable to find basename for %s.') % filename) - #print(filename) + # print(filename) link = re.sub('^%s' % profile_dir, '%s/%s' % (profile_dir, subdir), path) - #print(link) - #link = link + '/%s'%bname - #print(link) + # print(link) + # link = link + '/%s'%bname + # print(link) symlink_dir = os.path.dirname(link) if not os.path.exists(symlink_dir): # If the symlink directory does not exist create it @@ -327,7 +316,10 @@ def create_symlink(subdir, filename): try: os.symlink(filename, link) except: - raise AppArmorException(_('Could not create %(link)s symlink to %(file)s.') % { 'link': link, 'file': filename }) + raise AppArmorException( + _('Could not create %(link)s symlink to %(file)s.') + % {'link': link, 'file': filename}) + def head(file): """Returns the first/head line of the file""" @@ -342,14 +334,17 @@ def head(file): else: raise AppArmorException(_('Unable to read first line from %s: File Not Found') % file) + def get_output(params): - '''Runs the program with the given args and returns the return code and stdout (as list of lines)''' + """Runs the program with the given args and returns the return code and stdout (as list of lines)""" try: # Get the output of the program - output = subprocess.check_output(params) + output = subprocess.check_output(params) # nosec ret = 0 except OSError as e: - raise AppArmorException(_("Unable to fork: %(program)s\n\t%(error)s") % { 'program': params[0], 'error': str(e) }) + raise AppArmorException( + _("Unable to fork: %(program)s\n\t%(error)s") + % {'program': params[0], 'error': str(e)}) except subprocess.CalledProcessError as e: # If exit code != 0 output = e.output ret = e.returncode @@ -357,22 +352,23 @@ def get_output(params): output = output.decode('utf-8').split('\n') # Remove the extra empty string caused due to \n if present - if output[len(output) - 1] == '': + if not output[-1]: output.pop() return (ret, output) + def get_reqs(file): """Returns a list of paths from ldd output""" - pattern1 = re.compile('^\s*\S+ => (\/\S+)') - pattern2 = re.compile('^\s*(\/\S+)') + pattern1 = re.compile(r'^\s*\S+ => (/\S+)') + pattern2 = re.compile(r'^\s*(/\S+)') reqs = [] ldd = conf.find_first_file(cfg['settings'].get('ldd')) or '/usr/bin/ldd' if not os.path.isfile(ldd) or not os.access(ldd, os.EX_OK): - raise AppArmorException('Can\'t find ldd') + raise AppArmorException("Can't find ldd") - ret, ldd_out = get_output([ldd, file]) + ret, ldd_out = get_output((ldd, file)) if ret == 0 or ret == 1: for line in ldd_out: if 'not a dynamic executable' in line: # comes with ret == 1 @@ -390,6 +386,7 @@ def get_reqs(file): reqs.append(match.groups()[0]) return reqs + def handle_binfmt(profile, path): """Modifies the profile to add the requirements""" reqs_processed = dict() @@ -399,7 +396,7 @@ def handle_binfmt(profile, path): library = get_full_path(library) # resolve symlinks if not reqs_processed.get(library, False): if get_reqs(library): - reqs += get_reqs(library) + reqs.extend(get_reqs(library)) reqs_processed[library] = True library_rule = FileRule(library, 'mr', None, FileRule.ALL, owner=False, log_event=True) @@ -412,13 +409,14 @@ def handle_binfmt(profile, path): profile['file'].add(library_rule) + def get_interpreter_and_abstraction(exec_target): - '''Check if exec_target is a script. + """Check if exec_target is a script. If a hashbang is found, check if we have an abstraction for it. Returns (interpreter_path, abstraction) - interpreter_path is none if exec_target is not a script or doesn't have a hashbang line - - abstraction is None if no matching abstraction exists''' + - abstraction is None if no matching abstraction exists""" if not os.path.exists(exec_target): aaui.UI_Important(_('Execute target %s does not exist!') % exec_target) @@ -437,21 +435,22 @@ def get_interpreter_and_abstraction(exec_target): interpreter_path = get_full_path(interpreter) interpreter = re.sub('^(/usr)?/bin/', '', interpreter_path) - if interpreter in ['bash', 'dash', 'sh']: + if interpreter in ('bash', 'dash', 'sh'): abstraction = 'abstractions/bash' elif interpreter == 'perl': abstraction = 'abstractions/perl' - elif re.search('^python([23]|[23]\.[0-9]+)?$', interpreter): + elif re.search(r'^python([23]|[23]\.[0-9]+)?$', interpreter): abstraction = 'abstractions/python' - elif re.search('^ruby([0-9]+(\.[0-9]+)*)?$', interpreter): + elif re.search(r'^ruby([0-9]+(\.[0-9]+)*)?$', interpreter): abstraction = 'abstractions/ruby' else: abstraction = None return interpreter_path, abstraction + def create_new_profile(localfile, is_stub=False): - local_profile = hasher() + local_profile = {} local_profile[localfile] = ProfileStorage('NEW', localfile, 'create_new_profile()') local_profile[localfile]['flags'] = 'complain' @@ -482,16 +481,19 @@ def create_new_profile(localfile, is_stub=False): for hatglob in cfg['required_hats'].keys(): if re.search(hatglob, localfile): for hat in sorted(cfg['required_hats'][hatglob].split()): - if not local_profile.get(hat, False): - local_profile[hat] = ProfileStorage('NEW', hat, 'create_new_profile() required_hats') - local_profile[hat]['flags'] = 'complain' + full_hat = combine_profname([localfile, hat]) + if not local_profile.get(full_hat, False): + local_profile[full_hat] = ProfileStorage('NEW', hat, 'create_new_profile() required_hats') + local_profile[full_hat]['is_hat'] = True + local_profile[full_hat]['flags'] = 'complain' if not is_stub: created.append(localfile) changed[localfile] = True debug_logger.debug("Profile for %s:\n\t%s" % (localfile, local_profile.__str__())) - return {localfile: local_profile} + return local_profile + def delete_profile(local_prof): """Deletes the specified file from the disk and remove it from our list""" @@ -501,7 +503,8 @@ def delete_profile(local_prof): if aa.get(local_prof, False): aa.pop(local_prof) - #prof_unload(local_prof) + # prof_unload(local_prof) + def confirm_and_abort(): ans = aaui.UI_YesNo(_('Are you sure you want to abandon this set of profile changes and exit?'), 'n') @@ -511,18 +514,22 @@ def confirm_and_abort(): delete_profile(prof) sys.exit(0) + def get_profile(prof_name): - '''search for inactive/extra profile, and ask if it should be used''' + """search for inactive/extra profile, and ask if it should be used""" - if not extras.get(prof_name, False): + if not extra_profiles.profiles.get(prof_name, False): return None # no inactive profile found # TODO: search based on the attachment, not (only?) based on the profile name # (Note: in theory, multiple inactive profiles (with different profile names) could exist for a binary.) - inactive_profile = {prof_name: extras[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'] = '' + inactive_profile = deepcopy(extra_profiles.get_profile_and_childs(prof_name)) + + orig_filename = inactive_profile[prof_name]['filename'] # needed for CMD_VIEW_PROFILE + + for prof in inactive_profile: + inactive_profile[prof]['flags'] = 'complain' # TODO: preserve other flags, if any + inactive_profile[prof]['filename'] = '' # ensure active_profiles has the /etc/apparmor.d/ filename initialized # TODO: ideally serialize_profile() shouldn't always use active_profiles @@ -533,7 +540,7 @@ def get_profile(prof_name): uname = 'Inactive local profile for %s' % prof_name profile_hash = { uname: { - 'profile': serialize_profile(inactive_profile[prof_name], prof_name, {}), + 'profile': serialize_profile(inactive_profile, prof_name, {}), 'profile_data': inactive_profile, } } @@ -560,17 +567,18 @@ def get_profile(prof_name): return None # CMD_CREATE_PROFILE chosen + def autodep(bin_name, pname=''): bin_full = None if bin_name: bin_full = find_executable(bin_name) - #if not bin_full: + # if not bin_full: # bin_full = bin_name - #if not bin_full.startswith('/'): - #return None - # Return if exectuable path not found + # if not bin_full.startswith('/'): + # return + # Return if executable path not found if not bin_full: - return None + return else: bin_full = pname # for named profiles @@ -581,16 +589,16 @@ def autodep(bin_name, pname=''): if not profile_data: profile_data = create_new_profile(pname) file = get_profile_filename_from_profile_name(pname, True) - profile_data[pname][pname]['filename'] = file # change filename from extra_profile_dir to /etc/apparmor.d/ + profile_data[pname]['filename'] = file # change filename from extra_profile_dir to /etc/apparmor.d/ attach_profile_data(aa, profile_data) attach_profile_data(original_aa, profile_data) - attachment = profile_data[pname][pname]['attachment'] + attachment = profile_data[pname]['attachment'] if not attachment and pname.startswith('/'): - active_profiles.add_profile(file, pname, pname) # use name as name and attachment - else: - active_profiles.add_profile(file, pname, attachment) + attachment = pname # use name as name and attachment + + active_profiles.add_profile(file, pname, attachment) if os.path.isfile(profile_dir + '/abi/3.0'): active_profiles.add_abi(file, AbiRule('abi/3.0', False, True)) @@ -598,6 +606,7 @@ def autodep(bin_name, pname=''): active_profiles.add_inc_ie(file, IncludeRule('tunables/global', False, True)) write_profile_ui_feedback(pname) + def get_profile_flags(filename, program): # To-Do # XXX If more than one profile in a file then second one is being ignored XXX @@ -612,11 +621,13 @@ def get_profile_flags(filename, program): else: profile_glob = AARE(matches['profile'], True) flags = matches['flags'] - if (program is not None and profile_glob.match(program)) or program is None or program == matches['profile']: + if ((program is not None and profile_glob.match(program)) + or program is None or program == matches['profile']): return flags raise AppArmorException(_('%s contains no profile') % filename) + def change_profile_flags(prof_filename, program, flag, set_flag): """Reads the old profile file and updates the flags accordingly""" # TODO: count the number of matching lines (separated by profile and hat?) and return it @@ -624,56 +635,49 @@ def change_profile_flags(prof_filename, program, flag, set_flag): # TODO: change child profile flags even if program is specified found = False + depth = -1 - if not flag or (type_is_str(flag) and flag.strip() == ''): + if not flag or (type(flag) is str and not flag.strip()): raise AppArmorBug('New flag for %s is empty' % prof_filename) with open_file_read(prof_filename) as f_in: - temp_file = tempfile.NamedTemporaryFile('w', prefix=prof_filename, suffix='~', delete=False, dir=profile_dir) + temp_file = NamedTemporaryFile('w', prefix=prof_filename, suffix='~', delete=False, dir=profile_dir) + temp_file.close() shutil.copymode(prof_filename, temp_file.name) with open_file_write(temp_file.name) as f_out: - for line in f_in: + for lineno, line in enumerate(f_in): if RE_PROFILE_START.search(line): - matches = parse_profile_start_line(line, prof_filename) - space = matches['leadingspace'] or '' - profile = matches['profile'] - old_flags = matches['flags'] + depth += 1 + (profile, hat, prof_storage) = ProfileStorage.parse(line, prof_filename, lineno, '', '') + old_flags = prof_storage['flags'] newflags = ', '.join(add_or_remove_flag(old_flags, flag, set_flag)) - if (matches['attachment'] is not None): - profile_glob = AARE(matches['attachment'], True) + if (prof_storage['attachment']): + profile_glob = AARE(prof_storage['attachment'], True) else: - profile_glob = AARE(matches['profile'], False) # named profiles can come without an attachment path specified ("profile foo {...}") + profile_glob = AARE(prof_storage['name'], False) # named profiles can come without an attachment path specified ("profile foo {...}") - if (program is not None and profile_glob.match(program)) or program is None or program == matches['profile']: + if (program is not None and profile_glob.match(program)) or program is None or program == prof_storage['name']: found = True if program is not None and program != profile: aaui.UI_Info(_('Warning: profile %s represents multiple programs') % profile) - header_data = { - 'attachment': matches['attachment'] or '', - 'flags': newflags, - 'profile_keyword': matches['profile_keyword'], - 'header_comment': matches['comment'] or '', - 'xattrs': matches['xattrs'], - } - line = write_header(header_data, len(space)/2, profile, False, True) + prof_storage['flags'] = newflags + + line = prof_storage.get_header(depth, profile, False) line = '%s\n' % line[0] elif RE_PROFILE_HAT_DEF.search(line): - matches = RE_PROFILE_HAT_DEF.search(line) - space = matches.group('leadingspace') or '' - hat_keyword = matches.group('hat_keyword') - hat = matches.group('hat') - old_flags = matches['flags'] + depth += 1 + (profile, hat, prof_storage) = ProfileStorage.parse(line, prof_filename, lineno, '', '') + old_flags = prof_storage['flags'] newflags = ', '.join(add_or_remove_flag(old_flags, flag, set_flag)) - comment = matches.group('comment') or '' - if comment: - comment = ' %s' % comment + prof_storage['flags'] = newflags + + line = prof_storage.get_header(depth, profile, False) + line = '%s\n' % line[0] + elif RE_PROFILE_END.search(line): + depth -= 1 - if newflags: - line = '%s%s%s flags=(%s) {%s\n' % (space, hat_keyword, hat, newflags, comment) - else: - line = '%s%s%s {%s\n' % (space, hat_keyword, hat, comment) f_out.write(line) os.rename(temp_file.name, prof_filename) @@ -683,6 +687,7 @@ def change_profile_flags(prof_filename, program, flag, set_flag): else: raise AppArmorException("%(file)s doesn't contain a valid profile for %(profile)s (syntax error?)" % {'file': prof_filename, 'profile': program}) + def profile_exists(program): """Returns True if profile exists, False otherwise""" # Check cache of profiles @@ -691,7 +696,7 @@ def profile_exists(program): return True # Check the disk for profile prof_path = get_profile_filename_from_attachment(program, True) - #print(prof_path) + # print(prof_path) if os.path.isfile(prof_path): # Add to cache of profile raise AppArmorBug('Reached strange condition in profile_exists(), please open a bugreport!') @@ -699,6 +704,7 @@ def profile_exists(program): # return True return False + def build_x_functions(default, options, exec_toggle): ret_list = [] fallback_toggle = False @@ -737,11 +743,12 @@ def build_x_functions(default, options, exec_toggle): if fallback_toggle: ret_list.append('CMD_EXEC_IX_ON') - ret_list += ['CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED'] + ret_list.extend(('CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED')) return ret_list + def ask_addhat(hashlog): - '''ask the user about change_hat events (requests to add a hat)''' + """ask the user about change_hat events (requests to add a hat)""" for aamode in hashlog: for profile in hashlog[aamode]: @@ -760,23 +767,22 @@ def ask_addhat(hashlog): if re.search(hatglob, profile): default_hat = cfg['defaulthat'][hatglob] - context = profile - context = context + ' -> ^%s' % hat + context = profile + ' -> ^%s' % hat ans = transitions.get(context, 'XXXINVALIDXXX') - while ans not in ['CMD_ADDHAT', 'CMD_USEDEFAULT', 'CMD_DENY']: + while ans not in ('CMD_ADDHAT', 'CMD_USEDEFAULT', 'CMD_DENY'): q = aaui.PromptQuestion() - q.headers += [_('Profile'), profile] + q.headers.extend((_('Profile'), profile)) if default_hat: - q.headers += [_('Default Hat'), default_hat] + q.headers.extend((_('Default Hat'), default_hat)) - q.headers += [_('Requested Hat'), hat] + q.headers.extend((_('Requested Hat'), hat)) q.functions.append('CMD_ADDHAT') if default_hat: q.functions.append('CMD_USEDEFAULT') - q.functions += ['CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED'] + q.functions.extend(('CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED')) q.default = 'CMD_DENY' if aamode == 'PERMITTING': @@ -808,26 +814,23 @@ def ask_addhat(hashlog): hashlog[aamode][full_hat]['final_name'] = '' continue + def ask_exec(hashlog): - '''ask the user about exec events (requests to execute another program) and which exec mode to use''' + """ask the user about exec events (requests to execute another program) and which exec mode to use""" for aamode in hashlog: - for profile in hashlog[aamode]: - if '//' in hashlog[aamode][profile]['final_name'] and hashlog[aamode][profile]['exec'].keys(): - # TODO: is this really needed? Or would removing Cx from the options be good enough? - aaui.UI_Important('WARNING: Ignoring exec event in %s, nested profiles are not supported yet.' % hashlog[aamode][profile]['final_name']) - continue + for full_profile in hashlog[aamode]: + profile, hat = split_name(full_profile) # XXX temporary solution to avoid breaking the existing code - hat = profile # XXX temporary solution to avoid breaking the existing code - - for exec_target in hashlog[aamode][profile]['exec']: - for target_profile in hashlog[aamode][profile]['exec'][exec_target]: + for exec_target in hashlog[aamode][full_profile]['exec']: + for target_profile in hashlog[aamode][full_profile]['exec'][exec_target]: to_name = '' if os.path.isdir(exec_target): - raise AppArmorBug('exec permissions requested for directory %s. This should not happen - please open a bugreport!' % exec_target) + raise AppArmorBug( + 'exec permissions requested for directory %s (profile %s). This should not happen - please open a bugreport!' % (exec_target, full_profile)) - if not aa[profile][hat]: + if not aa[profile].get(hat): continue # ignore log entries for non-existing profiles exec_event = FileRule(exec_target, None, FileRule.ANY_EXEC, FileRule.ALL, owner=False, log_event=True) @@ -843,12 +846,14 @@ def ask_exec(hashlog): if True: options = cfg['qualifiers'].get(exec_target, 'ipcnu') - ### If profiled program executes itself only 'ix' option - ##if exec_target == profile: - ##options = 'i' + # If profiled program executes itself only 'ix' option + # if exec_target == profile: + # options = 'i' # Don't allow hats to cx? - options.replace('c', '') + if '//' in hashlog[aamode][full_profile]['final_name'] and hashlog[aamode][full_profile]['exec'].keys(): + options = options.replace('c', '') + # Add deny to options options += 'd' # Define the default option @@ -870,27 +875,31 @@ def ask_exec(hashlog): prof_filename = get_profile_filename_from_profile_name(profile) if prof_filename and active_profiles.files.get(prof_filename): - sev_db.set_variables(active_profiles.get_all_merged_variables(prof_filename, include_list_recursive(active_profiles.files[prof_filename]))) + sev_db.set_variables(active_profiles.get_all_merged_variables( + prof_filename, + include_list_recursive(active_profiles.files[prof_filename], True))) else: - sev_db.set_variables( {} ) + sev_db.set_variables({}) severity = sev_db.rank_path(exec_target, 'x') # Prompt portion starts q = aaui.PromptQuestion() - q.headers += [_('Profile'), combine_name(profile, hat)] + q.headers.extend(( + _('Profile'), combine_name(profile, hat), - # to_name should not exist here since, transitioning is already handeled - q.headers += [_('Execute'), exec_target] - q.headers += [_('Severity'), severity] + # to_name should not exist here since, transitioning is already handled + _('Execute'), exec_target, + _('Severity'), severity, + )) exec_toggle = False - q.functions += build_x_functions(default, options, exec_toggle) + q.functions.extend(build_x_functions(default, options, exec_toggle)) # ask user about the exec mode to use ans = '' - while ans not in ['CMD_ix', 'CMD_px', 'CMD_cx', 'CMD_nx', 'CMD_pix', 'CMD_cix', 'CMD_nix', 'CMD_ux', 'CMD_DENY']: # add '(I)gnore'? (hotkey conflict with '(i)x'!) + while ans not in ('CMD_ix', 'CMD_px', 'CMD_cx', 'CMD_nx', 'CMD_pix', 'CMD_cix', 'CMD_nix', 'CMD_ux', 'CMD_DENY'): # add '(I)gnore'? (hotkey conflict with '(i)x'!) ans = q.promptUser()[0] if ans.startswith('CMD_EXEC_IX_'): @@ -923,11 +932,24 @@ def ask_exec(hashlog): if ans == 'CMD_ix': exec_mode = 'ix' - elif ans in ['CMD_px', 'CMD_cx', 'CMD_pix', 'CMD_cix']: + elif ans in ('CMD_px', 'CMD_cx', 'CMD_pix', 'CMD_cix'): exec_mode = ans.replace('CMD_', '') - px_msg = _("Should AppArmor sanitise the environment when\nswitching profiles?\n\nSanitising environment is more secure,\nbut some applications depend on the presence\nof LD_PRELOAD or LD_LIBRARY_PATH.") + px_msg = _( + "Should AppArmor sanitise the environment when\n" + "switching profiles?\n" + "\n" + "Sanitising environment is more secure,\n" + "but some applications depend on the presence\n" + "of LD_PRELOAD or LD_LIBRARY_PATH.") if parent_uses_ld_xxx: - px_msg = _("Should AppArmor sanitise the environment when\nswitching profiles?\n\nSanitising environment is more secure,\nbut this application appears to be using LD_PRELOAD\nor LD_LIBRARY_PATH and sanitising the environment\ncould cause functionality problems.") + px_msg = _( + "Should AppArmor sanitise the environment when\n" + "switching profiles?\n" + "\n" + "Sanitising environment is more secure,\n" + "but this application appears to be using LD_PRELOAD\n" + "or LD_LIBRARY_PATH and sanitising the environment\n" + "could cause functionality problems.") ynans = aaui.UI_YesNo(px_msg, 'y') if ynans == 'y': @@ -935,9 +957,20 @@ def ask_exec(hashlog): exec_mode = exec_mode.capitalize() elif ans == 'CMD_ux': exec_mode = 'ux' - ynans = aaui.UI_YesNo(_("Launching processes in an unconfined state is a very\ndangerous operation and can cause serious security holes.\n\nAre you absolutely certain you wish to remove all\nAppArmor protection when executing %s ?") % exec_target, 'n') + ynans = aaui.UI_YesNo(_( + "Launching processes in an unconfined state is a very\n" + "dangerous operation and can cause serious security holes.\n" + "\n" + "Are you absolutely certain you wish to remove all\n" + "AppArmor protection when executing %s ?") % exec_target, 'n') if ynans == 'y': - ynans = aaui.UI_YesNo(_("Should AppArmor sanitise the environment when\nrunning this program unconfined?\n\nNot sanitising the environment when unconfining\na program opens up significant security holes\nand should be avoided if at all possible."), 'y') + ynans = aaui.UI_YesNo(_( + "Should AppArmor sanitise the environment when\n" + "running this program unconfined?\n" + "\n" + "Not sanitising the environment when unconfining\n" + "a program opens up significant security holes\n" + "and should be avoided if at all possible."), 'y') if ynans == 'y': # Disable the unsafe mode exec_mode = exec_mode.capitalize() @@ -1000,7 +1033,12 @@ def ask_exec(hashlog): hashlog[aamode][target_profile]['final_name'] = exec_target # Check profile exists for px - if not os.path.exists(get_profile_filename_from_attachment(exec_target, True)): + if exec_target.startswith(('/', '@', '{')): + prof_filename = get_profile_filename_from_attachment(exec_target, True) + else: # named exec + prof_filename = get_profile_filename_from_profile_name(exec_target, True) + + if not os.path.exists(prof_filename): 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') @@ -1025,11 +1063,9 @@ def ask_exec(hashlog): ynans = aaui.UI_YesNo(_('A profile for %s does not exist.\nDo you want to create one?') % exec_target, 'n') if ynans == 'y': if not aa[profile].get(exec_target, False): - stub_profile = create_new_profile(exec_target, True) + stub_profile = merged_to_split(create_new_profile(exec_target, True)) aa[profile][exec_target] = stub_profile[exec_target][exec_target] - aa[profile][exec_target]['profile'] = True - if profile != exec_target: aa[profile][exec_target]['flags'] = aa[profile][profile]['flags'] @@ -1045,6 +1081,7 @@ def ask_exec(hashlog): elif ans.startswith('CMD_ux'): continue + def order_globs(globs, original_path): """Returns the globs in sorted order, more specific behind""" # To-Do @@ -1059,6 +1096,7 @@ def order_globs(globs, original_path): return globs + def ask_the_questions(log_dict): for aamode in sorted(log_dict.keys()): # Describe the type of changes @@ -1071,41 +1109,38 @@ def ask_the_questions(log_dict): else: raise AppArmorBug(_('Invalid mode found: %s') % aamode) - for profile in sorted(log_dict[aamode].keys()): + for full_profile in sorted(log_dict[aamode].keys()): + profile, hat = split_name(full_profile) # XXX limited to two levels to avoid an Exception on nested child profiles or nested null-* + + # TODO: honor full profile name as soon as child profiles are listed in active_profiles prof_filename = get_profile_filename_from_profile_name(profile) if prof_filename and active_profiles.files.get(prof_filename): - sev_db.set_variables(active_profiles.get_all_merged_variables(prof_filename, include_list_recursive(active_profiles.files[prof_filename]))) + sev_db.set_variables(active_profiles.get_all_merged_variables(prof_filename, include_list_recursive(active_profiles.files[prof_filename], True))) else: - sev_db.set_variables( {} ) - - # Sorted list of hats with the profile name coming first - hats = list(filter(lambda key: key != profile, sorted(log_dict[aamode][profile].keys()))) - if log_dict[aamode][profile].get(profile, False): - hats = [profile] + hats - - for hat in hats: + sev_db.set_variables({}) + if aa.get(profile): # only continue/ask if the parent profile exists 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 + # Ignore log events for a non-existing profile or child profile. Such events can occur # 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 ProfileStorage.) - debug_logger.debug("Ignoring events for non-existing profile %s" % combine_name(profile, hat)) + debug_logger.debug("Ignoring events for non-existing profile %s" % full_profile) continue ans = '' - while ans not in ['CMD_ADDHAT', 'CMD_ADDSUBPROFILE', 'CMD_DENY']: + while ans not in ('CMD_ADDHAT', 'CMD_ADDSUBPROFILE', 'CMD_DENY'): q = aaui.PromptQuestion() - q.headers += [_('Profile'), profile] + q.headers.extend((_('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] + if log_dict[aamode][full_profile]['is_hat']: + q.headers.extend((_('Requested Hat'), hat)) q.functions.append('CMD_ADDHAT') + else: + q.headers.extend((_('Requested Subprofile'), hat)) + q.functions.append('CMD_ADDSUBPROFILE') - q.functions += ['CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED'] + q.functions.extend(('CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED')) q.default = 'CMD_DENY' @@ -1117,203 +1152,216 @@ def ask_the_questions(log_dict): 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] = ProfileStorage(profile, hat, 'mergeprof ask_the_questions() - missing subprofile') - aa[profile][hat]['profile'] = True - else: + if log_dict[aamode][full_profile]['is_hat']: aa[profile][hat] = ProfileStorage(profile, hat, 'mergeprof ask_the_questions() - missing hat') - aa[profile][hat]['profile'] = False + aa[profile][hat]['is_hat'] = True + else: + aa[profile][hat] = ProfileStorage(profile, hat, 'mergeprof ask_the_questions() - missing subprofile') + aa[profile][hat]['is_hat'] = False # check for and ask about conflicting exec modes - ask_conflict_mode(profile, hat, aa[profile][hat], log_dict[aamode][profile][hat]) + ask_conflict_mode(aa[profile][hat], log_dict[aamode][full_profile]) - prof_changed, end_profiling = ask_rule_questions(log_dict[aamode][profile][hat], combine_name(profile, hat), aa[profile][hat], ruletypes) + prof_changed, end_profiling = ask_rule_questions( + log_dict[aamode][full_profile], combine_name(profile, hat), + aa[profile][hat], ruletypes) if prof_changed: changed[profile] = True if end_profiling: return # end profiling loop + def ask_rule_questions(prof_events, profile_name, the_profile, r_types): - ''' ask questions about rules to add to a single profile/hat + """ask questions about rules to add to a single profile/hat - parameter typical value - prof_events log_dict[aamode][profile][hat] - profile_name profile name (possible profile//hat) - the_profile aa[profile][hat] -- will be modified - r_types ruletypes + parameter typical value + prof_events log_dict[aamode][full_profile] + profile_name profile name (possible profile//hat) + the_profile aa[profile][hat] -- will be modified + r_types ruletypes - returns: - changed True if the profile was changed - end_profiling True if the user wants to end profiling - ''' + returns: + changed True if the profile was changed + end_profiling True if the user wants to end profiling + """ changed = False for ruletype in r_types: for rule_obj in prof_events[ruletype].rules: - if is_known_rule(the_profile, ruletype, rule_obj): - continue + if is_known_rule(the_profile, ruletype, rule_obj): + continue - default_option = 1 - options = [] - newincludes = match_includes(the_profile, ruletype, rule_obj) - q = aaui.PromptQuestion() - if newincludes: - options += list(map(lambda inc: 'include <%s>' % inc, sorted(set(newincludes)))) + default_option = 1 + options = [] + newincludes = match_includes(the_profile, ruletype, rule_obj) + q = aaui.PromptQuestion() + if newincludes: + options.extend(map(lambda inc: 'include <%s>' % inc, sorted(set(newincludes)))) - if ruletype == 'file' and rule_obj.path: - options += propose_file_rules(the_profile, 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'), profile_name] - 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) - - # In complain mode: events default to allow - # In enforce mode: events default to deny - # XXX does this behaviour really make sense, except for "historical reasons"[tm]? - q.default = 'CMD_DENY' - if rule_obj.log_event == 'PERMITTING': - q.default = 'CMD_ALLOW' - - ans, selected = q.promptUser() - selection = options[selected] - - if ans == 'CMD_IGNORE_ENTRY': - done = True - break - - elif ans == 'CMD_FINISHED': - return changed, True - - 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 + if ruletype == 'file' and rule_obj.path: + options += propose_file_rules(the_profile, rule_obj) + else: + options.append(rule_obj.get_clean()) - options = set_options_audit_mode(rule_obj, options) + done = False + while not done: + q.options = options + q.selected = default_option - 1 + q.headers = [_('Profile'), profile_name] + q.headers.extend(rule_obj.logprof_header()) - elif ans.startswith('CMD_USER_'): - if ans == 'CMD_USER_ON': - rule_obj.owner = True - rule_obj.raw_rule = None - else: - rule_obj.owner = False - rule_obj.raw_rule = None + # 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.extend((_('Severity'), severity)) + + q.functions = available_buttons(rule_obj) - options = set_options_owner_mode(rule_obj, options) + # In complain mode: events default to allow + # In enforce mode: events default to deny + # XXX does this behaviour really make sense, except for "historical reasons"[tm]? + q.default = 'CMD_DENY' + if rule_obj.log_event == 'PERMITTING': + q.default = 'CMD_ALLOW' - elif ans == 'CMD_ALLOW': - done = True - changed = True + ans, selected = q.promptUser() + selection = options[selected] - inc = re_match_include(selection) - if inc: - deleted = delete_all_duplicates(the_profile, inc, r_types) + if ans == 'CMD_IGNORE_ENTRY': + done = True + break - the_profile['inc_ie'].add(IncludeRule.parse(selection)) + elif ans == 'CMD_FINISHED': + return changed, True - aaui.UI_Info(_('Adding %s to profile.') % selection) - if deleted: - aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) + 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 - else: - rule_obj = selection_to_rule_obj(rule_obj, selection) - deleted = the_profile[ruletype].add(rule_obj, cleanup=True) + options = set_options_audit_mode(rule_obj, options) - 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.startswith('CMD_USER_'): + if ans == 'CMD_USER_ON': + rule_obj.owner = True + rule_obj.raw_rule = None + else: + rule_obj.owner = False + rule_obj.raw_rule = None - elif ans == 'CMD_DENY': - if re_match_include(selection): - aaui.UI_Important("Denying via an include file isn't supported by the AppArmor tools") + options = set_options_owner_mode(rule_obj, options) - else: - done = True - changed = 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 = the_profile[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()) - user_globs[newpath] = AARE(newpath, True) + elif ans == 'CMD_ALLOW': + done = True + changed = True - else: - done = False + inc = re_match_include(selection) + if inc: + deleted = delete_all_duplicates(the_profile, inc, r_types) + + the_profile['inc_ie'].add(IncludeRule.parse(selection)) + + 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 = the_profile[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 = 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 = the_profile[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\n' + 'Do 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()) + user_globs[newpath] = AARE(newpath, True) + + else: + done = False return changed, False + def selection_to_rule_obj(rule_obj, selection): rule_type = type(rule_obj) return rule_type.parse(selection) + def set_options_audit_mode(rule_obj, options): - '''change audit state in options (proposed rules) to audit state in rule_obj. + """change audit state in options (proposed rules) to audit state in rule_obj. #include options will be kept unchanged - ''' + """ return set_options_mode(rule_obj, options, 'audit') + def set_options_owner_mode(rule_obj, options): - '''change owner state in options (proposed rules) to owner state in rule_obj. + """change owner state in options (proposed rules) to owner state in rule_obj. #include options will be kept unchanged - ''' + """ return set_options_mode(rule_obj, options, 'owner') + def set_options_mode(rule_obj, options, what): - ''' helper function for set_options_audit_mode() and set_options_owner_mode''' + """helper function for set_options_audit_mode() and set_options_owner_mode""" new_options = [] for rule in options: @@ -1333,38 +1381,40 @@ def set_options_mode(rule_obj, options, what): return new_options + def available_buttons(rule_obj): buttons = [] if not rule_obj.deny: - buttons += ['CMD_ALLOW'] + buttons.append('CMD_ALLOW') - buttons += ['CMD_DENY', 'CMD_IGNORE_ENTRY'] + buttons.extend(('CMD_DENY', 'CMD_IGNORE_ENTRY')) if rule_obj.can_glob: - buttons += ['CMD_GLOB'] + buttons.append('CMD_GLOB') if rule_obj.can_glob_ext: - buttons += ['CMD_GLOBEXT'] + buttons.append('CMD_GLOBEXT') if rule_obj.can_edit: - buttons += ['CMD_NEW'] + buttons.append('CMD_NEW') if rule_obj.audit: - buttons += ['CMD_AUDIT_OFF'] + buttons.append('CMD_AUDIT_OFF') else: - buttons += ['CMD_AUDIT_NEW'] + buttons.append('CMD_AUDIT_NEW') if rule_obj.can_owner: if rule_obj.owner: - buttons += ['CMD_USER_OFF'] + buttons.append('CMD_USER_OFF') else: - buttons += ['CMD_USER_ON'] + buttons.append('CMD_USER_ON') - buttons += ['CMD_ABORT', 'CMD_FINISHED'] + buttons.extend(('CMD_ABORT', 'CMD_FINISHED')) return buttons + def add_to_options(options, newpath): if newpath not in options: options.append(newpath) @@ -1372,6 +1422,7 @@ def add_to_options(options, newpath): default_option = options.index(newpath) + 1 return (options, default_option) + def delete_all_duplicates(profile, incname, r_types): deleted = 0 # Allow rules covered by denied rules shouldn't be deleted @@ -1383,17 +1434,19 @@ def delete_all_duplicates(profile, incname, r_types): return deleted -def ask_conflict_mode(profile, hat, old_profile, merge_profile): - '''ask user about conflicting exec rules''' + +def ask_conflict_mode(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()) + q.headers = [ + _('Path'), oldrule.path.regex, + _('Select the appropriate mode'), '', + ] + options = [oldrule.get_clean()] for rule in conflictingrules.rules: options.append(rule.get_clean()) q.options = options @@ -1416,11 +1469,12 @@ def ask_conflict_mode(profile, hat, old_profile, merge_profile): done = True + def match_includes(profile, rule_type, rule_obj): - ''' propose abstractions that allow the given rule_obj + """propose abstractions that allow the given rule_obj - Note: This function will return relative paths for includes inside profile_dir - ''' + Note: This function will return relative paths for includes inside profile_dir + """ newincludes = [] for incname in include.keys(): @@ -1447,8 +1501,9 @@ def match_includes(profile, rule_type, rule_obj): return newincludes + def valid_include(incname): - ''' check if the given include file exists or is whitelisted in custom_includes ''' + """check if the given include file exists or is whitelisted in custom_includes""" if cfg['settings']['custom_includes']: for incm in cfg['settings']['custom_includes'].split(): if incm == incname: @@ -1461,8 +1516,9 @@ def valid_include(incname): return False + def set_logfile(filename): - ''' set logfile to a) the specified filename or b) if not given, the first existing logfile from logprof.conf''' + """set logfile to a) the specified filename or b) if not given, the first existing logfile from logprof.conf""" global logfile @@ -1483,9 +1539,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=''): # set up variables for this pass -# transitions = hasher() global active_profiles global sev_db # aa = hasher() @@ -1495,8 +1551,8 @@ def do_logprof_pass(logmark=''): if not sev_db: sev_db = apparmor.severity.Severity(CONFDIR + '/severity.db', _('unknown')) - #print(pid) - #print(active_profiles) + # print(pid) + # print(active_profiles) log_reader = apparmor.logparser.ReadLog(logfile, active_profiles, profile_dir) hashlog = log_reader.read_log(logmark) @@ -1510,6 +1566,7 @@ def do_logprof_pass(logmark=''): save_profiles() + def save_profiles(is_mergeprof=False): # Ensure the changed profiles are actual active profiles for prof_name in changed.keys(): @@ -1541,28 +1598,28 @@ def save_profiles(is_mergeprof=False): ans, arg = q.promptUser() q.selected = arg # remember selection - which = options[arg] + profile_name = options[arg] if ans == 'CMD_SAVE_SELECTED': - write_profile_ui_feedback(which) - reload_base(which) + write_profile_ui_feedback(profile_name) + reload_base(profile_name) q.selected = 0 # saving the selected profile removes it from the list, therefore reset selection elif ans == 'CMD_VIEW_CHANGES': oldprofile = None - if aa[which][which].get('filename', False): - oldprofile = aa[which][which]['filename'] + if aa[profile_name][profile_name].get('filename', False): + oldprofile = aa[profile_name][profile_name]['filename'] else: - oldprofile = get_profile_filename_from_attachment(which, True) + oldprofile = get_profile_filename_from_attachment(profile_name, True) serialize_options = {'METADATA': True} - newprofile = serialize_profile(aa[which], which, serialize_options) + newprofile = serialize_profile(split_to_merged(aa), profile_name, serialize_options) aaui.UI_Changes(oldprofile, newprofile, comments=True) elif ans == 'CMD_VIEW_CHANGES_CLEAN': - oldprofile = serialize_profile(original_aa[which], which, {}) - newprofile = serialize_profile(aa[which], which, {}) + oldprofile = serialize_profile(split_to_merged(original_aa), profile_name, {}) + newprofile = serialize_profile(split_to_merged(aa), profile_name, {}) aaui.UI_Changes(oldprofile, newprofile) @@ -1573,32 +1630,37 @@ def save_profiles(is_mergeprof=False): write_profile_ui_feedback(profile_name) reload_base(profile_name) + def collapse_log(hashlog, ignore_null_profiles=True): - log_dict = hasher() + log_dict = {} for aamode in hashlog.keys(): + log_dict[aamode] = {} + for full_profile in hashlog[aamode].keys(): - if hashlog[aamode][full_profile]['final_name'] == '': + final_name = hashlog[aamode][full_profile]['final_name'] + + if final_name == '': continue # user chose "deny" or "unconfined" for this target, therefore ignore log events - if '//null-' in hashlog[aamode][full_profile]['final_name'] and ignore_null_profiles: + if '//null-' in final_name and ignore_null_profiles: # ignore null-* profiles (probably nested childs) - # otherwise we'd accidently create a null-* hat in the profile which is worse + # otherwise we'd accidentally create a null-* hat in the profile which is worse # XXX drop this once we support nested childs continue - profile, hat = split_name(hashlog[aamode][full_profile]['final_name']) # XXX limited to two levels to avoid an Exception on nested child profiles or nested null-* + profile, hat = split_name(final_name) # XXX limited to two levels to avoid an Exception on nested child profiles or nested null-* # TODO: support nested child profiles - # used to avoid to accidently initialize aa[profile][hat] or calling is_known_rule() on events for a non-existing profile + # used to avoid to accidentally initialize aa[profile][hat] or calling is_known_rule() on events for a non-existing profile hat_exists = False if aa.get(profile) and aa[profile].get(hat): hat_exists = True if True: - if not log_dict[aamode][profile].get(hat): + if not log_dict[aamode].get(final_name): # with execs in ix mode, we already have ProfileStorage initialized and should keep the content it already has - log_dict[aamode][profile][hat] = ProfileStorage(profile, hat, 'collapse_log()') + log_dict[aamode][final_name] = ProfileStorage(profile, hat, 'collapse_log()') for path in hashlog[aamode][full_profile]['path'].keys(): for owner in hashlog[aamode][full_profile]['path'][path]: @@ -1611,76 +1673,81 @@ def collapse_log(hashlog, ignore_null_profiles=True): file_event = FileRule(path, mode, None, FileRule.ALL, owner=owner, log_event=True) if not hat_exists or not is_known_rule(aa[profile][hat], 'file', file_event): - log_dict[aamode][profile][hat]['file'].add(file_event) + log_dict[aamode][final_name]['file'].add(file_event) # TODO: check for existing rules with this path, and merge them into one rule for cap in hashlog[aamode][full_profile]['capability'].keys(): cap_event = CapabilityRule(cap, log_event=True) if not hat_exists or not is_known_rule(aa[profile][hat], 'capability', cap_event): - log_dict[aamode][profile][hat]['capability'].add(cap_event) + log_dict[aamode][final_name]['capability'].add(cap_event) for cp in hashlog[aamode][full_profile]['change_profile'].keys(): cp_event = ChangeProfileRule(None, ChangeProfileRule.ALL, cp, log_event=True) if not hat_exists or not is_known_rule(aa[profile][hat], 'change_profile', cp_event): - log_dict[aamode][profile][hat]['change_profile'].add(cp_event) + log_dict[aamode][final_name]['change_profile'].add(cp_event) dbus = hashlog[aamode][full_profile]['dbus'] - for access in dbus: - for bus in dbus[access]: - for path in dbus[access][bus]: - for name in dbus[access][bus][path]: - for interface in dbus[access][bus][path][name]: - for member in dbus[access][bus][path][name][interface]: + for access in dbus: # noqa: E271 + for bus in dbus[access]: # noqa: E271 + for path in dbus[access][bus]: # noqa: E271 + for name in dbus[access][bus][path]: # noqa: E271 + for interface in dbus[access][bus][path][name]: # noqa: E271 + for member in dbus[access][bus][path][name][interface]: # noqa: E271 for peer_profile in dbus[access][bus][path][name][interface][member]: # Depending on the access type, not all parameters are allowed. # Ignore them, even if some of them appear in the log. # Also, the log doesn't provide a peer name, therefore always use ALL. - if access in ['send', 'receive']: - dbus_event = DbusRule(access, bus, path, DbusRule.ALL, interface, member, DbusRule.ALL, peer_profile, log_event=True) + if access in ('send', 'receive'): + dbus_event = DbusRule(access, bus, path, DbusRule.ALL, interface, member, DbusRule.ALL, peer_profile, log_event=True) elif access == 'bind': - dbus_event = DbusRule(access, bus, DbusRule.ALL, name, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, log_event=True) + dbus_event = DbusRule(access, bus, DbusRule.ALL, name, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, log_event=True) elif access == 'eavesdrop': - dbus_event = DbusRule(access, bus, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, log_event=True) + dbus_event = DbusRule(access, bus, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, log_event=True) else: - raise AppArmorBug('unexpected dbus access: %s') + raise AppArmorBug('unexpected dbus access: {}'.format(access)) if not hat_exists or not is_known_rule(aa[profile][hat], 'dbus', dbus_event): - log_dict[aamode][profile][hat]['dbus'].add(dbus_event) + log_dict[aamode][final_name]['dbus'].add(dbus_event) nd = hashlog[aamode][full_profile]['network'] for family in nd.keys(): for sock_type in nd[family].keys(): net_event = NetworkRule(family, sock_type, log_event=True) if not hat_exists or not is_known_rule(aa[profile][hat], 'network', net_event): - log_dict[aamode][profile][hat]['network'].add(net_event) + log_dict[aamode][final_name]['network'].add(net_event) ptrace = hashlog[aamode][full_profile]['ptrace'] for peer in ptrace.keys(): + if '//null-' in peer: + continue # ignore null-* peers + for access in ptrace[peer].keys(): ptrace_event = PtraceRule(access, peer, log_event=True) if not hat_exists or not is_known_rule(aa[profile][hat], 'ptrace', ptrace_event): - log_dict[aamode][profile][hat]['ptrace'].add(ptrace_event) + log_dict[aamode][final_name]['ptrace'].add(ptrace_event) sig = hashlog[aamode][full_profile]['signal'] for peer in sig.keys(): + if '//null-' in peer: + continue # ignore null-* peers + for access in sig[peer].keys(): for signal in sig[peer][access].keys(): signal_event = SignalRule(access, signal, peer, log_event=True) if not hat_exists or not is_known_rule(aa[profile][hat], 'signal', signal_event): - log_dict[aamode][profile][hat]['signal'].add(signal_event) + log_dict[aamode][final_name]['signal'].add(signal_event) return log_dict -def is_skippable_dir(path): - if re.search('^(.*/)?(disable|cache|cache\.d|force-complain|lxc|abi|\.git)/?$', path): - return True - return False -def read_profiles(ui_msg=False): +def read_profiles(ui_msg=False, skip_profiles=()): # we'll read all profiles from disk, so reset the storage first (autodep() might have created/stored # a profile already, which would cause a 'Conflicting profile' error in attach_profile_data()) + # + # The skip_profiles parameter should only be specified by tests. + global aa, original_aa - aa = hasher() + aa = {} original_aa = hasher() if ui_msg: @@ -1696,10 +1763,16 @@ def read_profiles(ui_msg=False): if os.path.isfile(full_file): if is_skippable_file(file): continue + elif file in skip_profiles: + aaui.UI_Info("skipping profile %s" % full_file) + continue else: read_profile(full_file, True) -def read_inactive_profiles(): + +def read_inactive_profiles(skip_profiles=()): + # The skip_profiles parameter should only be specified by tests. + if hasattr(read_inactive_profiles, 'already_read'): # each autodep() run calls read_inactive_profiles, but that's a) superfluous and b) triggers a conflict because the inactive profiles are already loaded # therefore don't do anything if the inactive profiles were already loaded @@ -1708,7 +1781,7 @@ def read_inactive_profiles(): read_inactive_profiles.already_read = True if not os.path.exists(extra_profile_dir): - return None + return try: os.listdir(profile_dir) except: @@ -1719,9 +1792,13 @@ def read_inactive_profiles(): if os.path.isfile(full_file): if is_skippable_file(file): continue + elif file in skip_profiles: + aaui.UI_Info("skipping profile %s" % full_file) + continue else: read_profile(full_file, False) + def read_profile(file, active_profile): data = None try: @@ -1730,101 +1807,73 @@ def read_profile(file, active_profile): except IOError as e: aaui.UI_Important('WARNING: Error reading file %s, skipping.\n %s' % (file, e)) debug_logger.debug("read_profile: can't read %s - skipping" % file) - return None + return + + profile_data = parse_profile_data(data, file, 0, True) - profile_data = parse_profile_data(data, file, 0) + if not profile_data: + return - if profile_data and active_profile: + if 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'] + for profile in profile_data: + if '//' in profile: + continue # TODO: handle hats/child profiles independent of main profiles - if not attachment and name.startswith('/'): - active_profiles.add_profile(filename, name, name) # use name as name and attachment - else: - active_profiles.add_profile(filename, name, attachment) + attachment = profile_data[profile]['attachment'] + filename = profile_data[profile]['filename'] - elif profile_data: - attach_profile_data(extras, profile_data) + if not attachment and profile.startswith('/'): + attachment = profile # use profile as name and attachment - 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'] + active_profiles.add_profile(filename, profile, attachment) + + else: + for profile in profile_data: + attachment = profile_data[profile]['attachment'] + filename = profile_data[profile]['filename'] + + if not attachment and profile.startswith('/'): + attachment = profile # use profile as name and attachment + + extra_profiles.add_profile(filename, profile, attachment, profile_data[profile]) - if not attachment and name.startswith('/'): - extra_profiles.add_profile(filename, name, name) # use name as name and attachment - else: - extra_profiles.add_profile(filename, name, attachment) def attach_profile_data(profiles, profile_data): + profile_data = merged_to_split(profile_data) # Make deep copy of data to avoid changes to # arising due to mutables for p in profile_data.keys(): if profiles.get(p, False): for hat in profile_data[p].keys(): if profiles[p].get(hat, False): - raise AppArmorException(_("Conflicting profiles for %s defined in two files:\n- %s\n- %s") % - (combine_name(p, hat), profiles[p][hat]['filename'], profile_data[p][hat]['filename'])) + raise AppArmorException( + _("Conflicting profiles for %s defined in two files:\n- %s\n- %s") + % (combine_name(p, hat), profiles[p][hat]['filename'], profile_data[p][hat]['filename'])) profiles[p] = deepcopy(profile_data[p]) -def parse_profile_start(line, file, lineno, profile, hat): - matches = parse_profile_start_line(line, file) - - if profile: # we are inside a profile, so we expect a child profile - if not matches['profile_keyword']: - raise AppArmorException(_('%(profile)s profile in %(file)s contains syntax errors in line %(line)s: missing "profile" keyword.') % { - 'profile': profile, 'file': file, 'line': lineno + 1 }) - if profile != hat: - # nesting limit reached - a child profile can't contain another child profile - raise AppArmorException(_('%(profile)s profile in %(file)s contains syntax errors in line %(line)s: a child profile inside another child profile is not allowed.') % { - 'profile': profile, 'file': file, 'line': lineno + 1 }) - - hat = matches['profile'] - in_contained_hat = True - pps_set_profile = True - pps_set_hat_external = False - - else: # stand-alone profile - profile = matches['profile'] - if len(profile.split('//')) > 2: - raise AppArmorException("Nested child profiles ('%(profile)s', found in %(file)s) are not supported by the AppArmor tools yet." % {'profile': profile, 'file': file}) - elif len(profile.split('//')) == 2: - profile, hat = profile.split('//') - pps_set_hat_external = True - else: - hat = profile - pps_set_hat_external = False - - in_contained_hat = False - pps_set_profile = False - - attachment = matches['attachment'] - flags = matches['flags'] - xattrs = matches['xattrs'] - - return (profile, hat, attachment, xattrs, flags, in_contained_hat, pps_set_profile, pps_set_hat_external) - -def parse_profile_data(data, file, do_include): - profile_data = hasher() +def parse_profile_data(data, file, do_include, in_preamble): + profile_data = {} profile = None hat = None + profname = None in_contained_hat = None parsed_profiles = [] initial_comment = '' lastline = None + active_profiles.init_file(file) + if do_include: profile = file - hat = file - profile_data[profile][hat] = ProfileStorage(profile, hat, 'parse_profile_data() do_include') - profile_data[profile][hat]['filename'] = file + hat = None + profname = combine_profname([profile, hat]) + profile_data[profname] = ProfileStorage(profile, hat, 'parse_profile_data() do_include') + profile_data[profname]['filename'] = file for lineno, line in enumerate(data): line = line.strip() @@ -1834,94 +1883,72 @@ def parse_profile_data(data, file, do_include): if lastline: line = '%s %s' % (lastline, line) lastline = None - # Starting line of a profile - if RE_PROFILE_START.search(line): - (profile, hat, attachment, xattrs, flags, in_contained_hat, pps_set_profile, pps_set_hat_external) = parse_profile_start(line, file, lineno, profile, hat) - if profile_data[profile].get(hat, False): - raise AppArmorException('Profile %(profile)s defined twice in %(file)s, last found in line %(line)s' % - { 'file': file, 'line': lineno + 1, 'profile': combine_name(profile, hat) }) + # is line handled by a *Rule class? + (rule_name, rule_obj) = match_line_against_rule_classes(line, profile, file, lineno, in_preamble) + if rule_name: + if in_preamble: + active_profiles.add_rule(file, rule_name, rule_obj) + else: + profile_data[profname][rule_name].add(rule_obj) + + if rule_name == 'inc_ie': + for incname in rule_obj.get_full_paths(profile_dir): + if incname == file: + # warn about endless loop, and don't call load_include() (again) for this file + aaui.UI_Important(_('WARNING: endless loop detected: file %s includes itsself' % incname)) + else: + load_include(incname, in_preamble) + + elif RE_PROFILE_START.search(line) or RE_PROFILE_HAT_DEF.search(line): # Starting line of a profile/hat + # in_contained_hat is needed to know if we are already in a profile or not. Simply checking if we are in a hat doesn't work, + # because something like "profile foo//bar" will set profile and hat at once, and later (wrongfully) expect another "}". + # The logic is simple and resembles a "poor man's stack" (with limited/hardcoded height). + if profile: + in_contained_hat = True + else: + in_contained_hat = False + + in_preamble = False - profile_data[profile][hat] = ProfileStorage(profile, hat, 'parse_profile_data() profile_start') + (profile, hat, prof_storage) = ProfileStorage.parse(line, file, lineno, profile, hat) - if attachment: - profile_data[profile][hat]['attachment'] = attachment - if pps_set_profile: - profile_data[profile][hat]['profile'] = True - if pps_set_hat_external: - profile_data[profile][hat]['external'] = True + if profile == hat: + hat = None + profname = combine_profname([profile, hat]) - # save profile name and filename - profile_data[profile][hat]['name'] = profile - profile_data[profile][hat]['filename'] = file + if profile_data.get(profname, False): + raise AppArmorException( + 'Profile %(profile)s defined twice in %(file)s, last found in line %(line)s' + % {'file': file, 'line': lineno + 1, 'profile': combine_name(profile, hat)}) - profile_data[profile][hat]['xattrs'] = xattrs - profile_data[profile][hat]['flags'] = flags + profile_data[profname] = prof_storage # Save the initial comment if initial_comment: - profile_data[profile][hat]['initial_comment'] = initial_comment + profile_data[profname]['initial_comment'] = initial_comment initial_comment = '' elif RE_PROFILE_END.search(line): # If profile ends and we're not in one if not profile: - raise AppArmorException(_('Syntax Error: Unexpected End of Profile reached in file: %(file)s line: %(line)s') % { 'file': file, 'line': lineno + 1 }) + raise AppArmorException( + _('Syntax Error: Unexpected End of Profile reached in file: %(file)s line: %(line)s') + % {'file': file, 'line': lineno + 1}) if in_contained_hat: - hat = profile + hat = None in_contained_hat = False + profname = combine_profname([profile, hat]) else: parsed_profiles.append(profile) profile = None + profname = None + in_preamble = True initial_comment = '' - elif CapabilityRule.match(line): - if not profile: - raise AppArmorException(_('Syntax Error: Unexpected capability entry found in file: %(file)s line: %(line)s') % { 'file': file, 'line': lineno + 1 }) - - profile_data[profile][hat]['capability'].add(CapabilityRule.parse(line)) - - elif ChangeProfileRule.match(line): - if not profile: - raise AppArmorException(_('Syntax Error: Unexpected change profile entry found in file: %(file)s line: %(line)s') % { 'file': file, 'line': lineno + 1 }) - - profile_data[profile][hat]['change_profile'].add(ChangeProfileRule.parse(line)) - - elif AliasRule.match(line): - if profile and not do_include: - raise AppArmorException(_('Syntax Error: Unexpected alias definition found inside profile in file: %(file)s line: %(line)s') % { - 'file': file, 'line': lineno + 1 }) - else: - active_profiles.add_alias(file, AliasRule.parse(line)) - - elif RlimitRule.match(line): - if not profile: - raise AppArmorException(_('Syntax Error: Unexpected rlimit entry found in file: %(file)s line: %(line)s') % { 'file': file, 'line': lineno + 1 }) - - profile_data[profile][hat]['rlimit'].add(RlimitRule.parse(line)) - - elif RE_PROFILE_BOOLEAN.search(line): - matches = RE_PROFILE_BOOLEAN.search(line).groups() - - if profile and not do_include: - raise AppArmorException(_('Syntax Error: Unexpected boolean definition found inside profile in file: %(file)s line: %(line)s') % { - 'file': file, 'line': lineno + 1 }) - - bool_var = matches[0] - value = matches[1] - - profile_data[profile][hat]['lvar'][bool_var] = value - - elif VariableRule.match(line): - if profile and not do_include: - raise AppArmorException(_('Syntax Error: Unexpected variable definition found inside profile in file: %(file)s line: %(line)s') % { - 'file': file, 'line': lineno + 1 }) - else: - active_profiles.add_variable(file, VariableRule.parse(line)) - elif RE_PROFILE_CONDITIONAL.search(line): # Conditional Boolean pass @@ -1934,43 +1961,12 @@ def parse_profile_data(data, file, do_include): # Conditional Boolean defined pass - elif AbiRule.match(line): - if profile: - profile_data[profile][hat]['abi'].add(AbiRule.parse(line)) - else: - active_profiles.add_abi(file, AbiRule.parse(line)) - - elif IncludeRule.match(line): - rule_obj = IncludeRule.parse(line) - if profile: - profile_data[profile][hat]['inc_ie'].add(rule_obj) - else: - active_profiles.add_inc_ie(file, rule_obj) - - for incname in rule_obj.get_full_paths(profile_dir): - if incname == file: - # warn about endless loop, and don't call load_include() (again) for this file - aaui.UI_Important(_('WARNING: endless loop detected: file %s includes itsself' % incname)) - else: - load_include(incname) - - elif NetworkRule.match(line): - if not profile: - raise AppArmorException(_('Syntax Error: Unexpected network entry found in file: %(file)s line: %(line)s') % { 'file': file, 'line': lineno + 1 }) - - profile_data[profile][hat]['network'].add(NetworkRule.parse(line)) - - elif DbusRule.match(line): - if not profile: - raise AppArmorException(_('Syntax Error: Unexpected dbus entry found in file: %(file)s line: %(line)s') % {'file': file, 'line': lineno + 1 }) - - profile_data[profile][hat]['dbus'].add(DbusRule.parse(line)) - elif RE_PROFILE_MOUNT.search(line): matches = RE_PROFILE_MOUNT.search(line).groups() if not profile: - raise AppArmorException(_('Syntax Error: Unexpected mount entry found in file: %(file)s line: %(line)s') % { 'file': file, 'line': lineno + 1 }) + raise AppArmorException(_('Syntax Error: Unexpected mount entry found in file: %(file)s line: %(line)s') + % {'file': file, 'line': lineno + 1}) audit = False if matches[0]: @@ -1984,27 +1980,16 @@ def parse_profile_data(data, file, do_include): mount_rule.audit = audit mount_rule.deny = (allow == 'deny') - mount_rules = profile_data[profile][hat][allow].get('mount', list()) + mount_rules = profile_data[profname][allow].get('mount', []) mount_rules.append(mount_rule) - profile_data[profile][hat][allow]['mount'] = mount_rules - - elif SignalRule.match(line): - if not profile: - raise AppArmorException(_('Syntax Error: Unexpected signal entry found in file: %(file)s line: %(line)s') % { 'file': file, 'line': lineno + 1 }) - - profile_data[profile][hat]['signal'].add(SignalRule.parse(line)) - - elif PtraceRule.match(line): - if not profile: - raise AppArmorException(_('Syntax Error: Unexpected ptrace entry found in file: %(file)s line: %(line)s') % { 'file': file, 'line': lineno + 1 }) - - profile_data[profile][hat]['ptrace'].add(PtraceRule.parse(line)) + profile_data[profname][allow]['mount'] = mount_rules elif RE_PROFILE_PIVOT_ROOT.search(line): matches = RE_PROFILE_PIVOT_ROOT.search(line).groups() if not profile: - raise AppArmorException(_('Syntax Error: Unexpected pivot_root entry found in file: %(file)s line: %(line)s') % { 'file': file, 'line': lineno + 1 }) + raise AppArmorException(_('Syntax Error: Unexpected pivot_root entry found in file: %(file)s line: %(line)s') + % {'file': file, 'line': lineno + 1}) audit = False if matches[0]: @@ -2018,15 +2003,16 @@ def parse_profile_data(data, file, do_include): pivot_root_rule.audit = audit pivot_root_rule.deny = (allow == 'deny') - pivot_root_rules = profile_data[profile][hat][allow].get('pivot_root', list()) + pivot_root_rules = profile_data[profname][allow].get('pivot_root', []) pivot_root_rules.append(pivot_root_rule) - profile_data[profile][hat][allow]['pivot_root'] = pivot_root_rules + profile_data[profname][allow]['pivot_root'] = pivot_root_rules elif RE_PROFILE_UNIX.search(line): matches = RE_PROFILE_UNIX.search(line).groups() if not profile: - raise AppArmorException(_('Syntax Error: Unexpected unix entry found in file: %(file)s line: %(line)s') % { 'file': file, 'line': lineno + 1 }) + raise AppArmorException(_('Syntax Error: Unexpected unix entry found in file: %(file)s line: %(line)s') + % {'file': file, 'line': lineno + 1}) audit = False if matches[0]: @@ -2040,48 +2026,21 @@ def parse_profile_data(data, file, do_include): unix_rule.audit = audit unix_rule.deny = (allow == 'deny') - unix_rules = profile_data[profile][hat][allow].get('unix', list()) + unix_rules = profile_data[profname][allow].get('unix', []) unix_rules.append(unix_rule) - profile_data[profile][hat][allow]['unix'] = unix_rules + profile_data[profname][allow]['unix'] = unix_rules elif RE_PROFILE_CHANGE_HAT.search(line): matches = RE_PROFILE_CHANGE_HAT.search(line).groups() if not profile: - raise AppArmorException(_('Syntax Error: Unexpected change hat declaration found in file: %(file)s line: %(line)s') % { 'file': file, 'line': lineno + 1 }) - - aaui.UI_Important(_('Ignoring no longer supported change hat declaration "^%(hat)s," found in file: %(file)s line: %(line)s') % { - 'hat': matches[0], 'file': file, 'line': lineno + 1 }) - - elif RE_PROFILE_HAT_DEF.search(line): - # An embedded hat syntax definition starts - matches = RE_PROFILE_HAT_DEF.search(line) - if not profile: - raise AppArmorException(_('Syntax Error: Unexpected hat definition found in file: %(file)s line: %(line)s') % { 'file': file, 'line': lineno + 1 }) - - in_contained_hat = True - hat = matches.group('hat') - hat = strip_quotes(hat) - - if profile_data[profile].get(hat, False) and not do_include: - raise AppArmorException('Profile %(profile)s defined twice in %(file)s, last found in line %(line)s' % - { 'file': file, 'line': lineno + 1, 'profile': combine_name(profile, hat) }) - - # if hat is already known, the check above will error out (if not do_include) - # nevertheless, just to be sure, don't overwrite existing profile_data. - if not profile_data[profile].get(hat, False): - profile_data[profile][hat] = ProfileStorage(profile, hat, 'parse_profile_data() hat_def') - profile_data[profile][hat]['filename'] = file + raise AppArmorException(_('Syntax Error: Unexpected change hat declaration found in file: %(file)s line: %(line)s') + % {'file': file, 'line': lineno + 1}) - flags = matches.group('flags') + aaui.UI_Important(_('Ignoring no longer supported change hat declaration "^%(hat)s," found in file: %(file)s line: %(line)s') + % {'hat': matches[0], 'file': file, 'line': lineno + 1}) - profile_data[profile][hat]['flags'] = flags - - if initial_comment: - profile_data[profile][hat]['initial_comment'] = initial_comment - initial_comment = '' - - elif line[0] == '#': + elif line.startswith('#'): # Handle initial comments if not profile: if line.startswith('# Last Modified:'): @@ -2089,21 +2048,14 @@ def parse_profile_data(data, file, do_include): else: initial_comment = initial_comment + line + '\n' - if line.startswith('# LOGPROF-SUGGEST:'): # TODO: allow any number of spaces/tabs after '#' + if line.startswith('# LOGPROF-SUGGEST:'): # TODO: allow any number of spaces/tabs after '#' parts = line.split() if len(parts) > 2: - profile_data[profile][hat]['logprof_suggest'] = parts[2] + profile_data[profname]['logprof_suggest'] = parts[2] # keep line as part of initial_comment (if we ever support writing abstractions, we should update serialize_profile()) initial_comment = initial_comment + line + '\n' - elif FileRule.match(line): - # leading permissions could look like a keyword, therefore handle file rules after everything else - if not profile: - raise AppArmorException(_('Syntax Error: Unexpected path entry found in file: %(file)s line: %(line)s') % { 'file': file, 'line': lineno + 1 }) - - profile_data[profile][hat]['file'].add(FileRule.parse(line)) - elif not RE_RULE_HAS_COMMA.search(line): # Bah, line continues on to the next line if RE_HAS_COMMENT_SPLIT.search(line): @@ -2112,12 +2064,16 @@ def parse_profile_data(data, file, do_include): else: lastline = line 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 }) + 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 }) + # If it isn't empty, this means there's something unparsable 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: @@ -2125,57 +2081,104 @@ def parse_profile_data(data, file, do_include): for parsed_prof in sorted(parsed_profiles): if re.search(hatglob, parsed_prof): for hat in cfg['required_hats'][hatglob].split(): - if not profile_data[parsed_prof].get(hat, False): - profile_data[parsed_prof][hat] = ProfileStorage(parsed_prof, hat, 'parse_profile_data() required_hats') + profname = combine_profname([parsed_prof, hat]) + if not profile_data.get(profname, False): + profile_data[profname] = ProfileStorage(parsed_prof, hat, 'parse_profile_data() required_hats') + profile_data[profname]['is_hat'] = True # End of file reached but we're stuck in a profile if profile and not do_include: - raise AppArmorException(_("Syntax Error: Missing '}' or ','. Reached end of file %(file)s while inside profile %(profile)s") % { 'file': file, 'profile': profile }) + raise AppArmorException( + _("Syntax Error: Missing '}' or ','. Reached end of file %(file)s while inside profile %(profile)s") + % {'file': file, 'profile': profile}) return profile_data + +def match_line_against_rule_classes(line, profile, file, lineno, in_preamble): + """handle all lines handled by *Rule classes""" + + for rule_name in ( + 'abi', + 'alias', + 'boolean', + 'variable', + 'inc_ie', + 'capability', + 'change_profile', + 'dbus', + 'file', # file rules need to be parsed after variable rules + 'network', + 'ptrace', + 'rlimit', + 'signal', + ): + + if rule_name in ruletypes: + rule_class = ruletypes[rule_name]['rule'] + else: + rule_class = preamble_ruletypes[rule_name]['rule'] + + if rule_class.match(line): + if not in_preamble and rule_name not in ruletypes: + raise AppArmorException( + _('Syntax Error: Unexpected %(rule)s definition found inside profile in file: %(file)s line: %(line)s') + % {'file': file, 'line': lineno + 1, 'rule': rule_name}) + + if in_preamble and rule_name not in preamble_ruletypes: + raise AppArmorException( + _('Syntax Error: Unexpected %(rule)s entry found in file: %(file)s line: %(line)s') + % {'file': file, 'line': lineno + 1, 'rule': rule_name}) + + rule_obj = rule_class.parse(line) + return (rule_name, rule_obj) + + return (None, None) + + +def merged_to_split(profile_data): + """(temporary) helper function to convert a list of profile['foo//bar'] profiles into compat['foo']['bar']""" + compat = hasher() + for prof in profile_data: + profile, hat = split_name(prof) # XXX limited to two levels to avoid an Exception on nested child profiles or nested null-* + compat[profile][hat] = profile_data[prof] + + return compat + + +def split_to_merged(profile_data): + """(temporary) helper function to convert a traditional compat['foo']['bar'] to a profile['foo//bar'] list""" + + merged = {} + + for profile in profile_data: + for hat in profile_data[profile]: + if profile == hat: + merged_name = profile + else: + merged_name = combine_profname([profile, hat]) + + merged[merged_name] = profile_data[profile][hat] + + return merged + + def parse_mount_rule(line): # XXX Do real parsing here return aarules.Raw_Mount_Rule(line) + def parse_pivot_root_rule(line): # XXX Do real parsing here return aarules.Raw_Pivot_Root_Rule(line) + def parse_unix_rule(line): # XXX Do real parsing here return aarules.Raw_Unix_Rule(line) -def write_header(prof_data, depth, name, embedded_hat, write_flags): - pre = ' ' * int(depth * 2) - data = [] - unquoted_name = name - name = quote_if_needed(name) - - attachment = '' - if prof_data['attachment']: - attachment = ' %s' % quote_if_needed(prof_data['attachment']) - - comment = '' - if prof_data['header_comment']: - comment = ' %s' % prof_data['header_comment'] - - if (not embedded_hat and re.search('^[^/]', unquoted_name)) or (embedded_hat and re.search('^[^^]', unquoted_name)) or prof_data['attachment'] or prof_data['profile_keyword']: - name = 'profile %s%s' % (name, attachment) - - xattrs = '' - if prof_data['xattrs']: - xattrs = ' xattrs=(%s)' % prof_data['xattrs'] - - flags = '' - if write_flags and prof_data['flags']: - flags = ' flags=(%s)' % prof_data['flags'] - - data.append('%s%s%s%s {%s' % (pre, name, xattrs, flags, comment)) - - return data -def write_piece(profile_data, depth, name, nhat, write_flags): +def write_piece(profile_data, depth, name, nhat): pre = ' ' * depth data = [] wname = None @@ -2186,20 +2189,25 @@ def write_piece(profile_data, depth, name, nhat, write_flags): wname = name + '//' + nhat name = nhat inhat = True - data += write_header(profile_data[name], depth, wname, False, write_flags) + data += profile_data[name].get_header(depth, wname, False) data += profile_data[name].get_rules_clean(depth + 1) pre2 = ' ' * (depth + 1) if not inhat: # Embedded hats - for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))): + all_childs = [] + for child in sorted(profile_data.keys()): + if child.startswith('%s//' % name): + all_childs.append(child) + + for hat in all_childs: + profile, only_hat = split_name(hat) + if not profile_data[hat]['external']: data.append('') - if profile_data[hat]['profile']: - data += write_header(profile_data[hat], depth + 1, hat, True, write_flags) - else: - data += write_header(profile_data[hat], depth + 1, '^' + hat, True, write_flags) + + data += profile_data[hat].get_header(depth + 1, only_hat, True) data += profile_data[hat].get_rules_clean(depth + 2) @@ -2208,14 +2216,15 @@ def write_piece(profile_data, depth, name, nhat, write_flags): data.append('%s}' % pre) # External hats - for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))): + for hat in all_childs: if name == nhat and profile_data[hat].get('external', False): data.append('') - data += list(map(lambda x: ' %s' % x, write_piece(profile_data, depth - 1, name, nhat, write_flags))) + data.extend(map(lambda x: ' %s' % x, write_piece(profile_data, depth - 1, name, nhat))) data.append(' }') return data + def serialize_profile(profile_data, name, options): string = '' data = [] @@ -2224,7 +2233,6 @@ def serialize_profile(profile_data, name, options): raise AppArmorBug('serialize_profile(): options is not a dict: %s' % options) include_metadata = options.get('METADATA', False) - include_flags = options.get('FLAGS', True) if include_metadata: string = '# Last Modified: %s\n' % time.asctime() @@ -2239,33 +2247,35 @@ def serialize_profile(profile_data, name, options): else: prof_filename = get_profile_filename_from_profile_name(name, True) - data += active_profiles.get_clean(prof_filename, 0) + data.extend(active_profiles.get_clean(prof_filename, 0)) - #Here should be all the profiles from the files added write after global/common stuff + # Here should be all the profiles from the files added write after global/common stuff for prof in sorted(active_profiles.profiles_in_file(prof_filename)): if prof != name: if original_aa[prof][prof].get('initial_comment', False): comment = original_aa[prof][prof]['initial_comment'] comment.replace('\\n', '\n') - data += [comment + '\n'] - data += write_piece(original_aa[prof], 0, prof, prof, include_flags) + data.append(comment + '\n') + data.extend(write_piece(split_to_merged(original_aa), 0, prof, prof)) else: if profile_data[name].get('initial_comment', False): comment = profile_data[name]['initial_comment'] comment.replace('\\n', '\n') - data += [comment + '\n'] - data += write_piece(profile_data, 0, name, name, include_flags) + data.append(comment + '\n') + + data.extend(write_piece(profile_data, 0, name, name)) string += '\n'.join(data) return string + '\n' + def write_profile_ui_feedback(profile, is_attachment=False): aaui.UI_Info(_('Writing updated profile for %s.') % profile) write_profile(profile, is_attachment) + 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: @@ -2273,19 +2283,19 @@ def write_profile(profile, is_attachment=False): else: 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): - shutil.copymode(prof_filename, newprof.name) - else: - #permission_600 = stat.S_IRUSR | stat.S_IWUSR # Owner read and write - #os.chmod(newprof.name, permission_600) - pass - serialize_options = {'METADATA': True, 'is_attachment': is_attachment} - - profile_string = serialize_profile(aa[profile], profile, serialize_options) - newprof.write(profile_string) - newprof.close() + profile_string = serialize_profile(split_to_merged(aa), profile, serialize_options) + try: + with NamedTemporaryFile('w', suffix='~', delete=False, dir=profile_dir) as newprof: + if os.path.exists(prof_filename): + shutil.copymode(prof_filename, newprof.name) + else: + # permission_600 = stat.S_IRUSR | stat.S_IWUSR # Owner read and write + # os.chmod(newprof.name, permission_600) + pass + newprof.write(profile_string) + except PermissionError as e: + raise AppArmorException(e) os.rename(newprof.name, prof_filename) @@ -2296,8 +2306,9 @@ def write_profile(profile, is_attachment=False): original_aa[profile] = deepcopy(aa[profile]) -def include_list_recursive(profile): - ''' get a list of all includes in a profile and its included files ''' + +def include_list_recursive(profile, in_preamble=False): + """get a list of all includes in a profile and its included files""" includelist = profile['inc_ie'].get_all_full_paths(profile_dir) full_list = [] @@ -2309,13 +2320,19 @@ def include_list_recursive(profile): continue full_list.append(incname) - for childinc in include[incname][incname]['inc_ie'].rules: + if in_preamble: + look_at = active_profiles.files[incname] + else: + look_at = include[incname][incname] + + for childinc in look_at['inc_ie'].rules: for childinc_file in childinc.get_full_paths(profile_dir): if childinc_file not in full_list: - includelist += [childinc_file] + includelist.append(childinc_file) return full_list + def is_known_rule(profile, rule_type, rule_obj): # XXX get rid of get() checks after we have a proper function to initialize a profile if profile.get(rule_type, False): @@ -2330,8 +2347,9 @@ def is_known_rule(profile, rule_type, rule_obj): return False + def get_file_perms(profile, path, audit, deny): - '''get the current permissions for the given path''' + """get the current permissions for the given path""" perms = profile['file'].get_perms_for_path(path, audit, deny) @@ -2340,8 +2358,8 @@ def get_file_perms(profile, path, audit, deny): for incname in includelist: incperms = include[incname][incname]['file'].get_perms_for_path(path, audit, deny) - for allow_or_deny in ['allow', 'deny']: - for owner_or_all in ['all', 'owner']: + for allow_or_deny in ('allow', 'deny'): + for owner_or_all in ('all', 'owner'): for perm in incperms[allow_or_deny][owner_or_all]: perms[allow_or_deny][owner_or_all].add(perm) @@ -2353,11 +2371,12 @@ def get_file_perms(profile, path, audit, deny): return perms + def propose_file_rules(profile_obj, rule_obj): - '''Propose merged file rules based on the existing profile and the log events + """Propose merged file rules based on the existing profile and the log events - permissions get merged - matching paths from existing rules, common_glob() and user_globs get proposed - - IMPORTANT: modifies rule_obj.original_perms and rule_obj.perms''' + - IMPORTANT: modifies rule_obj.original_perms and rule_obj.perms""" options = [] original_path = rule_obj.path.regex @@ -2394,21 +2413,33 @@ def propose_file_rules(profile_obj, rule_obj): return options + def reload_base(bin_path): if not check_for_apparmor(): - return None + return 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) + reload_profile(prof_filename) + + +def reload_profile(prof_filename, raise_exc=False): + """run apparmor_parser to reload the given profile file""" + + ret, out = cmd((parser, '-I%s' % profile_dir, '--base', profile_dir, '-r', prof_filename)) + + if ret != 0: + if raise_exc: + raise AppArmorException(out) + else: + print(out) + def reload(bin_path): bin_path = find_executable(bin_path) - if not bin_path: - return None + if bin_path: + reload_base(bin_path) - return reload_base(bin_path) def get_include_data(filename): data = [] @@ -2421,10 +2452,11 @@ def get_include_data(filename): raise AppArmorException(_('File Not Found: %s') % filename) return data + def include_dir_filelist(include_name): - '''returns a list of files in the given include_name directory, + """returns a list of files in the given include_name directory, except skippable files. - ''' + """ if not include_name.startswith('/'): raise AppArmorBug('incfile %s not starting with /' % include_name) @@ -2440,7 +2472,8 @@ def include_dir_filelist(include_name): return files -def load_include(incname): + +def load_include(incname, in_preamble=False): load_includeslist = [incname] while load_includeslist: incfile = load_includeslist.pop(0) @@ -2451,9 +2484,9 @@ def load_include(incname): pass # already read, do nothing elif os.path.isfile(incfile): data = get_include_data(incfile) - incdata = parse_profile_data(data, incfile, True) + incdata = parse_profile_data(data, incfile, True, in_preamble) attach_profile_data(include, incdata) - #If the include is a directory means include all subfiles + # If the include is a directory means include all subfiles elif os.path.isdir(incfile): load_includeslist += include_dir_filelist(incfile) else: @@ -2461,41 +2494,50 @@ def load_include(incname): return 0 + def check_qualifiers(program): if cfg['qualifiers'].get(program, False): if cfg['qualifiers'][program] != 'p': - fatal_error(_("%s is currently marked as a program that should not have its own\nprofile. Usually, programs are marked this way if creating a profile for \nthem is likely to break the rest of the system. If you know what you\'re\ndoing and are certain you want to create a profile for this program, edit\nthe corresponding entry in the [qualifiers] section in /etc/apparmor/logprof.conf.") % program) + fatal_error( + _("%s is currently marked as a program that should not have its own\n" + "profile. Usually, programs are marked this way if creating a profile for \n" + "them is likely to break the rest of the system. If you know what you're\n" + "doing and are certain you want to create a profile for this program, edit\n" + "the corresponding entry in the [qualifiers] section in /etc/apparmor/logprof.conf.") + % program) return False + def get_subdirectories(current_dir): """Returns a list of all directories directly inside given directory""" - if sys.version_info < (3, 0): - return os.walk(current_dir).next()[1] - else: - return os.walk(current_dir).__next__()[1] + return next(os.walk(current_dir))[1] + def loadincludes(): - incdirs = get_subdirectories(profile_dir) - for idir in incdirs: - if is_skippable_dir(idir): - continue - for dirpath, dirname, files in os.walk(os.path.join(profile_dir, idir)): - if is_skippable_dir(dirpath): - continue + loadincludes_dir('tunables', True) + loadincludes_dir('abstractions', False) + + +def loadincludes_dir(subdir, in_preamble): + idir = os.path.join(profile_dir, subdir) + + if os.path.isdir(idir): # if directory doesn't exist, silently skip loading it + for dirpath, dirname, files in os.walk(idir): for fi in files: if is_skippable_file(fi): continue else: fi = os.path.join(dirpath, fi) - load_include(fi) + load_include(fi, in_preamble) + def glob_common(path): globs = [] - if re.search('[\d\.]+\.so$', path) or re.search('\.so\.[\d\.]+$', path): + if re.search(r'[\d.]+\.so$', path) or re.search(r'\.so\.[\d.]+$', path): libpath = path - libpath = re.sub('[\d\.]+\.so$', '*.so', libpath) - libpath = re.sub('\.so\.[\d\.]+$', '.so.*', libpath) + libpath = re.sub(r'[\d.]+\.so$', '*.so', libpath) + libpath = re.sub(r'\.so\.[\d.]+$', '.so.*', libpath) if libpath != path: globs.append(libpath) @@ -2508,21 +2550,24 @@ def glob_common(path): return sorted(set(globs)) + def combine_name(name1, name2): if name1 == name2: return name1 else: return '%s^%s' % (name1, name2) + def logger_path(): logger = conf.find_first_file(cfg['settings']['logger']) or '/bin/logger' if not os.path.isfile(logger) or not os.access(logger, os.EX_OK): raise AppArmorException("Can't find logger!\nPlease make sure %s exists, or update the 'logger' path in logprof.conf." % logger) return logger + ######Initialisations###### -def init_aa(confdir="/etc/apparmor", profiledir=None): +def init_aa(confdir=None, profiledir=None): global CONFDIR global conf global cfg @@ -2533,6 +2578,9 @@ def init_aa(confdir="/etc/apparmor", profiledir=None): if CONFDIR: return # config already initialized (and possibly changed afterwards), so don't overwrite the config variables + if not confdir: + confdir = "/etc/apparmor" + CONFDIR = confdir conf = apparmor.config.Config('ini', CONFDIR) cfg = conf.read_config('logprof.conf') @@ -2551,10 +2599,10 @@ def init_aa(confdir="/etc/apparmor", profiledir=None): profile_dir = conf.find_first_dir(cfg['settings'].get('profiledir')) or '/etc/apparmor.d' profile_dir = os.path.abspath(profile_dir) if not os.path.isdir(profile_dir): - raise AppArmorException('Can\'t find AppArmor profiles in %s' % (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)) + raise AppArmorException("Can't find apparmor_parser at %s" % (parser)) diff --git a/utils/apparmor/aare.py b/utils/apparmor/aare.py index 67d06cb48..573c46637 100644 --- a/utils/apparmor/aare.py +++ b/utils/apparmor/aare.py @@ -14,14 +14,15 @@ import re -from apparmor.common import convert_regexp, type_is_str, AppArmorBug, AppArmorException +from apparmor.common import convert_regexp, AppArmorBug, AppArmorException -class AARE(object): - '''AARE (AppArmor Regular Expression) wrapper class''' + +class AARE: + """AARE (AppArmor Regular Expression) wrapper class""" def __init__(self, regex, is_path, log_event=None): - '''create an AARE instance for the given AppArmor regex - If is_path is true, the regex is expected to be a path and therefore must start with / or a variable.''' + """create an AARE instance for the given AppArmor regex + If is_path is true, the regex is expected to be a path and therefore must start with / or a variable.""" # using the specified variables when matching. if is_path: @@ -43,7 +44,7 @@ class AARE(object): # self.variables = variables # XXX def __repr__(self): - '''returns a "printable" representation of AARE''' + """returns a "printable" representation of AARE""" return "AARE('%s')" % self.regex def __deepcopy__(self, memo): @@ -58,7 +59,7 @@ class AARE(object): plain_path = re.compile('^[0-9a-zA-Z/._-]+$') def match(self, expression): - '''check if the given expression (string or AARE) matches the regex''' + """check if the given expression (string or AARE) matches the regex""" if type(expression) == AARE: if expression.orig_regex: @@ -68,7 +69,7 @@ class AARE(object): expression = expression.regex else: return self.is_equal(expression) # better safe than sorry - elif not type_is_str(expression): + elif type(expression) is not str: raise AppArmorBug('AARE.match() called with unknown object: %s' % str(expression)) if self._regex_compiled is None: @@ -77,69 +78,69 @@ class AARE(object): return bool(self._regex_compiled.match(expression)) def is_equal(self, expression): - '''check if the given expression is equal''' + """check if the given expression is equal""" if type(expression) == AARE: return self.regex == expression.regex - elif type_is_str(expression): + elif type(expression) is str: return self.regex == expression else: raise AppArmorBug('AARE.is_equal() called with unknown object: %s' % str(expression)) def glob_path(self): - '''Glob the given file or directory path''' - if self.regex[-1] == '/': - if self.regex[-4:] == '/**/' or self.regex[-3:] == '/*/': + """Glob the given file or directory path""" + if self.regex.endswith('/'): + if self.regex.endswith(('/**/', '/*/')): # /foo/**/ and /foo/*/ => /**/ - newpath = re.sub('/[^/]+/\*{1,2}/$', '/**/', self.regex) # re.sub('/[^/]+/\*{1,2}$/', '/\*\*/', self.regex) - elif re.search('/[^/]+\*\*[^/]*/$', self.regex): + newpath = re.sub(r'/[^/]+/\*{1,2}/$', '/**/', self.regex) + elif re.search(r'/[^/]+\*\*[^/]*/$', self.regex): # /foo**/ and /foo**bar/ => /**/ - newpath = re.sub('/[^/]+\*\*[^/]*/$', '/**/', self.regex) - elif re.search('/\*\*[^/]+/$', self.regex): + newpath = re.sub(r'/[^/]+\*\*[^/]*/$', '/**/', self.regex) + elif re.search(r'/\*\*[^/]+/$', self.regex): # /**bar/ => /**/ - newpath = re.sub('/\*\*[^/]+/$', '/**/', self.regex) + newpath = re.sub(r'/\*\*[^/]+/$', '/**/', self.regex) else: newpath = re.sub('/[^/]+/$', '/*/', self.regex) else: - if self.regex[-3:] == '/**' or self.regex[-2:] == '/*': - # /foo/** and /foo/* => /** - newpath = re.sub('/[^/]+/\*{1,2}$', '/**', self.regex) - elif re.search('/[^/]*\*\*[^/]+$', self.regex): - # /**foo and /foor**bar => /** - newpath = re.sub('/[^/]*\*\*[^/]+$', '/**', self.regex) - elif re.search('/[^/]+\*\*$', self.regex): - # /foo** => /** - newpath = re.sub('/[^/]+\*\*$', '/**', self.regex) - else: - newpath = re.sub('/[^/]+$', '/*', self.regex) + if self.regex.endswith(('/**', '/*')): + # /foo/** and /foo/* => /** + newpath = re.sub(r'/[^/]+/\*{1,2}$', '/**', self.regex) + elif re.search(r'/[^/]*\*\*[^/]+$', self.regex): + # /**foo and /foor**bar => /** + newpath = re.sub(r'/[^/]*\*\*[^/]+$', '/**', self.regex) + elif re.search(r'/[^/]+\*\*$', self.regex): + # /foo** => /** + newpath = re.sub(r'/[^/]+\*\*$', '/**', self.regex) + else: + newpath = re.sub('/[^/]+$', '/*', self.regex) return AARE(newpath, False) def glob_path_withext(self): - '''Glob given file path with extension - Files without extensions and directories won't be changed''' + """Glob given file path with extension + Files without extensions and directories won't be changed""" # match /**.ext and /*.ext - match = re.search('/\*{1,2}(\.[^/]+)$', self.regex) + match = re.search(r'/\*{1,2}(\.[^/]+)$', self.regex) if match: # /foo/**.ext and /foo/*.ext => /**.ext - newpath = re.sub('/[^/]+/\*{1,2}\.[^/]+$', '/**' + match.groups()[0], self.regex) - elif re.search('/[^/]+\*\*[^/]*\.[^/]+$', self.regex): + newpath = re.sub(r'/[^/]+/\*{1,2}\.[^/]+$', '/**' + match.groups()[0], self.regex) + elif re.search(r'/[^/]+\*\*[^/]*\.[^/]+$', self.regex): # /foo**.ext and /foo**bar.ext => /**.ext - match = re.search('/[^/]+\*\*[^/]*(\.[^/]+)$', self.regex) - newpath = re.sub('/[^/]+\*\*[^/]*\.[^/]+$', '/**' + match.groups()[0], self.regex) - elif re.search('/\*\*[^/]+\.[^/]+$', self.regex): + match = re.search(r'/[^/]+\*\*[^/]*(\.[^/]+)$', self.regex) + newpath = re.sub(r'/[^/]+\*\*[^/]*\.[^/]+$', '/**' + match.groups()[0], self.regex) + elif re.search(r'/\*\*[^/]+\.[^/]+$', self.regex): # /**foo.ext => /**.ext - match = re.search('/\*\*[^/]+(\.[^/]+)$', self.regex) - newpath = re.sub('/\*\*[^/]+\.[^/]+$', '/**' + match.groups()[0], self.regex) + match = re.search(r'/\*\*[^/]+(\.[^/]+)$', self.regex) + newpath = re.sub(r'/\*\*[^/]+\.[^/]+$', '/**' + match.groups()[0], self.regex) else: newpath = self.regex - match = re.search('(\.[^/]+)$', self.regex) + match = re.search(r'(\.[^/]+)$', self.regex) if match: - newpath = re.sub('/[^/]+(\.[^/]+)$', '/*' + match.groups()[0], self.regex) - return AARE(newpath, False) + newpath = re.sub(r'/[^/]+(\.[^/]+)$', '/*' + match.groups()[0], self.regex) + return type(self)(newpath, False) def convert_expression_to_aare(expression): - '''convert an expression (taken from audit.log) to an AARE string''' + """convert an expression (taken from audit.log) to an AARE string""" aare_escape_chars = ['\\', '?', '*', '[', ']', '{', '}', '"', '!'] for char in aare_escape_chars: diff --git a/utils/apparmor/cleanprofile.py b/utils/apparmor/cleanprofile.py index af2380bef..c35acb0e1 100644 --- a/utils/apparmor/cleanprofile.py +++ b/utils/apparmor/cleanprofile.py @@ -14,7 +14,8 @@ # ---------------------------------------------------------------------- import apparmor.aa as apparmor -class Prof(object): + +class Prof: def __init__(self, filename): apparmor.init_aa() self.aa = apparmor.aa @@ -22,9 +23,10 @@ class Prof(object): self.include = apparmor.include self.filename = filename -class CleanProf(object): + +class CleanProf: def __init__(self, same_file, profile, other): - #If same_file we're basically comparing the file against itself to check superfluous rules + # If same_file we're basically comparing the file against itself to check superfluous rules self.same_file = same_file self.profile = profile self.other = other @@ -40,28 +42,28 @@ class CleanProf(object): return deleted def remove_duplicate_rules(self, program): - #Process the profile of the program + # Process the profile of the program deleted = 0 # remove duplicate rules from the preamble deleted += self.profile.active_profiles.delete_preamble_duplicates(self.profile.filename) - #Process every hat in the profile individually + # Process every hat in the profile individually for hat in sorted(self.profile.aa[program].keys()): includes = self.profile.aa[program][hat]['inc_ie'].get_all_full_paths(apparmor.profile_dir) - #Clean up superfluous rules from includes in the other profile + # Clean up superfluous rules from includes in the other profile for inc in includes: if not self.profile.include.get(inc, {}).get(inc, False): apparmor.load_include(inc) - if self.other.aa[program].get(hat): # carefully avoid to accidently initialize self.other.aa[program][hat] + if self.other.aa[program].get(hat): # carefully avoid to accidentally initialize self.other.aa[program][hat] deleted += apparmor.delete_all_duplicates(self.other.aa[program][hat], inc, apparmor.ruletypes) - #Clean duplicate rules in other profile + # Clean duplicate rules in other profile for ruletype in apparmor.ruletypes: if not self.same_file: - if self.other.aa[program].get(hat): # carefully avoid to accidently initialize self.other.aa[program][hat] + if self.other.aa[program].get(hat): # carefully avoid to accidentally initialize self.other.aa[program][hat] deleted += self.other.aa[program][hat][ruletype].delete_duplicates(self.profile.aa[program][hat][ruletype]) else: deleted += self.other.aa[program][hat][ruletype].delete_duplicates(None) diff --git a/utils/apparmor/common.py b/utils/apparmor/common.py index ec90a25d6..21612bf06 100644 --- a/utils/apparmor/common.py +++ b/utils/apparmor/common.py @@ -9,7 +9,6 @@ # # ------------------------------------------------------------------ -from __future__ import print_function import collections import glob import logging @@ -19,6 +18,8 @@ import subprocess import sys import termios import tty +from tempfile import NamedTemporaryFile + import apparmor.rules as rules DEBUGGING = False @@ -28,22 +29,23 @@ DEBUGGING = False # Utility classes # class AppArmorException(Exception): - '''This class represents AppArmor exceptions''' + """This class represents AppArmor exceptions""" def __init__(self, value): self.value = value def __str__(self): return repr(self.value) + class AppArmorBug(Exception): - '''This class represents AppArmor exceptions "that should never happen"''' - pass + """This class represents AppArmor exceptions "that should never happen".""" + # # Utility functions # def error(out, exit_code=1, do_exit=True): - '''Print error message and exit''' + """Print error message and exit""" try: print("ERROR: %s" % (out), file=sys.stderr) except IOError: @@ -52,22 +54,25 @@ def error(out, exit_code=1, do_exit=True): if do_exit: sys.exit(exit_code) + def warn(out): - '''Print warning message''' + """Print warning message""" try: print("WARN: %s" % (out), file=sys.stderr) except IOError: pass + def msg(out, output=sys.stdout): - '''Print message''' + """Print message""" try: print("%s" % (out), file=output) except IOError: pass + def debug(out): - '''Print debug message''' + """Print debug message""" global DEBUGGING if DEBUGGING: try: @@ -75,41 +80,43 @@ def debug(out): except IOError: pass -def recursive_print(src, dpth = 0, key = ''): + +def recursive_print(src, dpth=0, key=''): # print recursively in a nicely formatted way # useful for debugging, too verbose for production code ;-) # based on code "stolen" from Scott S-Allen / MIT License # http://code.activestate.com/recipes/578094-recursively-print-nested-dictionaries/ - """ Recursively prints nested elements.""" + """Recursively prints nested elements.""" tabs = ' ' * dpth * 4 # or 2 or 8 or... if isinstance(src, dict): empty = True for key in src.keys(): - print (tabs + '[%s]' % key) + print(tabs + '[%s]' % key) recursive_print(src[key], dpth + 1, key) empty = False if empty: - print (tabs + '[--- empty ---]') + print(tabs + '[--- empty ---]') elif isinstance(src, list) or isinstance(src, tuple): - if len(src) == 0: - print (tabs + '[--- empty ---]') + if not src: + print(tabs + '[--- empty ---]') else: - print (tabs + "[") + print(tabs + "[") for litem in src: recursive_print(litem, dpth + 1) - print (tabs + "]") + print(tabs + "]") elif isinstance(src, rules._Raw_Rule): src.recursive_print(dpth) else: if key: - print (tabs + '%s = %s' % (key, src)) + print(tabs + '%s = %s' % (key, src)) else: - print (tabs + '- %s' % src) + print(tabs + '- %s' % src) + def cmd(command): - '''Try to execute the given command.''' + """Try to execute the given command.""" debug(command) try: sp = subprocess.Popen(command, stdout=subprocess.PIPE, @@ -117,31 +124,26 @@ def cmd(command): except OSError as ex: return [127, str(ex)] - if sys.version_info[0] >= 3: - out = sp.communicate()[0].decode('ascii', 'ignore') - else: - out = sp.communicate()[0] + out = sp.communicate()[0].decode('ascii', 'ignore') return [sp.returncode, out] def cmd_pipe(command1, command2): - '''Try to pipe command1 into command2.''' + """Try to pipe command1 into command2.""" try: sp1 = subprocess.Popen(command1, stdout=subprocess.PIPE) sp2 = subprocess.Popen(command2, stdin=sp1.stdout) except OSError as ex: return [127, str(ex)] - if sys.version_info[0] >= 3: - out = sp2.communicate()[0].decode('ascii', 'ignore') - else: - out = sp2.communicate()[0] + out = sp2.communicate()[0].decode('ascii', 'ignore') return [sp2.returncode, out] + def valid_path(path): - '''Valid path''' + """Valid path""" # No relative paths m = "Invalid path: %s" % (path) if not path.startswith('/'): @@ -159,8 +161,9 @@ def valid_path(path): return False return True + def get_directory_contents(path): - '''Find contents of the given directory''' + """Find contents of the given directory""" if not valid_path(path): return None @@ -171,6 +174,7 @@ def get_directory_contents(path): files.sort() return files + 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() @@ -178,40 +182,39 @@ def is_skippable_file(path): basename = os.path.basename(path) - if not basename or basename[0] == '.' or basename == 'README': + if not basename or basename.startswith('.') or basename == 'README': return True - skippable_suffix = ('.dpkg-new', '.dpkg-old', '.dpkg-dist', '.dpkg-bak', '.dpkg-remove', '.pacsave', '.pacnew', '.rpmnew', '.rpmsave', '.orig', '.rej', '~') + skippable_suffix = ( + '.dpkg-new', '.dpkg-old', '.dpkg-dist', '.dpkg-bak', '.dpkg-remove', + '.pacsave', '.pacnew', '.rpmnew', '.rpmsave', '.orig', '.rej', '~') if basename.endswith(skippable_suffix): return True return False + def open_file_read(path, encoding='UTF-8'): - '''Open specified file read-only''' + """Open specified file read-only""" return open_file_anymode('r', path, encoding) + def open_file_write(path): - '''Open specified file in write/overwrite mode''' + """Open specified file in write/overwrite mode""" return open_file_anymode('w', path, 'UTF-8') + def open_file_anymode(mode, path, encoding='UTF-8'): - '''Crash-resistant wrapper to open a specified file in specified mode''' + """Crash-resistant wrapper to open a specified file in specified mode""" # This avoids a crash when reading a logfile with special characters that # are not utf8-encoded (for example a latin1 "ö"), and also avoids crashes # at several other places we don't know yet ;-) - errorhandling = 'surrogateescape' - - if sys.version_info[0] < 3: - errorhandling = 'replace' + return open(path, mode, encoding=encoding, errors='surrogateescape') - orig = open(path, mode, encoding=encoding, errors=errorhandling) - - return orig def readkey(): - '''Returns the pressed key''' + """Returns the pressed key""" fd = sys.stdin.fileno() old_settings = termios.tcgetattr(fd) try: @@ -222,20 +225,22 @@ def readkey(): return ch + def hasher(): - '''A neat alternative to perl's hash reference''' + """A neat alternative to perl's hash reference""" # Creates a dictionary for any depth and returns empty dictionary otherwise # WARNING: when reading non-existing sub-dicts, empty dicts will be added. # This might cause strange effects when using .keys() return collections.defaultdict(hasher) + def convert_regexp(regexp): regex_paren = re.compile('^(.*){([^}]*)}(.*)$') regexp = regexp.strip() regexp = regexp.replace('(', '\\(').replace(')', '\\)') # escape '(' and ')' - new_reg = re.sub(r'(?<!\\)(\.|\+|\$)', r'\\\1', regexp) + new_reg = re.sub(r'(?<!\\)([.+$])', r'\\\1', regexp) while regex_paren.search(new_reg): match = regex_paren.search(new_reg).groups() @@ -248,34 +253,26 @@ def convert_regexp(regexp): multi_glob = '__KJHDKVZH_AAPROF_INTERNAL_GLOB_SVCUZDGZID__' new_reg = new_reg.replace('**', multi_glob) - #print(new_reg) + # print(new_reg) - # Match atleast one character if * or ** after / + # Match at least one character if * or ** after / # ?< is the negative lookback operator new_reg = new_reg.replace('*', '(((?<=/)[^/\000]+)|((?<!/)[^/\000]*))') new_reg = new_reg.replace(multi_glob, '(((?<=/)[^\000]+)|((?<!/)[^\000]*))') - if regexp[0] != '^': + if not regexp.startswith('^'): new_reg = '^' + new_reg - if regexp[-1] != '$': + if not regexp.endswith('$'): new_reg = new_reg + '$' return new_reg + def user_perm(prof_dir): if not os.access(prof_dir, os.W_OK): - sys.stdout.write("Cannot write to profile directory.\n" + + sys.stdout.write("Cannot write to profile directory.\n" "Please run as a user with appropriate permissions.\n") return False return True -if sys.version_info[0] > 2: - unicode = str # python 3 dropped the unicode type. To keep type_is_str() simple (and pyflakes3 happy), re-create it as alias of str. - -def type_is_str(var): - ''' returns True if the given variable is a str (or unicode string when using python 2)''' - if type(var) in [str, unicode]: # python 2 sometimes uses the 'unicode' type - return True - else: - return False def split_name(full_profile): if '//' in full_profile: @@ -288,13 +285,26 @@ def split_name(full_profile): return (profile, hat) -class DebugLogger(object): - '''Unified debug facility. Logs to file or stderr. +def combine_profname(name_parts): + """combine name_parts (main profile, child) into a joint main//child profile name""" + + if type(name_parts) is not list: + raise AppArmorBug('combine_name() called with parameter of type %s, must be a list' % type(name_parts)) + + # if last item is None, drop it (can happen when called with [profile, hat] when hat is None) + if name_parts[len(name_parts)-1] is None: + name_parts.pop(-1) + + return '//'.join(name_parts) + + +class DebugLogger: + """Unified debug facility. Logs to file or stderr. Does not log anything by default. Will only log if environment variable LOGPROF_DEBUG is set to a number between 1 and 3 or if method activateStderr is run. - ''' + """ def __init__(self, module_name=__name__): self.debugging = False self.debug_level = logging.DEBUG @@ -323,8 +333,8 @@ class DebugLogger(object): format='%(asctime)s - %(name)s - %(message)s\n') except IOError: # Unable to open the default logfile, so create a temporary logfile and tell use about it - import tempfile - templog = tempfile.NamedTemporaryFile('w', prefix='apparmor', suffix='.log', delete=False) + templog = NamedTemporaryFile('w', prefix='apparmor', suffix='.log', delete=False) + templog.close() sys.stdout.write("\nCould not open: %s\nLogging to: %s\n" % (self.logfile, templog.name)) logging.basicConfig(filename=templog.name, level=self.debug_level, @@ -355,4 +365,4 @@ class DebugLogger(object): def shutdown(self): logging.shutdown() - #logging.shutdown([self.logger]) + # logging.shutdown([self.logger]) diff --git a/utils/apparmor/config.py b/utils/apparmor/config.py index 9997655df..7226819f0 100644 --- a/utils/apparmor/config.py +++ b/utils/apparmor/config.py @@ -11,29 +11,12 @@ # GNU General Public License for more details. # # ---------------------------------------------------------------------- -from __future__ import with_statement import os import shlex import shutil import stat -import sys -import tempfile -if sys.version_info < (3, 0): - import ConfigParser as configparser - - # Class to provide the object[section][option] behavior in Python2 - class configparser_py2(configparser.ConfigParser): - def __getitem__(self, section): - section_val = self.items(section) - section_options = dict() - for option, value in section_val: - section_options[option] = value - return section_options - - -else: - import configparser - +from configparser import ConfigParser +from tempfile import NamedTemporaryFile from apparmor.common import AppArmorException, open_file_read # , warn, msg, @@ -41,7 +24,7 @@ from apparmor.common import AppArmorException, open_file_read # , warn, msg, # CFG = None # REPO_CFG = None # SHELL_FILES = ['easyprof.conf', 'notify.conf', 'parser.conf'] -class Config(object): +class Config: def __init__(self, conf_type, conf_dir='/etc/apparmor'): self.CONF_DIR = conf_dir # The type of config file that'll be read and/or written @@ -55,7 +38,7 @@ class Config(object): if self.conf_type == 'shell': config = {'': dict()} elif self.conf_type == 'ini': - config = configparser.ConfigParser() + config = ConfigParser() return config def read_config(self, filename): @@ -65,21 +48,10 @@ class Config(object): if self.conf_type == 'shell': config = self.read_shell(filepath) elif self.conf_type == 'ini': - if sys.version_info > (3, 0): - config = configparser.ConfigParser() - else: - config = configparser_py2() + config = ConfigParser() # Set the option form to string -prevents forced conversion to lowercase config.optionxform = str - if sys.version_info > (3, 0): - config.read(filepath) - else: - try: - config.read(filepath) - except configparser.ParsingError: - tmp_filepath = py2_parser(filepath) - config.read(tmp_filepath.name) - ##config.__get__() + config.read(filepath) return config def write_config(self, filename, config): @@ -88,18 +60,17 @@ class Config(object): permission_600 = stat.S_IRUSR | stat.S_IWUSR # Owner read and write try: # Open a temporary file in the CONF_DIR to write the config file - config_file = tempfile.NamedTemporaryFile('w', prefix='aa_temp', delete=False, dir=self.CONF_DIR) - if os.path.exists(self.input_file): - # Copy permissions from an existing file to temporary file - shutil.copymode(self.input_file, config_file.name) - else: - # If no existing permission set the file permissions as 0600 - os.chmod(config_file.name, permission_600) - if self.conf_type == 'shell': - self.write_shell(filepath, config_file, config) - elif self.conf_type == 'ini': - self.write_configparser(filepath, config_file, config) - config_file.close() + with NamedTemporaryFile('w', prefix='aa_temp', delete=False, dir=self.CONF_DIR) as config_file: + if os.path.exists(self.input_file): + # Copy permissions from an existing file to temporary file + shutil.copymode(self.input_file, config_file.name) + else: + # If no existing permission set the file permissions as 0600 + os.chmod(config_file.name, permission_600) + if self.conf_type == 'shell': + self.write_shell(filepath, config_file, config) + elif self.conf_type == 'ini': + self.write_configparser(filepath, config_file, config) except IOError: raise AppArmorException("Unable to write to %s" % filename) else: @@ -219,7 +190,7 @@ class Config(object): if os.path.exists(self.input_file): with open_file_read(self.input_file) as f_in: for line in f_in: - # If its a section + # If it's a section if line.lstrip().startswith('['): # If any options from preceding section remain write them if options: @@ -275,20 +246,3 @@ class Config(object): for option in options: line = ' ' + option + ' = ' + config[section][option] + '\n' f_out.write(line) - -def py2_parser(filename): - """Returns the de-dented ini file from the new format ini""" - tmp = tempfile.NamedTemporaryFile('rw') - f_out = open(tmp.name, 'w') - if os.path.exists(filename): - with open_file_read(filename) as f_in: - for line in f_in: - # The ini format allows for multi-line entries, with the subsequent - # entries being indented deeper hence simple lstrip() is not appropriate - if line[:2] == ' ': - line = line[2:] - elif line[0] == '\t': - line = line[1:] - f_out.write(line) - f_out.flush() - return tmp diff --git a/utils/apparmor/easyprof.py b/utils/apparmor/easyprof.py index 9ccc7ef10..74b04316e 100644 --- a/utils/apparmor/easyprof.py +++ b/utils/apparmor/easyprof.py @@ -8,42 +8,28 @@ # # ------------------------------------------------------------------ -from __future__ import with_statement - -import codecs import copy import glob import json import optparse import os import re -import shutil import subprocess import sys -import tempfile +from shutil import which +from tempfile import NamedTemporaryFile + +from apparmor.common import AppArmorException, open_file_read -# -# TODO: move this out to the common library -# -#from apparmor import AppArmorException -class AppArmorException(Exception): - '''This class represents AppArmor exceptions''' - def __init__(self, value): - self.value = value - - def __str__(self): - return repr(self.value) -# -# End common -# DEBUGGING = False + # # TODO: move this out to a utilities library # def error(out, exit_code=1, do_exit=True): - '''Print error message and exit''' + """Print error message and exit""" try: sys.stderr.write("ERROR: %s\n" % (out)) except IOError: @@ -54,7 +40,7 @@ def error(out, exit_code=1, do_exit=True): def warn(out): - '''Print warning message''' + """Print warning message""" try: sys.stderr.write("WARN: %s\n" % (out)) except IOError: @@ -62,7 +48,7 @@ def warn(out): def msg(out, output=sys.stdout): - '''Print message''' + """Print message""" try: sys.stdout.write("%s\n" % (out)) except IOError: @@ -70,7 +56,7 @@ def msg(out, output=sys.stdout): def cmd(command): - '''Try to execute the given command.''' + """Try to execute the given command.""" debug(command) try: sp = subprocess.Popen(command, stdout=subprocess.PIPE, @@ -83,7 +69,7 @@ def cmd(command): def debug(out): - '''Print debug message''' + """Print debug message""" if DEBUGGING: try: sys.stderr.write("DEBUG: %s\n" % (out)) @@ -92,7 +78,7 @@ def debug(out): def valid_binary_path(path): - '''Validate name''' + """Validate name""" try: a_path = os.path.abspath(path) except Exception: @@ -115,7 +101,7 @@ def valid_binary_path(path): def valid_variable(v): - '''Validate variable name''' + """Validate variable name""" debug("Checking '%s'" % v) try: (key, value) = v.split('=') @@ -140,13 +126,13 @@ def valid_variable(v): def valid_path(path, relative_ok=False): - '''Valid path''' + """Valid path""" m = "Invalid path: %s" % (path) if not relative_ok and not path.startswith('/'): debug("%s (relative)" % (m)) return False - if '"' in path: # We double quote elsewhere + if '"' in path: # We double quote elsewhere debug("%s (quote)" % (m)) return False @@ -169,19 +155,19 @@ def valid_path(path, relative_ok=False): def _is_safe(s): - '''Known safe regex''' - if re.search(r'^[a-zA-Z_0-9\-\.]+$', s): + """Known safe regex""" + if re.search(r'^[a-zA-Z_0-9\-.]+$', s): return True return False def valid_policy_vendor(s): - '''Verify the policy vendor''' + """Verify the policy vendor""" return _is_safe(s) def valid_policy_version(v): - '''Verify the policy version''' + """Verify the policy version""" try: float(v) except ValueError: @@ -192,7 +178,7 @@ def valid_policy_version(v): def valid_template_name(s, strict=False): - '''Verify the template name''' + """Verify the template name""" if not strict and s.startswith('/'): if not valid_path(s): return False @@ -201,12 +187,12 @@ def valid_template_name(s, strict=False): def valid_abstraction_name(s): - '''Verify the template name''' + """Verify the template name""" return _is_safe(s) def valid_profile_name(s): - '''Verify the profile name''' + """Verify the profile name""" # profile name specifies path if s.startswith('/'): if not valid_path(s): @@ -214,19 +200,19 @@ def valid_profile_name(s): return True # profile name does not specify path - # alpha-numeric and Debian version, plus '_' - if re.search(r'^[a-zA-Z0-9][a-zA-Z0-9_\+\-\.:~]+$', s): + # alphanumeric and Debian version, plus '_' + if re.search(r'^[a-zA-Z0-9][a-zA-Z0-9_+\-.:~]+$', s): return True return False def valid_policy_group_name(s): - '''Verify policy group name''' + """Verify policy group name""" return _is_safe(s) def get_directory_contents(path): - '''Find contents of the given directory''' + """Find contents of the given directory""" if not valid_path(path): return None @@ -237,45 +223,33 @@ def get_directory_contents(path): files.sort() return files -def open_file_read(path): - '''Open specified file read-only''' - try: - orig = codecs.open(path, 'r', "UTF-8") - except Exception: - raise - - return orig - def verify_policy(policy, exe, base=None, include=None): - '''Verify policy compiles''' + """Verify policy compiles""" 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 if len(policy.splitlines()) == 1 and valid_path(policy): fn = policy else: - f, fn = tempfile.mkstemp(prefix='aa-easyprof') - if not isinstance(policy, bytes): - policy = policy.encode('utf-8') - os.write(f, policy) - os.close(f) + with NamedTemporaryFile('wb', prefix='aa-easyprof', delete=False) as f: + fn = f.name + if not isinstance(policy, bytes): + policy = policy.encode('utf-8') + f.write(policy) command = [exe, '-QTK'] if base: - command.extend(['-b', base]) + command.extend(('-b', base)) if include: - command.extend(['-I', include]) + command.extend(('-I', include)) command.append(fn) rc, out = cmd(command) os.unlink(fn) - if rc == 0: - return True - return False + return rc == 0 # # End utility functions @@ -283,7 +257,7 @@ def verify_policy(policy, exe, base=None, include=None): class AppArmorEasyProfile: - '''Easy profile class''' + """Easy profile class""" def __init__(self, binary, opt): verify_options(opt) opt.ensure_value("conffile", "/etc/apparmor/easyprof.conf") @@ -294,13 +268,9 @@ class AppArmorEasyProfile: if os.path.isfile(self.conffile): self._get_defaults() - self.parser_path = '/sbin/apparmor_parser' + self.parser_path = which('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: @@ -312,30 +282,28 @@ class AppArmorEasyProfile: if opt.templates_dir and os.path.isdir(opt.templates_dir): self.dirs['templates'] = os.path.abspath(opt.templates_dir) - elif not opt.templates_dir and \ - opt.template and \ - os.path.isfile(opt.template) and \ - valid_path(opt.template): - # If we specified the template and it is an absolute path, just set - # the templates directory to the parent of the template so we don't + elif (not opt.templates_dir + and opt.template + and os.path.isfile(opt.template) + and valid_path(opt.template)): + # If we specified the template and it is an absolute path, just set + # the templates directory to the parent of the template so we don't # have to require --template-dir with absolute paths. self.dirs['templates'] = os.path.abspath(os.path.dirname(opt.template)) - if opt.include_templates_dir and \ - os.path.isdir(opt.include_templates_dir): + if opt.include_templates_dir and os.path.isdir(opt.include_templates_dir): self.dirs['templates_include'] = os.path.abspath(opt.include_templates_dir) if opt.policy_groups_dir and os.path.isdir(opt.policy_groups_dir): self.dirs['policygroups'] = os.path.abspath(opt.policy_groups_dir) - if opt.include_policy_groups_dir and \ - os.path.isdir(opt.include_policy_groups_dir): + if opt.include_policy_groups_dir and os.path.isdir(opt.include_policy_groups_dir): self.dirs['policygroups_include'] = os.path.abspath(opt.include_policy_groups_dir) self.policy_version = None self.policy_vendor = None - if (opt.policy_version and not opt.policy_vendor) or \ - (opt.policy_vendor and not opt.policy_version): + if ((opt.policy_version and not opt.policy_vendor) + or (opt.policy_vendor and not opt.policy_version)): raise AppArmorException("Must specify both policy version and vendor") # If specified --policy-version and --policy-vendor, use @@ -344,18 +312,18 @@ class AppArmorEasyProfile: self.policy_vendor = opt.policy_vendor self.policy_version = str(opt.policy_version) - for i in ['templates', 'policygroups']: - d = os.path.join(self.dirs[i], \ - self.policy_vendor, \ + for i in ('templates', 'policygroups'): + d = os.path.join(self.dirs[i], + self.policy_vendor, self.policy_version) if not os.path.isdir(d): raise AppArmorException( "Could not find %s directory '%s'" % (i, d)) self.dirs[i] = d - if not 'templates' in self.dirs: + if 'templates' not in self.dirs: raise AppArmorException("Could not find templates directory") - if not 'policygroups' in self.dirs: + if 'policygroups' not in self.dirs: raise AppArmorException("Could not find policygroups directory") self.binary = binary @@ -371,7 +339,7 @@ class AppArmorEasyProfile: self.set_policygroup(opt.policy_groups) if opt.name: self.set_name(opt.name) - elif self.binary != None: + elif self.binary is not None: self.set_name(self.binary) self.templates = [] @@ -395,24 +363,22 @@ class AppArmorEasyProfile: self.policy_groups.append(f) def _get_defaults(self): - '''Read in defaults from configuration''' + """Read in defaults from configuration""" if not os.path.exists(self.conffile): raise AppArmorException("Could not find '%s'" % self.conffile) # Read in the configuration - f = open_file_read(self.conffile) - pat = re.compile(r'^\w+=".*"?') - for line in f: - if not pat.search(line): - continue - if line.startswith("POLICYGROUPS_DIR="): - d = re.split(r'=', line.strip())[1].strip('["\']') - self.dirs['policygroups'] = d - elif line.startswith("TEMPLATES_DIR="): - d = re.split(r'=', line.strip())[1].strip('["\']') - self.dirs['templates'] = d - f.close() + with open_file_read(self.conffile) as f: + for line in f: + if not pat.search(line): + continue + if line.startswith("POLICYGROUPS_DIR="): + d = re.split('=', line.strip())[1].strip('["\']') + self.dirs['policygroups'] = d + elif line.startswith("TEMPLATES_DIR="): + d = re.split('=', line.strip())[1].strip('["\']') + self.dirs['templates'] = d keys = self.dirs.keys() if 'templates' not in keys: @@ -425,15 +391,16 @@ class AppArmorEasyProfile: raise AppArmorException("Could not find '%s'" % self.dirs[k]) def set_name(self, name): - '''Set name of policy''' + """Set name of policy""" self.name = name def get_template(self): - '''Get contents of current template''' - return open(self.template).read() + """Get contents of current template""" + with open(self.template) as f: + return f.read() def set_template(self, template, allow_abs_path=True): - '''Set current template''' + """Set current template""" if "../" in template: raise AppArmorException('template "%s" contains "../" escape path' % (template)) elif template.startswith('/') and not allow_abs_path: @@ -460,11 +427,11 @@ class AppArmorEasyProfile: raise AppArmorException('%s does not exist' % (template)) def get_templates(self): - '''Get list of all available templates by filename''' + """Get list of all available templates by filename""" return self.templates def get_policygroup(self, policygroup): - '''Get contents of specific policygroup''' + """Get contents of specific policygroup""" p = policygroup if not p.startswith('/'): sys_p = os.path.join(self.dirs['policygroups'], p) @@ -477,14 +444,15 @@ class AppArmorEasyProfile: elif inc_p is not None and os.path.exists(inc_p): p = inc_p - if self.policy_groups == None or not p in self.policy_groups: + if self.policy_groups is None or p not in self.policy_groups: raise AppArmorException("Policy group '%s' does not exist" % p) - return open(p).read() + with open(p) as f: + return f.read() def set_policygroup(self, policygroups): - '''Set policygroups''' + """Set policygroups""" self.policy_groups = [] - if policygroups != None: + if policygroups is not None: for p in policygroups.split(','): # If have abs path, just use it if p.startswith('/'): @@ -507,11 +475,11 @@ class AppArmorEasyProfile: raise AppArmorException('%s does not exist' % (p)) def get_policy_groups(self): - '''Get list of all policy groups by filename''' + """Get list of all policy groups by filename""" return self.policy_groups def gen_abstraction_rule(self, abstraction): - '''Generate an abstraction rule''' + """Generate an abstraction rule""" base = os.path.join(self.parser_base, "abstractions", abstraction) if not os.path.exists(base): if not self.parser_include: @@ -524,7 +492,7 @@ class AppArmorEasyProfile: return "#include <abstractions/%s>" % abstraction def gen_variable_declaration(self, dec): - '''Generate a variable declaration''' + """Generate a variable declaration""" if not valid_variable(dec): raise AppArmorException("Invalid variable declaration '%s'" % dec) # Make sure we always quote @@ -551,21 +519,22 @@ class AppArmorEasyProfile: return rule - - def gen_policy(self, name, - binary=None, - profile_name=None, - template_var=[], - abstractions=None, - policy_groups=None, - read_path=[], - write_path=[], - author=None, - comment=None, - copyright=None, - no_verify=False): + def gen_policy( + self, + name, + binary=None, + profile_name=None, + template_var=[], + abstractions=None, + policy_groups=None, + read_path=[], + write_path=[], + author=None, + comment=None, + copyright=None, + no_verify=False): def find_prefix(t, s): - '''Calculate whitespace prefix based on occurrence of s in t''' + """Calculate whitespace prefix based on occurrence of s in t""" pat = re.compile(r'^ *%s' % s) p = "" for line in t.splitlines(): @@ -589,8 +558,7 @@ class AppArmorEasyProfile: attachment = "" if binary: if not valid_binary_path(binary): - raise AppArmorException("Invalid path for binary: '%s'" % \ - binary) + raise AppArmorException("Invalid path for binary: '%s'" % binary) if profile_name: attachment = 'profile "%s" "%s"' % (profile_name, binary) else: @@ -604,20 +572,20 @@ class AppArmorEasyProfile: policy = re.sub(r'###NAME###', name, policy) # Fill-in various comment fields - if comment != None: + if comment is not None: policy = re.sub(r'###COMMENT###', "Comment: %s" % comment, policy) - if author != None: + if author is not None: policy = re.sub(r'###AUTHOR###', "Author: %s" % author, policy) - if copyright != None: + if copyright is not None: policy = re.sub(r'###COPYRIGHT###', "Copyright: %s" % copyright, policy) # Fill-in rules and variables with proper indenting search = '###ABSTRACTIONS###' prefix = find_prefix(policy, search) s = "%s# No abstractions specified" % prefix - if abstractions != None: + if abstractions is not None: s = "%s# Specified abstractions" % (prefix) t = abstractions.split(',') t.sort() @@ -628,7 +596,7 @@ class AppArmorEasyProfile: search = '###POLICYGROUPS###' prefix = find_prefix(policy, search) s = "%s# No policy groups specified" % prefix - if policy_groups != None: + if policy_groups is not None: s = "%s# Rules specified via policy groups" % (prefix) t = policy_groups.split(',') t.sort() @@ -642,7 +610,7 @@ class AppArmorEasyProfile: search = '###VAR###' prefix = find_prefix(policy, search) s = "%s# No template variables specified" % prefix - if len(template_var) > 0: + if template_var: s = "%s# Specified profile variables" % (prefix) template_var.sort() for i in template_var: @@ -652,7 +620,7 @@ class AppArmorEasyProfile: search = '###READS###' prefix = find_prefix(policy, search) s = "%s# No read paths specified" % prefix - if len(read_path) > 0: + if read_path: s = "%s# Specified read permissions" % (prefix) read_path.sort() for i in read_path: @@ -663,7 +631,7 @@ class AppArmorEasyProfile: search = '###WRITES###' prefix = find_prefix(policy, search) s = "%s# No write paths specified" % prefix - if len(write_path) > 0: + if write_path: s = "%s# Specified write permissions" % (prefix) write_path.sort() for i in write_path: @@ -680,7 +648,7 @@ class AppArmorEasyProfile: return policy def output_policy(self, params, count=0, dir=None): - '''Output policy''' + """Output policy""" policy = self.gen_policy(**params) if not dir: if count: @@ -692,10 +660,10 @@ class AppArmorEasyProfile: out_fn = params['profile_name'] elif 'binary' in params: out_fn = params['binary'] - else: # should not ever reach this + else: # should not ever reach this raise AppArmorException("Could not determine output filename") - # Generate an absolute path, convertng any path delimiters to '.' + # Generate an absolute path, converting any path delimiters to '.' out_fn = os.path.join(dir, re.sub(r'/', '.', out_fn.lstrip('/'))) if os.path.exists(out_fn): raise AppArmorException("'%s' already exists" % out_fn) @@ -706,16 +674,14 @@ class AppArmorEasyProfile: if not os.path.isdir(dir): raise AppArmorException("'%s' is not a directory" % dir) - f, fn = tempfile.mkstemp(prefix='aa-easyprof') if not isinstance(policy, bytes): policy = policy.encode('utf-8') - os.write(f, policy) - os.close(f) - - shutil.move(fn, out_fn) + with NamedTemporaryFile('wb', prefix='aa-easyprof', suffix='~', delete=False) as f: + f.write(policy) + os.rename(f.name, out_fn) def gen_manifest(self, params): - '''Take params list and output a JSON file''' + """Take params list and output a JSON file""" d = dict() d['security'] = dict() d['security']['profiles'] = dict() @@ -744,11 +710,11 @@ class AppArmorEasyProfile: d['security']['profiles'][pkey]['policy_vendor'] = self.policy_vendor for key in params: - if key == 'profile_name' or \ - (key == 'binary' and not 'profile_name' in params): - continue # don't re-add the pkey + if (key == 'profile_name' + or (key == 'binary' and 'profile_name' not in params)): + continue # don't re-add the pkey elif key == 'binary' and not params[key]: - continue # binary can by None when specifying --profile-name + continue # binary can by None when specifying --profile-name elif key == 'template_var': d['security']['profiles'][pkey]['template_variables'] = dict() for tvar in params[key]: @@ -762,24 +728,28 @@ class AppArmorEasyProfile: d['security']['profiles'][pkey][key].sort() else: d['security']['profiles'][pkey][key] = params[key] - json_str = json.dumps(d, - sort_keys=True, - indent=2, - separators=(',', ': ') - ) + json_str = json.dumps( + d, + sort_keys=True, + indent=2, + separators=(',', ': ') + ) return json_str + def print_basefilenames(files): for i in files: sys.stdout.write("%s\n" % (os.path.basename(i))) + def print_files(files): for i in files: with open(i) as f: sys.stdout.write(f.read()+"\n") + def check_manifest_conflict_args(option, opt_str, value, parser): - '''Check for -m/--manifest with conflicting args''' + """Check for -m/--manifest with conflicting args""" conflict_args = ['abstractions', 'read_path', 'write_path', @@ -796,26 +766,29 @@ def check_manifest_conflict_args(option, opt_str, value, parser): 'template_var'] for conflict in conflict_args: if getattr(parser.values, conflict, False): - raise optparse.OptionValueError("can't use --%s with --manifest " \ - "argument" % conflict) + raise optparse.OptionValueError( + "can't use --%s with --manifest argument" % conflict) setattr(parser.values, option.dest, value) + def check_for_manifest_arg(option, opt_str, value, parser): - '''Check for -m/--manifest with conflicting args''' + """Check for -m/--manifest with conflicting args""" if parser.values.manifest: - raise optparse.OptionValueError("can't use --%s with --manifest " \ - "argument" % opt_str.lstrip('-')) + raise optparse.OptionValueError( + "can't use --%s with --manifest argument" % opt_str.lstrip('-')) setattr(parser.values, option.dest, value) + def check_for_manifest_arg_append(option, opt_str, value, parser): - '''Check for -m/--manifest with conflicting args (with append)''' + """Check for -m/--manifest with conflicting args (with append)""" if parser.values.manifest: - raise optparse.OptionValueError("can't use --%s with --manifest " \ - "argument" % opt_str.lstrip('-')) + raise optparse.OptionValueError( + "can't use --%s with --manifest argument" % opt_str.lstrip('-')) parser.values.ensure_value(option.dest, []).append(value) + def add_parser_policy_args(parser): - '''Add parser arguments''' + """Add parser arguments""" parser.add_option("--parser", dest="parser_path", help="The path to the profile parser used for verification", @@ -898,11 +871,12 @@ def add_parser_policy_args(parser): help="AppArmor profile name", metavar="PROFILENAME") + def parse_args(args=None, parser=None): - '''Parse arguments''' + """Parse arguments""" global DEBUGGING - if parser == None: + if parser is None: parser = optparse.OptionParser() parser.add_option("-c", "--config-file", @@ -994,7 +968,6 @@ def parse_args(args=None, parser=None): dest="verify_manifest", help="Verify JSON manifest file") - # add policy args now add_parser_policy_args(parser) @@ -1004,8 +977,9 @@ def parse_args(args=None, parser=None): DEBUGGING = True return (my_opt, my_args) + def gen_policy_params(binary, opt): - '''Generate parameters for gen_policy''' + """Generate parameters for gen_policy""" params = dict(binary=binary) if not binary and not opt.profile_name: @@ -1022,7 +996,7 @@ def gen_policy_params(binary, opt): elif binary: params['name'] = os.path.basename(binary) - if opt.template_var: # What about specified multiple times? + if opt.template_var: # What about specified multiple times? params['template_var'] = opt.template_var if opt.abstractions: params['abstractions'] = opt.abstractions @@ -1045,9 +1019,10 @@ def gen_policy_params(binary, opt): return params + def parse_manifest(manifest, opt_orig): - '''Take a JSON manifest as a string and updates options, returning an - updated binary. Note that a JSON file may contain multiple profiles.''' + """Take a JSON manifest as a string and updates options, returning an + updated binary. Note that a JSON file may contain multiple profiles.""" try: m = json.loads(manifest) @@ -1064,21 +1039,22 @@ def parse_manifest(manifest, opt_orig): table = top_table['profiles'] # generally mirrors what is settable in gen_policy_params() - valid_keys = ['abstractions', - 'author', - 'binary', - 'comment', - 'copyright', - 'name', - 'policy_groups', - 'policy_version', - 'policy_vendor', - 'profile_name', - 'read_path', - 'template', - 'template_variables', - 'write_path', - ] + valid_keys = [ + 'abstractions', + 'author', + 'binary', + 'comment', + 'copyright', + 'name', + 'policy_groups', + 'policy_version', + 'policy_vendor', + 'profile_name', + 'read_path', + 'template', + 'template_variables', + 'write_path', + ] profiles = [] @@ -1113,7 +1089,7 @@ def parse_manifest(manifest, opt_orig): raise AppArmorException("Invalid key '%s'" % key) if key == 'binary': - continue # handled above + continue # handled above elif key == 'abstractions' or key == 'policy_groups': setattr(opt, key, ",".join(table[profile_name][key])) elif key == "template_variables": @@ -1126,32 +1102,30 @@ def parse_manifest(manifest, opt_orig): if hasattr(opt, key): setattr(opt, key, table[profile_name][key]) - profiles.append( (binary, opt) ) + profiles.append((binary, opt)) return profiles def verify_options(opt, strict=False): - '''Make sure our options are valid''' + """Make sure our options are valid""" if hasattr(opt, 'binary') and opt.binary and not valid_path(opt.binary): raise AppArmorException("Invalid binary '%s'" % opt.binary) - if hasattr(opt, 'profile_name') and opt.profile_name != None and \ - not valid_profile_name(opt.profile_name): + if (hasattr(opt, 'profile_name') and opt.profile_name is not None + and not valid_profile_name(opt.profile_name)): raise AppArmorException("Invalid profile name '%s'" % opt.profile_name) - if hasattr(opt, 'binary') and opt.binary and \ - hasattr(opt, 'profile_name') and opt.profile_name != None and \ - opt.profile_name.startswith('/'): + if (hasattr(opt, 'binary') and opt.binary + and hasattr(opt, 'profile_name') and opt.profile_name is not None + and opt.profile_name.startswith('/')): raise AppArmorException("Profile name should not specify path with binary") - if hasattr(opt, 'policy_vendor') and opt.policy_vendor and \ - not valid_policy_vendor(opt.policy_vendor): - raise AppArmorException("Invalid policy vendor '%s'" % \ - opt.policy_vendor) - if hasattr(opt, 'policy_version') and opt.policy_version and \ - not valid_policy_version(opt.policy_version): - raise AppArmorException("Invalid policy version '%s'" % \ - opt.policy_version) - if hasattr(opt, 'template') and opt.template and \ - not valid_template_name(opt.template, strict): + if (hasattr(opt, 'policy_vendor') and opt.policy_vendor + and not valid_policy_vendor(opt.policy_vendor)): + raise AppArmorException("Invalid policy vendor '%s'" % opt.policy_vendor) + if (hasattr(opt, 'policy_version') and opt.policy_version + and not valid_policy_version(opt.policy_version)): + raise AppArmorException("Invalid policy version '%s'" % opt.policy_version) + if (hasattr(opt, 'template') and opt.template + and not valid_template_name(opt.template, strict)): raise AppArmorException("Invalid template '%s'" % opt.template) if hasattr(opt, 'template_var') and opt.template_var: for i in opt.template_var: @@ -1176,7 +1150,7 @@ def verify_options(opt, strict=False): def verify_manifest(params, args=None): - '''Verify manifest for safe and unsafe options''' + """Verify manifest for safe and unsafe options""" err_str = "" (opt, args) = parse_args(args) fake_easyp = AppArmorEasyProfile(None, opt) @@ -1188,15 +1162,15 @@ def verify_manifest(params, args=None): if k in unsafe_keys: err_str += "\nfound %s key" % k elif k == 'profile_name': - if params['profile_name'].startswith('/') or \ - '*' in params['profile_name']: + if (params['profile_name'].startswith('/') + or '*' in params['profile_name']): err_str += "\nprofile_name '%s'" % params['profile_name'] elif k == 'abstractions': for a in params['abstractions'].split(','): - if not a in safe_abstractions: + if a not in safe_abstractions: err_str += "\nfound '%s' abstraction" % a elif k == "template_var": - pat = re.compile(r'[*/\{\}\[\]]') + pat = re.compile(r'[*/{}\[\]]') for tv in params['template_var']: if not fake_easyp.gen_variable_declaration(tv): err_str += "\n%s" % tv @@ -1211,4 +1185,3 @@ def verify_manifest(params, args=None): return False return True - diff --git a/utils/apparmor/fail.py b/utils/apparmor/fail.py index 6a0c57e8c..ece6efc43 100644 --- a/utils/apparmor/fail.py +++ b/utils/apparmor/fail.py @@ -8,50 +8,46 @@ # # ------------------------------------------------------------------ -from __future__ import print_function # needed in py2 for print('...', file=sys.stderr) - import cgitb -import os import sys -import tempfile import traceback +from tempfile import NamedTemporaryFile from apparmor.common import error + # # Exception handling # def handle_exception(*exc_info): - '''Used as exception handler in the aa-* tools. + """Used as exception handler in the aa-* tools. For AppArmorException (used for profile syntax errors etc.), print only the exceptions value because a backtrace is superfluous and would confuse users. For other exceptions, print backtrace and save detailed information in a file in /tmp/ (including variable content etc.) to make debugging easier. - ''' + """ (ex_cls, ex, tb) = exc_info - if ex_cls.__name__ == 'AppArmorException': # I didn't find a way to get this working with isinstance() :-/ + if ex_cls.__name__ == 'AppArmorException': # I didn't find a way to get this working with isinstance() :-/ print('', file=sys.stderr) error(ex.value) else: - (fd, path) = tempfile.mkstemp(prefix='apparmor-bugreport-', suffix='.txt') - file = os.fdopen(fd, 'w') - #file = open_file_write(path) # writes everything converted to utf8 - not sure if we want this... + with NamedTemporaryFile('w', prefix='apparmor-bugreport-', suffix='.txt', delete=False) as file: + cgitb_hook = cgitb.Hook(display=1, file=file, format='text', context=10) + cgitb_hook.handle(exc_info) - cgitb_hook = cgitb.Hook(display=1, file=file, format='text', context=10) - cgitb_hook.handle(exc_info) - - file.write('Please consider reporting a bug at https://gitlab.com/apparmor/apparmor/-/issues\n') - file.write('and attach this file.\n') + file.write('Please consider reporting a bug at https://gitlab.com/apparmor/apparmor/-/issues\n') + file.write('and attach this file.\n') print(''.join(traceback.format_exception(*exc_info)), file=sys.stderr) print('', file=sys.stderr) - print('An unexpected error occoured!', file=sys.stderr) + print('An unexpected error occurred!', file=sys.stderr) print('', file=sys.stderr) - print('For details, see %s' % path, file=sys.stderr) + print('For details, see %s' % file.name, file=sys.stderr) print('Please consider reporting a bug at https://gitlab.com/apparmor/apparmor/-/issues', file=sys.stderr) print('and attach this file.', file=sys.stderr) + def enable_aa_exception_handler(): - '''Setup handle_exception() as exception handler''' + """Setup handle_exception() as exception handler""" sys.excepthook = handle_exception diff --git a/utils/apparmor/logparser.py b/utils/apparmor/logparser.py index a64b64b10..d1d21023f 100644 --- a/utils/apparmor/logparser.py +++ b/utils/apparmor/logparser.py @@ -16,13 +16,14 @@ import ctypes import re import sys import time -import LibAppArmor -from apparmor.common import AppArmorException, AppArmorBug, hasher, open_file_read, split_name, DebugLogger -# setup module translations +import LibAppArmor +from apparmor.common import AppArmorBug, AppArmorException, DebugLogger, hasher, open_file_read, split_name from apparmor.translations import init_translation + _ = init_translation() + class ReadLog: # used to pre-filter log lines so that we hand over only relevant lines to LibAppArmor parsing @@ -32,7 +33,7 @@ class ReadLog: self.filename = filename self.profile_dir = profile_dir self.active_profiles = active_profiles - self.hashlog = { 'PERMITTING': {}, 'REJECTING': {}, 'AUDIT': {} } # structure inside {}: {'profilename': init_hashlog(aamode, profilename), 'profilename2': init_hashlog(...), ...} + self.hashlog = {'PERMITTING': {}, 'REJECTING': {}} # structure inside {}: {'profilename': init_hashlog(aamode, profilename), 'profilename2': init_hashlog(...), ...} self.debug_logger = DebugLogger('ReadLog') self.LOG = None self.logmark = '' @@ -40,7 +41,7 @@ class ReadLog: self.next_log_entry = None def init_hashlog(self, aamode, profile): - ''' initialize self.hashlog[aamode][profile] for all rule types''' + """initialize self.hashlog[aamode][profile] for all rule types""" if profile in self.hashlog[aamode].keys(): return # already initialized, don't overwrite existing data @@ -79,9 +80,6 @@ class ReadLog: """Parse the event from log into key value pairs""" msg = msg.strip() self.debug_logger.info('parse_event: %s' % msg) - if sys.version_info < (3, 0): - # parse_record fails with u'foo' style strings hence typecasting to string - msg = str(msg) event = LibAppArmor.parse_record(msg) ev = dict() ev['resource'] = event.info @@ -104,6 +102,7 @@ class ReadLog: ev['family'] = event.net_family ev['protocol'] = event.net_protocol ev['sock_type'] = event.net_sock_type + ev['class'] = event._class if event.ouid != ctypes.c_ulong(-1).value: # ULONG_MAX ev['fsuid'] = event.fsuid @@ -157,12 +156,12 @@ class ReadLog: if aamode == 'UNKNOWN': raise AppArmorBug('aamode is UNKNOWN - %s' % e['type']) # should never happen - if aamode in ['AUDIT', 'STATUS', 'ERROR']: - return None + if aamode in ('AUDIT', 'STATUS', 'ERROR'): + return # Skip if AUDIT event was issued due to a change_hat in unconfined mode if not e.get('profile', False): - return None + return full_profile = e['profile'] # full, nested profile name self.init_hashlog(aamode, full_profile) @@ -174,7 +173,7 @@ class ReadLog: profile, hat = split_name(e['profile']) if profile != 'null-complain-profile' and not self.profile_exists(profile): - return None + return if e['operation'] == 'exec': if not e['name']: raise AppArmorException('exec without executed binary') @@ -183,7 +182,7 @@ class ReadLog: e['name2'] = '' # exec events in enforce mode don't have target=... self.hashlog[aamode][full_profile]['exec'][e['name']][e['name2']] = True - return None + return elif self.op_type(e) == 'file': # Map c (create) and d (delete) to w (logging is more detailed than the profile language) @@ -208,51 +207,54 @@ class ReadLog: # in current log style, owner permissions are indicated by a match of fsuid and ouid owner = True + if 'x' in dmask and dmask != 'x': + dmask = dmask.replace('x', '') # if dmask contains x and another mode, drop x here - we should see a separate exec event + for perm in dmask: if perm in 'mrwalk': # intentionally not allowing 'x' here self.hashlog[aamode][full_profile]['path'][e['name']][owner][perm] = True else: raise AppArmorException(_('Log contains unknown mode %s') % dmask) - return None + return elif e['operation'] == 'capable': self.hashlog[aamode][full_profile]['capability'][e['name']] = True - return None + return elif self.op_type(e) == 'net': self.hashlog[aamode][full_profile]['network'][e['family']][e['sock_type']][e['protocol']] = True - return None + return elif e['operation'] == 'change_hat': if e['error_code'] == 1 and e['info'] == 'unconfined can not change_hat': - return None + return self.hashlog[aamode][full_profile]['change_hat'][e['name2']] = True - return None + return elif e['operation'] == 'change_profile': self.hashlog[aamode][full_profile]['change_profile'][e['name2']] = True - return None + return elif e['operation'] == 'ptrace': if not e['peer']: self.debug_logger.debug('ignored garbage ptrace event with empty peer') - return None + return if not e['denied_mask']: self.debug_logger.debug('ignored garbage ptrace event with empty denied_mask') - return None + return self.hashlog[aamode][full_profile]['ptrace'][e['peer']][e['denied_mask']] = True - return None + return elif e['operation'] == 'signal': - self.hashlog[aamode][full_profile]['signal'][e['peer']][e['denied_mask']][e['signal']]= True - return None + self.hashlog[aamode][full_profile]['signal'][e['peer']][e['denied_mask']][e['signal']] = True + return - elif e['operation'].startswith('dbus_'): + elif e['operation'] and e['operation'].startswith('dbus_'): self.hashlog[aamode][full_profile]['dbus'][e['denied_mask']][e['bus']][e['path']][e['name']][e['interface']][e['member']][e['peer_profile']] = True - return None + return else: self.debug_logger.debug('UNHANDLED: %s' % e) @@ -266,32 +268,31 @@ class ReadLog: self.LOG = open_file_read(self.filename) except IOError: raise AppArmorException('Can not read AppArmor logfile: ' + self.filename) - line = True - while line: - line = self.get_next_log_entry() - if not line: - break - line = line.strip() - self.debug_logger.debug('read_log: %s' % line) - if self.logmark in line: - seenmark = True - - self.debug_logger.debug('read_log: seenmark = %s' % seenmark) - if not seenmark: - continue - - event = self.parse_event(line) - if event: - try: - self.parse_event_for_tree(event) - - except AppArmorException as e: - ex_msg = ('%(msg)s\n\nThis error was caused by the log line:\n%(logline)s' % - {'msg': e.value, 'logline': line}) - # when py3 only: Drop the original AppArmorException by passing None as the parent exception - raise AppArmorBug(ex_msg) # py3-only: from None - - self.LOG.close() + with self.LOG: + line = True + while line: + line = self.get_next_log_entry() + if not line: + break + line = line.strip() + self.debug_logger.debug('read_log: %s' % line) + if self.logmark in line: + seenmark = True + + self.debug_logger.debug('read_log: seenmark = %s' % seenmark) + if not seenmark: + continue + + event = self.parse_event(line) + if event: + try: + self.parse_event_for_tree(event) + + except AppArmorException as e: + ex_msg = ('%(msg)s\n\nThis error was caused by the log line:\n%(logline)s' + % {'msg': e.value, 'logline': line}) + raise AppArmorBug(ex_msg) from None + self.logmark = '' return self.hashlog @@ -300,43 +301,45 @@ class ReadLog: # (used by op_type() which checks some event details to decide) OP_TYPE_FILE_OR_NET = { # Note: op_type() also uses some startswith() checks which are not listed here! - 'create', - 'post_create', - 'bind', - 'connect', - 'listen', - 'accept', - 'sendmsg', - 'recvmsg', - 'getsockname', - 'getpeername', - 'getsockopt', - 'setsockopt', - 'socket_create', - 'sock_shutdown', - 'open', - 'truncate', - 'mkdir', - 'mknod', - 'chmod', - 'chown', - 'rename_src', - 'rename_dest', - 'unlink', - 'rmdir', - 'symlink', - 'symlink_create', - 'link', - 'sysctl', - 'getattr', - 'setattr', - 'xattr', + 'create', + 'post_create', + 'bind', + 'connect', + 'listen', + 'accept', + 'sendmsg', + 'recvmsg', + 'getsockname', + 'getpeername', + 'getsockopt', + 'setsockopt', + 'socket_create', + 'sock_shutdown', + 'open', + 'truncate', + 'mkdir', + 'mknod', + 'chmod', + 'chown', + 'rename_src', + 'rename_dest', + 'unlink', + 'rmdir', + 'symlink', + 'symlink_create', + 'link', + 'sysctl', + 'getattr', + 'setattr', + 'xattr', } def op_type(self, event): - """Returns the operation type if known, unkown otherwise""" + """Returns the operation type if known, unknown otherwise""" - if ( event['operation'].startswith('file_') or event['operation'].startswith('inode_') or event['operation'] in self.OP_TYPE_FILE_OR_NET ): + if event['operation'] and (event['operation'].startswith('file_') or + event['operation'].startswith('inode_') or + event['operation'] in self.OP_TYPE_FILE_OR_NET): # file or network event? if event['family'] and event['protocol'] and event['sock_type']: # 'unix' events also use keywords like 'connect', but protocol is 0 and should therefore be filtered out diff --git a/utils/apparmor/notify.py b/utils/apparmor/notify.py index 3043b4737..84aaaf3e1 100644 --- a/utils/apparmor/notify.py +++ b/utils/apparmor/notify.py @@ -22,7 +22,7 @@ debug_logger = DebugLogger('apparmor.notify') def sane_timestamp(timestamp): - ''' Check if the given timestamp is in a date range that makes sense for a wtmp file ''' + """Check if the given timestamp is in a date range that makes sense for a wtmp file""" if timestamp < 946681200: # 2000-01-01 return False @@ -31,8 +31,9 @@ def sane_timestamp(timestamp): return True + def get_last_login_timestamp(username, filename='/var/log/wtmp'): - '''Directly read wtmp and get last login for user as epoch timestamp''' + """Directly read wtmp and get last login for user as epoch timestamp""" timestamp = 0 last_login = 0 @@ -48,10 +49,11 @@ def get_last_login_timestamp(username, filename='/var/log/wtmp'): # detect architecture based on utmp format differences wtmp_file.seek(340) # first possible timestamp position - timestamp_x86_64 = struct.unpack("<L", wtmp_file.read(4))[0] - timestamp_aarch64 = struct.unpack("<L", wtmp_file.read(4))[0] - timestamp_s390x = struct.unpack(">L", wtmp_file.read(4))[0] - debug_logger.debug('WTMP timestamps: x86_64 %s, aarch64 %s, s390x %s' % (timestamp_x86_64, timestamp_aarch64, timestamp_s390x)) + timestamp_x86_64 = struct.unpack("<L", wtmp_file.read(4))[0] # noqa: E221 + timestamp_aarch64 = struct.unpack("<L", wtmp_file.read(4))[0] + timestamp_s390x = struct.unpack(">L", wtmp_file.read(4))[0] # noqa: E221 + debug_logger.debug('WTMP timestamps: x86_64 %s, aarch64 %s, s390x %s' + % (timestamp_x86_64, timestamp_aarch64, timestamp_s390x)) if sane_timestamp(timestamp_x86_64): endianness = '<' # little endian @@ -66,7 +68,9 @@ def get_last_login_timestamp(username, filename='/var/log/wtmp'): extra_offset_before = 8 extra_offset_after = 8 else: - raise AppArmorBug('Your /var/log/wtmp is broken or has an unknown format. Please open a bugreport with /var/log/wtmp and the output of "last" attached!') + raise AppArmorBug( + 'Your /var/log/wtmp is broken or has an unknown format. ' + 'Please open a bugreport with /var/log/wtmp and the output of "last" attached!') while offset < wtmp_filesize: wtmp_file.seek(offset) diff --git a/utils/apparmor/profile_list.py b/utils/apparmor/profile_list.py index e18ef048c..cdeed5fa4 100644 --- a/utils/apparmor/profile_list.py +++ b/utils/apparmor/profile_list.py @@ -14,47 +14,57 @@ from apparmor.aare import AARE from apparmor.common import AppArmorBug, AppArmorException -from apparmor.rule.alias import AliasRule, AliasRuleset +from apparmor.profile_storage import ProfileStorage from apparmor.rule.abi import AbiRule, AbiRuleset +from apparmor.rule.alias import AliasRule, AliasRuleset +from apparmor.rule.boolean import BooleanRule, BooleanRuleset from apparmor.rule.include import IncludeRule, IncludeRuleset from apparmor.rule.variable import VariableRule, VariableRuleset - -# setup module translations from apparmor.translations import init_translation + _ = init_translation() +preamble_ruletypes = { + 'abi': {'rule': AbiRule, 'ruleset': AbiRuleset}, + 'alias': {'rule': AliasRule, 'ruleset': AliasRuleset}, + 'inc_ie': {'rule': IncludeRule, 'ruleset': IncludeRuleset}, + 'variable': {'rule': VariableRule, 'ruleset': VariableRuleset}, + 'boolean': {'rule': BooleanRule, 'ruleset': BooleanRuleset}, +} +header_rule_write_order = ('abi', 'alias', 'inc_ie', 'variable', 'boolean') # TODO: Dicts are ordered in Python 3.7+; use above dict's keys instead + class ProfileList: - ''' Stores the preamble section and the list of profile(s) (both name and - attachment) that live in profile files. + """Stores the preamble section and the list of profile(s) (both name and + attachment) that live in profile files. - Also allows "reverse" lookups to find out in which file a profile - lives. - ''' + Also allows "reverse" lookups to find out in which file a profile + lives. + """ def __init__(self): self.profile_names = {} # profile name -> filename - self.attachments = {} # attachment -> filename - self.attachments_AARE = {} # AARE(attachment) -> filename + self.attachments = {} # attachment -> {'f': filename, 'p': profile} + self.attachments_AARE = {} # attachment -> AARE(attachment) self.files = {} # filename -> content - see init_file() + self.profiles = {} # profile_name -> ProfileStorage def __repr__(self): - return('\n<ProfileList>\n%s\n</ProfileList>\n' % '\n'.join(self.files)) + return ('\n<ProfileList>\n%s\n</ProfileList>\n' % '\n'.join(self.files)) def init_file(self, filename): if self.files.get(filename): return # don't re-initialize / overwrite existing data self.files[filename] = { - 'abi': AbiRuleset(), - 'alias': AliasRuleset(), - 'inc_ie': IncludeRuleset(), - 'variable': VariableRuleset(), 'profiles': [], } - def add_profile(self, filename, profile_name, attachment): - ''' Add the given profile and attachment to the list ''' + for rule in preamble_ruletypes: + self.files[filename][rule] = preamble_ruletypes[rule]['ruleset']() + + def add_profile(self, filename, profile_name, attachment, prof_storage=None): + """Add the given profile and attachment to the list""" if not filename: raise AppArmorBug('Empty filename given to ProfileList') @@ -62,28 +72,44 @@ class ProfileList: if not profile_name and not attachment: raise AppArmorBug('Neither profile name or attachment given') + if type(prof_storage) is not ProfileStorage and prof_storage is not None: + raise AppArmorBug('Invalid profile type: %s' % type(prof_storage)) + 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]})) + 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]})) + 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[attachment] = {'f': filename, 'p': profile_name or attachment} # if a profile doesn't have a name, the attachment is stored as profile name self.attachments_AARE[attachment] = AARE(attachment, True) self.init_file(filename) if profile_name: self.files[filename]['profiles'].append(profile_name) + self.profiles[profile_name] = prof_storage else: self.files[filename]['profiles'].append(attachment) + self.profiles[attachment] = prof_storage + + def add_rule(self, filename, ruletype, rule): + """Store the given rule for the given profile filename preamble""" + + self.init_file(filename) + + self.files[filename][ruletype].add(rule) def add_abi(self, filename, abi_rule): - ''' Store the given abi rule for the given profile filename preamble ''' + """Store the given abi rule for the given profile filename preamble""" if type(abi_rule) is not AbiRule: raise AppArmorBug('Wrong type given to ProfileList: %s' % abi_rule) @@ -93,7 +119,7 @@ class ProfileList: self.files[filename]['abi'].add(abi_rule) def add_alias(self, filename, alias_rule): - ''' Store the given alias rule for the given profile filename preamble ''' + """Store the given alias rule for the given profile filename preamble""" if type(alias_rule) is not AliasRule: raise AppArmorBug('Wrong type given to ProfileList: %s' % alias_rule) @@ -103,7 +129,7 @@ class ProfileList: self.files[filename]['alias'].add(alias_rule) def add_inc_ie(self, filename, inc_rule): - ''' Store the given include / include if exists rule for the given profile filename preamble ''' + """Store the given include / include if exists rule for the given profile filename preamble""" if type(inc_rule) is not IncludeRule: raise AppArmorBug('Wrong type given to ProfileList: %s' % inc_rule) @@ -112,7 +138,7 @@ class ProfileList: self.files[filename]['inc_ie'].add(inc_rule) def add_variable(self, filename, var_rule): - ''' Store the given variable rule for the given profile filename preamble ''' + """Store the given variable rule for the given profile filename preamble""" if type(var_rule) is not VariableRule: raise AppArmorBug('Wrong type given to ProfileList: %s' % var_rule) @@ -120,73 +146,96 @@ class ProfileList: self.files[filename]['variable'].add(var_rule) + def add_boolean(self, filename, bool_rule): + """Store the given boolean variable rule for the given profile filename preamble""" + if type(bool_rule) is not BooleanRule: + raise AppArmorBug('Wrong type given to ProfileList: %s' % bool_rule) + + self.init_file(filename) + + self.files[filename]['boolean'].add(bool_rule) + def delete_preamble_duplicates(self, filename): - ''' Delete duplicates in the preamble of the given profile file ''' + """Delete duplicates in the preamble of the given profile file""" if not self.files.get(filename): raise AppArmorBug('%s not listed in ProfileList files' % filename) deleted = 0 - for r_type in ['abi', 'alias', 'inc_ie', 'variable']: # TODO: don't hardcode + for r_type in preamble_ruletypes: deleted += self.files[filename][r_type].delete_duplicates(None) # None means not to check includes -- TODO check if this makes sense for all preamble rule types return deleted + def get_profile_and_childs(self, profile_name): + found = {} + for prof in self.profiles: + if prof == profile_name or prof.startswith('%s//' % profile_name): + found[prof] = self.profiles[prof] + + return found + def get_raw(self, filename, depth=0): - ''' Get the preamble for the given profile filename (in original formatting) ''' + """Get the preamble for the given profile filename (in original formatting)""" if not self.files.get(filename): raise AppArmorBug('%s not listed in ProfileList files' % filename) data = [] - data += self.files[filename]['abi'].get_raw(depth) - data += self.files[filename]['alias'].get_raw(depth) - data += self.files[filename]['inc_ie'].get_raw(depth) - data += self.files[filename]['variable'].get_raw(depth) + for rule_type in header_rule_write_order: + data.extend(self.files[filename][rule_type].get_raw(depth)) return data def get_clean(self, filename, depth=0): - ''' Get the preamble for the given profile filename (in clean formatting) ''' + """Get the preamble for the given profile filename (in clean formatting)""" if not self.files.get(filename): raise AppArmorBug('%s not listed in ProfileList files' % filename) data = [] - data += self.files[filename]['abi'].get_clean_unsorted(depth) - data += self.files[filename]['alias'].get_clean_unsorted(depth) - data += self.files[filename]['inc_ie'].get_clean_unsorted(depth) - data += self.files[filename]['variable'].get_clean_unsorted(depth) + for rule_type in header_rule_write_order: + data.extend(self.files[filename][rule_type].get_clean_unsorted(depth)) return data def filename_from_profile_name(self, name): - ''' Return profile filename for the given profile name, or None ''' + """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 ''' + """Return profile filename for the given attachment/executable path, or None""" + return self.thing_from_attachment(attachment, 'f') + + def profile_from_attachment(self, attachment): + """Return profile filename for the given attachment/executable path, or None""" + return self.thing_from_attachment(attachment, 'p') + + def thing_from_attachment(self, attachment, thing): + """Return thing for the given attachment/executable path, or None. - if not attachment.startswith( ('/', '@', '{') ): + thing can be 'f' for filename or 'p' for profile name""" + + 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] + return self.attachments[attachment][thing] # 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 self.attachments[path][thing] # XXX this returns the first match, not necessarily the best one return None # nothing found def get_all_merged_variables(self, filename, all_incfiles): - ''' Get merged variables of a file and its includes + """Get merged variables of a file and its includes - Note that this function is more forgiving than apparmor_parser. - It detects variable redefinitions and adding values to non-existing variables. - However, it doesn't honor the order - so adding to a variable first and defining - it later won't trigger an error. - ''' + Note that this function is more forgiving than apparmor_parser. + It detects variable redefinitions and adding values to non-existing variables. + However, it doesn't honor the order - so adding to a variable first and defining + it later won't trigger an error. + """ if not self.files.get(filename): raise AppArmorBug('%s not listed in ProfileList files' % filename) @@ -207,15 +256,13 @@ class ProfileList: inc_add[filename] = mainfile_variables['+='] # variable additions from main file for incname in all_incfiles: - if not self.files.get(incname): - continue # tunables/* only end up in self.files if they contain variable or alias definitions - inc_vars = self.files[incname]['variable'].get_merged_variables() for var in inc_vars['=']: if merged_variables.get(var): - raise AppArmorException('While parsing %(profile)s: Conflicting variable definitions for variable %(var)s found in %(file1)s and %(file2)s.' % { - 'var': var, 'profile': filename, 'file1': set_in[var], 'file2': incname}) + raise AppArmorException( + 'While parsing %(profile)s: Conflicting variable definitions for variable %(var)s found in %(file1)s and %(file2)s.' + % {'var': var, 'profile': filename, 'file1': set_in[var], 'file2': incname}) else: merged_variables[var] = inc_vars['='][var] set_in[var] = incname @@ -230,13 +277,14 @@ class ProfileList: if merged_variables.get(var): merged_variables[var] |= inc_add[incname][var] else: - raise AppArmorException('While parsing %(profile)s: Variable %(var)s was not previously declared, but is being assigned additional value in file %(file)s.' % { - 'var': var, 'profile': filename, 'file': incname}) + raise AppArmorException( + 'While parsing %(profile)s: Variable %(var)s was not previously declared, but is being assigned additional value in file %(file)s.' + % {'var': var, 'profile': filename, 'file': incname}) return merged_variables def profiles_in_file(self, filename): - ''' Return list of profiles in the given file ''' + """Return list of profiles in the given file""" if not self.files.get(filename): raise AppArmorBug('%s not listed in ProfileList files' % filename) diff --git a/utils/apparmor/profile_storage.py b/utils/apparmor/profile_storage.py index 19e62383f..bd8040dfe 100644 --- a/utils/apparmor/profile_storage.py +++ b/utils/apparmor/profile_storage.py @@ -1,6 +1,6 @@ # ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.com> -# Copyright (C) 2014-2017 Christian Boltz <apparmor@cboltz.de> +# Copyright (C) 2014-2021 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 @@ -14,43 +14,42 @@ # ---------------------------------------------------------------------- -from apparmor.common import AppArmorBug, type_is_str - -from apparmor.rule.abi import AbiRuleset -from apparmor.rule.capability import CapabilityRuleset -from apparmor.rule.change_profile import ChangeProfileRuleset -from apparmor.rule.dbus import DbusRuleset -from apparmor.rule.file import FileRuleset -from apparmor.rule.include import IncludeRuleset -from apparmor.rule.network import NetworkRuleset -from apparmor.rule.ptrace import PtraceRuleset -from apparmor.rule.rlimit import RlimitRuleset -from apparmor.rule.signal import SignalRuleset - +from apparmor.common import AppArmorBug, AppArmorException +from apparmor.regex import parse_profile_start_line from apparmor.rule import quote_if_needed - -# setup module translations +from apparmor.rule.abi import AbiRule, AbiRuleset +from apparmor.rule.capability import CapabilityRule, CapabilityRuleset +from apparmor.rule.change_profile import ChangeProfileRule, ChangeProfileRuleset +from apparmor.rule.dbus import DbusRule, DbusRuleset +from apparmor.rule.file import FileRule, FileRuleset +from apparmor.rule.include import IncludeRule, IncludeRuleset +from apparmor.rule.network import NetworkRule, NetworkRuleset +from apparmor.rule.ptrace import PtraceRule, PtraceRuleset +from apparmor.rule.rlimit import RlimitRule, RlimitRuleset +from apparmor.rule.signal import SignalRule, SignalRuleset from apparmor.translations import init_translation + _ = init_translation() ruletypes = { - 'abi': {'ruleset': AbiRuleset}, - 'inc_ie': {'ruleset': IncludeRuleset}, - 'capability': {'ruleset': CapabilityRuleset}, - 'change_profile': {'ruleset': ChangeProfileRuleset}, - 'dbus': {'ruleset': DbusRuleset}, - 'file': {'ruleset': FileRuleset}, - 'network': {'ruleset': NetworkRuleset}, - 'ptrace': {'ruleset': PtraceRuleset}, - 'rlimit': {'ruleset': RlimitRuleset}, - 'signal': {'ruleset': SignalRuleset}, + 'abi': {'rule': AbiRule, 'ruleset': AbiRuleset}, + 'inc_ie': {'rule': IncludeRule, 'ruleset': IncludeRuleset}, + 'capability': {'rule': CapabilityRule, 'ruleset': CapabilityRuleset}, + 'change_profile': {'rule': ChangeProfileRule, 'ruleset': ChangeProfileRuleset}, + 'dbus': {'rule': DbusRule, 'ruleset': DbusRuleset}, + 'file': {'rule': FileRule, 'ruleset': FileRuleset}, + 'network': {'rule': NetworkRule, 'ruleset': NetworkRuleset}, + 'ptrace': {'rule': PtraceRule, 'ruleset': PtraceRuleset}, + 'rlimit': {'rule': RlimitRule, 'ruleset': RlimitRuleset}, + 'signal': {'rule': SignalRule, 'ruleset': SignalRuleset}, } + class ProfileStorage: - '''class to store the content (header, rules, comments) of a profilename + """class to store the content (header, rules, comments) of a profilename Acts like a dict(), but has some additional checks. - ''' + """ def __init__(self, profilename, hat, calledby): data = dict() @@ -61,28 +60,29 @@ class ProfileStorage: for rule in ruletypes: data[rule] = ruletypes[rule]['ruleset']() - data['filename'] = '' - data['logprof_suggest'] = '' # set in abstractions that should be suggested by aa-logprof - data['name'] = '' - data['attachment'] = '' - data['xattrs'] = '' - data['flags'] = '' - data['external'] = False - data['header_comment'] = '' # currently only set by change_profile_flags() - data['initial_comment'] = '' - data['profile_keyword'] = False # currently only set by change_profile_flags() - data['profile'] = False # profile or hat? + data['filename'] = '' + data['logprof_suggest'] = '' # set in abstractions that should be suggested by aa-logprof + data['name'] = '' + data['attachment'] = '' + data['xattrs'] = '' + data['flags'] = '' + data['external'] = False + data['header_comment'] = '' # comment in the profile/hat start line + data['initial_comment'] = '' + data['profile_keyword'] = False + data['is_hat'] = False # profile or hat? + data['hat_keyword'] = False # True for 'hat foo', False for '^foo' data['allow'] = dict() data['deny'] = dict() # mount, pivot_root, unix have a .get() fallback to list() - initialize them nevertheless - data['allow']['mount'] = list() - data['deny']['mount'] = list() - data['allow']['pivot_root'] = list() - data['deny']['pivot_root'] = list() - data['allow']['unix'] = list() - data['deny']['unix'] = list() + data['allow']['mount'] = [] + data['deny']['mount'] = [] + data['allow']['pivot_root'] = [] + data['deny']['pivot_root'] = [] + data['allow']['unix'] = [] + data['deny']['unix'] = [] self.data = data @@ -104,15 +104,15 @@ class ProfileStorage: raise AppArmorBug('Attempt to change type of "%s" from %s to %s, value %s' % (key, type(self.data[key]), type(value), value)) # allow writing str or None to some keys - elif key in ('xattrs', 'flags', 'filename'): - if type_is_str(value) or value is None: + elif key in ('flags', 'filename'): + if type(value) is str or value is None: self.data[key] = value else: raise AppArmorBug('Attempt to change type of "%s" from %s to %s, value %s' % (key, type(self.data[key]), type(value), value)) # allow writing str values - elif type_is_str(self.data[key]): - if type_is_str(value): + elif type(self.data[key]) is str: + if type(value) is str: self.data[key] = value else: raise AppArmorBug('Attempt to change type of "%s" from %s to %s, value %s' % (key, type(self.data[key]), type(value), value)) @@ -122,7 +122,7 @@ class ProfileStorage: raise AppArmorBug('Attempt to overwrite "%s" with %s, type %s' % (key, value, type(value))) def __repr__(self): - return('\n<ProfileStorage>\n%s\n</ProfileStorage>\n' % '\n'.join(self.get_rules_clean(1))) + return ('\n<ProfileStorage>\n%s\n</ProfileStorage>\n' % '\n'.join(self.get_rules_clean(1))) def get(self, key, fallback=None): if key in self.data: @@ -130,11 +130,45 @@ class ProfileStorage: else: raise AppArmorBug('attempt to read unknown key %s' % key) + def get_header(self, depth, name, embedded_hat): + pre = ' ' * int(depth * 2) + data = [] + unquoted_name = name + name = quote_if_needed(name) + + attachment = '' + if self.data['attachment']: + attachment = ' %s' % quote_if_needed(self.data['attachment']) + + comment = '' + if self.data['header_comment']: + comment = ' %s' % self.data['header_comment'] + + if self.data['is_hat']: + if self.data['hat_keyword']: + name = 'hat %s' % name + else: + name = '^%s' % name + elif (not embedded_hat and not unquoted_name.startswith('/')) or (embedded_hat and not unquoted_name.startswith('^')) or self.data['attachment'] or self.data['profile_keyword']: + name = 'profile %s%s' % (name, attachment) + + xattrs = '' + if self.data['xattrs']: + xattrs = ' xattrs=(%s)' % self.data['xattrs'] + + flags = '' + if self.data['flags']: + flags = ' flags=(%s)' % self.data['flags'] + + data.append('%s%s%s%s {%s' % (pre, name, xattrs, flags, comment)) + + return data + def get_rules_clean(self, depth): - '''return all clean rules of a profile (with default formatting, and leading whitespace as specified in the depth parameter) + """return all clean rules of a profile (with default formatting, and leading whitespace as specified in the depth parameter) Note that the profile header and the closing "}" are _not_ included. - ''' + """ # "old" write functions for rule types not implemented as *Rule class yet write_functions = { @@ -163,15 +197,66 @@ class ProfileStorage: for ruletype in write_order: if write_functions.get(ruletype): - data += write_functions[ruletype](self.data, depth) + data.extend(write_functions[ruletype](self.data, depth)) else: - data += self.data[ruletype].get_clean(depth) + data.extend(self.data[ruletype].get_clean(depth)) return data + @classmethod + def parse(cls, line, file, lineno, profile, hat): + """parse a profile start line (using parse_profile_startline()) and convert it to a ProfileStorage""" + + matches = parse_profile_start_line(line, file) + + if profile: # we are inside a profile, so we expect a child profile + if not matches['profile_keyword']: + raise AppArmorException( + _('%(profile)s profile in %(file)s contains syntax errors in line %(line)s: missing "profile" keyword.') + % {'profile': profile, 'file': file, 'line': lineno + 1}) + if hat is not None: + # nesting limit reached - a child profile can't contain another child profile + raise AppArmorException( + _('%(profile)s profile in %(file)s contains syntax errors in line %(line)s: a child profile inside another child profile is not allowed.') + % {'profile': profile, 'file': file, 'line': lineno + 1}) + + hat = matches['profile'] + pps_set_hat_external = False + + else: # stand-alone profile + profile = matches['profile'] + if len(profile.split('//')) > 2: + raise AppArmorException( + "Nested child profiles ('%(profile)s', found in %(file)s) are not supported by the AppArmor tools yet." + % {'profile': profile, 'file': file}) + elif len(profile.split('//')) == 2: + profile, hat = profile.split('//') + pps_set_hat_external = True + else: + hat = profile + pps_set_hat_external = False + + prof_storage = ProfileStorage(profile, hat, 'ProfileStorage.parse()') + + prof_storage['name'] = profile + prof_storage['filename'] = file + prof_storage['external'] = pps_set_hat_external + prof_storage['flags'] = matches['flags'] + prof_storage['header_comment'] = matches['comment'] or '' + prof_storage['is_hat'] = matches['is_hat'] + + if matches['is_hat']: + prof_storage['hat_keyword'] = matches['hat_keyword'] + else: + prof_storage['profile_keyword'] = matches['profile_keyword'] + prof_storage['attachment'] = matches['attachment'] or '' + prof_storage['xattrs'] = matches['xattrs'] or '' + + return (profile, hat, prof_storage) + def split_flags(flags): - '''split the flags given as string into a sorted, de-duplicated list''' + """split the flags given as string into a sorted, de-duplicated list""" if flags is None: flags = '' @@ -181,13 +266,14 @@ def split_flags(flags): # sort and remove duplicates return sorted(set(flags_list)) + def add_or_remove_flag(flags, flags_to_change, set_flag): - '''add (if set_flag == True) or remove the given flags_to_change to flags''' + """add (if set_flag is True) or remove the given flags_to_change to flags""" - if type_is_str(flags) or flags is None: + if type(flags) is str or flags is None: flags = split_flags(flags) - if type_is_str(flags_to_change) or flags_to_change is None: + if type(flags_to_change) is str or flags_to_change is None: flags_to_change = split_flags(flags_to_change) if set_flag: @@ -210,6 +296,7 @@ def var_transform(ref): data.append(quote_if_needed(value)) return ' '.join(data) + def write_mount_rules(prof_data, depth, allow): pre = ' ' * depth data = [] @@ -223,11 +310,13 @@ def write_mount_rules(prof_data, depth, allow): data.append('') return data + def write_mount(prof_data, depth): data = write_mount_rules(prof_data, depth, 'deny') - data += write_mount_rules(prof_data, depth, 'allow') + data.extend(write_mount_rules(prof_data, depth, 'allow')) return data + def write_pivot_root_rules(prof_data, depth, allow): pre = ' ' * depth data = [] @@ -241,16 +330,19 @@ def write_pivot_root_rules(prof_data, depth, allow): data.append('') return data + def write_pivot_root(prof_data, depth): data = write_pivot_root_rules(prof_data, depth, 'deny') - data += write_pivot_root_rules(prof_data, depth, 'allow') + data.extend(write_pivot_root_rules(prof_data, depth, 'allow')) return data + def write_unix(prof_data, depth): data = write_unix_rules(prof_data, depth, 'deny') - data += write_unix_rules(prof_data, depth, 'allow') + data.extend(write_unix_rules(prof_data, depth, 'allow')) return data + def write_unix_rules(prof_data, depth, allow): pre = ' ' * depth data = [] diff --git a/utils/apparmor/regex.py b/utils/apparmor/regex.py index 868e3cc14..216e41407 100644 --- a/utils/apparmor/regex.py +++ b/utils/apparmor/regex.py @@ -14,74 +14,75 @@ # ---------------------------------------------------------------------- import re -from apparmor.common import AppArmorBug, AppArmorException -# setup module translations +from apparmor.common import AppArmorBug, AppArmorException from apparmor.translations import init_translation + _ = init_translation() -## Profile parsing Regex -RE_AUDIT_DENY = '^\s*(?P<audit>audit\s+)?(?P<allow>allow\s+|deny\s+)?' # line start, optionally: leading whitespace, <audit> and <allow>/deny -RE_EOL = '\s*(?P<comment>#.*?)?\s*$' # optional whitespace, optional <comment>, optional whitespace, end of the line -RE_COMMA_EOL = '\s*,' + RE_EOL # optional whitespace, comma + RE_EOL - -RE_PROFILE_NAME = '(?P<%s>(\S+|"[^"]+"))' # string without spaces, or quoted string. %s is the match group name -RE_PATH = '/\S*|"/[^"]*"' # filename (starting with '/') without spaces, or quoted filename. -RE_PROFILE_PATH = '(?P<%s>(' + RE_PATH + '))' # quoted or unquoted filename. %s is the match group name -RE_PROFILE_PATH_OR_VAR = '(?P<%s>(' + RE_PATH + '|@{\S+}\S*|"@{\S+}[^"]*"))' # quoted or unquoted filename or variable. %s is the match group name -RE_SAFE_OR_UNSAFE = '(?P<execmode>(safe|unsafe))' -RE_XATTRS = '(\s+xattrs\s*=\s*\((?P<xattrs>([^)=]+(=[^)=]+)?\s?)+)\)\s*)?' -RE_FLAGS = '(\s+(flags\s*=\s*)?\((?P<flags>[^)]+)\))?' - -RE_PROFILE_END = re.compile('^\s*\}' + RE_EOL) -RE_PROFILE_CAP = re.compile(RE_AUDIT_DENY + 'capability(?P<capability>(\s+\S+)+)?' + RE_COMMA_EOL) -RE_PROFILE_ALIAS = re.compile('^\s*alias\s+(?P<orig_path>"??.+?"??)\s+->\s*(?P<target>"??.+?"??)' + RE_COMMA_EOL) -RE_PROFILE_RLIMIT = re.compile('^\s*set\s+rlimit\s+(?P<rlimit>[a-z]+)\s*<=\s*(?P<value>[^ ]+(\s+[a-zA-Z]+)?)' + RE_COMMA_EOL) -RE_PROFILE_BOOLEAN = re.compile('^\s*(\$\{?\w*\}?)\s*=\s*(true|false)\s*,?' + RE_EOL, flags=re.IGNORECASE) -RE_PROFILE_VARIABLE = re.compile('^\s*(?P<varname>@\{?\w+\}?)\s*(?P<mode>\+?=)\s*(?P<values>@*.+?)' + RE_EOL) -RE_PROFILE_CONDITIONAL = re.compile('^\s*if\s+(not\s+)?(\$\{?\w*\}?)\s*\{' + RE_EOL) -RE_PROFILE_CONDITIONAL_VARIABLE = re.compile('^\s*if\s+(not\s+)?defined\s+(@\{?\w+\}?)\s*\{\s*(#.*)?$') -RE_PROFILE_CONDITIONAL_BOOLEAN = re.compile('^\s*if\s+(not\s+)?defined\s+(\$\{?\w+\}?)\s*\{\s*(#.*)?$') -RE_PROFILE_NETWORK = re.compile(RE_AUDIT_DENY + 'network(?P<details>\s+.*)?' + RE_COMMA_EOL) -RE_PROFILE_CHANGE_HAT = re.compile('^\s*\^(\"??.+?\"??)' + RE_COMMA_EOL) -RE_PROFILE_HAT_DEF = re.compile('^(?P<leadingspace>\s*)(?P<hat_keyword>\^|hat\s+)(?P<hat>\"??[^)]+?\"??)' + RE_FLAGS + '\s*\{' + RE_EOL) -RE_PROFILE_DBUS = re.compile(RE_AUDIT_DENY + '(dbus\s*,|dbus(?P<details>\s+[^#]*)\s*,)' + RE_EOL) -RE_PROFILE_MOUNT = re.compile(RE_AUDIT_DENY + '((mount|remount|umount|unmount)(\s+[^#]*)?\s*,)' + RE_EOL) -RE_PROFILE_SIGNAL = re.compile(RE_AUDIT_DENY + '(signal\s*,|signal(?P<details>\s+[^#]*)\s*,)' + RE_EOL) -RE_PROFILE_PTRACE = re.compile(RE_AUDIT_DENY + '(ptrace\s*,|ptrace(?P<details>\s+[^#]*)\s*,)' + RE_EOL) -RE_PROFILE_PIVOT_ROOT = re.compile(RE_AUDIT_DENY + '(pivot_root\s*,|pivot_root\s+[^#]*\s*,)' + RE_EOL) -RE_PROFILE_UNIX = re.compile(RE_AUDIT_DENY + '(unix\s*,|unix\s+[^#]*\s*,)' + RE_EOL) +# Profile parsing Regex +RE_AUDIT_DENY = r'^\s*(?P<audit>audit\s+)?(?P<allow>allow\s+|deny\s+)?' # line start, optionally: leading whitespace, <audit> and <allow>/deny +RE_EOL = r'\s*(?P<comment>#.*?)?\s*$' # optional whitespace, optional <comment>, optional whitespace, end of the line +RE_COMMA_EOL = r'\s*,' + RE_EOL # optional whitespace, comma + RE_EOL + +RE_PROFILE_NAME = r'(?P<%s>(\S+|"[^"]+"))' # string without spaces, or quoted string. %s is the match group name +RE_PATH = r'/\S*|"/[^"]*"' # filename (starting with '/') without spaces, or quoted filename. +RE_PROFILE_PATH = '(?P<%s>(' + RE_PATH + '))' # quoted or unquoted filename. %s is the match group name +RE_PROFILE_PATH_OR_VAR = '(?P<%s>(' + RE_PATH + r'|@{\S+}\S*|"@{\S+}[^"]*"))' # quoted or unquoted filename or variable. %s is the match group name +RE_SAFE_OR_UNSAFE = '(?P<execmode>(safe|unsafe))' +RE_XATTRS = r'(\s+xattrs\s*=\s*\((?P<xattrs>([^)=]+(=[^)=]+)?\s?)+)\)\s*)?' +RE_FLAGS = r'(\s+(flags\s*=\s*)?\((?P<flags>[^)]+)\))?' + +RE_PROFILE_END = re.compile(r'^\s*\}' + RE_EOL) +RE_PROFILE_CAP = re.compile(RE_AUDIT_DENY + r'capability(?P<capability>(\s+\S+)+)?' + RE_COMMA_EOL) +RE_PROFILE_ALIAS = re.compile(r'^\s*alias\s+(?P<orig_path>"??.+?"??)\s+->\s*(?P<target>"??.+?"??)' + RE_COMMA_EOL) +RE_PROFILE_RLIMIT = re.compile(r'^\s*set\s+rlimit\s+(?P<rlimit>[a-z]+)\s*<=\s*(?P<value>[^ ]+(\s+[a-zA-Z]+)?)' + RE_COMMA_EOL) +RE_PROFILE_BOOLEAN = re.compile(r'^\s*(?P<varname>\$\{?\w*\}?)\s*=\s*(?P<value>true|false)\s*,?' + RE_EOL, flags=re.IGNORECASE) +RE_PROFILE_VARIABLE = re.compile(r'^\s*(?P<varname>@\{?\w+\}?)\s*(?P<mode>\+?=)\s*(?P<values>@*.+?)' + RE_EOL) +RE_PROFILE_CONDITIONAL = re.compile(r'^\s*if\s+(not\s+)?(\$\{?\w*\}?)\s*\{' + RE_EOL) +RE_PROFILE_CONDITIONAL_VARIABLE = re.compile(r'^\s*if\s+(not\s+)?defined\s+(@\{?\w+\}?)\s*\{\s*(#.*)?$') +RE_PROFILE_CONDITIONAL_BOOLEAN = re.compile(r'^\s*if\s+(not\s+)?defined\s+(\$\{?\w+\}?)\s*\{\s*(#.*)?$') +RE_PROFILE_NETWORK = re.compile(RE_AUDIT_DENY + r'network(?P<details>\s+.*)?' + RE_COMMA_EOL) +RE_PROFILE_CHANGE_HAT = re.compile(r'^\s*\^("??.+?"??)' + RE_COMMA_EOL) +RE_PROFILE_HAT_DEF = re.compile(r'^(?P<leadingspace>\s*)(?P<hat_keyword>\^|hat\s+)(?P<hat>"??[^)]+?"??)' + RE_FLAGS + r'\s*\{' + RE_EOL) +RE_PROFILE_DBUS = re.compile(RE_AUDIT_DENY + r'(dbus\s*,|dbus(?P<details>\s+[^#]*)\s*,)' + RE_EOL) +RE_PROFILE_MOUNT = re.compile(RE_AUDIT_DENY + r'((mount|remount|umount|unmount)(\s+[^#]*)?\s*,)' + RE_EOL) +RE_PROFILE_SIGNAL = re.compile(RE_AUDIT_DENY + r'(signal\s*,|signal(?P<details>\s+[^#]*)\s*,)' + RE_EOL) +RE_PROFILE_PTRACE = re.compile(RE_AUDIT_DENY + r'(ptrace\s*,|ptrace(?P<details>\s+[^#]*)\s*,)' + RE_EOL) +RE_PROFILE_PIVOT_ROOT = re.compile(RE_AUDIT_DENY + r'(pivot_root\s*,|pivot_root\s+[^#]*\s*,)' + RE_EOL) +RE_PROFILE_UNIX = re.compile(RE_AUDIT_DENY + r'(unix\s*,|unix\s+[^#]*\s*,)' + RE_EOL) # match anything that's not " or #, or matching quotes with anything except quotes inside __re_no_or_quoted_hash = '([^#"]|"[^"]*")*' -RE_RULE_HAS_COMMA = re.compile('^' + __re_no_or_quoted_hash + - ',\s*(#.*)?$') # match comma plus any trailing comment -RE_HAS_COMMENT_SPLIT = re.compile('^(?P<not_comment>' + __re_no_or_quoted_hash + ')' + # store in 'not_comment' group - '(?P<comment>#.*)$') # match trailing comment and store in 'comment' group - +RE_RULE_HAS_COMMA = re.compile( + '^' + __re_no_or_quoted_hash + + r',\s*(#.*)?$') # match comma plus any trailing comment +RE_HAS_COMMENT_SPLIT = re.compile( + '^(?P<not_comment>' + __re_no_or_quoted_hash + ')' # store in 'not_comment' group + + '(?P<comment>#.*)$') # match trailing comment and store in 'comment' group -RE_PROFILE_START = re.compile( - '^(?P<leadingspace>\s*)' + - '(' + - RE_PROFILE_PATH_OR_VAR % 'plainprofile' + # just a path - '|' + # or - '(' + 'profile' + '\s+' + RE_PROFILE_NAME % 'namedprofile' + '(\s+' + RE_PROFILE_PATH_OR_VAR % 'attachment' + ')?' + ')' + # 'profile', profile name, optionally attachment - ')' + - RE_XATTRS + - RE_FLAGS + - '\s*\{' + - RE_EOL) +RE_PROFILE_START = re.compile( + r'^(?P<leadingspace>\s*)' + + '(' + + RE_PROFILE_PATH_OR_VAR % 'plainprofile' # just a path + + '|' # or + + '(' + 'profile' + r'\s+' + RE_PROFILE_NAME % 'namedprofile' + r'(\s+' + RE_PROFILE_PATH_OR_VAR % 'attachment' + ')?' + ')' # 'profile', profile name, optionally attachment + + ')' + + RE_XATTRS + + RE_FLAGS + + r'\s*\{' + + RE_EOL) RE_PROFILE_CHANGE_PROFILE = re.compile( - RE_AUDIT_DENY + - 'change_profile' + - '(\s+' + RE_SAFE_OR_UNSAFE + ')?' + # optionally exec mode - '(\s+' + RE_PROFILE_PATH_OR_VAR % 'execcond' + ')?' + # optionally exec condition - '(\s+->\s*' + RE_PROFILE_NAME % 'targetprofile' + ')?' + # optionally '->' target profile - RE_COMMA_EOL) + RE_AUDIT_DENY + + 'change_profile' + + r'(\s+' + RE_SAFE_OR_UNSAFE + ')?' # optionally exec mode + + r'(\s+' + RE_PROFILE_PATH_OR_VAR % 'execcond' + ')?' # optionally exec condition + + r'(\s+->\s*' + RE_PROFILE_NAME % 'targetprofile' + ')?' # optionally '->' target profile + + RE_COMMA_EOL) # RE_PATH_PERMS is as restrictive as possible, but might still cause mismatches when adding different rule types. @@ -89,50 +90,69 @@ RE_PROFILE_CHANGE_PROFILE = re.compile( RE_PATH_PERMS = '(?P<%s>[mrwalkPUCpucix]+)' RE_PROFILE_FILE_ENTRY = re.compile( - RE_AUDIT_DENY + - '(?P<owner>owner\s+)?' + # optionally: <owner> - '(' + - '(?P<bare_file>file)' + # bare 'file,' - '|' + # or - '(?P<file_keyword>file\s+)?' + # optional 'file' keyword - '(' + - RE_PROFILE_PATH_OR_VAR % 'path' + '\s+' + RE_PATH_PERMS % 'perms' + # path and perms - '|' + # or - RE_PATH_PERMS % 'perms2' + '\s+' + RE_PROFILE_PATH_OR_VAR % 'path2' + # perms and path - ')' + - '(\s+->\s*' + RE_PROFILE_NAME % 'target' + ')?' + - '|' + # or - '(?P<link_keyword>link\s+)' + # 'link' keyword - '(?P<subset_keyword>subset\s+)?' + # optional 'subset' keyword - RE_PROFILE_PATH_OR_VAR % 'link_path' + # path - '\s+' + '->' + '\s+' + # ' -> ' - RE_PROFILE_PATH_OR_VAR % 'link_target' + # path - ')' + - RE_COMMA_EOL) + RE_AUDIT_DENY + + r'(?P<owner>owner\s+)?' # optionally: <owner> + + '(' + + '(?P<bare_file>file)' # bare 'file,' + + '|' # or + + r'(?P<file_keyword>file\s+)?' # optional 'file' keyword + + '(' + + RE_PROFILE_PATH_OR_VAR % 'path' + r'\s+' + RE_PATH_PERMS % 'perms' # path and perms + + '|' # or + + RE_PATH_PERMS % 'perms2' + r'\s+' + RE_PROFILE_PATH_OR_VAR % 'path2' # perms and path + + ')' + + r'(\s+->\s*' + RE_PROFILE_NAME % 'target' + ')?' + + '|' # or + + r'(?P<link_keyword>link\s+)' # 'link' keyword + + r'(?P<subset_keyword>subset\s+)?' # optional 'subset' keyword + + RE_PROFILE_PATH_OR_VAR % 'link_path' # path + + r'\s+' + '->' + r'\s+' # ' -> ' + + RE_PROFILE_PATH_OR_VAR % 'link_target' # path + + ')' + + RE_COMMA_EOL) def parse_profile_start_line(line, filename): + common_sections = ['leadingspace', 'flags', 'comment'] + + sections = ['plainprofile', 'namedprofile', 'attachment', 'xattrs'] + common_sections matches = RE_PROFILE_START.search(line) if not matches: - raise AppArmorBug('The given line from file %(filename)s is not the start of a profile: %(line)s' % { 'filename': filename, 'line': line } ) + sections = ['hat_keyword', 'hat'] + common_sections + matches = RE_PROFILE_HAT_DEF.search(line) + + if not matches: + raise AppArmorBug('The given line from file %(filename)s is not the start of a profile: %(line)s' + % {'filename': filename, 'line': line}) result = {} - for section in [ 'leadingspace', 'plainprofile', 'namedprofile', 'attachment', 'xattrs', 'flags', 'comment']: + for section in sections: if matches.group(section): result[section] = matches.group(section) # sections with optional quotes - if section in ['plainprofile', 'namedprofile', 'attachment']: + if section in ('plainprofile', 'namedprofile', 'attachment', 'hat'): result[section] = strip_quotes(result[section]) else: result[section] = None - if result['flags'] and result['flags'].strip() == '': - raise AppArmorException(_('Invalid syntax in %(filename)s: Empty set of flags in line %(line)s.' % { 'filename': filename, 'line': line } )) - - if result['plainprofile']: + if result['flags'] and not result['flags'].strip(): + raise AppArmorException( + _('Invalid syntax in %(filename)s: Empty set of flags in line %(line)s.' + % {'filename': filename, 'line': line})) + + result['is_hat'] = False + if result.get('hat'): + result['is_hat'] = True + result['profile'] = result['hat'] + if result['hat_keyword'] == '^': + result['hat_keyword'] = False + else: + result['hat_keyword'] = True + result['profile_keyword'] = True + elif result['plainprofile']: result['profile'] = result['plainprofile'] result['profile_keyword'] = False else: @@ -141,12 +161,14 @@ def parse_profile_start_line(line, filename): return result + RE_MAGIC_OR_QUOTED_PATH = '(<(?P<magicpath>.*)>|"(?P<quotedpath>.*)"|(?P<unquotedpath>[^<>"]*))' -RE_ABI = re.compile('^\s*#?abi\s*' + RE_MAGIC_OR_QUOTED_PATH + RE_COMMA_EOL) -RE_INCLUDE = re.compile('^\s*#?include(?P<ifexists>\s+if\s+exists)?\s*' + RE_MAGIC_OR_QUOTED_PATH + RE_EOL) +RE_ABI = re.compile(r'^\s*#?abi\s*' + RE_MAGIC_OR_QUOTED_PATH + RE_COMMA_EOL) +RE_INCLUDE = re.compile(r'^\s*#?include(?P<ifexists>\s+if\s+exists)?\s*' + RE_MAGIC_OR_QUOTED_PATH + RE_EOL) + def re_match_include_parse(line, rule_name): - '''Matches the path for include, include if exists and abi rules + """Matches the path for include, include if exists and abi rules rule_name can be 'include' or 'abi' @@ -154,7 +176,7 @@ def re_match_include_parse(line, rule_name): - if the "if exists" condition is given - the include/abi path - if the path is a magic path (enclosed in <...>) - ''' + """ if rule_name == 'include': matches = RE_INCLUDE.search(line) @@ -173,7 +195,7 @@ def re_match_include_parse(line, rule_name): ismagic = True elif matches.group('unquotedpath'): path = matches.group('unquotedpath').strip() - if re.search('\s', path): + if re.search(r'\s', path): raise AppArmorException(_('Syntax error: %s must use quoted path or <...>') % rule_name) # LP: #1738879 - parser doesn't handle unquoted paths everywhere if rule_name == 'include': @@ -182,15 +204,14 @@ def re_match_include_parse(line, rule_name): path = matches.group('quotedpath') # LP: 1738880 - parser doesn't handle relative paths everywhere, and # neither do we (see aa.py) - if rule_name == 'include' and len(path) > 0 and path[0] != '/': + if rule_name == 'include' and path and not path.startswith('/'): raise AppArmorException(_('Syntax error: %s must use quoted path or <...>') % rule_name) - # if path is empty or the empty string - if path is None or path == "": + if not path: raise AppArmorException(_('Syntax error: %s rule with empty filename') % rule_name) # LP: #1738877 - parser doesn't handle files with spaces in the name - if rule_name == 'include' and re.search('\s', path): + if rule_name == 'include' and re.search(r'\s', path): raise AppArmorException(_('Syntax error: %s rule filename cannot contain spaces') % rule_name) ifexists = False @@ -199,8 +220,9 @@ def re_match_include_parse(line, rule_name): return path, ifexists, ismagic + def re_match_include(line): - ''' return path of a 'include' rule ''' + """return path of a 'include' rule""" (path, ifexists, ismagic) = re_match_include_parse(line, 'include') if not ifexists: @@ -208,18 +230,20 @@ def re_match_include(line): return None + def strip_parenthesis(data): - '''strips parenthesis from the given string and returns the strip()ped result. + """strips parenthesis from the given string and returns the strip()ped result. The parenthesis must be the first and last char, otherwise they won't be removed. Even if no parenthesis get removed, the result will be strip()ped. - ''' - if data[0] + data[-1] == '()': + """ + if data.startswith('(') and data.endswith(')'): return data[1:-1].strip() else: return data.strip() + def strip_quotes(data): - if len(data) > 1 and data[0] + data[-1] == '""': + if len(data) > 1 and data.startswith('"') and data.endswith('"'): return data[1:-1] else: return data diff --git a/utils/apparmor/rule/__init__.py b/utils/apparmor/rule/__init__.py index 0484b2b03..41230dbd8 100644 --- a/utils/apparmor/rule/__init__.py +++ b/utils/apparmor/rule/__init__.py @@ -13,16 +13,17 @@ # # ---------------------------------------------------------------------- -from apparmor.aare import AARE -from apparmor.common import AppArmorBug, type_is_str +from abc import abstractmethod -# setup module translations +from apparmor.aare import AARE +from apparmor.common import AppArmorBug from apparmor.translations import init_translation + _ = init_translation() -class BaseRule(object): - '''Base class to handle and store a single rule''' +class BaseRule: + """Base class to handle and store a single rule""" # type specific rules should inherit from this class. # Methods that subclasses need to implement: @@ -51,7 +52,7 @@ class BaseRule(object): def __init__(self, audit=False, deny=False, allow_keyword=False, comment='', log_event=None): - '''initialize variables needed by all rule types''' + """initialize variables needed by all rule types""" self.audit = audit self.deny = deny self.allow_keyword = allow_keyword @@ -62,7 +63,7 @@ class BaseRule(object): self.raw_rule = None def _aare_or_all(self, rulepart, partname, is_path, log_event): - '''checks rulepart and returns + """checks rulepart and returns - (AARE, False) if rulepart is a (non-empty) string - (None, True) if rulepart is all_obj (typically *Rule.ALL) - raises AppArmorBug if rulepart is an empty string or has a wrong type @@ -72,18 +73,20 @@ class BaseRule(object): - partname: the name of the rulepart (for example 'peer', used for exception messages) - is_path (passed through to AARE) - log_event (passed through to AARE) - ''' + """ if rulepart == self.ALL: return None, True - elif type_is_str(rulepart): - if len(rulepart.strip()) == 0: - raise AppArmorBug('Passed empty %(partname)s to %(classname)s: %(rulepart)s' % - {'partname': partname, 'classname': self.__class__.__name__, 'rulepart': str(rulepart)}) + elif type(rulepart) is str: + if not rulepart.strip(): + raise AppArmorBug( + 'Passed empty %(partname)s to %(classname)s: %(rulepart)s' + % {'partname': partname, 'classname': self.__class__.__name__, 'rulepart': str(rulepart)}) return AARE(rulepart, is_path=is_path, log_event=log_event), False else: - raise AppArmorBug('Passed unknown %(partname)s to %(classname)s: %(rulepart)s' - % {'partname': partname, 'classname': self.__class__.__name__, 'rulepart': str(rulepart)}) + raise AppArmorBug( + 'Passed unknown %(partname)s to %(classname)s: %(rulepart)s' + % {'partname': partname, 'classname': self.__class__.__name__, 'rulepart': str(rulepart)}) def __repr__(self): classname = self.__class__.__name__ @@ -95,49 +98,49 @@ class BaseRule(object): @classmethod def match(cls, raw_rule): - '''return True if raw_rule matches the class (main) regex, False otherwise + """return True if raw_rule matches the class (main) regex, False otherwise Note: This function just provides an answer to "is this your job?". - It does not guarantee that the rule is completely valid.''' + It does not guarantee that the rule is completely valid.""" if cls._match(raw_rule): return True else: return False - # @abstractmethod FIXME - uncomment when python3 only @classmethod + @abstractmethod def _match(cls, raw_rule): - '''parse raw_rule and return regex match object''' + """parse raw_rule and return regex match object""" raise NotImplementedError("'%s' needs to implement _match(), but didn't" % (str(cls))) @classmethod def parse(cls, raw_rule): - '''parse raw_rule and return a rule object''' + """parse raw_rule and return a rule object""" rule = cls._parse(raw_rule) rule.raw_rule = raw_rule.strip() return rule - # @abstractmethod FIXME - uncomment when python3 only @classmethod + @abstractmethod def _parse(cls, raw_rule): - '''returns a Rule object created from parsing the raw rule. - required to be implemented by subclasses; raise exception if not''' + """returns a Rule object created from parsing the raw rule. + required to be implemented by subclasses; raise exception if not""" raise NotImplementedError("'%s' needs to implement _parse(), but didn't" % (str(cls))) - # @abstractmethod FIXME - uncomment when python3 only + @abstractmethod def get_clean(self, depth=0): - '''return clean rule (with default formatting, and leading whitespace as specified in the depth parameter)''' + """return clean rule (with default formatting, and leading whitespace as specified in the depth parameter)""" raise NotImplementedError("'%s' needs to implement get_clean(), but didn't" % (str(self.__class__))) def get_raw(self, depth=0): - '''return raw rule (with original formatting, and leading whitespace in the depth parameter)''' + """return raw rule (with original formatting, and leading whitespace in the depth parameter)""" if self.raw_rule: return '%s%s' % (' ' * depth, self.raw_rule) else: return self.get_clean(depth) def is_covered(self, other_rule, check_allow_deny=True, check_audit=False): - '''check if other_rule is covered by this rule object''' + """check if other_rule is covered by this rule object""" if not type(other_rule) == type(self): raise AppArmorBug('Passes %s instead of %s' % (str(other_rule), self.__class__.__name__)) @@ -157,13 +160,13 @@ class BaseRule(object): # still here? -> then the common part is covered, check rule-specific things now return self.is_covered_localvars(other_rule) - # @abstractmethod FIXME - uncomment when python3 only + @abstractmethod def is_covered_localvars(self, other_rule): - '''check if the rule-specific parts of other_rule is covered by this rule object''' + """check if the rule-specific parts of other_rule is covered by this rule object""" raise NotImplementedError("'%s' needs to implement is_covered_localvars(), but didn't" % (str(self))) def _is_covered_plain(self, self_value, self_all, other_value, other_all, cond_name): - '''check if other_* is covered by self_* - for plain str, int etc.''' + """check if other_* is covered by self_* - for plain str, int etc.""" if not other_value and not other_all: raise AppArmorBug('No %(cond_name)s specified in other %(rule_name)s rule' % {'cond_name': cond_name, 'rule_name': self.rule_name}) @@ -178,7 +181,7 @@ class BaseRule(object): return True def _is_covered_list(self, self_value, self_all, other_value, other_all, cond_name, sanity_check=True): - '''check if other_* is covered by self_* - for lists''' + """check if other_* is covered by self_* - for lists""" if sanity_check and not other_value and not other_all: raise AppArmorBug('No %(cond_name)s specified in other %(rule_name)s rule' % {'cond_name': cond_name, 'rule_name': self.rule_name}) @@ -193,7 +196,7 @@ class BaseRule(object): return True def _is_covered_aare(self, self_value, self_all, other_value, other_all, cond_name): - '''check if other_* is covered by self_* - for AARE''' + """check if other_* is covered by self_* - for AARE""" if not other_value and not other_all: raise AppArmorBug('No %(cond_name)s specified in other %(rule_name)s rule' % {'cond_name': cond_name, 'rule_name': self.rule_name}) @@ -208,8 +211,8 @@ class BaseRule(object): return True def is_equal(self, rule_obj, strict=False): - '''compare if rule_obj == self - Calls is_equal_localvars() to compare rule-specific variables''' + """compare if rule_obj == self + Calls is_equal_localvars() to compare rule-specific variables""" if self.audit != rule_obj.audit or self.deny != rule_obj.deny: return False @@ -224,7 +227,7 @@ class BaseRule(object): return self.is_equal_localvars(rule_obj, strict) def _is_equal_aare(self, self_value, self_all, other_value, other_all, cond_name): - '''check if other_* is the same as self_* - for AARE''' + """check if other_* is the same as self_* - for AARE""" if not other_value and not other_all: raise AppArmorBug('No %(cond_name)s specified in other %(rule_name)s rule' % {'cond_name': cond_name, 'rule_name': self.rule_name}) @@ -238,66 +241,66 @@ class BaseRule(object): # still here? -> then it is equal return True - # @abstractmethod FIXME - uncomment when python3 only + @abstractmethod def is_equal_localvars(self, other_rule, strict): - '''compare if rule-specific variables are equal''' + """compare if rule-specific variables are equal""" raise NotImplementedError("'%s' needs to implement is_equal_localvars(), but didn't" % (str(self))) def severity(self, sev_db): - '''return severity of this rule, which can be: + """return severity of this rule, which can be: - a number between 0 and 10, where 0 means harmless and 10 means critical, - "unknown" (to be exact: the value specified for "unknown" as set when loading the severity database), or - sev_db.NOT_IMPLEMENTED if no severity check is implemented for this rule type. - sev_db must be an apparmor.severity.Severity object.''' + sev_db must be an apparmor.severity.Severity object.""" return sev_db.NOT_IMPLEMENTED def logprof_header(self): - '''return the headers (human-readable version of the rule) to display in aa-logprof for this rule object - returns {'label1': 'value1', 'label2': 'value2'} ''' + """return the headers (human-readable version of the rule) to display in aa-logprof for this rule object + returns {'label1': 'value1', 'label2': 'value2'}""" headers = [] qualifier = [] if self.audit: - qualifier += ['audit'] + qualifier.append('audit') if self.deny: - qualifier += ['deny'] + qualifier.append('deny') elif self.allow_keyword: - qualifier += ['allow'] + qualifier.append('allow') if qualifier: - headers += [_('Qualifier'), ' '.join(qualifier)] + headers.extend((_('Qualifier'), ' '.join(qualifier))) - headers += self.logprof_header_localvars() + headers.extend(self.logprof_header_localvars()) return headers - # @abstractmethod FIXME - uncomment when python3 only + @abstractmethod def logprof_header_localvars(self): - '''return the headers (human-readable version of the rule) to display in aa-logprof for this rule object - returns {'label1': 'value1', 'label2': 'value2'} ''' + """return the headers (human-readable version of the rule) to display in aa-logprof for this rule object + returns {'label1': 'value1', 'label2': 'value2'}""" raise NotImplementedError("'%s' needs to implement logprof_header(), but didn't" % (str(self))) - # @abstractmethod FIXME - uncomment when python3 only + @abstractmethod def edit_header(self): - '''return the prompt for, and the path to edit when using '(N)ew' ''' + """return the prompt for, and the path to edit when using '(N)ew'""" raise NotImplementedError("'%s' needs to implement edit_header(), but didn't" % (str(self))) - # @abstractmethod FIXME - uncomment when python3 only + @abstractmethod def validate_edit(self, newpath): - '''validate the new path. - Returns True if it covers the previous path, False if it doesn't.''' + """validate the new path. + Returns True if it covers the previous path, False if it doesn't.""" raise NotImplementedError("'%s' needs to implement validate_edit(), but didn't" % (str(self))) - # @abstractmethod FIXME - uncomment when python3 only + @abstractmethod def store_edit(self, newpath): - '''store the changed path. - This is done even if the new path doesn't match the original one.''' + """store the changed path. + This is done even if the new path doesn't match the original one.""" raise NotImplementedError("'%s' needs to implement store_edit(), but didn't" % (str(self))) def modifiers_str(self): - '''return the allow/deny and audit keyword as string, including whitespace''' + """return the allow/deny and audit keyword as string, including whitespace""" if self.audit: auditstr = 'audit ' @@ -314,8 +317,8 @@ class BaseRule(object): return '%s%s' % (auditstr, allowstr) -class BaseRuleset(object): - '''Base class to handle and store a collection of rules''' +class BaseRuleset: + """Base class to handle and store a collection of rules""" # decides if the (G)lob and Glob w/ (E)xt options are displayed # XXX TODO: remove in all *Ruleset classes (moved to *Rule) @@ -323,14 +326,13 @@ class BaseRuleset(object): can_glob_ext = False def __init__(self): - '''initialize variables needed by all ruleset types - Do not override in child class unless really needed - override _init_vars() instead''' + """initialize variables needed by all ruleset types + Do not override in child class unless really needed - override _init_vars() instead""" self.rules = [] self._init_vars() def _init_vars(self): - '''called by __init__() and delete_all_rules() - override in child class to initialize more variables''' - pass + """called by __init__() and delete_all_rules() - override in child class to initialize more variables""" def __repr__(self): classname = self.__class__.__name__ @@ -340,11 +342,11 @@ class BaseRuleset(object): return '<%s (empty) />' % classname def add(self, rule, cleanup=False): - '''add a rule object + """add a rule object if cleanup is specified, delete rules that are covered by the new rule (the difference to delete_duplicates() is: cleanup only deletes rules that are covered by the new rule, but keeps other, unrelated superfluous rules) - ''' + """ deleted = 0 if cleanup: @@ -362,8 +364,8 @@ class BaseRuleset(object): return deleted def get_raw(self, depth=0): - '''return all raw rules (if possible/not modified in their original formatting). - Returns an array of lines, with depth * leading whitespace''' + """return all raw rules (if possible/not modified in their original formatting). + Returns an array of lines, with depth * leading whitespace""" data = [] for rule in self.rules: @@ -375,8 +377,8 @@ class BaseRuleset(object): return data def get_clean(self, depth=0): - '''return all rules (in clean/default formatting) - Returns an array of lines, with depth * leading whitespace''' + """return all rules (in clean/default formatting) + Returns an array of lines, with depth * leading whitespace""" allow_rules = [] deny_rules = [] @@ -403,8 +405,8 @@ class BaseRuleset(object): return cleandata def get_clean_unsorted(self, depth=0): - '''return all rules (in clean/default formatting) in original order - Returns an array of lines, with depth * leading whitespace''' + """return all rules (in clean/default formatting) in original order + Returns an array of lines, with depth * leading whitespace""" all_rules = [] @@ -417,7 +419,7 @@ class BaseRuleset(object): return all_rules def is_covered(self, rule, check_allow_deny=True, check_audit=False): - '''return True if rule is covered by existing rules, otherwise False''' + """return True if rule is covered by existing rules, otherwise False""" for r in self.rules: if r.is_covered(rule, check_allow_deny, check_audit): @@ -426,7 +428,7 @@ class BaseRuleset(object): return False # def is_log_covered(self, parsed_log_event, check_allow_deny=True, check_audit=False): -# '''return True if parsed_log_event is covered by existing rules, otherwise False''' +# """return True if parsed_log_event is covered by existing rules, otherwise False""" # # rule_obj = self.new_rule() # rule_obj.set_log(parsed_log_event) @@ -434,7 +436,7 @@ class BaseRuleset(object): # return self.is_covered(rule_obj, check_allow_deny, check_audit) def delete(self, rule): - '''Delete rule from rules''' + """Delete rule from rules""" rule_to_delete = False i = 0 @@ -450,8 +452,8 @@ class BaseRuleset(object): raise AppArmorBug('Attempt to delete non-existing rule %s' % rule.get_raw(0)) def delete_duplicates(self, include_rules): - '''Delete duplicate rules. - include_rules must be a *_rules object or None''' + """Delete duplicate rules. + include_rules must be a *_rules object or None""" deleted = 0 @@ -474,7 +476,7 @@ class BaseRuleset(object): return deleted def delete_in_profile_duplicates(self): - '''Delete duplicate rules inside a profile''' + """Delete duplicate rules inside a profile""" deleted = 0 oldrules = self.rules @@ -489,37 +491,40 @@ class BaseRuleset(object): return deleted def get_glob_ext(self, path_or_rule): - '''returns the next possible glob with extension (for file rules only). - For all other rule types, raise an exception''' + """returns the next possible glob with extension (for file rules only). + For all other rule types, raise an exception""" raise NotImplementedError("get_glob_ext is not available for this rule type!") def check_and_split_list(lst, allowed_keywords, all_obj, classname, keyword_name, allow_empty_list=False): - '''check if lst is all_obj or contains only items listed in allowed_keywords''' + """check if lst is all_obj or contains only items listed in allowed_keywords""" if lst == all_obj: return None, True, None - elif type_is_str(lst): + elif type(lst) is str: result_list = {lst} - elif type(lst) in [list, tuple, set] and (len(lst) > 0 or allow_empty_list): + elif type(lst) in (list, tuple, set) and (lst or allow_empty_list): result_list = set(lst) else: - raise AppArmorBug('Passed unknown %(type)s object to %(classname)s: %(unknown_object)s' % - {'type': type(lst), 'classname': classname, 'unknown_object': str(lst)}) + raise AppArmorBug( + 'Passed unknown %(type)s object to %(classname)s: %(unknown_object)s' + % {'type': type(lst), 'classname': classname, 'unknown_object': str(lst)}) unknown_items = set() for item in result_list: if not item.strip(): - raise AppArmorBug('Passed empty %(keyword_name)s to %(classname)s' % - {'keyword_name': keyword_name, 'classname': classname}) + raise AppArmorBug( + 'Passed empty %(keyword_name)s to %(classname)s' + % {'keyword_name': keyword_name, 'classname': classname}) if item not in allowed_keywords: unknown_items.add(item) return result_list, False, unknown_items + def logprof_value_or_all(value, all_values): - '''helper for logprof_header() to return 'all' (if all_values is True) or the specified value. - For some types, the value is made more readable.''' + """helper for logprof_header() to return 'all' (if all_values is True) or the specified value. + For some types, the value is made more readable.""" if all_values: return _('ALL') @@ -531,18 +536,20 @@ def logprof_value_or_all(value, all_values): else: return value + def parse_comment(matches): - '''returns the comment (with a leading space) from the matches object''' + """returns the comment (with a leading space) from the matches object""" comment = '' if matches.group('comment'): # include a space so that we don't need to add it everywhere when writing the rule comment = ' %s' % matches.group('comment') return comment + def parse_modifiers(matches): - '''returns audit, deny, allow_keyword and comment from the matches object + """returns audit, deny, allow_keyword and comment from the matches object - audit, deny and allow_keyword are True/False - - comment is the comment with a leading space''' + - comment is the comment with a leading space""" audit = False if matches.group('audit'): audit = True @@ -563,9 +570,9 @@ def parse_modifiers(matches): return (audit, deny, allow_keyword, comment) + def quote_if_needed(data): - '''quote data if it contains whitespace''' + """quote data if it contains whitespace""" if ' ' in data: data = '"' + data + '"' return data - diff --git a/utils/apparmor/rule/abi.py b/utils/apparmor/rule/abi.py index 261d20668..f985b78f4 100644 --- a/utils/apparmor/rule/abi.py +++ b/utils/apparmor/rule/abi.py @@ -12,28 +12,27 @@ # # ---------------------------------------------------------------------- -from apparmor.regex import RE_ABI from apparmor.common import AppArmorBug +from apparmor.regex import RE_ABI from apparmor.rule.include import IncludeRule, IncludeRuleset - -# setup module translations from apparmor.translations import init_translation + _ = init_translation() + # abi and include rules have a very similar syntax # base AbiRule on IncludeRule to inherit most of its behaviour class AbiRule(IncludeRule): - '''Class to handle and store a single abi rule''' + """Class to handle and store a single abi rule""" rule_name = 'abi' def __init__(self, path, ifexists, ismagic, audit=False, deny=False, allow_keyword=False, comment='', log_event=None): - super(AbiRule, self).__init__(path, ifexists, ismagic, - audit=audit, deny=deny, allow_keyword=allow_keyword, - comment=comment, - log_event=log_event) + super().__init__(path, ifexists, ismagic, + audit=audit, deny=deny, allow_keyword=allow_keyword, + comment=comment, log_event=log_event) # abi doesn't support 'if exists' if ifexists: @@ -44,21 +43,18 @@ class AbiRule(IncludeRule): return RE_ABI.search(raw_rule) def get_clean(self, depth=0): - '''return rule (in clean/default formatting)''' + """return rule (in clean/default formatting)""" space = ' ' * depth if self.ismagic: - return('%s%s <%s>,%s' % (space, self.rule_name, self.path, self.comment)) + return ('%s%s <%s>,%s' % (space, self.rule_name, self.path, self.comment)) else: - return('%s%s "%s",%s' % (space, self.rule_name, self.path, self.comment)) + return ('%s%s "%s",%s' % (space, self.rule_name, self.path, self.comment)) def logprof_header_localvars(self): - return [ - _('Abi'), self.get_clean(), - ] + return [_('Abi'), self.get_clean()] class AbiRuleset(IncludeRuleset): - '''Class to handle and store a collection of abi rules''' - pass + """Class to handle and store a collection of abi rules""" diff --git a/utils/apparmor/rule/alias.py b/utils/apparmor/rule/alias.py index 365002bd5..6cfc7796a 100644 --- a/utils/apparmor/rule/alias.py +++ b/utils/apparmor/rule/alias.py @@ -12,27 +12,24 @@ # # ---------------------------------------------------------------------- +from apparmor.common import AppArmorBug, AppArmorException from apparmor.regex import RE_PROFILE_ALIAS, strip_quotes -from apparmor.common import AppArmorBug, AppArmorException, type_is_str from apparmor.rule import BaseRule, BaseRuleset, parse_comment, quote_if_needed - -# setup module translations from apparmor.translations import init_translation + _ = init_translation() class AliasRule(BaseRule): - '''Class to handle and store a single alias rule''' + """Class to handle and store a single alias rule""" rule_name = 'alias' def __init__(self, orig_path, target, audit=False, deny=False, allow_keyword=False, comment='', log_event=None): - super(AliasRule, self).__init__(audit=audit, deny=deny, - allow_keyword=allow_keyword, - comment=comment, - log_event=log_event) + super().__init__(audit=audit, deny=deny, allow_keyword=allow_keyword, + comment=comment, log_event=log_event) # aliass don't support audit or deny if audit: @@ -40,14 +37,14 @@ class AliasRule(BaseRule): if deny: raise AppArmorBug('Attempt to initialize %s with deny flag' % self.__class__.__name__) - if not type_is_str(orig_path): + if type(orig_path) is not str: raise AppArmorBug('Passed unknown type for orig_path to %s: %s' % (self.__class__.__name__, orig_path)) if not orig_path: raise AppArmorException('Passed empty orig_path to %s: %s' % (self.__class__.__name__, orig_path)) if not orig_path.startswith('/'): raise AppArmorException("Alias path doesn't start with '/'") - if not type_is_str(target): + if type(target) is not str: raise AppArmorBug('Passed unknown type for target to %s: %s' % (self.__class__.__name__, target)) if not target: raise AppArmorException('Passed empty target to %s: %s' % (self.__class__.__name__, target)) @@ -63,7 +60,7 @@ class AliasRule(BaseRule): @classmethod def _parse(cls, raw_rule): - '''parse raw_rule and return AliasRule''' + """parse raw_rule and return AliasRule""" matches = cls._match(raw_rule) if not matches: @@ -75,23 +72,23 @@ class AliasRule(BaseRule): target = strip_quotes(matches.group('target').strip()) return AliasRule(orig_path, target, - audit=False, deny=False, allow_keyword=False, comment=comment) + audit=False, deny=False, allow_keyword=False, comment=comment) def get_clean(self, depth=0): - '''return rule (in clean/default formatting)''' + """return rule (in clean/default formatting)""" space = ' ' * depth return '%salias %s -> %s,' % (space, quote_if_needed(self.orig_path), quote_if_needed(self.target)) def is_covered_localvars(self, other_rule): - '''check if other_rule is covered by this rule object''' + """check if other_rule is covered by this rule object""" # the only way aliases can be covered are exact duplicates return self.is_equal_localvars(other_rule, False) def is_equal_localvars(self, rule_obj, strict): - '''compare if rule-specific aliass are equal''' + """compare if rule-specific aliass are equal""" if not type(rule_obj) == AliasRule: raise AppArmorBug('Passed non-alias rule: %s' % str(rule_obj)) @@ -111,6 +108,6 @@ class AliasRule(BaseRule): _('Alias'), '%s -> %s' % (self.orig_path, self.target), ] + class AliasRuleset(BaseRuleset): - '''Class to handle and store a collection of alias rules''' - pass + """Class to handle and store a collection of alias rules""" diff --git a/utils/apparmor/rule/boolean.py b/utils/apparmor/rule/boolean.py new file mode 100644 index 000000000..c59fa7628 --- /dev/null +++ b/utils/apparmor/rule/boolean.py @@ -0,0 +1,133 @@ +# ---------------------------------------------------------------------- +# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.com> +# Copyright (C) 2020 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.common import AppArmorBug, AppArmorException +from apparmor.regex import RE_PROFILE_BOOLEAN +from apparmor.rule import BaseRule, BaseRuleset, parse_comment +from apparmor.translations import init_translation + +_ = init_translation() + + +class BooleanRule(BaseRule): + """Class to handle and store a single variable rule""" + + rule_name = 'boolean' + + def __init__(self, varname, value, audit=False, deny=False, allow_keyword=False, + comment='', log_event=None): + + super().__init__(audit=audit, deny=deny, allow_keyword=allow_keyword, + comment=comment, log_event=log_event) + + # boolean variables don't support audit or deny + if audit: + raise AppArmorBug('Attempt to initialize %s with audit flag' % self.__class__.__name__) + if deny: + raise AppArmorBug('Attempt to initialize %s with deny flag' % self.__class__.__name__) + + if type(varname) is not str: + raise AppArmorBug('Passed unknown type for boolean variable to %s: %s' % (self.__class__.__name__, varname)) + if not varname.startswith('$'): + raise AppArmorException("Passed invalid boolean to %s (doesn't start with '$'): %s" % (self.__class__.__name__, varname)) + + if type(value) is not str: + raise AppArmorBug('Passed unknown type for value to %s: %s' % (self.__class__.__name__, value)) + if not value: + raise AppArmorException('Passed empty value to %s: %s' % (self.__class__.__name__, value)) + + value = value.lower() + if value not in ('true', 'false'): + raise AppArmorException('Passed invalid value to %s: %s' % (self.__class__.__name__, value)) + + self.varname = varname + self.value = value + + @classmethod + def _match(cls, raw_rule): + return RE_PROFILE_BOOLEAN.search(raw_rule) + + @classmethod + def _parse(cls, raw_rule): + """parse raw_rule and return BooleanRule""" + + matches = cls._match(raw_rule) + if not matches: + raise AppArmorException(_("Invalid boolean variable rule '%s'") % raw_rule) + + comment = parse_comment(matches) + + varname = matches.group('varname') + value = matches.group('value') + + return BooleanRule(varname, value, + audit=False, deny=False, allow_keyword=False, comment=comment) + + def get_clean(self, depth=0): + """return rule (in clean/default formatting)""" + + space = ' ' * depth + + return '%s%s = %s' % (space, self.varname, self.value) + + def is_covered_localvars(self, other_rule): + """check if other_rule is covered by this rule object""" + + if self.varname != other_rule.varname: + return False + + if not self._is_covered_list(self.value, None, set(other_rule.value), None, 'value'): + return False + + # still here? -> then it is covered + return True + + def is_equal_localvars(self, rule_obj, strict): + """compare if rule-specific variables are equal""" + + if not type(rule_obj) == BooleanRule: + raise AppArmorBug('Passed non-boolean rule: %s' % str(rule_obj)) + + if self.varname != rule_obj.varname: + return False + + if self.value != rule_obj.value: + return False + + return True + + def logprof_header_localvars(self): + headers = [] + + return headers + [ + _('Boolean Variable'), self.get_clean(), + ] + + +class BooleanRuleset(BaseRuleset): + """Class to handle and store a collection of variable rules""" + + def add(self, rule, cleanup=False): + """Add boolean variable rule object + + If the variable name is already known, raise an exception because re-defining a variable isn't allowed. + """ + + for knownrule in self.rules: + if rule.varname == knownrule.varname: + raise AppArmorException(_('Redefining existing variable %(variable)s: %(value)s') + % {'variable': rule.varname, 'value': rule.value}) + + super().add(rule, cleanup) diff --git a/utils/apparmor/rule/capability.py b/utils/apparmor/rule/capability.py index 979369715..bcd78d216 100644 --- a/utils/apparmor/rule/capability.py +++ b/utils/apparmor/rule/capability.py @@ -15,21 +15,20 @@ import re +from apparmor.common import AppArmorBug, AppArmorException from apparmor.regex import RE_PROFILE_CAP -from apparmor.common import AppArmorBug, AppArmorException, type_is_str from apparmor.rule import BaseRule, BaseRuleset, logprof_value_or_all, parse_modifiers - -# setup module translations from apparmor.translations import init_translation + _ = init_translation() class CapabilityRule(BaseRule): - '''Class to handle and store a single capability rule''' + """Class to handle and store a single capability rule""" # Nothing external should reference this class, all external users # should reference the class field CapabilityRule.ALL - class __CapabilityAll(object): + class __CapabilityAll: pass ALL = __CapabilityAll @@ -39,10 +38,8 @@ class CapabilityRule(BaseRule): def __init__(self, cap_list, audit=False, deny=False, allow_keyword=False, comment='', log_event=None): - super(CapabilityRule, self).__init__(audit=audit, deny=deny, - allow_keyword=allow_keyword, - comment=comment, - log_event=log_event) + super().__init__(audit=audit, deny=deny, allow_keyword=allow_keyword, + comment=comment, log_event=log_event) # Because we support having multiple caps in one rule, # initializer needs to accept a list of caps. self.all_caps = False @@ -50,16 +47,16 @@ class CapabilityRule(BaseRule): self.all_caps = True self.capability = set() else: - if type_is_str(cap_list): + if type(cap_list) is str: self.capability = {cap_list} - elif type(cap_list) == list and len(cap_list) > 0: + elif type(cap_list) == list and cap_list: self.capability = set(cap_list) else: raise AppArmorBug('Passed unknown object to CapabilityRule: %s' % str(cap_list)) # make sure none of the cap_list arguments are blank, in # case we decide to return one cap per output line for cap in self.capability: - if len(cap.strip()) == 0: + if not cap.strip(): raise AppArmorBug('Passed empty capability to CapabilityRule: %s' % str(cap_list)) @classmethod @@ -68,7 +65,7 @@ class CapabilityRule(BaseRule): @classmethod def _parse(cls, raw_rule): - '''parse raw_rule and return CapabilityRule''' + """parse raw_rule and return CapabilityRule""" matches = cls._match(raw_rule) if not matches: @@ -89,20 +86,20 @@ class CapabilityRule(BaseRule): comment=comment) def get_clean(self, depth=0): - '''return rule (in clean/default formatting)''' + """return rule (in clean/default formatting)""" space = ' ' * depth if self.all_caps: - return('%s%scapability,%s' % (space, self.modifiers_str(), self.comment)) + return ('%s%scapability,%s' % (space, self.modifiers_str(), self.comment)) else: caps = ' '.join(self.capability).strip() # XXX return multiple lines, one for each capability, instead? if caps: - return('%s%scapability %s,%s' % (space, self.modifiers_str(), ' '.join(sorted(self.capability)), self.comment)) + return ('%s%scapability %s,%s' % (space, self.modifiers_str(), ' '.join(sorted(self.capability)), self.comment)) else: raise AppArmorBug("Empty capability rule") def is_covered_localvars(self, other_rule): - '''check if other_rule is covered by this rule object''' + """check if other_rule is covered by this rule object""" if not self._is_covered_list(self.capability, self.all_caps, other_rule.capability, other_rule.all_caps, 'capability'): return False @@ -111,7 +108,7 @@ class CapabilityRule(BaseRule): return True def is_equal_localvars(self, rule_obj, strict): - '''compare if rule-specific variables are equal''' + """compare if rule-specific variables are equal""" if not type(rule_obj) == CapabilityRule: raise AppArmorBug('Passed non-capability rule: %s' % str(rule_obj)) @@ -146,8 +143,8 @@ class CapabilityRule(BaseRule): class CapabilityRuleset(BaseRuleset): - '''Class to handle and store a collection of capability rules''' + """Class to handle and store a collection of capability rules""" def get_glob(self, path_or_rule): - '''Return the next possible glob. For capability rules, that's always "capability," (all capabilities)''' + """Return the next possible glob. For capability rules, that's always "capability," (all capabilities)""" return 'capability,' diff --git a/utils/apparmor/rule/change_profile.py b/utils/apparmor/rule/change_profile.py index dea5d553a..321fe392f 100644 --- a/utils/apparmor/rule/change_profile.py +++ b/utils/apparmor/rule/change_profile.py @@ -13,40 +13,35 @@ # # ---------------------------------------------------------------------- +from apparmor.common import AppArmorBug, AppArmorException from apparmor.regex import RE_PROFILE_CHANGE_PROFILE, strip_quotes -from apparmor.common import AppArmorBug, AppArmorException, type_is_str -from apparmor.rule import BaseRule, BaseRuleset, parse_modifiers, logprof_value_or_all, quote_if_needed - -# setup module translations +from apparmor.rule import ( + BaseRule, BaseRuleset, logprof_value_or_all, parse_modifiers, quote_if_needed) from apparmor.translations import init_translation + _ = init_translation() class ChangeProfileRule(BaseRule): - '''Class to handle and store a single change_profile rule''' + """Class to handle and store a single change_profile rule""" # Nothing external should reference this class, all external users # should reference the class field ChangeProfileRule.ALL - class __ChangeProfileAll(object): + class __ChangeProfileAll: pass ALL = __ChangeProfileAll rule_name = 'change_profile' - equiv_execmodes = [ 'safe', '', None ] + equiv_execmodes = ['safe', '', None] def __init__(self, execmode, execcond, targetprofile, audit=False, deny=False, allow_keyword=False, comment='', log_event=None): + """CHANGE_PROFILE RULE = 'change_profile' [ [ EXEC MODE ] EXEC COND ] [ -> PROGRAMCHILD ]""" - ''' - CHANGE_PROFILE RULE = 'change_profile' [ [ EXEC MODE ] EXEC COND ] [ -> PROGRAMCHILD ] - ''' - - super(ChangeProfileRule, self).__init__(audit=audit, deny=deny, - allow_keyword=allow_keyword, - comment=comment, - log_event=log_event) + super().__init__(audit=audit, deny=deny, allow_keyword=allow_keyword, + comment=comment, log_event=log_event) if execmode: if execmode != 'safe' and execmode != 'unsafe': @@ -59,7 +54,7 @@ class ChangeProfileRule(BaseRule): self.all_execconds = False if execcond == ChangeProfileRule.ALL: self.all_execconds = True - elif type_is_str(execcond): + elif type(execcond) is str: if not execcond.strip(): raise AppArmorBug('Empty exec condition in change_profile rule') elif execcond.startswith('/') or execcond.startswith('@'): @@ -73,7 +68,7 @@ class ChangeProfileRule(BaseRule): self.all_targetprofiles = False if targetprofile == ChangeProfileRule.ALL: self.all_targetprofiles = True - elif type_is_str(targetprofile): + elif type(targetprofile) is str: if targetprofile.strip(): self.targetprofile = targetprofile else: @@ -87,7 +82,7 @@ class ChangeProfileRule(BaseRule): @classmethod def _parse(cls, raw_rule): - '''parse raw_rule and return ChangeProfileRule''' + """parse raw_rule and return ChangeProfileRule""" matches = cls._match(raw_rule) if not matches: @@ -108,10 +103,10 @@ class ChangeProfileRule(BaseRule): targetprofile = ChangeProfileRule.ALL return ChangeProfileRule(execmode, execcond, targetprofile, - audit=audit, deny=deny, allow_keyword=allow_keyword, comment=comment) + audit=audit, deny=deny, allow_keyword=allow_keyword, comment=comment) def get_clean(self, depth=0): - '''return rule (in clean/default formatting)''' + """return rule (in clean/default formatting)""" space = ' ' * depth @@ -134,14 +129,14 @@ class ChangeProfileRule(BaseRule): else: raise AppArmorBug('Empty target profile in change_profile rule') - return('%s%schange_profile%s%s%s,%s' % (space, self.modifiers_str(), execmode, execcond, targetprofile, self.comment)) + return ('%s%schange_profile%s%s%s,%s' % (space, self.modifiers_str(), execmode, execcond, targetprofile, self.comment)) def is_covered_localvars(self, other_rule): - '''check if other_rule is covered by this rule object''' + """check if other_rule is covered by this rule object""" - if self.execmode != other_rule.execmode and \ - (self.execmode not in ChangeProfileRule.equiv_execmodes or \ - other_rule.execmode not in ChangeProfileRule.equiv_execmodes): + if (self.execmode != other_rule.execmode + and (self.execmode not in ChangeProfileRule.equiv_execmodes + or other_rule.execmode not in ChangeProfileRule.equiv_execmodes)): return False if not self._is_covered_plain(self.execcond, self.all_execconds, other_rule.execcond, other_rule.all_execconds, 'exec condition'): @@ -155,14 +150,14 @@ class ChangeProfileRule(BaseRule): return True def is_equal_localvars(self, rule_obj, strict): - '''compare if rule-specific variables are equal''' + """compare if rule-specific variables are equal""" if not type(rule_obj) == ChangeProfileRule: raise AppArmorBug('Passed non-change_profile rule: %s' % str(rule_obj)) - if self.execmode != rule_obj.execmode and \ - (self.execmode not in ChangeProfileRule.equiv_execmodes or \ - rule_obj.execmode not in ChangeProfileRule.equiv_execmodes): + if (self.execmode != rule_obj.execmode + and (self.execmode not in ChangeProfileRule.equiv_execmodes + or rule_obj.execmode not in ChangeProfileRule.equiv_execmodes)): return False if (self.execcond != rule_obj.execcond @@ -179,22 +174,24 @@ class ChangeProfileRule(BaseRule): headers = [] if self.execmode: - headers += [_('Exec Mode'), self.execmode] + headers.extend((_('Exec Mode'), self.execmode)) - execcond_txt = logprof_value_or_all(self.execcond, self.all_execconds) - targetprofiles_txt = logprof_value_or_all(self.targetprofile, self.all_targetprofiles) + execcond_txt = logprof_value_or_all(self.execcond, self.all_execconds) # noqa: E221 + targetprofiles_txt = logprof_value_or_all(self.targetprofile, self.all_targetprofiles) - return headers + [ + headers.extend(( _('Exec Condition'), execcond_txt, _('Target Profile'), targetprofiles_txt, - ] + )) + return headers + class ChangeProfileRuleset(BaseRuleset): - '''Class to handle and store a collection of change_profile rules''' + """Class to handle and store a collection of change_profile rules""" def get_glob(self, path_or_rule): - '''Return the next possible glob. For change_profile rules, that can be "change_profile EXECCOND,", + """Return the next possible glob. For change_profile rules, that can be "change_profile EXECCOND,", "change_profile -> TARGET_PROFILE," or "change_profile," (all change_profile). - Also, EXECCOND filename can be globbed''' + Also, EXECCOND filename can be globbed""" # XXX implement all options mentioned above ;-) return 'change_profile,' diff --git a/utils/apparmor/rule/dbus.py b/utils/apparmor/rule/dbus.py index 2d5fe6d47..4a595795d 100644 --- a/utils/apparmor/rule/dbus.py +++ b/utils/apparmor/rule/dbus.py @@ -14,61 +14,64 @@ import re -from apparmor.regex import RE_PROFILE_DBUS, RE_PROFILE_NAME, strip_parenthesis, strip_quotes from apparmor.common import AppArmorBug, AppArmorException -from apparmor.rule import BaseRule, BaseRuleset, check_and_split_list, logprof_value_or_all, parse_modifiers, quote_if_needed - -# setup module translations +from apparmor.regex import RE_PROFILE_DBUS, RE_PROFILE_NAME, strip_parenthesis, strip_quotes +from apparmor.rule import ( + BaseRule, BaseRuleset, check_and_split_list, logprof_value_or_all, + parse_modifiers, quote_if_needed) from apparmor.translations import init_translation -_ = init_translation() +_ = init_translation() message_keywords = ['send', 'receive', 'r', 'read', 'w', 'write', 'rw'] -access_keywords = [ 'bind', 'eavesdrop' ] + message_keywords +access_keywords = ['bind', 'eavesdrop'] + message_keywords # XXX joint_access_keyword and RE_ACCESS_KEYWORDS exactly as in SignalRule - move to function? -joint_access_keyword = '(' + '(\s|,)*' + '(' + '|'.join(access_keywords) + ')(\s|,)*' + ')' -RE_ACCESS_KEYWORDS = ( joint_access_keyword + # one of the access_keyword or - '|' + # or - '\(' + '(\s|,)*' + joint_access_keyword + '?' + '(' + '(\s|,)+' + joint_access_keyword + ')*' + '\)' # one or more access_keyword in (...) - ) +joint_access_keyword = '(' + r'(\s|,)*' + '(' + '|'.join(access_keywords) + r')(\s|,)*' + ')' +RE_ACCESS_KEYWORDS = ( + joint_access_keyword # one of the access_keyword + + '|' # or + + r'\(' + r'(\s|,)*' + joint_access_keyword + '?' + '(' + r'(\s|,)+' + joint_access_keyword + ')*' + r'\)' # one or more access_keyword in (...) +) -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 +RE_FLAG = r'(?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+(peer\s*=\s*\((,|\s)*' + # optional peer=( name=AARE and/or label=AARE ), (...) required - '(' + - '(' + '(,|\s)*' + ')' + # empty peer=() - '|' # or - '(' + 'name\s*=\s*' + RE_PROFILE_NAME % 'peername1' + ')' + # only peer name (match group peername1) - '|' # or - '(' + 'label\s*=\s*' + RE_PROFILE_NAME % 'peerlabel1' + ')' + # only peer label (match group peerlabel1) - '|' # or - '(' + 'name\s*=\s*' + RE_PROFILE_NAME % 'peername2' + '(,|\s)+' + 'label\s*=\s*' + RE_PROFILE_NAME % 'peerlabel2' + ')' + # peer name + label (match name peername2/peerlabel2) - '|' # or - '(' + 'label\s*=\s*' + RE_PROFILE_NAME % 'peerlabel3' + '(,|\s)+' + 'name\s*=\s*' + RE_PROFILE_NAME % 'peername3' + ')' + # peer label + name (match name peername3/peerlabel3) - ')' - '(,|\s)*\)))?){0,6}' - '\s*$') +RE_DBUS_DETAILS = re.compile( + '^' + + r'(\s+(?P<access>' + RE_ACCESS_KEYWORDS + '))?' # optional access keyword(s) + + '(' + + r'(\s+(bus\s*=\s*' + RE_FLAG % 'bus' + '))?|' # optional bus= system | session | AARE, (...) optional + + r'(\s+(path\s*=\s*' + RE_FLAG % 'path' + '))?|' # optional path=AARE, (...) optional + + r'(\s+(name\s*=\s*' + RE_FLAG % 'name' + '))?|' # optional name=AARE, (...) optional + + r'(\s+(interface\s*=\s*' + RE_FLAG % 'interface' + '))?|' # optional interface=AARE, (...) optional + + r'(\s+(member\s*=\s*' + RE_FLAG % 'member' + '))?|' # optional member=AARE, (...) optional + + r'(\s+(peer\s*=\s*\((,|\s)*' # optional peer=(name=AARE and/or label=AARE), (...) required + + '(' + + '(' + r'(,|\s)*' + ')' # empty peer=() + + '|' # or + + '(' + r'name\s*=\s*' + RE_PROFILE_NAME % 'peername1' + ')' # only peer name (match group peername1) + + '|' # or + + '(' r'label\s*=\s*' + RE_PROFILE_NAME % 'peerlabel1' + ')' # only peer label (match group peerlabel1) + + '|' # or + + '(' + r'name\s*=\s*' + RE_PROFILE_NAME % 'peername2' + r'(,|\s)+' + r'label\s*=\s*' + RE_PROFILE_NAME % 'peerlabel2' + ')' # peer name + label (match name peername2/peerlabel2) + + '|' # or + + '(' + r'label\s*=\s*' + RE_PROFILE_NAME % 'peerlabel3' + r'(,|\s)+' + r'name\s*=\s*' + RE_PROFILE_NAME % 'peername3' + ')' # peer label + name (match name peername3/peerlabel3) + + ')' + + r'(,|\s)*\)))?' + + '){0,6}' + + r'\s*$') class DbusRule(BaseRule): - '''Class to handle and store a single dbus rule''' + """Class to handle and store a single dbus rule""" # Nothing external should reference this class, all external users # should reference the class field DbusRule.ALL - class __DbusAll(object): + class __DbusAll: pass ALL = __DbusAll @@ -76,25 +79,23 @@ class DbusRule(BaseRule): rule_name = 'dbus' def __init__(self, access, bus, path, name, interface, member, peername, peerlabel, - audit=False, deny=False, allow_keyword=False, comment='', log_event=None): + audit=False, deny=False, allow_keyword=False, comment='', log_event=None): - super(DbusRule, self).__init__(audit=audit, deny=deny, - allow_keyword=allow_keyword, - comment=comment, - log_event=log_event) + super().__init__(audit=audit, deny=deny, allow_keyword=allow_keyword, + comment=comment, log_event=log_event) self.access, self.all_access, unknown_items = check_and_split_list(access, access_keywords, DbusRule.ALL, 'DbusRule', 'access') if unknown_items: raise AppArmorException(_('Passed unknown access keyword to DbusRule: %s') % ' '.join(unknown_items)) - # rulepart partname is_path log_event - self.bus, self.all_buses = self._aare_or_all(bus, 'bus', False, log_event) - self.path, self.all_paths = self._aare_or_all(path, 'path', True, log_event) - self.name, self.all_names = self._aare_or_all(name, 'name', False, log_event) - self.interface, self.all_interfaces = self._aare_or_all(interface, 'interface', False, log_event) - self.member, self.all_members = self._aare_or_all(member, 'member', False, log_event) - self.peername, self.all_peernames = self._aare_or_all(peername, 'peer name', False, log_event) - self.peerlabel, self.all_peerlabels = self._aare_or_all(peerlabel, 'peer label', False, log_event) + # rulepart partname is_path log_event + self.bus, self.all_buses = self._aare_or_all(bus, 'bus', False, log_event) + self.path, self.all_paths = self._aare_or_all(path, 'path', True, log_event) + self.name, self.all_names = self._aare_or_all(name, 'name', False, log_event) + self.interface, self.all_interfaces = self._aare_or_all(interface, 'interface', False, log_event) + self.member, self.all_members = self._aare_or_all(member, 'member', False, log_event) + self.peername, self.all_peernames = self._aare_or_all(peername, 'peer name', False, log_event) + self.peerlabel, self.all_peerlabels = self._aare_or_all(peerlabel, 'peer label', False, log_event) # not all combinations are allowed if self.access and 'bind' in self.access and (self.path or self.interface or self.member or self.peername or self.peerlabel): @@ -112,7 +113,7 @@ class DbusRule(BaseRule): @classmethod def _parse(cls, raw_rule): - '''parse raw_rule and return DbusRule''' + """parse raw_rule and return DbusRule""" matches = cls._match(raw_rule) if not matches: @@ -133,7 +134,7 @@ class DbusRule(BaseRule): # XXX move to function _split_access()? access = strip_parenthesis(details.group('access')) access = access.replace(',', ' ').split() # split by ',' or whitespace - if access == []: # XXX that happens for "dbus ( )," rules - correct behaviour? (also: same for signal rules?) + if not access: # XXX that happens for "dbus ( )," rules - correct behaviour? (also: same for signal rules?) access = DbusRule.ALL else: access = DbusRule.ALL @@ -192,10 +193,10 @@ class DbusRule(BaseRule): peerlabel = DbusRule.ALL return DbusRule(access, bus, path, name, interface, member, peername, peerlabel, - audit=audit, deny=deny, allow_keyword=allow_keyword, comment=comment) + audit=audit, deny=deny, allow_keyword=allow_keyword, comment=comment) def get_clean(self, depth=0): - '''return rule (in clean/default formatting)''' + """return rule (in clean/default formatting)""" space = ' ' * depth @@ -221,11 +222,11 @@ class DbusRule(BaseRule): if peer: peer = ' peer=(%s)' % peer.strip() - return('%s%sdbus%s%s%s%s%s%s%s,%s' % (space, self.modifiers_str(), access, bus, path, name, interface, member, peer, self.comment)) + return ('%s%sdbus%s%s%s%s%s%s%s,%s' % (space, self.modifiers_str(), access, bus, path, name, interface, member, peer, self.comment)) def _get_aare_rule_part(self, prefix, value, all_values): - '''helper function to write a rule part - value is expected to be a AARE''' + """helper function to write a rule part + value is expected to be a AARE""" if all_values: return '' elif value: @@ -233,9 +234,8 @@ class DbusRule(BaseRule): else: raise AppArmorBug('Empty %(prefix_name)s in %(rule_name)s rule' % {'prefix_name': prefix, 'rule_name': self.rule_name}) - def is_covered_localvars(self, other_rule): - '''check if other_rule is covered by this rule object''' + """check if other_rule is covered by this rule object""" if not self._is_covered_list(self.access, self.all_access, other_rule.access, other_rule.all_access, 'access'): return False @@ -264,9 +264,8 @@ class DbusRule(BaseRule): # still here? -> then it is covered return True - def is_equal_localvars(self, rule_obj, strict): - '''compare if rule-specific variables are equal''' + """compare if rule-specific variables are equal""" if not type(rule_obj) == DbusRule: raise AppArmorBug('Passed non-dbus rule: %s' % str(rule_obj)) @@ -321,9 +320,9 @@ class DbusRule(BaseRule): class DbusRuleset(BaseRuleset): - '''Class to handle and store a collection of dbus rules''' + """Class to handle and store a collection of dbus rules""" def get_glob(self, path_or_rule): - '''Return the next possible glob. For dbus rules, that means removing access or removing/globbing bus''' + """Return the next possible glob. For dbus rules, that means removing access or removing/globbing bus""" # XXX only remove one part, not all return 'dbus,' diff --git a/utils/apparmor/rule/file.py b/utils/apparmor/rule/file.py index 3867bd08e..425e466ea 100644 --- a/utils/apparmor/rule/file.py +++ b/utils/apparmor/rule/file.py @@ -13,30 +13,30 @@ # ---------------------------------------------------------------------- from apparmor.aare import AARE +from apparmor.common import AppArmorBug, AppArmorException from apparmor.regex import RE_PROFILE_FILE_ENTRY, strip_quotes -from apparmor.common import AppArmorBug, AppArmorException, type_is_str -from apparmor.rule import BaseRule, BaseRuleset, check_and_split_list, logprof_value_or_all, parse_modifiers, quote_if_needed - -# setup module translations +from apparmor.rule import ( + BaseRule, BaseRuleset, check_and_split_list, logprof_value_or_all, + parse_modifiers, quote_if_needed) from apparmor.translations import init_translation -_ = init_translation() +_ = init_translation() -allow_exec_transitions = ('ix', 'ux', 'Ux', 'px', 'Px', 'cx', 'Cx') # 2 chars - len relevant for split_perms() +allow_exec_transitions = ('ix', 'ux', 'Ux', 'px', 'Px', 'cx', 'Cx') # 2 chars - len relevant for split_perms() allow_exec_fallback_transitions = ('pix', 'Pix', 'cix', 'Cix', 'pux', 'PUx', 'cux', 'CUx') # 3 chars - len relevant for split_perms() -deny_exec_transitions = ('x') -file_permissions = ('m', 'r', 'w', 'a', 'l', 'k', 'link', 'subset') # also defines the write order - +deny_exec_transitions = ('x') +file_permissions = ('m', 'r', 'w', 'a', 'l', 'k', 'link', 'subset') # also defines the write order class FileRule(BaseRule): - '''Class to handle and store a single file rule''' + """Class to handle and store a single file rule""" # Nothing external should reference this class, all external users # should reference the class field FileRule.ALL - class __FileAll(object): + class __FileAll: pass - class __FileAnyExec(object): + + class __FileAnyExec: pass ALL = __FileAll @@ -45,8 +45,8 @@ class FileRule(BaseRule): rule_name = 'file' def __init__(self, path, perms, exec_perms, target, owner, file_keyword=False, leading_perms=False, - audit=False, deny=False, allow_keyword=False, comment='', log_event=None): - '''Initialize FileRule + audit=False, deny=False, allow_keyword=False, comment='', log_event=None): + """Initialize FileRule Parameters: - path: string, AARE or FileRule.ALL @@ -56,33 +56,34 @@ class FileRule(BaseRule): - owner: bool - file_keyword: bool - leading_perms: bool - ''' + """ - super(FileRule, self).__init__(audit=audit, deny=deny, allow_keyword=allow_keyword, - comment=comment, log_event=log_event) + super().__init__(audit=audit, deny=deny, allow_keyword=allow_keyword, + comment=comment, log_event=log_event) - # rulepart partperms is_path log_event - self.path, self.all_paths = self._aare_or_all(path, 'path', True, log_event) - self.target, self.all_targets, = self._aare_or_all(target, 'target', False, log_event) + # rulepart partperms is_path log_event + self.path, self.all_paths = self._aare_or_all(path, 'path', True, log_event) + self.target, self.all_targets = self._aare_or_all(target, 'target', False, log_event) self.can_glob = not self.all_paths self.can_glob_ext = not self.all_paths self.can_edit = not self.all_paths - if type_is_str(perms): + if type(perms) is str: perms, tmp_exec_perms = split_perms(perms, deny) if tmp_exec_perms: raise AppArmorBug('perms must not contain exec perms') - elif perms == None: + elif perms is None: perms = set() if perms == {'subset'}: raise AppArmorBug('subset without link permissions given') - elif perms in [{'link'}, {'link', 'subset'}]: + elif perms in ({'link'}, {'link', 'subset'}): self.perms = perms self.all_perms = False else: - self.perms, self.all_perms, unknown_items = check_and_split_list(perms, file_permissions, FileRule.ALL, 'FileRule', 'permissions', allow_empty_list=True) + self.perms, self.all_perms, unknown_items = check_and_split_list( + perms, file_permissions, FileRule.ALL, 'FileRule', 'permissions', allow_empty_list=True) if unknown_items: raise AppArmorBug('Passed unknown perms to FileRule: %s' % str(unknown_items)) if self.perms and 'a' in self.perms and 'w' in self.perms: @@ -96,7 +97,7 @@ class FileRule(BaseRule): raise AppArmorBug("link rules can't have execute permissions") elif exec_perms == self.ANY_EXEC: self.exec_perms = exec_perms - elif type_is_str(exec_perms): + elif type(exec_perms) is str: if deny: if exec_perms != 'x': raise AppArmorException(_("file deny rules only allow to use 'x' as execute mode, but not %s" % exec_perms)) @@ -137,7 +138,7 @@ class FileRule(BaseRule): @classmethod def _parse(cls, raw_rule): - '''parse raw_rule and return FileRule''' + """parse raw_rule and return FileRule""" matches = cls._match(raw_rule) if not matches: @@ -188,10 +189,10 @@ class FileRule(BaseRule): file_keyword = bool(matches.group('file_keyword')) return FileRule(path, perms, exec_perms, target, owner, file_keyword, leading_perms, - audit=audit, deny=deny, allow_keyword=allow_keyword, comment=comment) + audit=audit, deny=deny, allow_keyword=allow_keyword, comment=comment) def get_clean(self, depth=0): - '''return rule (in clean/default formatting)''' + """return rule (in clean/default formatting)""" space = ' ' * depth @@ -232,18 +233,18 @@ class FileRule(BaseRule): file_keyword = '' if self.all_paths and self.all_perms and not path and not perms and not target: - return('%s%s%sfile,%s' % (space, self.modifiers_str(), owner, self.comment)) # plain 'file,' rule + return ('%s%s%sfile,%s' % (space, self.modifiers_str(), owner, self.comment)) # plain 'file,' rule elif not self.all_paths and not self.all_perms and path and perms: - return('%s%s%s%s%s%s,%s' % (space, self.modifiers_str(), file_keyword, owner, path_and_perms, target, self.comment)) + return ('%s%s%s%s%s%s,%s' % (space, self.modifiers_str(), file_keyword, owner, path_and_perms, target, self.comment)) else: raise AppArmorBug('Invalid combination of path and perms in file rule - either specify path and perms, or none of them') def _joint_perms(self): - '''return the permissions as string (using self.perms and self.exec_perms)''' + """return the permissions as string (using self.perms and self.exec_perms)""" return self._join_given_perms(self.perms, self.exec_perms) def _join_given_perms(self, perms, exec_perms): - '''return the permissions as string (using the perms and exec_perms given as parameter)''' + """return the permissions as string (using the perms and exec_perms given as parameter)""" perm_string = '' for perm in file_permissions: if perm in perms: @@ -259,9 +260,9 @@ class FileRule(BaseRule): return perm_string def is_covered_localvars(self, other_rule): - '''check if other_rule is covered by this rule object''' + """check if other_rule is covered by this rule object""" - if not self._is_covered_aare(self.path, self.all_paths, other_rule.path, other_rule.all_paths, 'path'): + if not self._is_covered_aare(self.path, self.all_paths, other_rule.path, other_rule.all_paths, 'path'): return False if self.perms and 'subset' in self.perms and other_rule.perms and 'subset' not in other_rule.perms: @@ -290,10 +291,10 @@ class FileRule(BaseRule): # check exec_mode and target only if other_rule contains exec_perms (except ANY_EXEC) or link permissions # (for mrwk permissions, the target is ignored anyway) - if (other_rule.exec_perms and other_rule.exec_perms != self.ANY_EXEC) or \ - (other_rule.perms and 'l' in other_rule.perms) or \ - (other_rule.perms and 'link' in other_rule.perms): - if not self._is_covered_aare(self.target, self.all_targets, other_rule.target, other_rule.all_targets, 'target'): + if ((other_rule.exec_perms and other_rule.exec_perms != self.ANY_EXEC) + or (other_rule.perms and 'l' in other_rule.perms) + or (other_rule.perms and 'link' in other_rule.perms)): + if not self._is_covered_aare(self.target, self.all_targets, other_rule.target, other_rule.all_targets, 'target'): return False # a different target means running with a different profile, therefore we have to be more strict than _is_covered_aare() @@ -309,9 +310,8 @@ class FileRule(BaseRule): # still here? -> then it is covered return True - def is_equal_localvars(self, rule_obj, strict): - '''compare if rule-specific variables are equal''' + """compare if rule-specific variables are equal""" if not type(rule_obj) == FileRule: raise AppArmorBug('Passed non-file rule: %s' % str(rule_obj)) @@ -319,7 +319,7 @@ class FileRule(BaseRule): if self.owner != rule_obj.owner: return False - if not self._is_equal_aare(self.path, self.all_paths, rule_obj.path, rule_obj.all_paths, 'path'): + if not self._is_equal_aare(self.path, self.all_paths, rule_obj.path, rule_obj.all_paths, 'path'): return False if self.perms != rule_obj.perms: @@ -331,7 +331,7 @@ class FileRule(BaseRule): if self.exec_perms != rule_obj.exec_perms: return False - if not self._is_equal_aare(self.target, self.all_targets, rule_obj.target, rule_obj.all_targets, 'target'): + if not self._is_equal_aare(self.target, self.all_targets, rule_obj.target, rule_obj.all_targets, 'target'): return False if strict: # file_keyword and leading_perms are only cosmetics, but still a difference @@ -362,12 +362,13 @@ class FileRule(BaseRule): headers = [] path = logprof_value_or_all(self.path, self.all_paths) - headers += [_('Path'), path] + headers.extend((_('Path'), path)) old_mode = '' if self.original_perms: original_perms_all = self._join_given_perms(self.original_perms['allow']['all'], None) - original_perms_owner = self._join_given_perms(self.original_perms['allow']['owner'] - self.original_perms['allow']['all'], None) # only list owner perms that are not covered by other perms + original_perms_owner = self._join_given_perms( + self.original_perms['allow']['owner'] - self.original_perms['allow']['all'], None) # only list owner perms that are not covered by other perms if original_perms_all and original_perms_owner: old_mode = '%s + owner %s' % (original_perms_all, original_perms_owner) @@ -379,7 +380,7 @@ class FileRule(BaseRule): old_mode = '' if old_mode: - headers += [_('Old Mode'), old_mode] + headers.extend((_('Old Mode'), old_mode)) perms = logprof_value_or_all(self.perms, self.all_perms) if self.perms or self.exec_perms: @@ -391,7 +392,7 @@ class FileRule(BaseRule): if not self.all_targets: perms = "%s -> %s" % (perms, self.target.regex) - headers += [_('New Mode'), perms] + headers.extend((_('New Mode'), perms)) # TODO: different output for link rules? @@ -399,7 +400,7 @@ class FileRule(BaseRule): return headers def glob(self): - '''Change path to next possible glob''' + """Change path to next possible glob""" if self.all_paths: return @@ -407,7 +408,7 @@ class FileRule(BaseRule): self.raw_rule = None def glob_ext(self): - '''Change path to next possible glob with extension''' + """Change path to next possible glob with extension""" if self.all_paths: return @@ -418,7 +419,7 @@ class FileRule(BaseRule): if self.all_paths: raise AppArmorBug('Attemp to edit bare file rule') - return(_('Enter new path: '), self.path.regex) + return (_('Enter new path: '), self.path.regex) def validate_edit(self, newpath): if self.all_paths: @@ -436,13 +437,13 @@ class FileRule(BaseRule): class FileRuleset(BaseRuleset): - '''Class to handle and store a collection of file rules''' + """Class to handle and store a collection of file rules""" def get_rules_for_path(self, path, audit=False, deny=False): - '''Get all rules matching the given path + """Get all rules matching the given path path can be str or AARE If audit is True, only return rules with the audit flag set. - If deny is True, only return matching deny rules''' + If deny is True, only return matching deny rules""" matching_rules = FileRuleset() for rule in self.rules: @@ -452,7 +453,7 @@ class FileRuleset(BaseRuleset): return matching_rules def get_perms_for_path(self, path, audit=False, deny=False): - '''Get the summarized permissions of all rules matching the given path, and the list of paths involved in the calculation + """Get the summarized permissions of all rules matching the given path, and the list of paths involved in the calculation path can be str or AARE If audit is True, only analyze rules with the audit flag set. If deny is True, only analyze matching deny rules @@ -460,18 +461,18 @@ class FileRuleset(BaseRuleset): 'deny': {'owner': set_of_perms, 'all': set_of_perms}, 'path': involved_paths} Note: exec rules and exec/link target are not honored! - ''' - # XXX do we need to honor the link target? + """ + # XXX do we need to honor the link target? perms = { - 'allow': {'owner': set(), 'all': set() }, - 'deny': {'owner': set(), 'all': set() }, + 'allow': {'owner': set(), 'all': set()}, + 'deny': {'owner': set(), 'all': set()}, } all_perms = { - 'allow': {'owner': False, 'all': False }, - 'deny': {'owner': False, 'all': False }, + 'allow': {'owner': False, 'all': False}, + 'deny': {'owner': False, 'all': False}, } - paths = set() + paths = set() matching_rules = self.get_rules_for_path(path, audit, deny) @@ -492,7 +493,7 @@ class FileRuleset(BaseRuleset): allow = {} deny = {} - for who in ['all', 'owner']: + for who in ('all', 'owner'): if all_perms['allow'][who]: allow[who] = FileRule.ALL else: @@ -506,8 +507,8 @@ class FileRuleset(BaseRuleset): return {'allow': allow, 'deny': deny, 'paths': paths} def get_exec_rules_for_path(self, path, only_exact_matches=True): - '''Get all rules matching the given path that contain exec permissions - path can be str or AARE''' + """Get all rules matching the given path that contain exec permissions + path can be str or AARE""" matches = FileRuleset() @@ -521,7 +522,7 @@ class FileRuleset(BaseRuleset): return matches def get_exec_conflict_rules(self, oldrule): - '''check if one of the exec rules conflict with oldrule. If yes, return the conflicting rules.''' + """check if one of the exec rules conflict with oldrule. If yes, return the conflicting rules.""" conflictingrules = FileRuleset() @@ -535,12 +536,11 @@ class FileRuleset(BaseRuleset): return conflictingrules - def split_perms(perm_string, deny): - '''parse permission string + """parse permission string - perm_string: the permission string to parse - deny: True if this is a deny rule - ''' + """ perms = set() exec_mode = None @@ -548,7 +548,7 @@ def split_perms(perm_string, deny): if perm_string[0] in file_permissions: perms.add(perm_string[0]) perm_string = perm_string[1:] - elif perm_string[0] == 'x': + elif perm_string.startswith('x'): if not deny: raise AppArmorException(_("'x' must be preceded by an exec qualifier (i, P, C or U)")) exec_mode = 'x' @@ -568,10 +568,11 @@ def split_perms(perm_string, deny): return perms, exec_mode + def perms_with_a(perms): - '''if perms includes 'w', add 'a' perms + """if perms includes 'w', add 'a' perms - perms: the original permissions - ''' + """ perms_with_a = set() if perms: perms_with_a = set(perms) diff --git a/utils/apparmor/rule/include.py b/utils/apparmor/rule/include.py index f2cb76e0c..e1eea7e35 100644 --- a/utils/apparmor/rule/include.py +++ b/utils/apparmor/rule/include.py @@ -11,29 +11,26 @@ # GNU General Public License for more details. # # ---------------------------------------------------------------------- +import os +from apparmor.common import AppArmorBug, AppArmorException, is_skippable_file from apparmor.regex import RE_INCLUDE, re_match_include_parse -from apparmor.common import AppArmorBug, AppArmorException, is_skippable_file, type_is_str from apparmor.rule import BaseRule, BaseRuleset, parse_comment -import os - -# setup module translations from apparmor.translations import init_translation + _ = init_translation() class IncludeRule(BaseRule): - '''Class to handle and store a single include rule''' + """Class to handle and store a single include rule""" rule_name = 'include' def __init__(self, path, ifexists, ismagic, audit=False, deny=False, allow_keyword=False, comment='', log_event=None): - super(IncludeRule, self).__init__(audit=audit, deny=deny, - allow_keyword=allow_keyword, - comment=comment, - log_event=log_event) + super().__init__(audit=audit, deny=deny, allow_keyword=allow_keyword, + comment=comment, log_event=log_event) # include doesn't support audit or deny if audit: @@ -45,7 +42,7 @@ class IncludeRule(BaseRule): raise AppArmorBug('Passed unknown type for ifexists to %s: %s' % (self.__class__.__name__, ifexists)) if type(ismagic) is not bool: raise AppArmorBug('Passed unknown type for ismagic to %s: %s' % (self.__class__.__name__, ismagic)) - if not type_is_str(path): + if type(path) is not str: raise AppArmorBug('Passed unknown type for path to %s: %s' % (self.__class__.__name__, path)) if not path: raise AppArmorBug('Passed empty path to %s: %s' % (self.__class__.__name__, path)) @@ -60,7 +57,7 @@ class IncludeRule(BaseRule): @classmethod def _parse(cls, raw_rule): - '''parse raw_rule and return IncludeRule''' + """parse raw_rule and return IncludeRule""" matches = cls._match(raw_rule) if not matches: @@ -72,10 +69,10 @@ class IncludeRule(BaseRule): path, ifexists, ismagic = re_match_include_parse(raw_rule, cls.rule_name) return cls(path, ifexists, ismagic, - audit=False, deny=False, allow_keyword=False, comment=comment) + audit=False, deny=False, allow_keyword=False, comment=comment) def get_clean(self, depth=0): - '''return rule (in clean/default formatting)''' + """return rule (in clean/default formatting)""" space = ' ' * depth @@ -84,17 +81,17 @@ class IncludeRule(BaseRule): ifexists_txt = ' if exists' if self.ismagic: - return('%s%s%s <%s>%s' % (space, self.rule_name, ifexists_txt, self.path, self.comment)) + return ('%s%s%s <%s>%s' % (space, self.rule_name, ifexists_txt, self.path, self.comment)) else: - return('%s%s%s "%s"%s' % (space, self.rule_name, ifexists_txt, self.path, self.comment)) + return ('%s%s%s "%s"%s' % (space, self.rule_name, ifexists_txt, self.path, self.comment)) def is_covered_localvars(self, other_rule): - '''check if other_rule is covered by this rule object''' + """check if other_rule is covered by this rule object""" if (self.path != other_rule.path): return False - if (self.ifexists != other_rule.ifexists) and (self.ifexists == True): # "if exists" is allowed to differ + if (self.ifexists != other_rule.ifexists) and self.ifexists: # "if exists" is allowed to differ return False if (self.ismagic != other_rule.ismagic): @@ -104,7 +101,7 @@ class IncludeRule(BaseRule): return True def is_equal_localvars(self, rule_obj, strict): - '''compare if rule-specific variables are equal''' + """compare if rule-specific variables are equal""" if not type(rule_obj) == type(self): raise AppArmorBug('Passed non-%s rule: %s' % (self.rule_name, str(rule_obj))) @@ -121,12 +118,10 @@ class IncludeRule(BaseRule): return True def logprof_header_localvars(self): - return [ - _('Include'), self.get_clean(), - ] + return [_('Include'), self.get_clean()] def get_full_paths(self, profile_dir): - ''' get list of full paths of an include (can contain multiple paths if self.path is a directory) ''' + """get list of full paths of an include (can contain multiple paths if self.path is a directory)""" # TODO: improve/fix logic to honor magic vs. quoted include paths if self.path.startswith('/'): @@ -148,19 +143,20 @@ class IncludeRule(BaseRule): elif os.path.exists(full_path): files.append(full_path) - elif self.ifexists == False: + elif not self.ifexists: files.append(full_path) # add full_path even if it doesn't exist on disk. Might cause a 'file not found' error later. return files + class IncludeRuleset(BaseRuleset): - '''Class to handle and store a collection of include rules''' + """Class to handle and store a collection of include rules""" def get_all_full_paths(self, profile_dir): - ''' get full path of all includes ''' + """get full path of all includes""" paths = [] for rule_obj in self.rules: - paths += rule_obj.get_full_paths(profile_dir) + paths.extend(rule_obj.get_full_paths(profile_dir)) return paths diff --git a/utils/apparmor/rule/network.py b/utils/apparmor/rule/network.py index c2efc6b31..713bed61b 100644 --- a/utils/apparmor/rule/network.py +++ b/utils/apparmor/rule/network.py @@ -15,41 +15,40 @@ import re +from apparmor.common import AppArmorBug, AppArmorException from apparmor.regex import RE_PROFILE_NETWORK -from apparmor.common import AppArmorBug, AppArmorException, type_is_str from apparmor.rule import BaseRule, BaseRuleset, logprof_value_or_all, parse_modifiers - -# setup module translations from apparmor.translations import init_translation -_ = init_translation() +_ = init_translation() -network_domain_keywords = [ 'unspec', 'unix', 'inet', 'ax25', 'ipx', 'appletalk', 'netrom', 'bridge', 'atmpvc', 'x25', 'inet6', - 'rose', 'netbeui', 'security', 'key', 'netlink', 'packet', 'ash', 'econet', 'atmsvc', 'rds', 'sna', - 'irda', 'pppox', 'wanpipe', 'llc', 'ib', 'mpls', 'can', 'tipc', 'bluetooth', 'iucv', 'rxrpc', 'isdn', - 'phonet', 'ieee802154', 'caif', 'alg', 'nfc', 'vsock', 'kcm', 'qipcrtr', 'smc', 'xdp', 'mctp' ] +network_domain_keywords = [ + 'unspec', 'unix', 'inet', 'ax25', 'ipx', 'appletalk', 'netrom', 'bridge', 'atmpvc', 'x25', 'inet6', + 'rose', 'netbeui', 'security', 'key', 'netlink', 'packet', 'ash', 'econet', 'atmsvc', 'rds', 'sna', + 'irda', 'pppox', 'wanpipe', 'llc', 'ib', 'mpls', 'can', 'tipc', 'bluetooth', 'iucv', 'rxrpc', 'isdn', + 'phonet', 'ieee802154', 'caif', 'alg', 'nfc', 'vsock', 'kcm', 'qipcrtr', 'smc', 'xdp', 'mctp'] -network_type_keywords = ['stream', 'dgram', 'seqpacket', 'rdm', 'raw', 'packet'] +network_type_keywords = ['stream', 'dgram', 'seqpacket', 'rdm', 'raw', 'packet'] network_protocol_keywords = ['tcp', 'udp', 'icmp'] -RE_NETWORK_DOMAIN = '(' + '|'.join(network_domain_keywords) + ')' -RE_NETWORK_TYPE = '(' + '|'.join(network_type_keywords) + ')' +RE_NETWORK_DOMAIN = '(' + '|'.join(network_domain_keywords) + ')' +RE_NETWORK_TYPE = '(' + '|'.join(network_type_keywords) + ')' RE_NETWORK_PROTOCOL = '(' + '|'.join(network_protocol_keywords) + ')' -RE_NETWORK_DETAILS = re.compile( - '^\s*' + - '(?P<domain>' + RE_NETWORK_DOMAIN + ')?' + # optional domain - '(\s+(?P<type_or_protocol>' + RE_NETWORK_TYPE + '|' + RE_NETWORK_PROTOCOL + '))?' + # optional type or protocol - '\s*$') +RE_NETWORK_DETAILS = re.compile( + r'^\s*' + + '(?P<domain>' + RE_NETWORK_DOMAIN + ')?' # optional domain + + r'(\s+(?P<type_or_protocol>' + RE_NETWORK_TYPE + '|' + RE_NETWORK_PROTOCOL + '))?' # optional type or protocol + + r'\s*$') class NetworkRule(BaseRule): - '''Class to handle and store a single network rule''' + """Class to handle and store a single network rule""" # Nothing external should reference this class, all external users # should reference the class field NetworkRule.ALL - class __NetworkAll(object): + class __NetworkAll: pass ALL = __NetworkAll @@ -59,16 +58,14 @@ class NetworkRule(BaseRule): def __init__(self, domain, type_or_protocol, audit=False, deny=False, allow_keyword=False, comment='', log_event=None): - super(NetworkRule, self).__init__(audit=audit, deny=deny, - allow_keyword=allow_keyword, - comment=comment, - log_event=log_event) + super().__init__(audit=audit, deny=deny, allow_keyword=allow_keyword, + comment=comment, log_event=log_event) self.domain = None self.all_domains = False if domain == NetworkRule.ALL: self.all_domains = True - elif type_is_str(domain): + elif type(domain) is str: if domain in network_domain_keywords: self.domain = domain else: @@ -80,7 +77,7 @@ class NetworkRule(BaseRule): self.all_type_or_protocols = False if type_or_protocol == NetworkRule.ALL: self.all_type_or_protocols = True - elif type_is_str(type_or_protocol): + elif type(type_or_protocol) is str: if type_or_protocol in network_protocol_keywords: self.type_or_protocol = type_or_protocol elif type_or_protocol in network_type_keywords: @@ -96,7 +93,7 @@ class NetworkRule(BaseRule): @classmethod def _parse(cls, raw_rule): - '''parse raw_rule and return NetworkRule''' + """parse raw_rule and return NetworkRule""" matches = cls._match(raw_rule) if not matches: @@ -130,7 +127,7 @@ class NetworkRule(BaseRule): audit=audit, deny=deny, allow_keyword=allow_keyword, comment=comment) def get_clean(self, depth=0): - '''return rule (in clean/default formatting)''' + """return rule (in clean/default formatting)""" space = ' ' * depth @@ -148,10 +145,10 @@ class NetworkRule(BaseRule): else: raise AppArmorBug('Empty type or protocol in network rule') - return('%s%snetwork%s%s,%s' % (space, self.modifiers_str(), domain, type_or_protocol, self.comment)) + return ('%s%snetwork%s%s,%s' % (space, self.modifiers_str(), domain, type_or_protocol, self.comment)) def is_covered_localvars(self, other_rule): - '''check if other_rule is covered by this rule object''' + """check if other_rule is covered by this rule object""" if not self._is_covered_plain(self.domain, self.all_domains, other_rule.domain, other_rule.all_domains, 'domain'): return False @@ -163,7 +160,7 @@ class NetworkRule(BaseRule): return True def is_equal_localvars(self, rule_obj, strict): - '''compare if rule-specific variables are equal''' + """compare if rule-specific variables are equal""" if not type(rule_obj) == NetworkRule: raise AppArmorBug('Passed non-network rule: %s' % str(rule_obj)) @@ -179,19 +176,19 @@ class NetworkRule(BaseRule): return True def logprof_header_localvars(self): - family = logprof_value_or_all(self.domain, self.all_domains) - sock_type = logprof_value_or_all(self.type_or_protocol, self.all_type_or_protocols) + family = logprof_value_or_all(self.domain, self.all_domains) # noqa: E221 + sock_type = logprof_value_or_all(self.type_or_protocol, self.all_type_or_protocols) return [ _('Network Family'), family, - _('Socket Type'), sock_type, + _('Socket Type'), sock_type, ] class NetworkRuleset(BaseRuleset): - '''Class to handle and store a collection of network rules''' + """Class to handle and store a collection of network rules""" def get_glob(self, path_or_rule): - '''Return the next possible glob. For network rules, that's "network DOMAIN," or "network," (all network)''' + """Return the next possible glob. For network rules, that's "network DOMAIN," or "network," (all network)""" # XXX return 'network DOMAIN,' if 'network DOMAIN TYPE_OR_PROTOCOL' was given return 'network,' diff --git a/utils/apparmor/rule/ptrace.py b/utils/apparmor/rule/ptrace.py index db230c5c3..57e382298 100644 --- a/utils/apparmor/rule/ptrace.py +++ b/utils/apparmor/rule/ptrace.py @@ -14,38 +14,37 @@ import re -from apparmor.regex import RE_PROFILE_PTRACE, RE_PROFILE_NAME, strip_quotes from apparmor.common import AppArmorBug, AppArmorException -from apparmor.rule import BaseRule, BaseRuleset, check_and_split_list, logprof_value_or_all, parse_modifiers, quote_if_needed - -# setup module translations +from apparmor.regex import RE_PROFILE_PTRACE, RE_PROFILE_NAME, strip_quotes +from apparmor.rule import ( + BaseRule, BaseRuleset, check_and_split_list, logprof_value_or_all, + parse_modifiers, quote_if_needed) from apparmor.translations import init_translation -_ = init_translation() +_ = init_translation() -access_keywords = ['r', 'w', 'rw', 'wr', 'read', 'write', 'readby', 'trace', 'tracedby'] # XXX 'wr' and 'write' accepted by the parser, but not documented in apparmor.d.pod +access_keywords = ['r', 'w', 'rw', 'wr', 'read', 'write', 'readby', 'trace', 'tracedby'] # XXX 'wr' and 'write' accepted by the parser, but not documented in apparmor.d.pod # XXX joint_access_keyword and RE_ACCESS_KEYWORDS exactly as in PtraceRule - move to function! -joint_access_keyword = '\s*(' + '|'.join(access_keywords) + ')\s*' -RE_ACCESS_KEYWORDS = ( joint_access_keyword + # one of the access_keyword or - '|' + # or - '\(' + joint_access_keyword + '(' + '(\s|,)+' + joint_access_keyword + ')*' + '\)' # one or more access_keyword in (...) - ) +joint_access_keyword = r'\s*(' + '|'.join(access_keywords) + r')\s*' +RE_ACCESS_KEYWORDS = (joint_access_keyword # one of the access_keyword + + '|' # or + + r'\(' + joint_access_keyword + '(' + r'(\s|,)+' + joint_access_keyword + ')*' + r'\)') # one or more access_keyword in (...) -RE_PTRACE_DETAILS = re.compile( - '^' + - '(\s+(?P<access>' + RE_ACCESS_KEYWORDS + '))?' + # optional access keyword(s) - '(\s+(peer=' + RE_PROFILE_NAME % 'peer' + '))?' + # optional peer - '\s*$') +RE_PTRACE_DETAILS = re.compile( + '^' + + r'(\s+(?P<access>' + RE_ACCESS_KEYWORDS + '))?' # optional access keyword(s) + + r'(\s+(peer=' + RE_PROFILE_NAME % 'peer' + '))?' # optional peer + + r'\s*$') class PtraceRule(BaseRule): - '''Class to handle and store a single ptrace rule''' + """Class to handle and store a single ptrace rule""" # Nothing external should reference this class, all external users # should reference the class field PtraceRule.ALL - class __PtraceAll(object): + class __PtraceAll: pass ALL = __PtraceAll @@ -55,12 +54,11 @@ class PtraceRule(BaseRule): def __init__(self, access, peer, audit=False, deny=False, allow_keyword=False, comment='', log_event=None): - super(PtraceRule, self).__init__(audit=audit, deny=deny, - allow_keyword=allow_keyword, - comment=comment, - log_event=log_event) + super().__init__(audit=audit, deny=deny, allow_keyword=allow_keyword, + comment=comment, log_event=log_event) - self.access, self.all_access, unknown_items = check_and_split_list(access, access_keywords, PtraceRule.ALL, 'PtraceRule', 'access') + self.access, self.all_access, unknown_items = check_and_split_list( + access, access_keywords, PtraceRule.ALL, 'PtraceRule', 'access') if unknown_items: raise AppArmorException(_('Passed unknown access keyword to PtraceRule: %s') % ' '.join(unknown_items)) @@ -72,7 +70,7 @@ class PtraceRule(BaseRule): @classmethod def _parse(cls, raw_rule): - '''parse raw_rule and return PtraceRule''' + """parse raw_rule and return PtraceRule""" matches = cls._match(raw_rule) if not matches: @@ -107,10 +105,10 @@ class PtraceRule(BaseRule): peer = PtraceRule.ALL return PtraceRule(access, peer, - audit=audit, deny=deny, allow_keyword=allow_keyword, comment=comment) + audit=audit, deny=deny, allow_keyword=allow_keyword, comment=comment) def get_clean(self, depth=0): - '''return rule (in clean/default formatting)''' + """return rule (in clean/default formatting)""" space = ' ' * depth @@ -130,10 +128,10 @@ class PtraceRule(BaseRule): else: raise AppArmorBug('Empty peer in ptrace rule') - return('%s%sptrace%s%s,%s' % (space, self.modifiers_str(), access, peer, self.comment)) + return ('%s%sptrace%s%s,%s' % (space, self.modifiers_str(), access, peer, self.comment)) def is_covered_localvars(self, other_rule): - '''check if other_rule is covered by this rule object''' + """check if other_rule is covered by this rule object""" if not self._is_covered_list(self.access, self.all_access, other_rule.access, other_rule.all_access, 'access'): return False @@ -145,7 +143,7 @@ class PtraceRule(BaseRule): return True def is_equal_localvars(self, rule_obj, strict): - '''compare if rule-specific variables are equal''' + """compare if rule-specific variables are equal""" if not type(rule_obj) == PtraceRule: raise AppArmorBug('Passed non-ptrace rule: %s' % str(rule_obj)) @@ -160,19 +158,19 @@ class PtraceRule(BaseRule): return True def logprof_header_localvars(self): - access = logprof_value_or_all(self.access,self.all_access) - peer = logprof_value_or_all(self.peer, self.all_peers) + access = logprof_value_or_all(self.access, self.all_access) + peer = logprof_value_or_all(self.peer, self.all_peers) # noqa: E221 return [ _('Access mode'), access, - _('Peer'), peer + _('Peer'), peer, ] class PtraceRuleset(BaseRuleset): - '''Class to handle and store a collection of ptrace rules''' + """Class to handle and store a collection of ptrace rules""" def get_glob(self, path_or_rule): - '''Return the next possible glob. For ptrace rules, that means removing access or removing/globbing peer''' + """Return the next possible glob. For ptrace rules, that means removing access or removing/globbing peer""" # XXX only remove one part, not all return 'ptrace,' diff --git a/utils/apparmor/rule/rlimit.py b/utils/apparmor/rule/rlimit.py index 2a2f5cd7a..12d1d45bc 100644 --- a/utils/apparmor/rule/rlimit.py +++ b/utils/apparmor/rule/rlimit.py @@ -15,33 +15,32 @@ import re +from apparmor.common import AppArmorBug, AppArmorException from apparmor.regex import RE_PROFILE_RLIMIT, strip_quotes -from apparmor.common import AppArmorBug, AppArmorException, type_is_str from apparmor.rule import BaseRule, BaseRuleset, parse_comment, quote_if_needed - -# setup module translations from apparmor.translations import init_translation + _ = init_translation() -rlimit_size = ['fsize', 'data', 'stack', 'core', 'rss', 'as', 'memlock', 'msgqueue'] # NUMBER ( 'K' | 'M' | 'G' ) -rlimit_number = ['ofile', 'nofile', 'locks', 'sigpending', 'nproc', 'rtprio'] -rlimit_time = ['cpu', 'rttime'] # number + time unit (cpu in seconds+, rttime in us+) -rlimit_nice = ['nice'] # a number between -20 and 19. +rlimit_size = ['fsize', 'data', 'stack', 'core', 'rss', 'as', 'memlock', 'msgqueue'] # NUMBER ( 'K' | 'M' | 'G' ) +rlimit_number = ['ofile', 'nofile', 'locks', 'sigpending', 'nproc', 'rtprio'] +rlimit_time = ['cpu', 'rttime'] # number + time unit (cpu in seconds+, rttime in us+) +rlimit_nice = ['nice'] # a number between -20 and 19. -rlimit_all = rlimit_size + rlimit_number + rlimit_time + rlimit_nice +rlimit_all = rlimit_size + rlimit_number + rlimit_time + rlimit_nice -RE_NUMBER_UNIT = re.compile('^(?P<number>[0-9]+)\s*(?P<unit>[a-zA-Z]*)$') -RE_NUMBER = re.compile('^[0-9]+$') -RE_UNIT_SIZE = re.compile('^[0-9]+\s*([KMG]B?)?$') -RE_NICE = re.compile('^(-20|-[01]?[0-9]|[01]?[0-9])$') +RE_NUMBER_UNIT = re.compile(r'^(?P<number>[0-9]+)\s*(?P<unit>[a-zA-Z]*)$') +RE_NUMBER = re.compile('^[0-9]+$') +RE_UNIT_SIZE = re.compile(r'^[0-9]+\s*([KMG]B?)?$') +RE_NICE = re.compile('^(-20|-[01]?[0-9]|[01]?[0-9])$') class RlimitRule(BaseRule): - '''Class to handle and store a single rlimit rule''' + """Class to handle and store a single rlimit rule""" # Nothing external should reference this class, all external users # should reference the class field RlimitRule.ALL - class __RlimitAll(object): + class __RlimitAll: pass ALL = __RlimitAll @@ -51,15 +50,13 @@ class RlimitRule(BaseRule): def __init__(self, rlimit, value, audit=False, deny=False, allow_keyword=False, comment='', log_event=None): - super(RlimitRule, self).__init__(audit=audit, deny=deny, - allow_keyword=allow_keyword, - comment=comment, - log_event=log_event) + super().__init__(audit=audit, deny=deny, allow_keyword=allow_keyword, + comment=comment, log_event=log_event) if audit or deny or allow_keyword: raise AppArmorBug('The audit, allow or deny keywords are not allowed in rlimit rules.') - if type_is_str(rlimit): + if type(rlimit) is str: if rlimit in rlimit_all: self.rlimit = rlimit else: @@ -72,7 +69,7 @@ class RlimitRule(BaseRule): self.all_values = False if value == RlimitRule.ALL: self.all_values = True - elif type_is_str(value): + elif type(value) is str: if not value.strip(): raise AppArmorBug('Empty value in rlimit rule') @@ -112,7 +109,7 @@ class RlimitRule(BaseRule): @classmethod def _parse(cls, raw_rule): - '''parse raw_rule and return RlimitRule''' + """parse raw_rule and return RlimitRule""" matches = cls._match(raw_rule) if not matches: @@ -133,11 +130,10 @@ class RlimitRule(BaseRule): else: raise AppArmorException(_("Invalid rlimit rule '%s' - value missing") % raw_rule) # pragma: no cover - would need breaking the regex - return RlimitRule(rlimit, value, - comment=comment) + return RlimitRule(rlimit, value, comment=comment) def get_clean(self, depth=0): - '''return rule (in clean/default formatting)''' + """return rule (in clean/default formatting)""" space = ' ' * depth @@ -153,12 +149,12 @@ class RlimitRule(BaseRule): else: raise AppArmorBug('Empty value in rlimit rule') - return('%s%sset rlimit%s%s,%s' % (space, self.modifiers_str(), rlimit, value, self.comment)) + return ('%s%sset rlimit%s%s,%s' % (space, self.modifiers_str(), rlimit, value, self.comment)) def size_to_int(self, value): number, unit = split_unit(value) - if unit == '': + if not unit: pass elif unit == 'K' or unit == 'KB': number = number * 1024 @@ -174,26 +170,26 @@ class RlimitRule(BaseRule): def time_to_int(self, value, default_unit): number, unit = split_unit(value) - if unit == '': + if not unit: unit = default_unit - if unit in ['us', 'microsecond', 'microseconds']: + if unit in ('us', 'microsecond', 'microseconds'): number = number / 1000000.0 if default_unit == 'seconds': raise AppArmorException(_('Invalid unit in rlimit cpu %s rule') % value) - elif unit in ['ms', 'millisecond', 'milliseconds']: + elif unit in ('ms', 'millisecond', 'milliseconds'): number = number / 1000.0 if default_unit == 'seconds': raise AppArmorException(_('Invalid unit in rlimit cpu %s rule') % value) - elif unit in ['s', 'sec', 'second', 'seconds']: # manpage doesn't list sec + elif unit in ('s', 'sec', 'second', 'seconds'): # manpage doesn't list sec pass - elif unit in ['min', 'minute', 'minutes']: + elif unit in ('min', 'minute', 'minutes'): number = number * 60 - elif unit in ['h', 'hour', 'hours']: + elif unit in ('h', 'hour', 'hours'): number = number * 60 * 60 - elif unit in ['d', 'day', 'days']: # manpage doesn't list 'd' + elif unit in ('d', 'day', 'days'): # manpage doesn't list 'd' number = number * 60 * 60 * 24 - elif unit in ['week', 'weeks']: + elif unit in ('week', 'weeks'): number = number * 60 * 60 * 24 * 7 else: raise AppArmorException('Unknown unit %s in rlimit %s %s' % (unit, self.rlimit, value)) @@ -201,7 +197,7 @@ class RlimitRule(BaseRule): return number def is_covered_localvars(self, other_rule): - '''check if other_rule is covered by this rule object''' + """check if other_rule is covered by this rule object""" if not self._is_covered_plain(self.rlimit, False, other_rule.rlimit, False, 'rlimit'): # rlimit can't be ALL, therefore using False return False @@ -219,7 +215,7 @@ class RlimitRule(BaseRule): return True def is_equal_localvars(self, rule_obj, strict): - '''compare if rule-specific variables are equal''' + """compare if rule-specific variables are equal""" if not type(rule_obj) == RlimitRule: raise AppArmorBug('Passed non-rlimit rule: %s' % str(rule_obj)) @@ -246,11 +242,12 @@ class RlimitRule(BaseRule): _('Value'), values_txt, ] + class RlimitRuleset(BaseRuleset): - '''Class to handle and store a collection of rlimit rules''' + """Class to handle and store a collection of rlimit rules""" def get_glob(self, path_or_rule): - '''Return the next possible glob. For rlimit rules, that can mean changing the value to 'infinity' ''' + """Return the next possible glob. For rlimit rules, that can mean changing the value to 'infinity'""" # XXX implement all options mentioned above ;-) raise AppArmorBug('get_glob() is not (yet) available for this rule type') @@ -264,5 +261,3 @@ def split_unit(value): unit = matches.group('unit') or '' return number, unit - - diff --git a/utils/apparmor/rule/signal.py b/utils/apparmor/rule/signal.py index af83f920d..3eb50b15d 100644 --- a/utils/apparmor/rule/signal.py +++ b/utils/apparmor/rule/signal.py @@ -14,59 +14,62 @@ import re -from apparmor.regex import RE_PROFILE_SIGNAL, RE_PROFILE_NAME from apparmor.common import AppArmorBug, AppArmorException -from apparmor.rule import BaseRule, BaseRuleset, check_and_split_list, logprof_value_or_all, parse_modifiers, quote_if_needed - -# setup module translations +from apparmor.regex import RE_PROFILE_SIGNAL, RE_PROFILE_NAME +from apparmor.rule import ( + BaseRule, BaseRuleset, check_and_split_list, logprof_value_or_all, + parse_modifiers, quote_if_needed) from apparmor.translations import init_translation -_ = init_translation() - -access_keywords_read = ['receive', 'r', 'read'] -access_keywords_write = ['send', 'w', 'write'] -access_keywords_rw = ['rw', 'wr'] -access_keywords = access_keywords_read + access_keywords_write + access_keywords_rw - -signal_keywords = ['hup', 'int', 'quit', 'ill', 'trap', 'abrt', 'bus', 'fpe', 'kill', 'usr1', - 'segv', 'usr2', 'pipe', 'alrm', 'term', 'stkflt', 'chld', 'cont', 'stop', - 'stp', 'ttin', 'ttou', 'urg', 'xcpu', 'xfsz', 'vtalrm', 'prof', 'winch', - 'io', 'pwr', 'sys', 'emt', 'exists'] -RE_SIGNAL_REALTIME = re.compile('^rtmin\+0*([0-9]|[12][0-9]|3[0-2])$') # rtmin+0..rtmin+32, number may have leading zeros - -joint_access_keyword = '\s*(' + '|'.join(access_keywords) + ')\s*' -RE_ACCESS_KEYWORDS = ( joint_access_keyword + # one of the access_keyword or - '|' + # or - '\(' + joint_access_keyword + '(' + '(\s|,)+' + joint_access_keyword + ')*' + '\)' # one or more access_keyword in (...) - ) +_ = init_translation() -signal_keyword = '\s*([a-z0-9+]+|"[a-z0-9+]+")\s*' # don't check against the signal keyword list in the regex to allow a more helpful error message +access_keywords_read = ['receive', 'r', 'read'] +access_keywords_write = ['send', 'w', 'write'] +access_keywords_rw = ['rw', 'wr'] +access_keywords = access_keywords_read + access_keywords_write + access_keywords_rw + +signal_keywords = [ + 'hup', 'int', 'quit', 'ill', 'trap', 'abrt', 'bus', 'fpe', 'kill', 'usr1', + 'segv', 'usr2', 'pipe', 'alrm', 'term', 'stkflt', 'chld', 'cont', 'stop', + 'stp', 'ttin', 'ttou', 'urg', 'xcpu', 'xfsz', 'vtalrm', 'prof', 'winch', + 'io', 'pwr', 'sys', 'emt', 'exists'] +RE_SIGNAL_REALTIME = re.compile(r'^rtmin\+0*([0-9]|[12][0-9]|3[0-2])$') # rtmin+0..rtmin+32, number may have leading zeros + +joint_access_keyword = r'\s*(' + '|'.join(access_keywords) + r')\s*' +RE_ACCESS_KEYWORDS = ( + joint_access_keyword # one of the access_keyword + + '|' # or + + r'\(' + joint_access_keyword + '(' + r'(\s|,)+' + joint_access_keyword + ')*' + r'\)' # one or more access_keyword in (...) +) + +signal_keyword = r'\s*([a-z0-9+]+|"[a-z0-9+]+")\s*' # don't check against the signal keyword list in the regex to allow a more helpful error message RE_SIGNAL_KEYWORDS = ( - 'set\s*=\s*' + signal_keyword + # one of the signal_keyword or - '|' + # or - 'set\s*=\s*\(' + signal_keyword + '(' + '(\s|,)+' + signal_keyword + ')*' + '\)' # one or more signal_keyword in (...) - ) + r'set\s*=\s*' + signal_keyword # one of the signal_keyword + + '|' # or + + r'set\s*=\s*\(' + signal_keyword + '(' + r'(\s|,)+' + signal_keyword + ')*' + r'\)' # one or more signal_keyword in (...) +) -RE_SIGNAL_DETAILS = re.compile( - '^' + - '(\s+(?P<access>' + RE_ACCESS_KEYWORDS + '))?' + # optional access keyword(s) - '(?P<signal>' + '(\s+(' + RE_SIGNAL_KEYWORDS + '))+' + ')?' + # optional signal set(s) - '(\s+(peer=' + RE_PROFILE_NAME % 'peer' + '))?' + # optional peer - '\s*$') +RE_SIGNAL_DETAILS = re.compile( + '^' + + r'(\s+(?P<access>' + RE_ACCESS_KEYWORDS + '))?' # optional access keyword(s) + + '(?P<signal>' + r'(\s+(' + RE_SIGNAL_KEYWORDS + '))+' + ')?' # optional signal set(s) + + r'(\s+(peer=' + RE_PROFILE_NAME % 'peer' + '))?' # optional peer + + r'\s*$') -RE_FILTER_SET_1 = re.compile('set\s*=\s*\(([^)]*)\)') -RE_FILTER_SET_2 = re.compile('set\s*=') -RE_FILTER_PARENTHESIS = re.compile('\((.*)\)') +RE_FILTER_SET_1 = re.compile(r'set\s*=\s*\(([^)]*)\)') +RE_FILTER_SET_2 = re.compile(r'set\s*=') +RE_FILTER_PARENTHESIS = re.compile(r'\((.*)\)') RE_FILTER_QUOTES = re.compile('"([a-z0-9]+)"') # used to strip quotes around signal keywords - don't use for peer! + class SignalRule(BaseRule): - '''Class to handle and store a single signal rule''' + """Class to handle and store a single signal rule""" # Nothing external should reference this class, all external users # should reference the class field SignalRule.ALL - class __SignalAll(object): + class __SignalAll: pass ALL = __SignalAll @@ -76,16 +79,16 @@ class SignalRule(BaseRule): def __init__(self, access, signal, peer, audit=False, deny=False, allow_keyword=False, comment='', log_event=None): - super(SignalRule, self).__init__(audit=audit, deny=deny, - allow_keyword=allow_keyword, - comment=comment, - log_event=log_event) + super().__init__(audit=audit, deny=deny, allow_keyword=allow_keyword, + comment=comment, log_event=log_event) - self.access, self.all_access, unknown_items = check_and_split_list(access, access_keywords, SignalRule.ALL, 'SignalRule', 'access') + self.access, self.all_access, unknown_items = check_and_split_list( + access, access_keywords, SignalRule.ALL, 'SignalRule', 'access') if unknown_items: raise AppArmorException(_('Passed unknown access keyword to SignalRule: %s') % ' '.join(unknown_items)) - self.signal, self.all_signals, unknown_items = check_and_split_list(signal, signal_keywords, SignalRule.ALL, 'SignalRule', 'signal') + self.signal, self.all_signals, unknown_items = check_and_split_list( + signal, signal_keywords, SignalRule.ALL, 'SignalRule', 'signal') if unknown_items: for item in unknown_items: if RE_SIGNAL_REALTIME.match(item): @@ -101,7 +104,7 @@ class SignalRule(BaseRule): @classmethod def _parse(cls, raw_rule): - '''parse raw_rule and return SignalRule''' + """parse raw_rule and return SignalRule""" matches = cls._match(raw_rule) if not matches: @@ -145,10 +148,10 @@ class SignalRule(BaseRule): peer = SignalRule.ALL return SignalRule(access, signal, peer, - audit=audit, deny=deny, allow_keyword=allow_keyword, comment=comment) + audit=audit, deny=deny, allow_keyword=allow_keyword, comment=comment) def get_clean(self, depth=0): - '''return rule (in clean/default formatting)''' + """return rule (in clean/default formatting)""" space = ' ' * depth @@ -177,10 +180,10 @@ class SignalRule(BaseRule): else: raise AppArmorBug('Empty peer in signal rule') - return('%s%ssignal%s%s%s,%s' % (space, self.modifiers_str(), access, signal, peer, self.comment)) + return ('%s%ssignal%s%s%s,%s' % (space, self.modifiers_str(), access, signal, peer, self.comment)) def is_covered_localvars(self, other_rule): - '''check if other_rule is covered by this rule object''' + """check if other_rule is covered by this rule object""" if not self._is_covered_list(self.access, self.all_access, other_rule.access, other_rule.all_access, 'access'): return False @@ -195,7 +198,7 @@ class SignalRule(BaseRule): return True def is_equal_localvars(self, rule_obj, strict): - '''compare if rule-specific variables are equal''' + """compare if rule-specific variables are equal""" if not type(rule_obj) == SignalRule: raise AppArmorBug('Passed non-signal rule: %s' % str(rule_obj)) @@ -214,22 +217,21 @@ class SignalRule(BaseRule): return True def logprof_header_localvars(self): - access = logprof_value_or_all(self.access, self.all_access) - signal = logprof_value_or_all(self.signal, self.all_signals) - peer = logprof_value_or_all(self.peer, self.all_peers) + access = logprof_value_or_all(self.access, self.all_access) + signal = logprof_value_or_all(self.signal, self.all_signals) + peer = logprof_value_or_all(self.peer, self.all_peers) # noqa: E221 return [ _('Access mode'), access, - _('Signal'), signal, - _('Peer'), peer + _('Signal'), signal, + _('Peer'), peer ] class SignalRuleset(BaseRuleset): - '''Class to handle and store a collection of signal rules''' + """Class to handle and store a collection of signal rules""" def get_glob(self, path_or_rule): - '''Return the next possible glob. For signal rules, that means removing access, signal or peer''' + """Return the next possible glob. For signal rules, that means removing access, signal or peer""" # XXX only remove one part, not all return 'signal,' - diff --git a/utils/apparmor/rule/variable.py b/utils/apparmor/rule/variable.py index a785e149f..90eee9ba1 100644 --- a/utils/apparmor/rule/variable.py +++ b/utils/apparmor/rule/variable.py @@ -13,29 +13,26 @@ # # ---------------------------------------------------------------------- -from apparmor.regex import RE_PROFILE_VARIABLE, strip_quotes -from apparmor.common import AppArmorBug, AppArmorException, type_is_str -from apparmor.rule import BaseRule, BaseRuleset, parse_comment, quote_if_needed - import re -# setup module translations +from apparmor.common import AppArmorBug, AppArmorException +from apparmor.regex import RE_PROFILE_VARIABLE, strip_quotes +from apparmor.rule import BaseRule, BaseRuleset, parse_comment, quote_if_needed from apparmor.translations import init_translation + _ = init_translation() class VariableRule(BaseRule): - '''Class to handle and store a single variable rule''' + """Class to handle and store a single variable rule""" rule_name = 'variable' def __init__(self, varname, mode, values, audit=False, deny=False, allow_keyword=False, comment='', log_event=None): - super(VariableRule, self).__init__(audit=audit, deny=deny, - allow_keyword=allow_keyword, - comment=comment, - log_event=log_event) + super().__init__(audit=audit, deny=deny, allow_keyword=allow_keyword, + comment=comment, log_event=log_event) # variables don't support audit or deny if audit: @@ -43,16 +40,16 @@ class VariableRule(BaseRule): if deny: raise AppArmorBug('Attempt to initialize %s with deny flag' % self.__class__.__name__) - if not type_is_str(varname): + if type(varname) is not str: raise AppArmorBug('Passed unknown type for varname to %s: %s' % (self.__class__.__name__, varname)) if not varname.startswith('@{'): raise AppArmorException("Passed invalid varname to %s (doesn't start with '@{'): %s" % (self.__class__.__name__, varname)) if not varname.endswith('}'): raise AppArmorException("Passed invalid varname to %s (doesn't end with '}'): %s" % (self.__class__.__name__, varname)) - if not type_is_str(mode): + if type(mode) is not str: raise AppArmorBug('Passed unknown type for variable assignment mode to %s: %s' % (self.__class__.__name__, mode)) - if mode not in ['=', '+=']: + if mode not in ('=', '+='): raise AppArmorBug('Passed unknown variable assignment mode to %s: %s' % (self.__class__.__name__, mode)) if type(values) is not set: @@ -70,7 +67,7 @@ class VariableRule(BaseRule): @classmethod def _parse(cls, raw_rule): - '''parse raw_rule and return VariableRule''' + """parse raw_rule and return VariableRule""" matches = cls._match(raw_rule) if not matches: @@ -83,10 +80,10 @@ class VariableRule(BaseRule): values = separate_vars(matches.group('values')) return VariableRule(varname, mode, values, - audit=False, deny=False, allow_keyword=False, comment=comment) + audit=False, deny=False, allow_keyword=False, comment=comment) def get_clean(self, depth=0): - '''return rule (in clean/default formatting)''' + """return rule (in clean/default formatting)""" space = ' ' * depth @@ -99,7 +96,7 @@ class VariableRule(BaseRule): return '%s%s %s %s' % (space, self.varname, self.mode, ' '.join(data)) def is_covered_localvars(self, other_rule): - '''check if other_rule is covered by this rule object''' + """check if other_rule is covered by this rule object""" if self.varname != other_rule.varname: return False @@ -114,7 +111,7 @@ class VariableRule(BaseRule): return True def is_equal_localvars(self, rule_obj, strict): - '''compare if rule-specific variables are equal''' + """compare if rule-specific variables are equal""" if not type(rule_obj) == VariableRule: raise AppArmorBug('Passed non-variable rule: %s' % str(rule_obj)) @@ -137,27 +134,30 @@ class VariableRule(BaseRule): _('Variable'), self.get_clean(), ] + class VariableRuleset(BaseRuleset): - '''Class to handle and store a collection of variable rules''' + """Class to handle and store a collection of variable rules""" def add(self, rule, cleanup=False): - ''' Add variable rule object + """Add variable rule object - If the variable name is already known, raise an exception because re-defining a variable isn't allowed. - ''' + If the variable name is already known, raise an exception because re-defining a variable isn't allowed. + """ if rule.mode == '=': for knownrule in self.rules: if rule.varname == knownrule.varname: - raise AppArmorException(_('Redefining existing variable %(variable)s: %(value)s') % { 'variable': rule.varname, 'value': rule.values }) + raise AppArmorException( + _('Redefining existing variable %(variable)s: %(value)s') + % {'variable': rule.varname, 'value': rule.values}) - super(VariableRuleset, self).add(rule, cleanup) + super().add(rule, cleanup) def get_merged_variables(self): - ''' Get merged variables of this VariableRuleset. + """Get merged variables of this VariableRuleset. - Note that no error checking is done because variables can be defined in one file and extended in another. - ''' + Note that no error checking is done because variables can be defined in one file and extended in another. + """ var_set = {} var_add = {} @@ -173,14 +173,15 @@ class VariableRuleset(BaseRuleset): return {'=': var_set, '+=': var_add} + def separate_vars(vs): """Returns a list of all the values for a variable""" data = set() vs = vs.strip() - RE_VARS = re.compile('^(("[^"]*")|([^"\s]+))\s*(.*)$') - while RE_VARS.search(vs): - matches = RE_VARS.search(vs).groups() + re_vars = re.compile(r'^(("[^"]*")|([^"\s]+))\s*(.*)$') + while re_vars.search(vs): + matches = re_vars.search(vs).groups() if matches[0].endswith(','): raise AppArmorException(_('Variable declarations do not accept trailing commas')) diff --git a/utils/apparmor/rules.py b/utils/apparmor/rules.py index fb158d025..b17a10321 100644 --- a/utils/apparmor/rules.py +++ b/utils/apparmor/rules.py @@ -8,7 +8,7 @@ # # ------------------------------------------------------------------ -class _Raw_Rule(object): +class _Raw_Rule: audit = False deny = False @@ -17,7 +17,7 @@ class _Raw_Rule(object): def serialize(self): return "%s%s%s" % ('audit ' if self.audit else '', - 'deny ' if self.deny else '', + 'deny ' if self.deny else '', self.rule) def recursive_print(self, depth): @@ -32,8 +32,10 @@ class _Raw_Rule(object): class Raw_Mount_Rule(_Raw_Rule): pass + class Raw_Pivot_Root_Rule(_Raw_Rule): pass + class Raw_Unix_Rule(_Raw_Rule): pass diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index 76acebb71..447cc8836 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -8,8 +8,6 @@ # # ------------------------------------------------------------------ -from apparmor.common import AppArmorException, debug, error, msg, cmd -import apparmor.easyprof import optparse import os import pwd @@ -17,30 +15,37 @@ import re import signal import socket import sys -import tempfile import time +from shutil import which +from tempfile import NamedTemporaryFile + +import apparmor.easyprof +from apparmor.common import AppArmorException, cmd, debug, error, msg + def check_requirements(binary): - '''Verify necessary software is installed''' - exes = ['xset', # for detecting free X display - 'aa-easyprof', # for templates - 'aa-exec', # for changing profile - 'sudo', # eventually get rid of this - 'pkexec', # eventually get rid of this - binary] + """Verify necessary software is installed""" + exes = [ + 'xset', # for detecting free X display + 'aa-easyprof', # for templates + 'aa-exec', # for changing profile + 'sudo', # eventually get rid of this + 'pkexec', # eventually get rid of this + binary + ] for e in exes: debug("Searching for '%s'" % e) - rc, report = cmd(['which', e]) - if rc != 0: + if which(e) is None: error("Could not find '%s'" % e, do_exit=False) return False return True + def parse_args(args=None, parser=None): - '''Parse arguments''' - if parser == None: + """Parse arguments""" + if parser is None: parser = optparse.OptionParser() parser.add_option('-X', '--with-x', @@ -81,8 +86,8 @@ def parse_args(args=None, parser=None): valid_xservers = ['xpra', 'xpra3d', 'xephyr'] if my_opt.withx and my_opt.xserver.lower() not in valid_xservers: - error("Invalid server '%s'. Use one of: %s" % (my_opt.xserver, \ - ", ".join(valid_xservers))) + error("Invalid server '%s'. Use one of: %s" + % (my_opt.xserver, ", ".join(valid_xservers))) if my_opt.withx: if my_opt.xephyr_geometry and my_opt.xserver.lower() != "xephyr": @@ -98,21 +103,21 @@ def parse_args(args=None, parser=None): return (my_opt, my_args) + def gen_policy_name(binary): - '''Generate a temporary policy based on the binary name''' - return "sandbox-%s%s" % (pwd.getpwuid(os.geteuid())[0], - re.sub(r'/', '_', binary)) + """Generate a temporary policy based on the binary name""" + return "sandbox-%s%s" % (pwd.getpwuid(os.geteuid())[0], re.sub(r'/', '_', binary)) + def set_environ(env): - keys = env.keys() - keys.sort() - for k in keys: - msg("Using: %s=%s" % (k, env[k])) - os.environ[k] = env[k] + for k, v in sorted(env.items()): + msg("Using: {}={}".format(k, v)) + os.environ[k] = v + -def aa_exec(command, opt, environ={}, verify_rules=[]): - '''Execute binary under specified policy''' - if opt.profile != None: +def aa_exec(command, opt, environ=None, verify_rules=()): + """Execute binary under specified policy""" + if opt.profile is not None: policy_name = opt.profile else: opt.ensure_value("template_var", None) @@ -128,25 +133,21 @@ def aa_exec(command, opt, environ={}, verify_rules=[]): policy = easyp.gen_policy(**params) debug("\n%s" % policy) - tmp = tempfile.NamedTemporaryFile(prefix = '%s-' % policy_name) - if sys.version_info[0] >= 3: - tmp.write(bytes(policy, 'utf-8')) - else: - tmp.write(policy) - tmp.flush() + with NamedTemporaryFile(prefix='%s-' % policy_name) as tmp: + tmp.write(policy.encode('utf-8')) - debug("using '%s' template" % opt.template) - # TODO: get rid of this - if opt.withx: - rc, report = cmd(['pkexec', 'apparmor_parser', '-r', '%s' % tmp.name]) - else: - rc, report = cmd(['sudo', 'apparmor_parser', '-r', tmp.name]) - if rc != 0: - raise AppArmorException("Could not load policy") + debug("using '%s' template" % opt.template) + # TODO: get rid of this + if opt.withx: + rc, report = cmd(('pkexec', 'apparmor_parser', '-r', '%s' % tmp.name)) + else: + rc, report = cmd(('sudo', 'apparmor_parser', '-r', tmp.name)) + if rc != 0: + raise AppArmorException("Could not load policy") - rc, report = cmd(['sudo', 'apparmor_parser', '-p', tmp.name]) - if rc != 0: - raise AppArmorException("Could not dump policy") + rc, report = cmd(('sudo', 'apparmor_parser', '-p', tmp.name)) + if rc != 0: + raise AppArmorException("Could not dump policy") # Make sure the dynamic profile has the appropriate line for X for r in verify_rules: @@ -159,34 +160,35 @@ def aa_exec(command, opt, environ={}, verify_rules=[]): if not found: raise AppArmorException("Could not find required rule: %s" % r) - set_environ(environ) - args = ['aa-exec', '-p', policy_name, '--'] + command + if environ: + set_environ(environ) + args = ['aa-exec', '-p', policy_name, '--'] + args.extend(command) rc, report = cmd(args) return rc, report + def run_sandbox(command, opt): - '''Run application''' + """Run application""" # aa-exec rc, report = aa_exec(command, opt) return rc, report + class SandboxXserver(): - def __init__(self, title, geometry=None, - driver=None, - xauth=None, - clipboard=False): + def __init__(self, title, geometry=None, driver=None, xauth=None, clipboard=False): self.geometry = geometry self.title = title self.pids = [] self.driver = driver self.clipboard = clipboard self.tempfiles = [] - self.timeout = 5 # used by xauth and for server starts + self.timeout = 5 # used by xauth and for server starts # preserve our environment self.old_environ = dict() - for env in ['DISPLAY', 'XAUTHORITY', 'UBUNTU_MENUPROXY', - 'QT_X11_NO_NATIVE_MENUBAR', 'LIBOVERLAY_SCROLLBAR']: + for env in ('DISPLAY', 'XAUTHORITY', 'UBUNTU_MENUPROXY', + 'QT_X11_NO_NATIVE_MENUBAR', 'LIBOVERLAY_SCROLLBAR'): if env in os.environ: self.old_environ[env] = os.environ[env] @@ -207,7 +209,7 @@ class SandboxXserver(): self.new_environ["LIBOVERLAY_SCROLLBAR"] = "0" def cleanup(self): - '''Cleanup our forked pids, reset the environment, etc''' + """Cleanup our forked pids, reset the environment, etc""" self.pids.reverse() debug(self.pids) for pid in self.pids: @@ -231,7 +233,7 @@ class SandboxXserver(): set_environ(self.old_environ) def find_free_x_display(self): - '''Find a free X display''' + """Find a free X display""" old_lang = None if 'LANG' in os.environ: old_lang = os.environ['LANG'] @@ -239,11 +241,10 @@ class SandboxXserver(): display = "" current = self.old_environ["DISPLAY"] - for i in range(1,257): # TODO: this puts an artificial limit of 256 - # sandboxed applications + for i in range(1, 257): # TODO: this puts an artificial limit of 256 sandboxed applications tmp = ":%d" % i os.environ["DISPLAY"] = tmp - rc, report = cmd(['xset', '-q']) + rc, report = cmd(('xset', '-q')) if rc != 0 and 'Invalid MIT-MAGIC-COOKIE-1' not in report: display = tmp break @@ -252,11 +253,11 @@ class SandboxXserver(): os.environ['LANG'] = old_lang os.environ["DISPLAY"] = current - if display == "": + if not display: raise AppArmorException("Could not find available X display") # Use dedicated .Xauthority file - xauth = os.path.join(os.path.expanduser('~'), \ + xauth = os.path.join(os.path.expanduser('~'), '.Xauthority-sandbox%s' % display.split(':')[1]) return display, xauth @@ -265,13 +266,13 @@ class SandboxXserver(): return "(Sandbox%s) %s" % (self.display, self.title) def verify_host_setup(self): - '''Make sure we have everything we need''' + """Make sure we have everything we need""" old_lang = None if 'LANG' in os.environ: old_lang = os.environ['LANG'] os.environ['LANG'] = 'C' - rc, report = cmd(['xhost']) + rc, report = cmd(('xhost',)) if old_lang: os.environ['LANG'] = old_lang @@ -285,59 +286,63 @@ class SandboxXserver(): raise AppArmorException("Access control allows '%s' full access. Please see 'man aa-sandbox' for details" % username) def start(self): - '''Start a nested X server (need to override)''' + """Start a nested X server (need to override)""" # clean up the old one if os.path.exists(self.xauth): os.unlink(self.xauth) - rc, cookie = cmd(['mcookie']) + rc, cookie = cmd(('mcookie',)) if rc != 0: raise AppArmorException("Could not generate magic cookie") - rc, out = cmd(['xauth', '-f', self.xauth, \ - 'add', \ - self.display, \ - 'MIT-MAGIC-COOKIE-1', \ - cookie.strip()]) + rc, out = cmd( + ('xauth', '-f', self.xauth, + 'add', + self.display, + 'MIT-MAGIC-COOKIE-1', + cookie.strip()) + ) if rc != 0: raise AppArmorException("Could not generate '%s'" % self.display) class SandboxXephyr(SandboxXserver): def start(self): - for e in ['Xephyr', 'matchbox-window-manager']: + for e in ('Xephyr', 'matchbox-window-manager'): debug("Searching for '%s'" % e) - rc, report = cmd(['which', e]) - if rc != 0: + if which(e) is None: raise AppArmorException("Could not find '%s'" % e) - '''Run any setup code''' + # Run any setup code SandboxXserver.start(self) - '''Start a Xephyr server''' + # Start a Xephyr server listener_x = os.fork() if listener_x == 0: # TODO: break into config file? Which are needed? - x_exts = ['-extension', 'GLX', - '-extension', 'MIT-SHM', - '-extension', 'RENDER', - '-extension', 'SECURITY', - '-extension', 'DAMAGE' - ] + x_exts = [ + '-extension', 'GLX', + '-extension', 'MIT-SHM', + '-extension', 'RENDER', + '-extension', 'SECURITY', + '-extension', 'DAMAGE' + ] # verify_these - x_extra_args = ['-host-cursor', # less secure? - '-fakexa', # for games? seems not needed - '-nodri', # more secure? - ] + x_extra_args = [ + '-host-cursor', # less secure? + '-fakexa', # for games? seems not needed + '-nodri', # more secure? + ] if not self.geometry: self.geometry = "640x480" - x_args = ['-nolisten', 'tcp', - '-screen', self.geometry, - '-br', # black background - '-reset', # reset after last client exists - '-terminate', # terminate at server reset - '-title', self.generate_title(), - ] + x_exts + x_extra_args + x_args = [ + '-nolisten', 'tcp', + '-screen', self.geometry, + '-br', # black background + '-reset', # reset after last client exists + '-terminate', # terminate at server reset + '-title', self.generate_title(), + ] + x_exts + x_extra_args args = ['/usr/bin/Xephyr'] + x_args + [self.display] debug(" ".join(args)) @@ -345,7 +350,7 @@ class SandboxXephyr(SandboxXserver): sys.exit(0) self.pids.append(listener_x) - time.sleep(1) # FIXME: detect if running + time.sleep(1) # FIXME: detect if running # Next, start the window manager sys.stdout.flush() @@ -361,7 +366,7 @@ class SandboxXephyr(SandboxXserver): sys.exit(0) self.pids.append(listener_wm) - time.sleep(1) # FIXME: detect if running + time.sleep(1) # FIXME: detect if running class SandboxXpra(SandboxXserver): @@ -377,7 +382,7 @@ class SandboxXpra(SandboxXserver): # Annoyingly, xpra doesn't clean up itself well if the application # failed for some reason. Try to account for that. - rc, report = cmd(['ps', 'auxww']) + rc, report = cmd(('ps', 'auxww')) for line in report.splitlines(): if '-for-Xpra-%s' % self.display in line: self.pids.append(int(line.split()[1])) @@ -385,7 +390,7 @@ class SandboxXpra(SandboxXserver): SandboxXserver.cleanup(self) def _get_xvfb_args(self): - '''Setup xvfb arguments''' + """Setup xvfb arguments""" # Debugging tip (can also use glxinfo): # $ xdpyinfo > /tmp/native # $ aa-sandbox -X -t sandbox-x /usr/bin/xdpyinfo > /tmp/nested @@ -393,7 +398,7 @@ class SandboxXpra(SandboxXserver): xvfb_args = [] - if self.driver == None: + if self.driver is None: # The default from the man page, but be explicit in what we enable xvfb_args.append('--xvfb=Xvfb') xvfb_args.append('-screen 0 3840x2560x24+32') @@ -541,25 +546,20 @@ Section "ServerLayout" InputDevice "NoKeyboard" EndSection ''' - - tmp, xorg_conf = tempfile.mkstemp(prefix='aa-sandbox-xorg.conf-') - self.tempfiles.append(xorg_conf) - if sys.version_info[0] >= 3: - os.write(tmp, bytes(conf, 'utf-8')) - else: - os.write(tmp, conf) - os.close(tmp) + with NamedTemporaryFile('wb', prefix='aa-sandbox-xorg.conf-', delete=False) as tmp: + self.tempfiles.append(tmp.name) + tmp.write(conf.encode('utf-8')) xvfb_args.append('--xvfb=Xorg') - xvfb_args.append('-dpi 96') # https://www.xpra.org/trac/ticket/163 + xvfb_args.append('-dpi 96') # https://www.xpra.org/trac/ticket/163 xvfb_args.append('-nolisten tcp') xvfb_args.append('-noreset') xvfb_args.append('-logfile %s' % os.path.expanduser('~/.xpra/%s.log' % self.display)) xvfb_args.append('-auth %s' % self.new_environ['XAUTHORITY']) - xvfb_args.append('-config %s' % xorg_conf) - extensions = ['Composite', 'GLX', 'RANDR', 'RENDER', 'SECURITY'] - for i in extensions: - xvfb_args.append('+extension %s' % i) + xvfb_args.append('-config %s' % tmp.name) + xvfb_args.extend( + '+extension %s' % i for i in ('Composite', 'GLX', 'RANDR', 'RENDER', 'SECURITY') + ) else: raise AppArmorException("Unsupported X driver '%s'" % self.driver) @@ -567,8 +567,7 @@ EndSection def start(self): debug("Searching for '%s'" % 'xpra') - rc, report = cmd(['which', 'xpra']) - if rc != 0: + if which('xpra') is None: raise AppArmorException("Could not find '%s'" % 'xpra') if self.driver == "xdummy": @@ -578,7 +577,7 @@ EndSection if not os.path.exists(drv): raise AppArmorException("Could not find '%s'" % drv) - '''Run any setup code''' + # Run any setup code SandboxXserver.start(self) xvfb_args = self._get_xvfb_args() @@ -587,10 +586,10 @@ EndSection os.environ['XAUTHORITY'] = self.xauth # This will clean out any dead sessions - cmd(['xpra', 'list']) + cmd(('xpra', 'list')) x_args = ['--no-daemon', - #'--no-mmap', # for security? + # '--no-mmap', # for security? '--no-pulseaudio'] if not self.clipboard: x_args.append('--no-clipboard') @@ -608,9 +607,9 @@ EndSection started = False # We need to wait for the xpra socket to exist before attaching - fn = os.path.join(os.environ['HOME'], '.xpra', '%s-%s' % \ - (socket.gethostname(), self.display.split(':')[1])) - for i in range(self.timeout * 2): # up to self.timeout seconds to start + fn = os.path.join(os.environ['HOME'], '.xpra', '%s-%s' + % (socket.gethostname(), self.display.split(':')[1])) + for i in range(self.timeout * 2): # up to self.timeout seconds to start if os.path.exists(fn): debug("Found '%s'! Proceeding to attach" % fn) break @@ -622,8 +621,8 @@ EndSection self.cleanup() raise AppArmorException("Could not start xpra (try again with -d)") - for i in range(self.timeout): # Up to self.timeout seconds to start - rc, out = cmd(['xpra', 'list']) + for i in range(self.timeout): # Up to self.timeout seconds to start + rc, out = cmd(('xpra', 'list')) if 'DEAD session at %s' % self.display in out: error("xpra session at '%s' died" % self.display, do_exit=False) @@ -649,7 +648,7 @@ EndSection if listener_attach == 0: args = ['/usr/bin/xpra', 'attach', self.display, '--title=%s' % self.generate_title(), - #'--no-mmap', # for security? + # '--no-mmap', # for security? '--no-tray', '--no-pulseaudio'] if not self.clipboard: @@ -663,9 +662,9 @@ EndSection self.pids.append(listener_attach) # Make sure that a client has attached - for i in range(self.timeout): # up to self.timeout seconds to attach + for i in range(self.timeout): # up to self.timeout seconds to attach time.sleep(1) - rc, out = cmd (['xpra', 'info', self.display]) + rc, out = cmd(('xpra', 'info', self.display)) search = 'clients=1' if search in out: debug("Client successfully attached!") @@ -677,22 +676,19 @@ EndSection def run_xsandbox(command, opt): - '''Run X application in a sandbox''' + """Run X application in a sandbox""" old_cwd = os.getcwd() # first, start X if opt.xserver.lower() == "xephyr": - x = SandboxXephyr(command[0], geometry=opt.xephyr_geometry, - xauth=opt.xauthority) + x = SandboxXephyr(command[0], geometry=opt.xephyr_geometry, xauth=opt.xauthority) elif opt.xserver.lower() == "xpra3d": - x = SandboxXpra(command[0], geometry=None, - driver="xdummy", - xauth=opt.xauthority, - clipboard=opt.with_clipboard) + x = SandboxXpra( + command[0], geometry=None, driver="xdummy", xauth=opt.xauthority, + clipboard=opt.with_clipboard) else: - x = SandboxXpra(command[0], geometry=None, - xauth=opt.xauthority, - clipboard=opt.with_clipboard) + x = SandboxXpra( + command[0], geometry=None, xauth=opt.xauthority, clipboard=opt.with_clipboard) x.verify_host_setup() @@ -700,7 +696,7 @@ def run_xsandbox(command, opt): keys = x.old_environ.keys() keys.sort() for k in keys: - debug ("Old: %s=%s" % (k, x.old_environ[k])) + debug("Old: %s=%s" % (k, x.old_environ[k])) try: x.start() diff --git a/utils/apparmor/severity.py b/utils/apparmor/severity.py index eed43116e..a1b671d69 100644 --- a/utils/apparmor/severity.py +++ b/utils/apparmor/severity.py @@ -11,11 +11,12 @@ # GNU General Public License for more details. # # ---------------------------------------------------------------------- -from __future__ import with_statement import re -from apparmor.common import AppArmorException, open_file_read, warn, convert_regexp # , msg, error, debug -class Severity(object): +from apparmor.common import AppArmorException, convert_regexp, open_file_read, warn # , debug, error, msg + + +class Severity: def __init__(self, dbname=None, default_rank=10): """Initialises the class object""" self.PROF_DIR = '/etc/apparmor.d' # The profile directory @@ -34,24 +35,28 @@ class Severity(object): with open_file_read(dbname) as database: # open(dbname, 'r') for lineno, line in enumerate(database, start=1): line = line.strip() # or only rstrip and lstrip? - if line == '' or line.startswith('#'): + if not line or line.startswith('#'): continue if line.startswith('/'): try: path, read, write, execute = line.split() read, write, execute = int(read), int(write), int(execute) except ValueError: - raise AppArmorException("Insufficient values for permissions in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line)) + raise AppArmorException( + "Insufficient values for permissions in file: %s\n\t[Line %s]: %s" + % (dbname, lineno, line)) else: if read not in range(0, 11) or write not in range(0, 11) or execute not in range(0, 11): - raise AppArmorException("Inappropriate values for permissions in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line)) + raise AppArmorException( + "Inappropriate values for permissions in file: %s\n\t[Line %s]: %s" + % (dbname, lineno, line)) path = path.lstrip('/') if '*' not in path: self.severity['FILES'][path] = {'r': read, 'w': write, 'x': execute} else: ptr = self.severity['REGEXPS'] pieces = path.split('/') - for index, piece in enumerate(pieces): + for index, piece in enumerate(pieces): # pragma: no branch if '*' in piece: path = '/'.join(pieces[index:]) regexp = convert_regexp(path) @@ -66,11 +71,13 @@ class Severity(object): severity = int(severity) except ValueError: error_message = 'No severity value present in file: %s\n\t[Line %s]: %s' % (dbname, lineno, line) - #error(error_message) + # error(error_message) raise AppArmorException(error_message) # from None else: if severity not in range(0, 11): - raise AppArmorException("Inappropriate severity value present in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line)) + raise AppArmorException( + "Inappropriate severity value present in file: %s\n\t[Line %s]: %s" + % (dbname, lineno, line)) self.severity['CAPABILITIES'][resource] = severity else: raise AppArmorException("Unexpected line in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line)) @@ -90,14 +97,14 @@ class Severity(object): """Returns the rank for the given path""" if '@' in path: # path contains variable return self.handle_variable_rank(path, mode) - elif path[0] == '/': # file resource + elif path.startswith('/'): # file resource return self.handle_file(path, mode) else: raise AppArmorException("Unexpected path input: %s" % path) def check_subtree(self, tree, mode, sev, segments): """Returns the max severity from the regex tree""" - if len(segments) == 0: + if not segments: first = '' else: first = segments[0] @@ -114,7 +121,7 @@ class Severity(object): # Match rest of the path if re.search("^" + chunk, path): # Find max rank - if "AA_RANK" in tree[chunk].keys(): + if "AA_RANK" in tree[chunk].keys(): # pragma: no branch for m in mode: if sev is None or tree[chunk]["AA_RANK"].get(m, -1) > sev: sev = tree[chunk]["AA_RANK"].get(m, None) @@ -147,7 +154,7 @@ class Severity(object): if matches: rank = self.severity['DEFAULT_RANK'] variable = '@{%s}' % matches.groups()[0] - #variables = regex_variable.findall(resource) + # variables = regex_variable.findall(resource) for replacement in self.severity['VARIABLES'][variable]: resource_replaced = self.variable_replace(variable, replacement, resource) rank_new = self.handle_variable_rank(resource_replaced, mode) @@ -175,5 +182,5 @@ class Severity(object): return resource.replace(variable, replacement) def set_variables(self, vars): - ''' Set the profile variables to use for rating the severity ''' + """Set the profile variables to use for rating the severity""" self.severity['VARIABLES'] = vars diff --git a/utils/apparmor/tools.py b/utils/apparmor/tools.py index 639df807c..d4cd0b3b3 100644 --- a/utils/apparmor/tools.py +++ b/utils/apparmor/tools.py @@ -1,6 +1,6 @@ # ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.com> -# Copyright (C) 2015-2018 Christian Boltz <apparmor@cboltz.de> +# Copyright (C) 2015-2022 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 @@ -14,26 +14,29 @@ # ---------------------------------------------------------------------- import os import sys +from shutil import which import apparmor.aa as apparmor import apparmor.ui as aaui -from apparmor.common import user_perm, cmd - -# setup module translations +from apparmor.common import AppArmorException, cmd, is_skippable_file, user_perm from apparmor.translations import init_translation + _ = init_translation() + class aa_tools: def __init__(self, tool_name, args): - apparmor.init_aa(profiledir=args.dir) + apparmor.init_aa(profiledir=args.dir, confdir=args.configdir) + + if not user_perm(apparmor.profile_dir): + raise AppArmorException("Cannot write to profile directory: %s" % (apparmor.profile_dir)) self.name = tool_name self.profiling = args.program - self.check_profile_dir() self.silent = None self.do_reload = args.do_reload - if tool_name in ['audit']: + if tool_name == 'audit': self.remove = args.remove elif tool_name == 'autodep': self.force = args.force @@ -41,12 +44,8 @@ class aa_tools: elif tool_name == 'cleanprof': self.silent = args.silent - def check_profile_dir(self): - if not user_perm(apparmor.profile_dir): - raise apparmor.AppArmorException("Cannot write to profile directory: %s" % (apparmor.profile_dir)) - def get_next_to_profile(self): - '''Iterator function to walk the list of arguments passed''' + """Iterator function to walk the list of arguments passed""" for p in self.profiling: if not p: @@ -61,18 +60,31 @@ class aa_tools: profile = fq_path else: program = fq_path - profile = apparmor.get_profile_filename_from_attachment(fq_path, True) + if self.name == 'cleanprof': + profile = apparmor.active_profiles.profile_from_attachment(fq_path) + else: + 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_from_attachment(program, True) + which_ = which(p) + if self.name == 'cleanprof' and p in apparmor.aa: + program = p # not really correct, but works + profile = p + elif which_ is not None: + program = apparmor.get_full_path(which_) + if self.name == 'cleanprof': + profile = program + else: + 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() + if self.name == 'cleanprof': + profile = p + else: + profile = apparmor.get_full_path(os.path.join(apparmor.profile_dir, p)).strip() else: if '/' not in p: - aaui.UI_Info(_("Can't find %(program)s in the system path list. If the name of the application\nis correct, please run 'which %(program)s' as a user with correct PATH\nenvironment set up in order to find the fully-qualified path and\nuse the full path as parameter.") % { 'program': p }) + aaui.UI_Info(_("Can't find %(program)s in the system path list. If the name of the application\nis correct, please run 'which %(program)s' as a user with correct PATH\nenvironment set up in order to find the fully-qualified path and\nuse the full path as parameter.") + % {'program': p}) else: aaui.UI_Info(_("%s does not exist, please double-check the path.") % p) continue @@ -87,19 +99,20 @@ class aa_tools: if program is None: program = profile - if not program or not(os.path.exists(program) or apparmor.profile_exists(program)): + if not program or not(os.path.exists(program) or profile in apparmor.aa): if program and not program.startswith('/'): program = aaui.UI_GetString(_('The given program cannot be found, please try with the fully qualified path name of the program: '), '') else: aaui.UI_Info(_("%s does not exist, please double-check the path.") % program) sys.exit(1) - if program and apparmor.profile_exists(program): - self.clean_profile(program) + if program and profile in apparmor.aa: + self.clean_profile(program, profile) else: if '/' not in program: - aaui.UI_Info(_("Can't find %(program)s in the system path list. If the name of the application\nis correct, please run 'which %(program)s' as a user with correct PATH\nenvironment set up in order to find the fully-qualified path and\nuse the full path as parameter.") % { 'program': program }) + aaui.UI_Info(_("Can't find %(program)s in the system path list. If the name of the application\nis correct, please run 'which %(program)s' as a user with correct PATH\nenvironment set up in order to find the fully-qualified path and\nuse the full path as parameter.") + % {'program': program}) else: aaui.UI_Info(_("%s does not exist, please double-check the path.") % program) sys.exit(1) @@ -111,7 +124,7 @@ class aa_tools: output_name = profile if program is None else program - if not os.path.isfile(profile) or apparmor.is_skippable_file(profile): + if not os.path.isfile(profile) or is_skippable_file(profile): aaui.UI_Info(_('Profile for %s not found, skipping') % output_name) continue @@ -127,7 +140,7 @@ class aa_tools: output_name = profile if program is None else program - if not os.path.isfile(profile) or apparmor.is_skippable_file(profile): + if not os.path.isfile(profile) or is_skippable_file(profile): aaui.UI_Info(_('Profile for %s not found, skipping') % output_name) continue @@ -142,7 +155,7 @@ class aa_tools: output_name = profile if program is None else program - if not os.path.isfile(profile) or apparmor.is_skippable_file(profile): + if not os.path.isfile(profile) or is_skippable_file(profile): aaui.UI_Info(_('Profile for %s not found, skipping') % output_name) continue @@ -157,7 +170,7 @@ class aa_tools: output_name = profile if program is None else program - if not os.path.isfile(profile) or apparmor.is_skippable_file(profile): + if not os.path.isfile(profile) or is_skippable_file(profile): aaui.UI_Info(_('Profile for %s not found, skipping') % output_name) continue @@ -193,20 +206,20 @@ class aa_tools: if self.aa_mountpoint: apparmor.reload(program) - def clean_profile(self, program): - filename = apparmor.get_profile_filename_from_attachment(program, True) + def clean_profile(self, program, profile): + filename = apparmor.get_profile_filename_from_profile_name(profile) import apparmor.cleanprofile as cleanprofile prof = cleanprofile.Prof(filename) cleanprof = cleanprofile.CleanProf(True, prof, prof) - deleted = cleanprof.remove_duplicate_rules(program) + deleted = cleanprof.remove_duplicate_rules(profile) aaui.UI_Info(_("\nDeleted %s rules.") % deleted) - apparmor.changed[program] = True + apparmor.changed[profile] = True if filename: if not self.silent: q = aaui.PromptQuestion() q.title = 'Changed Local Profiles' - q.explanation = _('The local profile for %(program)s in file %(file)s was changed. Would you like to save it?') % { 'program': program, 'file': filename } + q.explanation = _('The local profile for %(program)s in file %(file)s was changed. Would you like to save it?') % {'program': program, 'file': filename} q.functions = ['CMD_SAVE_CHANGES', 'CMD_VIEW_CHANGES', 'CMD_ABORT'] q.default = 'CMD_VIEW_CHANGES' q.options = [] @@ -216,17 +229,17 @@ class aa_tools: while ans != 'CMD_SAVE_CHANGES': ans, arg = q.promptUser() if ans == 'CMD_SAVE_CHANGES': - apparmor.write_profile_ui_feedback(program, True) + apparmor.write_profile_ui_feedback(profile) 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, {'is_attachment': True}) + # oldprofile = apparmor.serialize_profile(apparmor.split_to_merged(apparmor.original_aa), profile, {}) + newprofile = apparmor.serialize_profile(apparmor.split_to_merged(apparmor.aa), profile, {}) # , {'is_attachment': True}) aaui.UI_Changes(filename, newprofile, comments=True) else: - apparmor.write_profile_ui_feedback(program, True) + apparmor.write_profile_ui_feedback(profile, True) self.reload_profile(filename) else: - raise apparmor.AppArmorException(_('The profile for %s does not exists. Nothing to clean.') % program) + raise AppArmorException(_('The profile for %s does not exists. Nothing to clean.') % program) def enable_profile(self, filename): apparmor.delete_symlink('disable', filename) @@ -242,13 +255,10 @@ class aa_tools: cmd_info = cmd([apparmor.parser, '-I%s' % apparmor.profile_dir, '--base', apparmor.profile_dir, '-R', profile]) if cmd_info[0] != 0: - raise apparmor.AppArmorException(cmd_info[1]) + raise AppArmorException(cmd_info[1]) def reload_profile(self, profile): if not self.do_reload: return - cmd_info = cmd([apparmor.parser, '-I%s' % apparmor.profile_dir, '--base', apparmor.profile_dir, '-r', profile]) - - if cmd_info[0] != 0: - raise apparmor.AppArmorException(cmd_info[1]) + apparmor.reload_profile(profile, raise_exc=True) diff --git a/utils/apparmor/translations.py b/utils/apparmor/translations.py index fb6d5c40d..1e29b7d7c 100644 --- a/utils/apparmor/translations.py +++ b/utils/apparmor/translations.py @@ -7,11 +7,12 @@ # License published by the Free Software Foundation. # # ------------------------------------------------------------------ +__apparmor_gettext__ = None + import gettext TRANSLATION_DOMAIN = 'apparmor-utils' -__apparmor_gettext__ = None def init_translation(): global __apparmor_gettext__ diff --git a/utils/apparmor/ui.py b/utils/apparmor/ui.py index a3a911236..7c3e73ac0 100644 --- a/utils/apparmor/ui.py +++ b/utils/apparmor/ui.py @@ -15,26 +15,21 @@ # ---------------------------------------------------------------------- import json -import sys +import os import re import readline -import os -import tempfile import subprocess +import sys +from tempfile import NamedTemporaryFile -from apparmor.common import readkey, AppArmorException, DebugLogger - -# setup module translations +from apparmor.common import AppArmorException, DebugLogger, readkey from apparmor.translations import init_translation + _ = init_translation() # Set up UI logger for separate messages from UI module debug_logger = DebugLogger('UI') -# If Python3, wrap input in raw_input so make check passes -if 'raw_input' not in dir(__builtins__): - raw_input = input - ARROWS = {'A': 'UP', 'B': 'DOWN', 'C': 'RIGHT', 'D': 'LEFT'} UI_mode = 'text' @@ -67,10 +62,11 @@ def set_text_mode(): global UI_mode UI_mode = 'text' + # reads the response on command line for json and verifies the response # for the dialog type def json_response(dialog_type): - string = raw_input('\n') + string = input('\n') rh = json.loads(string.strip()) if rh["dialog"] != dialog_type: raise AppArmorException('Expected response %s got %s.' % (dialog_type, string)) @@ -112,8 +108,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): - return re.search('\((\S+)\)', translated).groups()[0] + if re.search(r'\((\S+)\)', translated): + return re.search(r'\((\S+)\)', translated).groups()[0] else: if cmsg: raise AppArmorException(cmsg) @@ -129,7 +125,7 @@ def UI_YesNo(text, default): yeskey = get_translated_hotkey(yes).lower() nokey = get_translated_hotkey(no).lower() ans = 'XXXINVALIDXXX' - while ans not in ['y', 'n']: + while ans not in ('y', 'n'): if UI_mode == 'json': jsonout = {'dialog': 'yesno', 'text': text, 'default': default} write_json(jsonout) @@ -173,7 +169,7 @@ def UI_YesNoCancel(text, default): cancelkey = get_translated_hotkey(cancel).lower() ans = 'XXXINVALIDXXX' - while ans not in ['c', 'n', 'y']: + while ans not in ('c', 'n', 'y'): if UI_mode == 'json': jsonout = {'dialog': 'yesnocancel', 'text': text, 'default': default} write_json(jsonout) @@ -222,7 +218,7 @@ def UI_GetString(text, default): else: # text mode readline.set_startup_hook(lambda: readline.insert_text(default)) try: - string = raw_input('\n' + text) + string = input('\n' + text) except EOFError: string = '' finally: @@ -253,34 +249,29 @@ def UI_BusyStop(): def diff(oldprofile, newprofile): - difftemp = tempfile.NamedTemporaryFile('w') + difftemp = NamedTemporaryFile('w') subprocess.call('diff -u -p %s %s > %s' % (oldprofile, newprofile, difftemp.name), shell=True) return difftemp def write_profile_to_tempfile(profile): - temp = tempfile.NamedTemporaryFile('w') + temp = NamedTemporaryFile('w') temp.write(profile) temp.flush() return temp def generate_diff(oldprofile, newprofile): - oldtemp = write_profile_to_tempfile(oldprofile) - newtemp = write_profile_to_tempfile(newprofile) - difftemp = diff(oldtemp.name, newtemp.name) - oldtemp.close() - newtemp.close() - return difftemp + with write_profile_to_tempfile(oldprofile) as oldtemp, \ + write_profile_to_tempfile(newprofile) as newtemp: + return diff(oldtemp.name, newtemp.name) def generate_diff_with_comments(oldprofile, newprofile): if not os.path.exists(oldprofile): raise AppArmorException(_("Can't find existing profile %s to compare changes.") % oldprofile) - newtemp = write_profile_to_tempfile(newprofile) - difftemp = diff(oldprofile, newtemp.name) - newtemp.close() - return difftemp + with write_profile_to_tempfile(newprofile) as newtemp: + return diff(oldprofile, newtemp.name) def UI_Changes(oldprofile, newprofile, comments=False): @@ -290,8 +281,9 @@ def UI_Changes(oldprofile, newprofile, comments=False): else: difftemp = generate_diff_with_comments(oldprofile, newprofile) header = 'View Changes with comments' - UI_ShowFile(header, difftemp.name) - difftemp.close() + with difftemp: + UI_ShowFile(header, difftemp.name) + def UI_ShowFile(header, filename): if UI_mode == 'json': @@ -307,7 +299,7 @@ CMDS = {'CMD_ALLOW': _('(A)llow'), 'CMD_AUDIT_NEW': _('Audi(t)'), 'CMD_AUDIT_OFF': _('Audi(t) off'), 'CMD_AUDIT_FULL': _('Audit (A)ll'), - #'CMD_OTHER': '(O)pts', + # 'CMD_OTHER': '(O)pts', 'CMD_USER_ON': _('(O)wner permissions on'), 'CMD_USER_OFF': _('(O)wner permissions off'), 'CMD_DENY': _('(D)eny'), @@ -362,7 +354,7 @@ CMDS = {'CMD_ALLOW': _('(A)llow'), } -class PromptQuestion(object): +class PromptQuestion: title = None headers = None explanation = None @@ -373,8 +365,8 @@ class PromptQuestion(object): helptext = None def __init__(self): - self.headers = list() - self.functions = list() + self.headers = [] + self.functions = [] self.selected = 0 def promptUser(self, params=''): @@ -400,7 +392,7 @@ class PromptQuestion(object): if helptext: functions.append('CMD_HELP') - menu_items = list() + menu_items = [] keys = dict() for cmd in functions: @@ -412,7 +404,9 @@ class PromptQuestion(object): key = get_translated_hotkey(menutext).lower() # Duplicate hotkey if keys.get(key, False): - raise AppArmorException(_('PromptUser: Duplicate hotkey for %(command)s: %(menutext)s ') % { 'command': cmd, 'menutext': menutext }) + raise AppArmorException( + _('PromptUser: Duplicate hotkey for %(command)s: %(menutext)s ') + % {'command': cmd, 'menutext': menutext}) keys[key] = cmd @@ -445,7 +439,7 @@ class PromptQuestion(object): function_regexp = '^(' function_regexp += '|'.join(keys.keys()) if options: - function_regexp += '|\d' + function_regexp += r'|\d' function_regexp += ')$' ans = 'XXXINVALIDXXX' @@ -517,7 +511,7 @@ class PromptQuestion(object): # If they hit return choose default option ans = default_key - elif options and re.search('^\d$', ans): + elif options and re.search(r'^\d$', ans): ans = int(ans) if ans > 0 and ans <= len(options): selected = ans - 1 diff --git a/utils/logprof.conf b/utils/logprof.conf index 88e2209b1..fd804c0ab 100644 --- a/utils/logprof.conf +++ b/utils/logprof.conf @@ -60,7 +60,7 @@ /usr/lib/YaST2/servers_non_y2/ag_genprof = u /usr/lib/YaST2/servers_non_y2/ag_logprof = u - # these ones shouln't have their own profiles + # these ones shouldn't have their own profiles /bin/awk = icn /usr/bin/awk = icn /bin/cat = icn diff --git a/utils/logprof.conf.5 b/utils/logprof.conf.5 index 624a3ac74..ab60167ef 100644 --- a/utils/logprof.conf.5 +++ b/utils/logprof.conf.5 @@ -133,7 +133,7 @@ .\" ======================================================================== .\" .IX Title "LOGPROF.CONF 5" -.TH LOGPROF.CONF 5 "2022-11-22" "AppArmor 3.0.8" "AppArmor" +.TH LOGPROF.CONF 5 "2024-02-02" "AppArmor 3.1.7" "AppArmor" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l diff --git a/utils/python-tools-setup.py b/utils/python-tools-setup.py index 2cbde8fc1..8efe6ce9a 100644 --- a/utils/python-tools-setup.py +++ b/utils/python-tools-setup.py @@ -20,35 +20,37 @@ # Note: --version=... must be the last argument to this script # -from setuptools.command.install import install as _install -from setuptools import setup import os import shutil import sys -class Install(_install, object): - '''Override setuptools to install the files where we want them.''' +from setuptools import setup +from setuptools.command.install import install as _install + + +class Install(_install): + """Override setuptools to install the files where we want them.""" def run(self): # Now byte-compile everything - super(Install, self).run() + super().run() prefix = self.prefix - if self.root != None: + if self.root is not None: prefix = self.root # Install scripts, configuration files and data - scripts = ['/usr/bin/aa-easyprof'] + scripts = ('/usr/bin/aa-easyprof',) self.mkpath(prefix + os.path.dirname(scripts[0])) for s in scripts: f = prefix + s self.copy_file(os.path.basename(s), f) - configs = ['easyprof/easyprof.conf'] + configs = ('easyprof/easyprof.conf',) self.mkpath(prefix + "/etc/apparmor") for c in configs: self.copy_file(c, os.path.join(prefix + "/etc/apparmor", os.path.basename(c))) - data = ['easyprof/templates', 'easyprof/policygroups'] + data = ('easyprof/templates', 'easyprof/policygroups') self.mkpath(prefix + "/usr/share/apparmor/easyprof") for d in data: self.copy_tree(d, os.path.join(prefix + "/usr/share/apparmor/easyprof", os.path.basename(d))) @@ -61,21 +63,22 @@ shutil.copytree('apparmor', 'staging') # Support the --version=... since this will be part of a Makefile version = "unknown-version" if "--version=" in sys.argv[-1]: - version=sys.argv[-1].split('=')[1] + version = sys.argv[-1].split('=')[1] sys.argv = sys.argv[0:-1] -setup (name='apparmor', - version=version, - description='Python libraries for AppArmor utilities', - long_description='Python libraries for AppArmor utilities', - author='AppArmor Developers', - author_email='apparmor@lists.ubuntu.com', - url='https://gitlab.com/apparmor/apparmor', - license='GPL-2', - cmdclass={'install': Install}, - package_dir={'apparmor': 'staging'}, - packages=['apparmor', 'apparmor.rule'], - py_modules=['apparmor.easyprof'] +setup( + name='apparmor', + version=version, + description='Python libraries for AppArmor utilities', + long_description='Python libraries for AppArmor utilities', + author='AppArmor Developers', + author_email='apparmor@lists.ubuntu.com', + url='https://gitlab.com/apparmor/apparmor', + license='GPL-2', + cmdclass={'install': Install}, + package_dir={'apparmor': 'staging'}, + packages=['apparmor', 'apparmor.rule'], + py_modules=['apparmor.easyprof'] ) shutil.rmtree('staging') diff --git a/utils/test/Makefile b/utils/test/Makefile index e9abb725a..c86c4256a 100644 --- a/utils/test/Makefile +++ b/utils/test/Makefile @@ -20,6 +20,10 @@ COMMONDIR=../../common/ include $(COMMONDIR)/Make.rules +# files that don't have 100% test coverage +INCOMPLETE_COVERAGE=libraries/libapparmor/swig/python/.*/LibAppArmor/LibAppArmor.py|utils/apparmor/aa.py|utils/apparmor/common.py|utils/apparmor/config.py|utils/apparmor/easyprof.py|utils/apparmor/fail.py|utils/apparmor/logparser.py|utils/apparmor/profile_storage.py|utils/apparmor/rules.py|utils/apparmor/ui.py|minitools_test.py + + ifdef USE_SYSTEM LD_LIBRARY_PATH= PYTHONPATH= @@ -75,7 +79,7 @@ ifndef VERBOSE endif clean: - rm -rf __pycache__/ .coverage htmlcov + rm -rf __pycache__/ .coverage htmlcov coverage-report.txt check: __libapparmor __parser 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))) @@ -89,5 +93,14 @@ coverage: .coverage coverage-report: .coverage $(PYTHON) -m coverage report --omit="$(COVERAGE_OMIT)" +coverage-regression: .coverage + $(PYTHON) -m coverage report --omit="$(COVERAGE_OMIT)" > coverage-report.txt + $(PYTHON) -m coverage html --omit="$(COVERAGE_OMIT)" $(HTML_COVR_ARGS) + cat coverage-report.txt + @echo 'Checking for coverage changes...' + @if grep -vE ' 100%$$|TOTAL|$(INCOMPLETE_COVERAGE)' coverage-report.txt |grep '%$$' ; then echo "ERROR: Coverage regression - the files listed above are expected to have 100% test coverage"; exit 1; fi + @if grep -E '($(INCOMPLETE_COVERAGE)).*100%$$' coverage-report.txt ; then echo 'ERROR: Coverage improvement - the files listed above now have 100% coverage. Please adjust INCOMPLETE_COVERAGE in the Makefile!'; exit 2; fi + @echo ' ... unchanged' + coverage-html: .coverage $(PYTHON) -m coverage html --omit="$(COVERAGE_OMIT)" $(HTML_COVR_ARGS) diff --git a/utils/test/README.md b/utils/test/README.md index 2ac1fa09f..a2767d7af 100644 --- a/utils/test/README.md +++ b/utils/test/README.md @@ -1,3 +1,12 @@ +# Test data generated elsewhere + +The tests in `parser` generate additional test profiles in +`parser/tst/simple_tests/`: see `gen-dbus.py` and `gen-xtrans.py`. + +`utils/test/test-parser-simple-tests.py` uses this test data when it is +available. If this test data has not been generated, this test will not +complain: it will simply exercise fewer test profiles. + # Running individual tests Python's unittest allows individual tests to be executed by specifying the class name and the test on the command line. diff --git a/utils/test/cleanprof_test.in b/utils/test/cleanprof_test.in index 593c0b179..a7f91b2e6 100644 --- a/utils/test/cleanprof_test.in +++ b/utils/test/cleanprof_test.in @@ -14,8 +14,12 @@ @{asdf} = foo "" +$foo = false + + $bar = true + /usr/bin/a/simple/cleanprof/test/profile { - # Just for the heck of it, this comment wont see the day of light + # Just for the heck of it, this comment won't see the day of light #include <abstractions/base> #include if exists <foo> @@ -43,6 +47,10 @@ dbus send bus=session, dbus send bus=session peer=(label=foo), + profile test_child /foobar { + /etc/child rw, + } + set rlimit nofile <= 256, set rlimit nofile <= 64, @@ -60,6 +68,11 @@ mount options=(rw,suid) /c -> /3, + hat bar { + /etc/passwd r, + capability sys_admin, + } + pivot_root oldroot=/mnt/root/old/, deny owner link /some/thing -> /foo/bar , diff --git a/utils/test/cleanprof_test.out b/utils/test/cleanprof_test.out index dfe251e1f..d9865d7dc 100644 --- a/utils/test/cleanprof_test.out +++ b/utils/test/cleanprof_test.out @@ -8,6 +8,9 @@ include if exists <tunables/nothing> @{xy} = x y @{asdf} = "" foo +$foo = false +$bar = true + # A simple test comment which will persist @@ -43,12 +46,24 @@ include if exists <tunables/nothing> change_profile, + hat bar { + capability sys_admin, + + /etc/passwd r, + + } + ^foo { capability dac_override, /etc/fstab r, } + + profile test_child /foobar { + /etc/child rw, + + } } /usr/bin/other/cleanprof/test/profile { allow /home/*/** rw, diff --git a/utils/test/common_test.py b/utils/test/common_test.py index f3d5c0113..958cbdc13 100755 --- a/utils/test/common_test.py +++ b/utils/test/common_test.py @@ -12,17 +12,17 @@ # GNU General Public License for more details. # # ---------------------------------------------------------------------- -import unittest import inspect import os import shutil import sys import tempfile +import unittest - #def test_readkey(self): - # print("Please press the Y button on the keyboard.") - # self.assertEqual(apparmor.common.readkey().lower(), 'y', 'Error reading key from shell!') +# def test_readkey(self): +# print("Please press the Y button on the keyboard.") +# self.assertEqual(apparmor.common.readkey().lower(), 'y', 'Error reading key from shell!') class AATest(unittest.TestCase): @@ -31,8 +31,7 @@ class AATest(unittest.TestCase): self.AASetup() def AASetup(self): - '''override this function if a test needs additional setup steps (instead of overriding setUp())''' - pass + """override this function if a test needs additional setup steps (instead of overriding setUp())""" def tearDown(self): if self.tmpdir and os.path.exists(self.tmpdir): @@ -41,8 +40,7 @@ class AATest(unittest.TestCase): self.AATeardown() def AATeardown(self): - '''override this function if a test needs additional teardown steps (instead of overriding tearDown())''' - pass + """override this function if a test needs additional teardown steps (instead of overriding tearDown())""" def createTmpdir(self): self.tmpdir = tempfile.mkdtemp(prefix='aa-test-') @@ -52,28 +50,32 @@ class AATest(unittest.TestCase): self.createTmpdir() return write_file(self.tmpdir, file, contents) - tests = [] + tests = () tmpdir = None + class AAParseTest(unittest.TestCase): parse_function = None def _test_parse_rule(self, rule): self.assertIsNot(self.parse_function, 'Test class did not set a parse_function') parsed = self.parse_function(rule) - self.assertEqual(rule, parsed.serialize(), - 'parse object %s returned "%s", expected "%s"' \ - %(self.parse_function.__doc__, parsed.serialize(), rule)) + self.assertEqual( + rule, parsed.serialize(), + 'parse object %s returned "%s", expected "%s"' + % (self.parse_function.__doc__, parsed.serialize(), rule)) + def setup_all_loops(module_name): - '''call setup_tests_loop() for each class in module_name''' + """call setup_tests_loop() for each class in module_name""" for name, obj in inspect.getmembers(sys.modules[module_name]): if inspect.isclass(obj): if issubclass(obj, unittest.TestCase): setup_tests_loop(obj) + def setup_tests_loop(test_class): - '''Create tests in test_class using test_class.tests and self._run_test() + """Create tests in test_class using test_class.tests and self._run_test() test_class.tests should be tuples of (test_data, expected_results) test_data and expected_results can be of any type as long as test_class._run_test() @@ -81,7 +83,7 @@ def setup_tests_loop(test_class): A typical definition for _run_test() is: def test_class._run_test(self, test_data, expected) - ''' + """ for (i, (test_data, expected)) in enumerate(test_class.tests): def stub_test(self, test_data=test_data, expected=expected): @@ -92,10 +94,10 @@ def setup_tests_loop(test_class): def setup_regex_tests(test_class): - '''Create tests in test_class using test_class.tests and AAParseTest._test_parse_rule() + """Create tests in test_class using test_class.tests and AAParseTest._test_parse_rule() test_class.tests should be tuples of (line, description) - ''' + """ for (i, (line, desc)) in enumerate(test_class.tests): def stub_test(self, line=line): self._test_parse_rule(line) @@ -103,6 +105,7 @@ 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: @@ -114,20 +117,21 @@ def setup_aa(aa): # 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''' + """construct path, write contents to it, and return the constructed path""" path = os.path.join(directory, file) with open(path, 'w+') as f: f.write(contents) return path + def read_file(path): - '''read and return file contents''' + """read and return file contents""" with open(path, 'r') as f: return f.read() - if __name__ == "__main__": - #import sys;sys.argv = ['', 'Test.test_RegexParser'] + # import sys;sys.argv = ['', 'Test.test_RegexParser'] unittest.main() diff --git a/utils/test/logprof.conf b/utils/test/logprof.conf index 71b50e482..0276bfea6 100644 --- a/utils/test/logprof.conf +++ b/utils/test/logprof.conf @@ -11,10 +11,10 @@ [settings] profiledir = ../../profiles/apparmor.d - inactive_profiledir = ../../profiles/apparmor/profiles/extra + inactive_profiledir = ../../profiles/apparmor/profiles/extras logfiles = /var/log/audit/audit.log /var/log/syslog /var/log/messages - parser = ../../parser/apparmor_parser + parser = ../../parser/apparmor_parser ../parser/apparmor_parser ldd = /usr/bin/ldd logger = /bin/logger /usr/bin/logger @@ -52,7 +52,7 @@ /usr/lib/YaST2/servers_non_y2/ag_genprof = u /usr/lib/YaST2/servers_non_y2/ag_logprof = u - # these ones shouln't have their own profiles + # these ones shouldn't have their own profiles /bin/awk = icn /bin/cat = icn /bin/chmod = icn diff --git a/utils/test/minitools_test.py b/utils/test/minitools_test.py deleted file mode 100755 index 8cba4e60f..000000000 --- a/utils/test/minitools_test.py +++ /dev/null @@ -1,162 +0,0 @@ -# ---------------------------------------------------------------------- -# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.com> -# -# 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. -# -# ---------------------------------------------------------------------- -import os -import shutil -import subprocess -import sys -import unittest -from common_test import AATest, setup_all_loops, setup_aa - -import apparmor.aa as apparmor -from common_test import read_file - -python_interpreter = 'python' -if sys.version_info >= (3, 0): - python_interpreter = 'python3' - -class MinitoolsTest(AATest): - - def AASetup(self): - self.createTmpdir() - - #copy the local profiles to the test directory - #Should be the set of cleanprofile - self.profile_dir = '%s/profiles' % self.tmpdir - shutil.copytree('../../profiles/apparmor.d/', self.profile_dir, symlinks=True) - - apparmor.profile_dir = self.profile_dir - - # Path for the program - self.test_path = '/usr/sbin/winbindd' - # Path for the target file containing profile - self.local_profilename = '%s/usr.sbin.winbindd' % self.profile_dir - - def test_audit(self): - # Set test profile to audit mode and check if it was correctly set - str(subprocess.check_output('%s ./../aa-audit --no-reload -d %s %s' % (python_interpreter, self.profile_dir, self.test_path), shell=True)) - - self.assertEqual(apparmor.get_profile_flags(self.local_profilename, self.test_path), 'audit', - 'Audit flag could not be set in profile %s' % self.local_profilename) - - # Remove audit mode from test profile and check if it was correctly removed - subprocess.check_output('%s ./../aa-audit --no-reload -d %s -r %s' % (python_interpreter, self.profile_dir, self.test_path), shell=True) - - self.assertEqual(apparmor.get_profile_flags(self.local_profilename, self.test_path), None, - 'Audit flag could not be removed in profile %s' % self.local_profilename) - - - def test_complain(self): - # Set test profile to complain mode and check if it was correctly set - subprocess.check_output('%s ./../aa-complain --no-reload -d %s %s' % (python_interpreter, self.profile_dir, self.test_path), shell=True) - - # "manually" create a force-complain symlink (will be deleted by aa-enforce later) - force_complain_dir = '%s/force-complain' % self.profile_dir - if not os.path.isdir(force_complain_dir): - os.mkdir(force_complain_dir) - os.symlink(self.local_profilename, '%s/%s' % (force_complain_dir, os.path.basename(self.local_profilename))) - - self.assertEqual(os.path.islink('%s/%s' % (force_complain_dir, os.path.basename(self.local_profilename))), True, - 'Failed to create a symlink for %s in force-complain' % self.local_profilename) - self.assertEqual(apparmor.get_profile_flags(self.local_profilename, self.test_path), 'complain', - 'Complain flag could not be set in profile %s'%self.local_profilename) - - # Set test profile to enforce mode and check if it was correctly set - subprocess.check_output('%s ./../aa-enforce --no-reload -d %s %s'%(python_interpreter, self.profile_dir, self.test_path), shell=True) - - self.assertEqual(os.path.islink('%s/%s' % (force_complain_dir, os.path.basename(self.local_profilename))), False, - 'Failed to remove symlink for %s from force-complain'%self.local_profilename) - self.assertEqual(os.path.islink('%s/disable/%s' % (self.profile_dir, os.path.basename(self.local_profilename))), False, - 'Failed to remove symlink for %s from disable'%self.local_profilename) - self.assertEqual(apparmor.get_profile_flags(self.local_profilename, self.test_path), None, - 'Complain flag could not be removed in profile %s'%self.local_profilename) - - # Set audit flag and then complain flag in a profile - subprocess.check_output('%s ./../aa-audit --no-reload -d %s %s'%(python_interpreter, self.profile_dir, self.test_path), shell=True) - subprocess.check_output('%s ./../aa-complain --no-reload -d %s %s'%(python_interpreter, self.profile_dir, self.test_path), shell=True) - # "manually" create a force-complain symlink (will be deleted by aa-enforce later) - os.symlink(self.local_profilename, '%s/%s'% (force_complain_dir, os.path.basename(self.local_profilename))) - - self.assertEqual(os.path.islink('%s/%s' % (force_complain_dir, os.path.basename(self.local_profilename))), True, - 'Failed to create a symlink for %s in force-complain'%self.local_profilename) - self.assertEqual(apparmor.get_profile_flags(self.local_profilename, self.test_path), 'audit, complain', - 'Complain flag could not be set in profile %s'%self.local_profilename) - - #Remove complain flag first i.e. set to enforce mode - subprocess.check_output('%s ./../aa-enforce --no-reload -d %s %s'%(python_interpreter, self.profile_dir, self.test_path), shell=True) - - self.assertEqual(os.path.islink('%s/%s' % (force_complain_dir, os.path.basename(self.local_profilename))), False, - 'Failed to remove symlink for %s from force-complain'%self.local_profilename) - self.assertEqual(os.path.islink('%s/disable/%s' % (self.profile_dir, os.path.basename(self.local_profilename))), False, - 'Failed to remove symlink for %s from disable'%self.local_profilename) - self.assertEqual(apparmor.get_profile_flags(self.local_profilename, self.test_path), 'audit', - 'Complain flag could not be removed in profile %s'%self.local_profilename) - - #Remove audit flag - subprocess.check_output('%s ./../aa-audit --no-reload -d %s -r %s'%(python_interpreter, self.profile_dir, self.test_path), shell=True) - - def test_enforce(self): - # Set test profile to enforce mode and check if it was correctly set - subprocess.check_output('%s ./../aa-enforce --no-reload -d %s %s'%(python_interpreter, self.profile_dir, self.test_path), shell=True) - - self.assertEqual(os.path.islink('%s/force-complain/%s' % (self.profile_dir, os.path.basename(self.local_profilename))), False, - 'Failed to remove symlink for %s from force-complain'%self.local_profilename) - self.assertEqual(os.path.islink('%s/disable/%s' % (self.profile_dir, os.path.basename(self.local_profilename))), False, - 'Failed to remove symlink for %s from disable'%self.local_profilename) - self.assertEqual(apparmor.get_profile_flags(self.local_profilename, self.test_path), None, - 'Complain flag could not be removed in profile %s'%self.local_profilename) - - - def test_disable(self): - # Disable the test profile and check if it was correctly disabled - subprocess.check_output('%s ./../aa-disable --no-reload -d %s %s'%(python_interpreter, self.profile_dir, self.test_path), shell=True) - - self.assertEqual(os.path.islink('%s/disable/%s' % (self.profile_dir, os.path.basename(self.local_profilename))), True, - 'Failed to create a symlink for %s in disable' % self.local_profilename) - - def test_autodep(self): - pass - - def test_unconfined(self): - output = subprocess.check_output('%s ./../aa-unconfined'%python_interpreter, shell=True) - - output_force = subprocess.check_output('%s ./../aa-unconfined --paranoid'%python_interpreter, shell=True) - - self.assertIsNot(output, '', 'Failed to run aa-unconfined') - - self.assertIsNot(output_force, '', 'Failed to run aa-unconfined in paranoid mode') - - - def test_cleanprof(self): - input_file = 'cleanprof_test.in' - output_file = 'cleanprof_test.out' - #We position the local testfile - shutil.copy('./%s'%input_file, self.profile_dir) - #Our silly test program whose profile we wish to clean - cleanprof_test = '/usr/bin/a/simple/cleanprof/test/profile' - - subprocess.check_output('%s ./../aa-cleanprof --no-reload -d %s -s %s' % (python_interpreter, self.profile_dir, cleanprof_test), shell=True) - - #Strip off the first line (#modified line) - subprocess.check_output('sed -i 1d %s/%s' % (self.profile_dir, input_file), shell=True) - - exp_content = read_file('./%s' % output_file) - real_content = read_file('%s/%s' % (self.profile_dir, input_file)) - self.maxDiff = None - self.assertEqual(exp_content, real_content, 'Failed to cleanup profile properly') - - -setup_aa(apparmor) -setup_all_loops(__name__) -if __name__ == '__main__': - unittest.main(verbosity=1) diff --git a/utils/test/runtests-py2.sh b/utils/test/runtests-py2.sh deleted file mode 100755 index 9ab3c4054..000000000 --- a/utils/test/runtests-py2.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh - -RUNTESTS_PY__PYTHON_BINARY=python2 -. ./runtests-py3.sh - diff --git a/utils/test/severity.db b/utils/test/severity.db deleted file mode 100644 index 3a4a90345..000000000 --- a/utils/test/severity.db +++ /dev/null @@ -1,463 +0,0 @@ -# ------------------------------------------------------------------ -# -# Copyright (C) 2002-2005 Novell/SUSE -# Copyright (C) 2014 Canonical Ltd. -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of version 2 of the GNU General Public -# License published by the Free Software Foundation. -# -# ------------------------------------------------------------------ - -# Allow this process to 0wn the machine: - CAP_SYS_ADMIN 10 - CAP_SYS_CHROOT 10 - CAP_SYS_MODULE 10 - CAP_SYS_PTRACE 10 - CAP_SYS_RAWIO 10 - CAP_MAC_ADMIN 10 - CAP_MAC_OVERRIDE 10 -# Allow other processes to 0wn the machine: - CAP_SETPCAP 9 - CAP_SETFCAP 9 - CAP_CHOWN 9 - CAP_FSETID 9 - CAP_MKNOD 9 - CAP_LINUX_IMMUTABLE 9 - CAP_DAC_OVERRIDE 9 - CAP_SETGID 9 - CAP_SETUID 9 - CAP_FOWNER 9 -# Denial of service, bypass audit controls, information leak - CAP_SYS_TIME 8 - CAP_NET_ADMIN 8 - CAP_SYS_RESOURCE 8 - CAP_KILL 8 - CAP_IPC_OWNER 8 - CAP_SYS_PACCT 8 - CAP_SYS_BOOT 8 - CAP_NET_BIND_SERVICE 8 - CAP_NET_RAW 8 - CAP_SYS_NICE 8 - CAP_LEASE 8 - CAP_IPC_LOCK 8 - CAP_SYS_TTY_CONFIG 8 - CAP_AUDIT_CONTROL 8 - CAP_AUDIT_WRITE 8 - CAP_SYSLOG 8 - CAP_WAKE_ALARM 8 - CAP_BLOCK_SUSPEND 8 - CAP_DAC_READ_SEARCH 7 -# unused - CAP_NET_BROADCAST 0 - -# filename r w x -# 'hard drives' are generally 4 10 0 -/**/lost+found/** 5 5 0 -/boot/** 7 10 0 -/etc/passwd* 4 8 0 -/etc/group* 4 8 0 -/etc/shadow* 7 9 0 -/etc/shadow* 7 9 0 -/home/*/.ssh/** 7 9 0 -/home/*/.gnupg/** 5 7 0 -/home/** 4 6 0 -/srv/** 4 6 0 -/proc/** 6 9 0 -/proc/sys/kernel/hotplug 2 10 0 -/proc/sys/kernel/modprobe 2 10 0 -/proc/kallsyms 7 0 0 -/sys/** 4 8 0 -/sys/power/state 2 8 0 -/sys/firmware/** 2 10 0 -/dev/pts/* 8 9 0 -/dev/ptmx 8 9 0 -/dev/pty* 8 9 0 -/dev/null 0 0 0 -/dev/adbmouse 3 8 0 -/dev/ataraid 9 10 0 -/dev/zero 0 0 0 -/dev/agpgart* 8 10 0 -/dev/aio 3 3 0 -/dev/cbd/* 5 5 0 -/dev/cciss/* 4 10 0 -/dev/capi* 4 6 0 -/dev/cfs0 4 10 0 -/dev/compaq/* 4 10 0 -/dev/cdouble* 4 8 0 -/dev/cpu** 5 5 0 -/dev/cpu**microcode 1 10 0 -/dev/double* 4 8 0 -/dev/hd* 4 10 0 -/dev/sd* 4 10 0 -/dev/ida/* 4 10 0 -/dev/input/* 4 8 0 -/dev/mapper/control 4 10 0 -/dev/*mem 8 10 0 -/dev/loop* 4 10 0 -/dev/lp* 0 4 0 -/dev/md* 4 10 0 -/dev/msr 4 10 0 -/dev/nb* 4 10 0 -/dev/ram* 8 10 0 -/dev/rd/* 4 10 0 -/dev/*random 3 1 0 -/dev/sbpcd* 4 0 0 -/dev/rtc 6 0 0 -/dev/sd* 4 10 0 -/dev/sc* 4 10 0 -/dev/sg* 4 10 0 -/dev/st* 4 10 0 -/dev/snd/* 3 8 0 -/dev/usb/mouse* 4 6 0 -/dev/usb/hid* 4 6 0 -/dev/usb/tty* 4 6 0 -/dev/tty* 8 9 0 -/dev/stderr 0 0 0 -/dev/stdin 0 0 0 -/dev/stdout 0 0 0 -/dev/ubd* 4 10 0 -/dev/usbmouse* 4 6 0 -/dev/userdma 8 10 0 -/dev/vcs* 8 9 0 -/dev/xta* 4 10 0 -/dev/zero 0 0 0 -/dev/inittcl 8 10 0 -/dev/log 5 7 0 -/etc/fstab 3 8 0 -/etc/mtab 3 5 0 -/etc/SuSEconfig/* 1 8 0 -/etc/X11/* 2 7 0 -/etc/X11/xinit/* 2 8 0 -/etc/SuSE-release 1 5 0 -/etc/issue* 1 3 0 -/etc/motd 1 3 0 -/etc/aliases.d/* 1 7 0 -/etc/cron* 1 9 0 -/etc/cups/* 2 7 0 -/etc/default/* 3 8 0 -/etc/init.d/* 1 10 0 -/etc/permissions.d/* 1 8 0 -/etc/ppp/* 2 6 0 -/etc/ppp/*secrets 8 6 0 -/etc/profile.d/* 1 8 0 -/etc/skel/* 0 7 0 -/etc/sysconfig/* 4 10 0 -/etc/xinetd.d/* 1 9 0 -/etc/termcap/* 1 4 0 -/etc/ld.so.* 1 9 0 -/etc/pam.d/* 3 9 0 -/etc/udev/* 3 9 0 -/etc/insserv.conf 3 6 0 -/etc/security/* 1 9 0 -/etc/securetty 0 7 0 -/etc/sudoers 4 9 0 -/etc/hotplug/* 2 10 0 -/etc/xinitd.conf 1 9 0 -/etc/gpm/* 2 10 0 -/etc/ssl/** 2 7 0 -/etc/shadow* 5 9 0 -/etc/bash.bashrc 1 9 0 -/etc/csh.cshrc 1 9 0 -/etc/csh.login 1 9 0 -/etc/inittab 1 10 0 -/etc/profile* 1 9 0 -/etc/shells 1 5 0 -/etc/alternatives 1 6 0 -/etc/sysctl.conf 3 7 0 -/etc/dev.d/* 1 8 0 -/etc/manpath.config 1 6 0 -/etc/permissions* 1 8 0 -/etc/evms.conf 3 8 0 -/etc/exports 3 8 0 -/etc/samba/* 5 8 0 -/etc/ssh/* 3 8 0 -/etc/ssh/ssh_host_*key 8 8 0 -/etc/krb5.conf 4 8 0 -/etc/ntp.conf 3 8 0 -/etc/auto.* 3 8 0 -/etc/postfix/* 3 7 0 -/etc/postfix/*passwd* 6 7 0 -/etc/postfix/*cert* 6 7 0 -/etc/foomatic/* 3 5 0 -/etc/printcap 3 5 0 -/etc/youservers 4 9 0 -/etc/grub.conf 7 10 0 -/etc/modules.conf 4 10 0 -/etc/resolv.conf 2 7 0 -/etc/apache2/** 3 7 0 -/etc/apache2/**ssl** 7 7 0 -/etc/subdomain.d/** 6 10 0 -/etc/apparmor.d/** 6 10 0 -/etc/apparmor/** 6 10 0 -/var/log/** 3 8 0 -/var/adm/SuSEconfig/** 3 8 0 -/var/adm/** 3 7 0 -/var/lib/rpm/** 4 8 0 -/var/run/nscd/* 3 3 0 -/var/run/.nscd_socket 3 3 0 -/usr/share/doc/** 1 1 0 -/usr/share/man/** 3 5 0 -/usr/X11/man/** 3 5 0 -/usr/share/info/** 2 4 0 -/usr/share/java/** 2 5 0 -/usr/share/locale/** 2 4 0 -/usr/share/sgml/** 2 4 0 -/usr/share/YaST2/** 3 9 0 -/usr/share/ghostscript/** 3 5 0 -/usr/share/terminfo/** 1 8 0 -/usr/share/latex2html/** 2 4 0 -/usr/share/cups/** 5 6 0 -/usr/share/susehelp/** 2 6 0 -/usr/share/susehelp/cgi-bin/** 3 7 7 -/usr/share/zoneinfo/** 2 7 0 -/usr/share/zsh/** 3 6 0 -/usr/share/vim/** 3 8 0 -/usr/share/groff/** 3 7 0 -/usr/share/vnc/** 3 8 0 -/usr/share/wallpapers/** 2 4 0 -/usr/X11** 3 8 5 -/usr/X11*/bin/XFree86 3 8 8 -/usr/X11*/bin/Xorg 3 8 8 -/usr/X11*/bin/sux 3 8 8 -/usr/X11*/bin/xconsole 3 7 7 -/usr/X11*/bin/xhost 3 7 7 -/usr/X11*/bin/xauth 3 7 7 -/usr/X11*/bin/ethereal 3 6 8 -/usr/lib/ooo-** 3 6 5 -/usr/lib/lsb/** 2 8 8 -/usr/lib/pt_chwon 2 8 5 -/usr/lib/tcl** 2 5 3 -/usr/lib/lib*so* 3 8 4 -/usr/lib/iptables/* 2 8 2 -/usr/lib/perl5/** 4 10 6 -/usr/lib/*/perl/** 4 10 6 -/usr/lib/*/perl5/** 4 10 6 -/usr/lib/gconv/* 4 7 4 -/usr/lib/locale/** 4 8 0 -/usr/lib/jvm/** 5 7 5 -/usr/lib/sasl*/** 5 8 4 -/usr/lib/jvm-exports/** 5 7 5 -/usr/lib/jvm-private/** 5 7 5 -/usr/lib/python*/** 5 7 5 -/usr/lib/libkrb5* 4 8 4 -/usr/lib/postfix/* 4 7 4 -/usr/lib/rpm/** 4 8 6 -/usr/lib/rpm/gnupg/** 4 9 0 -/usr/lib/apache2** 4 7 4 -/usr/lib/mailman/** 4 6 4 -/usr/bin/ldd 1 7 4 -/usr/bin/netcat 5 7 8 -/usr/bin/clear 2 6 3 -/usr/bin/reset 2 6 3 -/usr/bin/tput 2 6 3 -/usr/bin/tset 2 6 3 -/usr/bin/file 2 6 3 -/usr/bin/ftp 3 7 5 -/usr/bin/busybox 4 8 6 -/usr/bin/rbash 4 8 5 -/usr/bin/screen 3 6 5 -/usr/bin/getfacl 3 7 4 -/usr/bin/setfacl 3 7 9 -/usr/bin/*awk* 3 7 7 -/usr/bin/sudo 2 9 10 -/usr/bin/lsattr 2 6 5 -/usr/bin/chattr 2 7 8 -/usr/bin/sed 3 7 6 -/usr/bin/grep 2 7 2 -/usr/bin/chroot 2 6 10 -/usr/bin/dircolors 2 9 3 -/usr/bin/cut 2 7 2 -/usr/bin/du 2 7 3 -/usr/bin/env 2 7 2 -/usr/bin/head 2 7 2 -/usr/bin/tail 2 7 2 -/usr/bin/install 2 8 4 -/usr/bin/link 2 6 4 -/usr/bin/logname 2 6 2 -/usr/bin/md5sum 2 8 3 -/usr/bin/mkfifo 2 6 10 -/usr/bin/nice 2 7 7 -/usr/bin/nohup 2 7 7 -/usr/bin/printf 2 7 1 -/usr/bin/readlink 2 7 3 -/usr/bin/seq 2 7 1 -/usr/bin/sha1sum 2 8 3 -/usr/bin/shred 2 7 3 -/usr/bin/sort 2 7 3 -/usr/bin/split 2 7 3 -/usr/bin/stat 2 7 4 -/usr/bin/sum 2 8 3 -/usr/bin/tac 2 7 3 -/usr/bin/tail 3 8 4 -/usr/bin/tee 2 7 3 -/usr/bin/test 2 8 4 -/usr/bin/touch 2 7 3 -/usr/bin/tr 2 8 3 -/usr/bin/tsort 2 7 3 -/usr/bin/tty 2 7 3 -/usr/bin/unexpand 2 7 3 -/usr/bin/uniq 2 7 3 -/usr/bin/unlink 2 8 4 -/usr/bin/uptime 2 7 3 -/usr/bin/users 2 8 4 -/usr/bin/vdir 2 8 4 -/usr/bin/wc 2 7 3 -/usr/bin/who 2 8 4 -/usr/bin/whoami 2 8 4 -/usr/bin/yes 1 6 1 -/usr/bin/ed 2 7 5 -/usr/bin/red 2 7 4 -/usr/bin/find 2 8 5 -/usr/bin/xargs 2 7 5 -/usr/bin/ispell 2 7 4 -/usr/bin/a2p 2 7 5 -/usr/bin/perlcc 2 7 5 -/usr/bin/perldoc 2 7 5 -/usr/bin/pod2* 2 7 5 -/usr/bin/prove 2 7 5 -/usr/bin/perl 2 10 7 -/usr/bin/perl* 2 10 7 -/usr/bin/suidperl 2 8 8 -/usr/bin/csh 2 8 8 -/usr/bin/tcsh 2 8 8 -/usr/bin/tree 2 6 5 -/usr/bin/last 2 7 5 -/usr/bin/lastb 2 7 5 -/usr/bin/utmpdump 2 6 5 -/usr/bin/alsamixer 2 6 8 -/usr/bin/amixer 2 6 8 -/usr/bin/amidi 2 6 8 -/usr/bin/aoss 2 6 8 -/usr/bin/aplay 2 6 8 -/usr/bin/aplaymidi 2 6 8 -/usr/bin/arecord 2 6 8 -/usr/bin/arecordmidi 2 6 8 -/usr/bin/aseqnet 2 6 8 -/usr/bin/aserver 2 6 8 -/usr/bin/iecset 2 6 8 -/usr/bin/rview 2 6 5 -/usr/bin/ex 2 7 5 -/usr/bin/enscript 2 6 5 -/usr/bin/genscript 2 6 5 -/usr/bin/xdelta 2 6 5 -/usr/bin/edit 2 6 5 -/usr/bin/vimtutor 2 6 5 -/usr/bin/rvim 2 6 5 -/usr/bin/vim 2 8 7 -/usr/bin/vimdiff 2 8 7 -/usr/bin/aspell 2 6 5 -/usr/bin/xxd 2 6 5 -/usr/bin/spell 2 6 5 -/usr/bin/eqn 2 6 5 -/usr/bin/eqn2graph 2 6 5 -/usr/bin/word-list-compress 2 6 4 -/usr/bin/afmtodit 2 6 4 -/usr/bin/hpf2dit 2 6 4 -/usr/bin/geqn 2 6 4 -/usr/bin/grn 2 6 4 -/usr/bin/grodvi 2 6 4 -/usr/bin/groff 2 6 5 -/usr/bin/groffer 2 6 4 -/usr/bin/grolj4 2 6 4 -/usr/bin/grotty 2 6 4 -/usr/bin/gtbl 2 6 4 -/usr/bin/pic2graph 2 6 4 -/usr/bin/indxbib 2 6 4 -/usr/bin/lkbib 2 6 4 -/usr/bin/lookbib 2 6 4 -/usr/bin/mmroff 2 6 4 -/usr/bin/neqn 2 6 4 -/usr/bin/pfbtops 2 6 4 -/usr/bin/pic 2 6 4 -/usr/bin/tfmtodit 2 6 4 -/usr/bin/tbl 2 6 4 -/usr/bin/post-grohtml 2 6 4 -/usr/bin/pre-grohtml 2 6 4 -/usr/bin/refer 2 6 4 -/usr/bin/soelim 2 6 4 -/usr/bin/disable-paste 2 6 6 -/usr/bin/troff 2 6 4 -/usr/bin/strace-graph 2 6 4 -/usr/bin/gpm-root 2 6 7 -/usr/bin/hltest 2 6 7 -/usr/bin/mev 2 6 6 -/usr/bin/mouse-test 2 6 6 -/usr/bin/strace 2 8 9 -/usr/bin/scsiformat 2 7 10 -/usr/bin/lsscsi 2 7 7 -/usr/bin/scsiinfo 2 7 7 -/usr/bin/sg_* 2 7 7 -/usr/bin/build-classpath 2 6 6 -/usr/bin/build-classpath-directory 2 6 6 -/usr/bin/build-jar-repository 2 6 6 -/usr/bin/diff-jars 2 6 6 -/usr/bin/jvmjar 2 6 6 -/usr/bin/rebuild-jar-repository 2 6 6 -/usr/bin/scriptreplay 2 6 5 -/usr/bin/cal 2 6 3 -/usr/bin/chkdupexe 2 6 5 -/usr/bin/col 2 6 4 -/usr/bin/colcrt 2 6 4 -/usr/bin/colrm 2 6 3 -/usr/bin/column 2 6 4 -/usr/bin/cytune 2 6 6 -/usr/bin/ddate 2 6 3 -/usr/bin/fdformat 2 6 6 -/usr/bin/getopt 2 8 6 -/usr/bin/hexdump 2 6 4 -/usr/bin/hostid 2 6 4 -/usr/bin/ipcrm 2 7 7 -/usr/bin/ipcs 2 7 6 -/usr/bin/isosize 2 6 4 -/usr/bin/line 2 6 4 -/usr/bin/look 2 6 5 -/usr/bin/mcookie 2 7 5 -/usr/bin/mesg 2 6 4 -/usr/bin/namei 2 6 5 -/usr/bin/rename 2 6 5 -/usr/bin/renice 2 6 7 -/usr/bin/rev 2 6 5 -/usr/bin/script 2 6 6 -/usr/bin/ChangeSymlinks 2 8 8 -/usr/bin/setfdprm 2 6 7 -/usr/bin/setsid 2 6 3 -/usr/bin/setterm 2 6 5 -/usr/bin/tailf 2 6 4 -/usr/bin/time 2 6 4 -/usr/bin/ul 2 6 4 -/usr/bin/wall 2 6 5 -/usr/bin/whereis 2 6 4 -/usr/bin/which 2 6 3 -/usr/bin/c_rehash 2 7 6 -/usr/bin/openssl 2 8 6 -/usr/bin/lsdev 2 6 5 -/usr/bin/procinfo 2 6 5 -/usr/bin/socklist 2 6 5 -/usr/bin/filesize 2 6 3 -/usr/bin/linkto 2 6 3 -/usr/bin/mkinfodir 2 6 5 -/usr/bin/old 2 6 4 -/usr/bin/rpmlocate 2 6 5 -/usr/bin/safe-rm 2 8 6 -/usr/bin/safe-rmdir 2 8 6 -/usr/bin/setJava 2 6 1 -/usr/bin/vmstat 2 6 4 -/usr/bin/top 2 6 6 -/usr/bin/pinentry* 2 7 6 -/usr/bin/free 2 8 4 -/usr/bin/pmap 2 6 5 -/usr/bin/slabtop 2 6 4 -/usr/bin/tload 2 6 4 -/usr/bin/watch 2 6 3 -/usr/bin/w 2 6 4 -/usr/bin/pstree.x11 2 6 4 -/usr/bin/pstree 2 6 4 -/usr/bin/snice 2 6 6 -/usr/bin/skill 2 6 7 -/usr/bin/pgrep 2 6 4 -/usr/bin/killall 2 6 7 -/usr/bin/curl 2 7 7 -/usr/bin/slptool 2 7 8 -/usr/bin/ldap* 2 7 7 -/usr/bin/whatis 2 7 5 diff --git a/utils/test/test-aa-cli-bootstrap.py b/utils/test/test-aa-cli-bootstrap.py index 8b8883d0f..206445260 100644 --- a/utils/test/test-aa-cli-bootstrap.py +++ b/utils/test/test-aa-cli-bootstrap.py @@ -9,28 +9,25 @@ # # ------------------------------------------------------------------ -import unittest -from common_test import AATest, setup_all_loops, setup_aa - -# Imports for test code +import atexit import io import os import sys +import unittest -# Imports for AppArmor -import atexit import apparmor.aa as aa import apparmor.ui as aaui from apparmor.common import DebugLogger from apparmor.fail import enable_aa_exception_handler from apparmor.translations import init_translation +from common_test import AATest, setup_aa, setup_all_loops class AACliBootstrapTest(AATest): - ''' + """ Generic test of the core AppArmor Python libraries that all command line tools rely on. - ''' + """ def AASetup(self): # Redirect sys.stdout to a buffer sys.stdout = io.StringIO() @@ -67,7 +64,9 @@ class AACliBootstrapTest(AATest): aaui.set_json_mode() sys.stdout.getvalue() aaui.UI_Info('Test string') - self.assertEqual(sys.stdout.getvalue(), '{"dialog": "apparmor-json-version","data": "2.12"}\n{"dialog": "info","data": "Test string"}\n') + self.assertEqual( + sys.stdout.getvalue(), + '{"dialog": "apparmor-json-version","data": "2.12"}\n{"dialog": "info","data": "Test string"}\n') aaui.set_text_mode() diff --git a/utils/test/test-aa-decode.py b/utils/test/test-aa-decode.py index 96dfcfb48..3abac701a 100755 --- a/utils/test/test-aa-decode.py +++ b/utils/test/test-aa-decode.py @@ -12,14 +12,15 @@ import os import signal import subprocess -import tempfile import unittest +from tempfile import NamedTemporaryFile -# The locationg of the aa-decode utility can be overridden by setting +# The location of the aa-decode utility can be overridden by setting # the APPARMOR_DECODE environment variable; this is useful for running # these tests in an installed environment aadecode_bin = "../aa-decode" + # http://www.chiark.greenend.org.uk/ucgi/~cjwatson/blosxom/2009-07-02-python-sigpipe.html # This is needed so that the subprocesses that produce endless output # actually quit when the reader goes away. @@ -28,10 +29,11 @@ def subprocess_setup(): # non-Python subprocesses expect. signal.signal(signal.SIGPIPE, signal.SIG_DFL) + # Define only arguments that are actually ever used: command and stdin def cmd(command, stdin=None): - '''Try to execute given command (array) and return its stdout, or return - a textual error if it failed.''' + """Try to execute given command (array) and return its stdout, or return + a textual error if it failed.""" try: sp = subprocess.Popen( @@ -43,7 +45,7 @@ def cmd(command, stdin=None): preexec_fn=subprocess_setup ) except OSError as e: - return [127, str(e)] + return 127, str(e) stdout, stderr = sp.communicate(input) @@ -54,83 +56,69 @@ def cmd(command, stdin=None): else: out = stdout - return [sp.returncode, out.decode('utf-8')] - + return sp.returncode, out.decode('utf-8') -def mkstemp_fill(contents, suffix='', prefix='tst-aadecode-', dir=None): - '''As tempfile.mkstemp does, return a (file, name) pair, but with prefilled contents.''' - - handle, name = tempfile.mkstemp(suffix=suffix, prefix=prefix, dir=dir) - os.close(handle) - handle = open(name, "w+") - handle.write(contents) - handle.flush() - handle.seek(0) - - return handle, name class AADecodeTest(unittest.TestCase): - def setUp(self): - self.tmpfile = None - - def tearDown(self): - if self.tmpfile and os.path.exists(self.tmpfile): - os.remove(self.tmpfile) - def test_help(self): - '''Test --help argument''' + """Test --help argument""" expected = 0 - rc, report = cmd([aadecode_bin, "--help"]) + rc, report = cmd((aadecode_bin, "--help")) result = 'Got exit code %d, expected %d\n' % (rc, expected) self.assertEqual(expected, rc, result + report) - def _run_file_test(self, content, expected_list): - '''test case helper function; takes log content and a list of - expected strings as arguments''' + def _run_file_test(self, content, expected): + """test case helper function; takes log content and a list of + expected strings as arguments""" - expected = 0 + expected_return_code = 0 - (f, self.tmpfile) = mkstemp_fill(content) + with NamedTemporaryFile("w+", prefix='tst-aadecode-') as temp_file: + self.tmpfile = temp_file.name + temp_file.write(content) + temp_file.flush() + temp_file.seek(0) + rc, report = cmd((aadecode_bin,), stdin=temp_file) - rc, report = cmd([aadecode_bin], stdin=f) - result = 'Got exit code %d, expected %d\n' % (rc, expected) - self.assertEqual(expected, rc, result + report) - for expected_string in expected_list: + result = 'Got exit code %d, expected %d\n' % (rc, expected_return_code) + self.assertEqual(expected_return_code, rc, result + report) + for expected_string in expected: result = 'could not find expected %s in output:\n' % (expected_string) self.assertIn(expected_string, report, result + report) - f.close() def test_simple_decode(self): - '''Test simple decode on command line''' + """Test simple decode on command line""" expected = 0 expected_output = 'Decoded: /tmp/foo bar' test_code = '2F746D702F666F6F20626172' - rc, report = cmd([aadecode_bin, test_code]) + rc, report = cmd((aadecode_bin, test_code)) result = 'Got exit code %d, expected %d\n' % (rc, expected) self.assertEqual(expected, rc, result + report) result = 'Got output "%s", expected "%s"\n' % (report, expected_output) self.assertIn(expected_output, report, result + report) def test_simple_filter(self): - '''test simple decoding of the name argument''' + """test simple decoding of the name argument""" expected_string = 'name="/tmp/foo bar"' content = \ '''type=AVC msg=audit(1348982151.183:2934): apparmor="DENIED" operation="open" parent=30751 profile="/usr/lib/firefox/firefox{,*[^s] [^h]}" name=2F746D702F666F6F20626172 pid=30833 comm="plugin-containe" requested_mask="r" denied_mask="r" fsuid=1000 ouid=0 ''' - self._run_file_test(content, [expected_string]) + self._run_file_test(content, (expected_string,)) def test_simple_multiline(self): - '''test simple multiline decoding of the name argument''' + """test simple multiline decoding of the name argument""" - expected_strings = ['ses=4294967295 new ses=2762', - 'name="/tmp/foo bar"', - 'name="/home/steve/tmp/my test file"'] + expected_strings = ( + 'ses=4294967295 new ses=2762', + 'name="/tmp/foo bar"', + 'name="/home/steve/tmp/my test file"', + ) content = \ ''' type=LOGIN msg=audit(1348980001.155:2925): login pid=17875 uid=0 old auid=4294967295 new auid=0 old ses=4294967295 new ses=2762 type=AVC msg=audit(1348982151.183:2934): apparmor="DENIED" operation="open" parent=30751 profile="/usr/lib/firefox/firefox{,*[^s] [^h]}" name=2F746D702F666F6F20626172 pid=30833 comm="plugin-containe" requested_mask="r" denied_mask="r" fsuid=1000 ouid=0 @@ -140,11 +128,11 @@ type=AVC msg=audit(1348982148.195:2933): apparmor="DENIED" operation="file_lock" self._run_file_test(content, expected_strings) def test_simple_profile(self): - '''test simple decoding of the profile argument''' + """test simple decoding of the profile argument""" - '''Example take from LP: #897957''' - expected_strings = ['name="/lib/x86_64-linux-gnu/libdl-2.13.so"', - 'profile="/test space"'] + # Example take from LP: #897957 + expected_strings = ( + 'name="/lib/x86_64-linux-gnu/libdl-2.13.so"', 'profile="/test space"') content = \ '''[289763.843292] type=1400 audit(1322614912.304:857): apparmor="ALLOWED" operation="getattr" parent=16001 profile=2F74657374207370616365 name="/lib/x86_64-linux-gnu/libdl-2.13.so" pid=17011 comm="bash" requested_mask="r" denied_mask="r" fsuid=0 ouid=0 ''' @@ -152,11 +140,11 @@ type=AVC msg=audit(1348982148.195:2933): apparmor="DENIED" operation="file_lock" self._run_file_test(content, expected_strings) def test_simple_profile2(self): - '''test simple decoding of name and profile argument''' + """test simple decoding of name and profile argument""" - '''Example take from LP: #897957''' - expected_strings = ['name="/home/steve/tmp/my test file"', - 'profile="/home/steve/tmp/my prog.sh"'] + # Example take from LP: #897957 + expected_strings = ('name="/home/steve/tmp/my test file"', + 'profile="/home/steve/tmp/my prog.sh"') content = \ '''type=AVC msg=audit(1349805073.402:6857): apparmor="DENIED" operation="mknod" parent=5890 profile=2F686F6D652F73746576652F746D702F6D792070726F672E7368 name=2F686F6D652F73746576652F746D702F6D7920746573742066696C65 pid=5891 comm="touch" requested_mask="c" denied_mask="c" fsuid=1000 ouid=1000 ''' @@ -164,9 +152,9 @@ type=AVC msg=audit(1348982148.195:2933): apparmor="DENIED" operation="file_lock" self._run_file_test(content, expected_strings) def test_simple_embedded_carat(self): - '''test simple decoding of embedded ^ in files''' + """test simple decoding of embedded ^ in files""" - expected_strings = ['name="/home/steve/tmp/my test ^file"'] + expected_strings = ('name="/home/steve/tmp/my test ^file"',) content = \ '''type=AVC msg=audit(1349805073.402:6857): apparmor="DENIED" operation="mknod" parent=5890 profile="/usr/bin/test_profile" name=2F686F6D652F73746576652F746D702F6D792074657374205E66696C65 pid=5891 comm="touch" requested_mask="c" denied_mask="c" fsuid=1000 ouid=1000 ''' @@ -174,9 +162,9 @@ type=AVC msg=audit(1348982148.195:2933): apparmor="DENIED" operation="file_lock" self._run_file_test(content, expected_strings) def test_simple_embedded_backslash_carat(self): - '''test simple decoding of embedded \^ in files''' + r"""test simple decoding of embedded \^ in files""" - expected_strings = ['name="/home/steve/tmp/my test \^file"'] + expected_strings = (r'name="/home/steve/tmp/my test \^file"',) content = \ '''type=AVC msg=audit(1349805073.402:6857): apparmor="DENIED" operation="mknod" parent=5890 profile="/usr/bin/test_profile" name=2F686F6D652F73746576652F746D702F6D792074657374205C5E66696C65 pid=5891 comm="touch" requested_mask="c" denied_mask="c" fsuid=1000 ouid=1000 ''' @@ -184,9 +172,9 @@ type=AVC msg=audit(1348982148.195:2933): apparmor="DENIED" operation="file_lock" self._run_file_test(content, expected_strings) def test_simple_embedded_singlequote(self): - '''test simple decoding of embedded \' in files''' + """test simple decoding of embedded \' in files""" - expected_strings = ['name="/home/steve/tmp/my test \'file"'] + expected_strings = ('name="/home/steve/tmp/my test \'file"',) content = \ '''type=AVC msg=audit(1349805073.402:6857): apparmor="DENIED" operation="mknod" parent=5890 profile="/usr/bin/test_profile" name=2F686F6D652F73746576652F746D702F6D792074657374202766696C65 pid=5891 comm="touch" requested_mask="c" denied_mask="c" fsuid=1000 ouid=1000 ''' @@ -194,16 +182,16 @@ type=AVC msg=audit(1348982148.195:2933): apparmor="DENIED" operation="file_lock" self._run_file_test(content, expected_strings) def test_simple_encoded_nonpath_profiles(self): - '''test simple decoding of nonpath profiles''' + """test simple decoding of nonpath profiles""" - expected_strings = ['name="/lib/x86_64-linux-gnu/libdl-2.13.so"', - 'profile="test space"'] + expected_strings = ('name="/lib/x86_64-linux-gnu/libdl-2.13.so"', 'profile="test space"') content = \ '''[289763.843292] type=1400 audit(1322614912.304:857): apparmor="ALLOWED" operation="getattr" parent=16001 profile=74657374207370616365 name="/lib/x86_64-linux-gnu/libdl-2.13.so" pid=17011 comm="bash" requested_mask="r" denied_mask="r" fsuid=0 ouid=0 ''' self._run_file_test(content, expected_strings) + # # Main # diff --git a/utils/test/test-aa-easyprof.py b/utils/test/test-aa-easyprof.py index d20579724..eb4ea0a85 100755 --- a/utils/test/test-aa-easyprof.py +++ b/utils/test/test-aa-easyprof.py @@ -19,12 +19,14 @@ import tempfile import unittest import apparmor.easyprof as easyprof +from apparmor.common import AppArmorException topdir = None debugging = False + def recursive_rm(dirPath, contents_only=False): - '''recursively remove directory''' + """recursively remove directory""" names = os.listdir(dirPath) for name in names: path = os.path.join(dirPath, name) @@ -32,9 +34,10 @@ def recursive_rm(dirPath, contents_only=False): os.unlink(path) else: recursive_rm(path) - if contents_only == False: + if not contents_only: os.rmdir(dirPath) + # From Lib/test/test_optparse.py from python 2.7.4 class InterceptedError(Exception): def __init__(self, @@ -57,7 +60,7 @@ class InterceptingOptionParser(optparse.OptionParser): raise InterceptedError(error_message=msg) -class Manifest(object): +class Manifest: def __init__(self, profile_name): self.security = dict() self.security['profiles'] = dict() @@ -83,7 +86,7 @@ class Manifest(object): self.security['profiles'][self.profile_name]['template'] = template def add_template_variable(self, name, value): - if not 'template_variables' in self.security['profiles'][self.profile_name]: + if 'template_variables' not in self.security['profiles'][self.profile_name]: self.security['profiles'][self.profile_name]['template_variables'] = dict() self.security['profiles'][self.profile_name]['template_variables'][name] = value @@ -98,6 +101,7 @@ class Manifest(object): return json.dumps(dumpee, indent=2) + # # Our test class # @@ -107,13 +111,13 @@ class T(unittest.TestCase): ls = os.path.realpath('/bin/ls') def setUp(self): - '''Setup for tests''' + """Setup for tests""" global topdir self.tmpdir = os.path.realpath(tempfile.mkdtemp(prefix='test-aa-easyprof')) # Copy everything into place - for d in ['easyprof/policygroups', 'easyprof/templates']: + for d in ('easyprof/policygroups', 'easyprof/templates'): shutil.copytree(os.path.join(topdir, d), os.path.join(self.tmpdir, os.path.basename(d))) @@ -143,7 +147,8 @@ class T(unittest.TestCase): } ''' % (self.test_template) - open(os.path.join(self.tmpdir, 'templates', self.test_template), 'w').write(contents) + with open(os.path.join(self.tmpdir, 'templates', self.test_template), 'w') as f: + f.write(contents) # Create a test policygroup self.test_policygroup = "test-policygroup" @@ -152,7 +157,8 @@ class T(unittest.TestCase): #include <abstractions/gnome> #include <abstractions/nameservice> ''' % (self.test_policygroup) - open(os.path.join(self.tmpdir, 'policygroups', self.test_policygroup), 'w').write(contents) + with open(os.path.join(self.tmpdir, 'policygroups', self.test_policygroup), 'w') as f: + f.write(contents) # setup our conffile self.conffile = os.path.join(self.tmpdir, 'easyprof.conf') @@ -160,7 +166,8 @@ class T(unittest.TestCase): POLICYGROUPS_DIR="%s/policygroups" TEMPLATES_DIR="%s/templates" ''' % (self.tmpdir, self.tmpdir) - open(self.conffile, 'w').write(contents) + with open(self.conffile, 'w') as f: + f.write(contents) self.binary = "/opt/bin/foo" self.full_args = ['-c', self.conffile, self.binary] @@ -192,14 +199,14 @@ TEMPLATES_DIR="%s/templates" os.mkdir(self.test_include_dir) os.mkdir(os.path.join(self.test_include_dir, "templates")) os.mkdir(os.path.join(self.test_include_dir, "policygroups")) - for d in ['policygroups', 'templates']: + for d in ('policygroups', 'templates'): for f in easyprof.get_directory_contents(os.path.join( self.tmpdir, d)): shutil.copy(f, os.path.join(self.test_include_dir, d, "inc_%s" % os.path.basename(f))) def tearDown(self): - '''Teardown for tests''' + """Teardown for tests""" if os.path.exists(self.tmpdir): if debugging: sys.stdout.write("%s\n" % self.tmpdir) @@ -210,58 +217,61 @@ TEMPLATES_DIR="%s/templates" # config file tests # def test_configuration_file_p_invalid(self): - '''Test config parsing (invalid POLICYGROUPS_DIR)''' + """Test config parsing (invalid POLICYGROUPS_DIR)""" contents = ''' POLICYGROUPS_DIR= TEMPLATES_DIR="%s/templates" ''' % (self.tmpdir) - open(self.conffile, 'w').write(contents) + with open(self.conffile, 'w') as f: + f.write(contents) try: easyprof.AppArmorEasyProfile(self.binary, self.options) - except easyprof.AppArmorException: + except AppArmorException: return except Exception: raise - raise Exception ("File should have been invalid") + raise Exception("File should have been invalid") def test_configuration_file_p_empty(self): - '''Test config parsing (empty POLICYGROUPS_DIR)''' + """Test config parsing (empty POLICYGROUPS_DIR)""" contents = ''' POLICYGROUPS_DIR="%s" TEMPLATES_DIR="%s/templates" ''' % ('', self.tmpdir) - open(self.conffile, 'w').write(contents) + with open(self.conffile, 'w') as f: + f.write(contents) try: easyprof.AppArmorEasyProfile(self.binary, self.options) - except easyprof.AppArmorException: + except AppArmorException: return except Exception: raise - raise Exception ("File should have been invalid") + raise Exception("File should have been invalid") def test_configuration_file_p_nonexistent(self): - '''Test config parsing (nonexistent POLICYGROUPS_DIR)''' + """Test config parsing (nonexistent POLICYGROUPS_DIR)""" contents = ''' POLICYGROUPS_DIR="%s/policygroups" TEMPLATES_DIR="%s/templates" ''' % ('/nonexistent', self.tmpdir) - open(self.conffile, 'w').write(contents) + with open(self.conffile, 'w') as f: + f.write(contents) try: easyprof.AppArmorEasyProfile(self.binary, self.options) - except easyprof.AppArmorException: + except AppArmorException: return except Exception: raise - raise Exception ("File should have been invalid") + raise Exception("File should have been invalid") def test_policygroups_dir_relative(self): - '''Test --policy-groups-dir (relative DIR)''' + """Test --policy-groups-dir (relative DIR)""" os.chdir(self.tmpdir) rel = os.path.join(self.tmpdir, 'relative') os.mkdir(rel) @@ -273,12 +283,13 @@ TEMPLATES_DIR="%s/templates" easyp = easyprof.AppArmorEasyProfile(self.binary, self.options) # no fallback - self.assertTrue(easyp.dirs['policygroups'] == rel, "Not using specified --policy-groups-dir\n" + - "Specified dir: %s\nActual dir: %s" % (rel, str(easyp.dirs['policygroups']))) - self.assertFalse(easyp.get_policy_groups() == None, "Could not find policy-groups") + self.assertTrue(easyp.dirs['policygroups'] == rel, + "Not using specified --policy-groups-dir\n" + "Specified dir: %s\nActual dir: %s" % (rel, str(easyp.dirs['policygroups']))) + self.assertFalse(easyp.get_policy_groups() is None, "Could not find policy-groups") def test_policygroups_dir_nonexistent(self): - '''Test --policy-groups-dir (nonexistent DIR)''' + """Test --policy-groups-dir (nonexistent DIR)""" os.chdir(self.tmpdir) rel = os.path.join(self.tmpdir, 'nonexistent') @@ -291,10 +302,10 @@ TEMPLATES_DIR="%s/templates" self.assertFalse(easyp.dirs['policygroups'] == rel, "Using nonexistent --policy-groups-dir") # test fallback - self.assertTrue(easyp.get_policy_groups() != None, "Found policy-groups when shouldn't have") + self.assertTrue(easyp.get_policy_groups() is not None, "Found policy-groups when shouldn't have") def test_policygroups_dir_valid(self): - '''Test --policy-groups-dir (valid DIR)''' + """Test --policy-groups-dir (valid DIR)""" os.chdir(self.tmpdir) valid = os.path.join(self.tmpdir, 'valid') os.mkdir(valid) @@ -307,14 +318,15 @@ TEMPLATES_DIR="%s/templates" # no fallback self.assertTrue(easyp.dirs['policygroups'] == valid, "Not using specified --policy-groups-dir") - self.assertFalse(easyp.get_policy_groups() == None, "Could not find policy-groups") + self.assertFalse(easyp.get_policy_groups() is None, "Could not find policy-groups") def test_policygroups_dir_valid_with_vendor(self): - '''Test --policy-groups-dir (valid DIR with vendor)''' + """Test --policy-groups-dir (valid DIR with vendor)""" os.chdir(self.tmpdir) valid = os.path.join(self.tmpdir, 'valid') os.mkdir(valid) - shutil.copy(os.path.join(self.tmpdir, 'policygroups', self.test_policygroup), os.path.join(valid, self.test_policygroup)) + shutil.copy(os.path.join(self.tmpdir, 'policygroups', self.test_policygroup), + os.path.join(valid, self.test_policygroup)) vendor = "ubuntu" version = "1.0" @@ -329,63 +341,66 @@ TEMPLATES_DIR="%s/templates" easyp = easyprof.AppArmorEasyProfile(self.binary, self.options) self.assertTrue(easyp.dirs['policygroups'] == valid, "Not using specified --policy-groups-dir") - self.assertFalse(easyp.get_policy_groups() == None, "Could not find policy-groups") + self.assertFalse(easyp.get_policy_groups() is None, "Could not find policy-groups") for f in easyp.get_policy_groups(): self.assertFalse(os.path.basename(f) == vendor, "Found '%s' in %s" % (vendor, f)) def test_configuration_file_t_invalid(self): - '''Test config parsing (invalid TEMPLATES_DIR)''' + """Test config parsing (invalid TEMPLATES_DIR)""" contents = ''' TEMPLATES_DIR= POLICYGROUPS_DIR="%s/templates" ''' % (self.tmpdir) - open(self.conffile, 'w').write(contents) + with open(self.conffile, 'w') as f: + f.write(contents) try: easyprof.AppArmorEasyProfile(self.binary, self.options) - except easyprof.AppArmorException: + except AppArmorException: return except Exception: raise - raise Exception ("File should have been invalid") + raise Exception("File should have been invalid") def test_configuration_file_t_empty(self): - '''Test config parsing (empty TEMPLATES_DIR)''' + """Test config parsing (empty TEMPLATES_DIR)""" contents = ''' TEMPLATES_DIR="%s" POLICYGROUPS_DIR="%s/templates" ''' % ('', self.tmpdir) - open(self.conffile, 'w').write(contents) + with open(self.conffile, 'w') as f: + f.write(contents) try: easyprof.AppArmorEasyProfile(self.binary, self.options) - except easyprof.AppArmorException: + except AppArmorException: return except Exception: raise - raise Exception ("File should have been invalid") + raise Exception("File should have been invalid") def test_configuration_file_t_nonexistent(self): - '''Test config parsing (nonexistent TEMPLATES_DIR)''' + """Test config parsing (nonexistent TEMPLATES_DIR)""" contents = ''' TEMPLATES_DIR="%s/policygroups" POLICYGROUPS_DIR="%s/templates" ''' % ('/nonexistent', self.tmpdir) - open(self.conffile, 'w').write(contents) + with open(self.conffile, 'w') as f: + f.write(contents) try: easyprof.AppArmorEasyProfile(self.binary, self.options) - except easyprof.AppArmorException: + except AppArmorException: return except Exception: raise - raise Exception ("File should have been invalid") + raise Exception("File should have been invalid") def test_templates_dir_relative(self): - '''Test --templates-dir (relative DIR)''' + """Test --templates-dir (relative DIR)""" os.chdir(self.tmpdir) rel = os.path.join(self.tmpdir, 'relative') os.mkdir(rel) @@ -397,12 +412,13 @@ POLICYGROUPS_DIR="%s/templates" easyp = easyprof.AppArmorEasyProfile(self.binary, self.options) # no fallback - self.assertTrue(easyp.dirs['templates'] == rel, "Not using specified --template-dir\n" + - "Specified dir: %s\nActual dir: %s" % (rel, str(easyp.dirs['templates']))) - self.assertFalse(easyp.get_templates() == None, "Could not find templates") + self.assertTrue(easyp.dirs['templates'] == rel, + "Not using specified --template-dir\n" + "Specified dir: %s\nActual dir: %s" % (rel, str(easyp.dirs['templates']))) + self.assertFalse(easyp.get_templates() is None, "Could not find templates") def test_templates_dir_nonexistent(self): - '''Test --templates-dir (nonexistent DIR)''' + """Test --templates-dir (nonexistent DIR)""" os.chdir(self.tmpdir) rel = os.path.join(self.tmpdir, 'nonexistent') @@ -415,10 +431,10 @@ POLICYGROUPS_DIR="%s/templates" self.assertFalse(easyp.dirs['templates'] == rel, "Using nonexistent --template-dir") # test fallback - self.assertTrue(easyp.get_templates() != None, "Found templates when shouldn't have") + self.assertTrue(easyp.get_templates() is not None, "Found templates when shouldn't have") def test_templates_dir_valid(self): - '''Test --templates-dir (valid DIR)''' + """Test --templates-dir (valid DIR)""" os.chdir(self.tmpdir) valid = os.path.join(self.tmpdir, 'valid') os.mkdir(valid) @@ -431,10 +447,10 @@ POLICYGROUPS_DIR="%s/templates" # no fallback self.assertTrue(easyp.dirs['templates'] == valid, "Not using specified --template-dir") - self.assertFalse(easyp.get_templates() == None, "Could not find templates") + self.assertFalse(easyp.get_templates() is None, "Could not find templates") def test_templates_dir_valid_with_vendor(self): - '''Test --templates-dir (valid DIR with vendor)''' + """Test --templates-dir (valid DIR with vendor)""" os.chdir(self.tmpdir) valid = os.path.join(self.tmpdir, 'valid') os.mkdir(valid) @@ -453,7 +469,7 @@ POLICYGROUPS_DIR="%s/templates" easyp = easyprof.AppArmorEasyProfile(self.binary, self.options) self.assertTrue(easyp.dirs['templates'] == valid, "Not using specified --template-dir") - self.assertFalse(easyp.get_templates() == None, "Could not find templates") + self.assertFalse(easyp.get_templates() is None, "Could not find templates") for f in easyp.get_templates(): self.assertFalse(os.path.basename(f) == vendor, "Found '%s' in %s" % (vendor, f)) @@ -461,39 +477,39 @@ POLICYGROUPS_DIR="%s/templates" # Binary file tests # def test_binary_without_profile_name(self): - '''Test binary (<binary> { })''' + """Test binary (<binary> { })""" easyprof.AppArmorEasyProfile(self.ls, self.options) def test_binary_with_profile_name(self): - '''Test binary (profile <name> <binary> { })''' + """Test binary (profile <name> <binary> { })""" args = self.full_args args += ['--profile-name=some-profile-name'] (self.options, self.args) = easyprof.parse_args(args) easyprof.AppArmorEasyProfile(self.ls, self.options) def test_binary_omitted_with_profile_name(self): - '''Test binary (profile <name> { })''' + """Test binary (profile <name> { })""" args = self.full_args args += ['--profile-name=some-profile-name'] (self.options, self.args) = easyprof.parse_args(args) easyprof.AppArmorEasyProfile(None, self.options) def test_binary_nonexistent(self): - '''Test binary (nonexistent)''' + """Test binary (nonexistent)""" easyprof.AppArmorEasyProfile(os.path.join(self.tmpdir, 'nonexistent'), self.options) def test_binary_relative(self): - '''Test binary (relative)''' + """Test binary (relative)""" try: easyprof.AppArmorEasyProfile('./foo', self.options) - except easyprof.AppArmorException: + except AppArmorException: return except Exception: raise - raise Exception ("Binary should have been invalid") + raise Exception("Binary should have been invalid") def test_binary_symlink(self): - '''Test binary (symlink)''' + """Test binary (symlink)""" exe = os.path.join(self.tmpdir, 'exe') open(exe, 'a').close() symlink = exe + ".lnk" @@ -501,17 +517,17 @@ POLICYGROUPS_DIR="%s/templates" try: easyprof.AppArmorEasyProfile(symlink, self.options) - except easyprof.AppArmorException: + except AppArmorException: return except Exception: raise - raise Exception ("Binary should have been invalid") + raise Exception("Binary should have been invalid") # # Templates tests # def test_templates_list(self): - '''Test templates (list)''' + """Test templates (list)""" args = self.full_args args.append('--list-templates') (self.options, self.args) = easyprof.parse_args(args) @@ -521,11 +537,8 @@ POLICYGROUPS_DIR="%s/templates" self.assertTrue(os.path.exists(i), "Could not find '%s'" % i) def test_templates_show(self): - '''Test templates (show)''' - files = [] - for f in glob.glob("%s/templates/*" % self.tmpdir): - files.append(f) - + """Test templates (show)""" + files = glob.glob("%s/templates/*" % self.tmpdir) for f in files: args = self.full_args args += ['--show-template', '--template', f] @@ -534,10 +547,11 @@ POLICYGROUPS_DIR="%s/templates" path = os.path.join(easyp.dirs['templates'], f) self.assertTrue(os.path.exists(path), "Could not find '%s'" % path) - open(path).read() + with open(path) as fd: + fd.read() def test_templates_list_include(self): - '''Test templates (list with --include-templates-dir)''' + """Test templates (list with --include-templates-dir)""" args = self.full_args args.append('--list-templates') (self.options, self.args) = easyprof.parse_args(args) @@ -547,8 +561,8 @@ POLICYGROUPS_DIR="%s/templates" args = self.full_args args.append('--list-templates') - args.append('--include-templates-dir=%s' % - os.path.join(self.test_include_dir, 'templates')) + args.append('--include-templates-dir=%s' + % os.path.join(self.test_include_dir, 'templates')) (self.options, self.args) = easyprof.parse_args(args) easyp = easyprof.AppArmorEasyProfile(None, self.options) @@ -560,23 +574,21 @@ POLICYGROUPS_DIR="%s/templates" self.assertTrue(os.path.exists(i), "Could not find '%s'" % i) def test_templates_show_include(self): - '''Test templates (show with --include-templates-dir)''' - files = [] - for f in glob.glob("%s/templates/*" % self.test_include_dir): - files.append(f) - + """Test templates (show with --include-templates-dir)""" + files = glob.glob("%s/templates/*" % self.test_include_dir) for f in files: args = self.full_args args += ['--show-template', '--template', f, - '--include-templates-dir=%s' % - os.path.join(self.test_include_dir, 'templates')] + '--include-templates-dir=%s' + % os.path.join(self.test_include_dir, 'templates')] (self.options, self.args) = easyprof.parse_args(args) easyp = easyprof.AppArmorEasyProfile(None, self.options) path = os.path.join(easyp.dirs['templates_include'], f) self.assertTrue(os.path.exists(path), "Could not find '%s'" % path) - open(path).read() + with open(path) as fd: + fd.read() bn = os.path.basename(f) # setup() copies everything in the include prefixed with inc_ @@ -587,7 +599,7 @@ POLICYGROUPS_DIR="%s/templates" # Policygroups tests # def test_policygroups_list(self): - '''Test policygroups (list)''' + """Test policygroups (list)""" args = self.full_args args.append('--list-policy-groups') (self.options, self.args) = easyprof.parse_args(args) @@ -597,11 +609,8 @@ POLICYGROUPS_DIR="%s/templates" self.assertTrue(os.path.exists(i), "Could not find '%s'" % i) def test_policygroups_show(self): - '''Test policygroups (show)''' - files = [] - for f in glob.glob("%s/policygroups/*" % self.tmpdir): - files.append(f) - + """Test policygroups (show)""" + files = glob.glob("%s/policygroups/*" % self.tmpdir) for f in files: args = self.full_args args += ['--show-policy-group', @@ -611,10 +620,11 @@ POLICYGROUPS_DIR="%s/templates" path = os.path.join(easyp.dirs['policygroups'], f) self.assertTrue(os.path.exists(path), "Could not find '%s'" % path) - open(path).read() + with open(path) as fd: + fd.read() def test_policygroups_list_include(self): - '''Test policygroups (list with --include-policy-groups-dir)''' + """Test policygroups (list with --include-policy-groups-dir)""" args = self.full_args args.append('--list-policy-groups') (self.options, self.args) = easyprof.parse_args(args) @@ -624,8 +634,8 @@ POLICYGROUPS_DIR="%s/templates" args = self.full_args args.append('--list-policy-groups') - args.append('--include-policy-groups-dir=%s' % - os.path.join(self.test_include_dir, 'policygroups')) + args.append('--include-policy-groups-dir=%s' + % os.path.join(self.test_include_dir, 'policygroups')) (self.options, self.args) = easyprof.parse_args(args) easyp = easyprof.AppArmorEasyProfile(None, self.options) @@ -637,23 +647,21 @@ POLICYGROUPS_DIR="%s/templates" self.assertTrue(os.path.exists(i), "Could not find '%s'" % i) def test_policygroups_show_include(self): - '''Test policygroups (show with --include-policy-groups-dir)''' - files = [] - for f in glob.glob("%s/policygroups/*" % self.test_include_dir): - files.append(f) - + """Test policygroups (show with --include-policy-groups-dir)""" + files = glob.glob("%s/policygroups/*" % self.test_include_dir) for f in files: args = self.full_args args += ['--show-policy-group', '--policy-groups', os.path.basename(f), - '--include-policy-groups-dir=%s' % - os.path.join(self.test_include_dir, 'policygroups')] + '--include-policy-groups-dir=%s' + % os.path.join(self.test_include_dir, 'policygroups')] (self.options, self.args) = easyprof.parse_args(args) easyp = easyprof.AppArmorEasyProfile(None, self.options) path = os.path.join(easyp.dirs['policygroups_include'], f) self.assertTrue(os.path.exists(path), "Could not find '%s'" % path) - open(path).read() + with open(path) as fd: + fd.read() bn = os.path.basename(f) # setup() copies everything in the include prefixed with inc_ @@ -664,119 +672,119 @@ POLICYGROUPS_DIR="%s/templates" # Manifest file argument tests # def test_manifest_argument(self): - '''Test manifest argument''' + """Test manifest argument""" # setup our manifest self.manifest = os.path.join(self.tmpdir, 'manifest.json') contents = ''' {"security": {"domain.reverse.appname": {"name": "simple-app"}}} ''' - open(self.manifest, 'w').write(contents) + with open(self.manifest, 'w') as f: + f.write(contents) args = self.full_args - args.extend(['--manifest', self.manifest]) + args.extend(('--manifest', self.manifest)) easyprof.parse_args(args) def _manifest_conflicts(self, opt, value): - '''Helper for conflicts tests''' + """Helper for conflicts tests""" # setup our manifest self.manifest = os.path.join(self.tmpdir, 'manifest.json') contents = ''' {"security": {"domain.reverse.appname": {"binary": /nonexistent"}}} ''' - open(self.manifest, 'w').write(contents) + with open(self.manifest, 'w') as f: + f.write(contents) # opt first args = self.full_args - args.extend([opt, value, '--manifest', self.manifest]) + args.extend((opt, value, '--manifest', self.manifest)) raised = False try: easyprof.parse_args(args, InterceptingOptionParser()) except InterceptedError: raised = True - self.assertTrue(raised, msg="%s and manifest arguments did not " \ + self.assertTrue(raised, msg="%s and manifest arguments did not " "raise a parse error" % opt) # manifest first args = self.full_args - args.extend(['--manifest', self.manifest, opt, value]) + args.extend(('--manifest', self.manifest, opt, value)) raised = False try: easyprof.parse_args(args, InterceptingOptionParser()) except InterceptedError: raised = True - self.assertTrue(raised, msg="%s and manifest arguments did not " \ + self.assertTrue(raised, msg="%s and manifest arguments did not " "raise a parse error" % opt) def test_manifest_conflicts_profilename(self): - '''Test manifest arg conflicts with profile_name arg''' + """Test manifest arg conflicts with profile_name arg""" self._manifest_conflicts("--profile-name", "simple-app") def test_manifest_conflicts_copyright(self): - '''Test manifest arg conflicts with copyright arg''' + """Test manifest arg conflicts with copyright arg""" self._manifest_conflicts("--copyright", "2013-01-01") def test_manifest_conflicts_author(self): - '''Test manifest arg conflicts with author arg''' + """Test manifest arg conflicts with author arg""" self._manifest_conflicts("--author", "Foo Bar") def test_manifest_conflicts_comment(self): - '''Test manifest arg conflicts with comment arg''' + """Test manifest arg conflicts with comment arg""" self._manifest_conflicts("--comment", "some comment") def test_manifest_conflicts_abstractions(self): - '''Test manifest arg conflicts with abstractions arg''' + """Test manifest arg conflicts with abstractions arg""" self._manifest_conflicts("--abstractions", "base") def test_manifest_conflicts_read_path(self): - '''Test manifest arg conflicts with read-path arg''' + """Test manifest arg conflicts with read-path arg""" self._manifest_conflicts("--read-path", "/etc/passwd") def test_manifest_conflicts_write_path(self): - '''Test manifest arg conflicts with write-path arg''' + """Test manifest arg conflicts with write-path arg""" self._manifest_conflicts("--write-path", "/tmp/foo") def test_manifest_conflicts_policy_groups(self): - '''Test manifest arg conflicts with policy-groups arg''' + """Test manifest arg conflicts with policy-groups arg""" self._manifest_conflicts("--policy-groups", "opt-application") def test_manifest_conflicts_name(self): - '''Test manifest arg conflicts with name arg''' + """Test manifest arg conflicts with name arg""" self._manifest_conflicts("--name", "foo") def test_manifest_conflicts_template_var(self): - '''Test manifest arg conflicts with template-var arg''' + """Test manifest arg conflicts with template-var arg""" self._manifest_conflicts("--template-var", "foo") def test_manifest_conflicts_policy_version(self): - '''Test manifest arg conflicts with policy-version arg''' + """Test manifest arg conflicts with policy-version arg""" self._manifest_conflicts("--policy-version", "1.0") def test_manifest_conflicts_policy_vendor(self): - '''Test manifest arg conflicts with policy-vendor arg''' + """Test manifest arg conflicts with policy-vendor arg""" self._manifest_conflicts("--policy-vendor", "somevendor") - # # Test genpolicy # - - def _gen_policy(self, name=None, template=None, extra_args=[]): - '''Generate a policy''' + def _gen_policy(self, name=None, template=None, extra_args=None): + """Generate a policy""" # Build up our args args = self.full_args - if template == None: + if template is None: args.append('--template=%s' % self.test_template) else: args.append('--template=%s' % template) - if name != None: + if name is not None: args.append('--name=%s' % name) - if len(extra_args) > 0: + if extra_args: args += extra_args args.append(self.binary) @@ -789,10 +797,10 @@ POLICYGROUPS_DIR="%s/templates" # We always need to check for these search_terms = [self.binary] - if name != None: + if name is not None: search_terms.append(name) - if template == None: + if template is None: search_terms.append(self.test_template) for s in search_terms: @@ -828,53 +836,55 @@ POLICYGROUPS_DIR="%s/templates" return p def test__is_safe(self): - '''Test _is_safe()''' - bad = [ - "/../../../../etc/passwd", - "abstraction with spaces", - "semicolon;bad", - "bad\x00baz", - "foo/bar", - "foo'bar", - 'foo"bar', - ] + """Test _is_safe()""" + bad = ( + "/../../../../etc/passwd", + "abstraction with spaces", + "semicolon;bad", + "bad\x00baz", + "foo/bar", + "foo'bar", + 'foo"bar', + ) for s in bad: - self.assertFalse(easyprof._is_safe(s), "'%s' should be bad" %s) + self.assertFalse(easyprof._is_safe(s), "'%s' should be bad" % s) def test_genpolicy_templates_abspath(self): - '''Test genpolicy (abspath to template)''' + """Test genpolicy (abspath to template)""" # create a new template template = os.path.join(self.tmpdir, "test-abspath-template") shutil.copy(os.path.join(self.tmpdir, 'templates', self.test_template), template) - contents = open(template).read() + with open(template) as f: + contents = f.read() test_string = "#teststring" - open(template, 'w').write(contents + "\n%s\n" % test_string) + with open(template, 'w') as f: + f.write(contents + "\n%s\n" % test_string) p = self._gen_policy(template=template) - for s in [self.test_template, test_string]: + for s in (self.test_template, test_string): self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) def test_genpolicy_templates_system(self): - '''Test genpolicy (system template)''' + """Test genpolicy (system template)""" self._gen_policy() def test_genpolicy_templates_nonexistent(self): - '''Test genpolicy (nonexistent template)''' + """Test genpolicy (nonexistent template)""" try: self._gen_policy(template=os.path.join(self.tmpdir, "/nonexistent")) - except easyprof.AppArmorException: + except AppArmorException: return except Exception: raise - raise Exception ("template should be invalid") + raise Exception("template should be invalid") def test_genpolicy_name(self): - '''Test genpolicy (name)''' + """Test genpolicy (name)""" self._gen_policy(name='test-foo') def test_genpolicy_comment(self): - '''Test genpolicy (comment)''' + """Test genpolicy (comment)""" s = "test comment" p = self._gen_policy(extra_args=['--comment=%s' % s]) self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) @@ -882,7 +892,7 @@ POLICYGROUPS_DIR="%s/templates" self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_author(self): - '''Test genpolicy (author)''' + """Test genpolicy (author)""" s = "Archibald Poindexter" p = self._gen_policy(extra_args=['--author=%s' % s]) self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) @@ -890,7 +900,7 @@ POLICYGROUPS_DIR="%s/templates" self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_copyright(self): - '''Test genpolicy (copyright)''' + """Test genpolicy (copyright)""" s = "2112/01/01" p = self._gen_policy(extra_args=['--copyright=%s' % s]) self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) @@ -898,7 +908,7 @@ POLICYGROUPS_DIR="%s/templates" self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_abstractions(self): - '''Test genpolicy (single abstraction)''' + """Test genpolicy (single abstraction)""" s = "nameservice" p = self._gen_policy(extra_args=['--abstractions=%s' % s]) search = "#include <abstractions/%s>" % s @@ -907,7 +917,7 @@ POLICYGROUPS_DIR="%s/templates" self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_abstractions_multiple(self): - '''Test genpolicy (multiple abstractions)''' + """Test genpolicy (multiple abstractions)""" abstractions = "authentication,X,user-tmp" p = self._gen_policy(extra_args=['--abstractions=%s' % abstractions]) for s in abstractions.split(','): @@ -917,23 +927,23 @@ POLICYGROUPS_DIR="%s/templates" self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_abstractions_bad(self): - '''Test genpolicy (abstractions - bad values)''' - bad = [ + """Test genpolicy (abstractions - bad values)""" + bad = ( "nonexistent", "/../../../../etc/passwd", "abstraction with spaces", - ] + ) for s in bad: try: self._gen_policy(extra_args=['--abstractions=%s' % s]) - except easyprof.AppArmorException: + except AppArmorException: continue except Exception: raise - raise Exception ("abstraction '%s' should be invalid" % s) + raise Exception("abstraction '%s' should be invalid" % s) - def _create_tmp_base_dir(self, prefix='', abstractions=[], tunables=[]): - '''Create a temporary base dir layout''' + 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) @@ -950,19 +960,21 @@ POLICYGROUPS_DIR="%s/templates" # Abstraction file for testing /%s r, ''' % (f) - open(os.path.join(abstractions_dir, f), 'w').write(contents) + with open(os.path.join(abstractions_dir, f), 'w') as fd: + fd.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) + with open(os.path.join(tunables_dir, f), 'w') as fd: + fd.write(contents) return base_dir def test_genpolicy_abstractions_custom_base(self): - '''Test genpolicy (custom base dir)''' + """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 @@ -976,23 +988,23 @@ POLICYGROUPS_DIR="%s/templates" 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)''' + """Test genpolicy (custom base dir - bad base dirs)""" abstraction = "custom-base-dir-test-abstraction" - bad = [ None, '/etc/apparmor.d', '/' ] + 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: + except AppArmorException: continue except Exception: raise - raise Exception ("abstraction '%s' should be invalid" % abstraction) + raise Exception("abstraction '%s' should be invalid" % abstraction) def test_genpolicy_abstractions_custom_include(self): - '''Test genpolicy (custom include dir)''' + """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 @@ -1005,96 +1017,97 @@ POLICYGROUPS_DIR="%s/templates" 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)''' + """Test genpolicy (custom include dir - bad include dirs)""" abstraction = "custom-include-dir-test-abstraction" - bad = [ None, '/etc/apparmor.d', '/' ] + 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: + except AppArmorException: continue except Exception: raise - raise Exception ("abstraction '%s' should be invalid" % abstraction) + raise Exception("abstraction '%s' should be invalid" % abstraction) def test_genpolicy_profile_name_bad(self): - '''Test genpolicy (profile name - bad values)''' + """Test genpolicy (profile name - bad values)""" bad = [ - "/../../../../etc/passwd", - "../../../../etc/passwd", - "profile name with spaces", - ] + "/../../../../etc/passwd", + "../../../../etc/passwd", + "profile name with spaces", + ] for s in bad: try: self._gen_policy(extra_args=['--profile-name=%s' % s]) - except easyprof.AppArmorException: + except AppArmorException: continue except Exception: raise - raise Exception ("profile_name '%s' should be invalid" % s) + raise Exception("profile_name '%s' should be invalid" % s) def test_genpolicy_policy_group_bad(self): - '''Test genpolicy (policy group - bad values)''' + """Test genpolicy (policy group - bad values)""" bad = [ - "/../../../../etc/passwd", - "../../../../etc/passwd", - "profile name with spaces", - ] + "/../../../../etc/passwd", + "../../../../etc/passwd", + "profile name with spaces", + ] for s in bad: try: self._gen_policy(extra_args=['--policy-groups=%s' % s]) - except easyprof.AppArmorException: + except AppArmorException: continue except Exception: raise - raise Exception ("policy group '%s' should be invalid" % s) + raise Exception("policy group '%s' should be invalid" % s) def test_genpolicy_policygroups(self): - '''Test genpolicy (single policygroup)''' + """Test genpolicy (single policygroup)""" groups = self.test_policygroup p = self._gen_policy(extra_args=['--policy-groups=%s' % groups]) - for s in ['#include <abstractions/nameservice>', '#include <abstractions/gnome>']: + for s in ('#include <abstractions/nameservice>', '#include <abstractions/gnome>'): self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) inv_s = '###POLICYGROUPS###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_policygroups_multiple(self): - '''Test genpolicy (multiple policygroups)''' + """Test genpolicy (multiple policygroups)""" test_policygroup2 = "test-policygroup2" contents = ''' # %s #include <abstractions/kde> #include <abstractions/openssl> ''' % (self.test_policygroup) - open(os.path.join(self.tmpdir, 'policygroups', test_policygroup2), 'w').write(contents) + with open(os.path.join(self.tmpdir, 'policygroups', test_policygroup2), 'w') as f: + f.write(contents) groups = "%s,%s" % (self.test_policygroup, test_policygroup2) p = self._gen_policy(extra_args=['--policy-groups=%s' % groups]) - for s in ['#include <abstractions/nameservice>', + for s in ('#include <abstractions/nameservice>', '#include <abstractions/gnome>', '#include <abstractions/kde>', - '#include <abstractions/openssl>']: + '#include <abstractions/openssl>'): self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) inv_s = '###POLICYGROUPS###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_policygroups_nonexistent(self): - '''Test genpolicy (nonexistent policygroup)''' + """Test genpolicy (nonexistent policygroup)""" try: self._gen_policy(extra_args=['--policy-groups=nonexistent']) - except easyprof.AppArmorException: + except AppArmorException: return except Exception: raise - raise Exception ("policygroup should be invalid") + raise Exception("policygroup should be invalid") def test_genpolicy_readpath_file(self): - '''Test genpolicy (read-path file)''' + """Test genpolicy (read-path file)""" s = "/opt/test-foo" p = self._gen_policy(extra_args=['--read-path=%s' % s]) search = "%s rk," % s @@ -1103,7 +1116,7 @@ POLICYGROUPS_DIR="%s/templates" self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_readpath_home_file(self): - '''Test genpolicy (read-path file in /home)''' + """Test genpolicy (read-path file in /home)""" s = "/home/*/test-foo" p = self._gen_policy(extra_args=['--read-path=%s' % s]) search = "owner %s rk," % s @@ -1112,7 +1125,7 @@ POLICYGROUPS_DIR="%s/templates" self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_readpath_homevar_file(self): - '''Test genpolicy (read-path file in @{HOME})''' + """Test genpolicy (read-path file in @{HOME})""" s = "@{HOME}/test-foo" p = self._gen_policy(extra_args=['--read-path=%s' % s]) search = "owner %s rk," % s @@ -1121,7 +1134,7 @@ POLICYGROUPS_DIR="%s/templates" self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_readpath_homedirs_file(self): - '''Test genpolicy (read-path file in @{HOMEDIRS})''' + """Test genpolicy (read-path file in @{HOMEDIRS})""" s = "@{HOMEDIRS}/test-foo" p = self._gen_policy(extra_args=['--read-path=%s' % s]) search = "owner %s rk," % s @@ -1130,7 +1143,7 @@ POLICYGROUPS_DIR="%s/templates" self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_readpath_dir(self): - '''Test genpolicy (read-path directory/)''' + """Test genpolicy (read-path directory/)""" s = "/opt/test-foo-dir/" p = self._gen_policy(extra_args=['--read-path=%s' % s]) search_terms = ["%s rk," % s, "%s** rk," % s] @@ -1140,7 +1153,7 @@ POLICYGROUPS_DIR="%s/templates" self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_readpath_dir_glob(self): - '''Test genpolicy (read-path directory/*)''' + """Test genpolicy (read-path directory/*)""" s = "/opt/test-foo-dir/*" p = self._gen_policy(extra_args=['--read-path=%s' % s]) search_terms = ["%s rk," % os.path.dirname(s), "%s rk," % s] @@ -1150,7 +1163,7 @@ POLICYGROUPS_DIR="%s/templates" self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_readpath_dir_glob_all(self): - '''Test genpolicy (read-path directory/**)''' + """Test genpolicy (read-path directory/**)""" s = "/opt/test-foo-dir/**" p = self._gen_policy(extra_args=['--read-path=%s' % s]) search_terms = ["%s rk," % os.path.dirname(s), "%s rk," % s] @@ -1160,14 +1173,16 @@ POLICYGROUPS_DIR="%s/templates" self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_readpath_multiple(self): - '''Test genpolicy (read-path multiple)''' - paths = ["/opt/test-foo", - "/home/*/test-foo", - "@{HOME}/test-foo", - "@{HOMEDIRS}/test-foo", - "/opt/test-foo-dir/", - "/opt/test-foo-dir/*", - "/opt/test-foo-dir/**"] + """Test genpolicy (read-path multiple)""" + paths = [ + "/opt/test-foo", + "/home/*/test-foo", + "@{HOME}/test-foo", + "@{HOMEDIRS}/test-foo", + "/opt/test-foo-dir/", + "/opt/test-foo-dir/*", + "/opt/test-foo-dir/**", + ] args = [] search_terms = [] for s in paths: @@ -1192,18 +1207,18 @@ POLICYGROUPS_DIR="%s/templates" self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_readpath_bad(self): - '''Test genpolicy (read-path bad)''' + """Test genpolicy (read-path bad)""" s = "bar" try: self._gen_policy(extra_args=['--read-path=%s' % s]) - except easyprof.AppArmorException: + except AppArmorException: return except Exception: raise - raise Exception ("read-path should be invalid") + raise Exception("read-path should be invalid") def test_genpolicy_writepath_file(self): - '''Test genpolicy (write-path file)''' + """Test genpolicy (write-path file)""" s = "/opt/test-foo" p = self._gen_policy(extra_args=['--write-path=%s' % s]) search = "%s rwk," % s @@ -1212,7 +1227,7 @@ POLICYGROUPS_DIR="%s/templates" self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_writepath_home_file(self): - '''Test genpolicy (write-path file in /home)''' + """Test genpolicy (write-path file in /home)""" s = "/home/*/test-foo" p = self._gen_policy(extra_args=['--write-path=%s' % s]) search = "owner %s rwk," % s @@ -1221,7 +1236,7 @@ POLICYGROUPS_DIR="%s/templates" self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_writepath_homevar_file(self): - '''Test genpolicy (write-path file in @{HOME})''' + """Test genpolicy (write-path file in @{HOME})""" s = "@{HOME}/test-foo" p = self._gen_policy(extra_args=['--write-path=%s' % s]) search = "owner %s rwk," % s @@ -1230,7 +1245,7 @@ POLICYGROUPS_DIR="%s/templates" self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_writepath_homedirs_file(self): - '''Test genpolicy (write-path file in @{HOMEDIRS})''' + """Test genpolicy (write-path file in @{HOMEDIRS})""" s = "@{HOMEDIRS}/test-foo" p = self._gen_policy(extra_args=['--write-path=%s' % s]) search = "owner %s rwk," % s @@ -1239,7 +1254,7 @@ POLICYGROUPS_DIR="%s/templates" self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_writepath_dir(self): - '''Test genpolicy (write-path directory/)''' + """Test genpolicy (write-path directory/)""" s = "/opt/test-foo-dir/" p = self._gen_policy(extra_args=['--write-path=%s' % s]) search_terms = ["%s rwk," % s, "%s** rwk," % s] @@ -1249,7 +1264,7 @@ POLICYGROUPS_DIR="%s/templates" self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_writepath_dir_glob(self): - '''Test genpolicy (write-path directory/*)''' + """Test genpolicy (write-path directory/*)""" s = "/opt/test-foo-dir/*" p = self._gen_policy(extra_args=['--write-path=%s' % s]) search_terms = ["%s rwk," % os.path.dirname(s), "%s rwk," % s] @@ -1259,7 +1274,7 @@ POLICYGROUPS_DIR="%s/templates" self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_writepath_dir_glob_all(self): - '''Test genpolicy (write-path directory/**)''' + """Test genpolicy (write-path directory/**)""" s = "/opt/test-foo-dir/**" p = self._gen_policy(extra_args=['--write-path=%s' % s]) search_terms = ["%s rwk," % os.path.dirname(s), "%s rwk," % s] @@ -1269,14 +1284,16 @@ POLICYGROUPS_DIR="%s/templates" self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_writepath_multiple(self): - '''Test genpolicy (write-path multiple)''' - paths = ["/opt/test-foo", - "/home/*/test-foo", - "@{HOME}/test-foo", - "@{HOMEDIRS}/test-foo", - "/opt/test-foo-dir/", - "/opt/test-foo-dir/*", - "/opt/test-foo-dir/**"] + """Test genpolicy (write-path multiple)""" + paths = [ + "/opt/test-foo", + "/home/*/test-foo", + "@{HOME}/test-foo", + "@{HOMEDIRS}/test-foo", + "/opt/test-foo-dir/", + "/opt/test-foo-dir/*", + "/opt/test-foo-dir/**", + ] args = [] search_terms = [] for s in paths: @@ -1301,18 +1318,18 @@ POLICYGROUPS_DIR="%s/templates" self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_writepath_bad(self): - '''Test genpolicy (write-path bad)''' + """Test genpolicy (write-path bad)""" s = "bar" try: self._gen_policy(extra_args=['--write-path=%s' % s]) - except easyprof.AppArmorException: + except AppArmorException: return except Exception: raise - raise Exception ("write-path should be invalid") + raise Exception("write-path should be invalid") def test_genpolicy_templatevar(self): - '''Test genpolicy (template-var single)''' + """Test genpolicy (template-var single)""" s = "@{FOO}=bar" p = self._gen_policy(extra_args=['--template-var=%s' % s]) k, v = s.split('=') @@ -1322,7 +1339,7 @@ POLICYGROUPS_DIR="%s/templates" self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_templatevar_multiple(self): - '''Test genpolicy (template-var multiple)''' + """Test genpolicy (template-var multiple)""" variables = ['@{FOO}=bar', '@{BAR}=baz'] args = [] for s in variables: @@ -1337,7 +1354,7 @@ POLICYGROUPS_DIR="%s/templates" self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_templatevar_bad(self): - '''Test genpolicy (template-var - bad values)''' + """Test genpolicy (template-var - bad values)""" bad = [ "{FOO}=bar", "@FOO}=bar", @@ -1352,18 +1369,19 @@ POLICYGROUPS_DIR="%s/templates" for s in bad: try: self._gen_policy(extra_args=['--template-var=%s' % s]) - except easyprof.AppArmorException: + except AppArmorException: continue except Exception: raise - raise Exception ("template-var should be invalid") + raise Exception("template-var should be invalid") def test_genpolicy_invalid_template_policy(self): - '''Test genpolicy (invalid template policy)''' + """Test genpolicy (invalid template policy)""" # create a new template template = os.path.join(self.tmpdir, "test-invalid-template") shutil.copy(os.path.join(self.tmpdir, 'templates', self.test_template), template) - contents = open(template).read() + with open(template) as f: + contents = f.read() bad_pol = "" bad_string = "bzzzt" for line in contents.splitlines(): @@ -1372,27 +1390,28 @@ POLICYGROUPS_DIR="%s/templates" else: bad_pol += line bad_pol += "\n" - open(template, 'w').write(bad_pol) + with open(template, 'w') as f: + f.write(bad_pol) try: self._gen_policy(template=template) - except easyprof.AppArmorException: + except AppArmorException: return except Exception: raise - raise Exception ("policy should be invalid") + raise Exception("policy should be invalid") def test_genpolicy_no_binary_without_profile_name(self): - '''Test genpolicy (no binary with no profile name)''' + """Test genpolicy (no binary with no profile name)""" try: easyprof.gen_policy_params(None, self.options) - except easyprof.AppArmorException: + except AppArmorException: return except Exception: raise - raise Exception ("No binary or profile name should have been invalid") + raise Exception("No binary or profile name should have been invalid") def test_genpolicy_with_binary_with_profile_name(self): - '''Test genpolicy (binary with profile name)''' + """Test genpolicy (binary with profile name)""" profile_name = "some-profile-name" p = self._gen_policy(extra_args=['--profile-name=%s' % profile_name]) s = 'profile "%s" "%s" {' % (profile_name, self.binary) @@ -1401,7 +1420,7 @@ POLICYGROUPS_DIR="%s/templates" self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_with_binary_without_profile_name(self): - '''Test genpolicy (binary without profile name)''' + """Test genpolicy (binary without profile name)""" p = self._gen_policy() s = '"%s" {' % (self.binary) self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) @@ -1409,7 +1428,7 @@ POLICYGROUPS_DIR="%s/templates" self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_without_binary_with_profile_name(self): - '''Test genpolicy (no binary with profile name)''' + """Test genpolicy (no binary with profile name)""" profile_name = "some-profile-name" args = self.full_args args.append('--profile-name=%s' % profile_name) @@ -1425,66 +1444,66 @@ POLICYGROUPS_DIR="%s/templates" # manifest tests def test_gen_manifest_policy_with_binary_with_profile_name(self): - '''Test gen_manifest_policy (binary with profile name)''' + """Test gen_manifest_policy (binary with profile name)""" m = Manifest("test_gen_manifest_policy") m.add_binary(self.ls) self._gen_manifest_policy(m) def test_gen_manifest_policy_without_binary_with_profile_name(self): - '''Test gen_manifest_policy (no binary with profile name)''' + """Test gen_manifest_policy (no binary with profile name)""" m = Manifest("test_gen_manifest_policy") self._gen_manifest_policy(m) def test_gen_manifest_policy_templates_system(self): - '''Test gen_manifest_policy (system template)''' + """Test gen_manifest_policy (system template)""" m = Manifest("test_gen_manifest_policy") m.add_template(self.test_template) self._gen_manifest_policy(m) def test_gen_manifest_policy_templates_system_noprefix(self): - '''Test gen_manifest_policy (system template, no security prefix)''' + """Test gen_manifest_policy (system template, no security prefix)""" m = Manifest("test_gen_manifest_policy") m.add_template(self.test_template) self._gen_manifest_policy(m, use_security_prefix=False) def test_gen_manifest_abs_path_template(self): - '''Test gen_manifest_policy (abs path template)''' + """Test gen_manifest_policy (abs path template)""" m = Manifest("test_gen_manifest_policy") m.add_template("/etc/shadow") try: self._gen_manifest_policy(m) - except easyprof.AppArmorException: + except AppArmorException: return except Exception: raise - raise Exception ("abs path template name should be invalid") + raise Exception("abs path template name should be invalid") def test_gen_manifest_escape_path_templates(self): - '''Test gen_manifest_policy (esc path template)''' + """Test gen_manifest_policy (esc path template)""" m = Manifest("test_gen_manifest_policy") m.add_template("../../../../../../../../etc/shadow") try: self._gen_manifest_policy(m) - except easyprof.AppArmorException: + except AppArmorException: return except Exception: raise - raise Exception ("../ template name should be invalid") + raise Exception("../ template name should be invalid") def test_gen_manifest_policy_templates_nonexistent(self): - '''Test gen manifest policy (nonexistent template)''' + """Test gen manifest policy (nonexistent template)""" m = Manifest("test_gen_manifest_policy") m.add_template("nonexistent") try: self._gen_manifest_policy(m) - except easyprof.AppArmorException: + except AppArmorException: return except Exception: raise - raise Exception ("template should be invalid") + raise Exception("template should be invalid") def test_gen_manifest_policy_comment(self): - '''Test gen manifest policy (comment)''' + """Test gen manifest policy (comment)""" s = "test comment" m = Manifest("test_gen_manifest_policy") m.add_comment(s) @@ -1494,7 +1513,7 @@ POLICYGROUPS_DIR="%s/templates" self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_gen_manifest_policy_author(self): - '''Test gen manifest policy (author)''' + """Test gen manifest policy (author)""" s = "Archibald Poindexter" m = Manifest("test_gen_manifest_policy") m.add_author(s) @@ -1504,7 +1523,7 @@ POLICYGROUPS_DIR="%s/templates" self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_gen_manifest_policy_copyright(self): - '''Test genpolicy (copyright)''' + """Test genpolicy (copyright)""" s = "2112/01/01" m = Manifest("test_gen_manifest_policy") m.add_copyright(s) @@ -1514,55 +1533,56 @@ POLICYGROUPS_DIR="%s/templates" self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_gen_manifest_policy_policygroups(self): - '''Test gen manifest policy (single policygroup)''' + """Test gen manifest policy (single policygroup)""" groups = self.test_policygroup m = Manifest("test_gen_manifest_policy") m.add_policygroups(groups) p = self._gen_manifest_policy(m) - for s in ['#include <abstractions/nameservice>', '#include <abstractions/gnome>']: + for s in ('#include <abstractions/nameservice>', '#include <abstractions/gnome>'): self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) inv_s = '###POLICYGROUPS###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_gen_manifest_policy_policygroups_multiple(self): - '''Test genpolicy (multiple policygroups)''' + """Test genpolicy (multiple policygroups)""" test_policygroup2 = "test-policygroup2" contents = ''' # %s #include <abstractions/kde> #include <abstractions/openssl> ''' % (self.test_policygroup) - open(os.path.join(self.tmpdir, 'policygroups', test_policygroup2), 'w').write(contents) + with open(os.path.join(self.tmpdir, 'policygroups', test_policygroup2), 'w') as f: + f.write(contents) groups = "%s,%s" % (self.test_policygroup, test_policygroup2) m = Manifest("test_gen_manifest_policy") m.add_policygroups(groups) p = self._gen_manifest_policy(m) - for s in ['#include <abstractions/nameservice>', + for s in ('#include <abstractions/nameservice>', '#include <abstractions/gnome>', '#include <abstractions/kde>', - '#include <abstractions/openssl>']: + '#include <abstractions/openssl>'): self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) inv_s = '###POLICYGROUPS###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_gen_manifest_policy_policygroups_nonexistent(self): - '''Test gen manifest policy (nonexistent policygroup)''' + """Test gen manifest policy (nonexistent policygroup)""" groups = "nonexistent" m = Manifest("test_gen_manifest_policy") m.add_policygroups(groups) try: self._gen_manifest_policy(m) - except easyprof.AppArmorException: + except AppArmorException: return except Exception: raise - raise Exception ("policygroup should be invalid") + raise Exception("policygroup should be invalid") def test_gen_manifest_policy_templatevar(self): - '''Test gen manifest policy (template-var single)''' + """Test gen manifest policy (template-var single)""" m = Manifest("test_gen_manifest_policy") m.add_template_variable("FOO", "bar") p = self._gen_manifest_policy(m) @@ -1572,7 +1592,7 @@ POLICYGROUPS_DIR="%s/templates" self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_gen_manifest_policy_templatevar_multiple(self): - '''Test gen manifest policy (template-var multiple)''' + """Test gen manifest policy (template-var multiple)""" variables = [["FOO", "bar"], ["BAR", "baz"]] m = Manifest("test_gen_manifest_policy") for s in variables: @@ -1586,25 +1606,26 @@ POLICYGROUPS_DIR="%s/templates" self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_gen_manifest_policy_invalid_keys(self): - '''Test gen manifest policy (invalid keys)''' - keys = ['config_file', - 'debug', - 'help', - 'list-templates', - 'list_templates', - 'show-template', - 'show_template', - 'list-policy-groups', - 'list_policy_groups', - 'show-policy-group', - 'show_policy_group', - 'templates-dir', - 'templates_dir', - 'policy-groups-dir', - 'policy_groups_dir', - 'nonexistent', - 'no_verify', - ] + """Test gen manifest policy (invalid keys)""" + keys = [ + 'config_file', + 'debug', + 'help', + 'list-templates', + 'list_templates', + 'show-template', + 'show_template', + 'list-policy-groups', + 'list_policy_groups', + 'show-policy-group', + 'show_policy_group', + 'templates-dir', + 'templates_dir', + 'policy-groups-dir', + 'policy_groups_dir', + 'nonexistent', + 'no_verify', + ] args = self.full_args args.append("--manifest=/dev/null") @@ -1616,12 +1637,12 @@ POLICYGROUPS_DIR="%s/templates" j = json.dumps(security, indent=2) try: easyprof.parse_manifest(j, self.options) - except easyprof.AppArmorException: + except AppArmorException: continue - raise Exception ("'%s' should be invalid" % k) + raise Exception("'%s' should be invalid" % k) def test_gen_manifest(self): - '''Test gen_manifest''' + """Test gen_manifest""" # this should come from manpage m = '''{ "security": { @@ -1661,7 +1682,7 @@ POLICYGROUPS_DIR="%s/templates" } }''' - for d in ['policygroups', 'templates']: + for d in ('policygroups', 'templates'): shutil.copytree(os.path.join(self.tmpdir, d), os.path.join(self.tmpdir, d, "somevendor/1.0")) @@ -1677,7 +1698,7 @@ POLICYGROUPS_DIR="%s/templates" self.assertEqual(m, man_new) def test_gen_manifest_ubuntu(self): - '''Test gen_manifest (ubuntu)''' + """Test gen_manifest (ubuntu)""" # this should be based on the manpage (but use existing policy_groups # and template m = '''{ @@ -1701,7 +1722,7 @@ POLICYGROUPS_DIR="%s/templates" } }''' - for d in ['policygroups', 'templates']: + for d in ('policygroups', 'templates'): shutil.copytree(os.path.join(self.tmpdir, d), os.path.join(self.tmpdir, d, "ubuntu/1.0")) @@ -1717,7 +1738,7 @@ POLICYGROUPS_DIR="%s/templates" self.assertEqual(m, man_new) def test_parse_manifest_no_version(self): - '''Test parse_manifest (vendor with no version)''' + """Test parse_manifest (vendor with no version)""" # this should come from manpage m = '''{ "security": { @@ -1744,12 +1765,12 @@ POLICYGROUPS_DIR="%s/templates" (binary, self.options) = easyprof.parse_manifest(m, self.options)[0] try: easyprof.AppArmorEasyProfile(binary, self.options) - except easyprof.AppArmorException: + except AppArmorException: return - raise Exception ("Should have failed on missing version") + raise Exception("Should have failed on missing version") def test_parse_manifest_no_vendor(self): - '''Test parse_manifest (version with no vendor)''' + """Test parse_manifest (version with no vendor)""" # this should come from manpage m = '''{ "security": { @@ -1776,12 +1797,12 @@ POLICYGROUPS_DIR="%s/templates" (binary, self.options) = easyprof.parse_manifest(m, self.options)[0] try: easyprof.AppArmorEasyProfile(binary, self.options) - except easyprof.AppArmorException: + except AppArmorException: return - raise Exception ("Should have failed on missing vendor") + raise Exception("Should have failed on missing vendor") def test_parse_manifest_multiple(self): - '''Test parse_manifest_multiple''' + """Test parse_manifest_multiple""" m = '''{ "security": { "profiles": { @@ -1830,7 +1851,7 @@ POLICYGROUPS_DIR="%s/templates" } }''' - for d in ['policygroups', 'templates']: + for d in ('policygroups', 'templates'): shutil.copytree(os.path.join(self.tmpdir, d), os.path.join(self.tmpdir, d, "ubuntu/1.0")) @@ -1844,7 +1865,6 @@ POLICYGROUPS_DIR="%s/templates" easyp.gen_manifest(params) easyp.gen_policy(**params) - # verify manifest tests def _verify_manifest(self, m, expected, invalid=False): args = self.full_args @@ -1852,18 +1872,18 @@ POLICYGROUPS_DIR="%s/templates" (self.options, self.args) = easyprof.parse_args(args) try: (binary, options) = easyprof.parse_manifest(m, self.options)[0] - except easyprof.AppArmorException: + except AppArmorException: if invalid: return raise params = easyprof.gen_policy_params(binary, options) if expected: - self.assertTrue(easyprof.verify_manifest(params, args), "params=%s\nmanifest=%s" % (params,m)) + self.assertTrue(easyprof.verify_manifest(params, args), "params=%s\nmanifest=%s" % (params, m)) else: - self.assertFalse(easyprof.verify_manifest(params, args), "params=%s\nmanifest=%s" % (params,m)) + self.assertFalse(easyprof.verify_manifest(params, args), "params=%s\nmanifest=%s" % (params, m)) def test_verify_manifest_full(self): - '''Test verify_manifest (full)''' + """Test verify_manifest (full)""" m = '''{ "security": { "profiles": { @@ -1892,7 +1912,7 @@ POLICYGROUPS_DIR="%s/templates" self._verify_manifest(m, expected=True) def test_verify_manifest_full_bad(self): - '''Test verify_manifest (full bad)''' + """Test verify_manifest (full bad)""" m = '''{ "security": { "profiles": { @@ -1938,7 +1958,7 @@ POLICYGROUPS_DIR="%s/templates" self._verify_manifest(m, expected=False, invalid=True) def test_verify_manifest_binary(self): - '''Test verify_manifest (binary in /usr)''' + """Test verify_manifest (binary in /usr)""" m = '''{ "security": { "profiles": { @@ -1952,7 +1972,7 @@ POLICYGROUPS_DIR="%s/templates" self._verify_manifest(m, expected=True) def test_verify_manifest_profile_profile_name_bad(self): - '''Test verify_manifest (bad profile_name)''' + """Test verify_manifest (bad profile_name)""" m = '''{ "security": { "profiles": { @@ -1978,7 +1998,7 @@ POLICYGROUPS_DIR="%s/templates" self._verify_manifest(m, expected=False) def test_verify_manifest_profile_profile_name(self): - '''Test verify_manifest (profile_name)''' + """Test verify_manifest (profile_name)""" m = '''{ "security": { "profiles": { @@ -1992,7 +2012,7 @@ POLICYGROUPS_DIR="%s/templates" self._verify_manifest(m, expected=True) def test_verify_manifest_profile_abstractions(self): - '''Test verify_manifest (abstractions)''' + """Test verify_manifest (abstractions)""" m = '''{ "security": { "profiles": { @@ -2009,7 +2029,7 @@ POLICYGROUPS_DIR="%s/templates" self._verify_manifest(m, expected=True) def test_verify_manifest_profile_abstractions_bad(self): - '''Test verify_manifest (bad abstractions)''' + """Test verify_manifest (bad abstractions)""" m = '''{ "security": { "profiles": { @@ -2026,7 +2046,7 @@ POLICYGROUPS_DIR="%s/templates" self._verify_manifest(m, expected=False) def test_verify_manifest_profile_template_var(self): - '''Test verify_manifest (good template_var)''' + """Test verify_manifest (good template_var)""" m = '''{ "security": { "profiles": { @@ -2045,8 +2065,8 @@ POLICYGROUPS_DIR="%s/templates" self._verify_manifest(m, expected=True) def test_verify_manifest_profile_template_var_bad(self): - '''Test verify_manifest (bad template_var)''' - for v in ['"VAR1": "f*o"', + """Test verify_manifest (bad template_var)""" + for v in ('"VAR1": "f*o"', '"VAR2": "*foo"', '"VAR3": "fo*"', '"VAR4": "b{ar"', @@ -2054,8 +2074,8 @@ POLICYGROUPS_DIR="%s/templates" '"VAR6": "b}ar"', '"VAR7": "bar[0-9]"', '"VAR8": "b{ar"', - '"VAR9": "foo/bar"' # this is valid, but potentially unsafe - ]: + '"VAR9": "foo/bar"' # this is valid, but potentially unsafe + ): m = '''{ "security": { "profiles": { @@ -2072,7 +2092,7 @@ POLICYGROUPS_DIR="%s/templates" self._verify_manifest(m, expected=False) def test_manifest_invalid(self): - '''Test invalid manifest (parse error)''' + """Test invalid manifest (parse error)""" m = '''{ "security": { "com.example.foo": { @@ -2085,7 +2105,7 @@ POLICYGROUPS_DIR="%s/templates" self._verify_manifest(m, expected=False, invalid=True) def test_manifest_invalid2(self): - '''Test invalid manifest (profile_name is not key)''' + """Test invalid manifest (profile_name is not key)""" m = '''{ "security": { "binary": "/opt/com.example/foo/**", @@ -2098,7 +2118,7 @@ POLICYGROUPS_DIR="%s/templates" self._verify_manifest(m, expected=False, invalid=True) def test_manifest_invalid3(self): - '''Test invalid manifest (profile_name in dict)''' + """Test invalid manifest (profile_name in dict)""" m = '''{ "security": { "binary": "/opt/com.example/foo/**", @@ -2112,12 +2132,12 @@ POLICYGROUPS_DIR="%s/templates" self._verify_manifest(m, expected=False, invalid=True) def test_manifest_invalid4(self): - '''Test invalid manifest (bad path in template var)''' - for v in ['"VAR1": "/tmp/../etc/passwd"', + """Test invalid manifest (bad path in template var)""" + for v in ('"VAR1": "/tmp/../etc/passwd"', '"VAR2": "./"', '"VAR3": "foo\"bar"', '"VAR4": "foo//bar"', - ]: + ): m = '''{ "security": { "profiles": { @@ -2138,15 +2158,14 @@ POLICYGROUPS_DIR="%s/templates" params = easyprof.gen_policy_params(binary, options) try: easyprof.verify_manifest(params) - except easyprof.AppArmorException: + except AppArmorException: return - raise Exception ("Should have failed with invalid variable declaration") - + raise Exception("Should have failed with invalid variable declaration") # policy version tests def test_policy_vendor_manifest_nonexistent(self): - '''Test policy vendor via manifest (nonexistent)''' + """Test policy vendor via manifest (nonexistent)""" m = '''{ "security": { "profiles": { @@ -2167,13 +2186,13 @@ POLICYGROUPS_DIR="%s/templates" (binary, self.options) = easyprof.parse_manifest(m, self.options)[0] try: easyprof.AppArmorEasyProfile(binary, self.options) - except easyprof.AppArmorException: + except AppArmorException: return - raise Exception ("Should have failed with non-existent directory") + raise Exception("Should have failed with non-existent directory") def test_policy_version_manifest(self): - '''Test policy version via manifest (good)''' + """Test policy version via manifest (good)""" policy_vendor = "somevendor" policy_version = "1.0" policy_subdir = "%s/%s" % (policy_vendor, policy_version) @@ -2189,7 +2208,7 @@ POLICYGROUPS_DIR="%s/templates" } } }''' % (policy_vendor, policy_version) - for d in ['policygroups', 'templates']: + for d in ('policygroups', 'templates'): shutil.copytree(os.path.join(self.tmpdir, d), os.path.join(self.tmpdir, d, policy_subdir)) @@ -2213,13 +2232,13 @@ POLICYGROUPS_DIR="%s/templates" easyp.gen_policy(**params) def test_policy_vendor_version_args(self): - '''Test policy vendor and version via command line args (good)''' + """Test policy vendor and version via command line args (good)""" policy_version = "1.0" policy_vendor = "somevendor" policy_subdir = "%s/%s" % (policy_vendor, policy_version) # Create the directories - for d in ['policygroups', 'templates']: + for d in ('policygroups', 'templates'): shutil.copytree(os.path.join(self.tmpdir, d), os.path.join(self.tmpdir, d, policy_subdir)) @@ -2234,19 +2253,19 @@ POLICYGROUPS_DIR="%s/templates" tdir = os.path.join(self.tmpdir, 'templates', policy_subdir) for t in easyp.get_templates(): - self.assertTrue(t.startswith(tdir), \ + self.assertTrue(t.startswith(tdir), "'%s' does not start with '%s'" % (t, tdir)) pdir = os.path.join(self.tmpdir, 'policygroups', policy_subdir) for p in easyp.get_policy_groups(): - self.assertTrue(p.startswith(pdir), \ + self.assertTrue(p.startswith(pdir), "'%s' does not start with '%s'" % (p, pdir)) params = easyprof.gen_policy_params(self.binary, self.options) easyp.gen_policy(**params) def test_policy_vendor_args_nonexistent(self): - '''Test policy vendor via command line args (nonexistent)''' + """Test policy vendor via command line args (nonexistent)""" policy_vendor = "nonexistent" policy_version = "1.0" args = self.full_args @@ -2257,19 +2276,19 @@ POLICYGROUPS_DIR="%s/templates" (self.options, self.args) = easyprof.parse_args(self.full_args + [self.binary]) try: easyprof.AppArmorEasyProfile(self.binary, self.options) - except easyprof.AppArmorException: + except AppArmorException: return - raise Exception ("Should have failed with non-existent directory") + raise Exception("Should have failed with non-existent directory") def test_policy_version_args_bad(self): - '''Test policy version via command line args (bad)''' + """Test policy version via command line args (bad)""" bad = [ - "../../../../../../etc", - "notanumber", - "v1.0a", - "-1", - ] + "../../../../../../etc", + "notanumber", + "v1.0a", + "-1", + ] for policy_version in bad: args = self.full_args args.append("--policy-version=%s" % policy_version) @@ -2279,18 +2298,18 @@ POLICYGROUPS_DIR="%s/templates" (self.options, self.args) = easyprof.parse_args(self.full_args + [self.binary]) try: easyprof.AppArmorEasyProfile(self.binary, self.options) - except easyprof.AppArmorException: + except AppArmorException: continue - raise Exception ("Should have failed with bad version") + raise Exception("Should have failed with bad version") def test_policy_vendor_args_bad(self): - '''Test policy vendor via command line args (bad)''' + """Test policy vendor via command line args (bad)""" bad = [ - "../../../../../../etc", - "vendor with space", - "semicolon;isbad", - ] + "../../../../../../etc", + "vendor with space", + "semicolon;isbad", + ] for policy_vendor in bad: args = self.full_args args.append("--policy-vendor=%s" % policy_vendor) @@ -2300,14 +2319,14 @@ POLICYGROUPS_DIR="%s/templates" (self.options, self.args) = easyprof.parse_args(self.full_args + [self.binary]) try: easyprof.AppArmorEasyProfile(self.binary, self.options) - except easyprof.AppArmorException: + except AppArmorException: continue - raise Exception ("Should have failed with bad vendor") + raise Exception("Should have failed with bad vendor") # output_directory tests def test_output_directory_multiple(self): - '''Test output_directory (multiple)''' + """Test output_directory (multiple)""" files = dict() files["com.example.foo"] = "com.example.foo" files["com.ubuntu.developer.myusername.MyCoolApp"] = "com.ubuntu.developer.myusername.MyCoolApp" @@ -2388,7 +2407,7 @@ POLICYGROUPS_DIR="%s/templates" self.assertTrue(os.path.exists(f), "Could not find '%s'" % f) def test_output_directory_single(self): - '''Test output_directory (single)''' + """Test output_directory (single)""" files = dict() files["com.example.foo"] = "com.example.foo" m = '''{ @@ -2442,11 +2461,8 @@ POLICYGROUPS_DIR="%s/templates" f = os.path.join(out_dir, fn) self.assertTrue(os.path.exists(f), "Could not find '%s'" % f) - - - def test_output_directory_invalid(self): - '''Test output_directory (output directory exists as file)''' + """Test output_directory (output directory exists as file)""" files = dict() files["usr.bin.baz"] = "/usr/bin/baz" m = '''{ @@ -2467,7 +2483,6 @@ POLICYGROUPS_DIR="%s/templates" } }''' % files["usr.bin.baz"] - out_dir = os.path.join(self.tmpdir, "output") open(out_dir, 'w').close() @@ -2479,12 +2494,12 @@ POLICYGROUPS_DIR="%s/templates" params = easyprof.gen_policy_params(binary, options) try: easyp.output_policy(params, dir=out_dir) - except easyprof.AppArmorException: + except AppArmorException: return - raise Exception ("Should have failed with 'is not a directory'") + raise Exception("Should have failed with 'is not a directory'") def test_output_directory_invalid_params(self): - '''Test output_directory (no binary or profile_name)''' + """Test output_directory (no binary or profile_name)""" files = dict() files["usr.bin.baz"] = "/usr/bin/baz" m = '''{ @@ -2516,12 +2531,12 @@ POLICYGROUPS_DIR="%s/templates" del params['binary'] try: easyp.output_policy(params, dir=out_dir) - except easyprof.AppArmorException: + except AppArmorException: return - raise Exception ("Should have failed with 'Must specify binary and/or profile name'") + raise Exception("Should have failed with 'Must specify binary and/or profile name'") def test_output_directory_invalid2(self): - '''Test output_directory (profile exists)''' + """Test output_directory (profile exists)""" files = dict() files["usr.bin.baz"] = "/usr/bin/baz" m = '''{ @@ -2554,12 +2569,12 @@ POLICYGROUPS_DIR="%s/templates" params = easyprof.gen_policy_params(binary, options) try: easyp.output_policy(params, dir=out_dir) - except easyprof.AppArmorException: + except AppArmorException: return - raise Exception ("Should have failed with 'already exists'") + raise Exception("Should have failed with 'already exists'") def test_output_directory_args(self): - '''Test output_directory (args)''' + """Test output_directory (args)""" files = dict() files["usr.bin.baz"] = "/usr/bin/baz" @@ -2585,82 +2600,88 @@ POLICYGROUPS_DIR="%s/templates" # utility classes # def test_valid_profile_name(self): - '''Test valid_profile_name''' - names = ['foo', - 'com.example.foo', - '/usr/bin/foo', - 'com.example.app_myapp_1:2.3+ab12~foo', - ] + """Test valid_profile_name""" + names = [ + 'foo', + 'com.example.foo', + '/usr/bin/foo', + 'com.example.app_myapp_1:2.3+ab12~foo', + ] for n in names: self.assertTrue(easyprof.valid_profile_name(n), "'%s' should be valid" % n) def test_valid_profile_name_invalid(self): - '''Test valid_profile_name (invalid)''' - names = ['fo/o', - '/../../etc/passwd', - '../../etc/passwd', - './../etc/passwd', - './etc/passwd', - '/usr/bin//foo', - '/usr/bin/./foo', - 'foo`', - 'foo!', - 'foo@', - 'foo$', - 'foo#', - 'foo%', - 'foo^', - 'foo&', - 'foo*', - 'foo(', - 'foo)', - 'foo=', - 'foo{', - 'foo}', - 'foo[', - 'foo]', - 'foo|', - 'foo/', - 'foo\\', - 'foo;', - 'foo\'', - 'foo"', - 'foo<', - 'foo>', - 'foo?', - 'foo\/', - 'foo,', - '_foo', - ] + """Test valid_profile_name (invalid)""" + names = [ + 'fo/o', + '/../../etc/passwd', + '../../etc/passwd', + './../etc/passwd', + './etc/passwd', + '/usr/bin//foo', + '/usr/bin/./foo', + 'foo`', + 'foo!', + 'foo@', + 'foo$', + 'foo#', + 'foo%', + 'foo^', + 'foo&', + 'foo*', + 'foo(', + 'foo)', + 'foo=', + 'foo{', + 'foo}', + 'foo[', + 'foo]', + 'foo|', + 'foo/', + 'foo\\', + 'foo;', + "foo'", + 'foo"', + 'foo<', + 'foo>', + 'foo?', + r'foo\/', + 'foo,', + '_foo', + ] for n in names: self.assertFalse(easyprof.valid_profile_name(n), "'%s' should be invalid" % n) def test_valid_path(self): - '''Test valid_path''' - names = ['/bin/bar', - '/etc/apparmor.d/com.example.app_myapp_1:2.3+ab12~foo', - ] - names_rel = ['bin/bar', - 'apparmor.d/com.example.app_myapp_1:2.3+ab12~foo', - 'com.example.app_myapp_1:2.3+ab12~foo', - ] + """Test valid_path""" + names = [ + '/bin/bar', + '/etc/apparmor.d/com.example.app_myapp_1:2.3+ab12~foo', + ] + names_rel = [ + 'bin/bar', + 'apparmor.d/com.example.app_myapp_1:2.3+ab12~foo', + 'com.example.app_myapp_1:2.3+ab12~foo', + ] for n in names: self.assertTrue(easyprof.valid_path(n), "'%s' should be valid" % n) for n in names_rel: self.assertTrue(easyprof.valid_path(n, relative_ok=True), "'%s' should be valid" % n) def test_zz_valid_path_invalid(self): - '''Test valid_path (invalid)''' - names = ['/bin//bar', - 'bin/bar', - '/../etc/passwd', - './bin/bar', - './', - ] - names_rel = ['bin/../bar', - 'apparmor.d/../passwd', - 'com.example.app_"myapp_1:2.3+ab12~foo', - ] + """Test valid_path (invalid)""" + names = [ + '/bin//bar', + 'bin/bar', + '/../etc/passwd', + './bin/bar', + './', + ] + names_rel = [ + 'bin/../bar', + 'apparmor.d/../passwd', + 'com.example.app_"myapp_1:2.3+ab12~foo', + ] for n in names: self.assertFalse(easyprof.valid_path(n, relative_ok=False), "'%s' should be invalid" % n) for n in names_rel: diff --git a/utils/test/test-aa-notify.py b/utils/test/test-aa-notify.py index cfb5fa5a8..d87e56193 100644 --- a/utils/test/test-aa-notify.py +++ b/utils/test/test-aa-notify.py @@ -13,17 +13,18 @@ import os import signal import subprocess -import tempfile import time import unittest +from tempfile import NamedTemporaryFile -from common_test import AATest, setup_all_loops, setup_aa import apparmor.aa as aa +from common_test import AATest, setup_aa, setup_all_loops # The location of the aa-notify utility can be overridden by setting # the APPARMOR_NOTIFY environment variable; this is useful for running # these tests in an installed environment -aanotify_bin = "../aa-notify" +aanotify_bin = ["../aa-notify"] + # http://www.chiark.greenend.org.uk/ucgi/~cjwatson/blosxom/2009-07-02-python-sigpipe.html # This is needed so that the subprocesses that produce endless output @@ -35,8 +36,8 @@ def subprocess_setup(): def cmd(command): - '''Try to execute given command (array) and return its stdout, or return - a textual error if it failed.''' + """Try to execute given command (array) and return its stdout, or return + a textual error if it failed.""" try: sp = subprocess.Popen( @@ -48,7 +49,7 @@ def cmd(command): preexec_fn=subprocess_setup ) except OSError as e: - return [127, str(e)] + return 127, str(e) stdout, stderr = sp.communicate(input) @@ -59,13 +60,13 @@ def cmd(command): else: out = stdout - return [sp.returncode, out.decode('utf-8')] + return sp.returncode, out.decode('utf-8') class AANotifyTest(AATest): def AASetup(self): - '''Create temporary log file with 30 enties of different age''' + """Create temporary log file with 30 enties of different age""" test_logfile_contents_999_days_old = \ '''Feb 4 13:40:38 XPS-13-9370 kernel: [128552.834382] audit: type=1400 audit({epoch}:113): apparmor="ALLOWED" operation="exec" profile="libreoffice-soffice" name="/bin/uname" pid=4097 comm="sh" requested_mask="x" denied_mask="x" fsuid=1001 ouid=0 target="libreoffice-soffice//null-/bin/uname" @@ -112,19 +113,17 @@ Feb 4 13:40:38 XPS-13-9370 kernel: [128552.875891] audit: type=1400 audit({epoc Feb 4 13:40:38 XPS-13-9370 kernel: [128552.880347] audit: type=1400 audit({epoch}:122): apparmor="ALLOWED" operation="file_mmap" profile="libreoffice-soffice//null-/usr/bin/file" name="/usr/bin/file" pid=4111 comm="file" requested_mask="rm" denied_mask="rm" fsuid=1001 ouid=0 '''.format(epoch=round(time.time(), 3)) - handle, self.test_logfile = tempfile.mkstemp(prefix='test-aa-notify-') - os.close(handle) - handle = open(self.test_logfile, "w+") - handle.write( - test_logfile_contents_999_days_old + - test_logfile_contents_30_days_old + - test_logfile_contents_unrelevant_entries + - test_logfile_contents_0_seconds_old - ) - handle.close() + with NamedTemporaryFile("w+", prefix='test-aa-notify-', delete=False) as temp_file: + self.test_logfile = temp_file.name + temp_file.write( + test_logfile_contents_999_days_old + + test_logfile_contents_30_days_old + + test_logfile_contents_unrelevant_entries + + test_logfile_contents_0_seconds_old + ) def AATeardown(self): - '''Remove temporary log file after tests ended''' + """Remove temporary log file after tests ended""" if self.test_logfile and os.path.exists(self.test_logfile): os.remove(self.test_logfile) @@ -133,19 +132,19 @@ Feb 4 13:40:38 XPS-13-9370 kernel: [128552.880347] audit: type=1400 audit({epoc # before printing help when invoked without arguments (sic!). @unittest.skipUnless(os.path.isfile('/var/log/kern.log'), 'Requires kern.log on system') def test_no_arguments(self): - '''Test using no arguments at all''' + """Test using no arguments at all""" expected_return_code = 0 expected_output_has = 'usage: aa-notify' - return_code, output = cmd([aanotify_bin]) + return_code, output = cmd(aanotify_bin) result = 'Got return code %d, expected %d\n' % (return_code, expected_return_code) self.assertEqual(expected_return_code, return_code, result + output) result = 'Got output "%s", expected "%s"\n' % (output, expected_output_has) self.assertIn(expected_output_has, output, result + output) def test_help_contents(self): - '''Test output of help text''' + """Test output of help text""" expected_return_code = 0 expected_output_1 = \ @@ -173,7 +172,7 @@ Display AppArmor notifications or messages for DENIED entries. --debug debug mode ''' - return_code, output = cmd([aanotify_bin, '--help']) + return_code, output = cmd(aanotify_bin + ['--help']) result = 'Got return code %d, expected %d\n' % (return_code, expected_return_code) self.assertEqual(expected_return_code, return_code, result + output) @@ -181,12 +180,12 @@ Display AppArmor notifications or messages for DENIED entries. self.assertIn(expected_output_2, output) def test_entries_since_100_days(self): - '''Test showing log entries since 100 days''' + """Test showing log entries since 100 days""" expected_return_code = 0 expected_output_has = 'AppArmor denials: 20 (since' - return_code, output = cmd([aanotify_bin, '-f', self.test_logfile, '-s', '100']) + return_code, output = cmd(aanotify_bin + ['-f', self.test_logfile, '-s', '100']) result = 'Got return code %d, expected %d\n' % (return_code, expected_return_code) self.assertEqual(expected_return_code, return_code, result + output) result = 'Got output "%s", expected "%s"\n' % (output, expected_output_has) @@ -194,12 +193,12 @@ Display AppArmor notifications or messages for DENIED entries. @unittest.skipUnless(os.path.isfile('/var/log/wtmp'), 'Requires wtmp on system') def test_entries_since_login(self): - '''Test showing log entries since last login''' + """Test showing log entries since last login""" expected_return_code = 0 expected_output_has = 'AppArmor denials: 10 (since' - return_code, output = cmd([aanotify_bin, '-f', self.test_logfile, '-l']) + return_code, output = cmd(aanotify_bin + ['-f', self.test_logfile, '-l']) if "ERROR: Could not find last login" in output: self.skipTest('Could not find last login') result = 'Got return code %d, expected %d\n' % (return_code, expected_return_code) @@ -209,7 +208,7 @@ Display AppArmor notifications or messages for DENIED entries. @unittest.skipUnless(os.path.isfile('/var/log/wtmp'), 'Requires wtmp on system') def test_entries_since_login_verbose(self): - '''Test showing log entries since last login in verbose mode''' + """Test showing log entries since last login in verbose mode""" expected_return_code = 0 expected_output_has = \ @@ -275,7 +274,7 @@ Logfile: {logfile} AppArmor denials: 10 (since'''.format(logfile=self.test_logfile) - return_code, output = cmd([aanotify_bin, '-f', self.test_logfile, '-l', '-v']) + return_code, output = cmd(aanotify_bin + ['-f', self.test_logfile, '-l', '-v']) if "ERROR: Could not find last login" in output: self.skipTest('Could not find last login') result = 'Got return code %d, expected %d\n' % (return_code, expected_return_code) @@ -289,4 +288,8 @@ setup_all_loops(__name__) if __name__ == '__main__': if 'APPARMOR_NOTIFY' in os.environ: aanotify_bin = os.environ['APPARMOR_NOTIFY'] + + if '__AA_CONFDIR' in os.environ: + aanotify_bin = aanotify_bin + ['--configdir', os.getenv('__AA_CONFDIR')] + unittest.main(verbosity=1) diff --git a/utils/test/test-aa.py b/utils/test/test-aa.py index 89a5c3da3..13f203bd1 100644 --- a/utils/test/test-aa.py +++ b/utils/test/test-aa.py @@ -1,7 +1,7 @@ #! /usr/bin/python3 # ------------------------------------------------------------------ # -# Copyright (C) 2014-2015 Christian Boltz +# Copyright (C) 2014-2021 Christian Boltz # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public @@ -9,23 +9,20 @@ # # ------------------------------------------------------------------ -import unittest -from common_test import AATest, setup_all_loops, setup_aa -from common_test import read_file, write_file - import os import shutil -import sys +import unittest import apparmor.aa # needed to set global vars in some tests -from apparmor.aa import (check_for_apparmor, get_output, get_reqs, get_interpreter_and_abstraction, create_new_profile, - get_profile_flags, change_profile_flags, set_options_audit_mode, set_options_owner_mode, is_skippable_file, is_skippable_dir, - parse_profile_start, parse_profile_data, write_header, - get_file_perms, propose_file_rules) +from apparmor.aa import ( + change_profile_flags, check_for_apparmor, create_new_profile, get_file_perms, get_interpreter_and_abstraction, get_output, get_profile_flags, get_reqs, + merged_to_split, parse_profile_data, propose_file_rules, set_options_audit_mode, set_options_owner_mode, split_to_merged) from apparmor.aare import AARE -from apparmor.common import AppArmorException, AppArmorBug +from apparmor.common import AppArmorBug, AppArmorException, is_skippable_file from apparmor.rule.file import FileRule from apparmor.rule.include import IncludeRule +from common_test import AATest, read_file, setup_aa, setup_all_loops, write_file + class AaTestWithTempdir(AATest): def AASetup(self): @@ -36,12 +33,14 @@ class AaTest_check_for_apparmor(AaTestWithTempdir): FILESYSTEMS_WITH_SECURITYFS = 'nodev\tdevtmpfs\nnodev\tsecurityfs\nnodev\tsockfs\n\text3\n\text2\n\text4' FILESYSTEMS_WITHOUT_SECURITYFS = 'nodev\tdevtmpfs\nnodev\tsockfs\n\text3\n\text2\n\text4' - MOUNTS_WITH_SECURITYFS = ( 'proc /proc proc rw,relatime 0 0\n' + MOUNTS_WITH_SECURITYFS = ( + 'proc /proc proc rw,relatime 0 0\n' 'securityfs %s/security securityfs rw,nosuid,nodev,noexec,relatime 0 0\n' - '/dev/sda1 / ext3 rw,noatime,data=ordered 0 0' ) + '/dev/sda1 / ext3 rw,noatime,data=ordered 0 0') - MOUNTS_WITHOUT_SECURITYFS = ( 'proc /proc proc rw,relatime 0 0\n' - '/dev/sda1 / ext3 rw,noatime,data=ordered 0 0' ) + MOUNTS_WITHOUT_SECURITYFS = ( + 'proc /proc proc rw,relatime 0 0\n' + '/dev/sda1 / ext3 rw,noatime,data=ordered 0 0') def test_check_for_apparmor_None_1(self): filesystems = write_file(self.tmpdir, 'filesystems', self.FILESYSTEMS_WITHOUT_SECURITYFS) @@ -78,55 +77,48 @@ class AaTest_check_for_apparmor(AaTestWithTempdir): mounts = write_file(self.tmpdir, 'mounts', self.MOUNTS_WITH_SECURITYFS % self.tmpdir) self.assertEqual('%s/security/apparmor' % self.tmpdir, check_for_apparmor(filesystems, mounts)) + class AATest_get_output(AATest): - tests = [ - (['./fake_ldd', '/AATest/lib64/libc-2.22.so'], (0, [' /AATest/lib64/ld-linux-x86-64.so.2 (0x0000556858473000)', ' linux-vdso.so.1 (0x00007ffe98912000)'] )), - (['./fake_ldd', '/tmp/aa-test-foo'], (0, [' not a dynamic executable'] )), - (['./fake_ldd', 'invalid'], (1, [] )), # stderr is not part of output - ] + tests = ( + (('./fake_ldd', '/AATest/lib64/libc-2.22.so'), (0, [' /AATest/lib64/ld-linux-x86-64.so.2 (0x0000556858473000)', ' linux-vdso.so.1 (0x00007ffe98912000)'])), + (('./fake_ldd', '/tmp/aa-test-foo'), (0, [' not a dynamic executable'])), + (('./fake_ldd', 'invalid'), (1, [])), # stderr is not part of output + ) + def _run_test(self, params, expected): self.assertEqual(get_output(params), expected) def test_get_output_nonexisting(self): with self.assertRaises(AppArmorException): - ret, output = get_output(['./_file_/_not_/_found_']) + ret, output = get_output(('./_file_/_not_/_found_',)) + class AATest_get_reqs(AATest): - tests = [ + tests = ( ('/AATest/bin/bash', ['/AATest/lib64/libreadline.so.6', '/AATest/lib64/libtinfo.so.6', '/AATest/lib64/libdl.so.2', '/AATest/lib64/libc.so.6', '/AATest/lib64/ld-linux-x86-64.so.2']), ('/tmp/aa-test-foo', []), ('/AATest/sbin/ldconfig', []), # comes with $? == 1 - ] + ) def _run_test(self, params, expected): - # for some reason, setting the ldd config option does not get - # honored in python2.7 - # XXX KILL when python 2.7 is dropped XXX - if sys.version_info[0] < 3: - print("Skipping on python < 3.x") - return apparmor.aa.cfg['settings']['ldd'] = './fake_ldd' - self.assertEqual(get_reqs(params), expected) + class AaTest_create_new_profile(AATest): - tests = [ - # file content expected interpreter expected abstraction (besides 'base') - ('#!/bin/bash\ntrue', (u'/bin/bash', 'abstractions/bash')), - ('foo bar', (None, None)), - ] + tests = ( + # file content filename expected interpreter expected abstraction (besides 'base') expected profiles + (('#!/bin/bash\ntrue', 'script'), (u'/bin/bash', 'abstractions/bash', ['script'])), + (('foo bar', 'fake_binary'), (None, None, ['fake_binary'])), + (('hats expected', 'apache2'), (None, None, ['apache2', 'apache2//DEFAULT_URI', 'apache2//HANDLING_UNTRUSTED_INPUT'])), + ) + def _run_test(self, params, expected): apparmor.aa.cfg['settings']['ldd'] = './fake_ldd' - # for some reason, setting the ldd config option does not get - # honored in python2.7 - # XXX KILL when python 2.7 is dropped XXX - if sys.version_info[0] < 3: - print("Skipping on python < 3.x") - return self.createTmpdir() - #copy the local profiles to the test directory + # copy the local profiles to the test directory self.profile_dir = '%s/profiles' % self.tmpdir shutil.copytree('../../profiles/apparmor.d/', self.profile_dir, symlinks=True) @@ -135,45 +127,58 @@ class AaTest_create_new_profile(AATest): apparmor.aa.load_include(os.path.join(self.profile_dir, 'abstractions/base')) apparmor.aa.load_include(os.path.join(self.profile_dir, 'abstractions/bash')) - exp_interpreter_path, exp_abstraction = expected + exp_interpreter_path, exp_abstraction, exp_profiles = expected + # damn symlinks! if exp_interpreter_path: exp_interpreter_path = os.path.realpath(exp_interpreter_path) - program = self.writeTmpfile('script', params) + file_content, filename = params + program = self.writeTmpfile(filename, file_content) profile = create_new_profile(program) + expected_profiles = [] + for prof in exp_profiles: + expected_profiles.append('%s/%s' % (self.tmpdir, prof)) # actual profile names start with tmpdir, prepend it to the expected profile names + + self.assertEqual(list(profile.keys()), expected_profiles) + if exp_interpreter_path: - self.assertEqual(set(profile[program][program]['file'].get_clean()), {'%s ix,' % exp_interpreter_path, '%s r,' % program, '', - '/AATest/lib64/libtinfo.so.* mr,', '/AATest/lib64/libc.so.* mr,', '/AATest/lib64/libdl.so.* mr,', '/AATest/lib64/libreadline.so.* mr,', '/AATest/lib64/ld-linux-x86-64.so.* mr,' }) + self.assertEqual( + set(profile[program]['file'].get_clean()), + {'%s ix,' % exp_interpreter_path, '%s r,' % program, '', + '/AATest/lib64/libtinfo.so.* mr,', '/AATest/lib64/libc.so.* mr,', + '/AATest/lib64/libdl.so.* mr,', '/AATest/lib64/libreadline.so.* mr,', + '/AATest/lib64/ld-linux-x86-64.so.* mr,'}) else: - self.assertEqual(set(profile[program][program]['file'].get_clean()), {'%s mr,' % program, ''}) + self.assertEqual(set(profile[program]['file'].get_clean()), {'%s mr,' % program, ''}) if exp_abstraction: - self.assertEqual(profile[program][program]['inc_ie'].get_clean(), ['include <abstractions/base>', 'include <%s>' % exp_abstraction, '']) + self.assertEqual(profile[program]['inc_ie'].get_clean(), ['include <abstractions/base>', 'include <%s>' % exp_abstraction, '']) else: - self.assertEqual(profile[program][program]['inc_ie'].get_clean(), ['include <abstractions/base>', '']) + self.assertEqual(profile[program]['inc_ie'].get_clean(), ['include <abstractions/base>', '']) + class AaTest_get_interpreter_and_abstraction(AATest): - tests = [ - ('#!/bin/bash', ('/bin/bash', 'abstractions/bash')), - ('#!/bin/dash', ('/bin/dash', 'abstractions/bash')), - ('#!/bin/sh', ('/bin/sh', 'abstractions/bash')), - ('#! /bin/sh ', ('/bin/sh', 'abstractions/bash')), - ('#! /bin/sh -x ', ('/bin/sh', 'abstractions/bash')), # '-x' is not part of the interpreter path - ('#!/usr/bin/perl', ('/usr/bin/perl', 'abstractions/perl')), - ('#!/usr/bin/perl -w', ('/usr/bin/perl', 'abstractions/perl')), # '-w' is not part of the interpreter path - ('#!/usr/bin/python', ('/usr/bin/python', 'abstractions/python')), - ('#!/usr/bin/python2', ('/usr/bin/python2', 'abstractions/python')), - ('#!/usr/bin/python2.7', ('/usr/bin/python2.7', 'abstractions/python')), - ('#!/usr/bin/python3', ('/usr/bin/python3', 'abstractions/python')), - ('#!/usr/bin/python4', ('/usr/bin/python4', None)), # python abstraction is only applied to py2 and py3 - ('#!/usr/bin/ruby', ('/usr/bin/ruby', 'abstractions/ruby')), - ('#!/usr/bin/ruby2.2', ('/usr/bin/ruby2.2', 'abstractions/ruby')), - ('#!/usr/bin/ruby1.9.1', ('/usr/bin/ruby1.9.1', 'abstractions/ruby')), - ('#!/usr/bin/foobarbaz', ('/usr/bin/foobarbaz', None)), # we don't have an abstraction for "foobarbaz" - ('foo', (None, None)), # no hashbang - not a script - ] + tests = ( + ('#!/bin/bash', ('/bin/bash', 'abstractions/bash')), + ('#!/bin/dash', ('/bin/dash', 'abstractions/bash')), + ('#!/bin/sh', ('/bin/sh', 'abstractions/bash')), + ('#! /bin/sh ', ('/bin/sh', 'abstractions/bash')), + ('#! /bin/sh -x ', ('/bin/sh', 'abstractions/bash')), # '-x' is not part of the interpreter path + ('#!/usr/bin/perl', ('/usr/bin/perl', 'abstractions/perl')), + ('#!/usr/bin/perl -w', ('/usr/bin/perl', 'abstractions/perl')), # '-w' is not part of the interpreter path + ('#!/usr/bin/python', ('/usr/bin/python', 'abstractions/python')), + ('#!/usr/bin/python2', ('/usr/bin/python2', 'abstractions/python')), + ('#!/usr/bin/python2.7', ('/usr/bin/python2.7', 'abstractions/python')), + ('#!/usr/bin/python3', ('/usr/bin/python3', 'abstractions/python')), + ('#!/usr/bin/python4', ('/usr/bin/python4', None)), # python abstraction is only applied to py2 and py3 + ('#!/usr/bin/ruby', ('/usr/bin/ruby', 'abstractions/ruby')), + ('#!/usr/bin/ruby2.2', ('/usr/bin/ruby2.2', 'abstractions/ruby')), + ('#!/usr/bin/ruby1.9.1', ('/usr/bin/ruby1.9.1', 'abstractions/ruby')), + ('#!/usr/bin/foobarbaz', ('/usr/bin/foobarbaz', None)), # we don't have an abstraction for "foobarbaz" + ('foo', (None, None)), # no hashbang - not a script + ) def _run_test(self, params, expected): exp_interpreter_path, exp_abstraction = expected @@ -227,10 +232,11 @@ class AaTest_get_profile_flags(AaTestWithTempdir): with self.assertRaises(AppArmorException): self._test_get_flags('/no-such-profile flags=(complain)', 'complain') + class AaTest_change_profile_flags(AaTestWithTempdir): - def _test_change_profile_flags(self, profile, old_flags, flags_to_change, set_flag, expected_flags, whitespace='', comment='', - more_rules='', expected_more_rules='@-@-@', - check_new_flags=True, profile_name='/foo'): + def _test_change_profile_flags( + self, profile, old_flags, flags_to_change, set_flag, expected_flags, whitespace='', + comment='', more_rules='', expected_more_rules='@-@-@', check_new_flags=True, profile_name='/foo'): if old_flags: old_flags = ' %s' % old_flags @@ -248,7 +254,7 @@ class AaTest_change_profile_flags(AaTestWithTempdir): dummy_profile_content = ' #include <abstractions/base>\n capability chown,\n /bar r,' prof_template = '%s%s%s {%s\n%s\n%s\n}\n' old_prof = prof_template % (whitespace, profile, old_flags, comment, more_rules, dummy_profile_content) - new_prof = prof_template % (whitespace, profile, expected_flags, comment, expected_more_rules, dummy_profile_content) + new_prof = prof_template % ('', profile, expected_flags, comment, expected_more_rules, dummy_profile_content) self.file = write_file(self.tmpdir, 'profile', old_prof) change_profile_flags(self.file, profile_name, flags_to_change, set_flag) @@ -309,39 +315,45 @@ class AaTest_change_profile_flags(AaTestWithTempdir): # test handling of hat flags def test_set_flags_with_hat_01(self): - self._test_change_profile_flags('/foo', 'flags=(complain)', 'audit', True, 'audit, complain', + self._test_change_profile_flags( + '/foo', 'flags=(complain)', 'audit', True, 'audit, complain', more_rules='\n ^foobar {\n}\n', expected_more_rules='\n ^foobar flags=(audit) {\n}\n' ) def test_change_profile_flags_with_hat_02(self): - self._test_change_profile_flags('/foo', 'flags=(complain)', 'audit', False, 'complain', + self._test_change_profile_flags( + '/foo', 'flags=(complain)', 'audit', False, 'complain', profile_name=None, more_rules='\n ^foobar flags=(audit) {\n}\n', expected_more_rules='\n ^foobar {\n}\n' ) def test_change_profile_flags_with_hat_03(self): - self._test_change_profile_flags('/foo', 'flags=(complain)', 'audit', True, 'audit, complain', + self._test_change_profile_flags( + '/foo', 'flags=(complain)', 'audit', True, 'audit, complain', more_rules='\n^foobar (attach_disconnected) { # comment\n}\n', - expected_more_rules='\n^foobar flags=(attach_disconnected, audit) { # comment\n}\n' + expected_more_rules='\n ^foobar flags=(attach_disconnected, audit) { # comment\n}\n' ) def test_change_profile_flags_with_hat_04(self): - self._test_change_profile_flags('/foo', '', 'audit', True, 'audit', + self._test_change_profile_flags( + '/foo', '', 'audit', True, 'audit', more_rules='\n hat foobar (attach_disconnected) { # comment\n}\n', expected_more_rules='\n hat foobar flags=(attach_disconnected, audit) { # comment\n}\n' ) def test_change_profile_flags_with_hat_05(self): - self._test_change_profile_flags('/foo', '(audit)', 'audit', False, '', + self._test_change_profile_flags( + '/foo', '(audit)', 'audit', False, '', more_rules='\n hat foobar (attach_disconnected) { # comment\n}\n', expected_more_rules='\n hat foobar flags=(attach_disconnected) { # comment\n}\n' ) # test handling of child profiles def test_change_profile_flags_with_child_01(self): - self._test_change_profile_flags('/foo', 'flags=(complain)', 'audit', True, 'audit, complain', + self._test_change_profile_flags( + '/foo', 'flags=(complain)', 'audit', True, 'audit, complain', profile_name=None, more_rules='\n profile /bin/bar {\n}\n', expected_more_rules='\n profile /bin/bar flags=(audit) {\n}\n' @@ -349,12 +361,12 @@ class AaTest_change_profile_flags(AaTestWithTempdir): def test_change_profile_flags_with_child_02(self): # XXX child profile flags aren't changed if profile parameter is not None - self._test_change_profile_flags('/foo', 'flags=(complain)', 'audit', True, 'audit, complain', + self._test_change_profile_flags( + '/foo', 'flags=(complain)', 'audit', True, 'audit, complain', more_rules='\n profile /bin/bar {\n}\n', expected_more_rules='\n profile /bin/bar {\n}\n' # flags(audit) should be added ) - def test_change_profile_flags_invalid_01(self): with self.assertRaises(AppArmorBug): self._test_change_profile_flags('/foo', '()', None, False, '', check_new_flags=False) @@ -366,7 +378,7 @@ class AaTest_change_profile_flags(AaTestWithTempdir): self._test_change_profile_flags('/foo', '( )', '', True, '', check_new_flags=False) def test_change_profile_flags_invalid_04(self): with self.assertRaises(AppArmorBug): - self._test_change_profile_flags('/foo', 'flags=(complain, audit)', ' ', True, 'audit, complain', check_new_flags=False) # whitespace-only newflags + self._test_change_profile_flags('/foo', 'flags=(complain, audit)', ' ', True, 'audit, complain', check_new_flags=False) # whitespace-only newflags def test_change_profile_flags_other_profile(self): # test behaviour if the file doesn't contain the specified /foo profile @@ -396,34 +408,37 @@ class AaTest_change_profile_flags(AaTestWithTempdir): with self.assertRaises(IOError): change_profile_flags('%s/file-not-found' % self.tmpdir, '/foo', 'audit', True) + class AaTest_set_options_audit_mode(AATest): - tests = [ - ((FileRule.parse('audit /foo/bar r,'), ['/foo/bar r,', '/foo/* r,', '/** r,'] ), ['audit /foo/bar r,', 'audit /foo/* r,', 'audit /** r,']), - ((FileRule.parse('audit /foo/bar r,'), ['/foo/bar r,', 'audit /foo/* r,', 'audit /** r,'] ), ['audit /foo/bar r,', 'audit /foo/* r,', 'audit /** r,']), - ((FileRule.parse('/foo/bar r,'), ['/foo/bar r,', '/foo/* r,', '/** r,'] ), ['/foo/bar r,', '/foo/* r,', '/** r,']), - ((FileRule.parse('/foo/bar r,'), ['audit /foo/bar r,', 'audit /foo/* r,', 'audit /** r,'] ), ['/foo/bar r,', '/foo/* r,', '/** r,']), - ((FileRule.parse('audit /foo/bar r,'), ['/foo/bar r,', '/foo/* r,', '#include <abstractions/base>']), ['audit /foo/bar r,', 'audit /foo/* r,', '#include <abstractions/base>']), - ] + tests = ( + ((FileRule.parse('audit /foo/bar r,'), ['/foo/bar r,', '/foo/* r,', '/** r,']), ['audit /foo/bar r,', 'audit /foo/* r,', 'audit /** r,']), + ((FileRule.parse('audit /foo/bar r,'), ['/foo/bar r,', 'audit /foo/* r,', 'audit /** r,']), ['audit /foo/bar r,', 'audit /foo/* r,', 'audit /** r,']), + ((FileRule.parse('/foo/bar r,'), ['/foo/bar r,', '/foo/* r,', '/** r,']), ['/foo/bar r,', '/foo/* r,', '/** r,']), + ((FileRule.parse('/foo/bar r,'), ['audit /foo/bar r,', 'audit /foo/* r,', 'audit /** r,']), ['/foo/bar r,', '/foo/* r,', '/** r,']), + ((FileRule.parse('audit /foo/bar r,'), ['/foo/bar r,', '/foo/* r,', '#include <abstractions/base>']), ['audit /foo/bar r,', 'audit /foo/* r,', '#include <abstractions/base>']), + ) def _run_test(self, params, expected): rule_obj, options = params new_options = set_options_audit_mode(rule_obj, options) self.assertEqual(new_options, expected) + class AaTest_set_options_owner_mode(AATest): - tests = [ - ((FileRule.parse('owner /foo/bar r,'), ['/foo/bar r,', '/foo/* r,', '/** r,'] ), ['owner /foo/bar r,', 'owner /foo/* r,', 'owner /** r,']), - ((FileRule.parse('owner /foo/bar r,'), ['/foo/bar r,', 'owner /foo/* r,', 'owner /** r,'] ), ['owner /foo/bar r,', 'owner /foo/* r,', 'owner /** r,']), - ((FileRule.parse('/foo/bar r,'), ['/foo/bar r,', '/foo/* r,', '/** r,'] ), ['/foo/bar r,', '/foo/* r,', '/** r,']), - ((FileRule.parse('/foo/bar r,'), ['owner /foo/bar r,', 'owner /foo/* r,', 'owner /** r,'] ), ['/foo/bar r,', '/foo/* r,', '/** r,']), - ((FileRule.parse('audit owner /foo/bar r,'),['audit /foo/bar r,', 'audit /foo/* r,', '#include <abstractions/base>']), ['audit owner /foo/bar r,', 'audit owner /foo/* r,', '#include <abstractions/base>']), - ] + tests = ( + ((FileRule.parse('owner /foo/bar r,'), ['/foo/bar r,', '/foo/* r,', '/** r,']), ['owner /foo/bar r,', 'owner /foo/* r,', 'owner /** r,']), + ((FileRule.parse('owner /foo/bar r,'), ['/foo/bar r,', 'owner /foo/* r,', 'owner /** r,']), ['owner /foo/bar r,', 'owner /foo/* r,', 'owner /** r,']), + ((FileRule.parse('/foo/bar r,'), ['/foo/bar r,', '/foo/* r,', '/** r,']), ['/foo/bar r,', '/foo/* r,', '/** r,']), + ((FileRule.parse('/foo/bar r,'), ['owner /foo/bar r,', 'owner /foo/* r,', 'owner /** r,']), ['/foo/bar r,', '/foo/* r,', '/** r,']), + ((FileRule.parse('audit owner /foo/bar r,'), ['audit /foo/bar r,', 'audit /foo/* r,', '#include <abstractions/base>']), ['audit owner /foo/bar r,', 'audit owner /foo/* r,', '#include <abstractions/base>']), + ) def _run_test(self, params, expected): rule_obj, options = params new_options = set_options_owner_mode(rule_obj, options) self.assertEqual(new_options, expected) + class AaTest_is_skippable_file(AATest): def test_not_skippable_01(self): self.assertFalse(is_skippable_file('bin.ping')) @@ -473,255 +488,79 @@ class AaTest_is_skippable_file(AATest): self.assertTrue(is_skippable_file('README')) -class AaTest_is_skippable_dir(AATest): - tests = [ - ('disable', True), - ('cache', True), - ('lxc', True), - ('force-complain', True), - ('/etc/apparmor.d/cache', True), - ('/etc/apparmor.d/cache.d', True), - ('/etc/apparmor.d/cache.d/', True), - ('/etc/apparmor.d/lxc/', True), - ('/etc/apparmor.d/.git/', True), - - ('dont_disable', False), - ('/etc/apparmor.d/cache_foo', False), - ('abstractions', False), - ('apache2.d', False), - ('/etc/apparmor.d/apache2.d', False), - ('local', False), - ('/etc/apparmor.d/local/', False), - ('tunables', False), - ('/etc/apparmor.d/tunables', False), - ('/etc/apparmor.d/tunables/multiarch.d', False), - ('/etc/apparmor.d/tunables/xdg-user-dirs.d', False), - ('/etc/apparmor.d/tunables/home.d', False), - ('/etc/apparmor.d/abstractions', False), - ('/etc/apparmor.d/abstractions/ubuntu-browsers.d', False), - ('/etc/apparmor.d/abstractions/apparmor_api', False), - ] - - def _run_test(self, params, expected): - self.assertEqual(is_skippable_dir(params), expected) - -class AaTest_parse_profile_start(AATest): - def _parse(self, line, profile, hat): - return parse_profile_start(line, 'somefile', 1, profile, hat) - # (profile, hat, attachment, xattrs, flags, in_contained_hat, pps_set_profile, pps_set_hat_external) - - def test_parse_profile_start_01(self): - result = self._parse('/foo {', None, None) - expected = ('/foo', '/foo', None, None, None, False, False, False) - self.assertEqual(result, expected) - - def test_parse_profile_start_02(self): - result = self._parse('/foo (complain) {', None, None) - expected = ('/foo', '/foo', None, None, 'complain', False, False, False) - self.assertEqual(result, expected) - - def test_parse_profile_start_03(self): - result = self._parse('profile foo /foo {', None, None) # named profile - expected = ('foo', 'foo', '/foo', None, None, False, False, False) - self.assertEqual(result, expected) - - def test_parse_profile_start_04(self): - result = self._parse('profile /foo {', '/bar', '/bar') # child profile - expected = ('/bar', '/foo', None, None, None, True, True, False) - self.assertEqual(result, expected) - - def test_parse_profile_start_05(self): - result = self._parse('/foo//bar {', None, None) # external hat - expected = ('/foo', 'bar', None, None, None, False, False, True) - self.assertEqual(result, expected) - - def test_parse_profile_start_06(self): - result = self._parse('profile "/foo" (complain) {', None, None) - expected = ('/foo', '/foo', None, None, 'complain', False, False, False) - self.assertEqual(result, expected) - - def test_parse_profile_start_07(self): - result = self._parse('profile "/foo" xattrs=(user.bar=bar) {', None, None) - expected = ('/foo', '/foo', None, 'user.bar=bar', None, False, False, False) - self.assertEqual(result, expected) - - def test_parse_profile_start_08(self): - result = self._parse('profile "/foo" xattrs=(user.bar=bar user.foo=*) {', None, None) - expected = ('/foo', '/foo', None, 'user.bar=bar user.foo=*', None, False, False, False) - self.assertEqual(result, expected) - - def test_parse_profile_start_09(self): - result = self._parse('/usr/bin/xattrs-test xattrs=(myvalue="foo.bar") {', None, None) - expected = ('/usr/bin/xattrs-test', '/usr/bin/xattrs-test', None, 'myvalue="foo.bar"', None, False, False, False) - self.assertEqual(result, expected) - - def test_parse_profile_start_unsupported_01(self): - with self.assertRaises(AppArmorException): - self._parse('/foo///bar///baz {', None, None) # XXX deeply nested external hat - - def test_parse_profile_start_invalid_01(self): - with self.assertRaises(AppArmorException): - self._parse('/foo {', '/bar', '/bar') # child profile without profile keyword - - def test_parse_profile_start_invalid_02(self): - with self.assertRaises(AppArmorBug): - self._parse('xy', '/bar', '/bar') # not a profile start - class AaTest_parse_profile_data(AATest): def test_parse_empty_profile_01(self): - prof = parse_profile_data('/foo {\n}\n'.split(), 'somefile', False) + prof = parse_profile_data('/foo {\n}\n'.split(), 'somefile', False, False) self.assertEqual(list(prof.keys()), ['/foo']) - self.assertEqual(list(prof['/foo'].keys()), ['/foo']) - self.assertEqual(prof['/foo']['/foo']['name'], '/foo') - self.assertEqual(prof['/foo']['/foo']['filename'], 'somefile') - self.assertEqual(prof['/foo']['/foo']['flags'], None) + self.assertEqual(prof['/foo']['name'], '/foo') + self.assertEqual(prof['/foo']['filename'], 'somefile') + self.assertEqual(prof['/foo']['flags'], None) def test_parse_duplicate_profile(self): with self.assertRaises(AppArmorException): # file contains two profiles with the same name - parse_profile_data('profile /foo {\n}\nprofile /foo {\n}\n'.split(), 'somefile', False) + parse_profile_data('profile /foo {\n}\nprofile /foo {\n}\n'.split(), 'somefile', False, False) def test_parse_duplicate_child_profile(self): with self.assertRaises(AppArmorException): # file contains two child profiles with the same name - parse_profile_data('profile /foo {\nprofile /bar {\n}\nprofile /bar {\n}\n}\n'.split(), 'somefile', False) + parse_profile_data('profile /foo {\nprofile /bar {\n}\nprofile /bar {\n}\n}\n'.split(), 'somefile', False, False) def test_parse_duplicate_hat(self): with self.assertRaises(AppArmorException): # file contains two hats with the same name - parse_profile_data('profile /foo {\n^baz {\n}\n^baz {\n}\n}\n'.split(), 'somefile', False) + parse_profile_data('profile /foo {\n^baz {\n}\n^baz {\n}\n}\n'.split(), 'somefile', False, False) def test_parse_xattrs_01(self): - prof = parse_profile_data('/foo xattrs=(user.bar=bar) {\n}\n'.split(), 'somefile', False) + prof = parse_profile_data('/foo xattrs=(user.bar=bar) {\n}\n'.split(), 'somefile', False, False) self.assertEqual(list(prof.keys()), ['/foo']) - self.assertEqual(list(prof['/foo'].keys()), ['/foo']) - self.assertEqual(prof['/foo']['/foo']['name'], '/foo') - self.assertEqual(prof['/foo']['/foo']['filename'], 'somefile') - self.assertEqual(prof['/foo']['/foo']['flags'], None) - self.assertEqual(prof['/foo']['/foo']['xattrs'], 'user.bar=bar') + self.assertEqual(prof['/foo']['name'], '/foo') + self.assertEqual(prof['/foo']['filename'], 'somefile') + self.assertEqual(prof['/foo']['flags'], None) + self.assertEqual(prof['/foo']['xattrs'], 'user.bar=bar') def test_parse_xattrs_02(self): - prof = parse_profile_data('/foo xattrs=(user.bar=bar user.foo=*) {\n}\n'.split(), 'somefile', False) + prof = parse_profile_data('/foo xattrs=(user.bar=bar user.foo=*) {\n}\n'.split(), 'somefile', False, False) self.assertEqual(list(prof.keys()), ['/foo']) - self.assertEqual(list(prof['/foo'].keys()), ['/foo']) - self.assertEqual(prof['/foo']['/foo']['name'], '/foo') - self.assertEqual(prof['/foo']['/foo']['filename'], 'somefile') - self.assertEqual(prof['/foo']['/foo']['flags'], None) - self.assertEqual(prof['/foo']['/foo']['xattrs'], 'user.bar=bar user.foo=*') + self.assertEqual(prof['/foo']['name'], '/foo') + self.assertEqual(prof['/foo']['filename'], 'somefile') + self.assertEqual(prof['/foo']['flags'], None) + self.assertEqual(prof['/foo']['xattrs'], 'user.bar=bar user.foo=*') def test_parse_xattrs_03(self): d = '/foo xattrs=(user.bar=bar) flags=(complain) {\n}\n' - prof = parse_profile_data(d.split(), 'somefile', False) + prof = parse_profile_data(d.split(), 'somefile', False, False) self.assertEqual(list(prof.keys()), ['/foo']) - self.assertEqual(list(prof['/foo'].keys()), ['/foo']) - self.assertEqual(prof['/foo']['/foo']['name'], '/foo') - self.assertEqual(prof['/foo']['/foo']['filename'], 'somefile') - self.assertEqual(prof['/foo']['/foo']['flags'], 'complain') - self.assertEqual(prof['/foo']['/foo']['xattrs'], 'user.bar=bar') + self.assertEqual(prof['/foo']['name'], '/foo') + self.assertEqual(prof['/foo']['filename'], 'somefile') + self.assertEqual(prof['/foo']['flags'], 'complain') + self.assertEqual(prof['/foo']['xattrs'], 'user.bar=bar') def test_parse_xattrs_04(self): with self.assertRaises(AppArmorException): # flags before xattrs d = '/foo flags=(complain) xattrs=(user.bar=bar) {\n}\n' - parse_profile_data(d.split(), 'somefile', False) - -class AaTest_write_header(AATest): - tests = [ - # name embedded_hat write_flags depth flags attachment prof.keyw. comment expected - (['/foo', False, True, 1, 'complain', None, None, None ], ' /foo flags=(complain) {'), - (['/foo', True, True, 1, 'complain', None, None, None ], ' profile /foo flags=(complain) {'), - (['/foo sp', False, False, 2, 'complain', None, None, None ], ' "/foo sp" {'), - (['/foo' ,False, False, 2, 'complain', None, None, None ], ' /foo {'), - (['/foo', True, False, 2, 'complain', None, None, None ], ' profile /foo {'), - (['/foo', False, True, 0, None, None, None, None ], '/foo {'), - (['/foo', True, True, 0, None, None, None, None ], 'profile /foo {'), - (['/foo', False, False, 0, None, None, None, None ], '/foo {'), - (['/foo', True, False, 0, None, None, None, None ], 'profile /foo {'), - (['bar', False, True, 1, 'complain', None, None, None ], ' profile bar flags=(complain) {'), - (['bar', False, True, 1, 'complain', '/foo', None, None ], ' profile bar /foo flags=(complain) {'), - (['bar', True, True, 1, 'complain', '/foo', None, None ], ' profile bar /foo flags=(complain) {'), - (['bar baz', False, True, 1, None, '/foo', None, None ], ' profile "bar baz" /foo {'), - (['bar', True, True, 1, None, '/foo', None, None ], ' profile bar /foo {'), - (['bar baz', False, True, 1, 'complain', '/foo sp', None, None ], ' profile "bar baz" "/foo sp" flags=(complain) {'), - (['^foo', False, True, 1, 'complain', None, None, None ], ' profile ^foo flags=(complain) {'), - (['^foo', True, True, 1, 'complain', None, None, None ], ' ^foo flags=(complain) {'), - (['^foo', True, True, 1.5, 'complain', None, None, None ], ' ^foo flags=(complain) {'), - (['^foo', True, True, 1.3, 'complain', None, None, None ], ' ^foo flags=(complain) {'), - (['/foo', False, True, 1, 'complain', None, 'profile', None ], ' profile /foo flags=(complain) {'), - (['/foo', True, True, 1, 'complain', None, 'profile', None ], ' profile /foo flags=(complain) {'), - (['/foo', False, True, 1, 'complain', None, None, '# x' ], ' /foo flags=(complain) { # x'), - (['/foo', True, True, 1, None, None, None, '# x' ], ' profile /foo { # x'), - (['/foo', False, True, 1, None, None, 'profile', '# x' ], ' profile /foo { # x'), - (['/foo', True, True, 1, 'complain', None, 'profile', '# x' ], ' profile /foo flags=(complain) { # x'), - ] - - def _run_test(self, params, expected): - name = params[0] - embedded_hat = params[1] - write_flags = params[2] - depth = params[3] - prof_data = { 'flags': params[4], 'attachment': params[5], 'profile_keyword': params[6], 'header_comment': params[7], 'xattrs': '' } - - result = write_header(prof_data, depth, name, embedded_hat, write_flags) - self.assertEqual(result, [expected]) - -class AaTest_write_header_01(AATest): - tests = [ - ( - {'name': '/foo', 'write_flags': True, 'depth': 1, 'flags': 'complain'}, - ' /foo flags=(complain) {', - ), - ( - {'name': '/foo', 'write_flags': True, 'depth': 1, 'flags': 'complain', 'profile_keyword': 'profile'}, - ' profile /foo flags=(complain) {', - ), - ( - {'name': '/foo', 'write_flags': True, 'flags': 'complain'}, - '/foo flags=(complain) {', - ), - ( - {'name': '/foo', 'xattrs': 'user.foo=bar', 'write_flags': True, 'flags': 'complain'}, - '/foo xattrs=(user.foo=bar) flags=(complain) {', - ), - ( - {'name': '/foo', 'xattrs': 'user.foo=bar', 'embedded_hat': True}, - 'profile /foo xattrs=(user.foo=bar) {', - ), - ] + parse_profile_data(d.split(), 'somefile', False, False) - def _run_test(self, params, expected): - name = params['name'] - embedded_hat = params.get('embedded_hat', False) - write_flags = params.get('write_flags', False) - depth = params.get('depth', 0) - prof_data = { - 'xattrs': params.get('xattrs', None), - 'flags': params.get('flags', None), - 'attachment': params.get('attachment', None), - 'profile_keyword': params.get('profile_keyword', None), - 'header_comment': params.get('header_comment', None), - } - result = write_header(prof_data, depth, name, embedded_hat, write_flags) - self.assertEqual(result, [expected]) class AaTest_get_file_perms_1(AATest): - tests = [ - ('/usr/share/common-licenses/foo/bar', {'allow': {'all': set(), 'owner': {'w'} }, 'deny': {'all':set(), 'owner': set()}, 'paths': {'/usr/share/common-licenses/**'} }), - ('/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': set(), 'owner': set() }, 'deny': {'all':set(), 'owner': set()}, 'paths': set() }), - ('/usr/lib/aspell/*.so', {'allow': {'all': set(), 'owner': set() }, 'deny': {'all':set(), 'owner': set()}, 'paths': set() }), - ] + tests = ( + ('/usr/share/common-licenses/foo/bar', {'allow': {'all': set(), 'owner': {'w'}}, 'deny': {'all': set(), 'owner': set()}, 'paths': {'/usr/share/common-licenses/**'}}), + ('/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': set(), 'owner': set()}, 'deny': {'all': set(), 'owner': set()}, 'paths': set()}), + ('/usr/lib/aspell/*.so', {'allow': {'all': set(), 'owner': set()}, 'deny': {'all': set(), 'owner': set()}, 'paths': set()}), + ) def _run_test(self, params, expected): self.createTmpdir() - #copy the local profiles to the test directory + # copy the local profiles to the test directory self.profile_dir = '%s/profiles' % self.tmpdir shutil.copytree('../../profiles/apparmor.d/', self.profile_dir, symlinks=True) @@ -735,21 +574,22 @@ class AaTest_get_file_perms_1(AATest): perms = get_file_perms(profile, params, False, False) # only testing with audit and deny = False self.assertEqual(perms, expected) + class AaTest_get_file_perms_2(AATest): - tests = [ - ('/usr/share/common-licenses/foo/bar', {'allow': {'all': {'r'}, 'owner': {'w'} }, 'deny': {'all':set(), 'owner': set()}, 'paths': {'/usr/share/common-licenses/**'} }), - ('/usr/share/common-licenses/what/ever', {'allow': {'all': {'r'}, 'owner': {'w'} }, 'deny': {'all':set(), 'owner': set()}, 'paths': {'/usr/share/common-licenses/**', '/usr/share/common-licenses/what/ever'} }), - ('/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}/**', '/{usr/,}lib{,32,64}/**.so*'} }), # from abstractions/aspell via abstractions/enchant and from abstractions/base - ] + tests = ( + ('/usr/share/common-licenses/foo/bar', {'allow': {'all': {'r'}, 'owner': {'w'}}, 'deny': {'all': set(), 'owner': set()}, 'paths': {'/usr/share/common-licenses/**'}}), + ('/usr/share/common-licenses/what/ever', {'allow': {'all': {'r'}, 'owner': {'w'}}, 'deny': {'all': set(), 'owner': set()}, 'paths': {'/usr/share/common-licenses/**', '/usr/share/common-licenses/what/ever'}}), + ('/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}/**', '/{usr/,}lib{,32,64}/**.so*'}}), # from abstractions/aspell via abstractions/enchant and from abstractions/base + ) def _run_test(self, params, expected): self.createTmpdir() - #copy the local profiles to the test directory + # copy the local profiles to the test directory self.profile_dir = '%s/profiles' % self.tmpdir shutil.copytree('../../profiles/apparmor.d/', self.profile_dir, symlinks=True) @@ -773,21 +613,22 @@ class AaTest_get_file_perms_2(AATest): perms = get_file_perms(profile, params, False, False) # only testing with audit and deny = False self.assertEqual(perms, expected) + class AaTest_propose_file_rules(AATest): - tests = [ - # log event path and perms expected proposals - (['/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{,32,64}/**.so* mrk,', '/usr/lib/aspell/some.so mrk,'] ), - (['/foo/log', 'w'], ['/foo/log w,'] ), - ] + tests = ( + # log event path and perms expected proposals + (('/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{,32,64}/**.so* mrk,', '/usr/lib/aspell/some.so mrk,']), + (('/foo/log', 'w'), ['/foo/log w,']), + ) def _run_test(self, params, expected): self.createTmpdir() - #copy the local profiles to the test directory + # copy the local profiles to the test directory self.profile_dir = '%s/profiles' % self.tmpdir shutil.copytree('../../profiles/apparmor.d/', self.profile_dir, symlinks=True) @@ -807,7 +648,6 @@ class AaTest_propose_file_rules(AATest): profile['inc_ie'].add(IncludeRule.parse('include <abstractions/bash>')) profile['inc_ie'].add(IncludeRule.parse('include <abstractions/enchant>')) - profile['file'].add(FileRule.parse('owner /usr/share/common-licenses/** w,')) profile['file'].add(FileRule.parse('/dev/null rwk,')) profile['file'].add(FileRule.parse('/foo/bar rwix,')) @@ -819,18 +659,18 @@ class AaTest_propose_file_rules(AATest): class AaTest_propose_file_rules_with_absolute_includes(AATest): - tests = [ - # log event path and perms expected proposals - (['/not/found/anywhere', 'r'], ['/not/found/anywhere r,']), - (['/dev/null', 'w'], ['/dev/null rw,']), - (['/some/random/include', 'r'], ['/some/random/include rw,']), - (['/some/other/include', 'w'], ['/some/other/* rw,', '/some/other/inc* rw,', '/some/other/include rw,']), - ] + tests = ( + # log event path and perms expected proposals + (('/not/found/anywhere', 'r'), ['/not/found/anywhere r,']), + (('/dev/null', 'w'), ['/dev/null rw,']), + (('/some/random/include', 'r'), ['/some/random/include rw,']), + (('/some/other/include', 'w'), ['/some/other/* rw,', '/some/other/inc* rw,', '/some/other/include rw,']), + ) def _run_test(self, params, expected): self.createTmpdir() - #copy the local profiles to the test directory + # copy the local profiles to the test directory self.profile_dir = '%s/profiles' % self.tmpdir shutil.copytree('../../profiles/apparmor.d/', self.profile_dir, symlinks=True) @@ -859,16 +699,54 @@ class AaTest_propose_file_rules_with_absolute_includes(AATest): class AaTest_nonexistent_includes(AATest): - tests = [ - ("/nonexistent/absolute/path", AppArmorException), - ("nonexistent/relative/path", AppArmorBug), # load_include() only accepts absolute paths - ] + tests = ( + ("/nonexistent/absolute/path", AppArmorException), + ("nonexistent/relative/path", AppArmorBug), # load_include() only accepts absolute paths + ) def _run_test(self, params, expected): with self.assertRaises(expected): apparmor.aa.load_include(params) +class AaTest_merged_to_split(AATest): + tests = ( + ("foo", ("foo", "foo")), + ("foo//bar", ("foo", "bar")), + ("foo//bar//baz", ("foo", "bar")), # XXX known limitation + ) + + def _run_test(self, params, expected): + merged = {} + merged[params] = True # simplified, but enough for this test + result = merged_to_split(merged) + + profile, hat = expected + + self.assertEqual(list(result.keys()), [profile]) + self.assertEqual(list(result[profile].keys()), [hat]) + self.assertTrue(result[profile][hat]) + + +class AaTest_split_to_merged(AATest): + tests = ( + (("foo", "foo"), "foo"), + (("foo", "bar"), "foo//bar"), + ) + + def _run_test(self, params, expected): + old = {} + profile = params[0] + hat = params[1] + + old[profile] = {} + old[profile][hat] = True # simplified, but enough for this test + result = split_to_merged(old) + + self.assertEqual(list(result.keys()), [expected]) + self.assertTrue(result[expected]) + + setup_aa(apparmor.aa) setup_all_loops(__name__) if __name__ == '__main__': diff --git a/utils/test/test-aare.py b/utils/test/test-aare.py index bc1f30f90..7198470bb 100644 --- a/utils/test/test-aare.py +++ b/utils/test/test-aare.py @@ -10,139 +10,141 @@ # # ------------------------------------------------------------------ +import re import unittest -from common_test import AATest, setup_all_loops - from copy import deepcopy -import re -from apparmor.common import convert_regexp, AppArmorBug, AppArmorException + +from apparmor.common import AppArmorBug, AppArmorException, convert_regexp from apparmor.aare import AARE, convert_expression_to_aare +from common_test import AATest, setup_all_loops + class TestConvert_regexp(AATest): - tests = [ - ('/foo', '^/foo$'), - ('/{foo,bar}', '^/(foo|bar)$'), - # ('/\{foo,bar}', '^/\{foo,bar}$'), # XXX gets converted to ^/\(foo|bar)$ - ('/fo[abc]', '^/fo[abc]$'), - ('/foo bar', '^/foo bar$'), - ('/x\y', '^/x\y$'), - ('/x\[y', '^/x\[y$'), - ('/x\\y', '^/x\\y$'), - ('/fo?', '^/fo[^/\000]$'), - ('/foo/*', '^/foo/(((?<=/)[^/\000]+)|((?<!/)[^/\000]*))$'), - ('/foo/**.bar', '^/foo/(((?<=/)[^\000]+)|((?<!/)[^\000]*))\.bar$'), - ] + tests = ( + ('/foo', '^/foo$'), + ('/{foo,bar}', '^/(foo|bar)$'), + # ('/\{foo,bar}', '^/\{foo,bar}$'), # XXX gets converted to ^/\(foo|bar)$ + ('/fo[abc]', '^/fo[abc]$'), + ('/foo bar', '^/foo bar$'), + (r'/x\y', r'^/x\y$'), + (r'/x\[y', r'^/x\[y$'), + ('/x\\y', '^/x\\y$'), + ('/fo?', '^/fo[^/\000]$'), + ('/foo/*', '^/foo/(((?<=/)[^/\000]+)|((?<!/)[^/\000]*))$'), + ('/foo/**.bar', '^/foo/(((?<=/)[^\000]+)|((?<!/)[^\000]*))\\.bar$'), + ) def _run_test(self, params, expected): self.assertEqual(convert_regexp(params), expected) + class Test_convert_expression_to_aare(AATest): - tests = [ + tests = ( # note that \ always needs to be escaped in python, so \\ is actually just \ in the string - ('/foo', '/foo' ), - ('/foo?', '/foo\\?' ), - ('/foo*', '/foo\\*' ), - (r'/foo\*', r'/foo\\\*' ), # raw string, no backslash doubling - ('/foo[bar]', '/foo\\[bar\\]' ), - ('/foo{bar}', '/foo\\{bar\\}' ), - ('/foo{', '/foo\\{' ), - ('/foo\\', '/foo\\\\' ), - ('/foo"', '/foo\\"' ), - ('}]"\\[{', '\\}\\]\\"\\\\\\[\\{' ), - ] + ('/foo', '/foo'), + ('/foo?', '/foo\\?'), + ('/foo*', '/foo\\*'), + (r'/foo\*', r'/foo\\\*'), # raw string, no backslash doubling + ('/foo[bar]', '/foo\\[bar\\]'), + ('/foo{bar}', '/foo\\{bar\\}'), + ('/foo{', '/foo\\{'), + ('/foo\\', '/foo\\\\'), + ('/foo"', '/foo\\"'), + ('}]"\\[{', '\\}\\]\\"\\\\\\[\\{'), + ) def _run_test(self, params, expected): self.assertEqual(convert_expression_to_aare(params), expected) + class TestConvert_regexpAndAAREMatch(AATest): - tests = [ - # aare path to check match expected? - (['/foo/**/bar/', '/foo/user/tools/bar/' ], True), - (['/foo/**/bar/', '/foo/apparmor/bar/' ], True), - (['/foo/**/bar/', '/foo/apparmor/bar' ], False), - (['/foo/**/bar/', '/a/foo/apparmor/bar/' ], False), - (['/foo/**/bar/', '/foo/apparmor/bar/baz' ], False), - - (['/foo/*/bar/', '/foo/apparmor/bar/' ], True), - (['/foo/*/bar/', '/foo/apparmor/tools/bar/' ], False), - (['/foo/*/bar/', '/foo/apparmor/bar' ], False), - - (['/foo/user/ba?/', '/foo/user/bar/' ], True), - (['/foo/user/ba?/', '/foo/user/bar/apparmor/' ], False), - (['/foo/user/ba?/', '/foo/user/ba/' ], False), - (['/foo/user/ba?/', '/foo/user/ba//' ], False), - - (['/foo/user/bar/**', '/foo/user/bar/apparmor' ], True), - (['/foo/user/bar/**', '/foo/user/bar/apparmor/tools' ], True), - (['/foo/user/bar/**', '/foo/user/bar/' ], False), - - (['/foo/user/bar/*', '/foo/user/bar/apparmor' ], True), - (['/foo/user/bar/*', '/foo/user/bar/apparmor/tools' ], False), - (['/foo/user/bar/*', '/foo/user/bar/' ], False), - (['/foo/user/bar/*', '/foo/user/bar/apparmor/' ], False), - - (['/foo/**.jpg', '/foo/bar/baz/foobar.jpg' ], True), - (['/foo/**.jpg', '/foo/bar/foobar.jpg' ], True), - (['/foo/**.jpg', '/foo/bar/*.jpg' ], True), - (['/foo/**.jpg', '/foo/bar.jpg' ], True), - (['/foo/**.jpg', '/foo/**.jpg' ], True), - (['/foo/**.jpg', '/foo/*.jpg' ], True), - (['/foo/**.jpg', '/foo/barjpg' ], False), - (['/foo/**.jpg', '/foo/.*' ], False), - (['/foo/**.jpg', '/bar.jpg' ], False), - (['/foo/**.jpg', '/**.jpg' ], False), - (['/foo/**.jpg', '/*.jpg' ], False), - (['/foo/**.jpg', '/foo/*.bar' ], False), - - (['/foo/{**,}', '/foo/' ], True), - (['/foo/{**,}', '/foo/bar' ], True), - (['/foo/{**,}', '/foo/bar/' ], True), - (['/foo/{**,}', '/foo/bar/baz' ], True), - (['/foo/{**,}', '/foo/bar/baz/' ], True), - (['/foo/{**,}', '/bar/' ], False), - - (['/foo/{,**}', '/foo/' ], True), - (['/foo/{,**}', '/foo/bar' ], True), - (['/foo/{,**}', '/foo/bar/' ], True), - (['/foo/{,**}', '/foo/bar/baz' ], True), - (['/foo/{,**}', '/foo/bar/baz/' ], True), - (['/foo/{,**}', '/bar/' ], False), - - (['/foo/a[bcd]e', '/foo/abe' ], True), - (['/foo/a[bcd]e', '/foo/abend' ], False), - (['/foo/a[bcd]e', '/foo/axe' ], False), - - (['/foo/a[b-d]e', '/foo/abe' ], True), - (['/foo/a[b-d]e', '/foo/ace' ], True), - (['/foo/a[b-d]e', '/foo/abend' ], False), - (['/foo/a[b-d]e', '/foo/axe' ], False), - - (['/foo/a[^bcd]e', '/foo/abe' ], False), - (['/foo/a[^bcd]e', '/foo/abend' ], False), - (['/foo/a[^bcd]e', '/foo/axe' ], True), - - (['/foo/{foo,bar,user,other}/bar/', '/foo/user/bar/' ], True), - (['/foo/{foo,bar,user,other}/bar/', '/foo/bar/bar/' ], True), - (['/foo/{foo,bar,user,other}/bar/', '/foo/wrong/bar/' ], False), - - (['/foo/{foo,bar,user,other}/test,ca}se/{aa,sd,nd}/bar/', '/foo/user/test,ca}se/aa/bar/' ], True), - (['/foo/{foo,bar,user,other}/test,ca}se/{aa,sd,nd}/bar/', '/foo/bar/test,ca}se/sd/bar/' ], True), - (['/foo/{foo,bar,user,other}/test,ca}se/{aa,sd,nd}/bar/', '/foo/wrong/user/bar/' ], False), - (['/foo/{foo,bar,user,other}/test,ca}se/{aa,sd,nd}/bar/', '/foo/user/wrong/bar/' ], False), - (['/foo/{foo,bar,user,other}/test,ca}se/{aa,sd,nd}/bar/', '/foo/wrong/aa/bar/' ], False), - ] + tests = ( + # aare path to check match expected? + (('/foo/**/bar/', '/foo/user/tools/bar/'), True), + (('/foo/**/bar/', '/foo/apparmor/bar/'), True), + (('/foo/**/bar/', '/foo/apparmor/bar'), False), + (('/foo/**/bar/', '/a/foo/apparmor/bar/'), False), + (('/foo/**/bar/', '/foo/apparmor/bar/baz'), False), + + (('/foo/*/bar/', '/foo/apparmor/bar/'), True), + (('/foo/*/bar/', '/foo/apparmor/tools/bar/'), False), + (('/foo/*/bar/', '/foo/apparmor/bar'), False), + + (('/foo/user/ba?/', '/foo/user/bar/'), True), + (('/foo/user/ba?/', '/foo/user/bar/apparmor/'), False), + (('/foo/user/ba?/', '/foo/user/ba/'), False), + (('/foo/user/ba?/', '/foo/user/ba//'), False), + + (('/foo/user/bar/**', '/foo/user/bar/apparmor'), True), + (('/foo/user/bar/**', '/foo/user/bar/apparmor/tools'), True), + (('/foo/user/bar/**', '/foo/user/bar/'), False), + + (('/foo/user/bar/*', '/foo/user/bar/apparmor'), True), + (('/foo/user/bar/*', '/foo/user/bar/apparmor/tools'), False), + (('/foo/user/bar/*', '/foo/user/bar/'), False), + (('/foo/user/bar/*', '/foo/user/bar/apparmor/'), False), + + (('/foo/**.jpg', '/foo/bar/baz/foobar.jpg'), True), + (('/foo/**.jpg', '/foo/bar/foobar.jpg'), True), + (('/foo/**.jpg', '/foo/bar/*.jpg'), True), + (('/foo/**.jpg', '/foo/bar.jpg'), True), + (('/foo/**.jpg', '/foo/**.jpg'), True), + (('/foo/**.jpg', '/foo/*.jpg'), True), + (('/foo/**.jpg', '/foo/barjpg'), False), + (('/foo/**.jpg', '/foo/.*'), False), + (('/foo/**.jpg', '/bar.jpg'), False), + (('/foo/**.jpg', '/**.jpg'), False), + (('/foo/**.jpg', '/*.jpg'), False), + (('/foo/**.jpg', '/foo/*.bar'), False), + + (('/foo/{**,}', '/foo/'), True), + (('/foo/{**,}', '/foo/bar'), True), + (('/foo/{**,}', '/foo/bar/'), True), + (('/foo/{**,}', '/foo/bar/baz'), True), + (('/foo/{**,}', '/foo/bar/baz/'), True), + (('/foo/{**,}', '/bar/'), False), + + (('/foo/{,**}', '/foo/'), True), + (('/foo/{,**}', '/foo/bar'), True), + (('/foo/{,**}', '/foo/bar/'), True), + (('/foo/{,**}', '/foo/bar/baz'), True), + (('/foo/{,**}', '/foo/bar/baz/'), True), + (('/foo/{,**}', '/bar/'), False), + + (('/foo/a[bcd]e', '/foo/abe'), True), + (('/foo/a[bcd]e', '/foo/abend'), False), + (('/foo/a[bcd]e', '/foo/axe'), False), + + (('/foo/a[b-d]e', '/foo/abe'), True), + (('/foo/a[b-d]e', '/foo/ace'), True), + (('/foo/a[b-d]e', '/foo/abend'), False), + (('/foo/a[b-d]e', '/foo/axe'), False), + + (('/foo/a[^bcd]e', '/foo/abe'), False), + (('/foo/a[^bcd]e', '/foo/abend'), False), + (('/foo/a[^bcd]e', '/foo/axe'), True), + + (('/foo/{foo,bar,user,other}/bar/', '/foo/user/bar/'), True), + (('/foo/{foo,bar,user,other}/bar/', '/foo/bar/bar/'), True), + (('/foo/{foo,bar,user,other}/bar/', '/foo/wrong/bar/'), False), + + (('/foo/{foo,bar,user,other}/test,ca}se/{aa,sd,nd}/bar/', '/foo/user/test,ca}se/aa/bar/'), True), + (('/foo/{foo,bar,user,other}/test,ca}se/{aa,sd,nd}/bar/', '/foo/bar/test,ca}se/sd/bar/'), True), + (('/foo/{foo,bar,user,other}/test,ca}se/{aa,sd,nd}/bar/', '/foo/wrong/user/bar/'), False), + (('/foo/{foo,bar,user,other}/test,ca}se/{aa,sd,nd}/bar/', '/foo/user/wrong/bar/'), False), + (('/foo/{foo,bar,user,other}/test,ca}se/{aa,sd,nd}/bar/', '/foo/wrong/aa/bar/'), False), + ) def _run_test(self, params, expected): regex, path = params parsed_regex = re.compile(convert_regexp(regex)) - self.assertEqual(bool(parsed_regex.search(path)), expected, 'Incorrectly Parsed regex: %s' %regex) + self.assertEqual(bool(parsed_regex.search(path)), expected, 'Incorrectly Parsed regex: %s' % regex) aare_obj = AARE(regex, True) self.assertEqual(aare_obj.match(path), expected, 'Incorrectly parsed AARE object: %s' % regex) if not ('*' in path or '{' in path or '}' in path or '?' in path): self.assertEqual(aare_obj.match(AARE(path, False)), expected, 'Incorrectly parsed AARE object: AARE(%s)' % regex) - def test_multi_usage(self): aare_obj = AARE('/foo/*', True) self.assertTrue(aare_obj.match('/foo/bar')) @@ -166,19 +168,20 @@ class TestConvert_regexpAndAAREMatch(AATest): with self.assertRaises(AppArmorBug): aare_obj.match(set()) + class TestAAREMatchFromLog(AATest): - tests = [ - # AARE log event match expected? - (['/foo/bar', '/foo/bar' ], True), - (['/foo/*', '/foo/bar' ], True), - (['/**', '/foo/bar' ], True), - (['/foo/*', '/bar/foo' ], False), - (['/foo/*', '/foo/"*' ], True), - (['/foo/bar', '/foo/*' ], False), - (['/foo/?', '/foo/(' ], True), - (['/foo/{bar,baz}', '/foo/bar' ], True), - (['/foo/{bar,baz}', '/foo/bars' ], False), - ] + tests = ( + # AARE log event match expected? + (('/foo/bar', '/foo/bar'), True), + (('/foo/*', '/foo/bar'), True), + (('/**', '/foo/bar'), True), + (('/foo/*', '/bar/foo'), False), + (('/foo/*', '/foo/"*'), True), + (('/foo/bar', '/foo/*'), False), + (('/foo/?', '/foo/('), True), + (('/foo/{bar,baz}', '/foo/bar'), True), + (('/foo/{bar,baz}', '/foo/bars'), False), + ) def _run_test(self, params, expected): regex, log_event = params @@ -186,13 +189,14 @@ class TestAAREMatchFromLog(AATest): aare_obj_2 = AARE(log_event, True, log_event=True) self.assertEqual(aare_obj_1.match(aare_obj_2), expected) + class TestAAREIsEqual(AATest): - tests = [ - # regex is path? check for expected - (['/foo', True, '/foo' ], True ), - (['@{foo}', True, '@{foo}' ], True ), - (['/**', True, '/foo' ], False), - ] + tests = ( + # regex is path? check for expected + (('/foo', True, '/foo'), True), + (('@{foo}', True, '@{foo}'), True), + (('/**', True, '/foo'), False), + ) def _run_test(self, params, expected): regex, is_path, check_for = params @@ -206,13 +210,14 @@ class TestAAREIsEqual(AATest): with self.assertRaises(AppArmorBug): aare_obj.is_equal(42) + class TestAAREIsPath(AATest): - tests = [ - # regex is path? match for expected - (['/foo*', True, '/foobar' ], True ), - (['@{PROC}/', True, '/foobar' ], False), - (['foo*', False, 'foobar' ], True ), - ] + tests = ( + # regex is path? match for expected + (('/foo*', True, '/foobar'), True), + (('@{PROC}/', True, '/foobar'), False), + (('foo*', False, 'foobar'), True), + ) def _run_test(self, params, expected): regex, is_path, check_for = params @@ -223,19 +228,21 @@ class TestAAREIsPath(AATest): with self.assertRaises(AppArmorException): AARE('foo*', True) + class TestAARERepr(AATest): def test_repr(self): obj = AARE('/foo', True) self.assertEqual(str(obj), "AARE('/foo')") + class TestAAREDeepcopy(AATest): - tests = [ - # regex is path? log event expected (dummy value) - (AARE('/foo', False) , True), - (AARE('/foo', False, True) , True), - (AARE('/foo', True) , True), - (AARE('/foo', True, True) , True), - ] + tests = ( + # regex is path? log event expected (dummy value) + (AARE('/foo', False), True), + (AARE('/foo', False, True), True), + (AARE('/foo', True), True), + (AARE('/foo', True, True), True), + ) def _run_test(self, params, expected): dup = deepcopy(params) @@ -247,57 +254,57 @@ class TestAAREDeepcopy(AATest): self.assertEqual(params.orig_regex, dup.orig_regex) self.assertEqual(params.orig_regex, dup.orig_regex) + class TestAAREglobPath(AATest): - tests = [ + tests = ( # _run_test() will also run each test with '/' appended - # regex expected AARE.regex - ('/foo/bar/baz**', '/foo/bar/**'), - ('/foo/bar/**baz', '/foo/bar/**'), - ('/foo/bar/fo**baz', '/foo/bar/**'), - ('/foo/bar/**foo**', '/foo/bar/**'), - ('/foo/bar/**f?o**', '/foo/bar/**'), - ('/foo/bar/**fo[a-z]**', '/foo/bar/**'), - - ('/foo/bar/baz', '/foo/bar/*'), - ('/foo/bar/baz*', '/foo/bar/*'), - ('/foo/bar/*baz', '/foo/bar/*'), - ('/foo/bar/fo*baz', '/foo/bar/*'), - ('/foo/bar/*foo*', '/foo/bar/*'), - - ('/foo/bar/b[a-z]z', '/foo/bar/*'), - ('/foo/bar/{bar,baz}', '/foo/bar/*'), - ('/foo/bar/{bar,ba/z}', '/foo/bar/{bar,ba/*'), # XXX - ('/foo/*/baz', '/foo/*/*'), - - ('/foo/bar/**', '/foo/**'), - ('/foo/bar/*', '/foo/**'), - - ('/foo/**/*', '/foo/**'), - ('/foo/*/**', '/foo/**'), - ('/foo/*/*', '/foo/**'), - - ('/foo', '/*',), - ('/b*', '/*',), - ('/*b', '/*',), - ('/*', '/*',), - ('/*.foo', '/*',), - ('/**.foo', '/**',), - ('/foo/*', '/**',), - ('/usr/foo/*', '/usr/**',), - ('/usr/foo/**', '/usr/**',), - ('/usr/foo/bar**', '/usr/foo/**',), - ('/usr/foo/**bar', '/usr/foo/**',), - ('/usr/bin/foo**bar', '/usr/bin/**',), - ('/usr/foo/**/bar', '/usr/foo/**/*',), - ('/usr/foo/**/*', '/usr/foo/**',), - ('/usr/foo/*/bar', '/usr/foo/*/*',), - ('/usr/bin/foo*bar', '/usr/bin/*',), - ('/usr/bin/*foo*', '/usr/bin/*',), - ('/usr/foo/*/*', '/usr/foo/**',), - ('/usr/foo/*/**', '/usr/foo/**',), - ('/**', '/**',), - - ] + # regex expected AARE.regex + ('/foo/bar/baz**', '/foo/bar/**'), + ('/foo/bar/**baz', '/foo/bar/**'), + ('/foo/bar/fo**baz', '/foo/bar/**'), + ('/foo/bar/**foo**', '/foo/bar/**'), + ('/foo/bar/**f?o**', '/foo/bar/**'), + ('/foo/bar/**fo[a-z]**', '/foo/bar/**'), + + ('/foo/bar/baz', '/foo/bar/*'), + ('/foo/bar/baz*', '/foo/bar/*'), + ('/foo/bar/*baz', '/foo/bar/*'), + ('/foo/bar/fo*baz', '/foo/bar/*'), + ('/foo/bar/*foo*', '/foo/bar/*'), + + ('/foo/bar/b[a-z]z', '/foo/bar/*'), + ('/foo/bar/{bar,baz}', '/foo/bar/*'), + ('/foo/bar/{bar,ba/z}', '/foo/bar/{bar,ba/*'), # XXX + ('/foo/*/baz', '/foo/*/*'), + + ('/foo/bar/**', '/foo/**'), + ('/foo/bar/*', '/foo/**'), + + ('/foo/**/*', '/foo/**'), + ('/foo/*/**', '/foo/**'), + ('/foo/*/*', '/foo/**'), + + ('/foo', '/*'), + ('/b*', '/*'), + ('/*b', '/*'), + ('/*', '/*'), + ('/*.foo', '/*'), + ('/**.foo', '/**'), + ('/foo/*', '/**'), + ('/usr/foo/*', '/usr/**'), + ('/usr/foo/**', '/usr/**'), + ('/usr/foo/bar**', '/usr/foo/**'), + ('/usr/foo/**bar', '/usr/foo/**'), + ('/usr/bin/foo**bar', '/usr/bin/**'), + ('/usr/foo/**/bar', '/usr/foo/**/*'), + ('/usr/foo/**/*', '/usr/foo/**'), + ('/usr/foo/*/bar', '/usr/foo/*/*'), + ('/usr/bin/foo*bar', '/usr/bin/*'), + ('/usr/bin/*foo*', '/usr/bin/*'), + ('/usr/foo/*/*', '/usr/foo/**'), + ('/usr/foo/*/**', '/usr/foo/**'), + ('/**', '/**'), + ) def _run_test(self, params, expected): # test for files @@ -310,102 +317,101 @@ class TestAAREglobPath(AATest): newpath = oldpath.glob_path() self.assertEqual(expected + '/', newpath.regex) + class TestAAREglobPathWithExt(AATest): - tests = [ + tests = ( # _run_test() will also run each test with '/' appended # regex expected AARE.regex # no extension - shouldn't change - ('/foo/bar/baz**', '/foo/bar/baz**'), - ('/foo/bar/**baz', '/foo/bar/**baz'), - ('/foo/bar/fo**baz', '/foo/bar/fo**baz'), - ('/foo/bar/**foo**', '/foo/bar/**foo**'), - ('/foo/bar/**f?o**', '/foo/bar/**f?o**'), - ('/foo/bar/**fo[a-z]**', '/foo/bar/**fo[a-z]**'), - - ('/foo/bar/baz', '/foo/bar/baz'), - ('/foo/bar/baz*', '/foo/bar/baz*'), - ('/foo/bar/*baz', '/foo/bar/*baz'), - ('/foo/bar/fo*baz', '/foo/bar/fo*baz'), - ('/foo/bar/*foo*', '/foo/bar/*foo*'), - - ('/foo/bar/b[a-z]z', '/foo/bar/b[a-z]z'), - ('/foo/bar/{bar,baz}', '/foo/bar/{bar,baz}'), - ('/foo/bar/{bar,ba/z}', '/foo/bar/{bar,ba/z}'), - ('/foo/*/baz', '/foo/*/baz'), - - ('/foo/bar/**', '/foo/bar/**'), - ('/foo/bar/*', '/foo/bar/*'), - - ('/foo/**/*', '/foo/**/*'), - ('/foo/*/**', '/foo/*/**'), - ('/foo/*/*', '/foo/*/*'), - - ('/*', '/*'), - ('/**', '/**'), - - ('/foo/bar', '/foo/bar'), - ('/foo/**/bar', '/foo/**/bar'), - ('/foo.bar', '/*.bar'), - ('/*.foo', '/*.foo' ), - ('/usr/*.bar', '/**.bar'), - ('/usr/**.bar', '/**.bar'), - ('/usr/foo**.bar', '/usr/**.bar'), - ('/usr/foo*.bar', '/usr/*.bar'), - ('/usr/fo*oo.bar', '/usr/*.bar'), - ('/usr/*foo*.bar', '/usr/*.bar'), - ('/usr/**foo.bar', '/usr/**.bar'), - ('/usr/*foo.bar', '/usr/*.bar'), - ('/usr/foo.b*', '/usr/*.b*'), + ('/foo/bar/baz**', '/foo/bar/baz**'), + ('/foo/bar/**baz', '/foo/bar/**baz'), + ('/foo/bar/fo**baz', '/foo/bar/fo**baz'), + ('/foo/bar/**foo**', '/foo/bar/**foo**'), + ('/foo/bar/**f?o**', '/foo/bar/**f?o**'), + ('/foo/bar/**fo[a-z]**', '/foo/bar/**fo[a-z]**'), + + ('/foo/bar/baz', '/foo/bar/baz'), + ('/foo/bar/baz*', '/foo/bar/baz*'), + ('/foo/bar/*baz', '/foo/bar/*baz'), + ('/foo/bar/fo*baz', '/foo/bar/fo*baz'), + ('/foo/bar/*foo*', '/foo/bar/*foo*'), + + ('/foo/bar/b[a-z]z', '/foo/bar/b[a-z]z'), + ('/foo/bar/{bar,baz}', '/foo/bar/{bar,baz}'), + ('/foo/bar/{bar,ba/z}', '/foo/bar/{bar,ba/z}'), + ('/foo/*/baz', '/foo/*/baz'), + + ('/foo/bar/**', '/foo/bar/**'), + ('/foo/bar/*', '/foo/bar/*'), + + ('/foo/**/*', '/foo/**/*'), + ('/foo/*/**', '/foo/*/**'), + ('/foo/*/*', '/foo/*/*'), + + ('/*', '/*'), + ('/**', '/**'), + + ('/foo/bar', '/foo/bar'), + ('/foo/**/bar', '/foo/**/bar'), + ('/foo.bar', '/*.bar'), + ('/*.foo', '/*.foo'), + ('/usr/*.bar', '/**.bar'), + ('/usr/**.bar', '/**.bar'), + ('/usr/foo**.bar', '/usr/**.bar'), + ('/usr/foo*.bar', '/usr/*.bar'), + ('/usr/fo*oo.bar', '/usr/*.bar'), + ('/usr/*foo*.bar', '/usr/*.bar'), + ('/usr/**foo.bar', '/usr/**.bar'), + ('/usr/*foo.bar', '/usr/*.bar'), + ('/usr/foo.b*', '/usr/*.b*'), # with extension added - ('/foo/bar/baz**.xy', '/foo/bar/**.xy'), - ('/foo/bar/**baz.xy', '/foo/bar/**.xy'), - ('/foo/bar/fo**baz.xy', '/foo/bar/**.xy'), - ('/foo/bar/**foo**.xy', '/foo/bar/**.xy'), - ('/foo/bar/**f?o**.xy', '/foo/bar/**.xy'), - ('/foo/bar/**fo[a-z]**.xy', '/foo/bar/**.xy'), - - ('/foo/bar/baz.xy', '/foo/bar/*.xy'), - ('/foo/bar/baz*.xy', '/foo/bar/*.xy'), - ('/foo/bar/*baz.xy', '/foo/bar/*.xy'), - ('/foo/bar/fo*baz.xy', '/foo/bar/*.xy'), - ('/foo/bar/*foo*.xy', '/foo/bar/*.xy'), - - ('/foo/bar/b[a-z]z.xy', '/foo/bar/*.xy'), - ('/foo/bar/{bar,baz}.xy', '/foo/bar/*.xy'), - ('/foo/bar/{bar,ba/z}.xy', '/foo/bar/{bar,ba/*.xy'), # XXX - ('/foo/*/baz.xy', '/foo/*/*.xy'), - - ('/foo/bar/**.xy', '/foo/**.xy'), - ('/foo/bar/*.xy', '/foo/**.xy'), - - ('/foo/**/*.xy', '/foo/**.xy'), - ('/foo/*/**.xy', '/foo/**.xy'), - ('/foo/*/*.xy', '/foo/**.xy'), - - ('/*.foo', '/*.foo'), - ('/**.foo', '/**.foo'), - - ('/foo.baz', '/*.baz'), - ('/b*.baz', '/*.baz'), - ('/*b.baz', '/*.baz'), - ('/foo/*.baz', '/**.baz'), - ('/usr/foo/*.baz', '/usr/**.baz'), - ('/usr/foo/**.baz', '/usr/**.baz'), - ('/usr/foo/bar**.baz', '/usr/foo/**.baz'), - ('/usr/foo/**bar.baz', '/usr/foo/**.baz'), - ('/usr/bin/foo**bar.baz', '/usr/bin/**.baz'), - ('/usr/foo/**/bar.baz', '/usr/foo/**/*.baz'), - ('/usr/foo/**/*.baz', '/usr/foo/**.baz'), - ('/usr/foo/*/bar.baz', '/usr/foo/*/*.baz'), - ('/usr/bin/foo*bar.baz', '/usr/bin/*.baz'), - ('/usr/bin/*foo*.baz', '/usr/bin/*.baz'), - ('/usr/foo/*/*.baz', '/usr/foo/**.baz'), - ('/usr/foo/*/**.baz', '/usr/foo/**.baz'), - - - ] + ('/foo/bar/baz**.xy', '/foo/bar/**.xy'), + ('/foo/bar/**baz.xy', '/foo/bar/**.xy'), + ('/foo/bar/fo**baz.xy', '/foo/bar/**.xy'), + ('/foo/bar/**foo**.xy', '/foo/bar/**.xy'), + ('/foo/bar/**f?o**.xy', '/foo/bar/**.xy'), + ('/foo/bar/**fo[a-z]**.xy', '/foo/bar/**.xy'), + + ('/foo/bar/baz.xy', '/foo/bar/*.xy'), + ('/foo/bar/baz*.xy', '/foo/bar/*.xy'), + ('/foo/bar/*baz.xy', '/foo/bar/*.xy'), + ('/foo/bar/fo*baz.xy', '/foo/bar/*.xy'), + ('/foo/bar/*foo*.xy', '/foo/bar/*.xy'), + + ('/foo/bar/b[a-z]z.xy', '/foo/bar/*.xy'), + ('/foo/bar/{bar,baz}.xy', '/foo/bar/*.xy'), + ('/foo/bar/{bar,ba/z}.xy', '/foo/bar/{bar,ba/*.xy'), # XXX + ('/foo/*/baz.xy', '/foo/*/*.xy'), + + ('/foo/bar/**.xy', '/foo/**.xy'), + ('/foo/bar/*.xy', '/foo/**.xy'), + + ('/foo/**/*.xy', '/foo/**.xy'), + ('/foo/*/**.xy', '/foo/**.xy'), + ('/foo/*/*.xy', '/foo/**.xy'), + + ('/*.foo', '/*.foo'), + ('/**.foo', '/**.foo'), + + ('/foo.baz', '/*.baz'), + ('/b*.baz', '/*.baz'), + ('/*b.baz', '/*.baz'), + ('/foo/*.baz', '/**.baz'), + ('/usr/foo/*.baz', '/usr/**.baz'), + ('/usr/foo/**.baz', '/usr/**.baz'), + ('/usr/foo/bar**.baz', '/usr/foo/**.baz'), + ('/usr/foo/**bar.baz', '/usr/foo/**.baz'), + ('/usr/bin/foo**bar.baz', '/usr/bin/**.baz'), + ('/usr/foo/**/bar.baz', '/usr/foo/**/*.baz'), + ('/usr/foo/**/*.baz', '/usr/foo/**.baz'), + ('/usr/foo/*/bar.baz', '/usr/foo/*/*.baz'), + ('/usr/bin/foo*bar.baz', '/usr/bin/*.baz'), + ('/usr/bin/*foo*.baz', '/usr/bin/*.baz'), + ('/usr/foo/*/*.baz', '/usr/foo/**.baz'), + ('/usr/foo/*/**.baz', '/usr/foo/**.baz'), + ) def _run_test(self, params, expected): # test for files diff --git a/utils/test/test-abi.py b/utils/test/test-abi.py index f0a64dd28..5fad6d72b 100644 --- a/utils/test/test-abi.py +++ b/utils/test/test-abi.py @@ -17,19 +17,21 @@ import unittest from collections import namedtuple from common_test import AATest, setup_all_loops +from apparmor.common import AppArmorBug, AppArmorException +# from apparmor.logparser import ReadLog +# from apparmor.rule import BaseRule from apparmor.rule.abi import AbiRule, AbiRuleset -#from apparmor.rule import BaseRule -from apparmor.common import AppArmorException, AppArmorBug -#from apparmor.logparser import ReadLog from apparmor.translations import init_translation + _ = init_translation() -exp = namedtuple('exp', [ # 'audit', 'allow_keyword', 'deny', - 'comment', - 'path', 'ifexists', 'ismagic']) +exp = namedtuple( + 'exp', ( # 'audit', 'allow_keyword', 'deny', + 'comment', 'path', 'ifexists', 'ismagic')) # --- tests for single AbiRule --- # + class AbiTest(AATest): def _compare_obj(self, obj, expected): self.assertEqual(False, obj.allow_keyword) # not supported in abi rules, expected to be always False @@ -42,18 +44,19 @@ class AbiTest(AATest): self.assertEqual(False, obj.ifexists) # not supported in abi rules, expected to be always False self.assertEqual(expected.ismagic, obj.ismagic) + class AbiTestParse(AbiTest): - tests = [ - # AbiRule object comment path if exists ismagic - ('abi <abstractions/base>,', exp('', 'abstractions/base', False, True )), # magic path - ('abi <abstractions/base>, # comment', exp(' # comment', 'abstractions/base', False, True )), - ('abi<abstractions/base>,#comment', exp(' #comment', 'abstractions/base', False, True )), - (' abi <abstractions/base> , ', exp('', 'abstractions/base', False, True )), - ('abi "/foo/bar",', exp('', '/foo/bar', False, False)), # absolute path - ('abi "/foo/bar", # comment', exp(' # comment', '/foo/bar', False, False)), - ('abi "/foo/bar",#comment', exp(' #comment', '/foo/bar', False, False)), - (' abi "/foo/bar" , ', exp('', '/foo/bar', False, False)), - ] + tests = ( + # AbiRule object comment path if exists ismagic + ('abi <abstractions/base>,', exp('', 'abstractions/base', False, True)), # magic path + ('abi <abstractions/base>, # comment', exp(' # comment', 'abstractions/base', False, True)), + ('abi<abstractions/base>,#comment', exp(' #comment', 'abstractions/base', False, True)), + (' abi <abstractions/base> , ', exp('', 'abstractions/base', False, True)), + ('abi "/foo/bar",', exp('', '/foo/bar', False, False)), # absolute path + ('abi "/foo/bar", # comment', exp(' # comment', '/foo/bar', False, False)), + ('abi "/foo/bar",#comment', exp(' #comment', '/foo/bar', False, False)), + (' abi "/foo/bar" , ', exp('', '/foo/bar', False, False)), + ) def _run_test(self, rawrule, expected): self.assertTrue(AbiRule.match(rawrule)) @@ -61,13 +64,14 @@ class AbiTestParse(AbiTest): self.assertEqual(rawrule.strip(), obj.raw_rule) self._compare_obj(obj, expected) + class AbiTestParseInvalid(AbiTest): - tests = [ -# (' some abi <abstractions/base>', AppArmorException), -# (' /etc/fstab r,', AppArmorException), -# ('/usr/abi r,', AppArmorException), -# ('/abi r,', AppArmorException), - ] + # tests = ( + # (' some abi <abstractions/base>', AppArmorException), + # (' /etc/fstab r,', AppArmorException), + # ('/usr/abi r,', AppArmorException), + # ('/abi r,', AppArmorException), + # ) def _run_test(self, rawrule, expected): self.assertTrue(AbiRule.match(rawrule)) # the above invalid rules still match the main regex! @@ -76,35 +80,37 @@ class AbiTestParseInvalid(AbiTest): # class AbiTestParseFromLog(AbiTest): # we'll never have log events for abi + class AbiFromInit(AbiTest): - tests = [ - # AbiRule object ifexists ismagic comment path ifexists ismagic - (AbiRule('abi/4.19', False, False) , exp('', 'abi/4.19', False, False )), - (AbiRule('foo', False, False) , exp('', 'foo', False, False )), - (AbiRule('bar', False, True) , exp('', 'bar', False, True )), - (AbiRule('comment', False, False, comment='# cmt') , exp('# cmt', 'comment', False, False )), - ] + tests = ( + # AbiRule object ifexists ismagic comment path ifexists ismagic + (AbiRule('abi/4.19', False, False), exp('', 'abi/4.19', False, False)), + (AbiRule('foo', False, False), exp('', 'foo', False, False)), + (AbiRule('bar', False, True), exp('', 'bar', False, True)), + (AbiRule('comment', False, False, comment='# cmt'), exp('# cmt', 'comment', False, False)), + ) def _run_test(self, obj, expected): self._compare_obj(obj, expected) + class InvalidAbiInit(AATest): - tests = [ - # init params expected exception - ([False, False, False ] , AppArmorBug), # wrong type for path - (['', False, False ] , AppArmorBug), # empty path - ([None, False, False ] , AppArmorBug), # wrong type for path -# ([' ', False, False ] , AppArmorBug), # whitespace-only path - (['foo', None, False ] , AppArmorBug), # wrong type for ifexists - (['foo', '', False ] , AppArmorBug), # wrong type for ifexists - (['foo', False, None ] , AppArmorBug), # wrong type for ismagic - (['foo', False, '' ] , AppArmorBug), # wrong type for ismagic - (['', True, False ] , AppArmorBug), # ifexists set - ] + tests = ( + # init params expected exception + ((False, False, False), AppArmorBug), # wrong type for path + (('', False, False), AppArmorBug), # empty path + ((None, False, False), AppArmorBug), # wrong type for path + # ((' ', False, False), AppArmorBug), # whitespace-only path + (('foo', None, False), AppArmorBug), # wrong type for ifexists + (('foo', '', False), AppArmorBug), # wrong type for ifexists + (('foo', False, None), AppArmorBug), # wrong type for ismagic + (('foo', False, ''), AppArmorBug), # wrong type for ismagic + (('', True, False), AppArmorBug), # ifexists set + ) def _run_test(self, params, expected): with self.assertRaises(expected): - AbiRule(params[0], params[1], params[2]) + AbiRule(*params) def test_missing_params_1(self): with self.assertRaises(TypeError): @@ -130,8 +136,9 @@ class InvalidAbiInit(AATest): with self.assertRaises(AppArmorBug): AbiRule('foo', True, False) + class InvalidAbiTest(AATest): - def _check_invalid_rawrule(self, rawrule, matches_regex = False): + def _check_invalid_rawrule(self, rawrule, matches_regex=False): obj = None self.assertEqual(AbiRule.match(rawrule), matches_regex) with self.assertRaises(AppArmorException): @@ -152,6 +159,7 @@ class InvalidAbiTest(AATest): # with self.assertRaises(AppArmorBug): # obj.get_clean(1) + class WriteAbiTestAATest(AATest): def _run_test(self, rawrule, expected): self.assertTrue(AbiRule.match(rawrule)) @@ -162,20 +170,20 @@ class WriteAbiTestAATest(AATest): self.assertEqual(expected.strip(), clean, 'unexpected clean rule') self.assertEqual(rawrule.strip(), raw, 'unexpected raw rule') - tests = [ - # raw rule clean rule - (' abi <foo> , ', 'abi <foo>,' ), - (' abi foo , ', 'abi "foo",' ), - (' abi "foo" , ', 'abi "foo",' ), - (' abi /foo , ', 'abi "/foo",' ), - (' abi "/foo" , ', 'abi "/foo",' ), - - (' abi <foo>, # bar ', 'abi <foo>, # bar' ), - (' abi foo , # bar ', 'abi "foo", # bar' ), - (' abi "foo", # bar ', 'abi "foo", # bar' ), - (' abi /foo, # bar ', 'abi "/foo", # bar' ), - (' abi "/foo", # bar ', 'abi "/foo", # bar' ), - ] + tests = ( + # raw rule clean rule + (' abi <foo> , ', 'abi <foo>,'), + (' abi foo , ', 'abi "foo",'), + (' abi "foo" , ', 'abi "foo",'), + (' abi /foo , ', 'abi "/foo",'), + (' abi "/foo" , ', 'abi "/foo",'), + + (' abi <foo>, # bar ', 'abi <foo>, # bar'), + (' abi foo , # bar ', 'abi "foo", # bar'), + (' abi "foo", # bar ', 'abi "foo", # bar'), + (' abi /foo, # bar ', 'abi "/foo", # bar'), + (' abi "/foo", # bar ', 'abi "/foo", # bar'), + ) def test_write_manually(self): obj = AbiRule('abs/foo', False, True, comment=' # cmt') @@ -199,90 +207,87 @@ class AbiCoveredTest(AATest): self.assertEqual(obj.is_covered(check_obj), expected[2], 'Mismatch in is_covered, expected %s' % expected[2]) self.assertEqual(obj.is_covered(check_obj, True, True), expected[3], 'Mismatch in is_covered/exact, expected %s' % expected[3]) + class AbiCoveredTest_01(AbiCoveredTest): rule = 'abi <foo>,' - tests = [ - # rule equal strict equal covered covered exact - ('abi <foo>,' , [ True , True , True , True ]), - ('abi "foo",' , [ False , False , False , False ]), - ('abi <foobar>,' , [ False , False , False , False ]), - ('abi "foo",' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ('abi <foo>,', (True, True, True, True)), + ('abi "foo",', (False, False, False, False)), + ('abi <foobar>,', (False, False, False, False)), + ('abi "foo",', (False, False, False, False)), + ) + class AbiCoveredTest_02(AbiCoveredTest): rule = 'abi "foo",' - tests = [ - # rule equal strict equal covered covered exact - ('abi <foo>,' , [ False , False , False , False ]), - ('abi "foo",' , [ True , True , True , True ]), - ('abi "foobar",' , [ False , False , False , False ]), - ('abi foo,' , [ True , False , True , True ]), - ] - -#class AbiCoveredTest_Invalid(AATest): -# def test_borked_obj_is_covered_1(self): -# obj = AbiRule.parse('abi <foo>') - -# testobj = AbiRule('foo', True, True) -# testobj.path = '' - -# with self.assertRaises(AppArmorBug): -# obj.is_covered(testobj) - -# def test_borked_obj_is_covered_2(self): -# obj = AbiRule.parse('abi send set=quit peer=/foo,') - -# testobj = AbiRule('send', 'quit', '/foo') -# testobj.abi = '' - -# with self.assertRaises(AppArmorBug): -# obj.is_covered(testobj) - -# def test_borked_obj_is_covered_3(self): -# obj = AbiRule.parse('abi send set=quit peer=/foo,') - -# testobj = AbiRule('send', 'quit', '/foo') -# testobj.peer = '' - -# with self.assertRaises(AppArmorBug): -# obj.is_covered(testobj) - -# def test_invalid_is_covered(self): -# obj = AbiRule.parse('abi send,') + tests = ( + # rule equal strict equal covered covered exact + ('abi <foo>,', (False, False, False, False)), + ('abi "foo",', (True, True, True, True)), + ('abi "foobar",', (False, False, False, False)), + ('abi foo,', (True, False, True, True)), + ) -# testobj = BaseRule() # different type -# with self.assertRaises(AppArmorBug): -# obj.is_covered(testobj) - -# def test_invalid_is_equal(self): -# obj = AbiRule.parse('abi send,') - -# testobj = BaseRule() # different type +# class AbiCoveredTest_Invalid(AATest): +# def test_borked_obj_is_covered_1(self): +# obj = AbiRule.parse('abi <foo>') +# +# testobj = AbiRule('foo', True, True) +# testobj.path = '' +# +# with self.assertRaises(AppArmorBug): +# obj.is_covered(testobj) +# +# def test_borked_obj_is_covered_2(self): +# obj = AbiRule.parse('abi send set=quit peer=/foo,') +# +# testobj = AbiRule('send', 'quit', '/foo') +# testobj.abi = '' +# +# with self.assertRaises(AppArmorBug): +# obj.is_covered(testobj) +# +# def test_borked_obj_is_covered_3(self): +# obj = AbiRule.parse('abi send set=quit peer=/foo,') +# +# testobj = AbiRule('send', 'quit', '/foo') +# testobj.peer = '' +# +# with self.assertRaises(AppArmorBug): +# obj.is_covered(testobj) +# +# def test_invalid_is_covered(self): +# obj = AbiRule.parse('abi send,') +# +# testobj = BaseRule() # different type +# +# with self.assertRaises(AppArmorBug): +# obj.is_covered(testobj) +# +# def test_invalid_is_equal(self): +# obj = AbiRule.parse('abi send,') +# +# testobj = BaseRule() # different type +# +# with self.assertRaises(AppArmorBug): +# obj.is_equal(testobj) -# with self.assertRaises(AppArmorBug): -# obj.is_equal(testobj) class AbiLogprofHeaderTest(AATest): -# tests = [ -# ('abi,', [ _('Access mode'), _('ALL'), _('Abi'), _('ALL'), _('Peer'), _('ALL'), ]), -# ('abi send,', [ _('Access mode'), 'send', _('Abi'), _('ALL'), _('Peer'), _('ALL'), ]), -# ('abi send set=quit,', [ _('Access mode'), 'send', _('Abi'), 'quit', _('Peer'), _('ALL'), ]), -# ('deny abi,', [_('Qualifier'), 'deny', _('Access mode'), _('ALL'), _('Abi'), _('ALL'), _('Peer'), _('ALL'), ]), -# ('allow abi send,', [_('Qualifier'), 'allow', _('Access mode'), 'send', _('Abi'), _('ALL'), _('Peer'), _('ALL'), ]), -# ('audit abi send set=quit,', [_('Qualifier'), 'audit', _('Access mode'), 'send', _('Abi'), 'quit', _('Peer'), _('ALL'), ]), -# ('audit deny abi send,', [_('Qualifier'), 'audit deny', _('Access mode'), 'send', _('Abi'), _('ALL'), _('Peer'), _('ALL'), ]), -# ('abi set=(int, quit),', [ _('Access mode'), _('ALL'), _('Abi'), 'int quit', _('Peer'), _('ALL'), ]), -# ('abi set=( quit, int),', [ _('Access mode'), _('ALL'), _('Abi'), 'int quit', _('Peer'), _('ALL'), ]), -# ('abi (send, receive) set=( quit, int) peer=/foo,', [ _('Access mode'), 'receive send', _('Abi'), 'int quit', _('Peer'), '/foo', ]), -# ] + tests = ( + ('abi <abi/3.0>,', [_('Abi'), 'abi <abi/3.0>,']), + ('abi "/foo/bar",', [_('Abi'), 'abi "/foo/bar",']), + ) def _run_test(self, params, expected): - obj = AbiRule._parse(params) + obj = AbiRule.parse(params) self.assertEqual(obj.logprof_header(), expected) + ## --- tests for AbiRuleset --- # class AbiRulesTest(AATest): @@ -297,10 +302,10 @@ class AbiRulesTest(AATest): def test_ruleset_1(self): ruleset = AbiRuleset() - rules = [ + rules = ( ' abi <foo> ,', ' abi "/bar", ', - ] + ) expected_raw = [ 'abi <foo> ,', @@ -327,23 +332,26 @@ class AbiRulesTest(AATest): self.assertEqual(expected_clean, ruleset.get_clean()) self.assertEqual(expected_clean_unsorted, ruleset.get_clean_unsorted()) + class AbiGlobTestAATest(AATest): def setUp(self): self.maxDiff = None self.ruleset = AbiRuleset() -# def test_glob(self): -# with self.assertRaises(NotImplementedError): -# # get_glob_ext is not available for include rules -# self.ruleset.get_glob('include send set=int,') + # def test_glob(self): + # with self.assertRaises(NotImplementedError): + # # get_glob_ext is not available for include rules + # self.ruleset.get_glob('include send set=int,') def test_glob_ext(self): with self.assertRaises(NotImplementedError): # get_glob_ext is not available for include rules self.ruleset.get_glob_ext('include send set=int,') -#class AbiDeleteTestAATest(AATest): -# pass + +# class AbiDeleteTestAATest(AATest): +# pass + setup_all_loops(__name__) if __name__ == '__main__': diff --git a/utils/test/test-alias.py b/utils/test/test-alias.py index eff621c8d..6448ab2e2 100644 --- a/utils/test/test-alias.py +++ b/utils/test/test-alias.py @@ -15,22 +15,23 @@ import unittest from collections import namedtuple -from common_test import AATest, setup_all_loops -from apparmor.rule.alias import AliasRule, AliasRuleset +from apparmor.common import AppArmorBug, AppArmorException from apparmor.rule import BaseRule -from apparmor.common import AppArmorException, AppArmorBug +from apparmor.rule.alias import AliasRule, AliasRuleset from apparmor.translations import init_translation +from common_test import AATest, setup_all_loops + _ = init_translation() -exp = namedtuple('exp', ['comment', - 'orig_path', 'target']) +exp = namedtuple('exp', ('comment', 'orig_path', 'target')) # --- tests for single AliasRule --- # + class AliasTest(AATest): def _compare_obj(self, obj, expected): - # aliass don't support the allow, audit or deny keyword + # aliases don't support the allow, audit or deny keyword self.assertEqual(False, obj.allow_keyword) self.assertEqual(False, obj.audit) self.assertEqual(False, obj.deny) @@ -39,13 +40,14 @@ class AliasTest(AATest): self.assertEqual(expected.target, obj.target) self.assertEqual(expected.comment, obj.comment) + class AliasTestParse(AliasTest): - tests = [ - # rawrule comment orig_path target - ('alias /foo -> /bar,', exp('', '/foo', '/bar' )), - (' alias /foo -> /bar , # comment', exp(' # comment', '/foo', '/bar' )), - ('alias "/foo 2" -> "/bar 2" ,', exp('', '/foo 2', '/bar 2' )), - ] + tests = ( + # rawrule comment orig_path target + ('alias /foo -> /bar,', exp('', '/foo', '/bar')), + (' alias /foo -> /bar , # comment', exp(' # comment', '/foo', '/bar')), + ('alias "/foo 2" -> "/bar 2" ,', exp('', '/foo 2', '/bar 2')), + ) def _run_test(self, rawrule, expected): self.assertTrue(AliasRule.match(rawrule)) @@ -53,47 +55,49 @@ class AliasTestParse(AliasTest): self.assertEqual(rawrule.strip(), obj.raw_rule) self._compare_obj(obj, expected) + class AliasTestParseInvalid(AliasTest): - tests = [ - # rawrule matches regex exception - ('alias ,' , (False, AppArmorException)), - ('alias /foo ,' , (False, AppArmorException)), - ('alias /foo -> ,' , (True, AppArmorException)), - ('alias -> /bar ,' , (True, AppArmorException)), - ('/foo -> bar ,' , (False, AppArmorException)), - ] + tests = ( + # rawrule matches regex exception + ('alias ,', (False, AppArmorException)), + ('alias /foo ,', (False, AppArmorException)), + ('alias /foo -> ,', (True, AppArmorException)), + ('alias -> /bar ,', (True, AppArmorException)), + ('/foo -> bar ,', (False, AppArmorException)), + ) def _run_test(self, rawrule, expected): self.assertEqual(AliasRule.match(rawrule), expected[0]) with self.assertRaises(expected[1]): AliasRule.parse(rawrule) + class AliasFromInit(AliasTest): - tests = [ - # AliasRule object comment orig_path target - (AliasRule('/foo', '/bar'), exp('', '/foo', '/bar' )), - (AliasRule('/foo', '/bar', comment='# cmt'), exp('# cmt', '/foo', '/bar' )), - ] + tests = ( + # AliasRule object comment orig_path target + (AliasRule('/foo', '/bar'), exp('', '/foo', '/bar')), + (AliasRule('/foo', '/bar', comment='# cmt'), exp('# cmt', '/foo', '/bar')), + ) def _run_test(self, obj, expected): self._compare_obj(obj, expected) class InvalidAliasInit(AATest): - tests = [ - # init params expected exception - ([None, '/bar' ], AppArmorBug), # orig_path not a str - (['', '/bar' ], AppArmorException), # empty orig_path - (['foo', '/bar' ], AppArmorException), # orig_path not starting with / + tests = ( + # init params expected exception + ((None, '/bar'), AppArmorBug), # orig_path not a str + (('', '/bar'), AppArmorException), # empty orig_path + (('foo', '/bar'), AppArmorException), # orig_path not starting with / - (['/foo', None ], AppArmorBug), # target not a str - (['/foo', '' ], AppArmorException), # empty target - (['/foo', 'bar' ], AppArmorException), # target not starting with / - ] + (('/foo', None), AppArmorBug), # target not a str + (('/foo', ''), AppArmorException), # empty target + (('/foo', 'bar'), AppArmorException), # target not starting with / + ) def _run_test(self, params, expected): with self.assertRaises(expected): - AliasRule(params[0], params[1]) + AliasRule(*params) def test_missing_params_1(self): with self.assertRaises(TypeError): @@ -132,13 +136,13 @@ class InvalidAliasTest(AATest): class WriteAliasTestAATest(AATest): - tests = [ - # raw rule clean rule - (' alias /foo -> /bar, ', 'alias /foo -> /bar,'), - (' alias /foo -> /bar, # comment', 'alias /foo -> /bar,'), - (' alias "/foo" -> "/bar", ', 'alias /foo -> /bar,'), - (' alias "/foo 2" -> "/bar 2", ', 'alias "/foo 2" -> "/bar 2",'), - ] + tests = ( + # raw rule clean rule + (' alias /foo -> /bar, ', 'alias /foo -> /bar,'), + (' alias /foo -> /bar, # comment', 'alias /foo -> /bar,'), + (' alias "/foo" -> "/bar", ', 'alias /foo -> /bar,'), + (' alias "/foo 2" -> "/bar 2", ', 'alias "/foo 2" -> "/bar 2",'), + ) def _run_test(self, rawrule, expected): self.assertTrue(AliasRule.match(rawrule)) @@ -179,37 +183,39 @@ class AliasCoveredTest(AATest): self.assertEqual(obj.is_covered(check_obj), expected[2], 'Mismatch in is_covered, expected %s' % expected[2]) self.assertEqual(obj.is_covered(check_obj, True, True), expected[3], 'Mismatch in is_covered/exact, expected %s' % expected[3]) + class AliasCoveredTest_01(AliasCoveredTest): rule = 'alias /foo -> /bar,' - tests = [ - # rule equal strict equal covered covered exact - (' alias /foo -> /bar,' , [ True , True , True , True ]), - (' alias /foo -> /bar , ' , [ True , False , True , True ]), - (' alias /foo -> /bar, # comment' , [ True , False , True , True ]), - (' alias /foo -> /bar, # comment' , [ True , False , True , True ]), - (' alias /foo -> /asdf,' , [ False , False , False , False ]), - (' alias /whatever -> /bar,' , [ False , False , False , False ]), - (' alias /whatever -> /asdf,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + (' alias /foo -> /bar,', (True, True, True, True)), + (' alias /foo -> /bar , ', (True, False, True, True)), + (' alias /foo -> /bar, # comment', (True, False, True, True)), + (' alias /foo -> /bar, # comment', (True, False, True, True)), + (' alias /foo -> /asdf,', (False, False, False, False)), + (' alias /whatever -> /bar,', (False, False, False, False)), + (' alias /whatever -> /asdf,', (False, False, False, False)), + ) -class AliasCoveredTest_Invalid(AATest): -# def test_borked_obj_is_covered_1(self): -# obj = AliasRule.parse('alias /foo -> /bar,') -# testobj = AliasRule('/foo', '/bar') - -# with self.assertRaises(AppArmorBug): -# obj.is_covered(testobj) - -# def test_borked_obj_is_covered_2(self): -# obj = AliasRule.parse('alias /foo -> /bar,') - -# testobj = AliasRule('/foo', '/bar') -# testobj.target = '' - -# with self.assertRaises(AppArmorBug): -# obj.is_covered(testobj) +class AliasCoveredTest_Invalid(AATest): + # def test_borked_obj_is_covered_1(self): + # obj = AliasRule.parse('alias /foo -> /bar,') + # + # testobj = AliasRule('/foo', '/bar') + # + # with self.assertRaises(AppArmorBug): + # obj.is_covered(testobj) + # + # def test_borked_obj_is_covered_2(self): + # obj = AliasRule.parse('alias /foo -> /bar,') + # + # testobj = AliasRule('/foo', '/bar') + # testobj.target = '' + # + # with self.assertRaises(AppArmorBug): + # obj.is_covered(testobj) def test_invalid_is_covered_3(self): obj = AliasRule.parse('alias /foo -> /bar,') @@ -227,15 +233,17 @@ class AliasCoveredTest_Invalid(AATest): with self.assertRaises(AppArmorBug): obj.is_equal(testobj) + class AliasLogprofHeaderTest(AATest): - tests = [ - ('alias /foo -> /bar,', [_('Alias'), '/foo -> /bar' ]), - ] + tests = ( + ('alias /foo -> /bar,', [_('Alias'), '/foo -> /bar']), + ) def _run_test(self, params, expected): - obj = AliasRule._parse(params) + obj = AliasRule.parse(params) self.assertEqual(obj.logprof_header(), expected) + # --- tests for AliasRuleset --- # class AliasRulesTest(AATest): @@ -287,6 +295,7 @@ class AliasRulesTest(AATest): self.assertEqual(expected_clean, ruleset.get_clean()) self.assertEqual(expected_clean_unsorted, ruleset.get_clean_unsorted()) + class AliasGlobTestAATest(AATest): def setUp(self): self.ruleset = AliasRuleset() @@ -300,9 +309,11 @@ class AliasGlobTestAATest(AATest): # get_glob_ext is not available for change_profile rules self.ruleset.get_glob_ext('@{foo} = /bar') + class AliasDeleteTestAATest(AATest): pass + setup_all_loops(__name__) if __name__ == '__main__': unittest.main(verbosity=1) diff --git a/utils/test/test-baserule.py b/utils/test/test-baserule.py index e9c18f2a1..aff7d637e 100644 --- a/utils/test/test-baserule.py +++ b/utils/test/test-baserule.py @@ -9,14 +9,14 @@ # # ------------------------------------------------------------------ +import re import unittest -from common_test import AATest, setup_all_loops +import apparmor.severity as severity from apparmor.common import AppArmorBug from apparmor.rule import BaseRule, parse_modifiers -import apparmor.severity as severity +from common_test import AATest, setup_all_loops -import re class TestBaserule(AATest): def test_abstract__parse(self): @@ -51,14 +51,14 @@ class TestBaserule(AATest): obj.is_covered_localvars(None) def test_parse_modifiers_invalid(self): - regex = re.compile('^\s*(?P<audit>audit\s+)?(?P<allow>allow\s+|deny\s+|invalid\s+)?') + regex = re.compile(r'^\s*(?P<audit>audit\s+)?(?P<allow>allow\s+|deny\s+|invalid\s+)?') matches = regex.search('audit invalid ') with self.assertRaises(AppArmorBug): parse_modifiers(matches) def test_default_severity(self): - sev_db = severity.Severity('severity.db', 'unknown') + sev_db = severity.Severity('../severity.db', 'unknown') obj = BaseRule() rank = obj.severity(sev_db) self.assertEqual(rank, sev_db.NOT_IMPLEMENTED) diff --git a/utils/test/test-boolean.py b/utils/test/test-boolean.py new file mode 100644 index 000000000..a6f35ba65 --- /dev/null +++ b/utils/test/test-boolean.py @@ -0,0 +1,327 @@ +#!/usr/bin/python3 +# ---------------------------------------------------------------------- +# Copyright (C) 2020 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. +# +# ---------------------------------------------------------------------- + +import unittest +from collections import namedtuple + +from apparmor.common import AppArmorBug, AppArmorException +from apparmor.rule import BaseRule +from apparmor.rule.boolean import BooleanRule, BooleanRuleset +from apparmor.translations import init_translation +from common_test import AATest, setup_all_loops + +_ = init_translation() + +exp = namedtuple('exp', ('comment', 'varname', 'value')) + +# --- tests for single BooleanRule --- # + + +class BooleanTest(AATest): + def _compare_obj(self, obj, expected): + # boolean variables don't support the allow, audit or deny keyword + self.assertEqual(False, obj.allow_keyword) + self.assertEqual(False, obj.audit) + self.assertEqual(False, obj.deny) + + self.assertEqual(expected.varname, obj.varname) + self.assertEqual(expected.value, obj.value) + self.assertEqual(expected.comment, obj.comment) + + +class BooleanTestParse(BooleanTest): + tests = ( + # rawrule comment varname value + ('$foo=true', exp('', '$foo', 'true')), + ('$foo = false', exp('', '$foo', 'false')), + ('$foo=TrUe', exp('', '$foo', 'true')), + ('$foo = FaLsE', exp('', '$foo', 'false')), + (' $foo = true ', exp('', '$foo', 'true')), + (' $foo = true # comment', exp(' # comment', '$foo', 'true')), + ) + + def _run_test(self, rawrule, expected): + self.assertTrue(BooleanRule.match(rawrule)) + obj = BooleanRule.parse(rawrule) + self.assertEqual(rawrule.strip(), obj.raw_rule) + self._compare_obj(obj, expected) + + +class BooleanTestParseInvalid(BooleanTest): + tests = ( + # rawrule matches regex exception + ('$foo =', (False, AppArmorException)), + ('$ foo = # comment', (False, AppArmorException)), + ('${foo = ', (False, AppArmorException)), + # XXX RE_PROFILE_BOOLEAN allows a trailing comma even if the parser disallows it + # ('$foo = true,', (True, AppArmorException)), # trailing comma + # ('$foo = false , ', (True, AppArmorException)), # trailing comma + # ('$foo = true, # comment', (True, AppArmorException)), # trailing comma + ) + + def _run_test(self, rawrule, expected): + self.assertEqual(BooleanRule.match(rawrule), expected[0]) + with self.assertRaises(expected[1]): + BooleanRule.parse(rawrule) + + +class BooleanFromInit(BooleanTest): + # tests = ( + # # BooleanRule object comment varname value + # (BooleanRule('$foo', True), exp('', '$foo', True)), + # (BooleanRule('$foo', False), exp('', '$foo', False)), + # (BooleanRule('$foo', True, comment='# cmt'), exp('# cmt', '$foo', True)), + # (BooleanRule('$foo', False, comment='# cmt'), exp('# cmt', '$foo', False)), + # ) + + def _run_test(self, obj, expected): + self._compare_obj(obj, expected) + + +class InvalidBooleanInit(AATest): + tests = ( + # init params expected exception + ((None, True), AppArmorBug), # varname not a str + (('', True), AppArmorException), # empty varname + (('foo', True), AppArmorException), # varname not starting with '$' + (('foo', True), AppArmorException), # varname not starting with '$' + + (('$foo', None), AppArmorBug), # value not a string + (('$foo', ''), AppArmorException), # empty value + (('$foo', 'maybe'), AppArmorException), # invalid value + ) + + def _run_test(self, params, expected): + with self.assertRaises(expected): + BooleanRule(*params) + + def test_missing_params_1(self): + with self.assertRaises(TypeError): + BooleanRule() + + def test_missing_params_2(self): + with self.assertRaises(TypeError): + BooleanRule('$foo') + + def test_invalid_audit(self): + with self.assertRaises(AppArmorBug): + BooleanRule('$foo', 'true', audit=True) + + def test_invalid_deny(self): + with self.assertRaises(AppArmorBug): + BooleanRule('$foo', 'true', deny=True) + + +class InvalidBooleanTest(AATest): + def _check_invalid_rawrule(self, rawrule, matches_regex=False): + obj = None + self.assertEqual(BooleanRule.match(rawrule), matches_regex) + with self.assertRaises(AppArmorException): + obj = BooleanRule.parse(rawrule) + + self.assertIsNone(obj, 'BooleanRule handed back an object unexpectedly') + + def test_invalid_missing_value(self): + self._check_invalid_rawrule('$foo = ', matches_regex=False) # missing value + + def test_invalid_net_non_BooleanRule(self): + self._check_invalid_rawrule('dbus,') # not a boolean rule + + +class WriteBooleanTestAATest(AATest): + tests = ( + # raw rule clean rule + (' $foo = true ', '$foo = true'), + (' $foo = true # comment', '$foo = true'), + (' $foo = false ', '$foo = false'), + (' $foo = false # comment', '$foo = false'), + ) + + def _run_test(self, rawrule, expected): + self.assertTrue(BooleanRule.match(rawrule)) + obj = BooleanRule.parse(rawrule) + clean = obj.get_clean() + raw = obj.get_raw() + + self.assertEqual(expected.strip(), clean, 'unexpected clean rule') + self.assertEqual(rawrule.strip(), raw, 'unexpected raw rule') + + def test_write_manually_1(self): + obj = BooleanRule('$foo', 'true') + + expected = ' $foo = true' + + self.assertEqual(expected, obj.get_clean(2), 'unexpected clean rule') + self.assertEqual(expected, obj.get_raw(2), 'unexpected raw rule') + + def test_write_manually_2(self): + obj = BooleanRule('$foo', 'false') + + expected = ' $foo = false' + + self.assertEqual(expected, obj.get_clean(2), 'unexpected clean rule') + self.assertEqual(expected, obj.get_raw(2), 'unexpected raw rule') + + +class BooleanCoveredTest(AATest): + def _run_test(self, param, expected): + obj = BooleanRule.parse(self.rule) + check_obj = BooleanRule.parse(param) + + self.assertTrue(BooleanRule.match(param)) + + self.assertEqual(obj.is_equal(check_obj), expected[0], 'Mismatch in is_equal, expected %s' % expected[0]) + self.assertEqual(obj.is_equal(check_obj, True), expected[1], 'Mismatch in is_equal/strict, expected %s' % expected[1]) + + self.assertEqual(obj.is_covered(check_obj), expected[2], 'Mismatch in is_covered, expected %s' % expected[2]) + self.assertEqual(obj.is_covered(check_obj, True, True), expected[3], 'Mismatch in is_covered/exact, expected %s' % expected[3]) + + +class BooleanCoveredTest_01(BooleanCoveredTest): + rule = '$foo = true' + + tests = ( + # rule equal strict equal covered covered exact + (' $foo = true', (True, True, True, True)), + (' $foo = TRUE', (True, False, True, True)), # upper vs. lower case + (' $foo = true # comment', (True, False, True, True)), + (' $foo = false', (False, False, False, False)), + (' $foo = false # cmt', (False, False, False, False)), + (' $bar = true', (False, False, False, False)), # different variable name + ) + + +class BooleanCoveredTest_02(BooleanCoveredTest): + rule = '$foo = false' + + tests = ( + # rule equal strict equal covered covered exact + (' $foo = false', (True, True, True, True)), + (' $foo = false # comment', (True, False, True, True)), + (' $foo = true', (False, False, False, False)), + (' $foo = true # cmt', (False, False, False, False)), + (' $bar = false', (False, False, False, False)), # different variable name + ) + + +class BooleanCoveredTest_Invalid(AATest): + def test_borked_obj_is_covered_2(self): + obj = BooleanRule.parse('$foo = true') + + testobj = BooleanRule('$foo', 'true') + testobj.value = '' + + with self.assertRaises(AppArmorBug): + obj.is_covered(testobj) + + def test_invalid_is_covered_3(self): + obj = BooleanRule.parse('$foo = true') + + testobj = BaseRule() # different type + + with self.assertRaises(AppArmorBug): + obj.is_covered(testobj) + + def test_invalid_is_equal(self): + obj = BooleanRule.parse('$foo = true') + + testobj = BaseRule() # different type + + with self.assertRaises(AppArmorBug): + obj.is_equal(testobj) + + +class BooleanLogprofHeaderTest(AATest): + tests = ( + ('$foo = true', [_('Boolean Variable'), '$foo = true']), + ) + + def _run_test(self, params, expected): + obj = BooleanRule.parse(params) + self.assertEqual(obj.logprof_header(), expected) + + +# --- tests for BooleanRuleset --- # + +class BooleanRulesTest(AATest): + def test_empty_ruleset(self): + ruleset = BooleanRuleset() + ruleset_2 = BooleanRuleset() + self.assertEqual([], ruleset.get_raw(2)) + self.assertEqual([], ruleset.get_clean(2)) + self.assertEqual([], ruleset_2.get_raw(2)) + self.assertEqual([], ruleset_2.get_clean(2)) + + def test_ruleset_1(self): + ruleset = BooleanRuleset() + rules = [ + '$foo = true', + '$baz= false', + ] + + expected_raw = [ + '$foo = true', + '$baz= false', + '', + ] + + expected_clean = [ + '$baz = false', + '$foo = true', + '', + ] + + expected_clean_unsorted = [ + '$foo = true', + '$baz = false', + '', + ] + + for rule in rules: + ruleset.add(BooleanRule.parse(rule)) + + self.assertEqual(expected_raw, ruleset.get_raw()) + self.assertEqual(expected_clean, ruleset.get_clean()) + self.assertEqual(expected_clean_unsorted, ruleset.get_clean_unsorted()) + + def test_ruleset_overwrite(self): + ruleset = BooleanRuleset() + + ruleset.add(BooleanRule.parse('$foo = true')) + with self.assertRaises(AppArmorException): + ruleset.add(BooleanRule.parse('$foo = false')) # attempt to redefine @{foo} + + +class BooleanGlobTestAATest(AATest): + def setUp(self): + self.ruleset = BooleanRuleset() + +# def test_glob_1(self): +# with self.assertRaises(NotImplementedError): +# self.ruleset.get_glob('$foo = true') + + def test_glob_ext(self): + with self.assertRaises(NotImplementedError): + # get_glob_ext is not available for boolean rules + self.ruleset.get_glob_ext('$foo = true') + + +class BooleanDeleteTestAATest(AATest): + pass + + +setup_all_loops(__name__) +if __name__ == '__main__': + unittest.main(verbosity=1) diff --git a/utils/test/test-capability.py b/utils/test/test-capability.py index ab25223c4..3bb64b817 100644 --- a/utils/test/test-capability.py +++ b/utils/test/test-capability.py @@ -14,16 +14,18 @@ # ---------------------------------------------------------------------- import unittest -from common_test import AATest, setup_all_loops -from apparmor.rule.capability import CapabilityRule, CapabilityRuleset -from apparmor.rule import BaseRule import apparmor.severity as severity -from apparmor.common import AppArmorException, AppArmorBug, hasher +from apparmor.common import AppArmorBug, AppArmorException, hasher from apparmor.logparser import ReadLog +from apparmor.rule import BaseRule +from apparmor.rule.capability import CapabilityRule, CapabilityRuleset from apparmor.translations import init_translation +from common_test import AATest, setup_all_loops + _ = init_translation() + # --- tests for single CapabilityRule --- # class CapabilityTest(AATest): @@ -46,54 +48,54 @@ class CapabilityTest(AATest): def test_cap_allow_all(self): self._compare_obj_with_rawrule("capability,", { - 'allow_keyword': False, - 'deny': False, - 'audit': False, - 'capability': set(), - 'all_caps': True, - 'comment': "", + 'allow_keyword': False, + 'deny': False, + 'audit': False, + 'capability': set(), + 'all_caps': True, + 'comment': "", }) def test_cap_allow_sys_admin(self): self._compare_obj_with_rawrule("capability sys_admin,", { - 'allow_keyword': False, - 'deny': False, - 'audit': False, - 'capability': {'sys_admin'}, - 'all_caps': False, - 'comment': "", + 'allow_keyword': False, + 'deny': False, + 'audit': False, + 'capability': {'sys_admin'}, + 'all_caps': False, + 'comment': "", }) def test_cap_deny_sys_admin(self): self._compare_obj_with_rawrule(" deny capability sys_admin, # some comment", { - 'allow_keyword': False, - 'deny': True, - 'audit': False, - 'capability': {'sys_admin'}, - 'all_caps': False, - 'comment': " # some comment", + 'allow_keyword': False, + 'deny': True, + 'audit': False, + 'capability': {'sys_admin'}, + 'all_caps': False, + 'comment': " # some comment", }) def test_cap_multi(self): self._compare_obj_with_rawrule("capability sys_admin dac_override,", { - 'allow_keyword': False, - 'deny': False, - 'audit': False, - 'capability': {'sys_admin', 'dac_override'}, - 'all_caps': False, - 'comment': "", + 'allow_keyword': False, + 'deny': False, + 'audit': False, + 'capability': {'sys_admin', 'dac_override'}, + 'all_caps': False, + 'comment': "", }) # Template for test_cap_* functions - # def test_cap_(self): - # self._compare_obj_with_rawrule("capability,", { - # 'allow_keyword': False, - # 'deny': False, - # 'audit': False, - # 'capability': set(), # (or {'foo'} if not empty) - # 'all_caps': False, - # 'comment': "", - # }) + # def test_cap_(self): + # self._compare_obj_with_rawrule("capability,", { + # 'allow_keyword': False, + # 'deny': False, + # 'audit': False, + # 'capability': set(), # (or {'foo'} if not empty) + # 'all_caps': False, + # 'comment': "", + # }) def test_cap_from_log(self): parser = ReadLog('', '', '') @@ -122,97 +124,98 @@ class CapabilityTest(AATest): 'family': None, 'protocol': None, 'sock_type': None, + 'class': None, }) obj = CapabilityRule(parsed_event['name'], log_event=parsed_event) self._compare_obj(obj, { - 'allow_keyword': False, - 'deny': False, - 'audit': False, - 'capability': {'net_raw'}, - 'all_caps': False, - 'comment': "", + 'allow_keyword': False, + 'deny': False, + 'audit': False, + 'capability': {'net_raw'}, + 'all_caps': False, + 'comment': "", }) self.assertEqual(obj.get_raw(1), ' capability net_raw,') -# def test_cap_from_invalid_log(self): -# parser = ReadLog('', '', '') -# # invalid log entry, name= should contain the capability name -# event = 'type=AVC msg=audit(1415403814.628:662): apparmor="ALLOWED" operation="capable" profile="/bin/ping" pid=15454 comm="ping" capability=13 capname=""' -# -# parsed_event = parser.parse_event(event) -# -# obj = CapabilityRule() -# -# with self.assertRaises(AppArmorBug): -# obj.set_log(parsed_event) -# -# with self.assertRaises(AppArmorBug): -# obj.get_raw(1) -# -# def test_cap_from_non_cap_log(self): -# parser = ReadLog('', '', '') -# # log entry for different rule type -# event = 'type=AVC msg=audit(1415403814.973:667): apparmor="ALLOWED" operation="setsockopt" profile="/home/sys-tmp/ping" pid=15454 comm="ping" lport=1 family="inet" sock_type="raw" protocol=1' -# -# parsed_event = parser.parse_event(event) -# -# obj = CapabilityRule() -# -# with self.assertRaises(AppArmorBug): -# obj.set_log(parsed_event) -# -# with self.assertRaises(AppArmorBug): -# obj.get_raw(1) + # def test_cap_from_invalid_log(self): + # parser = ReadLog('', '', '') + # # invalid log entry, name= should contain the capability name + # event = 'type=AVC msg=audit(1415403814.628:662): apparmor="ALLOWED" operation="capable" profile="/bin/ping" pid=15454 comm="ping" capability=13 capname=""' + # + # parsed_event = parser.parse_event(event) + # + # obj = CapabilityRule() + # + # with self.assertRaises(AppArmorBug): + # obj.set_log(parsed_event) + # + # with self.assertRaises(AppArmorBug): + # obj.get_raw(1) + # + # def test_cap_from_non_cap_log(self): + # parser = ReadLog('', '', '') + # # log entry for different rule type + # event = 'type=AVC msg=audit(1415403814.973:667): apparmor="ALLOWED" operation="setsockopt" profile="/home/sys-tmp/ping" pid=15454 comm="ping" lport=1 family="inet" sock_type="raw" protocol=1' + # + # parsed_event = parser.parse_event(event) + # + # obj = CapabilityRule() + # + # with self.assertRaises(AppArmorBug): + # obj.set_log(parsed_event) + # + # with self.assertRaises(AppArmorBug): + # obj.get_raw(1) def test_cap_from_init_01(self): obj = CapabilityRule('chown') self._compare_obj(obj, { - 'allow_keyword': False, - 'deny': False, - 'audit': False, - 'capability': {'chown'}, - 'all_caps': False, - 'comment': "", + 'allow_keyword': False, + 'deny': False, + 'audit': False, + 'capability': {'chown'}, + 'all_caps': False, + 'comment': "", }) def test_cap_from_init_02(self): obj = CapabilityRule(['chown']) self._compare_obj(obj, { - 'allow_keyword': False, - 'deny': False, - 'audit': False, - 'capability': {'chown'}, - 'all_caps': False, - 'comment': "", + 'allow_keyword': False, + 'deny': False, + 'audit': False, + 'capability': {'chown'}, + 'all_caps': False, + 'comment': "", }) def test_cap_from_init_03(self): obj = CapabilityRule('chown', audit=True, deny=True) self._compare_obj(obj, { - 'allow_keyword': False, - 'deny': True, - 'audit': True, - 'capability': {'chown'}, - 'all_caps': False, - 'comment': "", + 'allow_keyword': False, + 'deny': True, + 'audit': True, + 'capability': {'chown'}, + 'all_caps': False, + 'comment': "", }) def test_cap_from_init_04(self): obj = CapabilityRule(['chown', 'fsetid'], deny=True) self._compare_obj(obj, { - 'allow_keyword': False, - 'deny': True, - 'audit': False, - 'capability': {'chown', 'fsetid'}, - 'all_caps': False, - 'comment': "", + 'allow_keyword': False, + 'deny': True, + 'audit': False, + 'capability': {'chown', 'fsetid'}, + 'all_caps': False, + 'comment': "", }) @@ -290,6 +293,7 @@ class WriteCapabilityTest(AATest): self.assertEqual(expected, obj.get_clean(2), 'unexpected clean rule') self.assertEqual(expected, obj.get_raw(2), 'unexpected raw rule') + class CapabilityCoveredTest(AATest): def _is_covered(self, obj, rule_to_test): self.assertTrue(CapabilityRule.match(rule_to_test)) @@ -425,35 +429,39 @@ class CapabilityCoveredTest(AATest): self.assertFalse(self._is_covered(obj2, 'capability sys_admin,')) self.assertTrue(self._is_covered(obj2, 'capability ptrace,')) + class CapabiliySeverityTest(AATest): - tests = [ + tests = ( ('fsetid', 9), ('dac_read_search', 7), (['fsetid', 'dac_read_search'], 9), (CapabilityRule.ALL, 10), ('foo', 'unknown'), - ] + ) + def _run_test(self, params, expected): - sev_db = severity.Severity('severity.db', 'unknown') + sev_db = severity.Severity('../severity.db', 'unknown') obj = CapabilityRule(params) rank = obj.severity(sev_db) self.assertEqual(rank, expected) + class CapabilityLogprofHeaderTest(AATest): - tests = [ - ('capability,', [ _('Capability'), _('ALL'), ]), - ('capability chown,', [ _('Capability'), 'chown', ]), - ('capability chown fsetid,', [ _('Capability'), 'chown fsetid', ]), - ('audit capability,', [_('Qualifier'), 'audit', _('Capability'), _('ALL'), ]), - ('deny capability chown,', [_('Qualifier'), 'deny', _('Capability'), 'chown', ]), - ('allow capability chown fsetid,', [_('Qualifier'), 'allow', _('Capability'), 'chown fsetid', ]), - ('audit deny capability,', [_('Qualifier'), 'audit deny', _('Capability'), _('ALL'), ]), - ] + tests = ( + ('capability,', [ _('Capability'), _('ALL')]), + ('capability chown,', [ _('Capability'), 'chown']), + ('capability chown fsetid,', [ _('Capability'), 'chown fsetid']), + ('audit capability,', [_('Qualifier'), 'audit', _('Capability'), _('ALL')]), + ('deny capability chown,', [_('Qualifier'), 'deny', _('Capability'), 'chown']), + ('allow capability chown fsetid,', [_('Qualifier'), 'allow', _('Capability'), 'chown fsetid']), + ('audit deny capability,', [_('Qualifier'), 'audit deny', _('Capability'), _('ALL')]), + ) def _run_test(self, params, expected): - obj = CapabilityRule._parse(params) + obj = CapabilityRule.parse(params) self.assertEqual(obj.logprof_header(), expected) + # --- tests for CapabilityRuleset --- # class CapabilityRulesTest(AATest): @@ -630,6 +638,7 @@ class CapabilityRulesCoveredTest(AATest): # parser = ReadLog('', '', '') # self.assertEqual(True, self.ruleset.is_log_covered(parser.parse_event(event_base%'chgrp'), False)) # ignores allow/deny + class CapabilityGlobTest(AATest): def AASetup(self): self.ruleset = CapabilityRuleset() @@ -641,6 +650,7 @@ class CapabilityGlobTest(AATest): with self.assertRaises(NotImplementedError): self.ruleset.get_glob_ext('capability net_raw,') + class CapabilityDeleteTest(AATest): def AASetup(self): self.ruleset = CapabilityRuleset() @@ -812,9 +822,7 @@ class CapabilityDeleteTest(AATest): def test_delete_duplicates_4(self): inc = CapabilityRuleset() - rules = [ - 'capability,', - ] + rules = ['capability,'] for rule in rules: inc.add(CapabilityRule.parse(rule)) @@ -873,7 +881,6 @@ class CapabilityDeleteTest(AATest): self.assertEqual(expected_raw, self.ruleset.get_raw(1)) self.assertEqual(expected_clean, self.ruleset.get_clean(1)) - def _check_test_delete_duplicates_in_profile(self, rules, expected_raw, expected_clean, expected_deleted): obj = CapabilityRuleset() @@ -886,7 +893,6 @@ class CapabilityDeleteTest(AATest): self.assertEqual(expected_clean, obj.get_clean(1)) self.assertEqual(deleted, expected_deleted) - def test_delete_duplicates_in_profile_01(self): rules = [ 'audit capability chown,', diff --git a/utils/test/test-change_profile.py b/utils/test/test-change_profile.py index 3fe3c4891..426884868 100644 --- a/utils/test/test-change_profile.py +++ b/utils/test/test-change_profile.py @@ -15,20 +15,23 @@ import unittest from collections import namedtuple -from common_test import AATest, setup_all_loops -from apparmor.rule.change_profile import ChangeProfileRule, ChangeProfileRuleset -from apparmor.rule import BaseRule -from apparmor.common import AppArmorException, AppArmorBug +from apparmor.common import AppArmorBug, AppArmorException from apparmor.logparser import ReadLog +from apparmor.rule import BaseRule +from apparmor.rule.change_profile import ChangeProfileRule, ChangeProfileRuleset from apparmor.translations import init_translation +from common_test import AATest, setup_all_loops + _ = init_translation() -exp = namedtuple('exp', ['audit', 'allow_keyword', 'deny', 'comment', - 'execmode', 'execcond', 'all_execconds', 'targetprofile', 'all_targetprofiles']) +exp = namedtuple( + 'exp', ('audit', 'allow_keyword', 'deny', 'comment', + 'execmode', 'execcond', 'all_execconds', 'targetprofile', 'all_targetprofiles')) # --- tests for single ChangeProfileRule --- # + class ChangeProfileTest(AATest): def _compare_obj(self, obj, expected): self.assertEqual(expected.allow_keyword, obj.allow_keyword) @@ -41,36 +44,37 @@ class ChangeProfileTest(AATest): self.assertEqual(expected.deny, obj.deny) self.assertEqual(expected.comment, obj.comment) + class ChangeProfileTestParse(ChangeProfileTest): - tests = [ - # rawrule audit allow deny comment execmode execcond all? targetprof all? - ('change_profile,' , exp(False, False, False, '' , None , None , True , None , True )), - ('change_profile /foo,' , exp(False, False, False, '' , None , '/foo', False, None , True )), - ('change_profile safe /foo,' , exp(False, False, False, '' , 'safe' , '/foo', False, None , True )), - ('change_profile unsafe /foo,' , exp(False, False, False, '' , 'unsafe' , '/foo', False, None , True )), - ('change_profile /foo -> /bar,' , exp(False, False, False, '' , None , '/foo', False, '/bar' , False)), - ('change_profile safe /foo -> /bar,' , exp(False, False, False, '' , 'safe' , '/foo', False, '/bar' , False)), - ('change_profile unsafe /foo -> /bar,' , exp(False, False, False, '' , 'unsafe' , '/foo', False, '/bar' , False)), - ('deny change_profile /foo -> /bar, # comment' , exp(False, False, True , ' # comment' , None , '/foo', False, '/bar' , False)), - ('audit allow change_profile safe /foo,' , exp(True , True , False, '' , 'safe' , '/foo', False, None , True )), - ('change_profile -> /bar,' , exp(False, False, False, '' , None , None , True , '/bar' , False)), - ('audit allow change_profile -> /bar,' , exp(True , True , False, '' , None , None , True , '/bar' , False)), + tests = ( + # rawrule audit allow deny comment execmode execcond all? targetprof all? + ('change_profile,', exp(False, False, False, '', None, None, True, None, True)), + ('change_profile /foo,', exp(False, False, False, '', None, '/foo', False, None, True)), + ('change_profile safe /foo,', exp(False, False, False, '', 'safe', '/foo', False, None, True)), + ('change_profile unsafe /foo,', exp(False, False, False, '', 'unsafe', '/foo', False, None, True)), + ('change_profile /foo -> /bar,', exp(False, False, False, '', None, '/foo', False, '/bar', False)), + ('change_profile safe /foo -> /bar,', exp(False, False, False, '', 'safe', '/foo', False, '/bar', False)), + ('change_profile unsafe /foo -> /bar,', exp(False, False, False, '', 'unsafe', '/foo', False, '/bar', False)), + ('deny change_profile /foo -> /bar, # comment', exp(False, False, True, ' # comment', None, '/foo', False, '/bar', False)), + ('audit allow change_profile safe /foo,', exp(True, True, False, '', 'safe', '/foo', False, None, True)), + ('change_profile -> /bar,', exp(False, False, False, '', None, None, True, '/bar', False)), + ('audit allow change_profile -> /bar,', exp(True, True, False, '', None, None, True, '/bar', False)), # quoted versions - ('change_profile "/foo",' , exp(False, False, False, '' , None , '/foo', False, None , True )), - ('change_profile "/foo" -> "/bar",' , exp(False, False, False, '' , None , '/foo', False, '/bar' , False)), - ('deny change_profile "/foo" -> "/bar", # cmt' , exp(False, False, True, ' # cmt' , None , '/foo', False, '/bar' , False)), - ('audit allow change_profile "/foo",' , exp(True , True , False, '' , None , '/foo', False, None , True )), - ('change_profile -> "/bar",' , exp(False, False, False, '' , None , None , True , '/bar' , False)), - ('audit allow change_profile -> "/bar",' , exp(True , True , False, '' , None , None , True , '/bar' , False)), + ('change_profile "/foo",', exp(False, False, False, '', None, '/foo', False, None, True)), + ('change_profile "/foo" -> "/bar",', exp(False, False, False, '', None, '/foo', False, '/bar', False)), + ('deny change_profile "/foo" -> "/bar", # cmt', exp(False, False, True, ' # cmt', None, '/foo', False, '/bar', False)), + ('audit allow change_profile "/foo",', exp(True, True, False, '', None, '/foo', False, None, True)), + ('change_profile -> "/bar",', exp(False, False, False, '', None, None, True, '/bar', False)), + ('audit allow change_profile -> "/bar",', exp(True, True, False, '', None, None, True, '/bar', False)), # with globbing and/or named profiles - ('change_profile,' , exp(False, False, False, '' , None , None , True , None , True )), - ('change_profile /*,' , exp(False, False, False, '' , None , '/*' , False, None , True )), - ('change_profile /* -> bar,' , exp(False, False, False, '' , None , '/*' , False, 'bar' , False)), - ('deny change_profile /** -> bar, # comment' , exp(False, False, True , ' # comment' , None , '/**' , False, 'bar' , False)), - ('audit allow change_profile /**,' , exp(True , True , False, '' , None , '/**' , False, None , True )), - ('change_profile -> "ba r",' , exp(False, False, False, '' , None , None , True , 'ba r' , False)), - ('audit allow change_profile -> "ba r",' , exp(True , True , False, '' , None , None , True , 'ba r' , False)), - ] + ('change_profile,', exp(False, False, False, '', None, None, True, None, True)), + ('change_profile /*,', exp(False, False, False, '', None, '/*', False, None, True)), + ('change_profile /* -> bar,', exp(False, False, False, '', None, '/*', False, 'bar', False)), + ('deny change_profile /** -> bar, # comment', exp(False, False, True, ' # comment', None, '/**', False, 'bar', False)), + ('audit allow change_profile /**,', exp(True, True, False, '', None, '/**', False, None, True)), + ('change_profile -> "ba r",', exp(False, False, False, '', None, None, True, 'ba r', False)), + ('audit allow change_profile -> "ba r",', exp(True, True, False, '', None, None, True, 'ba r', False)), + ) def _run_test(self, rawrule, expected): self.assertTrue(ChangeProfileRule.match(rawrule)) @@ -78,19 +82,21 @@ class ChangeProfileTestParse(ChangeProfileTest): self.assertEqual(rawrule.strip(), obj.raw_rule) self._compare_obj(obj, expected) + class ChangeProfileTestParseInvalid(ChangeProfileTest): - tests = [ - ('change_profile -> ,' , AppArmorException), - ('change_profile foo -> ,' , AppArmorException), - ('change_profile notsafe,' , AppArmorException), - ('change_profile safety -> /bar,' , AppArmorException), - ] + tests = ( + ('change_profile -> ,', AppArmorException), + ('change_profile foo -> ,', AppArmorException), + ('change_profile notsafe,', AppArmorException), + ('change_profile safety -> /bar,', AppArmorException), + ) def _run_test(self, rawrule, expected): self.assertFalse(ChangeProfileRule.match(rawrule)) with self.assertRaises(expected): ChangeProfileRule.parse(rawrule) + class ChangeProfileTestParseFromLog(ChangeProfileTest): def test_change_profile_from_log(self): parser = ReadLog('', '', '') @@ -118,17 +124,18 @@ class ChangeProfileTestParseFromLog(ChangeProfileTest): 'pid': 3459, 'task': 0, 'attr': None, - 'name2': '/foo/rename', # target + 'name2': '/foo/rename', # target 'name': None, 'family': None, 'protocol': None, 'sock_type': None, + 'class': None, }) obj = ChangeProfileRule(None, ChangeProfileRule.ALL, parsed_event['name2'], log_event=parsed_event) - # audit allow deny comment execmode execcond all? targetprof all? - expected = exp(False, False, False, '' , None, None, True, '/foo/rename', False) + # audit allow deny comment execmode execcond all? targetprof all? + expected = exp(False, False, False, '', None, None, True, '/foo/rename', False) self._compare_obj(obj, expected) @@ -136,40 +143,39 @@ class ChangeProfileTestParseFromLog(ChangeProfileTest): class ChangeProfileFromInit(ChangeProfileTest): - tests = [ - # ChangeProfileRule object audit allow deny comment execmode execcond all? targetprof all? - (ChangeProfileRule(None , '/foo', '/bar', deny=True) , exp(False, False, True , '' , None , '/foo', False, '/bar' , False)), - (ChangeProfileRule(None , '/foo', '/bar') , exp(False, False, False, '' , None , '/foo', False, '/bar' , False)), - (ChangeProfileRule('safe' , '/foo', '/bar') , exp(False, False, False, '' , 'safe' , '/foo', False, '/bar' , False)), - (ChangeProfileRule('unsafe', '/foo', '/bar') , exp(False, False, False, '' , 'unsafe', '/foo', False, '/bar' , False)), - (ChangeProfileRule(None , '/foo', ChangeProfileRule.ALL) , exp(False, False, False, '' , None , '/foo', False, None , True )), - (ChangeProfileRule(None , ChangeProfileRule.ALL, '/bar') , exp(False, False, False, '' , None , None , True , '/bar' , False)), - (ChangeProfileRule(None , ChangeProfileRule.ALL, - ChangeProfileRule.ALL) , exp(False, False, False, '' , None, None , True , None , True )), - ] + tests = ( + # ChangeProfileRule object audit allow deny comment execmode execcond all? targetprof all? + (ChangeProfileRule(None, '/foo', '/bar', deny=True), exp(False, False, True, '', None, '/foo', False, '/bar', False)), + (ChangeProfileRule(None, '/foo', '/bar'), exp(False, False, False, '', None, '/foo', False, '/bar', False)), + (ChangeProfileRule('safe', '/foo', '/bar'), exp(False, False, False, '', 'safe', '/foo', False, '/bar', False)), + (ChangeProfileRule('unsafe', '/foo', '/bar'), exp(False, False, False, '', 'unsafe', '/foo', False, '/bar', False)), + (ChangeProfileRule(None, '/foo', ChangeProfileRule.ALL), exp(False, False, False, '', None, '/foo', False, None, True)), + (ChangeProfileRule(None, ChangeProfileRule.ALL, '/bar'), exp(False, False, False, '', None, None, True, '/bar', False)), + (ChangeProfileRule(None, ChangeProfileRule.ALL, ChangeProfileRule.ALL), exp(False, False, False, '', None, None, True, None, True)), + ) def _run_test(self, obj, expected): self._compare_obj(obj, expected) class InvalidChangeProfileInit(AATest): - tests = [ - # init params expected exception - ([None , '/foo', '' ] , AppArmorBug), # empty targetprofile - ([None , '' , '/bar' ] , AppArmorBug), # empty execcond - ([None , ' ', '/bar' ] , AppArmorBug), # whitespace execcond - ([None , '/foo', ' ' ] , AppArmorBug), # whitespace targetprofile - ([None , 'xyxy', '/bar' ] , AppArmorException), # invalid execcond - ([None , dict(), '/bar' ] , AppArmorBug), # wrong type for execcond - ([None , None , '/bar' ] , AppArmorBug), # wrong type for execcond - ([None , '/foo', dict() ] , AppArmorBug), # wrong type for targetprofile - ([None , '/foo', None ] , AppArmorBug), # wrong type for targetprofile - (['maybe' , '/foo', '/bar' ] , AppArmorBug), # invalid keyword for execmode - ] + tests = ( + # init params expected exception + ((None, '/foo', ''), AppArmorBug), # empty targetprofile + ((None, '', '/bar'), AppArmorBug), # empty execcond + ((None, ' ', '/bar'), AppArmorBug), # whitespace execcond + ((None, '/foo', ' '), AppArmorBug), # whitespace targetprofile + ((None, 'xyxy', '/bar'), AppArmorException), # invalid execcond + ((None, dict(), '/bar'), AppArmorBug), # wrong type for execcond + ((None, None, '/bar'), AppArmorBug), # wrong type for execcond + ((None, '/foo', dict()), AppArmorBug), # wrong type for targetprofile + ((None, '/foo', None), AppArmorBug), # wrong type for targetprofile + (('maybe', '/foo', '/bar'), AppArmorBug), # invalid keyword for execmode + ) def _run_test(self, params, expected): with self.assertRaises(expected): - ChangeProfileRule(params[0], params[1], params[2]) + ChangeProfileRule(*params) def test_missing_params_1(self): with self.assertRaises(TypeError): @@ -211,16 +217,16 @@ class InvalidChangeProfileTest(AATest): class WriteChangeProfileTestAATest(AATest): - tests = [ - # raw rule clean rule - (' change_profile , # foo ' , 'change_profile, # foo'), - (' audit change_profile /foo,' , 'audit change_profile /foo,'), - (' deny change_profile /foo -> bar,# foo bar' , 'deny change_profile /foo -> bar, # foo bar'), - (' deny change_profile /foo ,# foo bar' , 'deny change_profile /foo, # foo bar'), - (' allow change_profile -> /bar ,# foo bar' , 'allow change_profile -> /bar, # foo bar'), - (' allow change_profile unsafe /** -> /bar ,# foo bar' , 'allow change_profile unsafe /** -> /bar, # foo bar'), - (' allow change_profile "/fo o" -> "/b ar",' , 'allow change_profile "/fo o" -> "/b ar",'), - ] + tests = ( + # raw rule clean rule + (' change_profile , # foo ', 'change_profile, # foo'), + (' audit change_profile /foo,', 'audit change_profile /foo,'), + (' deny change_profile /foo -> bar,# foo bar', 'deny change_profile /foo -> bar, # foo bar'), + (' deny change_profile /foo ,# foo bar', 'deny change_profile /foo, # foo bar'), + (' allow change_profile -> /bar ,# foo bar', 'allow change_profile -> /bar, # foo bar'), + (' allow change_profile unsafe /** -> /bar ,# foo bar', 'allow change_profile unsafe /** -> /bar, # foo bar'), + (' allow change_profile "/fo o" -> "/b ar",', 'allow change_profile "/fo o" -> "/b ar",'), + ) def _run_test(self, rawrule, expected): self.assertTrue(ChangeProfileRule.match(rawrule)) @@ -253,98 +259,104 @@ class ChangeProfileCoveredTest(AATest): self.assertEqual(obj.is_covered(check_obj), expected[2], 'Mismatch in is_covered, expected %s' % expected[2]) self.assertEqual(obj.is_covered(check_obj, True, True), expected[3], 'Mismatch in is_covered/exact, expected %s' % expected[3]) + class ChangeProfileCoveredTest_01(ChangeProfileCoveredTest): rule = 'change_profile /foo,' - tests = [ - # rule equal strict equal covered covered exact - (' change_profile,' , [ False , False , False , False ]), - (' change_profile /foo,' , [ True , True , True , True ]), - (' change_profile safe /foo,' , [ True , False , True , True ]), - (' change_profile unsafe /foo,' , [ False , False , False , False ]), - (' change_profile /foo, # comment', [ True , False , True , True ]), - (' allow change_profile /foo,' , [ True , False , True , True ]), - (' change_profile /foo,' , [ True , False , True , True ]), - (' change_profile /foo -> /bar,' , [ False , False , True , True ]), - (' change_profile /foo -> bar,' , [ False , False , True , True ]), - ('audit change_profile /foo,' , [ False , False , False , False ]), - ('audit change_profile,' , [ False , False , False , False ]), - (' change_profile /asdf,' , [ False , False , False , False ]), - (' change_profile -> /bar,' , [ False , False , False , False ]), - ('audit deny change_profile /foo,' , [ False , False , False , False ]), - (' deny change_profile /foo,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + (' change_profile,', (False, False, False, False)), + (' change_profile /foo,', (True, True, True, True)), + (' change_profile safe /foo,', (True, False, True, True)), + (' change_profile unsafe /foo,', (False, False, False, False)), + (' change_profile /foo, # comment', (True, False, True, True)), + (' allow change_profile /foo,', (True, False, True, True)), + (' change_profile /foo,', (True, False, True, True)), + (' change_profile /foo -> /bar,', (False, False, True, True)), + (' change_profile /foo -> bar,', (False, False, True, True)), + ('audit change_profile /foo,', (False, False, False, False)), + ('audit change_profile,', (False, False, False, False)), + (' change_profile /asdf,', (False, False, False, False)), + (' change_profile -> /bar,', (False, False, False, False)), + ('audit deny change_profile /foo,', (False, False, False, False)), + (' deny change_profile /foo,', (False, False, False, False)), + ) + class ChangeProfileCoveredTest_02(ChangeProfileCoveredTest): rule = 'audit change_profile /foo,' - tests = [ - # rule equal strict equal covered covered exact - ( 'change_profile /foo,' , [ False , False , True , False ]), - ('audit change_profile /foo,' , [ True , True , True , True ]), - ( 'change_profile /foo -> /bar,' , [ False , False , True , False ]), - ( 'change_profile safe /foo -> /bar,' , [ False , False , True , False ]), - ('audit change_profile /foo -> /bar,' , [ False , False , True , True ]), # XXX is "covered exact" correct here? - ( 'change_profile,' , [ False , False , False , False ]), - ('audit change_profile,' , [ False , False , False , False ]), - (' change_profile -> /bar,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ( 'change_profile /foo,', (False, False, True, False)), + ('audit change_profile /foo,', (True, True, True, True)), + ( 'change_profile /foo -> /bar,', (False, False, True, False)), + ( 'change_profile safe /foo -> /bar,', (False, False, True, False)), + ('audit change_profile /foo -> /bar,', (False, False, True, True)), # XXX is "covered exact" correct here? + ( 'change_profile,', (False, False, False, False)), + ('audit change_profile,', (False, False, False, False)), + (' change_profile -> /bar,', (False, False, False, False)), + ) class ChangeProfileCoveredTest_03(ChangeProfileCoveredTest): rule = 'change_profile /foo -> /bar,' - tests = [ - # rule equal strict equal covered covered exact - ( 'change_profile /foo -> /bar,' , [ True , True , True , True ]), - ('allow change_profile /foo -> /bar,' , [ True , False , True , True ]), - ( 'change_profile /foo,' , [ False , False , False , False ]), - ( 'change_profile,' , [ False , False , False , False ]), - ( 'change_profile /foo -> /xyz,' , [ False , False , False , False ]), - ('audit change_profile,' , [ False , False , False , False ]), - ('audit change_profile /foo -> /bar,' , [ False , False , False , False ]), - ( 'change_profile -> /bar,' , [ False , False , False , False ]), - ( 'change_profile,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ( 'change_profile /foo -> /bar,', (True, True, True, True)), + ('allow change_profile /foo -> /bar,', (True, False, True, True)), + ( 'change_profile /foo,', (False, False, False, False)), + ( 'change_profile,', (False, False, False, False)), + ( 'change_profile /foo -> /xyz,', (False, False, False, False)), + ('audit change_profile,', (False, False, False, False)), + ('audit change_profile /foo -> /bar,', (False, False, False, False)), + ( 'change_profile -> /bar,', (False, False, False, False)), + ( 'change_profile,', (False, False, False, False)), + ) + class ChangeProfileCoveredTest_04(ChangeProfileCoveredTest): rule = 'change_profile,' - tests = [ - # rule equal strict equal covered covered exact - ( 'change_profile,' , [ True , True , True , True ]), - ('allow change_profile,' , [ True , False , True , True ]), - ( 'change_profile /foo,' , [ False , False , True , True ]), - ( 'change_profile /xyz -> bar,' , [ False , False , True , True ]), - ( 'change_profile -> /bar,' , [ False , False , True , True ]), - ( 'change_profile /foo -> /bar,' , [ False , False , True , True ]), - ('audit change_profile,' , [ False , False , False , False ]), - ('deny change_profile,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ( 'change_profile,', (True, True, True, True)), + ('allow change_profile,', (True, False, True, True)), + ( 'change_profile /foo,', (False, False, True, True)), + ( 'change_profile /xyz -> bar,', (False, False, True, True)), + ( 'change_profile -> /bar,', (False, False, True, True)), + ( 'change_profile /foo -> /bar,', (False, False, True, True)), + ('audit change_profile,', (False, False, False, False)), + ('deny change_profile,', (False, False, False, False)), + ) + class ChangeProfileCoveredTest_05(ChangeProfileCoveredTest): rule = 'deny change_profile /foo,' - tests = [ - # rule equal strict equal covered covered exact - ( 'deny change_profile /foo,' , [ True , True , True , True ]), - ('audit deny change_profile /foo,' , [ False , False , False , False ]), - ( 'change_profile /foo,' , [ False , False , False , False ]), # XXX should covered be true here? - ( 'deny change_profile /bar,' , [ False , False , False , False ]), - ( 'deny change_profile,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ( 'deny change_profile /foo,', (True, True, True, True)), + ('audit deny change_profile /foo,', (False, False, False, False)), + ( 'change_profile /foo,', (False, False, False, False)), # XXX should covered be true here? + ( 'deny change_profile /bar,', (False, False, False, False)), + ( 'deny change_profile,', (False, False, False, False)), + ) + class ChangeProfileCoveredTest_06(ChangeProfileCoveredTest): rule = 'change_profile safe /foo,' - tests = [ - # rule equal strict equal covered covered exact - ( 'deny change_profile /foo,' , [ False , False , False , False ]), - ('audit deny change_profile /foo,' , [ False , False , False , False ]), - ( 'change_profile /foo,' , [ True , False , True , True ]), - ( 'deny change_profile /bar,' , [ False , False , False , False ]), - ( 'deny change_profile,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ( 'deny change_profile /foo,', (False, False, False, False)), + ('audit deny change_profile /foo,', (False, False, False, False)), + ( 'change_profile /foo,', (True, False, True, True)), + ( 'deny change_profile /bar,', (False, False, False, False)), + ( 'deny change_profile,', (False, False, False, False)), + ) + class ChangeProfileCoveredTest_Invalid(AATest): def test_borked_obj_is_covered_1(self): @@ -381,22 +393,24 @@ class ChangeProfileCoveredTest_Invalid(AATest): with self.assertRaises(AppArmorBug): obj.is_equal(testobj) + class ChangeProfileLogprofHeaderTest(AATest): - tests = [ - ('change_profile,', [ _('Exec Condition'), _('ALL'), _('Target Profile'), _('ALL'), ]), - ('change_profile -> /bin/ping,', [ _('Exec Condition'), _('ALL'), _('Target Profile'), '/bin/ping',]), - ('change_profile /bar -> /bin/bar,', [ _('Exec Condition'), '/bar', _('Target Profile'), '/bin/bar', ]), - ('change_profile safe /foo,', [ _('Exec Mode'), 'safe', _('Exec Condition'), '/foo', _('Target Profile'), _('ALL'), ]), - ('audit change_profile -> /bin/ping,', [_('Qualifier'), 'audit', _('Exec Condition'), _('ALL'), _('Target Profile'), '/bin/ping',]), - ('deny change_profile /bar -> /bin/bar,', [_('Qualifier'), 'deny', _('Exec Condition'), '/bar', _('Target Profile'), '/bin/bar', ]), - ('allow change_profile unsafe /foo,', [_('Qualifier'), 'allow', _('Exec Mode'), 'unsafe', _('Exec Condition'), '/foo', _('Target Profile'), _('ALL'), ]), - ('audit deny change_profile,', [_('Qualifier'), 'audit deny', _('Exec Condition'), _('ALL'), _('Target Profile'), _('ALL'), ]), - ] + tests = ( + ('change_profile,', [ _('Exec Condition'), _('ALL'), _('Target Profile'), _('ALL')]), + ('change_profile -> /bin/ping,', [ _('Exec Condition'), _('ALL'), _('Target Profile'), '/bin/ping']), + ('change_profile /bar -> /bin/bar,', [ _('Exec Condition'), '/bar', _('Target Profile'), '/bin/bar']), + ('change_profile safe /foo,', [ _('Exec Mode'), 'safe', _('Exec Condition'), '/foo', _('Target Profile'), _('ALL')]), + ('audit change_profile -> /bin/ping,', [_('Qualifier'), 'audit', _('Exec Condition'), _('ALL'), _('Target Profile'), '/bin/ping']), + ('deny change_profile /bar -> /bin/bar,', [_('Qualifier'), 'deny', _('Exec Condition'), '/bar', _('Target Profile'), '/bin/bar']), + ('allow change_profile unsafe /foo,', [_('Qualifier'), 'allow', _('Exec Mode'), 'unsafe', _('Exec Condition'), '/foo', _('Target Profile'), _('ALL')]), + ('audit deny change_profile,', [_('Qualifier'), 'audit deny', _('Exec Condition'), _('ALL'), _('Target Profile'), _('ALL')]), + ) def _run_test(self, params, expected): - obj = ChangeProfileRule._parse(params) + obj = ChangeProfileRule.parse(params) self.assertEqual(obj.logprof_header(), expected) + # --- tests for ChangeProfileRuleset --- # class ChangeProfileRulesTest(AATest): @@ -410,10 +424,10 @@ class ChangeProfileRulesTest(AATest): def test_ruleset_1(self): ruleset = ChangeProfileRuleset() - rules = [ + rules = ( 'change_profile -> /bar,', 'change_profile /foo,', - ] + ) expected_raw = [ 'change_profile -> /bar,', @@ -435,11 +449,11 @@ class ChangeProfileRulesTest(AATest): def test_ruleset_2(self): ruleset = ChangeProfileRuleset() - rules = [ + rules = ( 'change_profile /foo -> /bar,', 'allow change_profile /asdf,', 'deny change_profile -> xy, # example comment', - ] + ) expected_raw = [ ' change_profile /foo -> /bar,', @@ -479,9 +493,11 @@ class ChangeProfileGlobTestAATest(AATest): # get_glob_ext is not available for change_profile rules self.ruleset.get_glob_ext('change_profile /foo -> /bar,') + class ChangeProfileDeleteTestAATest(AATest): pass + setup_all_loops(__name__) if __name__ == '__main__': unittest.main(verbosity=1) diff --git a/utils/test/test-common.py b/utils/test/test-common.py index cf46530d0..7f885da18 100644 --- a/utils/test/test-common.py +++ b/utils/test/test-common.py @@ -10,32 +10,39 @@ # ------------------------------------------------------------------ import unittest + +from apparmor.common import AppArmorBug, combine_profname, split_name from common_test import AATest, setup_all_loops -from apparmor.common import type_is_str, split_name -class TestIs_str_type(AATest): - tests = [ - ('foo', True), - (u'foo', True), - (42, False), - (True, False), - ([], False), - ] +class AaTest_split_name(AATest): + tests = ( + # full profile name expected parts + ('foo', ('foo', 'foo')), + ('foo//bar', ('foo', 'bar')), + ('foo//bar//baz', ('foo', 'bar')), # XXX nested child profiles get cut off + ) def _run_test(self, params, expected): - self.assertEqual(type_is_str(params), expected) + self.assertEqual(split_name(params), expected) -class AaTest_split_name(AATest): - tests = [ - # full profile name expected parts - ('foo', ('foo', 'foo')), - ('foo//bar', ('foo', 'bar')), - ('foo//bar//baz', ('foo', 'bar')), # XXX nested child profiles get cut off - ] + +class AaTest_combine_profname(AATest): + tests = ( + # name parts expected full profile name + (['foo'], 'foo'), + (['foo', 'bar'], 'foo//bar'), + (['foo', 'bar', 'baz'], 'foo//bar//baz'), + (['foo', 'bar', None], 'foo//bar'), + (['foo', 'bar', 'baz', None], 'foo//bar//baz'), + ) def _run_test(self, params, expected): - self.assertEqual(split_name(params), expected) + self.assertEqual(combine_profname(params), expected) + + def test_wrong_type(self): + with self.assertRaises(AppArmorBug): + combine_profname('foo') setup_all_loops(__name__) diff --git a/utils/test/test-config.py b/utils/test/test-config.py index 1e0e63084..66e574978 100755 --- a/utils/test/test-config.py +++ b/utils/test/test-config.py @@ -15,16 +15,18 @@ import unittest import apparmor.config as config -class Test(unittest.TestCase): +class Test(unittest.TestCase): def test_IniConfig(self): ini_config = config.Config('ini') ini_config.CONF_DIR = '.' conf = ini_config.read_config('logprof.conf') logprof_sections = ['settings', 'qualifiers', 'required_hats', 'defaulthat', 'globs'] - logprof_sections_options = ['profiledir', 'inactive_profiledir', 'logfiles', 'parser', 'ldd', 'logger', 'default_owner_prompt', 'custom_includes'] - logprof_settings_parser = '../../parser/apparmor_parser' + logprof_sections_options = [ + 'profiledir', 'inactive_profiledir', 'logfiles', 'parser', 'ldd', + 'logger', 'default_owner_prompt', 'custom_includes'] + logprof_settings_parser = '../../parser/apparmor_parser ../parser/apparmor_parser' self.assertEqual(conf.sections(), logprof_sections) self.assertEqual(conf.options('settings'), logprof_sections_options) @@ -35,16 +37,14 @@ class Test(unittest.TestCase): shell_config.CONF_DIR = '.' conf = shell_config.read_config('easyprof.conf') easyprof_sections = ['POLICYGROUPS_DIR', 'TEMPLATES_DIR'] - easyprof_Policygroup = './policygroups' - easyprof_Templates = './templates' - - self.assertEqual(sorted(list(conf[''].keys())), sorted(easyprof_sections)) - self.assertEqual(conf['']['POLICYGROUPS_DIR'], easyprof_Policygroup) - self.assertEqual(conf['']['TEMPLATES_DIR'], easyprof_Templates) - + easyprof_policygroup = './policygroups' + easyprof_templates = './templates' + self.assertEqual(sorted(conf[''].keys()), easyprof_sections) + self.assertEqual(conf['']['POLICYGROUPS_DIR'], easyprof_policygroup) + self.assertEqual(conf['']['TEMPLATES_DIR'], easyprof_templates) if __name__ == "__main__": - #import sys;sys.argv = ['', 'Test.testConfig'] + # import sys;sys.argv = ['', 'Test.testConfig'] unittest.main() diff --git a/utils/test/test-dbus.py b/utils/test/test-dbus.py index 069445089..5ff8f90cc 100644 --- a/utils/test/test-dbus.py +++ b/utils/test/test-dbus.py @@ -15,17 +15,22 @@ import unittest from collections import namedtuple -from common_test import AATest, setup_all_loops -from apparmor.rule.dbus import DbusRule, DbusRuleset -from apparmor.rule import BaseRule -from apparmor.common import AppArmorException, AppArmorBug +from apparmor.common import AppArmorBug, AppArmorException from apparmor.logparser import ReadLog +from apparmor.rule import BaseRule +from apparmor.rule.dbus import DbusRule, DbusRuleset from apparmor.translations import init_translation +from common_test import AATest, setup_all_loops + _ = init_translation() -exp = namedtuple('exp', ['audit', 'allow_keyword', 'deny', 'comment', - 'access', 'all_access', 'bus', 'all_buses', 'path', 'all_paths', 'name', 'all_names', 'interface', 'all_interfaces', 'member', 'all_members', 'peername', 'all_peernames', 'peerlabel', 'all_peerlabels']) +exp = namedtuple( + 'exp', ('audit', 'allow_keyword', 'deny', 'comment', 'access', 'all_access', + 'bus', 'all_buses', 'path', 'all_paths', 'name', 'all_names', + 'interface', 'all_interfaces', 'member', 'all_members', + 'peername', 'all_peernames', 'peerlabel', 'all_peerlabels')) + # --- tests for single DbusRule --- # @@ -60,40 +65,41 @@ class DbusTest(AATest): else: self.assertEqual(obj, expected) + class DbusTestParse(DbusTest): - tests = [ - # DbusRule object audit allow deny comment access all? bus all? path all? name all? interface all? member all? peername all? peerlabel all? - ('dbus,' , exp(False, False, False, '', None , True , None, True, None, True, None, True, None, True, None, True, None, True, None, True)), - ('dbus ( ),' , exp(False, False, False, '', None , True , None, True, None, True, None, True, None, True, None, True, None, True, None, True)), - ('dbus ( , ),' , exp(False, False, False, '', None , True , None, True, None, True, None, True, None, True, None, True, None, True, None, True)), - ('dbus send,' , exp(False, False, False, '', {'send'}, False, None, True, None, True, None, True, None, True, None, True, None, True, None, True)), - ('dbus (send, receive),' , exp(False, False, False, '', {'send', 'receive'}, False, None, True, None, True, None, True, None, True, None, True, None, True, None, True)), - ('dbus send bus=session,' , exp(False, False, False, '', {'send'}, False, 'session', False, None, True, None, True, None, True, None, True, None, True, None, True)), - ('deny dbus send bus="session", # cmt' , exp(False, False, True , ' # cmt', {'send'}, False, 'session', False, None, True, None, True, None, True, None, True, None, True, None, True)), - ('audit allow dbus peer=(label=foo),' , exp(True , True , False, '', None , True , None, True, None, True, None, True, None, True, None, True, None, True, 'foo', False)), - ('dbus bus=session path=/foo/bar,' , exp(False, False, False, '', None , True , 'session', False, '/foo/bar', False, None, True, None, True, None, True, None, True, None, True)), - ('dbus send bus=(session),' , exp(False, False, False, '', {'send'}, False, 'session', False, None, True, None, True, None, True, None, True, None, True, None, True)), - ('dbus name=(SomeService),' , exp(False, False, False, '', None, True, None, True, None, True, 'SomeService',False, None, True, None, True, None, True, None, True)), - ('dbus send bus=session peer=(label="foo"),' , exp(False, False, False, '', {'send'}, False, 'session', False, None, True, None, True, None, True, None, True, None, True, 'foo', False)), - ('dbus send bus = ( session ) , ' , exp(False, False, False, '', {'send'}, False, 'session', False, None, True, None, True, None, True, None, True, None, True, None, True)), - ('dbus path=/foo,' , exp(False, False, False, '', None , True , None, True, '/foo', False, None, True, None, True, None, True, None, True, None, True)), - ('dbus eavesdrop bus=session,' , exp(False, False, False, '', {'eavesdrop'}, False, 'session', False, None, True, None, True, None, True, None, True, None, True, None, True)), - ('dbus peer=(name=foo label=bar),' , exp(False, False, False, '', None , True , None, True, None, True, None, True, None, True, None, True, 'foo', False, 'bar', False)), - ('dbus peer=( name = foo label = bar ),' , exp(False, False, False, '', None , True , None, True, None, True, None, True, None, True, None, True, 'foo', False, 'bar', False)), - ('dbus peer=( name = foo , label = bar ),' , exp(False, False, False, '', None , True , None, True, None, True, None, True, None, True, None, True, 'foo', False, 'bar', False)), - ('dbus peer=(, name = foo , label = bar ,),' , exp(False, False, False, '', None , True , None, True, None, True, None, True, None, True, None, True, 'foo', False, 'bar', False)), - ('dbus peer=( name = foo, label = bar ),' , exp(False, False, False, '', None , True , None, True, None, True, None, True, None, True, None, True, 'foo,', False, 'bar', False)), # XXX peername 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 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)), # 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 - ] + tests = ( + # DbusRule object audit allow deny comment access all? bus all? path all? name all? interface all? member all? peername all? peerlabel all? + ('dbus,', exp(False, False, False, '', None, True, None, True, None, True, None, True, None, True, None, True, None, True, None, True)), + ('dbus ( ),', exp(False, False, False, '', None, True, None, True, None, True, None, True, None, True, None, True, None, True, None, True)), + ('dbus ( , ),', exp(False, False, False, '', None, True, None, True, None, True, None, True, None, True, None, True, None, True, None, True)), + ('dbus send,', exp(False, False, False, '', {'send'}, False, None, True, None, True, None, True, None, True, None, True, None, True, None, True)), + ('dbus (send, receive),', exp(False, False, False, '', {'send', 'receive'}, False, None, True, None, True, None, True, None, True, None, True, None, True, None, True)), + ('dbus send bus=session,', exp(False, False, False, '', {'send'}, False, 'session', False, None, True, None, True, None, True, None, True, None, True, None, True)), + ('deny dbus send bus="session", # cmt', exp(False, False, True, ' # cmt', {'send'}, False, 'session', False, None, True, None, True, None, True, None, True, None, True, None, True)), + ('audit allow dbus peer=(label=foo),', exp(True, True, False, '', None, True, None, True, None, True, None, True, None, True, None, True, None, True, 'foo', False)), + ('dbus bus=session path=/foo/bar,', exp(False, False, False, '', None, True, 'session', False, '/foo/bar', False, None, True, None, True, None, True, None, True, None, True)), + ('dbus send bus=(session),', exp(False, False, False, '', {'send'}, False, 'session', False, None, True, None, True, None, True, None, True, None, True, None, True)), + ('dbus name=(SomeService),', exp(False, False, False, '', None, True, None, True, None, True, 'SomeService', False, None, True, None, True, None, True, None, True)), + ('dbus send bus=session peer=(label="foo"),', exp(False, False, False, '', {'send'}, False, 'session', False, None, True, None, True, None, True, None, True, None, True, 'foo', False)), + ('dbus send bus = ( session ) , ', exp(False, False, False, '', {'send'}, False, 'session', False, None, True, None, True, None, True, None, True, None, True, None, True)), + ('dbus path=/foo,', exp(False, False, False, '', None, True, None, True, '/foo', False, None, True, None, True, None, True, None, True, None, True)), + ('dbus eavesdrop bus=session,', exp(False, False, False, '', {'eavesdrop'}, False, 'session', False, None, True, None, True, None, True, None, True, None, True, None, True)), + ('dbus peer=(name=foo label=bar),', exp(False, False, False, '', None, True, None, True, None, True, None, True, None, True, None, True, 'foo', False, 'bar', False)), + ('dbus peer=( name = foo label = bar ),', exp(False, False, False, '', None, True, None, True, None, True, None, True, None, True, None, True, 'foo', False, 'bar', False)), + ('dbus peer=( name = foo , label = bar ),', exp(False, False, False, '', None, True, None, True, None, True, None, True, None, True, None, True, 'foo', False, 'bar', False)), + ('dbus peer=(, name = foo , label = bar ,),', exp(False, False, False, '', None, True, None, True, None, True, None, True, None, True, None, True, 'foo', False, 'bar', False)), + ('dbus peer=( name = foo, label = bar ),', exp(False, False, False, '', None, True, None, True, None, True, None, True, None, True, None, True, 'foo,', False, 'bar', False)), # XXX peername 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 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)), # 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): self.assertTrue(DbusRule.match(rawrule)) @@ -101,26 +107,28 @@ class DbusTestParse(DbusTest): self.assertEqual(rawrule.strip(), obj.raw_rule) self._compare_obj(obj, expected) + class DbusTestParseInvalid(DbusTest): - tests = [ - ('dbus foo,' , AppArmorException), - ('dbus foo bar,' , AppArmorException), - ('dbus foo int,' , AppArmorException), - ('dbus send bar,' , AppArmorException), - ('dbus send receive,' , AppArmorException), - ('dbus peer=,' , AppArmorException), - ('dbus peer=(label=foo) path=,' , AppArmorException), - ('dbus (invalid),' , AppArmorException), - ('dbus peer=,' , AppArmorException), - ('dbus bus=session bind bus=system,', AppArmorException), + tests = ( + ('dbus foo,', AppArmorException), + ('dbus foo bar,', AppArmorException), + ('dbus foo int,', AppArmorException), + ('dbus send bar,', AppArmorException), + ('dbus send receive,', AppArmorException), + ('dbus peer=,', AppArmorException), + ('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): self.assertTrue(DbusRule.match(rawrule)) # the above invalid rules still match the main regex! with self.assertRaises(expected): DbusRule.parse(rawrule) + class DbusTestParseFromLog(DbusTest): def test_dbus_from_log(self): parser = ReadLog('', '', '') @@ -154,117 +162,119 @@ class DbusTestParseFromLog(DbusTest): 'family': None, 'protocol': None, 'sock_type': None, + 'class': None, }) -# XXX send rules must not contain name conditional, but the log event includes it - how should we handle this in logparser.py? - -# # access bus path name interface member peername peerlabel -# obj = DbusRule(parsed_event['denied_mask'], parsed_event['bus'], parsed_event['path'], parsed_event['name'], parsed_event['interface'], parsed_event['member'], parsed_event['peer_profile'], DbusRule.ALL, log_event=parsed_event) - -# # DbusRule audit allow deny comment access all? bus all? path all? name all? interface all? member all? peername all? peerlabel all? -# expected = exp( False, False, False, '', {'send'}, False, 'system', False, '/org/freedesktop/DBus', False, 'org.freedesktop.DBus', False, 'org.freedesktop.DBus', False, -# 'Hello', False, 'unconfined', False, None, True) + # # XXX send rules must not contain name conditional, but the log event includes it - how should we handle this in logparser.py? + # + # # access bus path name interface member peername peerlabel + # obj = DbusRule(parsed_event['denied_mask'], parsed_event['bus'], parsed_event['path'], parsed_event['name'], parsed_event['interface'], parsed_event['member'], parsed_event['peer_profile'], DbusRule.ALL, log_event=parsed_event) + # + # # DbusRule audit allow deny comment access all? bus all? path all? name all? interface all? member all? peername all? peerlabel all? + # expected = exp(False, False, False, '', {'send'}, False, 'system', False, '/org/freedesktop/DBus', False, 'org.freedesktop.DBus', False, 'org.freedesktop.DBus', False, 'Hello', False, 'unconfined', False, None, True) + # + # self._compare_obj(obj, expected) + # + # self.assertEqual(obj.get_raw(1), ' dbus send bus=system path=/org/freedesktop/DBus name=org.freedesktop.DBus member=Hello peer=(name=unconfined),') -# self._compare_obj(obj, expected) - -# self.assertEqual(obj.get_raw(1), ' dbus send bus=system path=/org/freedesktop/DBus name=org.freedesktop.DBus member=Hello peer=(name=unconfined),') class DbusFromInit(DbusTest): - tests = [ - #DbusRule# access bus path name interface member peername peerlabel audit=, deny=, allow_keyword, comment=, log_event) - (DbusRule( 'send' , 'session', DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL), - #exp# audit allow deny comment access all? bus all? path all? name all? interface all? member all? peername all? peerlabel all? - exp( False, False, False, '', {'send'}, False, 'session', False, None, True, None, True, None, True, None, True, None, True, None, True)), - - #DbusRule# access bus path name interface member peername peerlabel audit=, deny=, allow_keyword, comment=, log_event) - (DbusRule(('send', 'receive'), 'session', DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL), - #exp# audit allow deny comment access all? bus all? path all? name all? interface all? member all? peername all? peerlabel all? - exp( False, False, False, '', {'send', 'receive'}, False, 'session', False, None, True, None, True, None, True, None, True, None, True, None, True)), - - #DbusRule# access bus path name interface member peername peerlabel audit=, deny=, allow_keyword, comment=, log_event) - (DbusRule(DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, '/int/face', '/mem/ber', '/peer/name', '/peer/label'), - #exp# audit allow deny comment access all? bus all? path all? name all? interface all? member all? peername all? peerlabel all? - exp( False, False, False, '', None , True , None, True, None, True, None, True, '/int/face',False, '/mem/ber', False, '/peer/name', False, '/peer/label', False)), - ] + tests = ( + # access bus path name interface member peername peerlabel audit=, deny=, allow_keyword, comment=, log_event) + (DbusRule('send', 'session', DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL), + # audit allow deny comment access all? bus all? path all? name all? interface all? member all? peername all? peerlabel all? + exp(False, False, False, '', {'send'}, False, 'session', False, None, True, None, True, None, True, None, True, None, True, None, True)), + + # access bus path name interface member peername peerlabel audit=, deny=, allow_keyword, comment=, log_event) + (DbusRule(('send', 'receive'), 'session', DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL), + # audit allow deny comment access all? bus all? path all? name all? interface all? member all? peername all? peerlabel all? + exp(False, False, False, '', {'send', 'receive'}, False, 'session', False, None, True, None, True, None, True, None, True, None, True, None, True)), + + # access bus path name interface member peername peerlabel audit=, deny=, allow_keyword, comment=, log_event) + (DbusRule(DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, '/int/face', '/mem/ber', '/peer/name', '/peer/label'), + # audit allow deny comment access all? bus all? path all? name all? interface all? member all? peername all? peerlabel all? + exp(False, False, False, '', None, True, None, True, None, True, None, True, '/int/face', False, '/mem/ber', False, '/peer/name', False, '/peer/label', False)), + ) def _run_test(self, obj, expected): self._compare_obj(obj, expected) + class InvalidDbusInit(AATest): - tests = [ - # access bus path name interface member peername peerlabel expected exception + tests = ( + # access bus path name interface member peername peerlabel expected exception # empty fields - ( ('', 'session', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), - ( ((), 'session', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), - ( (DbusRule.ALL, '', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), - ( (DbusRule.ALL, 'session', '', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), - ( (DbusRule.ALL, 'session', '/org/test', '', '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), - ( (DbusRule.ALL, 'session', '/org/test', 'com.aa.test', '', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), - ( (DbusRule.ALL, 'session', '/org/test', 'com.aa.test', '/int/face', '', '/peer/name', '/peer/label'), AppArmorBug), - ( (DbusRule.ALL, 'session', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', '', '/peer/label'), AppArmorBug), - ( (DbusRule.ALL, 'session', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', '' ), AppArmorBug), + (('', 'session', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), + (((), 'session', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), + ((DbusRule.ALL, '', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), + ((DbusRule.ALL, 'session', '', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), + ((DbusRule.ALL, 'session', '/org/test', '', '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), + ((DbusRule.ALL, 'session', '/org/test', 'com.aa.test', '', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), + ((DbusRule.ALL, 'session', '/org/test', 'com.aa.test', '/int/face', '', '/peer/name', '/peer/label'), AppArmorBug), + ((DbusRule.ALL, 'session', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', '', '/peer/label'), AppArmorBug), + ((DbusRule.ALL, 'session', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', ''), AppArmorBug), # whitespace fields - ( (' ', 'session', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), - ( (DbusRule.ALL, ' ', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), - ( (DbusRule.ALL, 'session', ' ', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), - ( (DbusRule.ALL, 'session', '/org/test', ' ', '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), - ( (DbusRule.ALL, 'session', '/org/test', 'com.aa.test', ' ', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), - ( (DbusRule.ALL, 'session', '/org/test', 'com.aa.test', '/int/face', ' ', '/peer/name', '/peer/label'), AppArmorBug), - ( (DbusRule.ALL, 'session', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', ' ', '/peer/label'), AppArmorBug), - ( (DbusRule.ALL, 'session', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', ' ' ), AppArmorBug), + ((' ', 'session', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), + ((DbusRule.ALL, ' ', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), + ((DbusRule.ALL, 'session', ' ', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), + ((DbusRule.ALL, 'session', '/org/test', ' ', '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), + ((DbusRule.ALL, 'session', '/org/test', 'com.aa.test', ' ', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), + ((DbusRule.ALL, 'session', '/org/test', 'com.aa.test', '/int/face', ' ', '/peer/name', '/peer/label'), AppArmorBug), + ((DbusRule.ALL, 'session', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', ' ', '/peer/label'), AppArmorBug), + ((DbusRule.ALL, 'session', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', ' '), AppArmorBug), # wrong type - dict() - ( (dict(), 'session', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), - ( (('send'), dict(), '/org/test', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), - ( (('send'), 'session', dict(), 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), - ( (('send'), 'session', '/org/test', dict(), '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), - ( (('send'), 'session', '/org/test', 'com.aa.test', dict(), '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), - ( (('send'), 'session', '/org/test', 'com.aa.test', '/int/face', dict(), '/peer/name', '/peer/label'), AppArmorBug), - ( (('send'), 'session', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', dict(), '/peer/label'), AppArmorBug), - ( (('send'), 'session', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', dict() ), AppArmorBug), + ((dict(), 'session', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), + ((('send'), dict(), '/org/test', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), + ((('send'), 'session', dict(), 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), + ((('send'), 'session', '/org/test', dict(), '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), + ((('send'), 'session', '/org/test', 'com.aa.test', dict(), '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), + ((('send'), 'session', '/org/test', 'com.aa.test', '/int/face', dict(), '/peer/name', '/peer/label'), AppArmorBug), + ((('send'), 'session', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', dict(), '/peer/label'), AppArmorBug), + ((('send'), 'session', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', dict()), AppArmorBug), # wrong type - None - ( (None, 'session', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), - ( ((None), 'session', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), - ( (('send'), None, '/org/test', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), - ( (('send'), 'session', None, 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), - ( (('send'), 'session', '/org/test', None, '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), - ( (('send'), 'session', '/org/test', 'com.aa.test', None, '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), - ( (('send'), 'session', '/org/test', 'com.aa.test', '/int/face', None, '/peer/name', '/peer/label'), AppArmorBug), - ( (('send'), 'session', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', None, '/peer/label'), AppArmorBug), - ( (('send'), 'session', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', None ), AppArmorBug), + ((None, 'session', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), + (((None), 'session', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), + ((('send'), None, '/org/test', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), + ((('send'), 'session', None, 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), + ((('send'), 'session', '/org/test', None, '/int/face', '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), + ((('send'), 'session', '/org/test', 'com.aa.test', None, '/mem/ber', '/peer/name', '/peer/label'), AppArmorBug), + ((('send'), 'session', '/org/test', 'com.aa.test', '/int/face', None, '/peer/name', '/peer/label'), AppArmorBug), + ((('send'), 'session', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', None, '/peer/label'), AppArmorBug), + ((('send'), 'session', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', None), AppArmorBug), # bind conflicts with path, interface, member, peer name and peer label - ( (('bind'), DbusRule.ALL, '/org/test', DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL ), AppArmorException), - ( (('bind'), DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, '/int/face', DbusRule.ALL, DbusRule.ALL, DbusRule.ALL ), AppArmorException), - ( (('bind'), DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, '/mem/ber', DbusRule.ALL, DbusRule.ALL ), AppArmorException), - ( (('bind'), DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, '/peer/name', DbusRule.ALL ), AppArmorException), - ( (('bind'), DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, '/peer/label'), AppArmorException), + ((('bind'), DbusRule.ALL, '/org/test', DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL), AppArmorException), + ((('bind'), DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, '/int/face', DbusRule.ALL, DbusRule.ALL, DbusRule.ALL), AppArmorException), + ((('bind'), DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, '/mem/ber', DbusRule.ALL, DbusRule.ALL), AppArmorException), + ((('bind'), DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, '/peer/name', DbusRule.ALL), AppArmorException), + ((('bind'), DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, '/peer/label'), AppArmorException), # eavesdrop conflcts with path, name, interface, member, peer name and peer label - ( (('eavesdrop'),DbusRule.ALL, '/org/test', DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL ), AppArmorException), - ( (('eavesdrop'),DbusRule.ALL, DbusRule.ALL, 'com.aa.test', DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL ), AppArmorException), - ( (('eavesdrop'),DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, '/int/face', DbusRule.ALL, DbusRule.ALL, DbusRule.ALL ), AppArmorException), - ( (('eavesdrop'),DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, '/mem/ber', DbusRule.ALL, DbusRule.ALL ), AppArmorException), - ( (('eavesdrop'),DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, '/peer/name', DbusRule.ALL ), AppArmorException), - ( (('eavesdrop'),DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, '/peer/label'), AppArmorException), + ((('eavesdrop'), DbusRule.ALL, '/org/test', DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL), AppArmorException), + ((('eavesdrop'), DbusRule.ALL, DbusRule.ALL, 'com.aa.test', DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL), AppArmorException), + ((('eavesdrop'), DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, '/int/face', DbusRule.ALL, DbusRule.ALL, DbusRule.ALL), AppArmorException), + ((('eavesdrop'), DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, '/mem/ber', DbusRule.ALL, DbusRule.ALL), AppArmorException), + ((('eavesdrop'), DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, '/peer/name', DbusRule.ALL), AppArmorException), + ((('eavesdrop'), DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, '/peer/label'), AppArmorException), # send and receive conflict with name - ( (('send'), DbusRule.ALL, DbusRule.ALL, 'com.aa.test', DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL ), AppArmorException), - ( (('receive'), DbusRule.ALL, DbusRule.ALL, 'com.aa.test', DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL ), AppArmorException), + ((('send'), DbusRule.ALL, DbusRule.ALL, 'com.aa.test', DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL), AppArmorException), + ((('receive'), DbusRule.ALL, DbusRule.ALL, 'com.aa.test', DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL), AppArmorException), # misc - ( (DbusRule.ALL, DbusRule.ALL, 'foo/bar', DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL ), AppArmorException), # path doesn't start with / - ( (('foo'), DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL ), AppArmorException), # invalid access keyword - ( (('foo', 'send'), DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL ), AppArmorException), # valid + invalid access keyword - ] + ((DbusRule.ALL, DbusRule.ALL, 'foo/bar', DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL), AppArmorException), # path doesn't start with / + ((('foo'), DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL), AppArmorException), # invalid access keyword + ((('foo', 'send'), DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL), AppArmorException), # valid + invalid access keyword + ) def _run_test(self, params, expected): with self.assertRaises(expected): - DbusRule(params[0], params[1], params[2], params[3], params[4], params[5], params[6], params[7]) + DbusRule(*params) def test_missing_params_1(self): with self.assertRaises(TypeError): @@ -272,27 +282,28 @@ class InvalidDbusInit(AATest): def test_missing_params_2(self): with self.assertRaises(TypeError): - DbusRule(('send'), 'session') + DbusRule(('send'), 'session') def test_missing_params_3(self): with self.assertRaises(TypeError): - DbusRule(('send'), 'session', '/org/test') + DbusRule(('send'), 'session', '/org/test') def test_missing_params_4(self): with self.assertRaises(TypeError): - DbusRule(('send'), 'session', '/org/test', 'com.aa.test') + DbusRule(('send'), 'session', '/org/test', 'com.aa.test') def test_missing_params_5(self): with self.assertRaises(TypeError): - DbusRule(('send'), 'session', '/org/test', 'com.aa.test', '/int/face') + DbusRule(('send'), 'session', '/org/test', 'com.aa.test', '/int/face') def test_missing_params_6(self): with self.assertRaises(TypeError): - DbusRule(('send'), 'session', '/org/test', 'com.aa.test', '/int/face', '/mem/ber') + DbusRule(('send'), 'session', '/org/test', 'com.aa.test', '/int/face', '/mem/ber') def test_missing_params_7(self): with self.assertRaises(TypeError): - DbusRule(('send'), 'session', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name') + DbusRule(('send'), 'session', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name') + class InvalidDbusTest(AATest): def _check_invalid_rawrule(self, rawrule): @@ -310,57 +321,57 @@ class InvalidDbusTest(AATest): self._check_invalid_rawrule('signal,') # not a dbus rule def test_empty_data_1(self): - # access bus path name interface member peername peerlabel expected exception - obj = DbusRule(('send'), 'session', '/org/test', DbusRule.ALL, '/int/face', '/mem/ber', '/peer/name', '/peer/label') + # access bus path name interface member peername peerlabel + obj = DbusRule(('send'), 'session', '/org/test', DbusRule.ALL, '/int/face', '/mem/ber', '/peer/name', '/peer/label') obj.access = '' # no access set, and ALL not set with self.assertRaises(AppArmorBug): obj.get_clean(1) def test_empty_data_2(self): - obj = DbusRule(('send'), 'session', '/org/test', DbusRule.ALL, '/int/face', '/mem/ber', '/peer/name', '/peer/label') + obj = DbusRule(('send'), 'session', '/org/test', DbusRule.ALL, '/int/face', '/mem/ber', '/peer/name', '/peer/label') obj.bus = '' # no bus set, and ALL not set with self.assertRaises(AppArmorBug): obj.get_clean(1) def test_empty_data_3(self): - obj = DbusRule(('send'), 'session', '/org/test', DbusRule.ALL, '/int/face', '/mem/ber', '/peer/name', '/peer/label') + obj = DbusRule(('send'), 'session', '/org/test', DbusRule.ALL, '/int/face', '/mem/ber', '/peer/name', '/peer/label') obj.path = '' # no path set, and ALL not set with self.assertRaises(AppArmorBug): obj.get_clean(1) def test_empty_data_4(self): - obj = DbusRule(DbusRule.ALL, 'session', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', '/peer/label') + obj = DbusRule(DbusRule.ALL, 'session', '/org/test', 'com.aa.test', '/int/face', '/mem/ber', '/peer/name', '/peer/label') obj.name = '' # no name set, and ALL not set with self.assertRaises(AppArmorBug): obj.get_clean(1) def test_empty_data_5(self): - obj = DbusRule(('send'), 'session', '/org/test', DbusRule.ALL, '/int/face', '/mem/ber', '/peer/name', '/peer/label') + obj = DbusRule(('send'), 'session', '/org/test', DbusRule.ALL, '/int/face', '/mem/ber', '/peer/name', '/peer/label') obj.interface = '' # no interface set, and ALL not set with self.assertRaises(AppArmorBug): obj.get_clean(1) def test_empty_data_6(self): - obj = DbusRule(('send'), 'session', '/org/test', DbusRule.ALL, '/int/face', '/mem/ber', '/peer/name', '/peer/label') + obj = DbusRule(('send'), 'session', '/org/test', DbusRule.ALL, '/int/face', '/mem/ber', '/peer/name', '/peer/label') obj.member = '' # no member set, and ALL not set with self.assertRaises(AppArmorBug): obj.get_clean(1) def test_empty_data_7(self): - obj = DbusRule(('send'), 'session', '/org/test', DbusRule.ALL, '/int/face', '/mem/ber', '/peer/name', '/peer/label') + obj = DbusRule(('send'), 'session', '/org/test', DbusRule.ALL, '/int/face', '/mem/ber', '/peer/name', '/peer/label') obj.peername = '' # no peername set, and ALL not set with self.assertRaises(AppArmorBug): obj.get_clean(1) def test_empty_data_8(self): - obj = DbusRule(('send'), 'session', '/org/test', DbusRule.ALL, '/int/face', '/mem/ber', '/peer/name', '/peer/label') + obj = DbusRule(('send'), 'session', '/org/test', DbusRule.ALL, '/int/face', '/mem/ber', '/peer/name', '/peer/label') obj.peerlabel = '' # no peerlabel set, and ALL not set with self.assertRaises(AppArmorBug): @@ -377,37 +388,37 @@ class WriteDbusTest(AATest): self.assertEqual(expected.strip(), clean, 'unexpected clean rule') self.assertEqual(rawrule.strip(), raw, 'unexpected raw rule') - tests = [ - # raw rule clean rule - (' dbus , # foo ' , 'dbus, # foo'), - (' audit dbus send,' , 'audit dbus send,'), - (' audit dbus (send ),' , 'audit dbus send,'), - (' audit dbus (send , receive ),' , 'audit dbus (receive send),'), - (' deny dbus send bus=session,# foo bar' , 'deny dbus send bus=session, # foo bar'), - (' deny dbus send bus=(session), ' , 'deny dbus send bus=session,'), - (' deny dbus send peer=(name=unconfined label=foo),' , 'deny dbus send peer=(name=unconfined label=foo),'), - (' deny dbus send interface = ( foo ),' , 'deny dbus send interface=foo,'), - (' deny dbus send ,# foo bar' , 'deny dbus send, # foo bar'), - (' allow dbus peer=(label=foo) ,# foo bar' , 'allow dbus peer=(label=foo), # foo bar'), - ('dbus,' , 'dbus,'), - ('dbus (receive),' , 'dbus receive,'), - ('dbus (send),' , 'dbus send,'), - ('dbus (send receive),' , 'dbus (receive send),'), - ('dbus receive,' , 'dbus receive,'), - ('dbus eavesdrop,' , 'dbus eavesdrop,'), - ('dbus bind bus = foo name = bar,' , 'dbus bind bus=foo name=bar,'), - ('dbus send peer=( label = /foo ) ,' , 'dbus send peer=(label=/foo),'), - ('dbus (receive) member=baz,' , 'dbus receive member=baz,'), - ('dbus send path = /foo,' , 'dbus send path=/foo,'), - ('dbus receive peer=(label=foo),' , 'dbus receive peer=(label=foo),'), - ('dbus (send receive) peer=(name=/usr/bin/bar),' , 'dbus (receive send) peer=(name=/usr/bin/bar),'), - ('dbus (, receive ,,, send ,) interface=/sbin/baz,' , 'dbus (receive send) interface=/sbin/baz,'), # XXX leading and trailing ',' inside (...) causes error + tests = ( + # raw rule clean rule + (' dbus , # foo ', 'dbus, # foo'), + (' audit dbus send,', 'audit dbus send,'), + (' audit dbus (send ),', 'audit dbus send,'), + (' audit dbus (send , receive ),', 'audit dbus (receive send),'), + (' deny dbus send bus=session,# foo bar', 'deny dbus send bus=session, # foo bar'), + (' deny dbus send bus=(session), ', 'deny dbus send bus=session,'), + (' deny dbus send peer=(name=unconfined label=foo),', 'deny dbus send peer=(name=unconfined label=foo),'), + (' deny dbus send interface = ( foo ),', 'deny dbus send interface=foo,'), + (' deny dbus send ,# foo bar', 'deny dbus send, # foo bar'), + (' allow dbus peer=(label=foo) ,# foo bar', 'allow dbus peer=(label=foo), # foo bar'), + ('dbus,', 'dbus,'), + ('dbus (receive),', 'dbus receive,'), + ('dbus (send),', 'dbus send,'), + ('dbus (send receive),', 'dbus (receive send),'), + ('dbus receive,', 'dbus receive,'), + ('dbus eavesdrop,', 'dbus eavesdrop,'), + ('dbus bind bus = foo name = bar,', 'dbus bind bus=foo name=bar,'), + ('dbus send peer=( label = /foo ) ,', 'dbus send peer=(label=/foo),'), + ('dbus (receive) member=baz,', 'dbus receive member=baz,'), + ('dbus send path = /foo,', 'dbus send path=/foo,'), + ('dbus receive peer=(label=foo),', 'dbus receive peer=(label=foo),'), + ('dbus (send receive) peer=(name=/usr/bin/bar),', 'dbus (receive send) peer=(name=/usr/bin/bar),'), + ('dbus (, receive ,,, send ,) interface=/sbin/baz,', 'dbus (receive send) interface=/sbin/baz,'), # XXX leading and trailing ',' inside (...) causes error # XXX add more complex rules - ] + ) def test_write_manually_1(self): - # access bus path name interface member peername peerlabel expected exception - obj = DbusRule(('send'), 'session', '/org/test', DbusRule.ALL, '/int/face', '/mem/ber', '/peer/name', '/peer/label', allow_keyword=True) + # access bus path name interface member peername peerlabel + obj = DbusRule(('send'), 'session', '/org/test', DbusRule.ALL, '/int/face', '/mem/ber', '/peer/name', '/peer/label', allow_keyword=True) expected = ' allow dbus send bus=session path=/org/test interface=/int/face member=/mem/ber peer=(name=/peer/name label=/peer/label),' @@ -415,8 +426,8 @@ class WriteDbusTest(AATest): self.assertEqual(expected, obj.get_raw(2), 'unexpected raw rule') def test_write_manually_2(self): - # access bus path name interface member peername peerlabel expected exception - obj = DbusRule(('send', 'receive'), DbusRule.ALL, '/org/test', DbusRule.ALL, DbusRule.ALL, '/mem/ber', '/peer/name', DbusRule.ALL, allow_keyword=True) + # access bus path name interface member peername peerlabel + obj = DbusRule(('send', 'receive'), DbusRule.ALL, '/org/test', DbusRule.ALL, DbusRule.ALL, '/mem/ber', '/peer/name', DbusRule.ALL, allow_keyword=True) expected = ' allow dbus (receive send) path=/org/test member=/mem/ber peer=(name=/peer/name),' @@ -437,263 +448,272 @@ class DbusCoveredTest(AATest): self.assertEqual(obj.is_covered(check_obj), expected[2], 'Mismatch in is_covered, expected %s' % expected[2]) self.assertEqual(obj.is_covered(check_obj, True, True), expected[3], 'Mismatch in is_covered/exact, expected %s' % expected[3]) + class DbusCoveredTest_01(DbusCoveredTest): rule = 'dbus send,' - tests = [ - # rule equal strict equal covered covered exact - ('dbus,' , [ False , False , False , False ]), - ('dbus send,' , [ True , True , True , True ]), - ('dbus send member=unconfined,' , [ False , False , True , True ]), - ('dbus send, # comment' , [ True , False , True , True ]), - ('allow dbus send,' , [ True , False , True , True ]), - ('dbus send,' , [ True , False , True , True ]), - ('dbus send bus=session,' , [ False , False , True , True ]), - ('dbus send member=(label=foo),' , [ False , False , True , True ]), - ('audit dbus send,' , [ False , False , False , False ]), - ('audit dbus,' , [ False , False , False , False ]), - ('dbus receive,' , [ False , False , False , False ]), - ('dbus member=(label=foo),' , [ False , False , False , False ]), - ('audit deny dbus send,' , [ False , False , False , False ]), - ('deny dbus send,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ('dbus,', (False, False, False, False)), + ('dbus send,', (True, True, True, True)), + ('dbus send member=unconfined,', (False, False, True, True)), + ('dbus send, # comment', (True, False, True, True)), + ('allow dbus send,', (True, False, True, True)), + ('dbus send,', (True, False, True, True)), + ('dbus send bus=session,', (False, False, True, True)), + ('dbus send member=(label=foo),', (False, False, True, True)), + ('audit dbus send,', (False, False, False, False)), + ('audit dbus,', (False, False, False, False)), + ('dbus receive,', (False, False, False, False)), + ('dbus member=(label=foo),', (False, False, False, False)), + ('audit deny dbus send,', (False, False, False, False)), + ('deny dbus send,', (False, False, False, False)), + ) + class DbusCoveredTest_02(DbusCoveredTest): rule = 'audit dbus send,' - tests = [ - # rule equal strict equal covered covered exact - ( 'dbus send,' , [ False , False , True , False ]), - ('audit dbus send,' , [ True , True , True , True ]), - ( 'dbus send bus=session,' , [ False , False , True , False ]), - ('audit dbus send bus=session,' , [ False , False , True , True ]), - ( 'dbus,' , [ False , False , False , False ]), - ('audit dbus,' , [ False , False , False , False ]), - ('dbus receive,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ( 'dbus send,', (False, False, True, False)), + ('audit dbus send,', (True, True, True, True)), + ( 'dbus send bus=session,', (False, False, True, False)), + ('audit dbus send bus=session,', (False, False, True, True)), + ( 'dbus,', (False, False, False, False)), + ('audit dbus,', (False, False, False, False)), + ('dbus receive,', (False, False, False, False)), + ) + class DbusCoveredTest_03(DbusCoveredTest): rule = 'dbus send bus=session,' - tests = [ - # rule equal strict equal covered covered exact - ( 'dbus send bus=session,' , [ True , True , True , True ]), - ('allow dbus send bus=session,' , [ True , False , True , True ]), - ( 'dbus send,' , [ False , False , False , False ]), - ( 'dbus,' , [ False , False , False , False ]), - ( 'dbus send member=(label=foo),' , [ False , False , False , False ]), - ('audit dbus,' , [ False , False , False , False ]), - ('audit dbus send bus=session,' , [ False , False , False , False ]), - ('audit dbus bus=session,' , [ False , False , False , False ]), - ( 'dbus send,' , [ False , False , False , False ]), - ( 'dbus,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ( 'dbus send bus=session,', (True, True, True, True)), + ('allow dbus send bus=session,', (True, False, True, True)), + ( 'dbus send,', (False, False, False, False)), + ( 'dbus,', (False, False, False, False)), + ( 'dbus send member=(label=foo),', (False, False, False, False)), + ('audit dbus,', (False, False, False, False)), + ('audit dbus send bus=session,', (False, False, False, False)), + ('audit dbus bus=session,', (False, False, False, False)), + ( 'dbus send,', (False, False, False, False)), + ( 'dbus,', (False, False, False, False)), + ) + class DbusCoveredTest_04(DbusCoveredTest): rule = 'dbus,' - tests = [ - # rule equal strict equal covered covered exact - ( 'dbus,' , [ True , True , True , True ]), - ('allow dbus,' , [ True , False , True , True ]), - ( 'dbus send,' , [ False , False , True , True ]), - ( 'dbus receive bus=session,' , [ False , False , True , True ]), - ( 'dbus member=(label=foo),' , [ False , False , True , True ]), - ( 'dbus send bus=session,' , [ False , False , True , True ]), - ('audit dbus,' , [ False , False , False , False ]), - ('deny dbus,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ( 'dbus,', (True, True, True, True)), + ('allow dbus,', (True, False, True, True)), + ( 'dbus send,', (False, False, True, True)), + ( 'dbus receive bus=session,', (False, False, True, True)), + ( 'dbus member=(label=foo),', (False, False, True, True)), + ( 'dbus send bus=session,', (False, False, True, True)), + ('audit dbus,', (False, False, False, False)), + ('deny dbus,', (False, False, False, False)), + ) + class DbusCoveredTest_05(DbusCoveredTest): rule = 'deny dbus send,' - tests = [ - # rule equal strict equal covered covered exact - ( 'deny dbus send,' , [ True , True , True , True ]), - ('audit deny dbus send,' , [ False , False , False , False ]), - ( 'dbus send,' , [ False , False , False , False ]), # XXX should covered be true here? - ( 'deny dbus receive,' , [ False , False , False , False ]), - ( 'deny dbus,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ( 'deny dbus send,', (True, True, True, True)), + ('audit deny dbus send,', (False, False, False, False)), + ( 'dbus send,', (False, False, False, False)), # XXX should covered be true here? + ( 'deny dbus receive,', (False, False, False, False)), + ( 'deny dbus,', (False, False, False, False)), + ) + class DbusCoveredTest_06(DbusCoveredTest): rule = 'dbus send peer=(name=unconfined),' - tests = [ - # rule equal strict equal covered covered exact - ('dbus,' , [ False , False , False , False ]), - ('dbus send,' , [ False , False , False , False ]), - ('dbus send peer=(name=unconfined),' , [ True , True , True , True ]), - ('dbus peer=(name=unconfined),' , [ False , False , False , False ]), - ('dbus send, # comment' , [ False , False , False , False ]), - ('allow dbus send,' , [ False , False , False , False ]), - ('allow dbus send peer=(name=unconfined),' , [ True , False , True , True ]), - ('allow dbus send peer=(name=/foo/bar),' , [ False , False , False , False ]), - ('allow dbus send peer=(name=/**),' , [ False , False , False , False ]), - ('allow dbus send peer=(name=**),' , [ False , False , False , False ]), - ('dbus send,' , [ False , False , False , False ]), - ('dbus send peer=(name=unconfined),' , [ True , False , True , True ]), - ('dbus send bus=session,' , [ False , False , False , False ]), - ('dbus send peer=(name=unconfined label=foo),' , [ False , False , True , True ]), - ('audit dbus send peer=(name=unconfined),' , [ False , False , False , False ]), - ('audit dbus,' , [ False , False , False , False ]), - ('dbus receive,' , [ False , False , False , False ]), - ('dbus peer=(label=foo),' , [ False , False , False , False ]), - ('audit deny dbus send,' , [ False , False , False , False ]), - ('deny dbus send,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ('dbus,', (False, False, False, False)), + ('dbus send,', (False, False, False, False)), + ('dbus send peer=(name=unconfined),', (True, True, True, True)), + ('dbus peer=(name=unconfined),', (False, False, False, False)), + ('dbus send, # comment', (False, False, False, False)), + ('allow dbus send,', (False, False, False, False)), + ('allow dbus send peer=(name=unconfined),', (True, False, True, True)), + ('allow dbus send peer=(name=/foo/bar),', (False, False, False, False)), + ('allow dbus send peer=(name=/**),', (False, False, False, False)), + ('allow dbus send peer=(name=**),', (False, False, False, False)), + ('dbus send,', (False, False, False, False)), + ('dbus send peer=(name=unconfined),', (True, False, True, True)), + ('dbus send bus=session,', (False, False, False, False)), + ('dbus send peer=(name=unconfined label=foo),', (False, False, True, True)), + ('audit dbus send peer=(name=unconfined),', (False, False, False, False)), + ('audit dbus,', (False, False, False, False)), + ('dbus receive,', (False, False, False, False)), + ('dbus peer=(label=foo),', (False, False, False, False)), + ('audit deny dbus send,', (False, False, False, False)), + ('deny dbus send,', (False, False, False, False)), + ) + class DbusCoveredTest_07(DbusCoveredTest): rule = 'dbus send peer=(label=unconfined),' - tests = [ - # rule equal strict equal covered covered exact - ('dbus,' , [ False , False , False , False ]), - ('dbus send,' , [ False , False , False , False ]), - ('dbus send peer=(label=unconfined),' , [ True , True , True , True ]), - ('dbus peer=(label=unconfined),' , [ False , False , False , False ]), - ('dbus send, # comment' , [ False , False , False , False ]), - ('allow dbus send,' , [ False , False , False , False ]), - ('allow dbus send peer=(label=unconfined),' , [ True , False , True , True ]), - ('allow dbus send peer=(label=/foo/bar),' , [ False , False , False , False ]), - ('allow dbus send peer=(label=/**),' , [ False , False , False , False ]), - ('allow dbus send peer=(label=**),' , [ False , False , False , False ]), - ('dbus send,' , [ False , False , False , False ]), - ('dbus send peer=(label=unconfined),' , [ True , False , True , True ]), - ('dbus send bus=session,' , [ False , False , False , False ]), - ('dbus send peer=(label=unconfined name=foo),' , [ False , False , True , True ]), - ('audit dbus send peer=(label=unconfined),' , [ False , False , False , False ]), - ('audit dbus,' , [ False , False , False , False ]), - ('dbus receive,' , [ False , False , False , False ]), - ('dbus peer=(label=foo),' , [ False , False , False , False ]), - ('audit deny dbus send,' , [ False , False , False , False ]), - ('deny dbus send,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ('dbus,', (False, False, False, False)), + ('dbus send,', (False, False, False, False)), + ('dbus send peer=(label=unconfined),', (True, True, True, True)), + ('dbus peer=(label=unconfined),', (False, False, False, False)), + ('dbus send, # comment', (False, False, False, False)), + ('allow dbus send,', (False, False, False, False)), + ('allow dbus send peer=(label=unconfined),', (True, False, True, True)), + ('allow dbus send peer=(label=/foo/bar),', (False, False, False, False)), + ('allow dbus send peer=(label=/**),', (False, False, False, False)), + ('allow dbus send peer=(label=**),', (False, False, False, False)), + ('dbus send,', (False, False, False, False)), + ('dbus send peer=(label=unconfined),', (True, False, True, True)), + ('dbus send bus=session,', (False, False, False, False)), + ('dbus send peer=(label=unconfined name=foo),', (False, False, True, True)), + ('audit dbus send peer=(label=unconfined),', (False, False, False, False)), + ('audit dbus,', (False, False, False, False)), + ('dbus receive,', (False, False, False, False)), + ('dbus peer=(label=foo),', (False, False, False, False)), + ('audit deny dbus send,', (False, False, False, False)), + ('deny dbus send,', (False, False, False, False)), + ) + class DbusCoveredTest_08(DbusCoveredTest): rule = 'dbus send path=/foo/bar,' - tests = [ - # rule equal strict equal covered covered exact - ('dbus,' , [ False , False , False , False ]), - ('dbus send,' , [ False , False , False , False ]), - ('dbus send path=/foo/bar,' , [ True , True , True , True ]), - ('dbus send path=/foo/*,' , [ False , False , False , False ]), - ('dbus send path=/**,' , [ False , False , False , False ]), - ('dbus send path=/what/*,' , [ False , False , False , False ]), - ('dbus path=/foo/bar,' , [ False , False , False , False ]), - ('dbus send, # comment' , [ False , False , False , False ]), - ('allow dbus send,' , [ False , False , False , False ]), - ('allow dbus send path=/foo/bar,' , [ True , False , True , True ]), - ('dbus send,' , [ False , False , False , False ]), - ('dbus send path=/foo/bar,' , [ True , False , True , True ]), - ('dbus send path=/what/ever,' , [ False , False , False , False ]), - ('dbus send bus=session,' , [ False , False , False , False ]), - ('dbus send path=/foo/bar peer=(label=foo),' , [ False , False , True , True ]), - ('audit dbus send path=/foo/bar,' , [ False , False , False , False ]), - ('audit dbus,' , [ False , False , False , False ]), - ('dbus receive,' , [ False , False , False , False ]), - ('dbus peer=(label=foo),' , [ False , False , False , False ]), - ('audit deny dbus send,' , [ False , False , False , False ]), - ('deny dbus send,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ('dbus,', (False, False, False, False)), + ('dbus send,', (False, False, False, False)), + ('dbus send path=/foo/bar,', (True, True, True, True)), + ('dbus send path=/foo/*,', (False, False, False, False)), + ('dbus send path=/**,', (False, False, False, False)), + ('dbus send path=/what/*,', (False, False, False, False)), + ('dbus path=/foo/bar,', (False, False, False, False)), + ('dbus send, # comment', (False, False, False, False)), + ('allow dbus send,', (False, False, False, False)), + ('allow dbus send path=/foo/bar,', (True, False, True, True)), + ('dbus send,', (False, False, False, False)), + ('dbus send path=/foo/bar,', (True, False, True, True)), + ('dbus send path=/what/ever,', (False, False, False, False)), + ('dbus send bus=session,', (False, False, False, False)), + ('dbus send path=/foo/bar peer=(label=foo),', (False, False, True, True)), + ('audit dbus send path=/foo/bar,', (False, False, False, False)), + ('audit dbus,', (False, False, False, False)), + ('dbus receive,', (False, False, False, False)), + ('dbus peer=(label=foo),', (False, False, False, False)), + ('audit deny dbus send,', (False, False, False, False)), + ('deny dbus send,', (False, False, False, False)), + ) + class DbusCoveredTest_09(DbusCoveredTest): rule = 'dbus send member=**,' - tests = [ - # rule equal strict equal covered covered exact - ('dbus,' , [ False , False , False , False ]), - ('dbus send,' , [ False , False , False , False ]), - ('dbus send member=/foo/bar,' , [ False , False , True , True ]), - ('dbus send member=/foo/*,' , [ False , False , False , False ]), # TODO: wildcard vs. wildcard never matches in is_covered_aare() - ('dbus send member=/**,' , [ False , False , False , False ]), # TODO: wildcard vs. wildcard never matches in is_covered_aare() - ('dbus send member=/what/*,' , [ False , False , False , False ]), # TODO: wildcard vs. wildcard never matches in is_covered_aare() - ('dbus member=/foo/bar,' , [ False , False , False , False ]), - ('dbus send, # comment' , [ False , False , False , False ]), - ('allow dbus send,' , [ False , False , False , False ]), - ('allow dbus send member=/foo/bar,' , [ False , False , True , True ]), - ('dbus send,' , [ False , False , False , False ]), - ('dbus send member=/foo/bar,' , [ False , False , True , True ]), - ('dbus send member=/what/ever,' , [ False , False , True , True ]), - ('dbus send bus=session,' , [ False , False , False , False ]), - ('dbus send member=/foo/bar peer=(label=foo),' , [ False , False , True , True ]), - ('audit dbus send member=/foo/bar,' , [ False , False , False , False ]), - ('audit dbus,' , [ False , False , False , False ]), - ('dbus receive,' , [ False , False , False , False ]), - ('dbus member=(label=foo),' , [ False , False , False , False ]), - ('audit deny dbus send,' , [ False , False , False , False ]), - ('deny dbus send,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ('dbus,', (False, False, False, False)), + ('dbus send,', (False, False, False, False)), + ('dbus send member=/foo/bar,', (False, False, True, True)), + ('dbus send member=/foo/*,', (False, False, False, False)), # TODO: wildcard vs. wildcard never matches in is_covered_aare() + ('dbus send member=/**,', (False, False, False, False)), # TODO: wildcard vs. wildcard never matches in is_covered_aare() + ('dbus send member=/what/*,', (False, False, False, False)), # TODO: wildcard vs. wildcard never matches in is_covered_aare() + ('dbus member=/foo/bar,', (False, False, False, False)), + ('dbus send, # comment', (False, False, False, False)), + ('allow dbus send,', (False, False, False, False)), + ('allow dbus send member=/foo/bar,', (False, False, True, True)), + ('dbus send,', (False, False, False, False)), + ('dbus send member=/foo/bar,', (False, False, True, True)), + ('dbus send member=/what/ever,', (False, False, True, True)), + ('dbus send bus=session,', (False, False, False, False)), + ('dbus send member=/foo/bar peer=(label=foo),', (False, False, True, True)), + ('audit dbus send member=/foo/bar,', (False, False, False, False)), + ('audit dbus,', (False, False, False, False)), + ('dbus receive,', (False, False, False, False)), + ('dbus member=(label=foo),', (False, False, False, False)), + ('audit deny dbus send,', (False, False, False, False)), + ('deny dbus send,', (False, False, False, False)), + ) + class DbusCoveredTest_10(DbusCoveredTest): rule = 'dbus (send, receive) interface=foo,' - tests = [ - # rule equal strict equal covered covered exact - ('dbus,' , [ False , False , False , False ]), - ('dbus send,' , [ False , False , False , False ]), - ('dbus send interface=foo,' , [ False , False , True , True ]), - ('dbus receive bus=session interface=foo,' , [ False , False , True , True ]), - ('dbus (receive,send) interface=foo,' , [ True , False , True , True ]), - ('dbus (receive,send),' , [ False , False , False , False ]), - ('dbus send bus=session,' , [ False , False , False , False ]), - ('dbus send member=/foo/bar,' , [ False , False , False , False ]), - ('dbus send member=/foo/*,' , [ False , False , False , False ]), - ('dbus send member=/**,' , [ False , False , False , False ]), - ('dbus send member=/what/*,' , [ False , False , False , False ]), - ('dbus member=/foo/bar,' , [ False , False , False , False ]), - ('dbus send, # comment' , [ False , False , False , False ]), - ('allow dbus send,' , [ False , False , False , False ]), - ('allow dbus send member=/foo/bar,' , [ False , False , False , False ]), - ('dbus send,' , [ False , False , False , False ]), - ('dbus send member=/foo/bar,' , [ False , False , False , False ]), - ('dbus send member=/what/ever,' , [ False , False , False , False ]), - ('dbus send bus=session,' , [ False , False , False , False ]), - ('dbus send bus=session interface=foo,' , [ False , False , True , True ]), - ('dbus send member=/foo/bar peer=(label=foo),' , [ False , False , False , False ]), - ('audit dbus send member=/foo/bar,' , [ False , False , False , False ]), - ('audit dbus,' , [ False , False , False , False ]), - ('dbus receive,' , [ False , False , False , False ]), - ('dbus peer=(label=foo),' , [ False , False , False , False ]), - ('audit deny dbus send,' , [ False , False , False , False ]), - ('deny dbus send,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ('dbus,', (False, False, False, False)), + ('dbus send,', (False, False, False, False)), + ('dbus send interface=foo,', (False, False, True, True)), + ('dbus receive bus=session interface=foo,', (False, False, True, True)), + ('dbus (receive,send) interface=foo,', (True, False, True, True)), + ('dbus (receive,send),', (False, False, False, False)), + ('dbus send bus=session,', (False, False, False, False)), + ('dbus send member=/foo/bar,', (False, False, False, False)), + ('dbus send member=/foo/*,', (False, False, False, False)), + ('dbus send member=/**,', (False, False, False, False)), + ('dbus send member=/what/*,', (False, False, False, False)), + ('dbus member=/foo/bar,', (False, False, False, False)), + ('dbus send, # comment', (False, False, False, False)), + ('allow dbus send,', (False, False, False, False)), + ('allow dbus send member=/foo/bar,', (False, False, False, False)), + ('dbus send,', (False, False, False, False)), + ('dbus send member=/foo/bar,', (False, False, False, False)), + ('dbus send member=/what/ever,', (False, False, False, False)), + ('dbus send bus=session,', (False, False, False, False)), + ('dbus send bus=session interface=foo,', (False, False, True, True)), + ('dbus send member=/foo/bar peer=(label=foo),', (False, False, False, False)), + ('audit dbus send member=/foo/bar,', (False, False, False, False)), + ('audit dbus,', (False, False, False, False)), + ('dbus receive,', (False, False, False, False)), + ('dbus peer=(label=foo),', (False, False, False, False)), + ('audit deny dbus send,', (False, False, False, False)), + ('deny dbus send,', (False, False, False, False)), + ) + class DbusCoveredTest_11(DbusCoveredTest): rule = 'dbus name=/foo/bar,' - tests = [ - # rule equal strict equal covered covered exact - ('dbus,' , [ False , False , False , False ]), - ('dbus name=/foo/bar,' , [ True , True , True , True ]), - ('dbus name=/foo/*,' , [ False , False , False , False ]), - ('dbus name=/**,' , [ False , False , False , False ]), - ('dbus name=/what/*,' , [ False , False , False , False ]), - ('dbus, # comment' , [ False , False , False , False ]), - ('allow dbus,' , [ False , False , False , False ]), - ('allow dbus name=/foo/bar,' , [ True , False , True , True ]), - ('dbus ,' , [ False , False , False , False ]), - ('dbus name=/foo/bar,' , [ True , False , True , True ]), - ('dbus name=/what/ever,' , [ False , False , False , False ]), - ('dbus bus=session,' , [ False , False , False , False ]), - ('dbus name=/foo/bar peer=(label=foo),' , [ False , False , True , True ]), - ('audit dbus name=/foo/bar,' , [ False , False , False , False ]), - ('audit dbus,' , [ False , False , False , False ]), - ('dbus receive,' , [ False , False , False , False ]), - ('dbus peer=(label=foo),' , [ False , False , False , False ]), - ('audit deny dbus,' , [ False , False , False , False ]), - ('deny dbus,' , [ False , False , False , False ]), - ] - + tests = ( + # rule equal strict equal covered covered exact + ('dbus,', (False, False, False, False)), + ('dbus name=/foo/bar,', (True, True, True, True)), + ('dbus name=/foo/*,', (False, False, False, False)), + ('dbus name=/**,', (False, False, False, False)), + ('dbus name=/what/*,', (False, False, False, False)), + ('dbus, # comment', (False, False, False, False)), + ('allow dbus,', (False, False, False, False)), + ('allow dbus name=/foo/bar,', (True, False, True, True)), + ('dbus ,', (False, False, False, False)), + ('dbus name=/foo/bar,', (True, False, True, True)), + ('dbus name=/what/ever,', (False, False, False, False)), + ('dbus bus=session,', (False, False, False, False)), + ('dbus name=/foo/bar peer=(label=foo),', (False, False, True, True)), + ('audit dbus name=/foo/bar,', (False, False, False, False)), + ('audit dbus,', (False, False, False, False)), + ('dbus receive,', (False, False, False, False)), + ('dbus peer=(label=foo),', (False, False, False, False)), + ('audit deny dbus,', (False, False, False, False)), + ('deny dbus,', (False, False, False, False)), + ) class DbusCoveredTest_Invalid(AATest): def AASetup(self): - # access bus path name interface member peername peerlabel expected exception - self.obj = DbusRule(('send', 'receive'), 'session', '/org/test', DbusRule.ALL, '/int/face', DbusRule.ALL, '/peer/name', '/peer/label', allow_keyword=True) - self.testobj = DbusRule(('send'), 'session', '/org/test', DbusRule.ALL, '/int/face', '/mem/ber', '/peer/name', '/peer/label', allow_keyword=True) - + # access bus path name interface member peername peerlabel + self.obj = DbusRule(('send', 'receive'), 'session', '/org/test', DbusRule.ALL, '/int/face', DbusRule.ALL, '/peer/name', '/peer/label', allow_keyword=True) + self.testobj = DbusRule(('send'), 'session', '/org/test', DbusRule.ALL, '/int/face', '/mem/ber', '/peer/name', '/peer/label', allow_keyword=True) def test_borked_obj_is_covered_1(self): self.testobj.access = '' @@ -715,8 +735,8 @@ class DbusCoveredTest_Invalid(AATest): def test_borked_obj_is_covered_4(self): # we need a different 'victim' because dbus send doesn't allow the name conditional we want to test here - self.obj = DbusRule(('bind'), 'session', DbusRule.ALL, '/name', DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, allow_keyword=True) - self.testobj = DbusRule(('bind'), 'session', DbusRule.ALL, '/name', DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, allow_keyword=True) + self.obj = DbusRule( ('bind'), 'session', DbusRule.ALL, '/name', DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, allow_keyword=True) + self.testobj = DbusRule(('bind'), 'session', DbusRule.ALL, '/name', DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, DbusRule.ALL, allow_keyword=True) self.testobj.name = '' with self.assertRaises(AppArmorBug): @@ -746,7 +766,6 @@ class DbusCoveredTest_Invalid(AATest): with self.assertRaises(AppArmorBug): self.obj.is_covered(self.testobj) - def test_invalid_is_covered(self): obj = DbusRule.parse('dbus send,') @@ -763,25 +782,27 @@ class DbusCoveredTest_Invalid(AATest): with self.assertRaises(AppArmorBug): obj.is_equal(testobj) + class DbusLogprofHeaderTest(AATest): - tests = [ - ('dbus,', [ _('Access mode'), _('ALL'), _('Bus'), _('ALL'), _('Path'), _('ALL'), _('Name'), _('ALL'), _('Interface'), _('ALL'), _('Member'), _('ALL'), _('Peer name'), _('ALL'), _('Peer label'), _('ALL')]), - ('dbus (send receive),', [ _('Access mode'), 'receive send', _('Bus'), _('ALL'), _('Path'), _('ALL'), _('Name'), _('ALL'), _('Interface'), _('ALL'), _('Member'), _('ALL'), _('Peer name'), _('ALL'), _('Peer label'), _('ALL')]), - ('dbus send bus=session,', [ _('Access mode'), 'send', _('Bus'), 'session', _('Path'), _('ALL'), _('Name'), _('ALL'), _('Interface'), _('ALL'), _('Member'), _('ALL'), _('Peer name'), _('ALL'), _('Peer label'), _('ALL')]), - ('deny dbus,', [_('Qualifier'), 'deny', _('Access mode'), _('ALL'), _('Bus'), _('ALL'), _('Path'), _('ALL'), _('Name'), _('ALL'), _('Interface'), _('ALL'), _('Member'), _('ALL'), _('Peer name'), _('ALL'), _('Peer label'), _('ALL')]), - ('allow dbus send,', [_('Qualifier'), 'allow', _('Access mode'), 'send', _('Bus'), _('ALL'), _('Path'), _('ALL'), _('Name'), _('ALL'), _('Interface'), _('ALL'), _('Member'), _('ALL'), _('Peer name'), _('ALL'), _('Peer label'), _('ALL')]), - ('audit dbus send bus=session,', [_('Qualifier'), 'audit', _('Access mode'), 'send', _('Bus'), 'session', _('Path'), _('ALL'), _('Name'), _('ALL'), _('Interface'), _('ALL'), _('Member'), _('ALL'), _('Peer name'), _('ALL'), _('Peer label'), _('ALL')]), - ('audit deny dbus send,', [_('Qualifier'), 'audit deny', _('Access mode'), 'send', _('Bus'), _('ALL'), _('Path'), _('ALL'), _('Name'), _('ALL'), _('Interface'), _('ALL'), _('Member'), _('ALL'), _('Peer name'), _('ALL'), _('Peer label'), _('ALL')]), - ('dbus bind name=bind.name,', [ _('Access mode'), 'bind', _('Bus'), _('ALL'), _('Path'), _('ALL'), _('Name'), 'bind.name', _('Interface'), _('ALL'), _('Member'), _('ALL'), _('Peer name'), _('ALL'), _('Peer label'), _('ALL')]), + tests = ( + ('dbus,', [ _('Access mode'), _('ALL'), _('Bus'), _('ALL'), _('Path'), _('ALL'), _('Name'), _('ALL'), _('Interface'), _('ALL'), _('Member'), _('ALL'), _('Peer name'), _('ALL'), _('Peer label'), _('ALL')]), + ('dbus (send receive),', [ _('Access mode'), 'receive send', _('Bus'), _('ALL'), _('Path'), _('ALL'), _('Name'), _('ALL'), _('Interface'), _('ALL'), _('Member'), _('ALL'), _('Peer name'), _('ALL'), _('Peer label'), _('ALL')]), + ('dbus send bus=session,', [ _('Access mode'), 'send', _('Bus'), 'session', _('Path'), _('ALL'), _('Name'), _('ALL'), _('Interface'), _('ALL'), _('Member'), _('ALL'), _('Peer name'), _('ALL'), _('Peer label'), _('ALL')]), + ('deny dbus,', [_('Qualifier'), 'deny', _('Access mode'), _('ALL'), _('Bus'), _('ALL'), _('Path'), _('ALL'), _('Name'), _('ALL'), _('Interface'), _('ALL'), _('Member'), _('ALL'), _('Peer name'), _('ALL'), _('Peer label'), _('ALL')]), + ('allow dbus send,', [_('Qualifier'), 'allow', _('Access mode'), 'send', _('Bus'), _('ALL'), _('Path'), _('ALL'), _('Name'), _('ALL'), _('Interface'), _('ALL'), _('Member'), _('ALL'), _('Peer name'), _('ALL'), _('Peer label'), _('ALL')]), + ('audit dbus send bus=session,', [_('Qualifier'), 'audit', _('Access mode'), 'send', _('Bus'), 'session', _('Path'), _('ALL'), _('Name'), _('ALL'), _('Interface'), _('ALL'), _('Member'), _('ALL'), _('Peer name'), _('ALL'), _('Peer label'), _('ALL')]), + ('audit deny dbus send,', [_('Qualifier'), 'audit deny', _('Access mode'), 'send', _('Bus'), _('ALL'), _('Path'), _('ALL'), _('Name'), _('ALL'), _('Interface'), _('ALL'), _('Member'), _('ALL'), _('Peer name'), _('ALL'), _('Peer label'), _('ALL')]), + ('dbus bind name=bind.name,', [ _('Access mode'), 'bind', _('Bus'), _('ALL'), _('Path'), _('ALL'), _('Name'), 'bind.name', _('Interface'), _('ALL'), _('Member'), _('ALL'), _('Peer name'), _('ALL'), _('Peer label'), _('ALL')]), ('dbus send bus=session path=/path interface=aa.test member=ExMbr peer=(name=(peer.name)),', - [ _('Access mode'), 'send', _('Bus'), 'session', _('Path'), '/path', _('Name'), _('ALL'), _('Interface'), 'aa.test', _('Member'), 'ExMbr', _('Peer name'), 'peer.name',_('Peer label'), _('ALL')]), - ('dbus send peer=(label=foo),', [ _('Access mode'), 'send', _('Bus'), _('ALL'), _('Path'), _('ALL'), _('Name'), _('ALL'), _('Interface'), _('ALL'), _('Member'), _('ALL'), _('Peer name'), _('ALL'), _('Peer label'), 'foo' ]), - ] + [ _('Access mode'), 'send', _('Bus'), 'session', _('Path'), '/path', _('Name'), _('ALL'), _('Interface'), 'aa.test', _('Member'), 'ExMbr', _('Peer name'), 'peer.name', _('Peer label'), _('ALL')]), + ('dbus send peer=(label=foo),', [ _('Access mode'), 'send', _('Bus'), _('ALL'), _('Path'), _('ALL'), _('Name'), _('ALL'), _('Interface'), _('ALL'), _('Member'), _('ALL'), _('Peer name'), _('ALL'), _('Peer label'), 'foo']), + ) def _run_test(self, params, expected): - obj = DbusRule._parse(params) + obj = DbusRule.parse(params) self.assertEqual(obj.logprof_header(), expected) + ## --- tests for DbusRuleset --- # class DbusRulesTest(AATest): @@ -865,8 +886,10 @@ class DbusGlobTest(AATest): # get_glob_ext is not available for dbus rules self.ruleset.get_glob_ext('dbus send peer=(label=foo),') -#class DbusDeleteTest(AATest): -# pass + +# class DbusDeleteTest(AATest): +# pass + setup_all_loops(__name__) if __name__ == '__main__': diff --git a/utils/test/test-example.py b/utils/test/test-example.py index 84b28c964..793fe32d1 100644 --- a/utils/test/test-example.py +++ b/utils/test/test-example.py @@ -10,24 +10,27 @@ # ------------------------------------------------------------------ import unittest -from common_test import AATest, setup_all_loops # , setup_aa + # import apparmor.aa as aa # see the setup_aa() call for details +from common_test import AATest, setup_all_loops # , setup_aa + class TestFoo(AATest): - tests = [ - (0, 0 ), + tests = ( + (0, 0), (42, 42), - ] + ) def _run_test(self, params, expected): self.assertEqual(params, expected) + class TestBar(AATest): - tests = [ + tests = ( ('a', 'foo'), ('b', 'bar'), ('c', 'baz'), - ] + ) def _run_test(self, params, expected): self.assertNotEqual(params, expected) @@ -35,6 +38,7 @@ class TestBar(AATest): def testAdditionalBarTest(self): self.assertEqual(1, 1) + class TestBaz(AATest): def AASetup(self): # called by setUp() - use AASetup() to avoid the need for using super(...) @@ -47,6 +51,7 @@ class TestBaz(AATest): def test_Baz_only_one_test(self): self.assertEqual("baz", "baz") + # if you import apparmor.aa and call init_aa() in your tests, uncomment this # setup_aa(aa) setup_all_loops(__name__) diff --git a/utils/test/test-file.py b/utils/test/test-file.py index 3e285cfef..227e4380e 100644 --- a/utils/test/test-file.py +++ b/utils/test/test-file.py @@ -15,18 +15,22 @@ import unittest from collections import namedtuple -from common_test import AATest, setup_all_loops -from apparmor.rule.file import FileRule, FileRuleset -from apparmor.rule import BaseRule import apparmor.severity as severity -from apparmor.common import AppArmorException, AppArmorBug +from apparmor.common import AppArmorBug, AppArmorException from apparmor.logparser import ReadLog +from apparmor.rule import BaseRule +from apparmor.rule.file import FileRule, FileRuleset from apparmor.translations import init_translation +from common_test import AATest, setup_all_loops + _ = init_translation() -exp = namedtuple('exp', ['audit', 'allow_keyword', 'deny', 'comment', - 'path', 'all_paths', 'perms', 'all_perms', 'exec_perms', 'target', 'all_targets', 'owner', 'file_keyword', 'leading_perms']) +exp = namedtuple( + 'exp', ('audit', 'allow_keyword', 'deny', 'comment', 'path', 'all_paths', + 'perms', 'all_perms', 'exec_perms', 'target', 'all_targets', + 'owner', 'file_keyword', 'leading_perms')) + # --- tests for single FileRule --- # @@ -56,47 +60,48 @@ class FileTest(AATest): else: self.assertEqual(obj, expected) + class FileTestParse(FileTest): - tests = [ - # FileRule object audit allow deny comment path all_paths perms all? exec_perms target all? owner file keyword leading perms + tests = ( + # FileRule object audit allow deny comment path all_paths perms all? exec_perms target all? owner file keyword leading perms # bare file rules - ('file,' , exp(False, False, False, '', None, True , None, True, None, None, True, False, False, False )), - ('allow file,' , exp(False, True, False, '', None, True , None, True, None, None, True, False, False, False )), - ('audit deny owner file, # cmt' , exp(True, False, True, ' # cmt', None, True , None, True, None, None, True, True, False, False )), + ('file,', exp(False, False, False, '', None, True, None, True, None, None, True, False, False, False)), + ('allow file,', exp(False, True, False, '', None, True, None, True, None, None, True, False, False, False)), + ('audit deny owner file, # cmt', exp(True, False, True, ' # cmt', None, True, None, True, None, None, True, True, False, False)), # "normal" file rules - ('/foo r,' , exp(False, False, False, '', '/foo', False, {'r'}, False, None, None, True, False, False, False )), - ('file /foo rwix,' , exp(False, False, False, '', '/foo', False, {'r', 'w'}, False, 'ix', None, True, False, True, False )), - ('/foo Px -> bar,' , exp(False, False, False, '', '/foo', False, set(), False, 'Px', 'bar', False, False, False, False )), - ('@{PROC}/[a-z]** mr,' , exp(False, False, False, '', '@{PROC}/[a-z]**',False, {'r', 'm'}, False, None, None, True, False, False, False )), + ('/foo r,', exp(False, False, False, '', '/foo', False, {'r'}, False, None, None, True, False, False, False)), + ('file /foo rwix,', exp(False, False, False, '', '/foo', False, {'r', 'w'}, False, 'ix', None, True, False, True, False)), + ('/foo Px -> bar,', exp(False, False, False, '', '/foo', False, set(), False, 'Px', 'bar', False, False, False, False)), + ('@{PROC}/[a-z]** mr,', exp(False, False, False, '', '@{PROC}/[a-z]**', False, {'r', 'm'}, False, None, None, True, False, False, False)), - ('audit /tmp/foo r,' , exp(True, False, False, '', '/tmp/foo', False, {'r'}, False, None, None, True, False, False, False )), - ('audit deny /tmp/foo r,' , exp(True, False, True, '', '/tmp/foo', False, {'r'}, False, None, None, True, False, False, False )), - ('audit deny /tmp/foo rx,' , exp(True, False, True, '', '/tmp/foo', False, {'r'}, False, 'x', None, True, False, False, False )), - ('allow /tmp/foo ra,' , exp(False, True, False, '', '/tmp/foo', False, {'r', 'a'}, False, None, None, True, False, False, False )), - ('audit allow /tmp/foo ra,' , exp(True, True, False, '', '/tmp/foo', False, {'r', 'a'}, False, None, None, True, False, False, False )), + ('audit /tmp/foo r,', exp(True, False, False, '', '/tmp/foo', False, {'r'}, False, None, None, True, False, False, False)), + ('audit deny /tmp/foo r,', exp(True, False, True, '', '/tmp/foo', False, {'r'}, False, None, None, True, False, False, False)), + ('audit deny /tmp/foo rx,', exp(True, False, True, '', '/tmp/foo', False, {'r'}, False, 'x', None, True, False, False, False)), + ('allow /tmp/foo ra,', exp(False, True, False, '', '/tmp/foo', False, {'r', 'a'}, False, None, None, True, False, False, False)), + ('audit allow /tmp/foo ra,', exp(True, True, False, '', '/tmp/foo', False, {'r', 'a'}, False, None, None, True, False, False, False)), # file rules with leading permission - ('r /foo,' , exp(False, False, False, '', '/foo', False, {'r'}, False, None, None, True, False, False, True )), - ('file rwix /foo,' , exp(False, False, False, '', '/foo', False, {'r', 'w'}, False, 'ix', None, True, False, True, True )), - ('Px /foo -> bar,' , exp(False, False, False, '', '/foo', False, set(), False, 'Px', 'bar', False, False, False, True )), - ('mr @{PROC}/[a-z]**,' , exp(False, False, False, '', '@{PROC}/[a-z]**',False, {'r', 'm'}, False, None, None, True, False, False, True )), + ('r /foo,', exp(False, False, False, '', '/foo', False, {'r'}, False, None, None, True, False, False, True)), + ('file rwix /foo,', exp(False, False, False, '', '/foo', False, {'r', 'w'}, False, 'ix', None, True, False, True, True)), + ('Px /foo -> bar,', exp(False, False, False, '', '/foo', False, set(), False, 'Px', 'bar', False, False, False, True)), + ('mr @{PROC}/[a-z]**,', exp(False, False, False, '', '@{PROC}/[a-z]**', False, {'r', 'm'}, False, None, None, True, False, False, True)), - ('audit r /tmp/foo,' , exp(True, False, False, '', '/tmp/foo', False, {'r'}, False, None, None, True, False, False, True )), - ('audit deny r /tmp/foo,' , exp(True, False, True, '', '/tmp/foo', False, {'r'}, False, None, None, True, False, False, True )), - ('allow ra /tmp/foo,' , exp(False, True, False, '', '/tmp/foo', False, {'r', 'a'}, False, None, None, True, False, False, True )), - ('audit allow ra /tmp/foo,' , exp(True, True, False, '', '/tmp/foo', False, {'r', 'a'}, False, None, None, True, False, False, True )), + ('audit r /tmp/foo,', exp(True, False, False, '', '/tmp/foo', False, {'r'}, False, None, None, True, False, False, True)), + ('audit deny r /tmp/foo,', exp(True, False, True, '', '/tmp/foo', False, {'r'}, False, None, None, True, False, False, True)), + ('allow ra /tmp/foo,', exp(False, True, False, '', '/tmp/foo', False, {'r', 'a'}, False, None, None, True, False, False, True)), + ('audit allow ra /tmp/foo,', exp(True, True, False, '', '/tmp/foo', False, {'r', 'a'}, False, None, None, True, False, False, True)), # duplicated (but not conflicting) permissions - ('/foo PxPxPxPxrwPx -> bar,' , exp(False, False, False, '', '/foo', False, {'r', 'w'}, False, 'Px', 'bar', False, False, False, False )), - ('/foo CixCixrwCix -> bar, ' , exp(False, False, False, '', '/foo', False, {'r', 'w'}, False, 'Cix', 'bar', False, False, False, False )), + ('/foo PxPxPxPxrwPx -> bar,', exp(False, False, False, '', '/foo', False, {'r', 'w'}, False, 'Px', 'bar', False, False, False, False)), + ('/foo CixCixrwCix -> bar, ', exp(False, False, False, '', '/foo', False, {'r', 'w'}, False, 'Cix', 'bar', False, False, False, False)), # link rules - ('link /foo -> /bar,' , exp(False, False, False, '', '/foo', False, {'link'}, False, None, '/bar', False, False, False, True )), - ('link subset /foo -> /bar,' , exp(False, False, False, '', '/foo', False, {'link', 'subset'}, False, None, '/bar', False, False, False, True )), - ] + ('link /foo -> /bar,', exp(False, False, False, '', '/foo', False, {'link'}, False, None, '/bar', False, False, False, True)), + ('link subset /foo -> /bar,', exp(False, False, False, '', '/foo', False, {'link', 'subset'}, False, None, '/bar', False, False, False, True)), + ) def _run_test(self, rawrule, expected): self.assertTrue(FileRule.match(rawrule)) @@ -104,43 +109,46 @@ class FileTestParse(FileTest): self.assertEqual(rawrule.strip(), obj.raw_rule) self._compare_obj(obj, expected) + class FileTestParseInvalid(FileTest): - tests = [ - ('/foo x,' , AppArmorException), # should be *x - ('/foo raw,' , AppArmorException), # r and a conflict - ('deny /foo ix,' , AppArmorException), # endy only allows x, but not *x - ('deny /foo Px,' , AppArmorException), # deny only allows x, but not *x - ('deny /foo Pi,' , AppArmorException), # missing 'x', and P not allowed - ('allow /foo x,' , AppArmorException), # should be *x - ('/foo Pxrix,' , AppArmorException), # exec mode conflict - ('/foo PixUx,' , AppArmorException), # exec mode conflict - ('/foo PxUx,' , AppArmorException), # exec mode conflict - ('/foo PUxPix,' , AppArmorException), # exec mode conflict - ('/foo Pi,' , AppArmorException), # missing 'x' - ] + tests = ( + ('/foo x,', AppArmorException), # should be *x + ('/foo raw,', AppArmorException), # r and a conflict + ('deny /foo ix,', AppArmorException), # endy only allows x, but not *x + ('deny /foo Px,', AppArmorException), # deny only allows x, but not *x + ('deny /foo Pi,', AppArmorException), # missing 'x', and P not allowed + ('allow /foo x,', AppArmorException), # should be *x + ('/foo Pxrix,', AppArmorException), # exec mode conflict + ('/foo PixUx,', AppArmorException), # exec mode conflict + ('/foo PxUx,', AppArmorException), # exec mode conflict + ('/foo PUxPix,', AppArmorException), # exec mode conflict + ('/foo Pi,', AppArmorException), # missing 'x' + ) def _run_test(self, rawrule, expected): self.assertTrue(FileRule.match(rawrule)) # the above invalid rules still match the main regex! with self.assertRaises(expected): FileRule.parse(rawrule) + class FileTestNonMatch(AATest): - tests = [ - ('file /foo,' , False ), - ('file rw,' , False ), - ('file -> bar,' , False ), - ('file Px -> bar,' , False ), - ('/foo bar,' , False ), - ('dbus /foo,' , False ), - ('link /foo,' , False ), # missing '-> /target' - ('link -> /bar,' , False ), # missing path - ('/foo -> bar link,', False ), # link has to be leading keyword - ('link,' , False ), # link isn't available as bare keyword - ] + tests = ( + ('file /foo,', False), + ('file rw,', False), + ('file -> bar,', False), + ('file Px -> bar,', False), + ('/foo bar,', False), + ('dbus /foo,', False), + ('link /foo,', False), # missing '-> /target' + ('link -> /bar,', False), # missing path + ('/foo -> bar link,', False), # link has to be leading keyword + ('link,', False), # link isn't available as bare keyword + ) def _run_test(self, rawrule, expected): self.assertFalse(FileRule.match(rawrule)) + class FileTestParseFromLog(FileTest): def test_file_from_log(self): parser = ReadLog('', '', '') @@ -169,125 +177,129 @@ class FileTestParseFromLog(FileTest): 'family': None, 'protocol': None, 'sock_type': None, + 'class': None, }) - #FileRule# path, perms, exec_perms, target, owner, file_keyword, leading_perms - #obj = FileRule(parsed_event['name'], parsed_event['denied_mask'], None, FileRule.ALL, False, False, False, ) - obj = FileRule(parsed_event['name'], 'r', None, FileRule.ALL, False, False, False, ) + # FileRule path, perms, exec_perms, target, owner, file_keyword, leading_perms + # obj = FileRule(parsed_event['name'], parsed_event['denied_mask'], None, FileRule.ALL, False, False, False,) + obj = FileRule(parsed_event['name'], 'r', None, FileRule.ALL, False, False, False,) # XXX handle things like '::r' # XXX split off exec perms? - # audit allow deny comment path all_paths perms all? exec_perms target all? owner file keyword leading perms - expected = exp(False, False, False, '', '/bin/dash', False, {'r'}, False, None, None, True, False, False, False ) + # audit allow deny comment path all_paths perms all? exec_perms target all? owner file keyword leading perms + expected = exp(False, False, False, '', '/bin/dash', False, {'r'}, False, None, None, True, False, False, False) self._compare_obj(obj, expected) self.assertEqual(obj.get_raw(1), ' /bin/dash r,') + # TODO: add logparser example for link event class FileFromInit(FileTest): - tests = [ + tests = ( - #FileRule# path, perms, exec_perms, target, owner, file_keyword, leading_perms - (FileRule( '/foo', 'rw', None, FileRule.ALL, False, False, False, audit=True, deny=True ), - #exp# audit allow deny comment path all_paths perms all? exec_perms target all? owner file keyword leading perms - exp( True, False, True, '', '/foo', False, {'r', 'w'}, False, None, None, True, False, False, False )), + # path, perms, exec_perms, target, owner, file_keyword, leading_perms + (FileRule('/foo', 'rw', None, FileRule.ALL, False, False, False, audit=True, deny=True), + # audit allow deny comment path all_paths perms all? exec_perms target all? owner file keyword leading perms + exp(True, False, True, '', '/foo', False, {'r', 'w'}, False, None, None, True, False, False, False)), - #FileRule# path, perms, exec_perms, target, owner, file_keyword, leading_perms - (FileRule( '/foo', None, 'Pix', 'bar_prof', True, True, True, allow_keyword=True ), - #exp# audit allow deny comment path all_paths perms all? exec_perms target all? owner file keyword leading perms - exp( False, True, False, '', '/foo', False, set(), False, 'Pix', 'bar_prof', False, True, True, True )), + # path, perms, exec_perms, target, owner, file_keyword, leading_perms + (FileRule('/foo', None, 'Pix', 'bar_prof', True, True, True, allow_keyword=True), + # audit allow deny comment path all_paths perms all? exec_perms target all? owner file keyword leading perms + exp(False, True, False, '', '/foo', False, set(), False, 'Pix', 'bar_prof', False, True, True, True)), - #FileRule# path, perms, exec_perms, target, owner, file_keyword, leading_perms - (FileRule( '/foo', {'link', 'subset'}, None, '/bar', False, False, True, audit=True, deny=True ), - #exp# audit allow deny comment path all_paths perms all? exec_perms target all? owner file keyword leading perms - exp( True, False, True, '', '/foo', False, {'link', 'subset'}, False, None, '/bar', False, False, False, True )), + # path, perms, exec_perms, target, owner, file_keyword, leading_perms + (FileRule('/foo', {'link', 'subset'}, None, '/bar', False, False, True, audit=True, deny=True), + # audit allow deny comment path all_paths perms all? exec_perms target all? owner file keyword leading perms + exp(True, False, True, '', '/foo', False, {'link', 'subset'}, False, None, '/bar', False, False, False, True)), - ] + ) def _run_test(self, obj, expected): self._compare_obj(obj, expected) + class InvalidFileInit(AATest): - tests = [ - #FileRule# path, perms, exec_perms, target, owner, file_keyword, leading_perms + tests = ( + # path, perms, exec_perms, target, owner, file_keyword, leading_perms expected exception # empty fields - ( ( '', 'rw', 'ix', '/bar', False, False, False ), AppArmorBug), - # OK ( '/foo', '', 'ix', '/bar', False, False, False ), AppArmorBug), - ( ( '/foo', 'rw', '', '/bar', False, False, False ), AppArmorBug), - ( ( '/foo', 'rw', 'ix', '', False, False, False ), AppArmorBug), + ( ('', 'rw', 'ix', '/bar', False, False, False), AppArmorBug), + # OK ('/foo', '', 'ix', '/bar', False, False, False), AppArmorBug), + ( ('/foo', 'rw', '', '/bar', False, False, False), AppArmorBug), + ( ('/foo', 'rw', 'ix', '', False, False, False), AppArmorBug), # whitespace fields - ( ( ' ', 'rw', 'ix', '/bar', False, False, False ), AppArmorBug), - ( ( '/foo', ' ', 'ix', '/bar', False, False, False ), AppArmorException), - ( ( '/foo', 'rw', ' ', '/bar', False, False, False ), AppArmorBug), - ( ( '/foo', 'rw', 'ix', ' ', False, False, False ), AppArmorBug), + ( (' ', 'rw', 'ix', '/bar', False, False, False), AppArmorBug), + ( ('/foo', ' ', 'ix', '/bar', False, False, False), AppArmorException), + ( ('/foo', 'rw', ' ', '/bar', False, False, False), AppArmorBug), + ( ('/foo', 'rw', 'ix', ' ', False, False, False), AppArmorBug), # wrong type - dict() - ( ( dict(), 'rw', 'ix', '/bar', False, False, False ), AppArmorBug), - ( ( '/foo', dict(), 'ix', '/bar', False, False, False ), AppArmorBug), - ( ( '/foo', 'rw', dict(), '/bar', False, False, False ), AppArmorBug), - ( ( '/foo', 'rw', 'ix', dict(), False, False, False ), AppArmorBug), - ( ( '/foo', 'rw', 'ix', '/bar', dict(), False, False ), AppArmorBug), - ( ( '/foo', 'rw', 'ix', '/bar', False, dict(), False ), AppArmorBug), - ( ( '/foo', 'rw', 'ix', '/bar', False, False, dict() ), AppArmorBug), + ( (dict(), 'rw', 'ix', '/bar', False, False, False), AppArmorBug), + ( ('/foo', dict(), 'ix', '/bar', False, False, False), AppArmorBug), + ( ('/foo', 'rw', dict(), '/bar', False, False, False), AppArmorBug), + ( ('/foo', 'rw', 'ix', dict(), False, False, False), AppArmorBug), + ( ('/foo', 'rw', 'ix', '/bar', dict(), False, False), AppArmorBug), + ( ('/foo', 'rw', 'ix', '/bar', False, dict(), False), AppArmorBug), + ( ('/foo', 'rw', 'ix', '/bar', False, False, dict()), AppArmorBug), # wrong type - None - ( ( None, 'rw', 'ix', '/bar', False, False, False ), AppArmorBug), - # OK ( '/foo', None, 'ix', '/bar', False, False, False ), AppArmorBug), - # OK ( '/foo', 'rw', None, '/bar', False, False, False ), AppArmorBug), - ( ( '/foo', 'rw', 'ix', None, False, False, False ), AppArmorBug), - ( ( '/foo', 'rw', 'ix', '/bar', None, False, False ), AppArmorBug), - ( ( '/foo', 'rw', 'ix', '/bar', False, None, False ), AppArmorBug), - ( ( '/foo', 'rw', 'ix', '/bar', False, False, None ), AppArmorBug), + ( (None, 'rw', 'ix', '/bar', False, False, False), AppArmorBug), + # OK ('/foo', None, 'ix', '/bar', False, False, False), AppArmorBug), + # OK ('/foo', 'rw', None, '/bar', False, False, False), AppArmorBug), + ( ('/foo', 'rw', 'ix', None, False, False, False), AppArmorBug), + ( ('/foo', 'rw', 'ix', '/bar', None, False, False), AppArmorBug), + ( ('/foo', 'rw', 'ix', '/bar', False, None, False), AppArmorBug), + ( ('/foo', 'rw', 'ix', '/bar', False, False, None), AppArmorBug), # misc - ( ( '/foo', 'rwa', 'ix', '/bar', False, False, False ), AppArmorException), # 'r' and 'a' conflict - ( ( '/foo', None, 'rw', '/bar', False, False, False ), AppArmorBug), # file perms in exec perms parameter - ( ( '/foo', 'ix', None, '/bar', False, False, False ), AppArmorBug), # exec perms in file perms parameter - ( ( 'foo', 'rw', 'ix', '/bar', False, False, False ), AppArmorException), # path doesn't start with / - ( ( '/foo', 'rb', 'ix', '/bar', False, False, False ), AppArmorException), # invalid file mode 'b' (str) - ( ( '/foo', {'b'}, 'ix', '/bar', False, False, False ), AppArmorBug), # invalid file mode 'b' (str) - ( ( '/foo', 'rw', 'ax', '/bar', False, False, False ), AppArmorBug), # invalid exec mode 'ax' - ( ( '/foo', 'rw', 'x', '/bar', False, False, False ), AppArmorException), # plain 'x' is only allowed in deny rules - ( ( FileRule.ALL, FileRule.ALL, None, '/bar', False, False, False ), AppArmorBug), # plain 'file,' doesn't allow exec target + ( ('/foo', 'rwa', 'ix', '/bar', False, False, False), AppArmorException), # 'r' and 'a' conflict + ( ('/foo', None, 'rw', '/bar', False, False, False), AppArmorBug), # file perms in exec perms parameter + ( ('/foo', 'ix', None, '/bar', False, False, False), AppArmorBug), # exec perms in file perms parameter + ( ('foo', 'rw', 'ix', '/bar', False, False, False), AppArmorException), # path doesn't start with / + ( ('/foo', 'rb', 'ix', '/bar', False, False, False), AppArmorException), # invalid file mode 'b' (str) + ( ('/foo', {'b'}, 'ix', '/bar', False, False, False), AppArmorBug), # invalid file mode 'b' (str) + ( ('/foo', 'rw', 'ax', '/bar', False, False, False), AppArmorBug), # invalid exec mode 'ax' + ( ('/foo', 'rw', 'x', '/bar', False, False, False), AppArmorException), # plain 'x' is only allowed in deny rules + ( (FileRule.ALL, FileRule.ALL, None, '/bar', False, False, False), AppArmorBug), # plain 'file,' doesn't allow exec target # link rules - ( ( None, {'link'}, None, None, False, False, False, ), AppArmorBug), # missing path and target - ( ( '/foo', {'link'}, None, None, False, False, False, ), AppArmorBug), # missing target - ( ( None, {'link'}, None, '/bar', False, False, False, ), AppArmorBug), # missing path - ( ( '/foo', {'subset'}, None, '/bar', False, False, False, ), AppArmorBug), # subset without link - ( ( '/foo', {'link'}, 'ix', '/bar', False, False, False, ), AppArmorBug), # link rule with exec perms - ( ( '/foo', {'link', 'subset'}, 'ix', '/bar', False, False, False, ), AppArmorBug), # link subset rule with exec perms - ] + ( (None, {'link'}, None, None, False, False, False), AppArmorBug), # missing path and target + ( ('/foo', {'link'}, None, None, False, False, False), AppArmorBug), # missing target + ( ( None, {'link'}, None, '/bar', False, False, False), AppArmorBug), # missing path + ( ('/foo', {'subset'}, None, '/bar', False, False, False), AppArmorBug), # subset without link + ( ('/foo', {'link'}, 'ix', '/bar', False, False, False), AppArmorBug), # link rule with exec perms + ( ('/foo', {'link', 'subset'}, 'ix', '/bar', False, False, False), AppArmorBug), # link subset rule with exec perms + ) def _run_test(self, params, expected): with self.assertRaises(expected): - FileRule(params[0], params[1], params[2], params[3], params[4], params[5], params[6]) + FileRule(*params) def test_missing_params_1(self): with self.assertRaises(TypeError): - FileRule( '/foo') + FileRule('/foo') def test_missing_params_2(self): with self.assertRaises(TypeError): - FileRule( '/foo', 'rw') + FileRule('/foo', 'rw') def test_missing_params_3(self): with self.assertRaises(TypeError): - FileRule( '/foo', 'rw', 'ix') + FileRule('/foo', 'rw', 'ix') def test_missing_params_4(self): with self.assertRaises(TypeError): - FileRule( '/foo', 'rw', 'ix', '/bar') + FileRule('/foo', 'rw', 'ix', '/bar') def test_deny_ix(self): with self.assertRaises(AppArmorException): - FileRule( '/foo', 'rw', 'ix', '/bar', False, False, False, deny=True) + FileRule('/foo', 'rw', 'ix', '/bar', False, False, False, deny=True) + class InvalidFileTest(AATest): def _check_invalid_rawrule(self, rawrule): @@ -304,10 +316,11 @@ class InvalidFileTest(AATest): def test_invalid_non_FileRule(self): self._check_invalid_rawrule('signal,') # not a file rule + class BrokenFileTest(AATest): def AASetup(self): - #FileRule# path, perms, exec_perms, target, owner, file_keyword, leading_perms - self.obj = FileRule('/foo', 'rw', 'ix', '/bar', False, False, False) + # path, perms, exec_perms, target, owner, file_keyword, leading_perms + self.obj = FileRule('/foo', 'rw', 'ix', '/bar', False, False, False) def test_empty_data_1(self): self.obj.path = '' @@ -340,6 +353,7 @@ class BrokenFileTest(AATest): with self.assertRaises(AppArmorBug): self.obj.get_clean(1) + class FileGlobTest(AATest): def _run_test(self, params, expected): exp_can_glob, exp_can_glob_ext, exp_rule_glob, exp_rule_glob_ext = expected @@ -362,86 +376,88 @@ class FileGlobTest(AATest): # These tests are meant to ensure AARE integration in FileRule works as expected. # test-aare.py has more comprehensive globbing tests. - tests = [ - # rule can glob can glob_ext globbed rule globbed_ext rule - ('/foo/bar r,', (True, True, '/foo/* r,', '/foo/bar r,')), - ('/foo/* r,', (True, True, '/** r,', '/foo/* r,')), - ('/foo/bar.xy r,', (True, True, '/foo/* r,', '/foo/*.xy r,')), - ('/foo/*.xy r,', (True, True, '/foo/* r,', '/**.xy r,')), - ('file,', (False, False, 'file,', 'file,')), # bare 'file,' rules can't be globbed - ('link /a/b -> /c,', (True, True, 'link /a/* -> /c,', 'link /a/b -> /c,')), - ] + tests = ( + # rule can glob can glob_ext globbed rule globbed_ext rule + ('/foo/bar r,', (True, True, '/foo/* r,', '/foo/bar r,')), + ('/foo/* r,', (True, True, '/** r,', '/foo/* r,')), + ('/foo/bar.xy r,', (True, True, '/foo/* r,', '/foo/*.xy r,')), + ('/foo/*.xy r,', (True, True, '/foo/* r,', '/**.xy r,')), + ('file,', (False, False, 'file,', 'file,')), # bare 'file,' rules can't be globbed + ('link /a/b -> /c,', (True, True, 'link /a/* -> /c,', 'link /a/b -> /c,')), + ) + class WriteFileTest(AATest): def _run_test(self, rawrule, expected): - self.assertTrue(FileRule.match(rawrule), 'FileRule.match() failed') - obj = FileRule.parse(rawrule) - clean = obj.get_clean() - raw = obj.get_raw() - - self.assertEqual(expected.strip(), clean, 'unexpected clean rule') - self.assertEqual(rawrule.strip(), raw, 'unexpected raw rule') - - tests = [ - # raw rule clean rule - ('file,' , 'file,'), - (' file , # foo ' , 'file, # foo'), - (' audit file /foo r,' , 'audit file /foo r,'), - (' audit file /foo lwr,' , 'audit file /foo rwl,'), - (' audit file /foo Pxrm -> bar,' , 'audit file /foo mrPx -> bar,'), - (' deny file /foo r,' , 'deny file /foo r,'), - (' deny file /foo wr,' , 'deny file /foo rw,'), - (' allow file /foo Pxrm -> bar,' , 'allow file /foo mrPx -> bar,'), - (' deny owner /foo r,' , 'deny owner /foo r,'), - (' deny owner /foo wr,' , 'deny owner /foo rw,'), - (' allow owner /foo Pxrm -> bar,' , 'allow owner /foo mrPx -> bar,'), - (' /foo r,' , '/foo r,'), - (' /foo lwr,' , '/foo rwl,'), - (' /foo Pxrm -> bar,' , '/foo mrPx -> bar,'), + self.assertTrue(FileRule.match(rawrule), 'FileRule.match() failed') + obj = FileRule.parse(rawrule) + clean = obj.get_clean() + raw = obj.get_raw() + + self.assertEqual(expected.strip(), clean, 'unexpected clean rule') + self.assertEqual(rawrule.strip(), raw, 'unexpected raw rule') + + tests = ( + # raw rule clean rule + ('file,', 'file,'), + (' file , # foo ', 'file, # foo'), + (' audit file /foo r,', 'audit file /foo r,'), + (' audit file /foo lwr,', 'audit file /foo rwl,'), + (' audit file /foo Pxrm -> bar,', 'audit file /foo mrPx -> bar,'), + (' deny file /foo r,', 'deny file /foo r,'), + (' deny file /foo wr,', 'deny file /foo rw,'), + (' allow file /foo Pxrm -> bar,', 'allow file /foo mrPx -> bar,'), + (' deny owner /foo r,', 'deny owner /foo r,'), + (' deny owner /foo wr,', 'deny owner /foo rw,'), + (' allow owner /foo Pxrm -> bar,', 'allow owner /foo mrPx -> bar,'), + (' /foo r,', '/foo r,'), + (' /foo lwr,', '/foo rwl,'), + (' /foo Pxrm -> bar,', '/foo mrPx -> bar,'), # with leading permissions - (' audit file r /foo,' , 'audit file r /foo,'), - (' audit file lwr /foo,' , 'audit file rwl /foo,'), - (' audit file Pxrm /foo -> bar,' , 'audit file mrPx /foo -> bar,'), - (' deny file r /foo,' , 'deny file r /foo,'), - (' deny file wr /foo ,' , 'deny file rw /foo,'), - (' allow file Pxmr /foo -> bar,' , 'allow file mrPx /foo -> bar,'), - (' deny owner r /foo ,' , 'deny owner r /foo,'), - (' deny owner wr /foo ,' , 'deny owner rw /foo,'), - (' allow owner Pxrm /foo -> bar,' , 'allow owner mrPx /foo -> bar,'), - (' r /foo ,' , 'r /foo,'), - (' klwr /foo ,' , 'rwlk /foo,'), - (' Pxrm /foo -> bar,' , 'mrPx /foo -> bar,'), + (' audit file r /foo,', 'audit file r /foo,'), + (' audit file lwr /foo,', 'audit file rwl /foo,'), + (' audit file Pxrm /foo -> bar,', 'audit file mrPx /foo -> bar,'), + (' deny file r /foo,', 'deny file r /foo,'), + (' deny file wr /foo ,', 'deny file rw /foo,'), + (' allow file Pxmr /foo -> bar,', 'allow file mrPx /foo -> bar,'), + (' deny owner r /foo ,', 'deny owner r /foo,'), + (' deny owner wr /foo ,', 'deny owner rw /foo,'), + (' allow owner Pxrm /foo -> bar,', 'allow owner mrPx /foo -> bar,'), + (' r /foo ,', 'r /foo,'), + (' klwr /foo ,', 'rwlk /foo,'), + (' Pxrm /foo -> bar,', 'mrPx /foo -> bar,'), # link rules - (' link /foo -> /bar,' , 'link /foo -> /bar,'), - (' audit deny owner link subset /foo -> /bar,' , 'audit deny owner link subset /foo -> /bar,'), - (' link subset /foo -> /bar,' , 'link subset /foo -> /bar,') - ] + (' link /foo -> /bar,', 'link /foo -> /bar,'), + (' audit deny owner link subset /foo -> /bar,', 'audit deny owner link subset /foo -> /bar,'), + (' link subset /foo -> /bar,', 'link subset /foo -> /bar,') + ) def test_write_manually_1(self): - #FileRule# path, perms, exec_perms, target, owner, file_keyword, leading_perms - obj = FileRule( '/foo', 'rw', 'Px', '/bar', False, True, False, allow_keyword=True) + # path, perms, exec_perms, target, owner, file_keyword, leading_perms + obj = FileRule('/foo', 'rw', 'Px', '/bar', False, True, False, allow_keyword=True) - expected = ' allow file /foo rwPx -> /bar,' + expected = ' allow file /foo rwPx -> /bar,' - self.assertEqual(expected, obj.get_clean(2), 'unexpected clean rule') - self.assertEqual(expected, obj.get_raw(2), 'unexpected raw rule') + self.assertEqual(expected, obj.get_clean(2), 'unexpected clean rule') + self.assertEqual(expected, obj.get_raw(2), 'unexpected raw rule') def test_write_manually_2(self): - #FileRule# path, perms, exec_perms, target, owner, file_keyword, leading_perms - obj = FileRule( '/foo', 'rw', 'x', FileRule.ALL, True, False, True, deny=True) + # path, perms, exec_perms, target, owner, file_keyword, leading_perms + obj = FileRule('/foo', 'rw', 'x', FileRule.ALL, True, False, True, deny=True) - expected = ' deny owner rwx /foo,' + expected = ' deny owner rwx /foo,' - self.assertEqual(expected, obj.get_clean(2), 'unexpected clean rule') - self.assertEqual(expected, obj.get_raw(2), 'unexpected raw rule') + self.assertEqual(expected, obj.get_clean(2), 'unexpected clean rule') + self.assertEqual(expected, obj.get_raw(2), 'unexpected raw rule') def test_write_any_exec(self): - obj = FileRule( '/foo', 'rw', FileRule.ANY_EXEC,'/bar', False, False, False) + obj = FileRule('/foo', 'rw', FileRule.ANY_EXEC, '/bar', False, False, False) with self.assertRaises(AppArmorBug): obj.get_clean() + class FileCoveredTest(AATest): def _run_test(self, param, expected): obj = FileRule.parse(self.rule) @@ -455,230 +471,239 @@ class FileCoveredTest(AATest): self.assertEqual(obj.is_covered(check_obj), expected[2], 'Mismatch in is_covered, expected %s' % expected[2]) self.assertEqual(obj.is_covered(check_obj, True, True), expected[3], 'Mismatch in is_covered/exact, expected %s' % expected[3]) + class FileCoveredTest_01(FileCoveredTest): rule = 'file /foo r,' +tests = ( + # rule equal strict equal covered covered exact + ('file /foo r,', (True, True, True, True)), + ('file /foo r ,', (True, False, True, True)), + ('allow file /foo r,', (True, False, True, True)), + ('allow /foo r, # comment', (True, False, True, True)), + ('allow owner /foo r,', (False, False, True, True)), + ('/foo r -> bar,', (False, False, True, True)), + ('file r /foo,', (True, False, True, True)), + ('allow file r /foo,', (True, False, True, True)), + ('allow r /foo, # comment', (True, False, True, True)), + ('allow owner r /foo,', (False, False, True, True)), + ('r /foo -> bar,', (False, False, True, True)), + ('file,', (False, False, False, False)), + ('file /foo w,', (False, False, False, False)), + ('file /foo rw,', (False, False, False, False)), + ('file /bar r,', (False, False, False, False)), + ('audit /foo r,', (False, False, False, False)), + ('audit file,', (False, False, False, False)), + ('audit deny /foo r,', (False, False, False, False)), + ('deny file /foo r,', (False, False, False, False)), + ('/foo rPx,', (False, False, False, False)), + ('/foo Pxr,', (False, False, False, False)), + ('/foo Px,', (False, False, False, False)), + ('/foo ix,', (False, False, False, False)), + ('/foo ix -> bar,', (False, False, False, False)), + ('/foo rPx -> bar,', (False, False, False, False)), +) - tests = [ - # rule equal strict equal covered covered exact - ('file /foo r,' , [ True , True , True , True ]), - ('file /foo r ,' , [ True , False , True , True ]), - ('allow file /foo r,' , [ True , False , True , True ]), - ('allow /foo r, # comment' , [ True , False , True , True ]), - ('allow owner /foo r,' , [ False , False , True , True ]), - ('/foo r -> bar,' , [ False , False , True , True ]), - ('file r /foo,' , [ True , False , True , True ]), - ('allow file r /foo,' , [ True , False , True , True ]), - ('allow r /foo, # comment' , [ True , False , True , True ]), - ('allow owner r /foo,' , [ False , False , True , True ]), - ('r /foo -> bar,' , [ False , False , True , True ]), - ('file,' , [ False , False , False , False ]), - ('file /foo w,' , [ False , False , False , False ]), - ('file /foo rw,' , [ False , False , False , False ]), - ('file /bar r,' , [ False , False , False , False ]), - ('audit /foo r,' , [ False , False , False , False ]), - ('audit file,' , [ False , False , False , False ]), - ('audit deny /foo r,' , [ False , False , False , False ]), - ('deny file /foo r,' , [ False , False , False , False ]), - ('/foo rPx,' , [ False , False , False , False ]), - ('/foo Pxr,' , [ False , False , False , False ]), - ('/foo Px,' , [ False , False , False , False ]), - ('/foo ix,' , [ False , False , False , False ]), - ('/foo ix -> bar,' , [ False , False , False , False ]), - ('/foo rPx -> bar,' , [ False , False , False , False ]), - ] class FileCoveredTest_02(FileCoveredTest): rule = 'audit /foo r,' - tests = [ - # rule equal strict equal covered covered exact - ('file /foo r,' , [ False , False , True , False ]), - ('allow file /foo r,' , [ False , False , True , False ]), - ('allow /foo r, # comment' , [ False , False , True , False ]), - ('allow owner /foo r,' , [ False , False , True , False ]), - ('/foo r -> bar,' , [ False , False , True , False ]), - ('file r /foo,' , [ False , False , True , False ]), - ('allow file r /foo,' , [ False , False , True , False ]), - ('allow r /foo, # comment' , [ False , False , True , False ]), - ('allow owner r /foo,' , [ False , False , True , False ]), - ('r /foo -> bar,' , [ False , False , True , False ]), # XXX exact - ('file,' , [ False , False , False , False ]), - ('file /foo w,' , [ False , False , False , False ]), - ('file /foo rw,' , [ False , False , False , False ]), - ('file /bar r,' , [ False , False , False , False ]), - ('audit /foo r,' , [ True , True , True , True ]), - ('audit file,' , [ False , False , False , False ]), - ('audit deny /foo r,' , [ False , False , False , False ]), - ('deny file /foo r,' , [ False , False , False , False ]), - ('/foo rPx,' , [ False , False , False , False ]), - ('/foo Pxr,' , [ False , False , False , False ]), - ('/foo Px,' , [ False , False , False , False ]), - ('/foo ix,' , [ False , False , False , False ]), - ('/foo ix -> bar,' , [ False , False , False , False ]), - ('/foo rPx -> bar,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ('file /foo r,', (False, False, True, False)), + ('allow file /foo r,', (False, False, True, False)), + ('allow /foo r, # comment', (False, False, True, False)), + ('allow owner /foo r,', (False, False, True, False)), + ('/foo r -> bar,', (False, False, True, False)), + ('file r /foo,', (False, False, True, False)), + ('allow file r /foo,', (False, False, True, False)), + ('allow r /foo, # comment', (False, False, True, False)), + ('allow owner r /foo,', (False, False, True, False)), + ('r /foo -> bar,', (False, False, True, False)), # XXX exact + ('file,', (False, False, False, False)), + ('file /foo w,', (False, False, False, False)), + ('file /foo rw,', (False, False, False, False)), + ('file /bar r,', (False, False, False, False)), + ('audit /foo r,', (True, True, True, True)), + ('audit file,', (False, False, False, False)), + ('audit deny /foo r,', (False, False, False, False)), + ('deny file /foo r,', (False, False, False, False)), + ('/foo rPx,', (False, False, False, False)), + ('/foo Pxr,', (False, False, False, False)), + ('/foo Px,', (False, False, False, False)), + ('/foo ix,', (False, False, False, False)), + ('/foo ix -> bar,', (False, False, False, False)), + ('/foo rPx -> bar,', (False, False, False, False)), + ) + class FileCoveredTest_03(FileCoveredTest): rule = '/foo mrwPx,' - tests = [ - # rule equal strict equal covered covered exact - ('file /foo r,' , [ False , False , True , True ]), - ('allow file /foo r,' , [ False , False , True , True ]), - ('allow /foo r, # comment' , [ False , False , True , True ]), - ('allow owner /foo r,' , [ False , False , True , True ]), - ('/foo r -> bar,' , [ False , False , True , True ]), - ('file r /foo,' , [ False , False , True , True ]), - ('allow file r /foo,' , [ False , False , True , True ]), - ('allow r /foo, # comment' , [ False , False , True , True ]), - ('allow owner r /foo,' , [ False , False , True , True ]), - ('r /foo -> bar,' , [ False , False , True , True ]), - ('file,' , [ False , False , False , False ]), - ('file /foo w,' , [ False , False , True , True ]), - ('file /foo rw,' , [ False , False , True , True ]), - ('file /bar r,' , [ False , False , False , False ]), - ('audit /foo r,' , [ False , False , False , False ]), - ('audit file,' , [ False , False , False , False ]), - ('audit deny /foo r,' , [ False , False , False , False ]), - ('deny file /foo r,' , [ False , False , False , False ]), - ('/foo mrwPx,' , [ True , True , True , True ]), - ('/foo wPxrm,' , [ True , False , True , True ]), - ('/foo rm,' , [ False , False , True , True ]), - ('/foo Px,' , [ False , False , True , True ]), - ('/foo ix,' , [ False , False , False , False ]), - ('/foo ix -> bar,' , [ False , False , False , False ]), - ('/foo mrwPx -> bar,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ('file /foo r,', (False, False, True, True)), + ('allow file /foo r,', (False, False, True, True)), + ('allow /foo r, # comment', (False, False, True, True)), + ('allow owner /foo r,', (False, False, True, True)), + ('/foo r -> bar,', (False, False, True, True)), + ('file r /foo,', (False, False, True, True)), + ('allow file r /foo,', (False, False, True, True)), + ('allow r /foo, # comment', (False, False, True, True)), + ('allow owner r /foo,', (False, False, True, True)), + ('r /foo -> bar,', (False, False, True, True)), + ('file,', (False, False, False, False)), + ('file /foo w,', (False, False, True, True)), + ('file /foo rw,', (False, False, True, True)), + ('file /bar r,', (False, False, False, False)), + ('audit /foo r,', (False, False, False, False)), + ('audit file,', (False, False, False, False)), + ('audit deny /foo r,', (False, False, False, False)), + ('deny file /foo r,', (False, False, False, False)), + ('/foo mrwPx,', (True, True, True, True)), + ('/foo wPxrm,', (True, False, True, True)), + ('/foo rm,', (False, False, True, True)), + ('/foo Px,', (False, False, True, True)), + ('/foo ix,', (False, False, False, False)), + ('/foo ix -> bar,', (False, False, False, False)), + ('/foo mrwPx -> bar,', (False, False, False, False)), + ) + class FileCoveredTest_04(FileCoveredTest): rule = '/foo mrwPx -> bar,' - tests = [ - # rule equal strict equal covered covered exact - ('file /foo r,' , [ False , False , True , True ]), - ('allow file /foo r,' , [ False , False , True , True ]), - ('allow /foo r, # comment' , [ False , False , True , True ]), - ('allow owner /foo r,' , [ False , False , True , True ]), - ('/foo r -> bar,' , [ False , False , True , True ]), - ('file r /foo,' , [ False , False , True , True ]), - ('allow file r /foo,' , [ False , False , True , True ]), - ('allow r /foo, # comment' , [ False , False , True , True ]), - ('allow owner r /foo,' , [ False , False , True , True ]), - ('r /foo -> bar,' , [ False , False , True , True ]), - ('file,' , [ False , False , False , False ]), - ('file /foo w,' , [ False , False , True , True ]), - ('file /foo rw,' , [ False , False , True , True ]), - ('file /bar r,' , [ False , False , False , False ]), - ('audit /foo r,' , [ False , False , False , False ]), - ('audit file,' , [ False , False , False , False ]), - ('audit deny /foo r,' , [ False , False , False , False ]), - ('deny file /foo r,' , [ False , False , False , False ]), - ('/foo mrwPx,' , [ False , False , False , False ]), - ('/foo wPxrm,' , [ False , False , False , False ]), - ('/foo rm,' , [ False , False , True , True ]), - ('/foo Px,' , [ False , False , False , False ]), - ('/foo ix,' , [ False , False , False , False ]), - ('/foo ix -> bar,' , [ False , False , False , False ]), - ('/foo mrwPx -> bar,' , [ True , True , True , True ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ('file /foo r,', (False, False, True, True)), + ('allow file /foo r,', (False, False, True, True)), + ('allow /foo r, # comment', (False, False, True, True)), + ('allow owner /foo r,', (False, False, True, True)), + ('/foo r -> bar,', (False, False, True, True)), + ('file r /foo,', (False, False, True, True)), + ('allow file r /foo,', (False, False, True, True)), + ('allow r /foo, # comment', (False, False, True, True)), + ('allow owner r /foo,', (False, False, True, True)), + ('r /foo -> bar,', (False, False, True, True)), + ('file,', (False, False, False, False)), + ('file /foo w,', (False, False, True, True)), + ('file /foo rw,', (False, False, True, True)), + ('file /bar r,', (False, False, False, False)), + ('audit /foo r,', (False, False, False, False)), + ('audit file,', (False, False, False, False)), + ('audit deny /foo r,', (False, False, False, False)), + ('deny file /foo r,', (False, False, False, False)), + ('/foo mrwPx,', (False, False, False, False)), + ('/foo wPxrm,', (False, False, False, False)), + ('/foo rm,', (False, False, True, True)), + ('/foo Px,', (False, False, False, False)), + ('/foo ix,', (False, False, False, False)), + ('/foo ix -> bar,', (False, False, False, False)), + ('/foo mrwPx -> bar,', (True, True, True, True)), + ) + class FileCoveredTest_05(FileCoveredTest): rule = 'file,' - tests = [ - # rule equal strict equal covered covered exact - ('file /foo r,' , [ False , False , True , True ]), - ('allow file /foo r,' , [ False , False , True , True ]), - ('allow /foo r, # comment' , [ False , False , True , True ]), - ('allow owner /foo r,' , [ False , False , True , True ]), - ('/foo r -> bar,' , [ False , False , True , True ]), - ('file r /foo,' , [ False , False , True , True ]), - ('allow file r /foo,' , [ False , False , True , True ]), - ('allow r /foo, # comment' , [ False , False , True , True ]), - ('allow owner r /foo,' , [ False , False , True , True ]), - ('r /foo -> bar,' , [ False , False , True , True ]), - ('file,' , [ True , True , True , True ]), - ('file /foo w,' , [ False , False , True , True ]), - ('file /foo rw,' , [ False , False , True , True ]), - ('file /bar r,' , [ False , False , True , True ]), - ('audit /foo r,' , [ False , False , False , False ]), - ('audit file,' , [ False , False , False , False ]), - ('audit deny /foo r,' , [ False , False , False , False ]), - ('deny file /foo r,' , [ False , False , False , False ]), - ('/foo mrwPx,' , [ False , False , False , False ]), - ('/foo wPxrm,' , [ False , False , False , False ]), - ('/foo rm,' , [ False , False , True , True ]), - ('/foo Px,' , [ False , False , False , False ]), - ('/foo ix,' , [ False , False , False , False ]), - ('/foo ix -> bar,' , [ False , False , False , False ]), - ('/foo mrwPx -> bar,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ('file /foo r,', (False, False, True, True)), + ('allow file /foo r,', (False, False, True, True)), + ('allow /foo r, # comment', (False, False, True, True)), + ('allow owner /foo r,', (False, False, True, True)), + ('/foo r -> bar,', (False, False, True, True)), + ('file r /foo,', (False, False, True, True)), + ('allow file r /foo,', (False, False, True, True)), + ('allow r /foo, # comment', (False, False, True, True)), + ('allow owner r /foo,', (False, False, True, True)), + ('r /foo -> bar,', (False, False, True, True)), + ('file,', (True, True, True, True)), + ('file /foo w,', (False, False, True, True)), + ('file /foo rw,', (False, False, True, True)), + ('file /bar r,', (False, False, True, True)), + ('audit /foo r,', (False, False, False, False)), + ('audit file,', (False, False, False, False)), + ('audit deny /foo r,', (False, False, False, False)), + ('deny file /foo r,', (False, False, False, False)), + ('/foo mrwPx,', (False, False, False, False)), + ('/foo wPxrm,', (False, False, False, False)), + ('/foo rm,', (False, False, True, True)), + ('/foo Px,', (False, False, False, False)), + ('/foo ix,', (False, False, False, False)), + ('/foo ix -> bar,', (False, False, False, False)), + ('/foo mrwPx -> bar,', (False, False, False, False)), + ) + class FileCoveredTest_06(FileCoveredTest): rule = 'deny /foo w,' - tests = [ - # rule equal strict equal covered covered exact - ('/foo w,' , [ False , False , False , False ]), - ('/foo a,' , [ False , False , False , False ]), - ('deny /foo w,' , [ True , True , True , True ]), - ('deny /foo a,' , [ False , False , True , True ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ('/foo w,', (False, False, False, False)), + ('/foo a,', (False, False, False, False)), + ('deny /foo w,', (True, True, True, True)), + ('deny /foo a,', (False, False, True, True)), + ) + class FileCoveredTest_07(FileCoveredTest): rule = '/foo w,' - tests = [ - # rule equal strict equal covered covered exact - ('/foo w,' , [ True , True , True , True ]), - ('/foo a,' , [ False , False , True , True ]), - ('deny /foo w,' , [ False , False , False , False ]), - ('deny /foo a,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ('/foo w,', (True, True, True, True)), + ('/foo a,', (False, False, True, True)), + ('deny /foo w,', (False, False, False, False)), + ('deny /foo a,', (False, False, False, False)), + ) + class FileCoveredTest_08(FileCoveredTest): rule = 'link /foo -> /bar,' - tests = [ - # rule equal strict equal covered covered exact - ('link /foo -> /bar,' , [ True , True , True , True ]), - ('link /asdf -> /bar,' , [ False , False , False , False ]), - ('link /foo -> /asdf,' , [ False , False , False , False ]), - ('deny link /foo -> /bar,' , [ False , False , False , False ]), - ('deny link /foo -> /bar,' , [ False , False , False , False ]), - ('link subset /foo -> /bar,' , [ False , False , True , True ]), # subset makes the rule more strict - # ('/foo l -> /bar,' , [ ? , ? , ? , ? ]), # TODO - # ('l /foo -> /bar,' , [ ? , ? , ? , ? ]), # TODO - ] + tests = ( + # rule equal strict equal covered covered exact + ('link /foo -> /bar,', (True, True, True, True)), + ('link /asdf -> /bar,', (False, False, False, False)), + ('link /foo -> /asdf,', (False, False, False, False)), + ('deny link /foo -> /bar,', (False, False, False, False)), + ('deny link /foo -> /bar,', (False, False, False, False)), + ('link subset /foo -> /bar,', (False, False, True, True)), # subset makes the rule more strict + # ('/foo l -> /bar,', (?, ?, ?, ?)), # TODO + # ('l /foo -> /bar,', (?, ?, ?, ?)), # TODO + ) + class FileCoveredTest_09(FileCoveredTest): rule = 'link subset /foo -> /bar,' - tests = [ - # rule equal strict equal covered covered exact - ('link subset /foo -> /bar,' , [ True , True , True , True ]), - ('link subset /asdf -> /bar,' , [ False , False , False , False ]), - ('link subset /foo -> /asdf,' , [ False , False , False , False ]), - ('deny link subset /foo -> /bar,' , [ False , False , False , False ]), - ('deny link subset /foo -> /bar,' , [ False , False , False , False ]), - ('link /foo -> /bar,' , [ False , False , False , False ]), # no subset means more permissions - # ('/foo l -> /bar,' , [ ? , ? , ? , ? ]), # TODO - # ('l /foo -> /bar,' , [ ? , ? , ? , ? ]), # TODO - ] + tests = ( + # rule equal strict equal covered covered exact + ('link subset /foo -> /bar,', (True, True, True, True)), + ('link subset /asdf -> /bar,', (False, False, False, False)), + ('link subset /foo -> /asdf,', (False, False, False, False)), + ('deny link subset /foo -> /bar,', (False, False, False, False)), + ('deny link subset /foo -> /bar,', (False, False, False, False)), + ('link /foo -> /bar,', (False, False, False, False)), # no subset means more permissions + # ('/foo l -> /bar,', (?, ?, ?, ?)), # TODO + # ('l /foo -> /bar,', (?, ?, ?, ?)), # TODO + ) + class FileCoveredTest_ManualOrInvalid(AATest): def AASetup(self): - #FileRule# path, perms, exec_perms, target, owner, file_keyword, leading_perms - self.obj = FileRule( '/foo', 'rw', 'ix', '/bar', False, False, False) - self.testobj = FileRule( '/foo', 'rw', 'ix', '/bar', False, False, False) + # path, perms, exec_perms, target, owner, file_keyword, leading_perms + self.obj = FileRule('/foo', 'rw', 'ix', '/bar', False, False, False) + self.testobj = FileRule('/foo', 'rw', 'ix', '/bar', False, False, False) def test_covered_owner_1(self): # testobj with 'owner' - self.testobj = FileRule( '/foo', 'rw', 'ix', '/bar', True, False, False) + self.testobj = FileRule('/foo', 'rw', 'ix', '/bar', True, False, False) self.assertTrue(self.obj.is_covered(self.testobj)) def test_covered_owner_2(self): # obj with 'owner' - self.obj = FileRule( '/foo', 'rw', 'ix', '/bar', True, False, False) + self.obj = FileRule('/foo', 'rw', 'ix', '/bar', True, False, False) self.assertFalse(self.obj.is_covered(self.testobj)) def test_equal_all_perms(self): @@ -687,60 +712,60 @@ class FileCoveredTest_ManualOrInvalid(AATest): def test_equal_file_keyword(self): # testobj with file_keyword - self.testobj = FileRule( '/foo', 'rw', 'ix', '/bar', False, True, False) + self.testobj = FileRule('/foo', 'rw', 'ix', '/bar', False, True, False) self.assertTrue(self.obj.is_equal(self.testobj, strict=False)) self.assertFalse(self.obj.is_equal(self.testobj, strict=True)) def test_equal_file_leading_perms(self): # testobj with leading_perms - self.testobj = FileRule( '/foo', 'rw', 'ix', '/bar', False, False, True) + self.testobj = FileRule('/foo', 'rw', 'ix', '/bar', False, False, True) self.assertTrue(self.obj.is_equal(self.testobj, strict=False)) self.assertFalse(self.obj.is_equal(self.testobj, strict=True)) def test_covered_anyperm_1(self): - self.obj = FileRule( '/foo', 'rw', None, '/bar', False, False, False) - self.testobj = FileRule( '/foo', 'rw', FileRule.ANY_EXEC, '/bar', False, False, False) + self.obj = FileRule('/foo', 'rw', None, '/bar', False, False, False) + self.testobj = FileRule('/foo', 'rw', FileRule.ANY_EXEC, '/bar', False, False, False) self.assertFalse(self.obj.is_covered(self.testobj)) self.assertFalse(self.obj.is_equal(self.testobj, strict=False)) self.assertFalse(self.obj.is_equal(self.testobj, strict=True)) def test_covered_anyperm_2(self): - self.testobj = FileRule( '/foo', 'rw', FileRule.ANY_EXEC,'/bar', False, False, False) + self.testobj = FileRule('/foo', 'rw', FileRule.ANY_EXEC, '/bar', False, False, False) self.assertTrue(self.obj.is_covered(self.testobj)) self.assertFalse(self.obj.is_equal(self.testobj, strict=False)) self.assertFalse(self.obj.is_equal(self.testobj, strict=True)) def test_covered_anyperm_3(self): # make sure a different exec target gets ignored with ANY_EXEC - self.testobj = FileRule( '/foo', 'rw', FileRule.ANY_EXEC, '/xyz', False, False, False) + self.testobj = FileRule('/foo', 'rw', FileRule.ANY_EXEC, '/xyz', False, False, False) self.assertTrue(self.obj.is_covered(self.testobj)) self.assertFalse(self.obj.is_equal(self.testobj, strict=False)) self.assertFalse(self.obj.is_equal(self.testobj, strict=True)) def test_covered_anyperm_4(self): # make sure a different exec target gets ignored with ANY_EXEC - self.testobj = FileRule( '/foo', 'rw', FileRule.ANY_EXEC, FileRule.ALL, False, False, False) + self.testobj = FileRule('/foo', 'rw', FileRule.ANY_EXEC, FileRule.ALL, False, False, False) self.assertTrue(self.obj.is_covered(self.testobj)) self.assertFalse(self.obj.is_equal(self.testobj, strict=False)) self.assertFalse(self.obj.is_equal(self.testobj, strict=True)) def test_covered_anyperm_5(self): # even with ANY_EXEC, a different link target causes a mismatch - self.testobj = FileRule( '/foo', 'rwl', FileRule.ANY_EXEC, '/xyz', False, False, False) + self.testobj = FileRule('/foo', 'rwl', FileRule.ANY_EXEC, '/xyz', False, False, False) self.assertFalse(self.obj.is_covered(self.testobj)) self.assertFalse(self.obj.is_equal(self.testobj, strict=False)) self.assertFalse(self.obj.is_equal(self.testobj, strict=True)) def test_covered_anyperm_6(self): # even with ANY_EXEC, a different link target causes a mismatch - self.testobj = FileRule( '/foo', 'rwl', FileRule.ANY_EXEC, FileRule.ALL, False, False, False) + self.testobj = FileRule('/foo', 'rwl', FileRule.ANY_EXEC, FileRule.ALL, False, False, False) self.assertFalse(self.obj.is_covered(self.testobj)) self.assertFalse(self.obj.is_equal(self.testobj, strict=False)) self.assertFalse(self.obj.is_equal(self.testobj, strict=True)) def test_covered_anyperm_7(self): - self.obj = FileRule( '/foo', 'rw', 'x', '/bar', False, False, False, deny=True) - self.testobj = FileRule( '/foo', 'rw', FileRule.ANY_EXEC,'/bar', False, False, False) + self.obj = FileRule('/foo', 'rw', 'x', '/bar', False, False, False, deny=True) + self.testobj = FileRule('/foo', 'rw', FileRule.ANY_EXEC, '/bar', False, False, False) self.assertFalse(self.obj.is_covered(self.testobj)) self.assertTrue(self.obj.is_covered(self.testobj, check_allow_deny=False)) self.assertFalse(self.obj.is_equal(self.testobj, strict=False)) @@ -781,59 +806,62 @@ class FileCoveredTest_ManualOrInvalid(AATest): with self.assertRaises(AppArmorBug): obj.is_equal(testobj) + class FileSeverityTest(AATest): - tests = [ - ('/usr/bin/whatis ix,', 5), - ('/etc ix,', 'unknown'), - ('/dev/doublehit ix,', 0), - ('/dev/doublehit rix,', 4), - ('/dev/doublehit rwix,', 8), - ('/dev/tty10 rwix,', 9), - ('/var/adm/foo/** rix,', 3), - ('/etc/apparmor/** r,', 6), - ('/etc/** r,', 'unknown'), - ('/usr/foo@bar r,', 'unknown'), # filename containing @ - ('/home/foo@bar rw,', 6), # filename containing @ - ('file,', 'unknown'), # bare file rule XXX should return maximum severity - ] + tests = ( + ('/usr/bin/whatis ix,', 5), + ('/etc ix,', 'unknown'), + ('/dev/doublehit ix,', 0), + ('/dev/doublehit rix,', 4), + ('/dev/doublehit rwix,', 8), + ('/dev/tty10 rwix,', 9), + ('/var/adm/foo/** rix,', 3), + ('/etc/apparmor/** r,', 6), + ('/etc/** r,', 'unknown'), + ('/usr/foo@bar r,', 'unknown'), # filename containing @ + ('/home/foo@bar rw,', 6), # filename containing @ + ('file,', 'unknown'), # bare file rule XXX should return maximum severity + ) def _run_test(self, params, expected): - sev_db = severity.Severity('severity.db', 'unknown') + sev_db = severity.Severity('../severity.db', 'unknown') obj = FileRule.parse(params) rank = obj.severity(sev_db) self.assertEqual(rank, expected) + class FileLogprofHeaderTest(AATest): - tests = [ + tests = ( # log event old perms ALL / owner - (['file,', set(), set() ], [ _('Path'), _('ALL'), _('New Mode'), _('ALL') ]), - (['/foo r,', set(), set() ], [ _('Path'), '/foo', _('New Mode'), 'r' ]), - (['file /bar Px -> foo,', set(), set() ], [ _('Path'), '/bar', _('New Mode'), 'Px -> foo' ]), - (['deny file,', set(), set() ], [_('Qualifier'), 'deny', _('Path'), _('ALL'), _('New Mode'), _('ALL') ]), - (['allow file /baz rwk,', set(), set() ], [_('Qualifier'), 'allow', _('Path'), '/baz', _('New Mode'), 'rwk' ]), - (['audit file /foo mr,', set(), set() ], [_('Qualifier'), 'audit', _('Path'), '/foo', _('New Mode'), 'mr' ]), - (['audit deny /foo wk,', set(), set() ], [_('Qualifier'), 'audit deny', _('Path'), '/foo', _('New Mode'), 'wk' ]), - (['owner file /foo ix,', set(), set() ], [ _('Path'), '/foo', _('New Mode'), 'owner ix' ]), - (['audit deny file /foo rlx -> /baz,', set(), set() ], [_('Qualifier'), 'audit deny', _('Path'), '/foo', _('New Mode'), 'rlx -> /baz' ]), - (['/foo rw,', set('r'), set() ], [ _('Path'), '/foo', _('Old Mode'), _('r'), _('New Mode'), _('rw') ]), - (['/foo rw,', set(), set('rw') ], [ _('Path'), '/foo', _('Old Mode'), _('owner rw'), _('New Mode'), _('rw') ]), - (['/foo mrw,', set('r'), set('k') ], [ _('Path'), '/foo', _('Old Mode'), _('r + owner k'), _('New Mode'), _('mrw') ]), - (['/foo mrw,', set('r'), set('rk') ], [ _('Path'), '/foo', _('Old Mode'), _('r + owner k'), _('New Mode'), _('mrw') ]), - (['link /foo -> /bar,', set(), set() ], [ _('Path'), '/foo', _('New Mode'), 'link -> /bar' ]), - (['link subset /foo -> /bar,', set(), set() ], [ _('Path'), '/foo', _('New Mode'), 'link subset -> /bar' ]), - ] + (('file,', set(), set()), [ _('Path'), _('ALL'), _('New Mode'), _('ALL')]), + (('/foo r,', set(), set()), [ _('Path'), '/foo', _('New Mode'), 'r']), + (('file /bar Px -> foo,', set(), set()), [ _('Path'), '/bar', _('New Mode'), 'Px -> foo']), + (('deny file,', set(), set()), [_('Qualifier'), 'deny', _('Path'), _('ALL'), _('New Mode'), _('ALL')]), + (('allow file /baz rwk,', set(), set()), [_('Qualifier'), 'allow', _('Path'), '/baz', _('New Mode'), 'rwk']), + (('audit file /foo mr,', set(), set()), [_('Qualifier'), 'audit', _('Path'), '/foo', _('New Mode'), 'mr']), + (('audit deny /foo wk,', set(), set()), [_('Qualifier'), 'audit deny', _('Path'), '/foo', _('New Mode'), 'wk']), + (('owner file /foo ix,', set(), set()), [ _('Path'), '/foo', _('New Mode'), 'owner ix']), + (('audit deny file /foo rlx -> /baz,', set(), set()), [_('Qualifier'), 'audit deny', _('Path'), '/foo', _('New Mode'), 'rlx -> /baz']), + (('/foo rw,', set('r'), set()), [ _('Path'), '/foo', _('Old Mode'), _('r'), _('New Mode'), _('rw')]), + (('/foo rw,', set(), set('rw')), [ _('Path'), '/foo', _('Old Mode'), _('owner rw'), _('New Mode'), _('rw')]), + (('/foo mrw,', set('r'), set('k')), [ _('Path'), '/foo', _('Old Mode'), _('r + owner k'), _('New Mode'), _('mrw')]), + (('/foo mrw,', set('r'), set('rk')), [ _('Path'), '/foo', _('Old Mode'), _('r + owner k'), _('New Mode'), _('mrw')]), + (('link /foo -> /bar,', set(), set()), [ _('Path'), '/foo', _('New Mode'), 'link -> /bar']), + (('link subset /foo -> /bar,', set(), set()), [ _('Path'), '/foo', _('New Mode'), 'link subset -> /bar']), + ) def _run_test(self, params, expected): - obj = FileRule._parse(params[0]) + obj = FileRule.parse(params[0]) if params[1] or params[2]: - obj.original_perms = {'allow': { 'all': params[1], 'owner': params[2]}} + obj.original_perms = {'allow': {'all': params[1], 'owner': params[2]}} self.assertEqual(obj.logprof_header(), expected) def test_empty_original_perms(self): - obj = FileRule._parse('/foo rw,') - obj.original_perms = {'allow': { 'all': set(), 'owner': set()}} + obj = FileRule.parse('/foo rw,') + obj.original_perms = {'allow': {'all': set(), 'owner': set()}} self.assertEqual(obj.logprof_header(), [_('Path'), '/foo', _('New Mode'), _('rw')]) + class FileEditHeaderTest(AATest): def _run_test(self, params, expected): rule_obj = FileRule.parse(params) @@ -841,11 +869,11 @@ class FileEditHeaderTest(AATest): prompt, path_to_edit = rule_obj.edit_header() self.assertEqual(path_to_edit, expected) - tests = [ - ('/foo/bar/baz r,', '/foo/bar/baz'), - ('/foo/**/baz r,', '/foo/**/baz'), - ('link /foo/** -> /bar,', '/foo/**'), - ] + tests = ( + ('/foo/bar/baz r,', '/foo/bar/baz'), + ('/foo/**/baz r,', '/foo/**/baz'), + ('link /foo/** -> /bar,', '/foo/**'), + ) def test_edit_header_bare_file(self): rule_obj = FileRule.parse('file,') @@ -853,6 +881,7 @@ class FileEditHeaderTest(AATest): with self.assertRaises(AppArmorBug): rule_obj.edit_header() + class FileValidateAndStoreEditTest(AATest): def _run_test(self, params, expected): rule_obj = FileRule('/foo/bar/baz', 'r', None, FileRule.ALL, False, False, False, log_event=True) @@ -862,14 +891,14 @@ class FileValidateAndStoreEditTest(AATest): rule_obj.store_edit(params) self.assertEqual(rule_obj.get_raw(), '%s r,' % params) - tests = [ - # edited path match - ('/foo/bar/baz', True), - ('/foo/bar/*', True), - ('/foo/bar/???', True), - ('/foo/xy**', False), - ('/foo/bar/baz/', False), - ] + tests = ( + # edited path match + ('/foo/bar/baz', True), + ('/foo/bar/*', True), + ('/foo/bar/???', True), + ('/foo/xy**', False), + ('/foo/bar/baz/', False), + ) def test_validate_not_a_path(self): rule_obj = FileRule.parse('/foo/bar/baz r,') @@ -891,7 +920,6 @@ class FileValidateAndStoreEditTest(AATest): rule_obj.store_edit('/foo/bar') - ## --- tests for FileRuleset --- # class FileRulesTest(AATest): @@ -945,7 +973,7 @@ class FileRulesTest(AATest): ' /foo Px,', ' /bar Cx -> bar_child ,', ' deny /asdf w,', - '', + '', ] expected_clean = [ @@ -953,7 +981,7 @@ class FileRulesTest(AATest): '', ' /bar Cx -> bar_child,', ' /foo Px,', - '', + '', ] deleted = 0 @@ -980,14 +1008,14 @@ class FileRulesTest(AATest): ' /foo/baz rw,', ' /foo/baz rwk,', ' /foo/* r,', - '', + '', ] expected_clean = [ ' /foo/* r,', ' /foo/baz rw,', ' /foo/baz rwk,', - '', + '', ] deleted = 0 @@ -1004,25 +1032,26 @@ class FileRulesTest(AATest): self.assertEqual(expected_clean, ruleset.get_clean(1)) -#class FileDeleteTest(AATest): -# pass +# class FileDeleteTest(AATest): +# pass + class FileGetRulesForPath(AATest): - tests = [ - # path audit deny expected - (('/etc/foo/dovecot.conf', False, False), ['/etc/foo/* r,', '/etc/foo/dovecot.conf rw,', '']), - (('/etc/foo/foo.conf', False, False), ['/etc/foo/* r,', '']), - (('/etc/foo/dovecot-database.conf.ext', False, False), ['/etc/foo/* r,', '/etc/foo/dovecot-database.conf.ext w,', '']), - (('/etc/foo/auth.d/authfoo.conf', False, False), ['/etc/foo/{auth,conf}.d/*.conf r,','/etc/foo/{auth,conf}.d/authfoo.conf w,', '']), - (('/etc/foo/dovecot-deny.conf', False, False), ['deny /etc/foo/dovecot-deny.conf r,', '', '/etc/foo/* r,', '']), - (('/foo/bar', False, True ), [ ]), - (('/etc/foo/dovecot-deny.conf', False, True ), ['deny /etc/foo/dovecot-deny.conf r,', '']), - (('/etc/foo/foo.conf', False, True ), [ ]), - (('/etc/foo/owner.conf', False, False), ['/etc/foo/* r,', 'owner /etc/foo/owner.conf w,', '']), - ] + tests = ( + # path audit deny expected + (('/etc/foo/dovecot.conf', False, False), ['/etc/foo/* r,', '/etc/foo/dovecot.conf rw,', '']), + (('/etc/foo/foo.conf', False, False), ['/etc/foo/* r,', '']), + (('/etc/foo/dovecot-database.conf.ext', False, False), ['/etc/foo/* r,', '/etc/foo/dovecot-database.conf.ext w,', '']), + (('/etc/foo/auth.d/authfoo.conf', False, False), ['/etc/foo/{auth,conf}.d/*.conf r,', '/etc/foo/{auth,conf}.d/authfoo.conf w,', '']), + (('/etc/foo/dovecot-deny.conf', False, False), ['deny /etc/foo/dovecot-deny.conf r,', '', '/etc/foo/* r,', '']), + (('/foo/bar', False, True), [ ]), + (('/etc/foo/dovecot-deny.conf', False, True), ['deny /etc/foo/dovecot-deny.conf r,', '']), + (('/etc/foo/foo.conf', False, True), [ ]), + (('/etc/foo/owner.conf', False, False), ['/etc/foo/* r,', 'owner /etc/foo/owner.conf w,', '']), + ) def _run_test(self, params, expected): - rules = [ + rules = ( '/etc/foo/* r,', '/etc/foo/dovecot.conf rw,', '/etc/foo/{auth,conf}.d/*.conf r,', @@ -1030,32 +1059,32 @@ class FileGetRulesForPath(AATest): '/etc/foo/dovecot-database.conf.ext w,', 'owner /etc/foo/owner.conf w,', 'deny /etc/foo/dovecot-deny.conf r,', - ] + ) ruleset = FileRuleset() for rule in rules: ruleset.add(FileRule.parse(rule)) - matching = ruleset.get_rules_for_path(params[0], params[1], params[2]) + matching = ruleset.get_rules_for_path(*params) self. assertEqual(matching.get_clean(), expected) class FileGetPermsForPath_1(AATest): - tests = [ + tests = ( # path audit deny expected - (('/etc/foo/dovecot.conf', False, False), {'allow': {'all': {'r', 'w'}, 'owner': set() }, 'deny': {'all': set(), 'owner': set() }, 'paths': {'/etc/foo/*', '/etc/foo/dovecot.conf' } }), - (('/etc/foo/foo.conf', False, False), {'allow': {'all': {'r' }, 'owner': set() }, 'deny': {'all': set(), 'owner': set() }, 'paths': {'/etc/foo/*' } }), - (('/etc/foo/dovecot-database.conf.ext', False, False), {'allow': {'all': {'r', 'w'}, 'owner': set() }, 'deny': {'all': set(), 'owner': set() }, 'paths': {'/etc/foo/*', '/etc/foo/dovecot-database.conf.ext' } }), - (('/etc/foo/auth.d/authfoo.conf', False, False), {'allow': {'all': {'r', 'w'}, 'owner': set() }, 'deny': {'all': set(), 'owner': set() }, 'paths': {'/etc/foo/{auth,conf}.d/*.conf', '/etc/foo/{auth,conf}.d/authfoo.conf' } }), - (('/etc/foo/dovecot-deny.conf', False, False), {'allow': {'all': {'r' }, 'owner': set() }, 'deny': {'all': {'r' }, 'owner': set() }, 'paths': {'/etc/foo/*', '/etc/foo/dovecot-deny.conf' } }), - (('/foo/bar', False, True ), {'allow': {'all': set(), 'owner': set() }, 'deny': {'all': set(), 'owner': set() }, 'paths': set() }), - (('/etc/foo/dovecot-deny.conf', False, True ), {'allow': {'all': set(), 'owner': set() }, 'deny': {'all': {'r' }, 'owner': set() }, 'paths': {'/etc/foo/dovecot-deny.conf' } }), - (('/etc/foo/foo.conf', False, True ), {'allow': {'all': set(), 'owner': set() }, 'deny': {'all': set(), 'owner': set() }, 'paths': set() }), - (('/usr/lib/dovecot/config', False, False), {'allow': {'all': set(), 'owner': set() }, 'deny': {'all': set(), 'owner': set() }, 'paths': set() }), # exec perms are not honored by get_perms_for_path() - ] + (('/etc/foo/dovecot.conf', False, False), {'allow': {'all': {'r', 'w'}, 'owner': set()}, 'deny': {'all': set(), 'owner': set()}, 'paths': {'/etc/foo/*', '/etc/foo/dovecot.conf'}}), + (('/etc/foo/foo.conf', False, False), {'allow': {'all': {'r'}, 'owner': set()}, 'deny': {'all': set(), 'owner': set()}, 'paths': {'/etc/foo/*'}}), + (('/etc/foo/dovecot-database.conf.ext', False, False), {'allow': {'all': {'r', 'w'}, 'owner': set()}, 'deny': {'all': set(), 'owner': set()}, 'paths': {'/etc/foo/*', '/etc/foo/dovecot-database.conf.ext'}}), + (('/etc/foo/auth.d/authfoo.conf', False, False), {'allow': {'all': {'r', 'w'}, 'owner': set()}, 'deny': {'all': set(), 'owner': set()}, 'paths': {'/etc/foo/{auth,conf}.d/*.conf', '/etc/foo/{auth,conf}.d/authfoo.conf'}}), + (('/etc/foo/dovecot-deny.conf', False, False), {'allow': {'all': {'r'}, 'owner': set()}, 'deny': {'all': {'r'}, 'owner': set()}, 'paths': {'/etc/foo/*', '/etc/foo/dovecot-deny.conf'}}), + (('/foo/bar', False, True), {'allow': {'all': set(), 'owner': set()}, 'deny': {'all': set(), 'owner': set()}, 'paths': set()}), + (('/etc/foo/dovecot-deny.conf', False, True), {'allow': {'all': set(), 'owner': set()}, 'deny': {'all': {'r'}, 'owner': set()}, 'paths': {'/etc/foo/dovecot-deny.conf'}}), + (('/etc/foo/foo.conf', False, True), {'allow': {'all': set(), 'owner': set()}, 'deny': {'all': set(), 'owner': set()}, 'paths': set()}), + (('/usr/lib/dovecot/config', False, False), {'allow': {'all': set(), 'owner': set()}, 'deny': {'all': set(), 'owner': set()}, 'paths': set()}), # exec perms are not honored by get_perms_for_path() + ) def _run_test(self, params, expected): - rules = [ + rules = ( '/etc/foo/* r,', '/etc/foo/dovecot.conf rw,', '/etc/foo/{auth,conf}.d/*.conf r,', @@ -1063,33 +1092,34 @@ class FileGetPermsForPath_1(AATest): '/etc/foo/dovecot-database.conf.ext w,', 'deny /etc/foo/dovecot-deny.conf r,', '/usr/lib/dovecot/config ix,', - ] + ) ruleset = FileRuleset() for rule in rules: ruleset.add(FileRule.parse(rule)) - perms = ruleset.get_perms_for_path(params[0], params[1], params[2]) + perms = ruleset.get_perms_for_path(*params) self. assertEqual(perms, expected) + class FileGetPermsForPath_2(AATest): - tests = [ + tests = ( # path audit deny expected - (('/etc/foo/dovecot.conf', False, False), {'allow': {'all': FileRule.ALL, 'owner': set() }, 'deny': {'all': FileRule.ALL, 'owner': set() }, 'paths': {'/etc/foo/*', '/etc/foo/dovecot.conf' } }), - (('/etc/foo/dovecot.conf', True, False), {'allow': {'all': {'r', 'w'}, 'owner': set() }, 'deny': {'all': set(), 'owner': set() }, 'paths': {'/etc/foo/dovecot.conf' } }), - (('/etc/foo/foo.conf', False, False), {'allow': {'all': FileRule.ALL, 'owner': set() }, 'deny': {'all': FileRule.ALL, 'owner': set() }, 'paths': {'/etc/foo/*' } }), - (('/etc/foo/dovecot-database.conf.ext', False, False), {'allow': {'all': FileRule.ALL, 'owner': set() }, 'deny': {'all': FileRule.ALL, 'owner': set() }, 'paths': {'/etc/foo/*', '/etc/foo/dovecot-database.conf.ext' } }), - (('/etc/foo/auth.d/authfoo.conf', False, False), {'allow': {'all': FileRule.ALL, 'owner': set() }, 'deny': {'all': FileRule.ALL, 'owner': set() }, 'paths': {'/etc/foo/{auth,conf}.d/*.conf', '/etc/foo/{auth,conf}.d/authfoo.conf' } }), - (('/etc/foo/auth.d/authfoo.conf', True, False), {'allow': {'all': {'w' }, 'owner': set() }, 'deny': {'all': set(), 'owner': set() }, 'paths': {'/etc/foo/{auth,conf}.d/authfoo.conf' } }), - (('/etc/foo/dovecot-deny.conf', False, False), {'allow': {'all': FileRule.ALL, 'owner': set() }, 'deny': {'all': FileRule.ALL, 'owner': set() }, 'paths': {'/etc/foo/*', '/etc/foo/dovecot-deny.conf' } }), - (('/foo/bar', False, True ), {'allow': {'all': set(), 'owner': set() }, 'deny': {'all': FileRule.ALL, 'owner': set() }, 'paths': set() }), - (('/etc/foo/dovecot-deny.conf', False, True ), {'allow': {'all': set(), 'owner': set() }, 'deny': {'all': FileRule.ALL, 'owner': set() }, 'paths': {'/etc/foo/dovecot-deny.conf' } }), - (('/etc/foo/foo.conf', False, True ), {'allow': {'all': set(), 'owner': set() }, 'deny': {'all': FileRule.ALL, 'owner': set() }, 'paths': set() }), - # (('/etc/foo/owner.conf', False, True ), {'allow': {'all': set(), 'owner': {'w'} }, 'deny': {'all': FileRule.ALL, 'owner': set() }, 'paths': {'/etc/foo/owner.conf' } }), # XXX doen't work yet - ] + (('/etc/foo/dovecot.conf', False, False), {'allow': {'all': FileRule.ALL, 'owner': set()}, 'deny': {'all': FileRule.ALL, 'owner': set()}, 'paths': {'/etc/foo/*', '/etc/foo/dovecot.conf'}}), + (('/etc/foo/dovecot.conf', True, False), {'allow': {'all': {'r', 'w'}, 'owner': set()}, 'deny': {'all': set(), 'owner': set()}, 'paths': {'/etc/foo/dovecot.conf'}}), + (('/etc/foo/foo.conf', False, False), {'allow': {'all': FileRule.ALL, 'owner': set()}, 'deny': {'all': FileRule.ALL, 'owner': set()}, 'paths': {'/etc/foo/*'}}), + (('/etc/foo/dovecot-database.conf.ext', False, False), {'allow': {'all': FileRule.ALL, 'owner': set()}, 'deny': {'all': FileRule.ALL, 'owner': set()}, 'paths': {'/etc/foo/*', '/etc/foo/dovecot-database.conf.ext'}}), + (('/etc/foo/auth.d/authfoo.conf', False, False), {'allow': {'all': FileRule.ALL, 'owner': set()}, 'deny': {'all': FileRule.ALL, 'owner': set()}, 'paths': {'/etc/foo/{auth,conf}.d/*.conf', '/etc/foo/{auth,conf}.d/authfoo.conf'}}), + (('/etc/foo/auth.d/authfoo.conf', True, False), {'allow': {'all': {'w'}, 'owner': set()}, 'deny': {'all': set(), 'owner': set()}, 'paths': {'/etc/foo/{auth,conf}.d/authfoo.conf'}}), + (('/etc/foo/dovecot-deny.conf', False, False), {'allow': {'all': FileRule.ALL, 'owner': set()}, 'deny': {'all': FileRule.ALL, 'owner': set()}, 'paths': {'/etc/foo/*', '/etc/foo/dovecot-deny.conf'}}), + (('/foo/bar', False, True), {'allow': {'all': set(), 'owner': set()}, 'deny': {'all': FileRule.ALL, 'owner': set()}, 'paths': set()}), + (('/etc/foo/dovecot-deny.conf', False, True), {'allow': {'all': set(), 'owner': set()}, 'deny': {'all': FileRule.ALL, 'owner': set()}, 'paths': {'/etc/foo/dovecot-deny.conf'}}), + (('/etc/foo/foo.conf', False, True), {'allow': {'all': set(), 'owner': set()}, 'deny': {'all': FileRule.ALL, 'owner': set()}, 'paths': set()}), + # (('/etc/foo/owner.conf', False, True), {'allow': {'all': set(), 'owner': {'w'}}, 'deny': {'all': FileRule.ALL, 'owner': set()}, 'paths': {'/etc/foo/owner.conf'}}), # XXX doesn't work yet + ) def _run_test(self, params, expected): - rules = [ + rules = ( '/etc/foo/* r,', 'audit /etc/foo/dovecot.conf rw,', '/etc/foo/{auth,conf}.d/*.conf r,', @@ -1099,29 +1129,30 @@ class FileGetPermsForPath_2(AATest): 'file,', 'owner /etc/foo/owner.conf w,', 'deny file,', - ] + ) ruleset = FileRuleset() for rule in rules: ruleset.add(FileRule.parse(rule)) - perms = ruleset.get_perms_for_path(params[0], params[1], params[2]) + perms = ruleset.get_perms_for_path(*params) self. assertEqual(perms, expected) + class FileGetExecRulesForPath_1(AATest): - tests = [ - ('/bin/foo', ['audit /bin/foo ix,', ''] ), - ('/bin/bar', ['deny /bin/bar x,', ''] ), - ('/foo', [] ), - ] + tests = ( + ('/bin/foo', ['audit /bin/foo ix,', '']), + ('/bin/bar', ['deny /bin/bar x,', '']), + ('/foo', []), + ) def _run_test(self, params, expected): - rules = [ + rules = ( '/foo r,', 'audit /bin/foo ix,', '/bin/b* Px,', 'deny /bin/bar x,', - ] + ) ruleset = FileRuleset() for rule in rules: @@ -1131,20 +1162,21 @@ class FileGetExecRulesForPath_1(AATest): matches = perms.get_clean() self. assertEqual(matches, expected) + class FileGetExecRulesForPath_2(AATest): - tests = [ - ('/bin/foo', ['audit /bin/foo ix,', ''] ), - ('/bin/bar', ['deny /bin/bar x,', '', '/bin/b* Px,', ''] ), - ('/foo', [] ), - ] + tests = ( + ('/bin/foo', ['audit /bin/foo ix,', '']), + ('/bin/bar', ['deny /bin/bar x,', '', '/bin/b* Px,', '']), + ('/foo', []), + ) def _run_test(self, params, expected): - rules = [ + rules = ( '/foo r,', 'audit /bin/foo ix,', '/bin/b* Px,', 'deny /bin/bar x,', - ] + ) ruleset = FileRuleset() for rule in rules: @@ -1154,23 +1186,24 @@ class FileGetExecRulesForPath_2(AATest): matches = perms.get_clean() self. assertEqual(matches, expected) + class FileGetExecConflictRules_1(AATest): - tests = [ - ('/bin/foo ix,', ['/bin/foo Px,', ''] ), - ('/bin/bar Px,', ['deny /bin/bar x,', '', '/bin/bar cx,', ''] ), - ('/bin/bar cx,', ['deny /bin/bar x,','',] ), - ('/bin/foo r,', [] ), - ] + tests = ( + ('/bin/foo ix,', ['/bin/foo Px,', '']), + ('/bin/bar Px,', ['deny /bin/bar x,', '', '/bin/bar cx,', '']), + ('/bin/bar cx,', ['deny /bin/bar x,', '']), + ('/bin/foo r,', []), + ) def _run_test(self, params, expected): - rules = [ + rules = ( '/foo r,', 'audit /bin/foo ix,', '/bin/foo Px,', '/bin/b* Px,', '/bin/bar cx,', 'deny /bin/bar x,', - ] + ) ruleset = FileRuleset() for rule in rules: @@ -1181,7 +1214,6 @@ class FileGetExecConflictRules_1(AATest): self. assertEqual(conflicts.get_clean(), expected) - setup_all_loops(__name__) if __name__ == '__main__': unittest.main(verbosity=1) diff --git a/utils/test/test-include.py b/utils/test/test-include.py index a64a39105..6b5d57b25 100644 --- a/utils/test/test-include.py +++ b/utils/test/test-include.py @@ -13,26 +13,27 @@ # # ---------------------------------------------------------------------- -import unittest -from collections import namedtuple -from common_test import AATest, setup_all_loops, write_file - import os import shutil +import unittest +from collections import namedtuple +from apparmor.common import AppArmorBug, AppArmorException +# from apparmor.logparser import ReadLog +from apparmor.rule import BaseRule from apparmor.rule.include import IncludeRule, IncludeRuleset -#from apparmor.rule import BaseRule -from apparmor.common import AppArmorException, AppArmorBug -#from apparmor.logparser import ReadLog from apparmor.translations import init_translation +from common_test import AATest, setup_all_loops, write_file + _ = init_translation() -exp = namedtuple('exp', [ # 'audit', 'allow_keyword', 'deny', - 'comment', - 'path', 'ifexists', 'ismagic']) +exp = namedtuple( + 'exp', ( # 'audit', 'allow_keyword', 'deny', + 'comment', 'path', 'ifexists', 'ismagic')) # --- tests for single IncludeRule --- # + class IncludeTest(AATest): def _compare_obj(self, obj, expected): self.assertEqual(False, obj.allow_keyword) # not supported in include rules, expected to be always False @@ -44,46 +45,47 @@ class IncludeTest(AATest): self.assertEqual(expected.ifexists, obj.ifexists) self.assertEqual(expected.ismagic, obj.ismagic) + class IncludeTestParse(IncludeTest): - tests = [ - # IncludeRule object comment path if exists ismagic + tests = ( + # IncludeRule object comment path if exists ismagic # #include - ('#include <abstractions/base>', exp('', 'abstractions/base', False, True )), # magic path - ('#include <abstractions/base> # comment', exp(' # comment', 'abstractions/base', False, True )), - ('#include<abstractions/base>#comment', exp(' #comment', 'abstractions/base', False, True )), - (' #include <abstractions/base> ', exp('', 'abstractions/base', False, True )), - ('#include "/foo/bar"', exp('', '/foo/bar', False, False)), # absolute path - ('#include "/foo/bar" # comment', exp(' # comment', '/foo/bar', False, False)), - ('#include "/foo/bar"#comment', exp(' #comment', '/foo/bar', False, False)), - (' #include "/foo/bar" ', exp('', '/foo/bar', False, False)), + ('#include <abstractions/base>', exp('', 'abstractions/base', False, True)), # magic path + ('#include <abstractions/base> # comment', exp(' # comment', 'abstractions/base', False, True)), + ('#include<abstractions/base>#comment', exp(' #comment', 'abstractions/base', False, True)), + (' #include <abstractions/base> ', exp('', 'abstractions/base', False, True)), + ('#include "/foo/bar"', exp('', '/foo/bar', False, False)), # absolute path + ('#include "/foo/bar" # comment', exp(' # comment', '/foo/bar', False, False)), + ('#include "/foo/bar"#comment', exp(' #comment', '/foo/bar', False, False)), + (' #include "/foo/bar" ', exp('', '/foo/bar', False, False)), # include (without #) - ('include <abstractions/base>', exp('', 'abstractions/base', False, True )), # magic path - ('include <abstractions/base> # comment', exp(' # comment', 'abstractions/base', False, True )), - ('include<abstractions/base>#comment', exp(' #comment', 'abstractions/base', False, True )), - (' include <abstractions/base> ', exp('', 'abstractions/base', False, True )), - ('include "/foo/bar"', exp('', '/foo/bar', False, False)), # absolute path - ('include "/foo/bar" # comment', exp(' # comment', '/foo/bar', False, False)), - ('include "/foo/bar"#comment', exp(' #comment', '/foo/bar', False, False)), - (' include "/foo/bar" ', exp('', '/foo/bar', False, False)), + ('include <abstractions/base>', exp('', 'abstractions/base', False, True)), # magic path + ('include <abstractions/base> # comment', exp(' # comment', 'abstractions/base', False, True)), + ('include<abstractions/base>#comment', exp(' #comment', 'abstractions/base', False, True)), + (' include <abstractions/base> ', exp('', 'abstractions/base', False, True)), + ('include "/foo/bar"', exp('', '/foo/bar', False, False)), # absolute path + ('include "/foo/bar" # comment', exp(' # comment', '/foo/bar', False, False)), + ('include "/foo/bar"#comment', exp(' #comment', '/foo/bar', False, False)), + (' include "/foo/bar" ', exp('', '/foo/bar', False, False)), # #include if exists - ('#include if exists <abstractions/base>', exp('', 'abstractions/base', True, True )), # magic path - ('#include if exists <abstractions/base> # comment', exp(' # comment', 'abstractions/base', True, True )), - ('#include if exists<abstractions/base>#comment', exp(' #comment', 'abstractions/base', True, True )), - (' #include if exists<abstractions/base> ', exp('', 'abstractions/base', True, True )), - ('#include if exists "/foo/bar"', exp('', '/foo/bar', True, False)), # absolute path - ('#include if exists "/foo/bar" # comment', exp(' # comment', '/foo/bar', True, False)), - ('#include if exists "/foo/bar"#comment', exp(' #comment', '/foo/bar', True, False)), - (' #include if exists "/foo/bar" ', exp('', '/foo/bar', True, False)), + ('#include if exists <abstractions/base>', exp('', 'abstractions/base', True, True)), # magic path + ('#include if exists <abstractions/base> # comment', exp(' # comment', 'abstractions/base', True, True)), + ('#include if exists<abstractions/base>#comment', exp(' #comment', 'abstractions/base', True, True)), + (' #include if exists<abstractions/base> ', exp('', 'abstractions/base', True, True)), + ('#include if exists "/foo/bar"', exp('', '/foo/bar', True, False)), # absolute path + ('#include if exists "/foo/bar" # comment', exp(' # comment', '/foo/bar', True, False)), + ('#include if exists "/foo/bar"#comment', exp(' #comment', '/foo/bar', True, False)), + (' #include if exists "/foo/bar" ', exp('', '/foo/bar', True, False)), # include if exists (without #) - ('include if exists <abstractions/base>', exp('', 'abstractions/base', True, True )), # magic path - ('include if exists <abstractions/base> # comment', exp(' # comment', 'abstractions/base', True, True )), - ('include if exists<abstractions/base>#comment', exp(' #comment', 'abstractions/base', True, True )), - (' include if exists<abstractions/base> ', exp('', 'abstractions/base', True, True )), - ('include if exists "/foo/bar"', exp('', '/foo/bar', True, False)), # absolute path - ('include if exists "/foo/bar" # comment', exp(' # comment', '/foo/bar', True, False)), - ('include if exists "/foo/bar"#comment', exp(' #comment', '/foo/bar', True, False)), - (' include if exists "/foo/bar" ', exp('', '/foo/bar', True, False)), - ] + ('include if exists <abstractions/base>', exp('', 'abstractions/base', True, True)), # magic path + ('include if exists <abstractions/base> # comment', exp(' # comment', 'abstractions/base', True, True)), + ('include if exists<abstractions/base>#comment', exp(' #comment', 'abstractions/base', True, True)), + (' include if exists<abstractions/base> ', exp('', 'abstractions/base', True, True)), + ('include if exists "/foo/bar"', exp('', '/foo/bar', True, False)), # absolute path + ('include if exists "/foo/bar" # comment', exp(' # comment', '/foo/bar', True, False)), + ('include if exists "/foo/bar"#comment', exp(' #comment', '/foo/bar', True, False)), + (' include if exists "/foo/bar" ', exp('', '/foo/bar', True, False)), + ) def _run_test(self, rawrule, expected): self.assertTrue(IncludeRule.match(rawrule)) @@ -91,13 +93,14 @@ class IncludeTestParse(IncludeTest): self.assertEqual(rawrule.strip(), obj.raw_rule) self._compare_obj(obj, expected) + class IncludeTestParseInvalid(IncludeTest): - tests = [ -# (' some #include if exists <abstractions/base>', AppArmorException), -# (' /etc/fstab r,', AppArmorException), -# ('/usr/include r,', AppArmorException), -# ('/include r,', AppArmorException), - ] + tests = ( + # (' some #include if exists <abstractions/base>', AppArmorException), + # (' /etc/fstab r,', AppArmorException), + # ('/usr/include r,', AppArmorException), + # ('/include r,', AppArmorException), + ) def _run_test(self, rawrule, expected): self.assertTrue(IncludeRule.match(rawrule)) # the above invalid rules still match the main regex! @@ -106,35 +109,37 @@ class IncludeTestParseInvalid(IncludeTest): # class IncludeTestParseFromLog(IncludeTest): # we'll never have log events for includes + class IncludeFromInit(IncludeTest): - tests = [ - # IncludeRule object ifexists ismagic comment path ifexists ismagic - (IncludeRule('abstractions/base', False, False) , exp('', 'abstractions/base', False, False )), - (IncludeRule('foo', True, False) , exp('', 'foo', True, False )), - (IncludeRule('bar', False, True) , exp('', 'bar', False, True )), - (IncludeRule('baz', True, True) , exp('', 'baz', True, True )), - (IncludeRule('comment', False, False, comment='# cmt') , exp('# cmt', 'comment', False, False )), - ] + tests = ( + # IncludeRule object ifexists ismagic comment path ifexists ismagic + (IncludeRule('abstractions/base', False, False), exp('', 'abstractions/base', False, False)), + (IncludeRule('foo', True, False), exp('', 'foo', True, False)), + (IncludeRule('bar', False, True), exp('', 'bar', False, True)), + (IncludeRule('baz', True, True), exp('', 'baz', True, True)), + (IncludeRule('comment', False, False, comment='# cmt'), exp('# cmt', 'comment', False, False)), + ) def _run_test(self, obj, expected): self._compare_obj(obj, expected) + class InvalidIncludeInit(AATest): - tests = [ - # init params expected exception - ([False, False, False ] , AppArmorBug), # wrong type for path - (['', False, False ] , AppArmorBug), # empty path - ([None, False, False ] , AppArmorBug), # wrong type for path -# ([' ', False, False ] , AppArmorBug), # whitespace-only path - (['foo', None, False ] , AppArmorBug), # wrong type for ifexists - (['foo', '', False ] , AppArmorBug), # wrong type for ifexists - (['foo', False, None ] , AppArmorBug), # wrong type for ismagic - (['foo', False, '' ] , AppArmorBug), # wrong type for ismagic - ] + tests = ( + # init params expected exception + ((False, False, False), AppArmorBug), # wrong type for path + (('', False, False), AppArmorBug), # empty path + ((None, False, False), AppArmorBug), # wrong type for path + # ((' ', False, False), AppArmorBug), # whitespace-only path + (('foo', None, False), AppArmorBug), # wrong type for ifexists + (('foo', '', False), AppArmorBug), # wrong type for ifexists + (('foo', False, None), AppArmorBug), # wrong type for ismagic + (('foo', False, ''), AppArmorBug), # wrong type for ismagic + ) def _run_test(self, params, expected): with self.assertRaises(expected): - IncludeRule(params[0], params[1], params[2]) + IncludeRule(*params) def test_missing_params_1(self): with self.assertRaises(TypeError): @@ -156,8 +161,9 @@ class InvalidIncludeInit(AATest): with self.assertRaises(AppArmorBug): IncludeRule('foo', False, False, deny=True) + class InvalidIncludeTest(AATest): - def _check_invalid_rawrule(self, rawrule, matches_regex = False): + def _check_invalid_rawrule(self, rawrule, matches_regex=False): obj = None self.assertEqual(IncludeRule.match(rawrule), matches_regex) with self.assertRaises(AppArmorException): @@ -171,12 +177,13 @@ class InvalidIncludeTest(AATest): def test_invalid_non_IncludeRule(self): self._check_invalid_rawrule('dbus,') # not a include rule -# def test_empty_data_1(self): -# obj = IncludeRule('foo', False, False) -# obj.path = '' -# # no path set -# with self.assertRaises(AppArmorBug): -# obj.get_clean(1) + # def test_empty_data_1(self): + # obj = IncludeRule('foo', False, False) + # obj.path = '' + # # no path set + # with self.assertRaises(AppArmorBug): + # obj.get_clean(1) + class WriteIncludeTestAATest(AATest): def _run_test(self, rawrule, expected): @@ -188,45 +195,45 @@ class WriteIncludeTestAATest(AATest): self.assertEqual(expected.strip(), clean, 'unexpected clean rule') self.assertEqual(rawrule.strip(), raw, 'unexpected raw rule') - tests = [ - # raw rule clean rule - (' include <foo> ', 'include <foo>' ), -# (' include foo ', 'include "foo"' ), # several test cases disabled due to implementation restrictions, see re_match_include_parse() -# (' include "foo" ', 'include "foo"' ), -# (' include /foo ', 'include "/foo"' ), - (' include "/foo" ', 'include "/foo"' ), - - (' include <foo> # bar ', 'include <foo> # bar' ), -# (' include foo # bar ', 'include "foo" # bar' ), -# (' include "foo" # bar ', 'include "foo" # bar' ), -# (' include /foo # bar ', 'include "/foo" # bar' ), - (' include "/foo" # bar ', 'include "/foo" # bar' ), - - (' include if exists <foo> ', 'include if exists <foo>' ), -# (' include if exists foo ', 'include if exists "foo"' ), -# (' include if exists "foo" ', 'include if exists "foo"' ), -# (' include if exists /foo ', 'include if exists "/foo"' ), - (' include if exists "/foo" ', 'include if exists "/foo"' ), + tests = ( + # (raw rule, clean rule) + (' include <foo> ', 'include <foo>'), + # (' include foo ', 'include "foo"'), # several test cases disabled due to implementation restrictions, see re_match_include_parse() + # (' include "foo" ', 'include "foo"'), + # (' include /foo ', 'include "/foo"'), + (' include "/foo" ', 'include "/foo"'), + + (' include <foo> # bar ', 'include <foo> # bar'), + # (' include foo # bar ', 'include "foo" # bar'), + # (' include "foo" # bar ', 'include "foo" # bar'), + # (' include /foo # bar ', 'include "/foo" # bar'), + (' include "/foo" # bar ', 'include "/foo" # bar'), + + (' include if exists <foo> ', 'include if exists <foo>'), + # (' include if exists foo ', 'include if exists "foo"'), + # (' include if exists "foo" ', 'include if exists "foo"'), + # (' include if exists /foo ', 'include if exists "/foo"'), + (' include if exists "/foo" ', 'include if exists "/foo"'), # and the same again with #include... - (' #include <foo> ', 'include <foo>' ), -# (' #include foo ', 'include "foo"' ), -# (' #include "foo" ', 'include "foo"' ), -# (' #include /foo ', 'include "/foo"' ), - (' #include "/foo" ', 'include "/foo"' ), - - (' #include <foo> # bar ', 'include <foo> # bar' ), -# (' #include foo # bar ', 'include "foo" # bar' ), -# (' #include "foo" # bar ', 'include "foo" # bar' ), -# (' #include /foo # bar ', 'include "/foo" # bar' ), - (' #include "/foo" # bar ', 'include "/foo" # bar' ), - - (' #include if exists <foo> ', 'include if exists <foo>' ), -# (' #include if exists foo ', 'include if exists "foo"' ), -# (' #include if exists "foo" ', 'include if exists "foo"' ), -# (' #include if exists /foo ', 'include if exists "/foo"' ), - (' #include if exists "/foo" ', 'include if exists "/foo"' ), - ] + (' #include <foo> ', 'include <foo>'), + # (' #include foo ', 'include "foo"'), + # (' #include "foo" ', 'include "foo"'), + # (' #include /foo ', 'include "/foo"'), + (' #include "/foo" ', 'include "/foo"'), + + (' #include <foo> # bar ', 'include <foo> # bar'), + # (' #include foo # bar ', 'include "foo" # bar'), + # (' #include "foo" # bar ', 'include "foo" # bar'), + # (' #include /foo # bar ', 'include "/foo" # bar'), + (' #include "/foo" # bar ', 'include "/foo" # bar'), + + (' #include if exists <foo> ', 'include if exists <foo>'), + # (' #include if exists foo ', 'include if exists "foo"'), + # (' #include if exists "foo" ', 'include if exists "foo"'), + # (' #include if exists /foo ', 'include if exists "/foo"'), + (' #include if exists "/foo" ', 'include if exists "/foo"'), + ) def test_write_manually(self): obj = IncludeRule('abs/foo', False, True, comment=' # cmt') @@ -250,100 +257,79 @@ class IncludeCoveredTest(AATest): self.assertEqual(obj.is_covered(check_obj), expected[2], 'Mismatch in is_covered, expected %s' % expected[2]) self.assertEqual(obj.is_covered(check_obj, True, True), expected[3], 'Mismatch in is_covered/exact, expected %s' % expected[3]) + class IncludeCoveredTest_01(IncludeCoveredTest): rule = 'include <foo>' - tests = [ - # rule equal strict equal covered covered exact - ('include <foo>' , [ True , True , True , True ]), - ('#include <foo>' , [ True , False , True , True ]), - ('include if exists <foo>' , [ False , False , True , True ]), - ('#include if exists <foo>' , [ False , False , True , True ]), - ('include <foobar>' , [ False , False , False , False ]), -# ('include "foo"' , [ False , False , False , False ]), # disabled due to implementation restrictions, see re_match_include_parse() -# ('include if exists "foo"' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ('include <foo>', (True, True, True, True)), + ('#include <foo>', (True, False, True, True)), + ('include if exists <foo>', (False, False, True, True)), + ('#include if exists <foo>', (False, False, True, True)), + ('include <foobar>', (False, False, False, False)), + # ('include "foo"', (False, False, False, False)), # disabled due to implementation restrictions, see re_match_include_parse() + # ('include if exists "foo"', (False, False, False, False)), + ) + class IncludeCoveredTest_02(IncludeCoveredTest): rule = 'include if exists <foo>' - tests = [ - # rule equal strict equal covered covered exact - ('include <foo>' , [ False , False , False , False ]), - ('#include <foo>' , [ False , False , False , False ]), - ('#include if exists <foo>' , [ True , False , True , True ]), - ('include <foobar>' , [ False , False , False , False ]), -# ('include "foo"' , [ False , False , False , False ]), # disabled due to implementation restrictions, see re_match_include_parse() -# ('include if exists "foo"' , [ False , False , False , False ]), - ] - -#class IncludeCoveredTest_Invalid(AATest): -# def test_borked_obj_is_covered_1(self): -# obj = IncludeRule.parse('include <foo>') - -# testobj = IncludeRule('foo', True, True) -# testobj.path = '' - -# with self.assertRaises(AppArmorBug): -# obj.is_covered(testobj) + tests = ( + # rule equal strict equal covered covered exact + ('include <foo>', (False, False, False, False)), + ('#include <foo>', (False, False, False, False)), + ('#include if exists <foo>', (True, False, True, True)), + ('include <foobar>', (False, False, False, False)), + # ('include "foo"', (False, False, False, False)), # disabled due to implementation restrictions, see re_match_include_parse() + # ('include if exists "foo"', (False, False, False, False)), + ) -# def test_borked_obj_is_covered_2(self): -# obj = IncludeRule.parse('include send set=quit peer=/foo,') -# testobj = IncludeRule('send', 'quit', '/foo') -# testobj.include = '' +class IncludeCoveredTest_Invalid(AATest): + # def test_borked_obj_is_covered_1(self): + # obj = IncludeRule.parse('include <foo>') + # + # testobj = IncludeRule('foo', True, True) + # testobj.path = '' + # + # with self.assertRaises(AppArmorBug): + # obj.is_covered(testobj) -# with self.assertRaises(AppArmorBug): -# obj.is_covered(testobj) + def test_invalid_is_covered(self): + obj = IncludeRule.parse('include <abstractions/base>') -# def test_borked_obj_is_covered_3(self): -# obj = IncludeRule.parse('include send set=quit peer=/foo,') + testobj = BaseRule() # different type -# testobj = IncludeRule('send', 'quit', '/foo') -# testobj.peer = '' - -# with self.assertRaises(AppArmorBug): -# obj.is_covered(testobj) - -# def test_invalid_is_covered(self): -# obj = IncludeRule.parse('include send,') - -# testobj = BaseRule() # different type + with self.assertRaises(AppArmorBug): + obj.is_covered(testobj) -# with self.assertRaises(AppArmorBug): -# obj.is_covered(testobj) + def test_invalid_is_equal(self): + obj = IncludeRule.parse('include <abstractions/base>') -# def test_invalid_is_equal(self): -# obj = IncludeRule.parse('include send,') + testobj = BaseRule() # different type -# testobj = BaseRule() # different type + with self.assertRaises(AppArmorBug): + obj.is_equal(testobj) -# with self.assertRaises(AppArmorBug): -# obj.is_equal(testobj) class IncludeLogprofHeaderTest(AATest): -# tests = [ -# ('include,', [ _('Access mode'), _('ALL'), _('Include'), _('ALL'), _('Peer'), _('ALL'), ]), -# ('include send,', [ _('Access mode'), 'send', _('Include'), _('ALL'), _('Peer'), _('ALL'), ]), -# ('include send set=quit,', [ _('Access mode'), 'send', _('Include'), 'quit', _('Peer'), _('ALL'), ]), -# ('deny include,', [_('Qualifier'), 'deny', _('Access mode'), _('ALL'), _('Include'), _('ALL'), _('Peer'), _('ALL'), ]), -# ('allow include send,', [_('Qualifier'), 'allow', _('Access mode'), 'send', _('Include'), _('ALL'), _('Peer'), _('ALL'), ]), -# ('audit include send set=quit,', [_('Qualifier'), 'audit', _('Access mode'), 'send', _('Include'), 'quit', _('Peer'), _('ALL'), ]), -# ('audit deny include send,', [_('Qualifier'), 'audit deny', _('Access mode'), 'send', _('Include'), _('ALL'), _('Peer'), _('ALL'), ]), -# ('include set=(int, quit),', [ _('Access mode'), _('ALL'), _('Include'), 'int quit', _('Peer'), _('ALL'), ]), -# ('include set=( quit, int),', [ _('Access mode'), _('ALL'), _('Include'), 'int quit', _('Peer'), _('ALL'), ]), -# ('include (send, receive) set=( quit, int) peer=/foo,', [ _('Access mode'), 'receive send', _('Include'), 'int quit', _('Peer'), '/foo', ]), -# ] + tests = ( + ('include <abstractions/base>', [_('Include'), 'include <abstractions/base>']), + ('include "/what/ever"', [_('Include'), 'include "/what/ever"']), + ) def _run_test(self, params, expected): - obj = IncludeRule._parse(params) + obj = IncludeRule.parse(params) self.assertEqual(obj.logprof_header(), expected) + class IncludeFullPathsTest(AATest): def AASetup(self): self.createTmpdir() - #copy the local profiles to the test directory + # copy the local profiles to the test directory self.profile_dir = '%s/profiles' % self.tmpdir shutil.copytree('../../profiles/apparmor.d/', self.profile_dir, symlinks=True) @@ -359,32 +345,33 @@ class IncludeFullPathsTest(AATest): empty_dir = os.path.join(self.profile_dir, 'abstractions/empty.d') os.mkdir(empty_dir, 0o755) - tests = [ - # @@ will be replaced with self.profile_dir - ('include <abstractions/base>', ['@@/abstractions/base'] ), -# ('include "foo"', ['@@/foo'] ), # TODO: adjust logic to honor quoted vs. magic paths (and allow quoted relative paths in re_match_include_parse()) - ('include "/foo/bar"', ['/foo/bar'] ), - ('include <abstractions/inc.d>', ['@@/abstractions/inc.d/incbar', '@@/abstractions/inc.d/incfoo'] ), - ('include <abstractions/empty.d>', [] ), - ('include <abstractions/not_found>', ['@@/abstractions/not_found'] ), - ('include if exists <abstractions/not_found>', [] ), - ] + tests = ( + # @@ will be replaced with self.profile_dir + ('include <abstractions/base>', ('@@/abstractions/base',)), + # ('include "foo"', ('@@/foo',)), # TODO: adjust logic to honor quoted vs. magic paths (and allow quoted relative paths in re_match_include_parse()) + ('include "/foo/bar"', ('/foo/bar',)), + ('include <abstractions/inc.d>', ('@@/abstractions/inc.d/incbar', '@@/abstractions/inc.d/incfoo')), + ('include <abstractions/empty.d>', ()), + ('include <abstractions/not_found>', ('@@/abstractions/not_found',)), + ('include if exists <abstractions/not_found>', ()), + ) def _run_test(self, params, expected): exp2 = [] for path in expected: exp2.append(path.replace('@@', self.profile_dir)) - obj = IncludeRule._parse(params) + obj = IncludeRule.parse(params) self.assertEqual(obj.get_full_paths(self.profile_dir), exp2) + ## --- tests for IncludeRuleset --- # class IncludeRulesTest(AATest): def AASetup(self): self.createTmpdir() - #copy the local profiles to the test directory + # copy the local profiles to the test directory self.profile_dir = '%s/profiles' % self.tmpdir shutil.copytree('../../profiles/apparmor.d/', self.profile_dir, symlinks=True) @@ -402,10 +389,10 @@ class IncludeRulesTest(AATest): def test_ruleset_1(self): ruleset = IncludeRuleset() - rules = [ + rules = ( ' include <foo> ', ' #include "/bar" ', - ] + ) expected_raw = [ 'include <foo>', @@ -440,12 +427,12 @@ class IncludeRulesTest(AATest): def test_ruleset_2(self): ruleset = IncludeRuleset() - rules = [ + rules = ( ' include if exists <baz> ', ' include <foo> ', ' #include "/bar" ', '#include if exists "/asdf" ', - ] + ) expected_raw = [ 'include if exists <baz>', @@ -485,23 +472,26 @@ class IncludeRulesTest(AATest): self.assertEqual(expected_clean_unsorted, ruleset.get_clean_unsorted()) self.assertEqual(expected_fullpaths, ruleset.get_all_full_paths(self.profile_dir)) + class IncludeGlobTestAATest(AATest): def setUp(self): self.maxDiff = None self.ruleset = IncludeRuleset() -# def test_glob(self): -# with self.assertRaises(NotImplementedError): -# # get_glob_ext is not available for include rules -# self.ruleset.get_glob('include send set=int,') + # def test_glob(self): + # with self.assertRaises(NotImplementedError): + # # get_glob_ext is not available for include rules + # self.ruleset.get_glob('include send set=int,') def test_glob_ext(self): with self.assertRaises(NotImplementedError): # get_glob_ext is not available for include rules self.ruleset.get_glob_ext('include send set=int,') -#class IncludeDeleteTestAATest(AATest): -# pass + +# class IncludeDeleteTestAATest(AATest): +# pass + setup_all_loops(__name__) if __name__ == '__main__': diff --git a/utils/test/test-libapparmor-test_multi.py b/utils/test/test-libapparmor-test_multi.py index 1c61eef9e..715af66a3 100644 --- a/utils/test/test-libapparmor-test_multi.py +++ b/utils/test/test-libapparmor-test_multi.py @@ -1,7 +1,7 @@ #! /usr/bin/python3 # ------------------------------------------------------------------ # -# Copyright (C) 2015-2018 Christian Boltz <apparmor@cboltz.de> +# Copyright (C) 2015-2021 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 @@ -9,20 +9,19 @@ # # ------------------------------------------------------------------ -import unittest -from common_test import AATest, setup_all_loops, setup_aa, read_file - import os import sys -from apparmor.common import open_file_read, split_name +import unittest import apparmor.aa +from apparmor.common import hasher, open_file_read, split_name from apparmor.logparser import ReadLog from apparmor.profile_list import ProfileList +from common_test import AATest, read_file, setup_aa, setup_all_loops class TestLibapparmorTestMulti(AATest): - '''Parse all libraries/libapparmor/testsuite/test_multi tests and compare the result with the *.out files''' + """Parse all libraries/libapparmor/testsuite/test_multi tests and compare the result with the *.out files""" tests = 'invalid' # filled by parse_test_profiles() @@ -33,28 +32,26 @@ class TestLibapparmorTestMulti(AATest): expected = self._parse_libapparmor_test_multi(params) + loglines = [] with open_file_read('%s.in' % params) as f_in: - loglines = f_in.readlines() + for line in f_in: + if line.strip(): + loglines.append(line) - loglines2 = [] - for line in loglines: - if line.strip(): - loglines2 += [line] - - self.assertEqual(len(loglines2), 1, '%s.in should only contain one line!' % params) + self.assertEqual(len(loglines), 1, '%s.in should only contain one line!' % params) parser = ReadLog('', '', '') - parsed_event = parser.parse_event(loglines2[0]) + parsed_event = parser.parse_event(loglines[0]) if parsed_event and expected: parsed_items = dict(parsed_event.items()) # check if the line passes the regex in logparser.py - if not parser.RE_LOG_ALL.search(loglines2[0]): + if not parser.RE_LOG_ALL.search(loglines[0]): raise Exception("Log event doesn't match RE_LOG_ALL") for label in expected: - if label in [ + if label in ( 'file', # filename of the *.in file 'event_type', # mapped to aamode 'audit_id', 'audit_sub_id', # not set nor relevant @@ -68,9 +65,9 @@ class TestLibapparmorTestMulti(AATest): 'src_name', # pivotroot 'dbus_bus', 'dbus_interface', 'dbus_member', 'dbus_path', # dbus 'peer_pid', 'peer_profile', # dbus - ]: + ): pass - elif parsed_items['operation'] == 'exec' and label in ['sock_type', 'family', 'protocol']: + elif parsed_items['operation'] == 'exec' and label in ('sock_type', 'family', 'protocol'): pass # XXX 'exec' + network? really? elif parsed_items['operation'] == 'ptrace' and label == 'name2' and params.endswith('/ptrace_garbage_lp1689667_1'): pass # libapparmor would better qualify this case as invalid event @@ -94,29 +91,30 @@ class TestLibapparmorTestMulti(AATest): # list of labels that use a different name in logparser.py than in the test_multi *.out files # (additionally, .lower() is applied to all labels) label_map = { - 'Mask': 'request_mask', - 'Command': 'comm', - 'Token': 'magic_token', - 'ErrorCode': 'error_code', - 'Network family': 'family', - 'Socket type': 'sock_type', - 'Local addr': 'net_local_addr', - 'Foreign addr': 'net_foreign_addr', - 'Local port': 'net_local_port', - 'Foreign port': 'net_foreign_port', - 'Audit subid': 'audit_sub_id', - 'Attribute': 'attr', - 'Epoch': 'time', + 'Mask': 'request_mask', + 'Command': 'comm', + 'Token': 'magic_token', + 'ErrorCode': 'error_code', + 'Network family': 'family', + 'Socket type': 'sock_type', + 'Local addr': 'net_local_addr', + 'Foreign addr': 'net_foreign_addr', + 'Local port': 'net_local_port', + 'Foreign port': 'net_foreign_port', + 'Audit subid': 'audit_sub_id', + 'Attribute': 'attr', + 'Epoch': 'time', } def _parse_libapparmor_test_multi(self, file_with_path): - '''parse the libapparmor test_multi *.in tests and their expected result in *.out''' + """parse the libapparmor test_multi *.in tests and their expected result in *.out""" with open_file_read('%s.out' % file_with_path) as f_in: expected = f_in.readlines() if expected[0].rstrip('\n') != 'START': - raise Exception("%s.out doesn't have 'START' in its first line! (%s)" % ( file_with_path, expected[0])) + raise Exception("%s.out doesn't have 'START' in its first line! (%s)" + % (file_with_path, expected[0])) expected.pop(0) @@ -131,7 +129,8 @@ class TestLibapparmorTestMulti(AATest): exresult[label] = value.strip() if not exresult['event_type'].startswith('AA_RECORD_'): - raise Exception("event_type doesn't start with AA_RECORD_: %s in file %s" % (exresult['event_type'], file_with_path)) + raise Exception("event_type doesn't start with AA_RECORD_: %s in file %s" + % (exresult['event_type'], file_with_path)) exresult['aamode'] = exresult['event_type'].replace('AA_RECORD_', '') if exresult['aamode'] == 'ALLOWED': @@ -144,6 +143,7 @@ class TestLibapparmorTestMulti(AATest): return exresult + # tests that cause crashes or need user interaction (will be skipped) log_to_skip = [ 'testcase_dbus_09', # multiline log not currently supported @@ -182,8 +182,9 @@ log_to_profile_known_empty_log = [ 'unconfined-change_hat', # unconfined trying to change_hat, which isn't allowed ] + class TestLogToProfile(AATest): - '''Check if the libraries/libapparmor/testsuite/test_multi tests result in the expected profile''' + """Check if the libraries/libapparmor/testsuite/test_multi tests result in the expected profile""" tests = 'invalid' # filled by parse_test_profiles() @@ -217,25 +218,27 @@ def logfile_to_profile(logfile): aamode = parsed_event['aamode'] - if aamode in['AUDIT', 'STATUS', 'HINT']: # ignore some event types # XXX maybe we shouldn't ignore AUDIT events? + if aamode in ('AUDIT', 'STATUS', 'HINT'): # ignore some event types # XXX maybe we shouldn't ignore AUDIT events? return None, aamode - if aamode not in ['PERMITTING', 'REJECTING']: + if aamode not in ('PERMITTING', 'REJECTING'): raise Exception('Unexpected aamode %s' % parsed_event['aamode']) # cleanup apparmor.aa storage apparmor.aa.log = dict() - apparmor.aa.aa = apparmor.aa.hasher() + apparmor.aa.aa = hasher() profile, hat = split_name(parsed_event['profile']) apparmor.aa.active_profiles = ProfileList() + dummy_prof = apparmor.aa.ProfileStorage('TEST DUMMY for active_profiles', profile_dummy_file, 'logprof_to_profile()') + # optional for now, might be needed one day # if profile.startswith('/'): - # apparmor.aa.active_profiles.add_profile(profile_dummy_file, profile, profile) + # apparmor.aa.active_profiles.add_profile(profile_dummy_file, profile, profile, dummy_prof) # else: - apparmor.aa.active_profiles.add_profile(profile_dummy_file, profile, '') + apparmor.aa.active_profiles.add_profile(profile_dummy_file, profile, '', dummy_prof) log_reader = ReadLog(logfile, apparmor.aa.active_profiles, '') hashlog = log_reader.read_log('') @@ -245,15 +248,16 @@ def logfile_to_profile(logfile): log_dict = apparmor.aa.collapse_log(hashlog, ignore_null_profiles=False) - if profile != hat: + if list(log_dict[aamode].keys()) != [parsed_event['profile']]: + raise Exception('log_dict[%s] contains unexpected keys. Logfile: %s, keys %s' % (aamode, logfile, log_dict.keys())) + + if '//' in parsed_event['profile']: # log event for a child profile means log_dict only contains the child profile # initialize parent profile in log_dict as ProfileStorage to ensure writing the profile doesn't fail # (in "normal" usage outside of this test, log_dict will not be handed over to serialize_profile()) - if log_dict[aamode][profile][profile] != {}: - raise Exception('event for child profile, but parent profile was initialized nevertheless. Logfile: %s' % logfile) - - log_dict[aamode][profile][profile] = apparmor.aa.ProfileStorage('TEST DUMMY for empty parent profile', profile_dummy_file, 'logfile_to_profile()') + log_dict[aamode][profile] = apparmor.aa.ProfileStorage('TEST DUMMY for empty parent profile', profile_dummy_file, 'logfile_to_profile()') + log_dict[aamode][parsed_event['profile']]['is_hat'] = True # for historical reasons, generate hats, not child profiles log_is_empty = True @@ -267,18 +271,19 @@ def logfile_to_profile(logfile): if logfile.split('/')[-1][:-3] in log_to_profile_known_empty_log: # unfortunately this function might be called outside Unittest.TestCase, therefore we can't use assertEqual / assertNotEqual - if log_is_empty == False: + if not log_is_empty: raise Exception('got non-empty log for logfile in log_to_profile_known_empty_log: %s %s' % (logfile, hashlog)) else: - if log_is_empty == True: + if log_is_empty: raise Exception('got empty log for logfile not in log_to_profile_known_empty_log: %s %s' % (logfile, hashlog)) - new_profile = apparmor.aa.serialize_profile(log_dict[aamode][profile], profile, {}) + new_profile = apparmor.aa.serialize_profile(log_dict[aamode], profile, {}) return profile, new_profile + def find_test_multi(log_dir): - '''find all log sniplets in the given log_dir''' + """find all log sniplets in the given log_dir""" log_dir = os.path.abspath(log_dir) @@ -287,7 +292,7 @@ def find_test_multi(log_dir): for file in files: if file.endswith('.in'): file_with_path = os.path.join(root, file[:-3]) # filename without '.in' - tests.append([file_with_path, True]) # True is a dummy testresult, parsing of the *.out files is done while running the tests + tests.append((file_with_path, True)) # True is a dummy testresult, parsing of the *.out files is done while running the tests elif file.endswith('.out') or file.endswith('.err') or file.endswith('.profile'): pass @@ -296,7 +301,8 @@ def find_test_multi(log_dir): return tests -# if a logfile is given as parameter, print the resulting profile and exit (with $? = 42 to make sure tests break if the caller accidently hands over a parameter) + +# if a logfile is given as parameter, print the resulting profile and exit (with $? = 42 to make sure tests break if the caller accidentally hands over a parameter) if __name__ == '__main__' and len(sys.argv) == 2: print(logfile_to_profile(sys.argv[1])[1]) exit(42) diff --git a/utils/test/test-logparser.py b/utils/test/test-logparser.py index b3d8e105a..39acae0e5 100644 --- a/utils/test/test-logparser.py +++ b/utils/test/test-logparser.py @@ -14,13 +14,13 @@ # ---------------------------------------------------------------------- import unittest +from apparmor.common import AppArmorException from apparmor.logparser import ReadLog +from common_test import AATest, setup_all_loops # , setup_aa -from common_test import AATest, setup_all_loops # , setup_aa -from apparmor.common import AppArmorException class TestParseEvent(AATest): - tests = [] + tests = () def setUp(self): self.parser = ReadLog('', '', '') @@ -95,15 +95,17 @@ class TestParseEvent(AATest): 'family': None, 'protocol': None, 'sock_type': None, + 'class': None, }) self.assertIsNotNone(ReadLog.RE_LOG_ALL.search(event)) + class TestParseEventForTreeInvalid(AATest): - tests = [ - ('type=AVC msg=audit(1556742870.707:3614): apparmor="ALLOWED" operation="open" profile="/bin/hello" name="/dev/tty" pid=12856 comm="hello" requested_mask="wr" denied_mask="foo" fsuid=1000 ouid=0', AppArmorException), # invalid file permissions "foo" - ('type=AVC msg=audit(1556742870.707:3614): apparmor="ALLOWED" operation="open" profile="/bin/hello" name="/dev/tty" pid=12856 comm="hello" requested_mask="wr" denied_mask="wr::w" fsuid=1000 ouid=0', AppArmorException), # "wr::w" mixes owner and other - ] + tests = ( + ('type=AVC msg=audit(1556742870.707:3614): apparmor="ALLOWED" operation="open" profile="/bin/hello" name="/dev/tty" pid=12856 comm="hello" requested_mask="wr" denied_mask="foo" fsuid=1000 ouid=0', AppArmorException), # invalid file permissions "foo" + ('type=AVC msg=audit(1556742870.707:3614): apparmor="ALLOWED" operation="open" profile="/bin/hello" name="/dev/tty" pid=12856 comm="hello" requested_mask="wr" denied_mask="wr::w" fsuid=1000 ouid=0', AppArmorException), # "wr::w" mixes owner and other + ) def _fake_profile_exists(self, program): return True @@ -115,6 +117,7 @@ class TestParseEventForTreeInvalid(AATest): with self.assertRaises(expected): self.parser.parse_event_for_tree(parsed_event) + setup_all_loops(__name__) if __name__ == "__main__": unittest.main(verbosity=1) diff --git a/utils/test/test-minitools.py b/utils/test/test-minitools.py new file mode 100755 index 000000000..f99fc4de7 --- /dev/null +++ b/utils/test/test-minitools.py @@ -0,0 +1,217 @@ +# ---------------------------------------------------------------------- +# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.com> +# +# 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. +# +# ---------------------------------------------------------------------- +import os +import shutil +import subprocess +import unittest + +import apparmor.aa as apparmor +from common_test import AATest, read_file, setup_aa, setup_all_loops + +python_interpreter = 'python3' + + +class MinitoolsTest(AATest): + + def AASetup(self): + self.createTmpdir() + + # copy the local profiles to the test directory + # Should be the set of cleanprofile + self.profile_dir = '%s/profiles' % self.tmpdir + shutil.copytree('../../profiles/apparmor.d/', self.profile_dir, symlinks=True) + + apparmor.profile_dir = self.profile_dir + + # Path for the program + self.test_path = '/usr/sbin/winbindd' + # Path for the target file containing profile + self.local_profilename = '%s/usr.sbin.winbindd' % self.profile_dir + + def test_audit(self): + # Set test profile to audit mode and check if it was correctly set + str(subprocess.check_output( + '%s ./../aa-audit --no-reload -d %s %s --configdir ./' + % (python_interpreter, self.profile_dir, self.test_path), shell=True)) + + self.assertEqual( + apparmor.get_profile_flags(self.local_profilename, self.test_path), + 'audit', + 'Audit flag could not be set in profile %s' % self.local_profilename) + + # Remove audit mode from test profile and check if it was correctly removed + subprocess.check_output( + '%s ./../aa-audit --no-reload -d %s -r %s --configdir ./' + % (python_interpreter, self.profile_dir, self.test_path), shell=True) + + self.assertEqual( + apparmor.get_profile_flags(self.local_profilename, self.test_path), + None, + 'Audit flag could not be removed in profile %s' % self.local_profilename) + + def test_complain(self): + # Set test profile to complain mode and check if it was correctly set + subprocess.check_output( + '%s ./../aa-complain --no-reload -d %s %s --configdir ./' + % (python_interpreter, self.profile_dir, self.test_path), shell=True) + + # "manually" create a force-complain symlink (will be deleted by aa-enforce later) + force_complain_dir = '%s/force-complain' % self.profile_dir + if not os.path.isdir(force_complain_dir): + os.mkdir(force_complain_dir) + os.symlink( + self.local_profilename, + '%s/%s' % (force_complain_dir, os.path.basename(self.local_profilename))) + + self.assertEqual( + os.path.islink('%s/%s' % (force_complain_dir, os.path.basename(self.local_profilename))), + True, + 'Failed to create a symlink for %s in force-complain' % self.local_profilename) + self.assertEqual( + apparmor.get_profile_flags(self.local_profilename, self.test_path), + 'complain', + 'Complain flag could not be set in profile %s' % self.local_profilename) + + # Set test profile to enforce mode and check if it was correctly set + subprocess.check_output( + '%s ./../aa-enforce --no-reload -d %s %s --configdir ./' + % (python_interpreter, self.profile_dir, self.test_path), shell=True) + + self.assertEqual( + os.path.islink('%s/%s' % (force_complain_dir, os.path.basename(self.local_profilename))), + False, + 'Failed to remove symlink for %s from force-complain' % self.local_profilename) + self.assertEqual( + os.path.islink('%s/disable/%s' % (self.profile_dir, os.path.basename(self.local_profilename))), + False, + 'Failed to remove symlink for %s from disable' % self.local_profilename) + self.assertEqual( + apparmor.get_profile_flags(self.local_profilename, self.test_path), + None, + 'Complain flag could not be removed in profile %s' % self.local_profilename) + + # Set audit flag and then complain flag in a profile + subprocess.check_output( + '%s ./../aa-audit --no-reload -d %s %s --configdir ./' + % (python_interpreter, self.profile_dir, self.test_path), shell=True) + subprocess.check_output( + '%s ./../aa-complain --no-reload -d %s %s --configdir ./' + % (python_interpreter, self.profile_dir, self.test_path), shell=True) + # "manually" create a force-complain symlink (will be deleted by aa-enforce later) + os.symlink( + self.local_profilename, + '%s/%s' % (force_complain_dir, os.path.basename(self.local_profilename))) + + self.assertEqual( + os.path.islink('%s/%s' % (force_complain_dir, os.path.basename(self.local_profilename))), + True, + 'Failed to create a symlink for %s in force-complain' % self.local_profilename) + self.assertEqual( + apparmor.get_profile_flags(self.local_profilename, self.test_path), + 'audit, complain', + 'Complain flag could not be set in profile %s' % self.local_profilename) + + # Remove complain flag first i.e. set to enforce mode + subprocess.check_output( + '%s ./../aa-enforce --no-reload -d %s %s --configdir ./' + % (python_interpreter, self.profile_dir, self.test_path), shell=True) + + self.assertEqual( + os.path.islink('%s/%s' % (force_complain_dir, os.path.basename(self.local_profilename))), + False, + 'Failed to remove symlink for %s from force-complain' % self.local_profilename) + self.assertEqual( + os.path.islink('%s/disable/%s' % (self.profile_dir, os.path.basename(self.local_profilename))), + False, + 'Failed to remove symlink for %s from disable' % self.local_profilename) + self.assertEqual( + apparmor.get_profile_flags(self.local_profilename, self.test_path), + 'audit', + 'Complain flag could not be removed in profile %s' % self.local_profilename) + + # Remove audit flag + subprocess.check_output( + '%s ./../aa-audit --no-reload -d %s -r %s --configdir ./' + % (python_interpreter, self.profile_dir, self.test_path), shell=True) + + def test_enforce(self): + # Set test profile to enforce mode and check if it was correctly set + subprocess.check_output( + '%s ./../aa-enforce --no-reload -d %s %s --configdir ./' + % (python_interpreter, self.profile_dir, self.test_path), shell=True) + + self.assertEqual( + os.path.islink('%s/force-complain/%s' % (self.profile_dir, os.path.basename(self.local_profilename))), + False, + 'Failed to remove symlink for %s from force-complain' % self.local_profilename) + self.assertEqual( + os.path.islink('%s/disable/%s' % (self.profile_dir, os.path.basename(self.local_profilename))), + False, + 'Failed to remove symlink for %s from disable' % self.local_profilename) + self.assertEqual( + apparmor.get_profile_flags(self.local_profilename, self.test_path), + None, + 'Complain flag could not be removed in profile %s' % self.local_profilename) + + def test_disable(self): + # Disable the test profile and check if it was correctly disabled + subprocess.check_output( + '%s ./../aa-disable --no-reload -d %s %s --configdir ./' + % (python_interpreter, self.profile_dir, self.test_path), shell=True) + + self.assertEqual( + os.path.islink('%s/disable/%s' % (self.profile_dir, os.path.basename(self.local_profilename))), + True, + 'Failed to create a symlink for %s in disable' % self.local_profilename) + + def test_autodep(self): + pass + + @unittest.skipIf(apparmor.check_for_apparmor() is None, "Securityfs not mounted or doesn't have the apparmor directory.") + def test_unconfined(self): + output = subprocess.check_output( + '%s ./../aa-unconfined --configdir ./' % python_interpreter, shell=True) + + output_force = subprocess.check_output( + '%s ./../aa-unconfined --paranoid --configdir ./' % python_interpreter, shell=True) + + self.assertIsNot(output, '', 'Failed to run aa-unconfined') + + self.assertIsNot(output_force, '', 'Failed to run aa-unconfined in paranoid mode') + + def test_cleanprof(self): + input_file = 'cleanprof_test.in' + output_file = 'cleanprof_test.out' + # We position the local testfile + shutil.copy('./%s' % input_file, self.profile_dir) + # Our silly test program whose profile we wish to clean + cleanprof_test = '/usr/bin/a/simple/cleanprof/test/profile' + + subprocess.check_output( + '%s ./../aa-cleanprof --no-reload -d %s -s %s --configdir ./' + % (python_interpreter, self.profile_dir, cleanprof_test), shell=True) + + # Strip off the first line (#modified line) + subprocess.check_output('sed -i 1d %s/%s' % (self.profile_dir, input_file), shell=True) + + exp_content = read_file('./%s' % output_file) + real_content = read_file('%s/%s' % (self.profile_dir, input_file)) + self.maxDiff = None + self.assertEqual(exp_content, real_content, 'Failed to cleanup profile properly') + + +setup_aa(apparmor) +setup_all_loops(__name__) +if __name__ == '__main__': + unittest.main(verbosity=1) diff --git a/utils/test/test-mount_parse.py b/utils/test/test-mount_parse.py index 3ebb57709..81c364092 100644 --- a/utils/test/test-mount_parse.py +++ b/utils/test/test-mount_parse.py @@ -9,35 +9,41 @@ # # ------------------------------------------------------------------ -import apparmor.aa as aa import unittest -from common_test import AAParseTest, setup_regex_tests, setup_aa + +import apparmor.aa as aa +from common_test import AAParseTest, setup_aa, setup_regex_tests + class BaseAAParseMountTest(AAParseTest): def setUp(self): self.parse_function = aa.parse_mount_rule + class AAParseMountTest(BaseAAParseMountTest): - tests = [ + tests = ( ('mount,', 'mount base keyword rule'), ('mount -o ro,', 'mount ro rule'), ('mount -o rw /dev/sdb1 -> /mnt/external,', 'mount rw with mount point'), - ] + ) + class AAParseRemountTest(BaseAAParseMountTest): - tests = [ + tests = ( ('remount,', 'remount base keyword rule'), ('remount -o ro,', 'remount ro rule'), ('remount -o ro /,', 'remount ro with mountpoint'), - ] + ) + class AAParseUmountTest(BaseAAParseMountTest): - tests = [ + tests = ( ('umount,', 'umount base keyword rule'), ('umount /mnt/external,', 'umount with mount point'), ('unmount,', 'unmount base keyword rule'), ('unmount /mnt/external,', 'unmount with mount point'), - ] + ) + setup_aa(aa) if __name__ == '__main__': diff --git a/utils/test/test-network.py b/utils/test/test-network.py index afff4de91..d9858cd05 100644 --- a/utils/test/test-network.py +++ b/utils/test/test-network.py @@ -15,20 +15,23 @@ import unittest from collections import namedtuple -from common_test import AATest, setup_all_loops -from apparmor.rule.network import NetworkRule, NetworkRuleset, network_domain_keywords -from apparmor.rule import BaseRule -from apparmor.common import AppArmorException, AppArmorBug, cmd +from apparmor.common import AppArmorBug, AppArmorException, cmd from apparmor.logparser import ReadLog +from apparmor.rule import BaseRule +from apparmor.rule.network import NetworkRule, NetworkRuleset, network_domain_keywords from apparmor.translations import init_translation +from common_test import AATest, setup_all_loops + _ = init_translation() -exp = namedtuple('exp', ['audit', 'allow_keyword', 'deny', 'comment', - 'domain', 'all_domains', 'type_or_protocol', 'all_type_or_protocols']) +exp = namedtuple( + 'exp', ('audit', 'allow_keyword', 'deny', 'comment', + 'domain', 'all_domains', 'type_or_protocol', 'all_type_or_protocols')) # --- check if the keyword list is up to date --- # + class NetworkKeywordsTest(AATest): def test_network_keyword_list(self): rc, output = cmd('../../common/list_af_names.sh') @@ -39,7 +42,7 @@ class NetworkKeywordsTest(AATest): for af_pair in af_pairs: af_name = af_pair.lstrip().split(" ")[0] # skip max af name definition - if len(af_name) > 0 and af_name != "max": + if af_name and af_name != "max": af_names.append(af_name) missing_af_names = [] @@ -48,11 +51,15 @@ class NetworkKeywordsTest(AATest): # keywords missing in the system are ok (= older kernel), but network_domain_keywords needs to have the full list missing_af_names.append(keyword) - self.assertEqual(missing_af_names, [], 'Missing af_names in NetworkRule network_domain_keywords. This test is likely running ' - 'on an newer kernel and will require updating the list of network domain keywords in utils/apparmor/rule/network.py') + self.assertEqual( + missing_af_names, [], + 'Missing af_names in NetworkRule network_domain_keywords. This test is likely running ' + 'on an newer kernel and will require updating the list of network domain keywords in ' + 'utils/apparmor/rule/network.py') # --- tests for single NetworkRule --- # + class NetworkTest(AATest): def _compare_obj(self, obj, expected): self.assertEqual(expected.allow_keyword, obj.allow_keyword) @@ -64,16 +71,17 @@ class NetworkTest(AATest): self.assertEqual(expected.deny, obj.deny) self.assertEqual(expected.comment, obj.comment) + class NetworkTestParse(NetworkTest): - tests = [ - # rawrule audit allow deny comment domain all? type/proto all? - ('network,' , exp(False, False, False, '' , None , True , None , True )), - ('network inet,' , exp(False, False, False, '' , 'inet', False, None , True )), - ('network inet stream,' , exp(False, False, False, '' , 'inet', False, 'stream' , False)), - ('deny network inet stream, # comment' , exp(False, False, True , ' # comment' , 'inet', False, 'stream' , False)), - ('audit allow network tcp,' , exp(True , True , False, '' , None , True , 'tcp' , False)), - ('network stream,' , exp(False, False, False, '' , None , True , 'stream' , False)), - ] + tests = ( + # rawrule audit allow deny comment domain all? type/proto all? + ('network,', exp(False, False, False, '', None, True, None, True)), + ('network inet,', exp(False, False, False, '', 'inet', False, None, True)), + ('network inet stream,', exp(False, False, False, '', 'inet', False, 'stream', False)), + ('deny network inet stream, # comment', exp(False, False, True, ' # comment', 'inet', False, 'stream', False)), + ('audit allow network tcp,', exp(True, True, False, '', None, True, 'tcp', False)), + ('network stream,', exp(False, False, False, '', None, True, 'stream', False)), + ) def _run_test(self, rawrule, expected): self.assertTrue(NetworkRule.match(rawrule)) @@ -81,19 +89,21 @@ class NetworkTestParse(NetworkTest): self.assertEqual(rawrule.strip(), obj.raw_rule) self._compare_obj(obj, expected) + class NetworkTestParseInvalid(NetworkTest): - tests = [ - ('network foo,' , AppArmorException), - ('network foo bar,' , AppArmorException), - ('network foo tcp,' , AppArmorException), - ('network inet bar,' , AppArmorException), - ] + tests = ( + ('network foo,', AppArmorException), + ('network foo bar,', AppArmorException), + ('network foo tcp,', AppArmorException), + ('network inet bar,', AppArmorException), + ) def _run_test(self, rawrule, expected): self.assertTrue(NetworkRule.match(rawrule)) # the above invalid rules still match the main regex! with self.assertRaises(expected): NetworkRule.parse(rawrule) + class NetworkTestParseFromLog(NetworkTest): def test_net_from_log(self): parser = ReadLog('', '', '') @@ -122,12 +132,13 @@ class NetworkTestParseFromLog(NetworkTest): 'attr': None, 'name2': None, 'name': None, + 'class': None, }) obj = NetworkRule(parsed_event['family'], parsed_event['sock_type'], log_event=parsed_event) - # audit allow deny comment domain all? type/proto all? - expected = exp(False, False, False, '' , 'inet', False, 'raw' , False) + # audit allow deny comment domain all? type/proto all? + expected = exp(False, False, False, '', 'inet', False, 'raw', False) self._compare_obj(obj, expected) @@ -135,38 +146,38 @@ class NetworkTestParseFromLog(NetworkTest): class NetworkFromInit(NetworkTest): - tests = [ - # NetworkRule object audit allow deny comment domain all? type/proto all? - (NetworkRule('inet', 'raw', deny=True) , exp(False, False, True , '' , 'inet', False, 'raw' , False)), - (NetworkRule('inet', 'raw') , exp(False, False, False, '' , 'inet', False, 'raw' , False)), - (NetworkRule('inet', NetworkRule.ALL) , exp(False, False, False, '' , 'inet', False, None , True )), - (NetworkRule(NetworkRule.ALL, NetworkRule.ALL) , exp(False, False, False, '' , None , True , None , True )), - (NetworkRule(NetworkRule.ALL, 'tcp') , exp(False, False, False, '' , None , True , 'tcp' , False)), - (NetworkRule(NetworkRule.ALL, 'stream') , exp(False, False, False, '' , None , True , 'stream' , False)), - ] + tests = ( + # NetworkRule object audit allow deny comment domain all? type/proto all? + (NetworkRule('inet', 'raw', deny=True), exp(False, False, True, '', 'inet', False, 'raw', False)), + (NetworkRule('inet', 'raw'), exp(False, False, False, '', 'inet', False, 'raw', False)), + (NetworkRule('inet', NetworkRule.ALL), exp(False, False, False, '', 'inet', False, None, True)), + (NetworkRule(NetworkRule.ALL, NetworkRule.ALL), exp(False, False, False, '', None, True, None, True)), + (NetworkRule(NetworkRule.ALL, 'tcp'), exp(False, False, False, '', None, True, 'tcp', False)), + (NetworkRule(NetworkRule.ALL, 'stream'), exp(False, False, False, '', None, True, 'stream', False)), + ) def _run_test(self, obj, expected): self._compare_obj(obj, expected) class InvalidNetworkInit(AATest): - tests = [ - # init params expected exception - (['inet', '' ] , AppArmorBug), # empty type_or_protocol - (['' , 'tcp' ] , AppArmorBug), # empty domain - ([' ', 'tcp' ] , AppArmorBug), # whitespace domain - (['inet', ' ' ] , AppArmorBug), # whitespace type_or_protocol - (['xyxy', 'tcp' ] , AppArmorBug), # invalid domain - (['inet', 'xyxy' ] , AppArmorBug), # invalid type_or_protocol - ([dict(), 'tcp' ] , AppArmorBug), # wrong type for domain - ([None , 'tcp' ] , AppArmorBug), # wrong type for domain - (['inet', dict() ] , AppArmorBug), # wrong type for type_or_protocol - (['inet', None ] , AppArmorBug), # wrong type for type_or_protocol - ] + tests = ( + # init params expected exception + (('inet', ''), AppArmorBug), # empty type_or_protocol + (('', 'tcp'), AppArmorBug), # empty domain + ((' ', 'tcp'), AppArmorBug), # whitespace domain + (('inet', ' '), AppArmorBug), # whitespace type_or_protocol + (('xyxy', 'tcp'), AppArmorBug), # invalid domain + (('inet', 'xyxy'), AppArmorBug), # invalid type_or_protocol + ((dict(), 'tcp'), AppArmorBug), # wrong type for domain + ((None, 'tcp'), AppArmorBug), # wrong type for domain + (('inet', dict()), AppArmorBug), # wrong type for type_or_protocol + (('inet', None), AppArmorBug), # wrong type for type_or_protocol + ) def _run_test(self, params, expected): with self.assertRaises(expected): - NetworkRule(params[0], params[1]) + NetworkRule(*params) def test_missing_params_1(self): with self.assertRaises(TypeError): @@ -217,14 +228,14 @@ class WriteNetworkTestAATest(AATest): self.assertEqual(expected.strip(), clean, 'unexpected clean rule') self.assertEqual(rawrule.strip(), raw, 'unexpected raw rule') - tests = [ - # raw rule clean rule - (' network , # foo ' , 'network, # foo'), - (' audit network inet,' , 'audit network inet,'), - (' deny network inet stream,# foo bar' , 'deny network inet stream, # foo bar'), - (' deny network inet ,# foo bar' , 'deny network inet, # foo bar'), - (' allow network tcp ,# foo bar' , 'allow network tcp, # foo bar'), - ] + tests = ( + # raw rule clean rule + (' network , # foo ', 'network, # foo'), + (' audit network inet,', 'audit network inet,'), + (' deny network inet stream,# foo bar', 'deny network inet stream, # foo bar'), + (' deny network inet ,# foo bar', 'deny network inet, # foo bar'), + (' allow network tcp ,# foo bar', 'allow network tcp, # foo bar'), + ) def test_write_manually(self): obj = NetworkRule('inet', 'stream', allow_keyword=True) @@ -242,89 +253,101 @@ class NetworkCoveredTest(AATest): self.assertTrue(NetworkRule.match(param)) - self.assertEqual(obj.is_equal(check_obj), expected[0], 'Mismatch in is_equal, expected %s' % expected[0]) - self.assertEqual(obj.is_equal(check_obj, True), expected[1], 'Mismatch in is_equal/strict, expected %s' % expected[1]) + self.assertEqual( + obj.is_equal(check_obj), expected[0], + 'Mismatch in is_equal, expected %s' % expected[0]) + self.assertEqual( + obj.is_equal(check_obj, True), expected[1], + 'Mismatch in is_equal/strict, expected %s' % expected[1]) + + self.assertEqual( + obj.is_covered(check_obj), expected[2], + 'Mismatch in is_covered, expected %s' % expected[2]) + self.assertEqual( + obj.is_covered(check_obj, True, True), expected[3], + 'Mismatch in is_covered/exact, expected %s' % expected[3]) - self.assertEqual(obj.is_covered(check_obj), expected[2], 'Mismatch in is_covered, expected %s' % expected[2]) - self.assertEqual(obj.is_covered(check_obj, True, True), expected[3], 'Mismatch in is_covered/exact, expected %s' % expected[3]) class NetworkCoveredTest_01(NetworkCoveredTest): rule = 'network inet,' - tests = [ - # rule equal strict equal covered covered exact - ('network,' , [ False , False , False , False ]), - ('network inet,' , [ True , True , True , True ]), - ('network inet, # comment' , [ True , False , True , True ]), - ('allow network inet,' , [ True , False , True , True ]), - ('network inet,' , [ True , False , True , True ]), - ('network inet stream,' , [ False , False , True , True ]), - ('network inet tcp,' , [ False , False , True , True ]), - ('audit network inet,' , [ False , False , False , False ]), - ('audit network,' , [ False , False , False , False ]), - ('network unix,' , [ False , False , False , False ]), - ('network tcp,' , [ False , False , False , False ]), - ('audit deny network inet,' , [ False , False , False , False ]), - ('deny network inet,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ('network,', (False, False, False, False)), + ('network inet,', (True, True, True, True)), + ('network inet, # comment', (True, False, True, True)), + ('allow network inet,', (True, False, True, True)), + ('network inet,', (True, False, True, True)), + ('network inet stream,', (False, False, True, True)), + ('network inet tcp,', (False, False, True, True)), + ('audit network inet,', (False, False, False, False)), + ('audit network,', (False, False, False, False)), + ('network unix,', (False, False, False, False)), + ('network tcp,', (False, False, False, False)), + ('audit deny network inet,', (False, False, False, False)), + ('deny network inet,', (False, False, False, False)), + ) + class NetworkCoveredTest_02(NetworkCoveredTest): rule = 'audit network inet,' - tests = [ - # rule equal strict equal covered covered exact - ( 'network inet,' , [ False , False , True , False ]), - ('audit network inet,' , [ True , True , True , True ]), - ( 'network inet stream,' , [ False , False , True , False ]), - ('audit network inet stream,' , [ False , False , True , True ]), - ( 'network,' , [ False , False , False , False ]), - ('audit network,' , [ False , False , False , False ]), - ('network unix,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ( 'network inet,', (False, False, True, False)), + ('audit network inet,', (True, True, True, True)), + ( 'network inet stream,', (False, False, True, False)), + ('audit network inet stream,', (False, False, True, True)), + ( 'network,', (False, False, False, False)), + ('audit network,', (False, False, False, False)), + ('network unix,', (False, False, False, False)), + ) class NetworkCoveredTest_03(NetworkCoveredTest): rule = 'network inet stream,' - tests = [ - # rule equal strict equal covered covered exact - ( 'network inet stream,' , [ True , True , True , True ]), - ('allow network inet stream,' , [ True , False , True , True ]), - ( 'network inet,' , [ False , False , False , False ]), - ( 'network,' , [ False , False , False , False ]), - ( 'network inet tcp,' , [ False , False , False , False ]), - ('audit network,' , [ False , False , False , False ]), - ('audit network inet stream,' , [ False , False , False , False ]), - ( 'network unix,' , [ False , False , False , False ]), - ( 'network,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ( 'network inet stream,', (True, True, True, True)), + ('allow network inet stream,', (True, False, True, True)), + ( 'network inet,', (False, False, False, False)), + ( 'network,', (False, False, False, False)), + ( 'network inet tcp,', (False, False, False, False)), + ('audit network,', (False, False, False, False)), + ('audit network inet stream,', (False, False, False, False)), + ( 'network unix,', (False, False, False, False)), + ( 'network,', (False, False, False, False)), + ) + class NetworkCoveredTest_04(NetworkCoveredTest): rule = 'network,' - tests = [ - # rule equal strict equal covered covered exact - ( 'network,' , [ True , True , True , True ]), - ('allow network,' , [ True , False , True , True ]), - ( 'network inet,' , [ False , False , True , True ]), - ( 'network inet6 stream,' , [ False , False , True , True ]), - ( 'network tcp,' , [ False , False , True , True ]), - ( 'network inet raw,' , [ False , False , True , True ]), - ('audit network,' , [ False , False , False , False ]), - ('deny network,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ( 'network,', (True, True, True, True)), + ('allow network,', (True, False, True, True)), + ( 'network inet,', (False, False, True, True)), + ( 'network inet6 stream,', (False, False, True, True)), + ( 'network tcp,', (False, False, True, True)), + ( 'network inet raw,', (False, False, True, True)), + ('audit network,', (False, False, False, False)), + ('deny network,', (False, False, False, False)), + ) + class NetworkCoveredTest_05(NetworkCoveredTest): rule = 'deny network inet,' - tests = [ - # rule equal strict equal covered covered exact - ( 'deny network inet,' , [ True , True , True , True ]), - ('audit deny network inet,' , [ False , False , False , False ]), - ( 'network inet,' , [ False , False , False , False ]), # XXX should covered be true here? - ( 'deny network unix,' , [ False , False , False , False ]), - ( 'deny network,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ( 'deny network inet,', (True, True, True, True)), + ('audit deny network inet,', (False, False, False, False)), + ( 'network inet,', (False, False, False, False)), # XXX should covered be true here? + ( 'deny network unix,', (False, False, False, False)), + ( 'deny network,', (False, False, False, False)), + ) class NetworkCoveredTest_Invalid(AATest): @@ -362,26 +385,29 @@ class NetworkCoveredTest_Invalid(AATest): with self.assertRaises(AppArmorBug): obj.is_equal(testobj) + class NetworkLogprofHeaderTest(AATest): - tests = [ - ('network,', [ _('Network Family'), _('ALL'), _('Socket Type'), _('ALL'), ]), - ('network inet,', [ _('Network Family'), 'inet', _('Socket Type'), _('ALL'), ]), - ('network inet stream,', [ _('Network Family'), 'inet', _('Socket Type'), 'stream', ]), - ('deny network,', [_('Qualifier'), 'deny', _('Network Family'), _('ALL'), _('Socket Type'), _('ALL'), ]), - ('allow network inet,', [_('Qualifier'), 'allow', _('Network Family'), 'inet', _('Socket Type'), _('ALL'), ]), - ('audit network inet stream,', [_('Qualifier'), 'audit', _('Network Family'), 'inet', _('Socket Type'), 'stream', ]), - ('audit deny network inet,', [_('Qualifier'), 'audit deny', _('Network Family'), 'inet', _('Socket Type'), _('ALL'), ]), - ] + tests = ( + ('network,', [ _('Network Family'), _('ALL'), _('Socket Type'), _('ALL')]), + ('network inet,', [ _('Network Family'), 'inet', _('Socket Type'), _('ALL')]), + ('network inet stream,', [ _('Network Family'), 'inet', _('Socket Type'), 'stream']), + ('deny network,', [_('Qualifier'), 'deny', _('Network Family'), _('ALL'), _('Socket Type'), _('ALL')]), + ('allow network inet,', [_('Qualifier'), 'allow', _('Network Family'), 'inet', _('Socket Type'), _('ALL')]), + ('audit network inet stream,', [_('Qualifier'), 'audit', _('Network Family'), 'inet', _('Socket Type'), 'stream']), + ('audit deny network inet,', [_('Qualifier'), 'audit deny', _('Network Family'), 'inet', _('Socket Type'), _('ALL')]), + ) def _run_test(self, params, expected): - obj = NetworkRule._parse(params) + obj = NetworkRule.parse(params) self.assertEqual(obj.logprof_header(), expected) + class NetworkRuleReprTest(AATest): - tests = [ + tests = ( (NetworkRule('inet', 'stream'), '<NetworkRule> network inet stream,'), (NetworkRule.parse(' allow network inet stream, # foo'), '<NetworkRule> allow network inet stream, # foo'), - ] + ) + def _run_test(self, params, expected): self.assertEqual(str(params), expected) @@ -399,10 +425,10 @@ class NetworkRulesTest(AATest): def test_ruleset_1(self): ruleset = NetworkRuleset() - rules = [ + rules = ( 'network tcp,', 'network inet,', - ] + ) expected_raw = [ 'network tcp,', @@ -424,11 +450,11 @@ class NetworkRulesTest(AATest): def test_ruleset_2(self): ruleset = NetworkRuleset() - rules = [ + rules = ( 'network inet6 raw,', 'allow network inet,', 'deny network udp, # example comment', - ] + ) expected_raw = [ ' network inet6 raw,', @@ -469,9 +495,11 @@ class NetworkGlobTestAATest(AATest): # get_glob_ext is not available for network rules self.ruleset.get_glob_ext('network inet raw,') + class NetworkDeleteTestAATest(AATest): pass + class NetworkRulesetReprTest(AATest): def test_network_ruleset_repr(self): obj = NetworkRuleset() @@ -482,7 +510,6 @@ class NetworkRulesetReprTest(AATest): self.assertEqual(str(obj), expected) - setup_all_loops(__name__) if __name__ == '__main__': unittest.main(verbosity=1) diff --git a/utils/test/test-notify.py b/utils/test/test-notify.py index f82bf68e0..bb2b123d2 100644 --- a/utils/test/test-notify.py +++ b/utils/test/test-notify.py @@ -10,33 +10,35 @@ # ------------------------------------------------------------------ import unittest -from common_test import AATest, setup_all_loops from apparmor.common import AppArmorBug from apparmor.notify import get_last_login_timestamp, sane_timestamp +from common_test import AATest, setup_all_loops + class TestSane_timestamp(AATest): - tests = [ - (2524704400, False), # Sun Jan 2 03:46:40 CET 2050 - ( 944780400, False), # Fri Dec 10 00:00:00 CET 1999 - (1635026400, True ), # Sun Oct 24 00:00:00 CEST 2021 - ] + tests = ( + (2524704400, False), # Sun Jan 2 03:46:40 CET 2050 + (944780400, False), # Fri Dec 10 00:00:00 CET 1999 + (1635026400, True), # Sun Oct 24 00:00:00 CEST 2021 + ) def _run_test(self, params, expected): self.assertEqual(sane_timestamp(params), expected) + class TestGet_last_login_timestamp(AATest): - tests = [ - (['wtmp-x86_64', 'root' ], 1635070346), # Sun Oct 24 12:12:26 CEST 2021 - (['wtmp-x86_64', 'whoever' ], 0), - (['wtmp-s390x', 'root' ], 1626368763), # Thu Jul 15 19:06:03 CEST 2021 - (['wtmp-s390x', 'linux1' ], 1626368772), # Thu Jul 15 19:06:12 CEST 2021 - (['wtmp-s390x', 'whoever' ], 0), - (['wtmp-aarch64', 'guillaume' ], 1611562789), # Mon Jan 25 09:19:49 CET 2021 - (['wtmp-aarch64', 'whoever' ], 0), - (['wtmp-truncated', 'root' ], 0), - (['wtmp-truncated', 'whoever' ], 0), - ] + tests = ( + (('wtmp-x86_64', 'root'), 1635070346), # Sun Oct 24 12:12:26 CEST 2021 + (('wtmp-x86_64', 'whoever'), 0), + (('wtmp-s390x', 'root'), 1626368763), # Thu Jul 15 19:06:03 CEST 2021 + (('wtmp-s390x', 'linux1'), 1626368772), # Thu Jul 15 19:06:12 CEST 2021 + (('wtmp-s390x', 'whoever'), 0), + (('wtmp-aarch64', 'guillaume'), 1611562789), # Mon Jan 25 09:19:49 CET 2021 + (('wtmp-aarch64', 'whoever'), 0), + (('wtmp-truncated', 'root'), 0), + (('wtmp-truncated', 'whoever'), 0), + ) def _run_test(self, params, expected): filename, user = params diff --git a/utils/test/test-parser-simple-tests.py b/utils/test/test-parser-simple-tests.py index 2314960c8..00c2fde2a 100644 --- a/utils/test/test-parser-simple-tests.py +++ b/utils/test/test-parser-simple-tests.py @@ -9,12 +9,12 @@ # # ------------------------------------------------------------------ +import os import unittest -from common_test import AATest, setup_all_loops, setup_aa -import apparmor.aa as apparmor -import os -from apparmor.common import open_file_read, AppArmorException +import apparmor.aa as apparmor +from apparmor.common import AppArmorException, is_skippable_file, open_file_read +from common_test import AATest, setup_aa, setup_all_loops # This testcase will parse all parser/tst/simple_tests with parse_profile_data(), # except the files listed in one of the arrays below. @@ -38,7 +38,7 @@ skip_startswith = ( ) # testcases that should raise an exception, but don't -exception_not_raised = [ +exception_not_raised = ( # most abi/bad_* aren't detected as bad by the basic implementation in the tools 'abi/bad_10.sd', 'abi/bad_11.sd', @@ -76,6 +76,10 @@ exception_not_raised = [ 'file/bad_re_brace_1.sd', 'file/bad_re_brace_2.sd', 'file/bad_re_brace_3.sd', + 'mount/bad_1.sd', + 'mount/bad_2.sd', + 'mount/bad_3.sd', + 'mount/bad_4.sd', 'mount/bad_opt_10.sd', 'mount/bad_opt_11.sd', 'mount/bad_opt_12.sd', @@ -104,6 +108,17 @@ exception_not_raised = [ 'mount/bad_opt_26.sd', 'mount/bad_opt_27.sd', 'mount/bad_opt_28.sd', + 'mount/bad_opt_29.sd', + 'mount/bad_opt_30.sd', + 'mount/bad_opt_31.sd', + 'mount/bad_opt_32.sd', + 'mount/bad_opt_35.sd', + 'mount/bad_opt_36.sd', + 'mount/bad_opt_37.sd', + 'mount/bad_opt_38.sd', + 'mount/bad_opt_39.sd', + 'mount/bad_opt_40.sd', + 'mount/bad_opt_41.sd', 'profile/flags/flags_bad10.sd', 'profile/flags/flags_bad11.sd', 'profile/flags/flags_bad12.sd', @@ -141,7 +156,6 @@ exception_not_raised = [ 'profile/flags/flags_bad44.sd', 'profile/flags/flags_bad45.sd', 'profile/flags/flags_bad46.sd', - 'profile/simple_bad_no_close_brace4.sd', 'profile/profile_ns_bad8.sd', # 'profile :ns/t' without terminating ':' 'ptrace/bad_05.sd', # actually contains a capability rule with invalid (ptrace-related) keyword 'ptrace/bad_06.sd', # actually contains a capability rule with invalid (ptrace-related) keyword @@ -176,9 +190,6 @@ exception_not_raised = [ 'unix/bad_attr_5.sd', 'unix/bad_opt_5.sd', 'unix/bad_shutdown_3.sd', - 'vars/boolean/boolean_bad_2.sd', - 'vars/boolean/boolean_bad_3.sd', - 'vars/boolean/boolean_bad_4.sd', 'vars/vars_bad_3.sd', 'vars/vars_bad_4.sd', 'vars/vars_bad_5.sd', @@ -222,10 +233,10 @@ exception_not_raised = [ 'xtrans/simple_bad_conflicting_x_6.sd', 'xtrans/simple_bad_conflicting_x_8.sd', 'xtrans/x-conflict.sd', -] +) # testcases with lines that don't match any regex and end up as "unknown line" -unknown_line = [ +unknown_line = ( # 'other' keyword 'file/allow/ok_other_1.sd', 'file/allow/ok_other_2.sd', @@ -302,16 +313,13 @@ unknown_line = [ 'bare_include_tests/ok_84.sd', 'bare_include_tests/ok_85.sd', 'bare_include_tests/ok_86.sd', -] +) # testcases with various unexpected failures -syntax_failure = [ +syntax_failure = ( # missing profile keywords 'profile/re_named_ok2.sd', - # Syntax Error: Unexpected hat definition found (external hat) - 'change_hat/new_style4.sd', - # Syntax Errors caused by boolean conditions (parse_profile_data() gets confused by the closing '}') 'conditional/defined_1.sd', 'conditional/defined_2.sd', @@ -411,7 +419,8 @@ syntax_failure = [ 'vars/vars_dbus_8.sd', # Path doesn't start with / or variable: {/@{TLDS}/foo,/com/@{DOMAINS}} 'vars/vars_simple_assignment_12.sd', # Redefining existing variable @{BAR} ('\' not handled) 'bare_include_tests/ok_2.sd', # two #include<...> in one line -] +) + class TestParseParserTests(AATest): tests = [] # filled by parse_test_profiles() @@ -433,16 +442,17 @@ class TestParseParserTests(AATest): apparmor.active_profiles.init_file(params['file']) if expected: - apparmor.parse_profile_data(data, params['file'], 0) + apparmor.parse_profile_data(data, params['file'], 0, True) apparmor.active_profiles.get_all_merged_variables(params['file'], apparmor.include_list_recursive(apparmor.active_profiles.files[params['file']])) else: with self.assertRaises(AppArmorException): - apparmor.parse_profile_data(data, params['file'], 0) + apparmor.parse_profile_data(data, params['file'], 0, True) apparmor.active_profiles.get_all_merged_variables(params['file'], apparmor.include_list_recursive(apparmor.active_profiles.files[params['file']])) + def parse_test_profiles(file_with_path): - '''parse the test-related headers of a profile (for example EXRESULT) and add the profile to the set of tests''' + """parse the test-related headers of a profile (for example EXRESULT) and add the profile to the set of tests""" exresult = None exresult_found = False description = None @@ -511,11 +521,11 @@ def parse_test_profiles(file_with_path): def find_and_setup_test_profiles(profile_dir): - '''find all profiles in the given profile_dir, excluding + """find all profiles in the given profile_dir, excluding - skippable files - include directories - files in the main directory (readme, todo etc.) - ''' + """ skipped = 0 profile_dir = os.path.abspath(profile_dir) @@ -535,7 +545,7 @@ def find_and_setup_test_profiles(profile_dir): for file in files: file_with_path = os.path.join(root, file) - if not apparmor.is_skippable_file(file) and relpath != '.': + if not is_skippable_file(file) and relpath != '.': skipped += parse_test_profiles(file_with_path) if skipped: diff --git a/utils/test/test-pivot_root_parse.py b/utils/test/test-pivot_root_parse.py index 30701b90b..d33532595 100644 --- a/utils/test/test-pivot_root_parse.py +++ b/utils/test/test-pivot_root_parse.py @@ -9,20 +9,23 @@ # # ------------------------------------------------------------------ -import apparmor.aa as aa import unittest -from common_test import AAParseTest, setup_regex_tests, setup_aa + +import apparmor.aa as aa +from common_test import AAParseTest, setup_aa, setup_regex_tests + class AAParsePivotRootTest(AAParseTest): def setUp(self): self.parse_function = aa.parse_pivot_root_rule - tests = [ - ('pivot_root,', 'pivot_root base keyword'), - ('pivot_root /old,', 'pivot_root oldroot rule'), - ('pivot_root /old /new,', 'pivot_root old and new root rule'), + tests = ( + ('pivot_root,', 'pivot_root base keyword'), + ('pivot_root /old,', 'pivot_root oldroot rule'), + ('pivot_root /old /new,', 'pivot_root old and new root rule'), ('pivot_root /old /new -> /usr/bin/child,', 'pivot_root child rule'), - ] + ) + setup_aa(aa) if __name__ == '__main__': diff --git a/utils/test/test-profile-list.py b/utils/test/test-profile-list.py index 559f8a7d5..3b5bb3e29 100644 --- a/utils/test/test-profile-list.py +++ b/utils/test/test-profile-list.py @@ -9,23 +9,26 @@ # # ------------------------------------------------------------------ -import unittest -from common_test import AATest, setup_aa, setup_all_loops, write_file - -import apparmor.aa import os import shutil +import unittest +import apparmor.aa from apparmor.common import AppArmorBug, AppArmorException from apparmor.profile_list import ProfileList +from apparmor.profile_storage import ProfileStorage from apparmor.rule.abi import AbiRule from apparmor.rule.alias import AliasRule +from apparmor.rule.boolean import BooleanRule from apparmor.rule.include import IncludeRule from apparmor.rule.variable import VariableRule +from common_test import AATest, setup_aa, setup_all_loops, write_file + class TestAdd_profile(AATest): def AASetup(self): self.pl = ProfileList() + self.dummy_profile = ProfileStorage('TEST DUMMY', 'AATest_no_file', 'TEST') def testEmpty(self): self.assertEqual(self.pl.profile_names, {}) @@ -33,21 +36,21 @@ class TestAdd_profile(AATest): self.assertEqual('%s' % self.pl, "\n".join(['', '<ProfileList>', '', '</ProfileList>', ''])) def testAdd_profile_1(self): - self.pl.add_profile('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo') + self.pl.add_profile('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo', self.dummy_profile) self.assertEqual(self.pl.profile_names, {'foo': '/etc/apparmor.d/bin.foo'}) - self.assertEqual(self.pl.attachments, {'/bin/foo': '/etc/apparmor.d/bin.foo'}) + self.assertEqual(self.pl.attachments, {'/bin/foo': {'f': '/etc/apparmor.d/bin.foo', 'p': 'foo'}}) self.assertEqual(self.pl.profiles_in_file('/etc/apparmor.d/bin.foo'), ['foo']) self.assertEqual('%s' % self.pl, '\n<ProfileList>\n/etc/apparmor.d/bin.foo\n</ProfileList>\n') def testAdd_profile_2(self): - self.pl.add_profile('/etc/apparmor.d/bin.foo', None, '/bin/foo') + self.pl.add_profile('/etc/apparmor.d/bin.foo', None, '/bin/foo', self.dummy_profile) self.assertEqual(self.pl.profile_names, {}) - self.assertEqual(self.pl.attachments, {'/bin/foo': '/etc/apparmor.d/bin.foo'}) + self.assertEqual(self.pl.attachments, {'/bin/foo': {'f': '/etc/apparmor.d/bin.foo', 'p': '/bin/foo'}}) self.assertEqual(self.pl.profiles_in_file('/etc/apparmor.d/bin.foo'), ['/bin/foo']) self.assertEqual('%s' % self.pl, '\n<ProfileList>\n/etc/apparmor.d/bin.foo\n</ProfileList>\n') def testAdd_profile_3(self): - self.pl.add_profile('/etc/apparmor.d/bin.foo', 'foo', None) + self.pl.add_profile('/etc/apparmor.d/bin.foo', 'foo', None, self.dummy_profile) self.assertEqual(self.pl.profile_names, {'foo': '/etc/apparmor.d/bin.foo'}) self.assertEqual(self.pl.attachments, {}) self.assertEqual(self.pl.profiles_in_file('/etc/apparmor.d/bin.foo'), ['foo']) @@ -55,76 +58,93 @@ class TestAdd_profile(AATest): def testAdd_profileError_1(self): with self.assertRaises(AppArmorBug): - self.pl.add_profile('', 'foo', '/bin/foo') # no filename + self.pl.add_profile('', 'foo', '/bin/foo', self.dummy_profile) # no filename def testAdd_profileError_2(self): with self.assertRaises(AppArmorBug): - self.pl.add_profile('/etc/apparmor.d/bin.foo', None, None) # neither attachment or profile name + self.pl.add_profile('/etc/apparmor.d/bin.foo', None, None, self.dummy_profile) # neither attachment nor profile name def testAdd_profileError_list_nonexisting_file(self): - self.pl.add_profile('/etc/apparmor.d/bin.foo', 'foo', None) + self.pl.add_profile('/etc/apparmor.d/bin.foo', 'foo', None, self.dummy_profile) with self.assertRaises(AppArmorBug): self.pl.profiles_in_file('/etc/apparmor.d/not.found') # different filename def testAdd_profileError_twice_1(self): - self.pl.add_profile('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo') + self.pl.add_profile('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo', self.dummy_profile) with self.assertRaises(AppArmorException): - self.pl.add_profile('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo') + self.pl.add_profile('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo', self.dummy_profile) def testAdd_profileError_twice_2(self): - self.pl.add_profile('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo') + self.pl.add_profile('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo', self.dummy_profile) with self.assertRaises(AppArmorException): - self.pl.add_profile('/etc/apparmor.d/bin.foo', 'foo', None) + self.pl.add_profile('/etc/apparmor.d/bin.foo', 'foo', None, self.dummy_profile) def testAdd_profileError_twice_3(self): - self.pl.add_profile('/etc/apparmor.d/bin.foo', None, '/bin/foo') + self.pl.add_profile('/etc/apparmor.d/bin.foo', None, '/bin/foo', self.dummy_profile) with self.assertRaises(AppArmorException): - self.pl.add_profile('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo') + self.pl.add_profile('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo', self.dummy_profile) def testAdd_profileError_twice_4(self): - self.pl.add_profile('/etc/apparmor.d/bin.foo', None, '/bin/foo') + self.pl.add_profile('/etc/apparmor.d/bin.foo', None, '/bin/foo', self.dummy_profile) with self.assertRaises(AppArmorException): - self.pl.add_profile('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo') + self.pl.add_profile('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo', self.dummy_profile) def testAdd_profileError_twice_5(self): - self.pl.add_profile('/etc/apparmor.d/bin.foo', 'foo', None) + self.pl.add_profile('/etc/apparmor.d/bin.foo', 'foo', None, self.dummy_profile) with self.assertRaises(AppArmorException): - self.pl.add_profile('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo') + self.pl.add_profile('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo', self.dummy_profile) + + def testAdd_profileError_wrong_prof_type(self): + with self.assertRaises(AppArmorBug): + self.pl.add_profile('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo', 'wrong_type') + class TestFilename_from_profile_name(AATest): - tests = [ + tests = ( ('foo', '/etc/apparmor.d/bin.foo'), ('/bin/foo', None), ('bar', None), - ('/usr{,{/lib,/lib32,/lib64}/wine}/bin/wine{,-preloader,server}{,-staging-*,-vanilla-*}', '/etc/apparmor.d/usr.bin.wine'), - ('/usr/lib/wine/bin/wine-preloader-staging-foo', None), # no AARE matching for profile names - ] + ('/usr{,{/lib,/lib32,/lib64}/wine}/bin/wine{,-preloader,server}{,-staging-*,-vanilla-*}', '/etc/apparmor.d/usr.bin.wine'), + ('/usr/lib/wine/bin/wine-preloader-staging-foo', None), # no AARE matching for profile names + ) def AASetup(self): self.pl = ProfileList() - self.pl.add_profile('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo') - self.pl.add_profile('/etc/apparmor.d/usr.bin.wine', '/usr{,{/lib,/lib32,/lib64}/wine}/bin/wine{,-preloader,server}{,-staging-*,-vanilla-*}', '/usr{,{/lib,/lib32,/lib64}/wine}/bin/wine{,-preloader,server}{,-staging-*,-vanilla-*}') + self.dummy_profile = ProfileStorage('TEST DUMMY', 'AATest_no_file', 'TEST') + self.pl.add_profile('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo', self.dummy_profile) + self.pl.add_profile( + '/etc/apparmor.d/usr.bin.wine', + '/usr{,{/lib,/lib32,/lib64}/wine}/bin/wine{,-preloader,server}{,-staging-*,-vanilla-*}', + '/usr{,{/lib,/lib32,/lib64}/wine}/bin/wine{,-preloader,server}{,-staging-*,-vanilla-*}', + self.dummy_profile) def _run_test(self, params, expected): self.assertEqual(self.pl.filename_from_profile_name(params), expected) + class TestFilename_from_attachment(AATest): - tests = [ + 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), - ('/usr{,{/lib,/lib32,/lib64}/wine}/bin/wine{,-preloader,server}{,-staging-*,-vanilla-*}', '/etc/apparmor.d/usr.bin.wine'), # XXX should this really match, or should attachment matching only use AARE? - ('/usr/lib/wine/bin/wine-preloader-staging-foo', '/etc/apparmor.d/usr.bin.wine'), # AARE match - ] + ('/usr{,{/lib,/lib32,/lib64}/wine}/bin/wine{,-preloader,server}{,-staging-*,-vanilla-*}', '/etc/apparmor.d/usr.bin.wine'), # XXX should this really match, or should attachment matching only use AARE? + ('/usr/lib/wine/bin/wine-preloader-staging-foo', '/etc/apparmor.d/usr.bin.wine'), # AARE match + ) def AASetup(self): self.pl = ProfileList() - self.pl.add_profile('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo') - self.pl.add_profile('/etc/apparmor.d/bin.baz', 'baz', '/bin/ba*') - self.pl.add_profile('/etc/apparmor.d/bin.foobar', 'foobar', '/bin/foo{bar,baz}') - self.pl.add_profile('/etc/apparmor.d/usr.bin.wine', '/usr{,{/lib,/lib32,/lib64}/wine}/bin/wine{,-preloader,server}{,-staging-*,-vanilla-*}', '/usr{,{/lib,/lib32,/lib64}/wine}/bin/wine{,-preloader,server}{,-staging-*,-vanilla-*}') + self.dummy_profile = ProfileStorage('TEST DUMMY', 'AATest_no_file', 'TEST') + self.pl.add_profile('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo', self.dummy_profile) + self.pl.add_profile('/etc/apparmor.d/bin.baz', 'baz', '/bin/ba*', self.dummy_profile) + self.pl.add_profile('/etc/apparmor.d/bin.foobar', 'foobar', '/bin/foo{bar,baz}', self.dummy_profile) + self.pl.add_profile('/etc/apparmor.d/bin.asdf', None, '/bin/asdf', self.dummy_profile) + self.pl.add_profile( + '/etc/apparmor.d/usr.bin.wine', + 'wine', + '/usr{,{/lib,/lib32,/lib64}/wine}/bin/wine{,-preloader,server}{,-staging-*,-vanilla-*}', + self.dummy_profile) def _run_test(self, params, expected): self.assertEqual(self.pl.filename_from_attachment(params), expected) @@ -133,6 +153,27 @@ class TestFilename_from_attachment(AATest): with self.assertRaises(AppArmorBug): self.pl.filename_from_attachment('foo') +class TestProfile_from_attachment(TestFilename_from_attachment): + # uses AASetup from TestFilename_from_attachment + tests = ( + ('/bin/foo', 'foo'), + ('/bin/baz', 'baz'), + ('/bin/foobar', 'foobar'), + ('/bin/asdf', '/bin/asdf'), + ('@{foo}', None), # XXX variables not supported yet (and @{foo} isn't defined in this test) + ('/bin/404', None), + ('/usr{,{/lib,/lib32,/lib64}/wine}/bin/wine{,-preloader,server}{,-staging-*,-vanilla-*}', 'wine'), # XXX should this really match, or should attachment matching only use AARE? + ('/usr/lib/wine/bin/wine-preloader-staging-foo', 'wine'), # AARE match + ) + + def _run_test(self, params, expected): + self.assertEqual(self.pl.profile_from_attachment(params), expected) + + def test_non_path_attachment(self): + with self.assertRaises(AppArmorBug): + self.pl.profile_from_attachment('foo') + + class TestAdd_inc_ie(AATest): def AASetup(self): self.pl = ProfileList() @@ -152,7 +193,7 @@ class TestAdd_inc_ie(AATest): def testAdd_inc_ie_error_1(self): with self.assertRaises(AppArmorBug): - self.pl.add_inc_ie('/etc/apparmor.d/bin.foo', 'tunables/global') # str insteadd of IncludeRule + self.pl.add_inc_ie('/etc/apparmor.d/bin.foo', 'tunables/global') # str instead of IncludeRule self.assertEqual(list(self.pl.files.keys()), []) def test_dedup_inc_ie_1(self): @@ -170,6 +211,7 @@ class TestAdd_inc_ie(AATest): self.pl.delete_preamble_duplicates('/file/not/found') self.assertEqual(list(self.pl.files.keys()), []) + class TestAdd_abi(AATest): def AASetup(self): self.pl = ProfileList() @@ -189,7 +231,7 @@ class TestAdd_abi(AATest): def testAdd_abi_error_1(self): with self.assertRaises(AppArmorBug): - self.pl.add_abi('/etc/apparmor.d/bin.foo', 'abi/4.19') # str insteadd of AbiRule + self.pl.add_abi('/etc/apparmor.d/bin.foo', 'abi/4.19') # str instead of AbiRule self.assertEqual(list(self.pl.files.keys()), []) def test_dedup_abi_1(self): @@ -201,6 +243,7 @@ class TestAdd_abi(AATest): self.assertEqual(self.pl.get_clean('/etc/apparmor.d/bin.foo'), ['abi <abi/4.19>,', '']) self.assertEqual(self.pl.get_raw('/etc/apparmor.d/bin.foo'), ['abi <abi/4.19>,', '']) + class TestAdd_alias(AATest): def AASetup(self): self.pl = ProfileList() @@ -227,17 +270,17 @@ class TestAdd_alias(AATest): def testAdd_alias_error_1(self): with self.assertRaises(AppArmorBug): - self.pl.add_alias('/etc/apparmor.d/bin.foo', AliasRule(None, '/foo')) # alias None insteadd of str + self.pl.add_alias('/etc/apparmor.d/bin.foo', AliasRule(None, '/foo')) # alias None instead of str self.assertEqual(list(self.pl.files.keys()), []) def testAdd_alias_error_2(self): with self.assertRaises(AppArmorBug): - self.pl.add_alias('/etc/apparmor.d/bin.foo', AliasRule('/foo', None)) # target None insteadd of str + self.pl.add_alias('/etc/apparmor.d/bin.foo', AliasRule('/foo', None)) # target None instead of str self.assertEqual(list(self.pl.files.keys()), []) def testAdd_alias_error_3(self): with self.assertRaises(AppArmorBug): - self.pl.add_alias('/etc/apparmor.d/bin.foo', 'alias /foo -> /bar,') # str insteadd of AliasRule + self.pl.add_alias('/etc/apparmor.d/bin.foo', 'alias /foo -> /bar,') # str instead of AliasRule self.assertEqual(list(self.pl.files.keys()), []) def test_dedup_alias_1(self): @@ -250,6 +293,7 @@ class TestAdd_alias(AATest): self.assertEqual(self.pl.get_clean('/etc/apparmor.d/bin.foo'), ['alias /foo -> /bar,', 'alias /foo -> /another_target,', '']) self.assertEqual(self.pl.get_raw('/etc/apparmor.d/bin.foo'), ['alias /foo -> /bar,', 'alias /foo -> /another_target,', '']) + class TestAdd_variable(AATest): def AASetup(self): self.pl = ProfileList() @@ -269,7 +313,7 @@ class TestAdd_variable(AATest): def testAdd_variable_error_1(self): with self.assertRaises(AppArmorBug): - self.pl.add_variable('/etc/apparmor.d/bin.foo', '@{foo}') # str insteadd of IncludeRule + self.pl.add_variable('/etc/apparmor.d/bin.foo', '@{foo}') # str instead of IncludeRule self.assertEqual(list(self.pl.files.keys()), []) def test_dedup_variable_1(self): @@ -287,6 +331,30 @@ class TestAdd_variable(AATest): self.pl.delete_preamble_duplicates('/file/not/found') self.assertEqual(list(self.pl.files.keys()), []) + +class TestAdd_boolean(AATest): + def AASetup(self): + self.pl = ProfileList() + + def testAdd_variable_1(self): + self.pl.add_boolean('/etc/apparmor.d/bin.foo', BooleanRule('$foo', 'true')) + self.assertEqual(list(self.pl.files.keys()), ['/etc/apparmor.d/bin.foo']) + self.assertEqual(self.pl.get_clean('/etc/apparmor.d/bin.foo'), ['$foo = true', '']) + self.assertEqual(self.pl.get_raw('/etc/apparmor.d/bin.foo'), ['$foo = true', '']) + + def testAdd_variable_2(self): + self.pl.add_boolean('/etc/apparmor.d/bin.foo', BooleanRule('$foo', 'true')) + self.pl.add_boolean('/etc/apparmor.d/bin.foo', BooleanRule('$bar', 'false')) + self.assertEqual(list(self.pl.files.keys()), ['/etc/apparmor.d/bin.foo']) + self.assertEqual(self.pl.get_clean('/etc/apparmor.d/bin.foo'), ['$foo = true', '$bar = false', '']) + self.assertEqual(self.pl.get_raw('/etc/apparmor.d/bin.foo'), ['$foo = true', '$bar = false', '']) + + def testAdd_variable_error_1(self): + with self.assertRaises(AppArmorBug): + self.pl.add_boolean('/etc/apparmor.d/bin.foo', '$foo') # str instead of IncludeRule + self.assertEqual(list(self.pl.files.keys()), []) + + class TestGet(AATest): def AASetup(self): self.pl = ProfileList() @@ -299,8 +367,9 @@ class TestGet(AATest): with self.assertRaises(AppArmorBug): self.pl.get_raw('/etc/apparmor.d/not.found') + class AaTest_get_all_merged_variables(AATest): - tests = [] + tests = () def AASetup(self): self.createTmpdir() @@ -321,7 +390,9 @@ class AaTest_get_all_merged_variables(AATest): def test_unchanged(self): self._load_profiles() prof_filename = os.path.join(self.profile_dir, 'usr.sbin.dnsmasq') - vars = apparmor.aa.active_profiles.get_all_merged_variables(os.path.join(self.profile_dir, 'usr.sbin.dnsmasq'), apparmor.aa.include_list_recursive(apparmor.aa.active_profiles.files[prof_filename])) + vars = apparmor.aa.active_profiles.get_all_merged_variables( + os.path.join(self.profile_dir, 'usr.sbin.dnsmasq'), + apparmor.aa.include_list_recursive(apparmor.aa.active_profiles.files[prof_filename], True)) self.assertEqual(vars['@{TFTP_DIR}'], {'/var/tftp', '/srv/tftp', '/srv/tftpboot'}) self.assertEqual(vars['@{HOME}'], {'@{HOMEDIRS}/*/', '/root/'}) @@ -329,7 +400,9 @@ class AaTest_get_all_merged_variables(AATest): write_file(self.profile_dir, 'tunables/home.d/extend_home', '@{HOME} += /my/castle/') self._load_profiles() prof_filename = os.path.join(self.profile_dir, 'usr.sbin.dnsmasq') - vars = apparmor.aa.active_profiles.get_all_merged_variables(os.path.join(self.profile_dir, 'usr.sbin.dnsmasq'), apparmor.aa.include_list_recursive(apparmor.aa.active_profiles.files[prof_filename])) + vars = apparmor.aa.active_profiles.get_all_merged_variables( + os.path.join(self.profile_dir, 'usr.sbin.dnsmasq'), + apparmor.aa.include_list_recursive(apparmor.aa.active_profiles.files[prof_filename], True)) self.assertEqual(vars['@{TFTP_DIR}'], {'/var/tftp', '/srv/tftp', '/srv/tftpboot'}) self.assertEqual(vars['@{HOME}'], {'@{HOMEDIRS}/*/', '/root/', '/my/castle/'}) @@ -338,7 +411,9 @@ class AaTest_get_all_merged_variables(AATest): write_file(self.profile_dir, 'tunables/home.d/moving_around', '@{HOME} += /on/the/road/') self._load_profiles() prof_filename = os.path.join(self.profile_dir, 'usr.sbin.dnsmasq') - vars = apparmor.aa.active_profiles.get_all_merged_variables(os.path.join(self.profile_dir, 'usr.sbin.dnsmasq'), apparmor.aa.include_list_recursive(apparmor.aa.active_profiles.files[prof_filename])) + vars = apparmor.aa.active_profiles.get_all_merged_variables( + os.path.join(self.profile_dir, 'usr.sbin.dnsmasq'), + apparmor.aa.include_list_recursive(apparmor.aa.active_profiles.files[prof_filename], True)) self.assertEqual(vars['@{TFTP_DIR}'], {'/var/tftp', '/srv/tftp', '/srv/tftpboot'}) self.assertEqual(vars['@{HOME}'], {'@{HOMEDIRS}/*/', '/root/', '/my/castle/', '/on/the/road/'}) @@ -347,7 +422,9 @@ class AaTest_get_all_merged_variables(AATest): write_file(self.profile_dir, 'dummy_profile', 'include <tunables/global>\n@{HOME} += /in/the/profile/') self._load_profiles() prof_filename = os.path.join(self.profile_dir, 'dummy_profile') - vars = apparmor.aa.active_profiles.get_all_merged_variables(os.path.join(self.profile_dir, 'dummy_profile'), apparmor.aa.include_list_recursive(apparmor.aa.active_profiles.files[prof_filename])) + vars = apparmor.aa.active_profiles.get_all_merged_variables( + os.path.join(self.profile_dir, 'dummy_profile'), + apparmor.aa.include_list_recursive(apparmor.aa.active_profiles.files[prof_filename], True)) self.assertEqual(vars.get('@{TFTP_DIR}', None), None) self.assertEqual(vars['@{HOME}'], {'@{HOMEDIRS}/*/', '/root/', '/my/castle/', '/in/the/profile/'}) @@ -356,18 +433,40 @@ class AaTest_get_all_merged_variables(AATest): self._load_profiles() prof_filename = os.path.join(self.profile_dir, 'usr.sbin.dnsmasq') with self.assertRaises(AppArmorException): - apparmor.aa.active_profiles.get_all_merged_variables(os.path.join(self.profile_dir, 'usr.sbin.dnsmasq'), apparmor.aa.include_list_recursive(apparmor.aa.active_profiles.files[prof_filename])) + apparmor.aa.active_profiles.get_all_merged_variables( + os.path.join(self.profile_dir, 'usr.sbin.dnsmasq'), + apparmor.aa.include_list_recursive(apparmor.aa.active_profiles.files[prof_filename], True)) def test_add_to_nonexisting(self): write_file(self.profile_dir, 'tunables/home.d/no_such_var', '@{NO_SUCH_HOME} += /my/castle/') # add to non-existing variable self._load_profiles() prof_filename = os.path.join(self.profile_dir, 'usr.sbin.dnsmasq') with self.assertRaises(AppArmorException): - apparmor.aa.active_profiles.get_all_merged_variables(os.path.join(self.profile_dir, 'usr.sbin.dnsmasq'), apparmor.aa.include_list_recursive(apparmor.aa.active_profiles.files[prof_filename])) + apparmor.aa.active_profiles.get_all_merged_variables( + os.path.join(self.profile_dir, 'usr.sbin.dnsmasq'), + apparmor.aa.include_list_recursive(apparmor.aa.active_profiles.files[prof_filename], True)) def test_vars_from_nonexisting_profile(self): with self.assertRaises(AppArmorBug): - apparmor.aa.active_profiles.get_all_merged_variables(os.path.join(self.profile_dir, 'file.not.found'), list()) + apparmor.aa.active_profiles.get_all_merged_variables( + os.path.join(self.profile_dir, 'file.not.found'), list()) + + +class TestGet_profile_and_childs(AATest): + def AASetup(self): + self.pl = ProfileList() + self.dummy_profile = ProfileStorage('TEST DUMMY', 'AATest_no_file', 'TEST') + + def testGet_profile_and_childs1(self): + self.pl.add_profile('/etc/apparmor.d/bin.foo', 'bafoo', '/bin/bafoo', self.dummy_profile) + self.pl.add_profile('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo', self.dummy_profile) + self.pl.add_profile('/etc/apparmor.d/bin.foo', 'foobar', '/bin/foobar', self.dummy_profile) + self.pl.add_profile('/etc/apparmor.d/bin.foo', 'foo//bar', '/bin/foo//bar', self.dummy_profile) + self.pl.add_profile('/etc/apparmor.d/bin.foo', 'foo//xy', '/bin/foo//xy', self.dummy_profile) + + expected = ['foo', 'foo//bar', 'foo//xy'] + + self.assertEqual(list(self.pl.get_profile_and_childs('foo')), expected) setup_aa(apparmor.aa) diff --git a/utils/test/test-profile-storage.py b/utils/test/test-profile-storage.py index bc219ea75..a970a1457 100644 --- a/utils/test/test-profile-storage.py +++ b/utils/test/test-profile-storage.py @@ -1,7 +1,7 @@ #! /usr/bin/python3 # ------------------------------------------------------------------ # -# Copyright (C) 2017 Christian Boltz <apparmor@cboltz.de> +# Copyright (C) 2017-2021 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 @@ -10,10 +10,11 @@ # ------------------------------------------------------------------ import unittest -from common_test import AATest, setup_all_loops -from apparmor.common import AppArmorBug +from apparmor.common import AppArmorBug, AppArmorException from apparmor.profile_storage import ProfileStorage, add_or_remove_flag, split_flags, var_transform +from common_test import AATest, setup_all_loops + class TestUnknownKey(AATest): def AASetup(self): @@ -35,58 +36,187 @@ class TestUnknownKey(AATest): with self.assertRaises(AppArmorBug): self.storage['foo'] = 'bar' + +class AaTest_get_header(AATest): + tests = ( + # name embedded_hat depth flags attachment xattrs prof.keyw. comment expected + (('/foo', False, 1, 'complain', '', '', False, ''), ' /foo flags=(complain) {'), + (('/foo', True, 1, 'complain', '', '', False, ''), ' profile /foo flags=(complain) {'), + (('/foo sp', False, 2, 'complain', '', '', False, ''), ' "/foo sp" flags=(complain) {'), + (('/foo', True, 2, 'complain', '', '', False, ''), ' profile /foo flags=(complain) {'), + (('/foo', False, 0, None, '', '', False, ''), '/foo {'), + (('/foo', False, 0, None, '', 'user.foo=bar', False, ''), '/foo xattrs=(user.foo=bar) {'), + (('/foo', True, 0, None, '', '', False, ''), 'profile /foo {'), + (('bar', False, 1, 'complain', '', '', False, ''), ' profile bar flags=(complain) {'), + (('bar', False, 1, 'complain', '/foo', '', False, ''), ' profile bar /foo flags=(complain) {'), + (('bar', True, 1, 'complain', '/foo', '', False, ''), ' profile bar /foo flags=(complain) {'), + (('bar baz', False, 1, None, '/foo', '', False, ''), ' profile "bar baz" /foo {'), + (('bar', True, 1, None, '/foo', '', False, ''), ' profile bar /foo {'), + (('bar baz', False, 1, 'complain', '/foo sp', '', False, ''), ' profile "bar baz" "/foo sp" flags=(complain) {'), + (('bar baz', False, 1, 'complain', '/foo sp', 'user.foo=bar', False, ''), ' profile "bar baz" "/foo sp" xattrs=(user.foo=bar) flags=(complain) {'), + (('^foo', False, 1, 'complain', '', '', False, ''), ' profile ^foo flags=(complain) {'), + (('^foo', True, 1, 'complain', '', '', False, ''), ' ^foo flags=(complain) {'), + (('^foo', True, 1.5, 'complain', '', '', False, ''), ' ^foo flags=(complain) {'), + (('^foo', True, 1.3, 'complain', '', '', False, ''), ' ^foo flags=(complain) {'), + (('/foo', False, 1, 'complain', '', '', True, ''), ' profile /foo flags=(complain) {'), + (('/foo', True, 1, 'complain', '', '', True, ''), ' profile /foo flags=(complain) {'), + (('/foo', False, 1, 'complain', '', '', False, '# x'), ' /foo flags=(complain) { # x'), + (('/foo', True, 1, None, '', '', False, '# x'), ' profile /foo { # x'), + (('/foo', False, 1, None, '', '', True, '# x'), ' profile /foo { # x'), + (('/foo', True, 1, 'complain', '', '', True, '# x'), ' profile /foo flags=(complain) { # x'), + ) + + def _run_test(self, params, expected): + name = params[0] + embedded_hat = params[1] + depth = params[2] + + prof_storage = ProfileStorage(name, '', 'test') + prof_storage['flags'] = params[3] + prof_storage['attachment'] = params[4] + prof_storage['xattrs'] = params[5] + prof_storage['profile_keyword'] = params[6] + prof_storage['header_comment'] = params[7] + + result = prof_storage.get_header(depth, name, embedded_hat) + self.assertEqual(result, [expected]) + + +class AaTest_get_header_01(AATest): + tests = ( + ({'name': '/foo', 'depth': 1, 'flags': 'complain' }, ' /foo flags=(complain) {'), + ({'name': '/foo', 'depth': 1, 'flags': 'complain', 'profile_keyword': True }, ' profile /foo flags=(complain) {'), + ({'name': '/foo', 'flags': 'complain' }, '/foo flags=(complain) {'), + ({'name': '/foo', 'xattrs': 'user.foo=bar', 'flags': 'complain' }, '/foo xattrs=(user.foo=bar) flags=(complain) {'), + ({'name': '/foo', 'xattrs': 'user.foo=bar', 'embedded_hat': True}, 'profile /foo xattrs=(user.foo=bar) {'), + ) + + def _run_test(self, params, expected): + name = params['name'] + embedded_hat = params.get('embedded_hat', False) + depth = params.get('depth', 0) + + prof_storage = ProfileStorage(name, '', 'test') + + for param in ('flags', 'attachment', 'profile_keyword', 'header_comment', 'xattrs'): + if params.get(param) is not None: + prof_storage[param] = params[param] + + result = prof_storage.get_header(depth, name, embedded_hat) + self.assertEqual(result, [expected]) + + +class TestSetInvalid(AATest): + tests = ( + (('profile_keyword', None), AppArmorBug), # expects bool + (('profile_keyword', 'foo'), AppArmorBug), + (('attachment', False), AppArmorBug), # expects string + (('attachment', None), AppArmorBug), + (('filename', True), AppArmorBug), # expects string or None + (('allow', None), AppArmorBug), # doesn't allow overwriting at all + ) + + def _run_test(self, params, expected): + self.storage = ProfileStorage('/test/foo', 'hat', 'TEST') + with self.assertRaises(expected): + self.storage[params[0]] = params[1] + + +class AaTest_parse_profile_start(AATest): + tests = ( + # profile start line profile hat profile hat attachment xattrs flags pps_set_hat_external + (('/foo {', None, None), ('/foo', '/foo', '', '', None, False)), + (('/foo (complain) {', None, None), ('/foo', '/foo', '', '', 'complain', False)), + (('profile foo /foo {', None, None), ('foo', 'foo', '/foo', '', None, False)), # named profile + (('profile /foo {', '/bar', None), ('/bar', '/foo', '', '', None, False)), # child profile + (('/foo//bar {', None, None), ('/foo', 'bar', '', '', None, True)), # external hat + (('profile "/foo" (complain) {', None, None), ('/foo', '/foo', '', '', 'complain', False)), + (('profile "/foo" xattrs=(user.bar=bar) {', None, None), ('/foo', '/foo', '', 'user.bar=bar', None, False)), + (('profile "/foo" xattrs=(user.bar=bar user.foo=*) {', None, None), ('/foo', '/foo', '', 'user.bar=bar user.foo=*', None, False)), + (('/usr/bin/xattrs-test xattrs=(myvalue="foo.bar") {', None, None), ('/usr/bin/xattrs-test', '/usr/bin/xattrs-test', '', 'myvalue="foo.bar"', None, False)), + ) + + def _run_test(self, params, expected): + (profile, hat, prof_storage) = ProfileStorage.parse(params[0], 'somefile', 1, params[1], params[2]) + + self.assertEqual(profile, expected[0]) + self.assertEqual(hat, expected[1]) + self.assertEqual(prof_storage['attachment'], expected[2]) + self.assertEqual(prof_storage['xattrs'], expected[3]) + self.assertEqual(prof_storage['flags'], expected[4]) + self.assertEqual(prof_storage['is_hat'], False) + self.assertEqual(prof_storage['external'], expected[5]) + + +class AaTest_parse_profile_start_errors(AATest): + tests = ( + (('/foo///bar///baz {', None, None), AppArmorException), # XXX deeply nested external hat + (('profile asdf {', '/foo', '/bar'), AppArmorException), # nested child profile + (('/foo {', '/bar', None), AppArmorException), # child profile without profile keyword + (('/foo {', '/bar', '/bar'), AppArmorException), # child profile without profile keyword + (('xy', '/bar', None), AppArmorBug), # not a profile start + (('xy', '/bar', '/bar'), AppArmorBug), # not a profile start + ) + + def _run_test(self, params, expected): + with self.assertRaises(expected): + ProfileStorage.parse(params[0], 'somefile', 1, params[1], params[2]) + + class AaTest_add_or_remove_flag(AATest): - tests = [ - # existing flag(s) flag to change add or remove? expected flags - ([ [], 'complain', True ], ['complain'] ), - ([ [], 'complain', False ], [] ), - ([ ['complain'], 'complain', True ], ['complain'] ), - ([ ['complain'], 'complain', False ], [] ), - ([ [], 'audit', True ], ['audit'] ), - ([ [], 'audit', False ], [] ), - ([ ['complain'], 'audit', True ], ['audit', 'complain'] ), - ([ ['complain'], 'audit', False ], ['complain'] ), - ([ '', 'audit', True ], ['audit'] ), - ([ None, 'audit', False ], [] ), - ([ 'complain', 'audit', True ], ['audit', 'complain'] ), - ([ ' complain ', 'audit', False ], ['complain'] ), - ([ 'audit complain', ['audit', 'complain'], False ], [] ), - ([ 'audit complain', 'audit complain', False ], [] ), - ([ 'audit complain', ['audit', 'enforce'], False ], ['complain'] ), - ([ 'audit complain', 'audit enforce', False ], ['complain'] ), - ([ '', ['audit', 'complain'], True ], ['audit', 'complain'] ), - ([ '', 'audit complain', True ], ['audit', 'complain'] ), - ([ 'audit', ['audit', 'enforce'], True ], ['audit', 'enforce'] ), - ([ 'audit', 'audit enforce', True ], ['audit', 'enforce'] ), - ] + tests = ( + # existing flag(s) flag to change add or remove? expected flags + (([], 'complain', True), ['complain']), + (([], 'complain', False), []), + ((['complain'], 'complain', True), ['complain']), + ((['complain'], 'complain', False), []), + (([], 'audit', True), ['audit']), + (([], 'audit', False), []), + ((['complain'], 'audit', True), ['audit', 'complain']), + ((['complain'], 'audit', False), ['complain']), + (('', 'audit', True), ['audit']), + ((None, 'audit', False), []), + (('complain', 'audit', True), ['audit', 'complain']), + ((' complain ', 'audit', False), ['complain']), + (('audit complain', ('audit', 'complain'), False), []), + (('audit complain', 'audit complain', False), []), + (('audit complain', ('audit', 'enforce'), False), ['complain']), + (('audit complain', 'audit enforce', False), ['complain']), + (('', ('audit', 'complain'), True), ['audit', 'complain']), + (('', 'audit complain', True), ['audit', 'complain']), + (('audit', ('audit', 'enforce'), True), ['audit', 'enforce']), + (('audit', 'audit enforce', True), ['audit', 'enforce']), + ) def _run_test(self, params, expected): - new_flags = add_or_remove_flag(params[0], params[1], params[2]) + new_flags = add_or_remove_flag(*params) self.assertEqual(new_flags, expected) + class AaTest_split_flags(AATest): - tests = [ - (None , [] ), - ('' , [] ), - (' ' , [] ), - (' , ' , [] ), - ('complain' , ['complain'] ), - (' complain attach_disconnected' , ['attach_disconnected', 'complain'] ), - (' complain , attach_disconnected' , ['attach_disconnected', 'complain'] ), - (' complain , , audit , , ' , ['audit', 'complain'] ), - ] + tests = ( + (None, []), + ('', []), + (' ', []), + (' , ', []), + ('complain', ['complain']), + (' complain attach_disconnected', ['attach_disconnected', 'complain']), + (' complain , attach_disconnected', ['attach_disconnected', 'complain']), + (' complain , , audit , , ', ['audit', 'complain']), + ) def _run_test(self, params, expected): split = split_flags(params) self.assertEqual(split, expected) + class AaTest_var_transform(AATest): - tests = [ - (['foo', ''], '"" foo' ), - (['foo', 'bar'], 'bar foo' ), - ([''], '""' ), - (['bar baz', 'foo'], '"bar baz" foo' ), - ] + tests = ( + (('foo', ''), '"" foo'), + (('foo', 'bar'), 'bar foo'), + (('',), '""'), + (('bar baz', 'foo'), '"bar baz" foo'), + ) def _run_test(self, params, expected): self.assertEqual(var_transform(params), expected) diff --git a/utils/test/test-profiles.py b/utils/test/test-profiles.py new file mode 100644 index 000000000..b8300a0c5 --- /dev/null +++ b/utils/test/test-profiles.py @@ -0,0 +1,46 @@ +#! /usr/bin/python3 +# ------------------------------------------------------------------ +# +# Copyright (C) 2020 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 + +import apparmor.aa as aa +from common_test import AATest, setup_aa, setup_all_loops + +# If a profile can't be parsed by the tools, add it to skip_active_profiles or skip_extra_profiles. +# Add only the filename (without path), for example 'usr.bin.foo'. +# These skip lists are meant as a temporary solution, and should be empty on release. +skip_active_profiles = [] + +skip_extra_profiles = [] + + +class TestFoo(AATest): + # Make sure the python code can parse all profiles shipped with AppArmor. + # If this fails, read_profiles() / read_inactive_profiles() will raise an exception. + # + # Checking for the number of read profiles is mostly done to ensure *something* is read + # (to make sure an empty or non-existing directory won't make this test useless). + + def test_active_profiles(self): + aa.read_profiles(skip_profiles=skip_active_profiles) + + self.assertGreaterEqual(len(aa.active_profiles.profile_names), 42) + + def test_extra_profiles(self): + aa.read_inactive_profiles(skip_profiles=skip_extra_profiles) + + self.assertGreaterEqual(len(aa.extra_profiles.profile_names), 100) + + +setup_aa(aa) +setup_all_loops(__name__) +if __name__ == '__main__': + unittest.main(verbosity=1) diff --git a/utils/test/test-ptrace.py b/utils/test/test-ptrace.py index e8b637fbe..6eac4bb02 100644 --- a/utils/test/test-ptrace.py +++ b/utils/test/test-ptrace.py @@ -15,17 +15,21 @@ import unittest from collections import namedtuple -from common_test import AATest, setup_all_loops -from apparmor.rule.ptrace import PtraceRule, PtraceRuleset -from apparmor.rule import BaseRule -from apparmor.common import AppArmorException, AppArmorBug +from apparmor.common import AppArmorBug, AppArmorException from apparmor.logparser import ReadLog +from apparmor.rule import BaseRule +from apparmor.rule.ptrace import PtraceRule, PtraceRuleset from apparmor.translations import init_translation +from common_test import AATest, setup_all_loops + _ = init_translation() -exp = namedtuple('exp', ['audit', 'allow_keyword', 'deny', 'comment', - 'access', 'all_access', 'peer', 'all_peers']) +exp = namedtuple( + 'exp', ('audit', 'allow_keyword', 'deny', 'comment', 'access', 'all_access', 'peer', + 'all_peers'), +) + # # --- tests for single PtraceRule --- # @@ -43,23 +47,24 @@ class PtraceTest(AATest): self.assertEqual(expected.deny, obj.deny) self.assertEqual(expected.comment, obj.comment) + class PtraceTestParse(PtraceTest): - tests = [ - # PtraceRule object audit allow deny comment access all? peer all? - ('ptrace,' , exp(False, False, False, '', None , True , None, True )), -# ('ptrace (),' , exp(False, False, False, '', None , True , None, True )), # XXX also broken in SignalRule? - ('ptrace read,' , exp(False, False, False, '', {'read'}, False, None, True )), - ('ptrace (read, tracedby),' , exp(False, False, False, '', {'read', 'tracedby'}, False, None, True )), - ('ptrace read,' , exp(False, False, False, '', {'read'}, False, None, True )), - ('deny ptrace read, # cmt' , exp(False, False, True , ' # cmt', {'read'}, False, None, True )), - ('audit allow ptrace,' , exp(True , True , False, '', None , True , None, True )), - ('ptrace peer=unconfined,' , exp(False, False, False, '', None , True , 'unconfined', False )), - ('ptrace peer="unconfined",' , exp(False, False, False, '', None , True , 'unconfined', False )), - ('ptrace read,' , exp(False, False, False, '', {'read'}, False, None, True )), - ('ptrace peer=/foo,' , exp(False, False, False, '', None , True , '/foo', False )), - ('ptrace r peer=/foo,' , exp(False, False, False, '', {'r'}, False, '/foo', False )), - ('ptrace r peer="/foo bar",' , exp(False, False, False, '', {'r'}, False, '/foo bar', False )), - ] + tests = ( + # PtraceRule object audit allow deny comment access all? peer all? + ('ptrace,', exp(False, False, False, '', None, True, None, True)), + # ('ptrace (),', exp(False, False, False, '', None, True, None, True)), # XXX also broken in SignalRule? + ('ptrace read,', exp(False, False, False, '', {'read'}, False, None, True)), + ('ptrace (read, tracedby),', exp(False, False, False, '', {'read', 'tracedby'}, False, None, True)), + ('ptrace read,', exp(False, False, False, '', {'read'}, False, None, True)), + ('deny ptrace read, # cmt', exp(False, False, True, ' # cmt', {'read'}, False, None, True)), + ('audit allow ptrace,', exp(True, True, False, '', None, True, None, True)), + ('ptrace peer=unconfined,', exp(False, False, False, '', None, True, 'unconfined', False)), + ('ptrace peer="unconfined",', exp(False, False, False, '', None, True, 'unconfined', False)), + ('ptrace read,', exp(False, False, False, '', {'read'}, False, None, True)), + ('ptrace peer=/foo,', exp(False, False, False, '', None, True, '/foo', False)), + ('ptrace r peer=/foo,', exp(False, False, False, '', {'r'}, False, '/foo', False)), + ('ptrace r peer="/foo bar",', exp(False, False, False, '', {'r'}, False, '/foo bar', False)), + ) def _run_test(self, rawrule, expected): self.assertTrue(PtraceRule.match(rawrule)) @@ -67,27 +72,28 @@ class PtraceTestParse(PtraceTest): self.assertEqual(rawrule.strip(), obj.raw_rule) self._compare_obj(obj, expected) + class PtraceTestParseInvalid(PtraceTest): - tests = [ - ('ptrace foo,' , AppArmorException), - ('ptrace foo bar,' , AppArmorException), - ('ptrace foo int,' , AppArmorException), - ('ptrace read bar,' , AppArmorException), - ('ptrace read tracedby,' , AppArmorException), - ('ptrace peer=,' , AppArmorException), - ] + tests = ( + ('ptrace foo,', AppArmorException), + ('ptrace foo bar,', AppArmorException), + ('ptrace foo int,', AppArmorException), + ('ptrace read bar,', AppArmorException), + ('ptrace read tracedby,', AppArmorException), + ('ptrace peer=,', AppArmorException), + ) def _run_test(self, rawrule, expected): self.assertTrue(PtraceRule.match(rawrule)) # the above invalid rules still match the main regex! with self.assertRaises(expected): PtraceRule.parse(rawrule) + class PtraceTestParseFromLog(PtraceTest): def test_ptrace_from_log(self): parser = ReadLog('', '', '') event = 'type=AVC msg=audit(1409700683.304:547661): apparmor="DENIED" operation="ptrace" profile="/home/ubuntu/bzr/apparmor/tests/regression/apparmor/ptrace" pid=22465 comm="ptrace" requested_mask="tracedby" denied_mask="tracedby" peer="/home/ubuntu/bzr/apparmor/tests/regression/apparmor/ptrace"' - parsed_event = parser.parse_event(event) self.assertEqual(parsed_event, { @@ -112,49 +118,54 @@ class PtraceTestParseFromLog(PtraceTest): 'family': None, 'protocol': None, 'sock_type': None, + 'class': None, }) obj = PtraceRule(parsed_event['denied_mask'], parsed_event['peer'], log_event=parsed_event) - # audit allow deny comment access all? peer all? - expected = exp(False, False, False, '', {'tracedby'}, False, '/home/ubuntu/bzr/apparmor/tests/regression/apparmor/ptrace', False) + # audit allow deny comment access all? peer all? + expected = exp(False, False, False, '', {'tracedby'}, False, '/home/ubuntu/bzr/apparmor/tests/regression/apparmor/ptrace', False) self._compare_obj(obj, expected) - self.assertEqual(obj.get_raw(1), ' ptrace tracedby peer=/home/ubuntu/bzr/apparmor/tests/regression/apparmor/ptrace,') + self.assertEqual( + obj.get_raw(1), + ' ptrace tracedby peer=/home/ubuntu/bzr/apparmor/tests/regression/apparmor/ptrace,') + class PtraceFromInit(PtraceTest): - tests = [ - # PtraceRule object audit allow deny comment access all? peer all? - (PtraceRule('r', 'unconfined', deny=True) , exp(False, False, True , '' , {'r'}, False, 'unconfined', False)), - (PtraceRule(('r', 'read'), '/bin/foo') , exp(False, False, False, '' , {'r', 'read'},False, '/bin/foo', False)), - (PtraceRule(PtraceRule.ALL, '/bin/foo') , exp(False, False, False, '' , None, True, '/bin/foo', False )), - (PtraceRule('rw', '/bin/foo') , exp(False, False, False, '' , {'rw'}, False, '/bin/foo', False )), - (PtraceRule('rw', PtraceRule.ALL) , exp(False, False, False, '' , {'rw'}, False, None, True )), - (PtraceRule(PtraceRule.ALL, PtraceRule.ALL) , exp(False, False, False, '' , None , True, None, True )), - ] + tests = ( + # PtraceRule object audit allow deny comment access all? peer all? + (PtraceRule('r', 'unconfined', deny=True), exp(False, False, True, '', {'r'}, False, 'unconfined', False)), + (PtraceRule(('r', 'read'), '/bin/foo'), exp(False, False, False, '', {'r', 'read'}, False, '/bin/foo', False)), + (PtraceRule(PtraceRule.ALL, '/bin/foo'), exp(False, False, False, '', None, True, '/bin/foo', False)), + (PtraceRule('rw', '/bin/foo'), exp(False, False, False, '', {'rw'}, False, '/bin/foo', False)), + (PtraceRule('rw', PtraceRule.ALL), exp(False, False, False, '', {'rw'}, False, None, True)), + (PtraceRule(PtraceRule.ALL, PtraceRule.ALL), exp(False, False, False, '', None, True, None, True)), + ) def _run_test(self, obj, expected): self._compare_obj(obj, expected) + class InvalidPtraceInit(AATest): - tests = [ - # init params expected exception - (['' , '/foo' ] , AppArmorBug), # empty access - (['read', '' ] , AppArmorBug), # empty peer - ([' ', '/foo' ] , AppArmorBug), # whitespace access - (['read', ' ' ] , AppArmorBug), # whitespace peer - (['xyxy', '/foo' ] , AppArmorException), # invalid access + tests = ( + # (init params, expected exception) + (('', '/foo'), AppArmorBug), # empty access + (('read', ''), AppArmorBug), # empty peer + ((' ', '/foo'), AppArmorBug), # whitespace access + (('read', ' '), AppArmorBug), # whitespace peer + (('xyxy', '/foo'), AppArmorException), # invalid access # XXX is 'invalid peer' possible at all? - ([dict(), '/foo' ] , AppArmorBug), # wrong type for access - ([None , '/foo' ] , AppArmorBug), # wrong type for access - (['read', dict() ] , AppArmorBug), # wrong type for peer - (['read', None ] , AppArmorBug), # wrong type for peer - ] + ((dict(), '/foo'), AppArmorBug), # wrong type for access + ((None, '/foo'), AppArmorBug), # wrong type for access + (('read', dict()), AppArmorBug), # wrong type for peer + (('read', None), AppArmorBug), # wrong type for peer + ) def _run_test(self, params, expected): with self.assertRaises(expected): - PtraceRule(params[0], params[1]) + PtraceRule(*params) def test_missing_params_1(self): with self.assertRaises(TypeError): @@ -164,6 +175,7 @@ class InvalidPtraceInit(AATest): with self.assertRaises(TypeError): PtraceRule('r') + class InvalidPtraceTest(AATest): def _check_invalid_rawrule(self, rawrule): obj = None @@ -204,35 +216,35 @@ class WritePtraceTestAATest(AATest): self.assertEqual(expected.strip(), clean, 'unexpected clean rule') self.assertEqual(rawrule.strip(), raw, 'unexpected raw rule') - tests = [ - # raw rule clean rule - ('ptrace,' , 'ptrace,'), - (' ptrace , # foo ' , 'ptrace, # foo'), - (' audit ptrace read,' , 'audit ptrace read,'), - (' audit ptrace (read ),' , 'audit ptrace read,'), - (' audit ptrace (read , tracedby ),' , 'audit ptrace (read tracedby),'), - (' deny ptrace read ,# foo bar' , 'deny ptrace read, # foo bar'), - (' deny ptrace ( read ), ' , 'deny ptrace read,'), - (' allow ptrace ,# foo bar' , 'allow ptrace, # foo bar'), - ('ptrace,' , 'ptrace,'), - ('ptrace (trace),' , 'ptrace trace,'), - ('ptrace (tracedby),' , 'ptrace tracedby,'), - ('ptrace (read),' , 'ptrace read,'), - ('ptrace (readby),' , 'ptrace readby,'), - ('ptrace (trace read),' , 'ptrace (read trace),'), - ('ptrace (read tracedby),' , 'ptrace (read tracedby),'), - ('ptrace r,' , 'ptrace r,'), - ('ptrace w,' , 'ptrace w,'), - ('ptrace rw,' , 'ptrace rw,'), - ('ptrace read,' , 'ptrace read,'), - ('ptrace (tracedby),' , 'ptrace tracedby,'), - ('ptrace w,' , 'ptrace w,'), - ('ptrace read peer=foo,' , 'ptrace read peer=foo,'), - ('ptrace tracedby peer=foo,' , 'ptrace tracedby peer=foo,'), - ('ptrace (read tracedby) peer=/usr/bin/bar,' , 'ptrace (read tracedby) peer=/usr/bin/bar,'), - ('ptrace (trace read) peer=/usr/bin/bar,' , 'ptrace (read trace) peer=/usr/bin/bar,'), - ('ptrace wr peer=/sbin/baz,' , 'ptrace wr peer=/sbin/baz,'), - ] + tests = ( + # raw rule clean rule + ('ptrace,', 'ptrace,'), + (' ptrace , # foo ', 'ptrace, # foo'), + (' audit ptrace read,', 'audit ptrace read,'), + (' audit ptrace (read ),', 'audit ptrace read,'), + (' audit ptrace (read , tracedby ),', 'audit ptrace (read tracedby),'), + (' deny ptrace read ,# foo bar', 'deny ptrace read, # foo bar'), + (' deny ptrace ( read ), ', 'deny ptrace read,'), + (' allow ptrace ,# foo bar', 'allow ptrace, # foo bar'), + ('ptrace,', 'ptrace,'), + ('ptrace (trace),', 'ptrace trace,'), + ('ptrace (tracedby),', 'ptrace tracedby,'), + ('ptrace (read),', 'ptrace read,'), + ('ptrace (readby),', 'ptrace readby,'), + ('ptrace (trace read),', 'ptrace (read trace),'), + ('ptrace (read tracedby),', 'ptrace (read tracedby),'), + ('ptrace r,', 'ptrace r,'), + ('ptrace w,', 'ptrace w,'), + ('ptrace rw,', 'ptrace rw,'), + ('ptrace read,', 'ptrace read,'), + ('ptrace (tracedby),', 'ptrace tracedby,'), + ('ptrace w,', 'ptrace w,'), + ('ptrace read peer=foo,', 'ptrace read peer=foo,'), + ('ptrace tracedby peer=foo,', 'ptrace tracedby peer=foo,'), + ('ptrace (read tracedby) peer=/usr/bin/bar,', 'ptrace (read tracedby) peer=/usr/bin/bar,'), + ('ptrace (trace read) peer=/usr/bin/bar,', 'ptrace (read trace) peer=/usr/bin/bar,'), + ('ptrace wr peer=/sbin/baz,', 'ptrace wr peer=/sbin/baz,'), + ) def test_write_manually(self): obj = PtraceRule('read', '/foo', allow_keyword=True) @@ -256,164 +268,171 @@ class PtraceCoveredTest(AATest): self.assertEqual(obj.is_covered(check_obj), expected[2], 'Mismatch in is_covered, expected %s' % expected[2]) self.assertEqual(obj.is_covered(check_obj, True, True), expected[3], 'Mismatch in is_covered/exact, expected %s' % expected[3]) + class PtraceCoveredTest_01(PtraceCoveredTest): rule = 'ptrace read,' - tests = [ - # rule equal strict equal covered covered exact - ('ptrace,' , [ False , False , False , False ]), - ('ptrace read,' , [ True , True , True , True ]), - ('ptrace read peer=unconfined,' , [ False , False , True , True ]), - ('ptrace read, # comment' , [ True , False , True , True ]), - ('allow ptrace read,' , [ True , False , True , True ]), - ('ptrace read,' , [ True , False , True , True ]), - ('audit ptrace read,' , [ False , False , False , False ]), - ('audit ptrace,' , [ False , False , False , False ]), - ('ptrace tracedby,' , [ False , False , False , False ]), - ('audit deny ptrace read,' , [ False , False , False , False ]), - ('deny ptrace read,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ('ptrace,', (False, False, False, False)), + ('ptrace read,', (True, True, True, True)), + ('ptrace read peer=unconfined,', (False, False, True, True)), + ('ptrace read, # comment', (True, False, True, True)), + ('allow ptrace read,', (True, False, True, True)), + ('ptrace read,', (True, False, True, True)), + ('audit ptrace read,', (False, False, False, False)), + ('audit ptrace,', (False, False, False, False)), + ('ptrace tracedby,', (False, False, False, False)), + ('audit deny ptrace read,', (False, False, False, False)), + ('deny ptrace read,', (False, False, False, False)), + ) + class PtraceCoveredTest_02(PtraceCoveredTest): rule = 'audit ptrace read,' - tests = [ - # rule equal strict equal covered covered exact - ( 'ptrace read,' , [ False , False , True , False ]), - ('audit ptrace read,' , [ True , True , True , True ]), - ( 'ptrace,' , [ False , False , False , False ]), - ('audit ptrace,' , [ False , False , False , False ]), - ('ptrace tracedby,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ( 'ptrace read,', (False, False, True, False)), + ('audit ptrace read,', (True, True, True, True)), + ( 'ptrace,', (False, False, False, False)), + ('audit ptrace,', (False, False, False, False)), + ('ptrace tracedby,', (False, False, False, False)), + ) + class PtraceCoveredTest_03(PtraceCoveredTest): rule = 'ptrace,' - tests = [ - # rule equal strict equal covered covered exact - ( 'ptrace,' , [ True , True , True , True ]), - ('allow ptrace,' , [ True , False , True , True ]), - ( 'ptrace read,' , [ False , False , True , True ]), - ( 'ptrace w,' , [ False , False , True , True ]), - ('audit ptrace,' , [ False , False , False , False ]), - ('deny ptrace,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ( 'ptrace,', (True, True, True, True)), + ('allow ptrace,', (True, False, True, True)), + ( 'ptrace read,', (False, False, True, True)), + ( 'ptrace w,', (False, False, True, True)), + ('audit ptrace,', (False, False, False, False)), + ('deny ptrace,', (False, False, False, False)), + ) + class PtraceCoveredTest_04(PtraceCoveredTest): rule = 'deny ptrace read,' - tests = [ - # rule equal strict equal covered covered exact - ( 'deny ptrace read,' , [ True , True , True , True ]), - ('audit deny ptrace read,' , [ False , False , False , False ]), - ( 'ptrace read,' , [ False , False , False , False ]), # XXX should covered be true here? - ( 'deny ptrace tracedby,' , [ False , False , False , False ]), - ( 'deny ptrace,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ( 'deny ptrace read,', (True, True, True, True)), + ('audit deny ptrace read,', (False, False, False, False)), + ( 'ptrace read,', (False, False, False, False)), # XXX should covered be true here? + ( 'deny ptrace tracedby,', (False, False, False, False)), + ( 'deny ptrace,', (False, False, False, False)), + ) + class PtraceCoveredTest_05(PtraceCoveredTest): rule = 'ptrace read peer=unconfined,' - tests = [ - # rule equal strict equal covered covered exact - ('ptrace,' , [ False , False , False , False ]), - ('ptrace read,' , [ False , False , False , False ]), - ('ptrace read peer=unconfined,' , [ True , True , True , True ]), - ('ptrace peer=unconfined,' , [ False , False , False , False ]), - ('ptrace read, # comment' , [ False , False , False , False ]), - ('allow ptrace read,' , [ False , False , False , False ]), - ('allow ptrace read peer=unconfined,' , [ True , False , True , True ]), - ('allow ptrace read peer=/foo/bar,' , [ False , False , False , False ]), - ('allow ptrace read peer=/**,' , [ False , False , False , False ]), - ('allow ptrace read peer=**,' , [ False , False , False , False ]), - ('ptrace read,' , [ False , False , False , False ]), - ('ptrace read peer=unconfined,' , [ True , False , True , True ]), - ('audit ptrace read peer=unconfined,' , [ False , False , False , False ]), - ('audit ptrace,' , [ False , False , False , False ]), - ('ptrace tracedby,' , [ False , False , False , False ]), - ('audit deny ptrace read,' , [ False , False , False , False ]), - ('deny ptrace read,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ('ptrace,', (False, False, False, False)), + ('ptrace read,', (False, False, False, False)), + ('ptrace read peer=unconfined,', (True, True, True, True)), + ('ptrace peer=unconfined,', (False, False, False, False)), + ('ptrace read, # comment', (False, False, False, False)), + ('allow ptrace read,', (False, False, False, False)), + ('allow ptrace read peer=unconfined,', (True, False, True, True)), + ('allow ptrace read peer=/foo/bar,', (False, False, False, False)), + ('allow ptrace read peer=/**,', (False, False, False, False)), + ('allow ptrace read peer=**,', (False, False, False, False)), + ('ptrace read,', (False, False, False, False)), + ('ptrace read peer=unconfined,', (True, False, True, True)), + ('audit ptrace read peer=unconfined,', (False, False, False, False)), + ('audit ptrace,', (False, False, False, False)), + ('ptrace tracedby,', (False, False, False, False)), + ('audit deny ptrace read,', (False, False, False, False)), + ('deny ptrace read,', (False, False, False, False)), + ) + class PtraceCoveredTest_06(PtraceCoveredTest): rule = 'ptrace read peer=/foo/bar,' - tests = [ - # rule equal strict equal covered covered exact - ('ptrace,' , [ False , False , False , False ]), - ('ptrace read,' , [ False , False , False , False ]), - ('ptrace read peer=/foo/bar,' , [ True , True , True , True ]), - ('ptrace read peer=/foo/*,' , [ False , False , False , False ]), - ('ptrace read peer=/**,' , [ False , False , False , False ]), - ('ptrace read peer=/what/*,' , [ False , False , False , False ]), - ('ptrace peer=/foo/bar,' , [ False , False , False , False ]), - ('ptrace read, # comment' , [ False , False , False , False ]), - ('allow ptrace read,' , [ False , False , False , False ]), - ('allow ptrace read peer=/foo/bar,' , [ True , False , True , True ]), - ('ptrace read,' , [ False , False , False , False ]), - ('ptrace read peer=/foo/bar,' , [ True , False , True , True ]), - ('ptrace read peer=/what/ever,' , [ False , False , False , False ]), - ('audit ptrace read peer=/foo/bar,' , [ False , False , False , False ]), - ('audit ptrace,' , [ False , False , False , False ]), - ('ptrace tracedby,' , [ False , False , False , False ]), - ('audit deny ptrace read,' , [ False , False , False , False ]), - ('deny ptrace read,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ('ptrace,', (False, False, False, False)), + ('ptrace read,', (False, False, False, False)), + ('ptrace read peer=/foo/bar,', (True, True, True, True)), + ('ptrace read peer=/foo/*,', (False, False, False, False)), + ('ptrace read peer=/**,', (False, False, False, False)), + ('ptrace read peer=/what/*,', (False, False, False, False)), + ('ptrace peer=/foo/bar,', (False, False, False, False)), + ('ptrace read, # comment', (False, False, False, False)), + ('allow ptrace read,', (False, False, False, False)), + ('allow ptrace read peer=/foo/bar,', (True, False, True, True)), + ('ptrace read,', (False, False, False, False)), + ('ptrace read peer=/foo/bar,', (True, False, True, True)), + ('ptrace read peer=/what/ever,', (False, False, False, False)), + ('audit ptrace read peer=/foo/bar,', (False, False, False, False)), + ('audit ptrace,', (False, False, False, False)), + ('ptrace tracedby,', (False, False, False, False)), + ('audit deny ptrace read,', (False, False, False, False)), + ('deny ptrace read,', (False, False, False, False)), + ) + class PtraceCoveredTest_07(PtraceCoveredTest): rule = 'ptrace read peer=**,' - tests = [ - # rule equal strict equal covered covered exact - ('ptrace,' , [ False , False , False , False ]), - ('ptrace read,' , [ False , False , False , False ]), - ('ptrace read peer=/foo/bar,' , [ False , False , True , True ]), - ('ptrace read peer=/foo/*,' , [ False , False , False , False ]), # TODO: wildcard vs. wildcard never matches in is_covered_aare() - ('ptrace read peer=/**,' , [ False , False , False , False ]), # TODO: wildcard vs. wildcard never matches in is_covered_aare() - ('ptrace read peer=/what/*,' , [ False , False , False , False ]), # TODO: wildcard vs. wildcard never matches in is_covered_aare() - ('ptrace peer=/foo/bar,' , [ False , False , False , False ]), - ('ptrace read, # comment' , [ False , False , False , False ]), - ('allow ptrace read,' , [ False , False , False , False ]), - ('allow ptrace read peer=/foo/bar,' , [ False , False , True , True ]), - ('ptrace read,' , [ False , False , False , False ]), - ('ptrace read peer=/foo/bar,' , [ False , False , True , True ]), - ('ptrace read peer=/what/ever,' , [ False , False , True , True ]), - ('audit ptrace read peer=/foo/bar,' , [ False , False , False , False ]), - ('audit ptrace,' , [ False , False , False , False ]), - ('ptrace tracedby,' , [ False , False , False , False ]), - ('audit deny ptrace read,' , [ False , False , False , False ]), - ('deny ptrace read,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ('ptrace,', (False, False, False, False)), + ('ptrace read,', (False, False, False, False)), + ('ptrace read peer=/foo/bar,', (False, False, True, True)), + ('ptrace read peer=/foo/*,', (False, False, False, False)), # TODO: wildcard vs. wildcard never matches in is_covered_aare() + ('ptrace read peer=/**,', (False, False, False, False)), # TODO: wildcard vs. wildcard never matches in is_covered_aare() + ('ptrace read peer=/what/*,', (False, False, False, False)), # TODO: wildcard vs. wildcard never matches in is_covered_aare() + ('ptrace peer=/foo/bar,', (False, False, False, False)), + ('ptrace read, # comment', (False, False, False, False)), + ('allow ptrace read,', (False, False, False, False)), + ('allow ptrace read peer=/foo/bar,', (False, False, True, True)), + ('ptrace read,', (False, False, False, False)), + ('ptrace read peer=/foo/bar,', (False, False, True, True)), + ('ptrace read peer=/what/ever,', (False, False, True, True)), + ('audit ptrace read peer=/foo/bar,', (False, False, False, False)), + ('audit ptrace,', (False, False, False, False)), + ('ptrace tracedby,', (False, False, False, False)), + ('audit deny ptrace read,', (False, False, False, False)), + ('deny ptrace read,', (False, False, False, False)), + ) + class PtraceCoveredTest_08(PtraceCoveredTest): rule = 'ptrace (trace, tracedby) peer=/foo/*,' - tests = [ - # rule equal strict equal covered covered exact - ('ptrace,' , [ False , False , False , False ]), - ('ptrace trace,' , [ False , False , False , False ]), - ('ptrace (tracedby, trace),' , [ False , False , False , False ]), - ('ptrace trace peer=/foo/bar,' , [ False , False , True , True ]), - ('ptrace (tracedby trace) peer=/foo/bar,',[ False , False , True , True ]), - ('ptrace (tracedby, trace) peer=/foo/*,', [ True , False , True , True ]), - ('ptrace tracedby peer=/foo/bar,' , [ False , False , True , True ]), - ('ptrace trace peer=/foo/*,' , [ False , False , True , True ]), - ('ptrace trace peer=/**,' , [ False , False , False , False ]), - ('ptrace trace peer=/what/*,' , [ False , False , False , False ]), - ('ptrace peer=/foo/bar,' , [ False , False , False , False ]), - ('ptrace trace, # comment' , [ False , False , False , False ]), - ('allow ptrace trace,' , [ False , False , False , False ]), - ('allow ptrace trace peer=/foo/bar,' , [ False , False , True , True ]), - ('ptrace trace,' , [ False , False , False , False ]), - ('ptrace trace peer=/foo/bar,' , [ False , False , True , True ]), - ('ptrace trace peer=/what/ever,' , [ False , False , False , False ]), - ('audit ptrace trace peer=/foo/bar,' , [ False , False , False , False ]), - ('audit ptrace,' , [ False , False , False , False ]), - ('ptrace tracedby,' , [ False , False , False , False ]), - ('audit deny ptrace trace,' , [ False , False , False , False ]), - ('deny ptrace trace,' , [ False , False , False , False ]), - ] - + tests = ( + # rule equal strict equal covered covered exact + ('ptrace,', (False, False, False, False)), + ('ptrace trace,', (False, False, False, False)), + ('ptrace (tracedby, trace),', (False, False, False, False)), + ('ptrace trace peer=/foo/bar,', (False, False, True, True)), + ('ptrace (tracedby trace) peer=/foo/bar,', (False, False, True, True)), + ('ptrace (tracedby, trace) peer=/foo/*,', (True, False, True, True)), + ('ptrace tracedby peer=/foo/bar,', (False, False, True, True)), + ('ptrace trace peer=/foo/*,', (False, False, True, True)), + ('ptrace trace peer=/**,', (False, False, False, False)), + ('ptrace trace peer=/what/*,', (False, False, False, False)), + ('ptrace peer=/foo/bar,', (False, False, False, False)), + ('ptrace trace, # comment', (False, False, False, False)), + ('allow ptrace trace,', (False, False, False, False)), + ('allow ptrace trace peer=/foo/bar,', (False, False, True, True)), + ('ptrace trace,', (False, False, False, False)), + ('ptrace trace peer=/foo/bar,', (False, False, True, True)), + ('ptrace trace peer=/what/ever,', (False, False, False, False)), + ('audit ptrace trace peer=/foo/bar,', (False, False, False, False)), + ('audit ptrace,', (False, False, False, False)), + ('ptrace tracedby,', (False, False, False, False)), + ('audit deny ptrace trace,', (False, False, False, False)), + ('deny ptrace trace,', (False, False, False, False)), + ) class PtraceCoveredTest_Invalid(AATest): @@ -462,20 +481,21 @@ class PtraceCoveredTest_Invalid(AATest): class PtraceLogprofHeaderTest(AATest): - tests = [ - ('ptrace,', [ _('Access mode'), _('ALL'), _('Peer'), _('ALL'), ]), - ('ptrace read,', [ _('Access mode'), 'read', _('Peer'), _('ALL'), ]), - ('deny ptrace,', [_('Qualifier'), 'deny', _('Access mode'), _('ALL'), _('Peer'), _('ALL'), ]), - ('allow ptrace read,', [_('Qualifier'), 'allow', _('Access mode'), 'read', _('Peer'), _('ALL'), ]), - ('audit ptrace read,', [_('Qualifier'), 'audit', _('Access mode'), 'read', _('Peer'), _('ALL'), ]), - ('audit deny ptrace read,', [_('Qualifier'), 'audit deny', _('Access mode'), 'read', _('Peer'), _('ALL'), ]), - ('ptrace (read, tracedby) peer=/foo,', [ _('Access mode'), 'read tracedby', _('Peer'), '/foo', ]), - ] + tests = ( + ('ptrace,', [ _('Access mode'), _('ALL'), _('Peer'), _('ALL')]), + ('ptrace read,', [ _('Access mode'), 'read', _('Peer'), _('ALL')]), + ('deny ptrace,', [_('Qualifier'), 'deny', _('Access mode'), _('ALL'), _('Peer'), _('ALL')]), + ('allow ptrace read,', [_('Qualifier'), 'allow', _('Access mode'), 'read', _('Peer'), _('ALL')]), + ('audit ptrace read,', [_('Qualifier'), 'audit', _('Access mode'), 'read', _('Peer'), _('ALL')]), + ('audit deny ptrace read,', [_('Qualifier'), 'audit deny', _('Access mode'), 'read', _('Peer'), _('ALL')]), + ('ptrace (read, tracedby) peer=/foo,', [ _('Access mode'), 'read tracedby', _('Peer'), '/foo']), + ) def _run_test(self, params, expected): - obj = PtraceRule._parse(params) + obj = PtraceRule.parse(params) self.assertEqual(obj.logprof_header(), expected) + ## --- tests for PtraceRuleset --- # class PtraceRulesTest(AATest): @@ -493,10 +513,10 @@ class PtraceRulesTest(AATest): def test_ruleset_1(self): ruleset = PtraceRuleset() - rules = [ + rules = ( 'ptrace peer=/foo,', 'ptrace read,', - ] + ) expected_raw = [ 'ptrace peer=/foo,', @@ -518,15 +538,16 @@ class PtraceRulesTest(AATest): # test __repr__() for non-empty ruleset as_string = '%s' % ruleset - self.assertEqual(as_string, '<PtraceRuleset>\n ptrace peer=/foo,\n ptrace read,\n</PtraceRuleset>') + self.assertEqual( + as_string, '<PtraceRuleset>\n ptrace peer=/foo,\n ptrace read,\n</PtraceRuleset>') def test_ruleset_2(self): ruleset = PtraceRuleset() - rules = [ + rules = ( 'ptrace read peer=/foo,', 'allow ptrace read,', 'deny ptrace peer=/bar, # example comment', - ] + ) expected_raw = [ ' ptrace read peer=/foo,', @@ -567,8 +588,10 @@ class PtraceGlobTestAATest(AATest): # get_glob_ext is not available for ptrace rules self.ruleset.get_glob_ext('ptrace read peer=/foo,') -#class PtraceDeleteTestAATest(AATest): -# pass + +# class PtraceDeleteTestAATest(AATest): +# pass + setup_all_loops(__name__) if __name__ == '__main__': diff --git a/utils/test/test-regex_matches.py b/utils/test/test-regex_matches.py index dfe25960c..f084dad3c 100644 --- a/utils/test/test-regex_matches.py +++ b/utils/test/test-regex_matches.py @@ -9,29 +9,31 @@ # # ------------------------------------------------------------------ -import apparmor.aa as aa import unittest -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, - re_match_include_parse, - RE_PROFILE_START, RE_PROFILE_DBUS, RE_PROFILE_CAP, RE_PROFILE_PTRACE, RE_PROFILE_SIGNAL ) +import apparmor.aa as aa +from apparmor.common import AppArmorBug, AppArmorException +from apparmor.regex import ( + RE_PROFILE_CAP, RE_PROFILE_DBUS, RE_PROFILE_PTRACE, RE_PROFILE_SIGNAL, + RE_PROFILE_START, parse_profile_start_line, re_match_include, + re_match_include_parse, strip_parenthesis, strip_quotes) +from common_test import AATest, setup_aa, setup_all_loops class AARegexTest(AATest): def _run_test(self, params, expected): return _regex_test(self, params, expected) + class AANamedRegexTest(AATest): def _run_test(self, line, expected): - '''Run a line through self.regex.search() and verify the result + """Run a line through self.regex.search() and verify the result Keyword arguments: line -- the line to search expected -- False if the search isn't expected to match or, if the search is expected to match, a tuple of expected match groups. - ''' + """ matches = self.regex.search(line) if not expected: self.assertFalse(matches) @@ -43,12 +45,11 @@ class AANamedRegexTest(AATest): match = matches.group(exp) if match: match = match - self.assertEqual(match, expected[exp], 'Group %s mismatch in rule %s' % (exp,line)) - + self.assertEqual(match, expected[exp], 'Group %s mismatch in rule %s' % (exp, line)) class AARegexHasComma(AATest): - '''Tests for apparmor.aa.RE_RULE_HAS_COMMA''' + """Tests for apparmor.aa.RE_RULE_HAS_COMMA""" def _check(self, line, expected=True): result = aa.RE_RULE_HAS_COMMA.search(line) @@ -57,7 +58,8 @@ class AARegexHasComma(AATest): else: self.assertEqual(None, result, 'Found an unexpected comma in "%s"' % line) -regex_has_comma_testcases = [ + +regex_has_comma_testcases = ( ('dbus send%s', 'simple'), ('dbus (r, w, bind, eavesdrop)%s', 'embedded parens 01'), ('dbus (r, w,, bind, eavesdrop) %s', 'embedded parens 02'), @@ -112,40 +114,48 @@ regex_has_comma_testcases = [ # ('@{BAR}={bar,baz,blort, %s', 'tricksy variable declaration') # The following fails the comma test, because it's really a no comma situation # ('@{BAR}="{bar,baz,blort%s" ', 'tricksy variable declaration') -] +) + def setup_has_comma_testcases(): i = 0 for (test_string, description) in regex_has_comma_testcases: i += 1 + def stub_test_comma(self, test_string=test_string): self._check(test_string % ',') + def stub_test_no_comma(self, test_string=test_string): self._check(test_string % ' ', False) + stub_test_comma.__doc__ = "test %s (w/comma)" % (description) stub_test_no_comma.__doc__ = "test %s (no comma)" % (description) setattr(AARegexHasComma, 'test_comma_%d' % (i), stub_test_comma) setattr(AARegexHasComma, 'test_no_comma_%d' % (i), stub_test_no_comma) + class AARegexSplitComment(AATest): - '''Tests for RE_HAS_COMMENT_SPLIT''' + """Tests for RE_HAS_COMMENT_SPLIT""" def _check(self, line, expected, comment=None, not_comment=None): result = aa.RE_HAS_COMMENT_SPLIT.search(line) if expected: self.assertTrue(result, 'Couldn\'t find a comment in "%s"' % line) - self.assertEqual(result.group('comment'), comment, 'Expected comment "%s", got "%s"' - % (comment, result.group('comment'))) - self.assertEqual(result.group('not_comment'), not_comment, 'Expected not comment "%s", got "%s"' - % (not_comment, result.group('not_comment'))) + self.assertEqual( + result.group('comment'), comment, + 'Expected comment "%s", got "%s"' % (comment, result.group('comment'))) + self.assertEqual( + result.group('not_comment'), not_comment, + 'Expected not comment "%s", got "%s"' % (not_comment, result.group('not_comment'))) else: self.assertEqual(None, result, 'Found an unexpected comment "%s" in "%s"' - % ("" if result is None else result.group('comment'), line )) + % ("" if result is None else result.group('comment'), line)) + # Tuples of (string, expected result), where expected result is False if # the string should not be considered as having a comment, or a second # tuple of the not comment and comment sections split apart -regex_split_comment_testcases = [ +regex_split_comment_testcases = ( ('dbus send # this is a comment', ('dbus send ', '# this is a comment')), ('dbus send member=no_comment', False), ('dbus send member=no_comment, ', False), @@ -160,30 +170,33 @@ regex_split_comment_testcases = [ ('ptrace (trace read) peer=/usr/bin/foo,', False), ('pivot_root, # comment', ('pivot_root, ', '# comment')), ('pivot_root /old /new -> child,', False), -] +) + def setup_split_comment_testcases(): i = 0 for (test_string, result) in regex_split_comment_testcases: i += 1 + def stub_test(self, test_string=test_string, result=result): if result is False: self._check(test_string, False) else: self._check(test_string, True, not_comment=result[0], comment=result[1]) + stub_test.__doc__ = "test '%s'" % (test_string) setattr(AARegexSplitComment, 'test_split_comment_%d' % (i), stub_test) def _regex_test(self, line, expected): - '''Run a line through self.regex.search() and verify the result + """Run a line through self.regex.search() and verify the result Keyword arguments: line -- the line to search expected -- False if the search isn't expected to match or, if the search is expected to match, a tuple of expected match groups with all of the strings stripped - ''' + """ result = self.regex.search(line) if not expected: self.assertFalse(result) @@ -196,126 +209,119 @@ def _regex_test(self, line, expected): for (i, group) in enumerate(groups): if group: group = group.strip() - self.assertEqual(group, expected[i], 'Group %d mismatch in rule %s' % (i,line)) - - - + self.assertEqual(group, expected[i], 'Group %d mismatch in rule %s' % (i, line)) class AARegexCapability(AARegexTest): - '''Tests for RE_PROFILE_CAP''' + """Tests for RE_PROFILE_CAP""" def AASetup(self): self.regex = RE_PROFILE_CAP - tests = [ + tests = ( (' capability net_raw,', (None, None, 'net_raw', 'net_raw', None)), ('capability net_raw , ', (None, None, 'net_raw', 'net_raw', None)), (' capability,', (None, None, None, None, None)), (' capability , ', (None, None, None, None, None)), (' capabilitynet_raw,', False) - ] + ) + class AARegexDbus(AARegexTest): - '''Tests for RE_PROFILE_DBUS''' + """Tests for RE_PROFILE_DBUS""" def AASetup(self): self.regex = RE_PROFILE_DBUS - tests = [ - (' dbus,', (None, None, 'dbus,', None, None)), - (' audit dbus,', ('audit', None, 'dbus,', None, None)), - (' dbus send member=no_comment,', (None, None, 'dbus send member=no_comment,', 'send member=no_comment', None)), - (' dbus send member=no_comment, # comment', (None, None, 'dbus send member=no_comment,', 'send member=no_comment', '# comment')), + tests = ( + (' dbus,', (None, None, 'dbus,', None, None)), + (' audit dbus,', ('audit', None, 'dbus,', None, None)), + (' dbus send member=no_comment,', (None, None, 'dbus send member=no_comment,', 'send member=no_comment', None)), + (' dbus send member=no_comment, # comment', (None, None, 'dbus send member=no_comment,', 'send member=no_comment', '# comment')), (' dbusdriver,', False), (' audit dbusdriver,', False), - ] + ) + class AARegexMount(AARegexTest): - '''Tests for RE_PROFILE_MOUNT''' + """Tests for RE_PROFILE_MOUNT""" def AASetup(self): self.regex = aa.RE_PROFILE_MOUNT - tests = [ - (' mount,', (None, None, 'mount,', 'mount', None, None)), - (' audit mount,', ('audit', None, 'mount,', 'mount', None, None)), - (' umount,', (None, None, 'umount,', 'umount', None, None)), - (' audit umount,', ('audit', None, 'umount,', 'umount', None, None)), - (' unmount,', (None, None, 'unmount,', 'unmount', None, None)), - (' audit unmount,', ('audit', None, 'unmount,', 'unmount', None, None)), - (' remount,', (None, None, 'remount,', 'remount', None, None)), - (' deny remount,', (None, 'deny', 'remount,', 'remount', None, None)), + tests = ( + (' mount,', (None, None, 'mount,', 'mount', None, None)), + (' audit mount,', ('audit', None, 'mount,', 'mount', None, None)), + (' umount,', (None, None, 'umount,', 'umount', None, None)), + (' audit umount,', ('audit', None, 'umount,', 'umount', None, None)), + (' unmount,', (None, None, 'unmount,', 'unmount', None, None)), + (' audit unmount,', ('audit', None, 'unmount,', 'unmount', None, None)), + (' remount,', (None, None, 'remount,', 'remount', None, None)), + (' deny remount,', (None, 'deny', 'remount,', 'remount', None, None)), - (' mount, # comment', (None, None, 'mount,', 'mount', None, '# comment')), + (' mount, # comment', (None, None, 'mount,', 'mount', None, '# comment')), (' mountain,', False), (' audit mountain,', False), - ] - + ) class AARegexSignal(AARegexTest): - '''Tests for RE_PROFILE_SIGNAL''' + """Tests for RE_PROFILE_SIGNAL""" def AASetup(self): self.regex = RE_PROFILE_SIGNAL - tests = [ - (' signal,', (None, None, 'signal,', None, None)), - (' audit signal,', ('audit', None, 'signal,', None, None)), - (' signal receive,', (None, None, 'signal receive,', 'receive', None)), - (' signal (send, receive),', (None, None, 'signal (send, receive),', '(send, receive)', None)), - (' audit signal (receive),', ('audit', None, 'signal (receive),', '(receive)', None)), - (' signal (send, receive) set=(usr1 usr2),', (None, None, 'signal (send, receive) set=(usr1 usr2),', '(send, receive) set=(usr1 usr2)', None)), - (' signal send set=(hup, quit) peer=/usr/sbin/daemon,', (None, None, 'signal send set=(hup, quit) peer=/usr/sbin/daemon,', - 'send set=(hup, quit) peer=/usr/sbin/daemon', None)), + tests = ( + (' signal,', (None, None, 'signal,', None, None)), + (' audit signal,', ('audit', None, 'signal,', None, None)), + (' signal receive,', (None, None, 'signal receive,', 'receive', None)), + (' signal (send, receive),', (None, None, 'signal (send, receive),', '(send, receive)', None)), + (' audit signal (receive),', ('audit', None, 'signal (receive),', '(receive)', None)), + (' signal (send, receive) set=(usr1 usr2),', (None, None, 'signal (send, receive) set=(usr1 usr2),', '(send, receive) set=(usr1 usr2)', None)), + (' signal send set=(hup, quit) peer=/usr/sbin/daemon,', (None, None, 'signal send set=(hup, quit) peer=/usr/sbin/daemon,', 'send set=(hup, quit) peer=/usr/sbin/daemon', None)), (' signalling,', False), (' audit signalling,', False), (' signalling receive,', False), - ] + ) class AARegexPtrace(AARegexTest): - '''Tests for RE_PROFILE_PTRACE''' + """Tests for RE_PROFILE_PTRACE""" def AASetup(self): self.regex = RE_PROFILE_PTRACE - tests = [ - # audit allow rule rule details comment - (' ptrace,', (None, None, 'ptrace,', None, None)), - (' audit ptrace,', ('audit', None, 'ptrace,', None, None)), - (' ptrace trace,', (None, None, 'ptrace trace,', 'trace', None)), - (' ptrace (tracedby, readby),', (None, None, 'ptrace (tracedby, readby),', '(tracedby, readby)', None)), - (' audit ptrace (read),', ('audit', None, 'ptrace (read),', '(read)', None)), - (' ptrace trace peer=/usr/sbin/daemon,', (None, None, 'ptrace trace peer=/usr/sbin/daemon,', 'trace peer=/usr/sbin/daemon', None)), + tests = ( + # audit allow rule rule details comment + (' ptrace,', (None, None, 'ptrace,', None, None)), + (' audit ptrace,', ('audit', None, 'ptrace,', None, None)), + (' ptrace trace,', (None, None, 'ptrace trace,', 'trace', None)), + (' ptrace (tracedby, readby),', (None, None, 'ptrace (tracedby, readby),', '(tracedby, readby)', None)), + (' audit ptrace (read),', ('audit', None, 'ptrace (read),', '(read)', None)), + (' ptrace trace peer=/usr/sbin/daemon,', (None, None, 'ptrace trace peer=/usr/sbin/daemon,', 'trace peer=/usr/sbin/daemon', None)), (' ptraceback,', False), (' audit ptraceback,', False), (' ptraceback trace,', False), - ] + ) class AARegexPivotRoot(AARegexTest): - '''Tests for RE_PROFILE_PIVOT_ROOT''' + """Tests for RE_PROFILE_PIVOT_ROOT""" def AASetup(self): self.regex = aa.RE_PROFILE_PIVOT_ROOT - tests = [ - (' pivot_root,', (None, None, 'pivot_root,', None)), - (' audit pivot_root,', ('audit', None, 'pivot_root,', None)), - (' pivot_root oldroot=/new/old,', - (None, None, 'pivot_root oldroot=/new/old,', None)), - (' pivot_root oldroot=/new/old /new,', - (None, None, 'pivot_root oldroot=/new/old /new,', None)), - (' pivot_root oldroot=/new/old /new -> child,', - (None, None, 'pivot_root oldroot=/new/old /new -> child,', None)), - (' audit pivot_root oldroot=/new/old /new -> child,', - ('audit', None, 'pivot_root oldroot=/new/old /new -> child,', None)), + tests = ( + (' pivot_root,', (None, None, 'pivot_root,', None)), + (' audit pivot_root,', ('audit', None, 'pivot_root,', None)), + (' pivot_root oldroot=/new/old,', (None, None, 'pivot_root oldroot=/new/old,', None)), + (' pivot_root oldroot=/new/old /new,', (None, None, 'pivot_root oldroot=/new/old /new,', None)), + (' pivot_root oldroot=/new/old /new -> child,', (None, None, 'pivot_root oldroot=/new/old /new -> child,', None)), + (' audit pivot_root oldroot=/new/old /new -> child,', ('audit', None, 'pivot_root oldroot=/new/old /new -> child,', None)), ('pivot_root', False), # comma missing @@ -324,97 +330,95 @@ class AARegexPivotRoot(AARegexTest): ('pivot_rootbeer, # comment', False), ('pivot_rootbeer /new, ', False), ('pivot_rootbeer /new, # comment', False), - ] + ) + class AARegexUnix(AARegexTest): - '''Tests for RE_PROFILE_UNIX''' + """Tests for RE_PROFILE_UNIX""" def AASetup(self): self.regex = aa.RE_PROFILE_UNIX - tests = [ - (' unix,', (None, None, 'unix,', None)), - (' audit unix,', ('audit', None, 'unix,', None)), - (' unix accept,', (None, None, 'unix accept,', None)), - (' allow unix connect,', (None, 'allow', 'unix connect,', None)), - (' audit allow unix bind,', ('audit', 'allow', 'unix bind,', None)), - (' deny unix bind,', (None, 'deny', 'unix bind,', None)), - ('unix peer=(label=@{profile_name}),', - (None, None, 'unix peer=(label=@{profile_name}),', None)), - ('unix (receive) peer=(label=unconfined),', - (None, None, 'unix (receive) peer=(label=unconfined),', None)), - (' unix (getattr, shutdown) peer=(addr=none),', - (None, None, 'unix (getattr, shutdown) peer=(addr=none),', None)), - ('unix (connect, receive, send) type=stream peer=(label=unconfined,addr="@/tmp/dbus-*"),', - (None, None, 'unix (connect, receive, send) type=stream peer=(label=unconfined,addr="@/tmp/dbus-*"),', None)), + tests = ( + (' unix,', (None, None, 'unix,', None)), + (' audit unix,', ('audit', None, 'unix,', None)), + (' unix accept,', (None, None, 'unix accept,', None)), + (' allow unix connect,', (None, 'allow', 'unix connect,', None)), + (' audit allow unix bind,', ('audit', 'allow', 'unix bind,', None)), + (' deny unix bind,', (None, 'deny', 'unix bind,', None)), + ('unix peer=(label=@{profile_name}),', (None, None, 'unix peer=(label=@{profile_name}),', None)), + ('unix (receive) peer=(label=unconfined),', (None, None, 'unix (receive) peer=(label=unconfined),', None)), + (' unix (getattr, shutdown) peer=(addr=none),', (None, None, 'unix (getattr, shutdown) peer=(addr=none),', None)), + ('unix (connect, receive, send) type=stream peer=(label=unconfined,addr="@/tmp/dbus-*"),', (None, None, 'unix (connect, receive, send) type=stream peer=(label=unconfined,addr="@/tmp/dbus-*"),', None)), ('unixlike', False), ('deny unixlike,', False), - ] + ) + class AANamedRegexProfileStart_2(AANamedRegexTest): - '''Tests for RE_PROFILE_START''' + """Tests for RE_PROFILE_START""" def AASetup(self): self.regex = RE_PROFILE_START - tests = [ - ('/bin/foo ', False), # no '{' - ('/bin/foo /bin/bar', False), # missing 'profile' keyword - ('profile {', False), # no attachment - (' profile foo bar /foo {', False), # missing quotes around "foo bar" - ('bin/foo {', False), # not starting with '/' - ('"bin/foo" {', False), # not starting with '/', quoted version - - (' /foo {', { 'plainprofile': '/foo', 'namedprofile': None, 'attachment': None, 'flags': None, 'comment': None }), - (' "/foo" {', { 'plainprofile': '"/foo"', 'namedprofile': None, 'attachment': None, 'flags': None, 'comment': None }), - (' profile /foo {', { 'plainprofile': None, 'namedprofile': '/foo', 'attachment': None, 'flags': None, 'comment': None }), - (' profile "/foo" {', { 'plainprofile': None, 'namedprofile': '"/foo"', 'attachment': None, 'flags': None, 'comment': None }), - (' profile foo /foo {', { 'plainprofile': None, 'namedprofile': 'foo', 'attachment': '/foo', 'flags': None, 'comment': None }), - (' profile foo /foo (audit) {', { 'plainprofile': None, 'namedprofile': 'foo', 'attachment': '/foo', 'flags': 'audit', 'comment': None }), - (' profile "foo" "/foo" {', { 'plainprofile': None, 'namedprofile': '"foo"', 'attachment': '"/foo"', 'flags': None, 'comment': None }), - (' profile "foo bar" /foo {', { 'plainprofile': None, 'namedprofile': '"foo bar"', 'attachment': '/foo', 'flags': None, 'comment': None }), - (' /foo (complain) {', { 'plainprofile': '/foo', 'namedprofile': None, 'attachment': None, 'flags': 'complain', 'comment': None }), - (' /foo flags=(complain) {', { 'plainprofile': '/foo', 'namedprofile': None, 'attachment': None, 'flags': 'complain', 'comment': None }), - (' /foo (complain) { # x', { 'plainprofile': '/foo', 'namedprofile': None, 'attachment': None, 'flags': 'complain', 'comment': '# x'}), - (' /foo flags = ( complain ){#',{ 'plainprofile': '/foo', 'namedprofile': None, 'attachment': None, 'flags': ' complain ', 'comment': '#'}), - (' @{foo} {', { 'plainprofile': '@{foo}', 'namedprofile': None, 'attachment': None, 'flags': None, 'comment': None }), - (' profile @{foo} {', { 'plainprofile': None, 'namedprofile': '@{foo}', 'attachment': None, 'flags': None, 'comment': None }), - (' profile @{foo} /bar {', { 'plainprofile': None, 'namedprofile': '@{foo}', 'attachment': '/bar', 'flags': None, 'comment': None }), - (' profile foo @{bar} {', { 'plainprofile': None, 'namedprofile': 'foo', 'attachment': '@{bar}', 'flags': None, 'comment': None }), - (' profile @{foo} @{bar} {', { 'plainprofile': None, 'namedprofile': '@{foo}', 'attachment': '@{bar}', 'flags': None, 'comment': None }), - - (' /foo {', { 'plainprofile': '/foo', 'namedprofile': None, 'leadingspace': ' ' }), - ('/foo {', { 'plainprofile': '/foo', 'namedprofile': None, 'leadingspace': '' }), - (' profile foo {', { 'plainprofile': None, 'namedprofile': 'foo', 'leadingspace': ' ' }), - ('profile foo {', { 'plainprofile': None, 'namedprofile': 'foo', 'leadingspace': '' }), - ] + tests = ( + ('/bin/foo ', False), # no '{' + ('/bin/foo /bin/bar', False), # missing 'profile' keyword + ('profile {', False), # no attachment + (' profile foo bar /foo {', False), # missing quotes around "foo bar" + ('bin/foo {', False), # not starting with '/' + ('"bin/foo" {', False), # not starting with '/', quoted version + + (' /foo {', {'plainprofile': '/foo', 'namedprofile': None, 'attachment': None, 'flags': None, 'comment': None}), + (' "/foo" {', {'plainprofile': '"/foo"', 'namedprofile': None, 'attachment': None, 'flags': None, 'comment': None}), + (' profile /foo {', {'plainprofile': None, 'namedprofile': '/foo', 'attachment': None, 'flags': None, 'comment': None}), + (' profile "/foo" {', {'plainprofile': None, 'namedprofile': '"/foo"', 'attachment': None, 'flags': None, 'comment': None}), + (' profile foo /foo {', {'plainprofile': None, 'namedprofile': 'foo', 'attachment': '/foo', 'flags': None, 'comment': None}), + (' profile foo /foo (audit) {', {'plainprofile': None, 'namedprofile': 'foo', 'attachment': '/foo', 'flags': 'audit', 'comment': None}), + (' profile "foo" "/foo" {', {'plainprofile': None, 'namedprofile': '"foo"', 'attachment': '"/foo"', 'flags': None, 'comment': None}), + (' profile "foo bar" /foo {', {'plainprofile': None, 'namedprofile': '"foo bar"', 'attachment': '/foo', 'flags': None, 'comment': None}), + (' /foo (complain) {', {'plainprofile': '/foo', 'namedprofile': None, 'attachment': None, 'flags': 'complain', 'comment': None}), + (' /foo flags=(complain) {', {'plainprofile': '/foo', 'namedprofile': None, 'attachment': None, 'flags': 'complain', 'comment': None}), + (' /foo (complain) { # x', {'plainprofile': '/foo', 'namedprofile': None, 'attachment': None, 'flags': 'complain', 'comment': '# x'}), + (' /foo flags = ( complain ){#', {'plainprofile': '/foo', 'namedprofile': None, 'attachment': None, 'flags': ' complain ', 'comment': '#'}), + (' @{foo} {', {'plainprofile': '@{foo}', 'namedprofile': None, 'attachment': None, 'flags': None, 'comment': None}), + (' profile @{foo} {', {'plainprofile': None, 'namedprofile': '@{foo}', 'attachment': None, 'flags': None, 'comment': None}), + (' profile @{foo} /bar {', {'plainprofile': None, 'namedprofile': '@{foo}', 'attachment': '/bar', 'flags': None, 'comment': None}), + (' profile foo @{bar} {', {'plainprofile': None, 'namedprofile': 'foo', 'attachment': '@{bar}', 'flags': None, 'comment': None}), + (' profile @{foo} @{bar} {', {'plainprofile': None, 'namedprofile': '@{foo}', 'attachment': '@{bar}', 'flags': None, 'comment': None}), + + (' /foo {', {'plainprofile': '/foo', 'namedprofile': None, 'leadingspace': ' '}), + ('/foo {', {'plainprofile': '/foo', 'namedprofile': None, 'leadingspace': ''}), + (' profile foo {', {'plainprofile': None, 'namedprofile': 'foo', 'leadingspace': ' '}), + ('profile foo {', {'plainprofile': None, 'namedprofile': 'foo', 'leadingspace': ''}), + ) class Test_parse_profile_start_line(AATest): - tests = [ - (' /foo {', { 'profile': '/foo', 'profile_keyword': False, 'plainprofile': '/foo', 'namedprofile': None, 'attachment': None, 'flags': None, 'comment': None }), - (' "/foo" {', { 'profile': '/foo', 'profile_keyword': False, 'plainprofile': '/foo', 'namedprofile': None, 'attachment': None, 'flags': None, 'comment': None }), - (' profile /foo {', { 'profile': '/foo', 'profile_keyword': True, 'plainprofile': None, 'namedprofile': '/foo', 'attachment': None, 'flags': None, 'comment': None }), - (' profile "/foo" {', { 'profile': '/foo', 'profile_keyword': True, 'plainprofile': None, 'namedprofile': '/foo', 'attachment': None, 'flags': None, 'comment': None }), - (' profile foo /foo {', { 'profile': 'foo', 'profile_keyword': True, 'plainprofile': None, 'namedprofile': 'foo', 'attachment': '/foo', 'flags': None, 'comment': None }), - (' profile foo /foo (audit) {', { 'profile': 'foo', 'profile_keyword': True, 'plainprofile': None, 'namedprofile': 'foo', 'attachment': '/foo', 'flags': 'audit', 'comment': None }), - (' profile "foo" "/foo" {', { 'profile': 'foo', 'profile_keyword': True, 'plainprofile': None, 'namedprofile': 'foo', 'attachment': '/foo', 'flags': None, 'comment': None }), - (' profile "foo bar" /foo {', { 'profile': 'foo bar', 'profile_keyword': True, 'plainprofile': None, 'namedprofile': 'foo bar','attachment': '/foo', 'flags': None, 'comment': None }), - (' /foo (complain) {', { 'profile': '/foo', 'profile_keyword': False, 'plainprofile': '/foo', 'namedprofile': None, 'attachment': None, 'flags': 'complain', 'comment': None }), - (' /foo flags=(complain) {', { 'profile': '/foo', 'profile_keyword': False, 'plainprofile': '/foo', 'namedprofile': None, 'attachment': None, 'flags': 'complain', 'comment': None }), - (' /foo flags = ( complain ){', { 'profile': '/foo', 'profile_keyword': False, 'plainprofile': '/foo', 'namedprofile': None, 'attachment': None, 'flags': ' complain ', 'comment': None }), - (' /foo (complain) { # x', { 'profile': '/foo', 'profile_keyword': False, 'plainprofile': '/foo', 'namedprofile': None, 'attachment': None, 'flags': 'complain', 'comment': '# x'}), - - (' /foo {', { 'profile': '/foo', 'plainprofile': '/foo', 'namedprofile': None, 'leadingspace': ' ' }), - ('/foo {', { 'profile': '/foo', 'plainprofile': '/foo', 'namedprofile': None, 'leadingspace': None }), - (' profile foo {', { 'profile': 'foo', 'plainprofile': None, 'namedprofile': 'foo', 'leadingspace': ' ' }), - ('profile foo {', { 'profile': 'foo', 'plainprofile': None, 'namedprofile': 'foo', 'leadingspace': None }), - (' @{foo} {', { 'profile': '@{foo}', 'plainprofile': '@{foo}', 'namedprofile': None, 'attachment': None, 'flags': None, 'comment': None }), - (' profile @{foo} {', { 'profile': '@{foo}', 'plainprofile': None, 'namedprofile': '@{foo}', 'attachment': None, 'flags': None, 'comment': None }), - (' profile @{foo} /bar {', { 'profile': '@{foo}', 'plainprofile': None, 'namedprofile': '@{foo}', 'attachment': '/bar', 'flags': None, 'comment': None }), - (' profile foo @{bar} {', { 'profile': 'foo', 'plainprofile': None, 'namedprofile': 'foo', 'attachment': '@{bar}', 'flags': None, 'comment': None }), - (' profile @{foo} @{bar} {', { 'profile': '@{foo}', 'plainprofile': None, 'namedprofile': '@{foo}', 'attachment': '@{bar}', 'flags': None, 'comment': None }), - ] + tests = ( + (' /foo {', {'profile': '/foo', 'profile_keyword': False, 'plainprofile': '/foo', 'namedprofile': None, 'attachment': None, 'flags': None, 'comment': None}), + (' "/foo" {', {'profile': '/foo', 'profile_keyword': False, 'plainprofile': '/foo', 'namedprofile': None, 'attachment': None, 'flags': None, 'comment': None}), + (' profile /foo {', {'profile': '/foo', 'profile_keyword': True, 'plainprofile': None, 'namedprofile': '/foo', 'attachment': None, 'flags': None, 'comment': None}), + (' profile "/foo" {', {'profile': '/foo', 'profile_keyword': True, 'plainprofile': None, 'namedprofile': '/foo', 'attachment': None, 'flags': None, 'comment': None}), + (' profile foo /foo {', {'profile': 'foo', 'profile_keyword': True, 'plainprofile': None, 'namedprofile': 'foo', 'attachment': '/foo', 'flags': None, 'comment': None}), + (' profile foo /foo (audit) {', {'profile': 'foo', 'profile_keyword': True, 'plainprofile': None, 'namedprofile': 'foo', 'attachment': '/foo', 'flags': 'audit', 'comment': None}), + (' profile "foo" "/foo" {', {'profile': 'foo', 'profile_keyword': True, 'plainprofile': None, 'namedprofile': 'foo', 'attachment': '/foo', 'flags': None, 'comment': None}), + (' profile "foo bar" /foo {', {'profile': 'foo bar', 'profile_keyword': True, 'plainprofile': None, 'namedprofile': 'foo bar', 'attachment': '/foo', 'flags': None, 'comment': None}), + (' /foo (complain) {', {'profile': '/foo', 'profile_keyword': False, 'plainprofile': '/foo', 'namedprofile': None, 'attachment': None, 'flags': 'complain', 'comment': None}), + (' /foo flags=(complain) {', {'profile': '/foo', 'profile_keyword': False, 'plainprofile': '/foo', 'namedprofile': None, 'attachment': None, 'flags': 'complain', 'comment': None}), + (' /foo flags = ( complain ){', {'profile': '/foo', 'profile_keyword': False, 'plainprofile': '/foo', 'namedprofile': None, 'attachment': None, 'flags': ' complain ', 'comment': None}), + (' /foo (complain) { # x', {'profile': '/foo', 'profile_keyword': False, 'plainprofile': '/foo', 'namedprofile': None, 'attachment': None, 'flags': 'complain', 'comment': '# x'}), + + (' /foo {', {'profile': '/foo', 'leadingspace': ' ', 'plainprofile': '/foo', 'namedprofile': None}), + ('/foo {', {'profile': '/foo', 'leadingspace': None, 'plainprofile': '/foo', 'namedprofile': None}), + (' profile foo {', {'profile': 'foo', 'leadingspace': ' ', 'plainprofile': None, 'namedprofile': 'foo'}), + ('profile foo {', {'profile': 'foo', 'leadingspace': None, 'plainprofile': None, 'namedprofile': 'foo'}), + (' @{foo} {', {'profile': '@{foo}', 'plainprofile': '@{foo}', 'namedprofile': None, 'attachment': None, 'flags': None, 'comment': None}), + (' profile @{foo} {', {'profile': '@{foo}', 'plainprofile': None, 'namedprofile': '@{foo}', 'attachment': None, 'flags': None, 'comment': None}), + (' profile @{foo} /bar {', {'profile': '@{foo}', 'plainprofile': None, 'namedprofile': '@{foo}', 'attachment': '/bar', 'flags': None, 'comment': None}), + (' profile foo @{bar} {', {'profile': 'foo', 'plainprofile': None, 'namedprofile': 'foo', 'attachment': '@{bar}', 'flags': None, 'comment': None}), + (' profile @{foo} @{bar} {', {'profile': '@{foo}', 'plainprofile': None, 'namedprofile': '@{foo}', 'attachment': '@{bar}', 'flags': None, 'comment': None}), + ) def _run_test(self, line, expected): matches = parse_profile_start_line(line, 'somefile') @@ -422,226 +426,236 @@ class Test_parse_profile_start_line(AATest): self.assertTrue(matches) for exp in expected: - self.assertEqual(matches[exp], expected[exp], 'Group %s mismatch in rule %s' % (exp,line)) + self.assertEqual( + matches[exp], expected[exp], + 'Group %s mismatch in rule %s' % (exp, line)) + class TestInvalid_parse_profile_start_line(AATest): - tests = [ - ('/bin/foo ', False), # no '{' - ('/bin/foo /bin/bar', False), # missing 'profile' keyword - ('profile {', False), # no attachment - (' profile foo bar /foo {', False), # missing quotes around "foo bar" - ] + tests = ( + ('/bin/foo ', False), # no '{' + ('/bin/foo /bin/bar', False), # missing 'profile' keyword + ('profile {', False), # no attachment + (' profile foo bar /foo {', False), # missing quotes around "foo bar" + ) def _run_test(self, line, expected): with self.assertRaises(AppArmorBug): parse_profile_start_line(line, 'somefile') + class Test_re_match_include(AATest): - tests = [ + tests = ( # #include - ('#include <abstractions/base>', 'abstractions/base' ), # magic path - ('#include <abstractions/base> # comment', 'abstractions/base' ), - ('#include<abstractions/base>#comment', 'abstractions/base' ), - (' #include <abstractions/base> ', 'abstractions/base' ), - ('#include "/foo/bar"', '/foo/bar' ), # absolute path - ('#include "/foo/bar" # comment', '/foo/bar' ), - ('#include "/foo/bar"#comment', '/foo/bar' ), - (' #include "/foo/bar" ', '/foo/bar' ), + ('#include <abstractions/base>', 'abstractions/base'), # magic path + ('#include <abstractions/base> # comment', 'abstractions/base'), + ('#include<abstractions/base>#comment', 'abstractions/base'), + (' #include <abstractions/base> ', 'abstractions/base'), + ('#include "/foo/bar"', '/foo/bar'), # absolute path + ('#include "/foo/bar" # comment', '/foo/bar'), + ('#include "/foo/bar"#comment', '/foo/bar'), + (' #include "/foo/bar" ', '/foo/bar'), # include (without #) - ('include <abstractions/base>', 'abstractions/base' ), # magic path - ('include <abstractions/base> # comment', 'abstractions/base' ), - ('include<abstractions/base>#comment', 'abstractions/base' ), - (' include <abstractions/base> ', 'abstractions/base' ), - ('include "/foo/bar"', '/foo/bar' ), # absolute path - ('include "/foo/bar" # comment', '/foo/bar' ), - ('include "/foo/bar"#comment', '/foo/bar' ), - (' include "/foo/bar" ', '/foo/bar' ), - - (' some #include <abstractions/base>', None, ), # non-matching - (' /etc/fstab r,', None, ), - ('/usr/include r,', None, ), - ('/include r,', None, ), - (' #include if exists <abstractions/base>', None, ), # include if exists - (' #include if exists "/foo/bar"', None, ), - ] + ('include <abstractions/base>', 'abstractions/base'), # magic path + ('include <abstractions/base> # comment', 'abstractions/base'), + ('include<abstractions/base>#comment', 'abstractions/base'), + (' include <abstractions/base> ', 'abstractions/base'), + ('include "/foo/bar"', '/foo/bar'), # absolute path + ('include "/foo/bar" # comment', '/foo/bar'), + ('include "/foo/bar"#comment', '/foo/bar'), + (' include "/foo/bar" ', '/foo/bar'), + + (' some #include <abstractions/base>', None), # non-matching + (' /etc/fstab r,', None), + ('/usr/include r,', None), + ('/include r,', None), + (' #include if exists <abstractions/base>', None), # include if exists + (' #include if exists "/foo/bar"', None), + ) def _run_test(self, params, expected): self.assertEqual(re_match_include(params), expected) + class TestInvalid_re_match_include(AATest): - tests = [ - ('#include <>', AppArmorException ), # '#include' - ('#include < >', AppArmorException ), - ('#include ""', AppArmorException ), - ('#include " "', AppArmorException ), - ('#include', AppArmorException ), - ('#include ', AppArmorException ), - ('#include "foo"', AppArmorException ), # LP: 1738880 (relative) - ('#include "foo" # comment', AppArmorException ), - ('#include "foo"#comment', AppArmorException ), - (' #include "foo" ', AppArmorException ), - ('#include "foo/bar"', AppArmorException ), - ('#include "foo/bar" # comment', AppArmorException ), - ('#include "foo/bar"#comment', AppArmorException ), - (' #include "foo/bar" ', AppArmorException ), - ('#include foo', AppArmorException ), # LP: 1738879 (no quotes) - ('#include foo/bar', AppArmorException ), - ('#include /foo/bar', AppArmorException ), - ('#include foo bar', AppArmorException ), # LP: 1738877 (space in name) - ('#include foo bar/baz', AppArmorException ), - ('#include "foo bar"', AppArmorException ), - ('#include /foo bar', AppArmorException ), - ('#include "/foo bar"', AppArmorException ), - ('#include "foo bar/baz"', AppArmorException ), - - ('include <>', AppArmorException ), # 'include' - ('include < >', AppArmorException ), - ('include ""', AppArmorException ), - ('include " "', AppArmorException ), - ('include', AppArmorException ), - ('include ', AppArmorException ), - ('include "foo"', AppArmorException ), # LP: 1738880 (relative) - ('include "foo" # comment', AppArmorException ), - ('include "foo"#comment', AppArmorException ), - (' include "foo" ', AppArmorException ), - ('include "foo/bar"', AppArmorException ), - ('include "foo/bar" # comment', AppArmorException ), - ('include "foo/bar"#comment', AppArmorException ), - (' include "foo/bar" ', AppArmorException ), - ('include foo', AppArmorException ), # LP: 1738879 (no quotes) - ('include foo/bar', AppArmorException ), - ('include /foo/bar', AppArmorException ), - ('include foo bar', AppArmorException ), # LP: 1738877 (space in name) - ('include foo bar/baz', AppArmorException ), - ('include "foo bar"', AppArmorException ), - ('include /foo bar', AppArmorException ), - ('include "/foo bar"', AppArmorException ), - ('include "foo bar/baz"', AppArmorException ), - ] + tests = ( + ('#include <>', AppArmorException), # '#include' + ('#include < >', AppArmorException), + ('#include ""', AppArmorException), + ('#include " "', AppArmorException), + ('#include', AppArmorException), + ('#include ', AppArmorException), + ('#include "foo"', AppArmorException), # LP: 1738880 (relative) + ('#include "foo" # comment', AppArmorException), + ('#include "foo"#comment', AppArmorException), + (' #include "foo" ', AppArmorException), + ('#include "foo/bar"', AppArmorException), + ('#include "foo/bar" # comment', AppArmorException), + ('#include "foo/bar"#comment', AppArmorException), + (' #include "foo/bar" ', AppArmorException), + ('#include foo', AppArmorException), # LP: 1738879 (no quotes) + ('#include foo/bar', AppArmorException), + ('#include /foo/bar', AppArmorException), + ('#include foo bar', AppArmorException), # LP: 1738877 (space in name) + ('#include foo bar/baz', AppArmorException), + ('#include "foo bar"', AppArmorException), + ('#include /foo bar', AppArmorException), + ('#include "/foo bar"', AppArmorException), + ('#include "foo bar/baz"', AppArmorException), + + ('include <>', AppArmorException), # 'include' + ('include < >', AppArmorException), + ('include ""', AppArmorException), + ('include " "', AppArmorException), + ('include', AppArmorException), + ('include ', AppArmorException), + ('include "foo"', AppArmorException), # LP: 1738880 (relative) + ('include "foo" # comment', AppArmorException), + ('include "foo"#comment', AppArmorException), + (' include "foo" ', AppArmorException), + ('include "foo/bar"', AppArmorException), + ('include "foo/bar" # comment', AppArmorException), + ('include "foo/bar"#comment', AppArmorException), + (' include "foo/bar" ', AppArmorException), + ('include foo', AppArmorException), # LP: 1738879 (no quotes) + ('include foo/bar', AppArmorException), + ('include /foo/bar', AppArmorException), + ('include foo bar', AppArmorException), # LP: 1738877 (space in name) + ('include foo bar/baz', AppArmorException), + ('include "foo bar"', AppArmorException), + ('include /foo bar', AppArmorException), + ('include "/foo bar"', AppArmorException), + ('include "foo bar/baz"', AppArmorException), + ) def _run_test(self, params, expected): with self.assertRaises(expected): re_match_include(params) + class Test_re_match_include_parse(AATest): - tests = [ - # path if exists magic path + tests = ( + # path if exists magic path # #include - ('#include <abstractions/base>', ('abstractions/base', False, True ) ), # magic path - ('#include <abstractions/base> # comment', ('abstractions/base', False, True ) ), - ('#include<abstractions/base>#comment', ('abstractions/base', False, True ) ), - (' #include <abstractions/base> ', ('abstractions/base', False, True ) ), - ('#include "/foo/bar"', ('/foo/bar', False, False) ), # absolute path - ('#include "/foo/bar" # comment', ('/foo/bar', False, False) ), - ('#include "/foo/bar"#comment', ('/foo/bar', False, False) ), - (' #include "/foo/bar" ', ('/foo/bar', False, False) ), + ('#include <abstractions/base>', ('abstractions/base', False, True)), # magic path + ('#include <abstractions/base> # comment', ('abstractions/base', False, True)), + ('#include<abstractions/base>#comment', ('abstractions/base', False, True)), + (' #include <abstractions/base> ', ('abstractions/base', False, True)), + ('#include "/foo/bar"', ('/foo/bar', False, False)), # absolute path + ('#include "/foo/bar" # comment', ('/foo/bar', False, False)), + ('#include "/foo/bar"#comment', ('/foo/bar', False, False)), + (' #include "/foo/bar" ', ('/foo/bar', False, False)), # include (without #) - ('include <abstractions/base>', ('abstractions/base', False, True ) ), # magic path - ('include <abstractions/base> # comment', ('abstractions/base', False, True ) ), - ('include<abstractions/base>#comment', ('abstractions/base', False, True ) ), - (' include <abstractions/base> ', ('abstractions/base', False, True ) ), - ('include "/foo/bar"', ('/foo/bar', False, False) ), # absolute path - ('include "/foo/bar" # comment', ('/foo/bar', False, False) ), - ('include "/foo/bar"#comment', ('/foo/bar', False, False) ), - (' include "/foo/bar" ', ('/foo/bar', False, False) ), + ('include <abstractions/base>', ('abstractions/base', False, True)), # magic path + ('include <abstractions/base> # comment', ('abstractions/base', False, True)), + ('include<abstractions/base>#comment', ('abstractions/base', False, True)), + (' include <abstractions/base> ', ('abstractions/base', False, True)), + ('include "/foo/bar"', ('/foo/bar', False, False)), # absolute path + ('include "/foo/bar" # comment', ('/foo/bar', False, False)), + ('include "/foo/bar"#comment', ('/foo/bar', False, False)), + (' include "/foo/bar" ', ('/foo/bar', False, False)), # #include if exists - ('#include if exists <abstractions/base>', ('abstractions/base', True, True ) ), # magic path - ('#include if exists <abstractions/base> # comment', ('abstractions/base', True, True ) ), - ('#include if exists<abstractions/base>#comment', ('abstractions/base', True, True ) ), - (' #include if exists<abstractions/base> ', ('abstractions/base', True, True ) ), - ('#include if exists "/foo/bar"', ('/foo/bar', True, False) ), # absolute path - ('#include if exists "/foo/bar" # comment', ('/foo/bar', True, False) ), - ('#include if exists "/foo/bar"#comment', ('/foo/bar', True, False) ), - (' #include if exists "/foo/bar" ', ('/foo/bar', True, False) ), + ('#include if exists <abstractions/base>', ('abstractions/base', True, True)), # magic path + ('#include if exists <abstractions/base> # comment', ('abstractions/base', True, True)), + ('#include if exists<abstractions/base>#comment', ('abstractions/base', True, True)), + (' #include if exists<abstractions/base> ', ('abstractions/base', True, True)), + ('#include if exists "/foo/bar"', ('/foo/bar', True, False)), # absolute path + ('#include if exists "/foo/bar" # comment', ('/foo/bar', True, False)), + ('#include if exists "/foo/bar"#comment', ('/foo/bar', True, False)), + (' #include if exists "/foo/bar" ', ('/foo/bar', True, False)), # include if exists (without #) - ('include if exists <abstractions/base>', ('abstractions/base', True, True ) ), # magic path - ('include if exists <abstractions/base> # comment', ('abstractions/base', True, True ) ), - ('include if exists<abstractions/base>#comment', ('abstractions/base', True, True ) ), - (' include if exists<abstractions/base> ', ('abstractions/base', True, True ) ), - ('include if exists "/foo/bar"', ('/foo/bar', True, False) ), # absolute path - ('include if exists "/foo/bar" # comment', ('/foo/bar', True, False) ), - ('include if exists "/foo/bar"#comment', ('/foo/bar', True, False) ), - (' include if exists "/foo/bar" ', ('/foo/bar', True, False) ), - - (' some #include if exists <abstractions/base>', (None, None, None ) ), # non-matching - (' /etc/fstab r,', (None, None, None ) ), - ('/usr/include r,', (None, None, None ) ), - ('/include r,', (None, None, None ) ), - ('abi <abi/4.19>,', (None, None, None ) ), # abi rule - ] + ('include if exists <abstractions/base>', ('abstractions/base', True, True)), # magic path + ('include if exists <abstractions/base> # comment', ('abstractions/base', True, True)), + ('include if exists<abstractions/base>#comment', ('abstractions/base', True, True)), + (' include if exists<abstractions/base> ', ('abstractions/base', True, True)), + ('include if exists "/foo/bar"', ('/foo/bar', True, False)), # absolute path + ('include if exists "/foo/bar" # comment', ('/foo/bar', True, False)), + ('include if exists "/foo/bar"#comment', ('/foo/bar', True, False)), + (' include if exists "/foo/bar" ', ('/foo/bar', True, False)), + + (' some #include if exists <abstractions/base>', (None, None, None)), # non-matching + (' /etc/fstab r,', (None, None, None)), + ('/usr/include r,', (None, None, None)), + ('/include r,', (None, None, None)), + ('abi <abi/4.19>,', (None, None, None)), # abi rule + ) def _run_test(self, params, expected): self.assertEqual(re_match_include_parse(params, 'include'), expected) + class Test_re_match_include_parse_abi(AATest): - tests = [ - # path if exists magic path - ('abi <abi/4.19>,', ('abi/4.19', False, True ) ), # magic path - ('abi <abi/4.19>, # comment', ('abi/4.19', False, True ) ), - (' abi <abi/4.19> , # comment', ('abi/4.19', False, True ) ), - ('abi "/abi/4.19" ,', ('/abi/4.19', False, False) ), # quoted path starting with / - ('abi "/abi/4.19", # comment', ('/abi/4.19', False, False) ), - (' abi "/abi/4.19" , # comment ', ('/abi/4.19', False, False) ), - (' abi "abi/4.19" , # comment ', ('abi/4.19', False, False) ), # quoted path, no leading / - ('abi abi/4.19,', ('abi/4.19', False, False) ), # without quotes - ('some abi <abi/4.19>,', (None, None, None ) ), # non-matching - (' /etc/fstab r,', (None, None, None ) ), - ('/usr/abi r,', (None, None, None ) ), - ('/abi r,', (None, None, None ) ), - ('#include <abstractions/base>', (None, None, None ) ), # include rule path - ] + tests = ( + # path if exists magic path + ('abi <abi/4.19>,', ('abi/4.19', False, True)), # magic path + ('abi <abi/4.19>, # comment', ('abi/4.19', False, True)), + (' abi <abi/4.19> , # comment', ('abi/4.19', False, True)), + ('abi "/abi/4.19" ,', ('/abi/4.19', False, False)), # quoted path starting with / + ('abi "/abi/4.19", # comment', ('/abi/4.19', False, False)), + (' abi "/abi/4.19" , # comment ', ('/abi/4.19', False, False)), + (' abi "abi/4.19" , # comment ', ('abi/4.19', False, False)), # quoted path, no leading / + ('abi abi/4.19,', ('abi/4.19', False, False)), # without quotes + ('some abi <abi/4.19>,', (None, None, None)), # non-matching + (' /etc/fstab r,', (None, None, None)), + ('/usr/abi r,', (None, None, None)), + ('/abi r,', (None, None, None)), + ('#include <abstractions/base>', (None, None, None)), # include rule path + ) def _run_test(self, params, expected): self.assertEqual(re_match_include_parse(params, 'abi'), expected) + class Test_re_match_include_parse_errors(AATest): - tests = [ - (('include <>', 'include'), AppArmorException), # various rules with empty filename - (('include ""', 'include'), AppArmorException), - (('include ', 'include'), AppArmorException), - (('abi <>,', 'abi'), AppArmorException), - (('abi "",', 'abi'), AppArmorException), - (('abi ,', 'abi'), AppArmorException), - (('abi <foo>,', 'invalid'), AppArmorBug), # invalid rule name - ] + tests = ( + (('include <>', 'include'), AppArmorException), # various rules with empty filename + (('include ""', 'include'), AppArmorException), + (('include ', 'include'), AppArmorException), + (('abi <>,', 'abi'), AppArmorException), + (('abi "",', 'abi'), AppArmorException), + (('abi ,', 'abi'), AppArmorException), + (('abi <foo>,', 'invalid'), AppArmorBug), # invalid rule name + ) def _run_test(self, params, expected): with self.assertRaises(expected): rule, rule_name = params re_match_include_parse(rule, rule_name) + class TestStripParenthesis(AATest): - tests = [ - ('foo', 'foo' ), - ('(foo)', 'foo' ), - ('( foo )', 'foo' ), - ('(foo', '(foo' ), - ('foo )', 'foo )' ), - ('foo ()', 'foo ()' ), - ('()', '' ), - ('( )', '' ), - ('(())', '()' ), - (' (foo)', '(foo)' ), # parenthesis not first char, whitespace stripped nevertheless - ('(foo) ', '(foo)' ), # parenthesis not last char, whitespace stripped nevertheless - ] + tests = ( + ('foo', 'foo'), + ('(foo)', 'foo'), + ('( foo )', 'foo'), + ('(foo', '(foo'), + ('foo )', 'foo )'), + ('foo ()', 'foo ()'), + ('()', ''), + ('( )', ''), + ('(())', '()'), + (' (foo)', '(foo)'), # parenthesis not first char, whitespace stripped nevertheless + ('(foo) ', '(foo)'), # parenthesis not last char, whitespace stripped nevertheless + ) def _run_test(self, params, expected): self.assertEqual(strip_parenthesis(params), expected) + class TestStripQuotes(AATest): - tests = [ - ('foo', 'foo'), - ('"foo"', 'foo'), - ('"foo', '"foo'), - ('foo"', 'foo"'), - ('""', ''), - ('foo"bar', 'foo"bar'), - ('"foo"bar"', 'foo"bar'), - ('""""foo"bar""""', '"""foo"bar"""'), - ('', ''), - ('/', '/'), - ('"', '"'), - ] + tests = ( + ('foo', 'foo'), + ('"foo"', 'foo'), + ('"foo', '"foo'), + ('foo"', 'foo"'), + ('""', ''), + ('foo"bar', 'foo"bar'), + ('"foo"bar"', 'foo"bar'), + ('""""foo"bar""""', '"""foo"bar"""'), + ('', ''), + ('/', '/'), + ('"', '"'), + ) def _run_test(self, params, expected): self.assertEqual(strip_quotes(params), expected) diff --git a/utils/test/test-rlimit.py b/utils/test/test-rlimit.py index a1e041340..be3ddebd5 100644 --- a/utils/test/test-rlimit.py +++ b/utils/test/test-rlimit.py @@ -15,20 +15,22 @@ import unittest from collections import namedtuple -from common_test import AATest, setup_all_loops -from apparmor.rule.rlimit import RlimitRule, RlimitRuleset, split_unit +from apparmor.common import AppArmorBug, AppArmorException +# from apparmor.logparser import ReadLog from apparmor.rule import BaseRule -from apparmor.common import AppArmorException, AppArmorBug -#from apparmor.logparser import ReadLog +from apparmor.rule.rlimit import RlimitRule, RlimitRuleset, split_unit from apparmor.translations import init_translation +from common_test import AATest, setup_all_loops + _ = init_translation() -exp = namedtuple('exp', ['audit', 'allow_keyword', 'deny', 'comment', - 'rlimit', 'value', 'all_values']) +exp = namedtuple( + 'exp', ('audit', 'allow_keyword', 'deny', 'comment', 'rlimit', 'value', 'all_values')) # --- tests for single RlimitRule --- # + class RlimitTest(AATest): def _compare_obj(self, obj, expected): self.assertEqual(expected.allow_keyword, obj.allow_keyword) @@ -39,32 +41,33 @@ class RlimitTest(AATest): self.assertEqual(expected.deny, obj.deny) self.assertEqual(expected.comment, obj.comment) + class RlimitTestParse(RlimitTest): - tests = [ - # rawrule audit allow deny comment rlimit value all/infinity? - ('set rlimit as <= 2047MB,' , exp(False, False, False, '' , 'as' , '2047MB' , False)), - ('set rlimit as <= 2047 MB,' , exp(False, False, False, '' , 'as' , '2047 MB' , False)), - ('set rlimit cpu <= 1024,' , exp(False, False, False, '' , 'cpu' , '1024' , False)), - ('set rlimit stack <= 1024GB,' , exp(False, False, False, '' , 'stack' , '1024GB' , False)), - ('set rlimit stack <= 1024 GB,' , exp(False, False, False, '' , 'stack' , '1024 GB' , False)), - ('set rlimit rtprio <= 10, # comment' , exp(False, False, False, ' # comment' , 'rtprio' , '10' , False)), - ('set rlimit core <= 44444KB,' , exp(False, False, False, '' , 'core' , '44444KB' , False)), - ('set rlimit core <= 44444 KB,' , exp(False, False, False, '' , 'core' , '44444 KB' , False)), - ('set rlimit rttime <= 60ms,' , exp(False, False, False, '' , 'rttime' , '60ms' , False)), - ('set rlimit cpu <= infinity,' , exp(False, False, False, '' , 'cpu' , None , True )), - ('set rlimit nofile <= 256,' , exp(False, False, False, '' , 'nofile' , '256' , False)), - ('set rlimit data <= 4095KB,' , exp(False, False, False, '' , 'data' , '4095KB' , False)), - ('set rlimit cpu <= 12, # cmt' , exp(False, False, False, ' # cmt' , 'cpu' , '12' , False)), - ('set rlimit ofile <= 1234,' , exp(False, False, False, '' , 'ofile' , '1234' , False)), - ('set rlimit msgqueue <= 4444,' , exp(False, False, False, '' , 'msgqueue' , '4444' , False)), - ('set rlimit nice <= -10,' , exp(False, False, False, '' , 'nice' , '-10' , False)), - ('set rlimit rttime <= 60minutes,' , exp(False, False, False, '' , 'rttime' , '60minutes' , False)), - ('set rlimit fsize <= 1023MB,' , exp(False, False, False, '' , 'fsize' , '1023MB' , False)), - ('set rlimit nproc <= 1,' , exp(False, False, False, '' , 'nproc' , '1' , False)), - ('set rlimit rss <= infinity, # cmt' , exp(False, False, False, ' # cmt' , 'rss' , None , True )), - ('set rlimit memlock <= 10240,' , exp(False, False, False, '' , 'memlock' , '10240' , False)), - ('set rlimit sigpending <= 42,' , exp(False, False, False, '' , 'sigpending' , '42' , False)), - ] + tests = ( + # rawrule audit allow deny comment rlimit value all/infinity? + ('set rlimit as <= 2047MB,', exp(False, False, False, '', 'as', '2047MB', False)), + ('set rlimit as <= 2047 MB,', exp(False, False, False, '', 'as', '2047 MB', False)), + ('set rlimit cpu <= 1024,', exp(False, False, False, '', 'cpu', '1024', False)), + ('set rlimit stack <= 1024GB,', exp(False, False, False, '', 'stack', '1024GB', False)), + ('set rlimit stack <= 1024 GB,', exp(False, False, False, '', 'stack', '1024 GB', False)), + ('set rlimit rtprio <= 10, # comment', exp(False, False, False, ' # comment', 'rtprio', '10', False)), + ('set rlimit core <= 44444KB,', exp(False, False, False, '', 'core', '44444KB', False)), + ('set rlimit core <= 44444 KB,', exp(False, False, False, '', 'core', '44444 KB', False)), + ('set rlimit rttime <= 60ms,', exp(False, False, False, '', 'rttime', '60ms', False)), + ('set rlimit cpu <= infinity,', exp(False, False, False, '', 'cpu', None, True)), + ('set rlimit nofile <= 256,', exp(False, False, False, '', 'nofile', '256', False)), + ('set rlimit data <= 4095KB,', exp(False, False, False, '', 'data', '4095KB', False)), + ('set rlimit cpu <= 12, # cmt', exp(False, False, False, ' # cmt', 'cpu', '12', False)), + ('set rlimit ofile <= 1234,', exp(False, False, False, '', 'ofile', '1234', False)), + ('set rlimit msgqueue <= 4444,', exp(False, False, False, '', 'msgqueue', '4444', False)), + ('set rlimit nice <= -10,', exp(False, False, False, '', 'nice', '-10', False)), + ('set rlimit rttime <= 60minutes,', exp(False, False, False, '', 'rttime', '60minutes', False)), + ('set rlimit fsize <= 1023MB,', exp(False, False, False, '', 'fsize', '1023MB', False)), + ('set rlimit nproc <= 1,', exp(False, False, False, '', 'nproc', '1', False)), + ('set rlimit rss <= infinity, # cmt', exp(False, False, False, ' # cmt', 'rss', None, True)), + ('set rlimit memlock <= 10240,', exp(False, False, False, '', 'memlock', '10240', False)), + ('set rlimit sigpending <= 42,', exp(False, False, False, '', 'sigpending', '42', False)), + ) def _run_test(self, rawrule, expected): self.assertTrue(RlimitRule.match(rawrule)) @@ -72,83 +75,85 @@ class RlimitTestParse(RlimitTest): self.assertEqual(rawrule.strip(), obj.raw_rule) self._compare_obj(obj, expected) + class RlimitTestParseInvalid(RlimitTest): - tests = [ - ('set rlimit,' , AppArmorException), # missing parts - ('set rlimit <= 5,' , AppArmorException), - ('set rlimit cpu <= ,' , AppArmorException), - ('set rlimit cpu <= "",' , AppArmorBug), # evil quoting trick - ('set rlimit foo <= 5,' , AppArmorException), # unknown rlimit - ('set rlimit rttime <= 60m,' , AppArmorException), # 'm' could mean 'ms' or 'minutes' - ('set rlimit cpu <= 20ms,' , AppArmorException), # cpu doesn't support 'ms'... - ('set rlimit cpu <= 20us,' , AppArmorException), # ... or 'us' - ('set rlimit nice <= 20MB,' , AppArmorException), # invalid unit for this rlimit type - ('set rlimit cpu <= 20MB,' , AppArmorException), - ('set rlimit data <= 20seconds,' , AppArmorException), - ('set rlimit locks <= 20seconds,' , AppArmorException), - ] + tests = ( + ('set rlimit,', AppArmorException), # missing parts + ('set rlimit <= 5,', AppArmorException), + ('set rlimit cpu <= ,', AppArmorException), + ('set rlimit cpu <= "",', AppArmorBug), # evil quoting trick + ('set rlimit foo <= 5,', AppArmorException), # unknown rlimit + ('set rlimit rttime <= 60m,', AppArmorException), # 'm' could mean 'ms' or 'minutes' + ('set rlimit cpu <= 20ms,', AppArmorException), # cpu doesn't support 'ms'... + ('set rlimit cpu <= 20us,', AppArmorException), # ... or 'us' + ('set rlimit nice <= 20MB,', AppArmorException), # invalid unit for this rlimit type + ('set rlimit cpu <= 20MB,', AppArmorException), + ('set rlimit data <= 20seconds,', AppArmorException), + ('set rlimit locks <= 20seconds,', AppArmorException), + ) def _run_test(self, rawrule, expected): - #self.assertFalse(RlimitRule.match(rawrule)) # the main regex isn't very strict + # self.assertFalse(RlimitRule.match(rawrule)) # the main regex isn't very strict with self.assertRaises(expected): RlimitRule.parse(rawrule) + class RlimitTestParseFromLog(RlimitTest): pass # def test_net_from_log(self): - # parser = ReadLog('', '', '') - - # event = 'type=AVC ...' - - # parsed_event = parser.parse_event(event) - - # self.assertEqual(parsed_event, { - # ... - # }) - - # obj = RlimitRule(RlimitRule.ALL, parsed_event['name2'], log_event=parsed_event) - - # # audit allow deny comment rlimit value all? - # expected = exp(False, False, False, '' , None, '/foo/rename', False) - - # self._compare_obj(obj, expected) - - # self.assertEqual(obj.get_raw(1), ' rlimit -> /foo/rename,') + # parser = ReadLog('', '', '') + # + # event = 'type=AVC ...' + # + # parsed_event = parser.parse_event(event) + # + # self.assertEqual(parsed_event, { + # ... + # }) + # + # obj = RlimitRule(RlimitRule.ALL, parsed_event['name2'], log_event=parsed_event) + # + # # audit allow deny comment rlimit value all? + # expected = exp(False, False, False, '', None, '/foo/rename', False) + # + # self._compare_obj(obj, expected) + # + # self.assertEqual(obj.get_raw(1), ' rlimit -> /foo/rename,') class RlimitFromInit(RlimitTest): - tests = [ - # RlimitRule object audit allow deny comment rlimit value all/infinity? - (RlimitRule('as', '2047MB') , exp(False, False, False, '' , 'as' , '2047MB' , False)), - (RlimitRule('as', '2047 MB') , exp(False, False, False, '' , 'as' , '2047 MB' , False)), - (RlimitRule('cpu', '1024') , exp(False, False, False, '' , 'cpu' , '1024' , False)), - (RlimitRule('rttime', '60minutes') , exp(False, False, False, '' , 'rttime' , '60minutes', False)), - (RlimitRule('nice', '-10') , exp(False, False, False, '' , 'nice' , '-10' , False)), - (RlimitRule('rss', RlimitRule.ALL) , exp(False, False, False, '' , 'rss' , None , True )), - ] + tests = ( + # RlimitRule object audit allow deny comment rlimit value all/infinity? + (RlimitRule('as', '2047MB'), exp(False, False, False, '', 'as', '2047MB', False)), + (RlimitRule('as', '2047 MB'), exp(False, False, False, '', 'as', '2047 MB', False)), + (RlimitRule('cpu', '1024'), exp(False, False, False, '', 'cpu', '1024', False)), + (RlimitRule('rttime', '60minutes'), exp(False, False, False, '', 'rttime', '60minutes', False)), + (RlimitRule('nice', '-10'), exp(False, False, False, '', 'nice', '-10', False)), + (RlimitRule('rss', RlimitRule.ALL), exp(False, False, False, '', 'rss', None, True)), + ) def _run_test(self, obj, expected): self._compare_obj(obj, expected) class InvalidRlimitInit(AATest): - tests = [ - # init params expected exception - (['as' , '' ] , AppArmorBug), # empty value - (['' , '1024' ] , AppArmorException), # empty rlimit - ([' ', '1024' ] , AppArmorException), # whitespace rlimit - (['as' , ' ' ] , AppArmorBug), # whitespace value - (['xyxy', '1024' ] , AppArmorException), # invalid rlimit - ([dict(), '1024' ] , AppArmorBug), # wrong type for rlimit - ([None , '1024' ] , AppArmorBug), # wrong type for rlimit - (['as' , dict() ] , AppArmorBug), # wrong type for value - (['as' , None ] , AppArmorBug), # wrong type for value - (['cpu' , '100xy2' ] , AppArmorException), # invalid unit - ] + tests = ( + # init params expected exception + (('as', ''), AppArmorBug), # empty value + (('', '1024'), AppArmorException), # empty rlimit + ((' ', '1024'), AppArmorException), # whitespace rlimit + (('as', ' '), AppArmorBug), # whitespace value + (('xyxy', '1024'), AppArmorException), # invalid rlimit + ((dict(), '1024'), AppArmorBug), # wrong type for rlimit + ((None, '1024'), AppArmorBug), # wrong type for rlimit + (('as', dict()), AppArmorBug), # wrong type for value + (('as', None), AppArmorBug), # wrong type for value + (('cpu', '100xy2'), AppArmorException), # invalid unit + ) def _run_test(self, params, expected): with self.assertRaises(expected): - RlimitRule(params[0], params[1]) + RlimitRule(*params) def test_missing_params_1(self): with self.assertRaises(TypeError): @@ -202,16 +207,16 @@ class InvalidRlimitTest(AATest): class WriteRlimitTest(AATest): - tests = [ - # raw rule clean rule - (' set rlimit cpu <= 1024 , # foo ' , 'set rlimit cpu <= 1024, # foo'), - (' set rlimit stack <= 1024GB ,' , 'set rlimit stack <= 1024GB,'), - (' set rlimit rttime <= 100ms , # foo bar' , 'set rlimit rttime <= 100ms, # foo bar'), - (' set rlimit cpu <= infinity , ' , 'set rlimit cpu <= infinity,'), - (' set rlimit msgqueue <= 4444 , ' , 'set rlimit msgqueue <= 4444,'), - (' set rlimit nice <= 5 , # foo bar' , 'set rlimit nice <= 5, # foo bar'), - (' set rlimit nice <= -5 , # cmt' , 'set rlimit nice <= -5, # cmt'), - ] + tests = ( + # raw rule clean rule + (' set rlimit cpu <= 1024 , # foo ', 'set rlimit cpu <= 1024, # foo'), + (' set rlimit stack <= 1024GB ,', 'set rlimit stack <= 1024GB,'), + (' set rlimit rttime <= 100ms , # foo bar', 'set rlimit rttime <= 100ms, # foo bar'), + (' set rlimit cpu <= infinity , ', 'set rlimit cpu <= infinity,'), + (' set rlimit msgqueue <= 4444 , ', 'set rlimit msgqueue <= 4444,'), + (' set rlimit nice <= 5 , # foo bar', 'set rlimit nice <= 5, # foo bar'), + (' set rlimit nice <= -5 , # cmt', 'set rlimit nice <= -5, # cmt'), + ) def _run_test(self, rawrule, expected): self.assertTrue(RlimitRule.match(rawrule)) @@ -238,82 +243,96 @@ class RlimitCoveredTest(AATest): self.assertTrue(RlimitRule.match(param)) - self.assertEqual(obj.is_equal(check_obj), expected[0], 'Mismatch in is_equal, expected %s' % expected[0]) - self.assertEqual(obj.is_equal(check_obj, True), expected[1], 'Mismatch in is_equal/strict, expected %s' % expected[1]) + self.assertEqual( + obj.is_equal(check_obj), expected[0], + 'Mismatch in is_equal, expected %s' % expected[0]) + self.assertEqual( + obj.is_equal(check_obj, True), expected[1], + 'Mismatch in is_equal/strict, expected %s' % expected[1]) + + self.assertEqual( + obj.is_covered(check_obj), expected[2], + 'Mismatch in is_covered, expected %s' % expected[2]) + self.assertEqual( + obj.is_covered(check_obj, True, True), expected[3], + 'Mismatch in is_covered/exact, expected %s' % expected[3]) - self.assertEqual(obj.is_covered(check_obj), expected[2], 'Mismatch in is_covered, expected %s' % expected[2]) - self.assertEqual(obj.is_covered(check_obj, True, True), expected[3], 'Mismatch in is_covered/exact, expected %s' % expected[3]) class RlimitCoveredTest_01(RlimitCoveredTest): rule = 'set rlimit cpu <= 150,' - tests = [ - # rule equal strict equal covered covered exact - ('set rlimit as <= 100MB,' , [ False , False , False , False ]), - ('set rlimit rttime <= 150,' , [ False , False , False , False ]), - ('set rlimit cpu <= 100,' , [ False , False , True , True ]), - ('set rlimit cpu <= 150,' , [ True , True , True , True ]), - ('set rlimit cpu <= 300,' , [ False , False , False , False ]), - ('set rlimit cpu <= 10seconds,' , [ False , False , True , True ]), - ('set rlimit cpu <= 150seconds,', [ True , False , True , True ]), - ('set rlimit cpu <= 300seconds,', [ False , False , False , False ]), - ('set rlimit cpu <= 1minutes,' , [ False , False , True , True ]), - ('set rlimit cpu <= 1min,' , [ False , False , True , True ]), - ('set rlimit cpu <= 3minutes,' , [ False , False , False , False ]), - ('set rlimit cpu <= 1hour,' , [ False , False , False , False ]), - ('set rlimit cpu <= 2 days,' , [ False , False , False , False ]), - ('set rlimit cpu <= 1 week,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ('set rlimit as <= 100MB,', (False, False, False, False)), + ('set rlimit rttime <= 150,', (False, False, False, False)), + ('set rlimit cpu <= 100,', (False, False, True, True)), + ('set rlimit cpu <= 150,', (True, True, True, True)), + ('set rlimit cpu <= 300,', (False, False, False, False)), + ('set rlimit cpu <= 10seconds,', (False, False, True, True)), + ('set rlimit cpu <= 150seconds,', (True, False, True, True)), + ('set rlimit cpu <= 300seconds,', (False, False, False, False)), + ('set rlimit cpu <= 1minutes,', (False, False, True, True)), + ('set rlimit cpu <= 1min,', (False, False, True, True)), + ('set rlimit cpu <= 3minutes,', (False, False, False, False)), + ('set rlimit cpu <= 1hour,', (False, False, False, False)), + ('set rlimit cpu <= 2 days,', (False, False, False, False)), + ('set rlimit cpu <= 1 week,', (False, False, False, False)), + ) + class RlimitCoveredTest_02(RlimitCoveredTest): rule = 'set rlimit data <= 4MB,' - tests = [ - # rule equal strict equal covered covered exact - ('set rlimit data <= 100,' , [ False , False , True , True ]), - ('set rlimit data <= 2KB,' , [ False , False , True , True ]), - ('set rlimit data <= 2MB,' , [ False , False , True , True ]), - ('set rlimit data <= 4194304,' , [ True , False , True , True ]), - ('set rlimit data <= 4096KB,' , [ True , False , True , True ]), - ('set rlimit data <= 4MB,' , [ True , True , True , True ]), - ('set rlimit data <= 4 MB,' , [ True , False , True , True ]), - ('set rlimit data <= 6MB,' , [ False , False , False , False ]), - ('set rlimit data <= 6 MB,' , [ False , False , False , False ]), - ('set rlimit data <= 1GB,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ('set rlimit data <= 100,', (False, False, True, True)), + ('set rlimit data <= 2KB,', (False, False, True, True)), + ('set rlimit data <= 2MB,', (False, False, True, True)), + ('set rlimit data <= 4194304,', (True, False, True, True)), + ('set rlimit data <= 4096KB,', (True, False, True, True)), + ('set rlimit data <= 4MB,', (True, True, True, True)), + ('set rlimit data <= 4 MB,', (True, False, True, True)), + ('set rlimit data <= 6MB,', (False, False, False, False)), + ('set rlimit data <= 6 MB,', (False, False, False, False)), + ('set rlimit data <= 1GB,', (False, False, False, False)), + ) + class RlimitCoveredTest_03(RlimitCoveredTest): rule = 'set rlimit nice <= -1,' - tests = [ - # rule equal strict equal covered covered exact - ('set rlimit nice <= 5,' , [ False , False , True , True ]), - ('set rlimit nice <= 0,' , [ False , False , True , True ]), - ('set rlimit nice <= -1,' , [ True , True , True , True ]), - ('set rlimit nice <= -3,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ('set rlimit nice <= 5,', (False, False, True, True)), + ('set rlimit nice <= 0,', (False, False, True, True)), + ('set rlimit nice <= -1,', (True, True, True, True)), + ('set rlimit nice <= -3,', (False, False, False, False)), + ) + class RlimitCoveredTest_04(RlimitCoveredTest): rule = 'set rlimit locks <= 42,' - tests = [ - # rule equal strict equal covered covered exact - ('set rlimit locks <= 20,' , [ False , False , True , True ]), - ('set rlimit locks <= 40,' , [ False , False , True , True ]), - ('set rlimit locks <= 42,' , [ True , True , True , True ]), - ('set rlimit locks <= 60,' , [ False , False , False , False ]), - ('set rlimit locks <= infinity,', [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ('set rlimit locks <= 20,', (False, False, True, True)), + ('set rlimit locks <= 40,', (False, False, True, True)), + ('set rlimit locks <= 42,', (True, True, True, True)), + ('set rlimit locks <= 60,', (False, False, False, False)), + ('set rlimit locks <= infinity,', (False, False, False, False)), + ) + class RlimitCoveredTest_05(RlimitCoveredTest): rule = 'set rlimit locks <= infinity,' - tests = [ - # rule equal strict equal covered covered exact - ('set rlimit locks <= 20,' , [ False , False , True , True ]), - ('set rlimit cpu <= 40,' , [ False , False , False , False ]), - ('set rlimit locks <= infinity,', [ True , True , True , True ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ('set rlimit locks <= 20,', (False, False, True, True)), + ('set rlimit cpu <= 40,', (False, False, False, False)), + ('set rlimit locks <= infinity,', (True, True, True, True)), + ) + class RlimitCoveredTest_Invalid(AATest): def test_borked_obj_is_covered_1(self): @@ -350,18 +369,20 @@ class RlimitCoveredTest_Invalid(AATest): with self.assertRaises(AppArmorBug): obj.is_equal(testobj) + class RlimitLogprofHeaderTest(AATest): - tests = [ - ('set rlimit cpu <= infinity,', [_('Rlimit'), 'cpu', _('Value'), 'infinity', ]), - ('set rlimit as <= 200MB,', [_('Rlimit'), 'as', _('Value'), '200MB', ]), - ('set rlimit rttime <= 200ms,', [_('Rlimit'), 'rttime', _('Value'), '200ms', ]), - ('set rlimit nproc <= 1,', [_('Rlimit'), 'nproc', _('Value'), '1', ]), - ] + tests = ( + ('set rlimit cpu <= infinity,', [_('Rlimit'), 'cpu', _('Value'), 'infinity']), + ('set rlimit as <= 200MB,', [_('Rlimit'), 'as', _('Value'), '200MB']), + ('set rlimit rttime <= 200ms,', [_('Rlimit'), 'rttime', _('Value'), '200ms']), + ('set rlimit nproc <= 1,', [_('Rlimit'), 'nproc', _('Value'), '1']), + ) def _run_test(self, params, expected): - obj = RlimitRule._parse(params) + obj = RlimitRule.parse(params) self.assertEqual(obj.logprof_header(), expected) + # --- tests for RlimitRuleset --- # class RlimitRulesTest(AATest): @@ -398,6 +419,7 @@ class RlimitRulesTest(AATest): self.assertEqual(expected_raw, ruleset.get_raw()) self.assertEqual(expected_clean, ruleset.get_clean()) + class RlimitGlobTestAATest(AATest): def setUp(self): self.ruleset = RlimitRuleset() @@ -415,6 +437,7 @@ class RlimitGlobTestAATest(AATest): # get_glob_ext is not available for rlimit rules self.ruleset.get_glob_ext('set rlimit cpu <= 100,') + class RlimitDeleteTestAATest(AATest): # no de-duplication tests for rlimit - de-duplication consists of is_covered() (which we already test) and # code from BaseRuleset (which is already tested in the capability, network and change_profile tests). @@ -424,11 +447,11 @@ class RlimitDeleteTestAATest(AATest): # --- other tests --- # class RlimitSplit_unitTest(AATest): - tests = [ - ('40MB' , ( 40, 'MB',)), - ('40 MB' , ( 40, 'MB',)), - ('40' , ( 40, '', )), - ] + tests = ( + ('40MB', (40, 'MB')), + ('40 MB', (40, 'MB')), + ('40', (40, '')), + ) def _run_test(self, params, expected): self.assertEqual(split_unit(params), expected) @@ -437,16 +460,17 @@ class RlimitSplit_unitTest(AATest): with self.assertRaises(AppArmorBug): split_unit('MB') + class RlimitSize_to_intTest(AATest): def AASetup(self): self.obj = RlimitRule('cpu', '1') - tests = [ - ('40GB' , 40 * 1024 * 1024 * 1024), - ('40MB' , 41943040), - ('40KB' , 40960), - ('40' , 40), - ] + tests = ( + ('40GB', 40 * 1024 * 1024 * 1024), + ('40MB', 41943040), + ('40KB', 40960), + ('40', 40), + ) def _run_test(self, params, expected): self.assertEqual(self.obj.size_to_int(params), expected) @@ -455,20 +479,21 @@ class RlimitSize_to_intTest(AATest): with self.assertRaises(AppArmorException): self.obj.size_to_int('20mice') + class RlimitTime_to_intTest(AATest): def AASetup(self): self.obj = RlimitRule('cpu', '1') - tests = [ - ('40' , 0.00004), - ('30us' , 0.00003), - ('40ms' , 0.04), - ('40seconds', 40), - ('2minutes' , 2*60), - ('2hours' , 2*60*60), - ('1 day' , 1*60*60*24), - ('2 weeks' , 2*60*60*24*7), - ] + tests = ( + ('40', 0.00004), + ('30us', 0.00003), + ('40ms', 0.04), + ('40seconds', 40), + ('2minutes', 2*60), + ('2hours', 2*60*60), + ('1 day', 1*60*60*24), + ('2 weeks', 2*60*60*24*7), + ) def _run_test(self, params, expected): self.assertEqual(self.obj.time_to_int(params, 'us'), expected) @@ -487,7 +512,6 @@ class RlimitTime_to_intTest(AATest): self.obj.time_to_int('20mice', 'seconds') - setup_all_loops(__name__) if __name__ == '__main__': unittest.main(verbosity=1) diff --git a/utils/test/test-severity.py b/utils/test/test-severity.py index 1e80ff100..0d720066d 100755 --- a/utils/test/test-severity.py +++ b/utils/test/test-severity.py @@ -15,15 +15,16 @@ # # ---------------------------------------------------------------------- import unittest -from common_test import AATest, setup_all_loops import apparmor.severity as severity from apparmor.common import AppArmorException +from common_test import AATest, setup_all_loops + class SeverityBaseTest(AATest): def AASetup(self): - self.sev_db = severity.Severity('severity.db', 'unknown') + self.sev_db = severity.Severity('../severity.db', 'unknown') def _capability_severity_test(self, cap, expected_rank): rank = self.sev_db.rank_capability(cap) @@ -35,37 +36,42 @@ class SeverityBaseTest(AATest): self.assertEqual(rank, expected_rank, 'expected rank %s, got %s' % (expected_rank, rank)) + class SeverityTest(SeverityBaseTest): - tests = [ - (['/usr/bin/whatis', 'x' ], 5), - (['/etc', 'x' ], 'unknown'), - (['/dev/doublehit', 'x' ], 0), - (['/dev/doublehit', 'rx' ], 4), - (['/dev/doublehit', 'rwx' ], 8), - (['/dev/tty10', 'rwx' ], 9), - (['/var/adm/foo/**', 'rx' ], 3), - (['/etc/apparmor/**', 'r' ], 6), - (['/etc/**', 'r' ], 'unknown'), - (['/usr/foo@bar', 'r' ], 'unknown'), ## filename containing @ - (['/home/foo@bar', 'rw' ], 6), ## filename containing @ - ] + tests = ( + (('/usr/bin/whatis', 'x'), 5), + (('/etc', 'x'), 'unknown'), + (('/dev/doublehit', 'x'), 0), + (('/dev/doublehit', 'rx'), 4), + (('/dev/doublehit', 'rwx'), 8), + (('/dev/tty10', 'rwx'), 9), + (('/var/adm/foo/**', 'rx'), 3), + (('/etc/apparmor/**', 'r'), 6), + (('/etc/**', 'r'), 'unknown'), + (('/usr/foo@bar', 'r'), 'unknown'), # filename containing @ + (('/home/foo@bar', 'rw'), 6), # filename containing @ + (('/etc/apache2/ssl.key/bar', 'r'), 7), # /etc/apache2/** (3) vs. /etc/apache2/**ssl** (7) + (('/etc/apache2/foo/ssl/bar', 'r'), 7), # additional path level triggers otherwise untested branch + (('/proc/sys/kernel/hotplug', 'rwx'), 10), # non-glob filename, severity depends on mode + ) def _run_test(self, params, expected): - self._simple_severity_w_perm(params[0], params[1], expected) ## filename containing @ + self._simple_severity_w_perm(params[0], params[1], expected) def test_invalid_rank(self): with self.assertRaises(AppArmorException): self._simple_severity_w_perm('unexpected_unput', 'rw', 6) + class SeverityTestCap(SeverityBaseTest): - tests = [ + tests = ( ('KILL', 8), ('SETPCAP', 9), ('setpcap', 9), ('UNKNOWN', 'unknown'), ('K*', 'unknown'), ('__ALL__', 10), - ] + ) def _run_test(self, params, expected): self._capability_severity_test(params, expected) @@ -75,45 +81,49 @@ class SeverityTestCap(SeverityBaseTest): class SeverityVarsTest(SeverityBaseTest): - tests = [ - (['@{PROC}/sys/vm/overcommit_memory', 'r'], 6), - (['@{HOME}/sys/@{PROC}/overcommit_memory', 'r'], 4), - (['/overco@{multiarch}mmit_memory', 'r'], 'unknown'), - (['@{PROC}/sys/@{TFTP_DIR}/overcommit_memory', 'r'], 6), - (['@{somepaths}/somefile', 'r'], 7), - ] + tests = ( + (('@{PROC}/sys/vm/overcommit_memory', 'r'), 6), + (('@{HOME}/sys/@{PROC}/overcommit_memory', 'r'), 4), + (('/overco@{multiarch}mmit_memory', 'r'), 'unknown'), + (('@{PROC}/sys/@{TFTP_DIR}/overcommit_memory', 'r'), 6), + (('@{somepaths}/somefile', 'r'), 7), + (('@{strangevar}/somefile', 'r'), 6), + ) def _run_test(self, params, expected): vars = { - '@{HOME}': {'@{HOMEDIRS}/*/', '/root/'}, - '@{HOMEDIRS}': {'/home/', '/storage/'}, - '@{multiarch}': {'*-linux-gnu*'}, - '@{TFTP_DIR}': {'/var/tftp /srv/tftpboot'}, - '@{PROC}': {'/proc/'}, - '@{somepaths}': {'/home/foo/downloads', '@{HOMEDIRS}/foo/.ssh/'}, + # Note: using [] instead of {} is intentional to keep order of checking (and therefore code coverage) constant + '@{HOME}': ['@{HOMEDIRS}/*/', '/root/'], + '@{HOMEDIRS}': ['/home/', '/storage/'], + '@{multiarch}': ['*-linux-gnu*'], + '@{TFTP_DIR}': ['/var/tftp /srv/tftpboot'], + '@{PROC}': ['/proc/'], + '@{somepaths}': ['/home/foo/downloads', '@{HOMEDIRS}/foo/.ssh/'], + '@{strangevar}': ['/srv/', '/proc/'], } self.sev_db.set_variables(vars) self._simple_severity_w_perm(params[0], params[1], expected) + class SeverityDBTest(AATest): def _test_db(self, contents): self.db_file = self.writeTmpfile('severity.db', contents) self.sev_db = severity.Severity(self.db_file) return self.sev_db - tests = [ - ("CAP_LEASE 18\n" , AppArmorException), # out of range - ("CAP_LEASE -1\n" , AppArmorException), # out of range - ("/etc/passwd* 0 4\n" , AppArmorException), # insufficient vals - ("/etc/passwd* 0 4 5 6\n" , AppArmorException), # too many vals - ("/etc/passwd* -2 4 6\n" , AppArmorException), # out of range - ("/etc/passwd* 12 4 6\n" , AppArmorException), # out of range - ("/etc/passwd* 2 -4 6\n" , AppArmorException), # out of range - ("/etc/passwd 2 14 6\n" , AppArmorException), # out of range - ("/etc/passwd 2 4 -12\n" , AppArmorException), # out of range - ("/etc/passwd 2 4 4294967297\n" , AppArmorException), # out of range - ("garbage line\n" , AppArmorException), - ] + tests = ( + ("CAP_LEASE 18\n", AppArmorException), # out of range + ("CAP_LEASE -1\n", AppArmorException), # out of range + ("/etc/passwd* 0 4\n", AppArmorException), # insufficient vals + ("/etc/passwd* 0 4 5 6\n", AppArmorException), # too many vals + ("/etc/passwd* -2 4 6\n", AppArmorException), # out of range + ("/etc/passwd* 12 4 6\n", AppArmorException), # out of range + ("/etc/passwd* 2 -4 6\n", AppArmorException), # out of range + ("/etc/passwd 2 14 6\n", AppArmorException), # out of range + ("/etc/passwd 2 4 -12\n", AppArmorException), # out of range + ("/etc/passwd 2 4 4294967297\n", AppArmorException), # out of range + ("garbage line\n", AppArmorException), + ) def _run_test(self, params, expected): with self.assertRaises(expected): @@ -141,6 +151,7 @@ class SeverityDBTest(AATest): with self.assertRaises(AppArmorException): severity.Severity() + setup_all_loops(__name__) if __name__ == '__main__': unittest.main(verbosity=1) diff --git a/utils/test/test-signal.py b/utils/test/test-signal.py index f95a17885..e272adedf 100644 --- a/utils/test/test-signal.py +++ b/utils/test/test-signal.py @@ -15,20 +15,23 @@ import unittest from collections import namedtuple -from common_test import AATest, setup_all_loops -from apparmor.rule.signal import SignalRule, SignalRuleset -from apparmor.rule import BaseRule -from apparmor.common import AppArmorException, AppArmorBug +from apparmor.common import AppArmorBug, AppArmorException from apparmor.logparser import ReadLog +from apparmor.rule import BaseRule +from apparmor.rule.signal import SignalRule, SignalRuleset from apparmor.translations import init_translation +from common_test import AATest, setup_all_loops + _ = init_translation() -exp = namedtuple('exp', ['audit', 'allow_keyword', 'deny', 'comment', - 'access', 'all_access', 'signal', 'all_signals', 'peer', 'all_peers']) +exp = namedtuple( + 'exp', ('audit', 'allow_keyword', 'deny', 'comment', + 'access', 'all_access', 'signal', 'all_signals', 'peer', 'all_peers')) # --- tests for single SignalRule --- # + class SignalTest(AATest): def _compare_obj(self, obj, expected): self.assertEqual(expected.allow_keyword, obj.allow_keyword) @@ -45,23 +48,24 @@ class SignalTest(AATest): self.assertEqual(expected.deny, obj.deny) self.assertEqual(expected.comment, obj.comment) + class SignalTestParse(SignalTest): - tests = [ - # SignalRule object audit allow deny comment access all? signal all? peer all? - ('signal,' , exp(False, False, False, '', None , True , None, True, None, True )), - ('signal send,' , exp(False, False, False, '', {'send'}, False, None, True, None, True )), - ('signal (send, receive),' , exp(False, False, False, '', {'send', 'receive'}, False, None, True, None, True )), - ('signal send set=quit,' , exp(False, False, False, '', {'send'}, False, {'quit'}, False, None, True )), - ('deny signal send set=quit, # cmt' , exp(False, False, True , ' # cmt', {'send'}, False, {'quit'}, False, None, True )), - ('audit allow signal set=int,' , exp(True , True , False, '', None , True , {'int'}, False, None, True )), - ('signal set=quit peer=unconfined,' , exp(False, False, False, '', None , True , {'quit'}, False, 'unconfined', False )), - ('signal send set=(quit),' , exp(False, False, False, '', {'send'}, False, {'quit'}, False, None, True )), - ('signal send set=(quit, int),' , exp(False, False, False, '', {'send'}, False, {'quit', 'int'}, False, None, True )), - ('signal set=(quit, int),' , exp(False, False, False, '', None, True, {'quit', 'int'}, False, None, True )), - ('signal send set = ( quit , int ) ,' , exp(False, False, False, '', {'send'}, False, {'quit', 'int'}, False, None, True )), - ('signal peer=/foo,' , exp(False, False, False, '', None , True , None, True, '/foo', False )), - ('signal r set=quit set=int peer=/foo,' , exp(False, False, False, '', {'r'}, False, {'quit', 'int'}, False, '/foo', False )), - ] + tests = ( + # SignalRule object audit allow deny comment access all? signal all? peer all? + ('signal,', exp(False, False, False, '', None, True, None, True, None, True)), + ('signal send,', exp(False, False, False, '', {'send'}, False, None, True, None, True)), + ('signal (send, receive),', exp(False, False, False, '', {'send', 'receive'}, False, None, True, None, True)), + ('signal send set=quit,', exp(False, False, False, '', {'send'}, False, {'quit'}, False, None, True)), + ('deny signal send set=quit, # cmt', exp(False, False, True, ' # cmt', {'send'}, False, {'quit'}, False, None, True)), + ('audit allow signal set=int,', exp(True, True, False, '', None, True, {'int'}, False, None, True)), + ('signal set=quit peer=unconfined,', exp(False, False, False, '', None, True, {'quit'}, False, 'unconfined', False)), + ('signal send set=(quit),', exp(False, False, False, '', {'send'}, False, {'quit'}, False, None, True)), + ('signal send set=(quit, int),', exp(False, False, False, '', {'send'}, False, {'quit', 'int'}, False, None, True)), + ('signal set=(quit, int),', exp(False, False, False, '', None, True, {'quit', 'int'}, False, None, True)), + ('signal send set = ( quit , int ) ,', exp(False, False, False, '', {'send'}, False, {'quit', 'int'}, False, None, True)), + ('signal peer=/foo,', exp(False, False, False, '', None, True, None, True, '/foo', False)), + ('signal r set=quit set=int peer=/foo,', exp(False, False, False, '', {'r'}, False, {'quit', 'int'}, False, '/foo', False)), + ) def _run_test(self, rawrule, expected): self.assertTrue(SignalRule.match(rawrule)) @@ -69,24 +73,26 @@ class SignalTestParse(SignalTest): self.assertEqual(rawrule.strip(), obj.raw_rule) self._compare_obj(obj, expected) + class SignalTestParseInvalid(SignalTest): - tests = [ - ('signal foo,' , AppArmorException), - ('signal foo bar,' , AppArmorException), - ('signal foo int,' , AppArmorException), - ('signal send bar,' , AppArmorException), - ('signal send receive,' , AppArmorException), - ('signal set=,' , AppArmorException), - ('signal set=int set=,' , AppArmorException), - ('signal set=invalid,' , AppArmorException), - ('signal peer=,' , AppArmorException), - ] + tests = ( + ('signal foo,', AppArmorException), + ('signal foo bar,', AppArmorException), + ('signal foo int,', AppArmorException), + ('signal send bar,', AppArmorException), + ('signal send receive,', AppArmorException), + ('signal set=,', AppArmorException), + ('signal set=int set=,', AppArmorException), + ('signal set=invalid,', AppArmorException), + ('signal peer=,', AppArmorException), + ) def _run_test(self, rawrule, expected): self.assertTrue(SignalRule.match(rawrule)) # the above invalid rules still match the main regex! with self.assertRaises(expected): SignalRule.parse(rawrule) + class SignalTestParseFromLog(SignalTest): def test_signal_from_log(self): parser = ReadLog('', '', '') @@ -117,54 +123,57 @@ class SignalTestParseFromLog(SignalTest): 'family': None, 'protocol': None, 'sock_type': None, + 'class': None, }) obj = SignalRule(parsed_event['denied_mask'], parsed_event['signal'], parsed_event['peer'], log_event=parsed_event) - # audit allow deny comment access all? signal all? peer all? - expected = exp(False, False, False, '', {'send'}, False, {'term'}, False, '/usr/bin/pulseaudio///usr/lib/pulseaudio/pulse/gconf-helper', False) + # audit allow deny comment access all? signal all? peer all? + expected = exp(False, False, False, '', {'send'}, False, {'term'}, False, '/usr/bin/pulseaudio///usr/lib/pulseaudio/pulse/gconf-helper', False) self._compare_obj(obj, expected) self.assertEqual(obj.get_raw(1), ' signal send set=term peer=/usr/bin/pulseaudio///usr/lib/pulseaudio/pulse/gconf-helper,') + class SignalFromInit(SignalTest): - tests = [ - # SignalRule object audit allow deny comment access all? signal all? peer all? - (SignalRule('r', 'hup', 'unconfined', deny=True) , exp(False, False, True , '' , {'r'}, False, {'hup'}, False, 'unconfined', False)), - (SignalRule(('r', 'send'), ('hup', 'int'), '/bin/foo') , exp(False, False, False, '' , {'r', 'send'},False, {'hup', 'int'}, False, '/bin/foo', False)), - (SignalRule(SignalRule.ALL, 'int', '/bin/foo') , exp(False, False, False, '' , None, True, {'int'}, False, '/bin/foo', False )), - (SignalRule('rw', SignalRule.ALL, '/bin/foo') , exp(False, False, False, '' , {'rw'}, False, None, True, '/bin/foo', False )), - (SignalRule('rw', ('int'), SignalRule.ALL) , exp(False, False, False, '' , {'rw'}, False, {'int'}, False, None, True )), - (SignalRule(SignalRule.ALL, SignalRule.ALL, SignalRule.ALL) , exp(False, False, False, '' , None , True, None, True, None, True )), - ] + tests = ( + # SignalRule object audit allow deny comment access all? signal all? peer all? + (SignalRule('r', 'hup', 'unconfined', deny=True), exp(False, False, True, '', {'r'}, False, {'hup'}, False, 'unconfined', False)), + (SignalRule(('r', 'send'), ('hup', 'int'), '/bin/foo'), exp(False, False, False, '', {'r', 'send'}, False, {'hup', 'int'}, False, '/bin/foo', False)), + (SignalRule(SignalRule.ALL, 'int', '/bin/foo'), exp(False, False, False, '', None, True, {'int'}, False, '/bin/foo', False)), + (SignalRule('rw', SignalRule.ALL, '/bin/foo'), exp(False, False, False, '', {'rw'}, False, None, True, '/bin/foo', False)), + (SignalRule('rw', ('int'), SignalRule.ALL), exp(False, False, False, '', {'rw'}, False, {'int'}, False, None, True)), + (SignalRule(SignalRule.ALL, SignalRule.ALL, SignalRule.ALL), exp(False, False, False, '', None, True, None, True, None, True)), + ) def _run_test(self, obj, expected): self._compare_obj(obj, expected) + class InvalidSignalInit(AATest): - tests = [ + tests = ( # init params expected exception - (['send', '' , '/foo' ] , AppArmorBug), # empty signal - (['' , 'int' , '/foo' ] , AppArmorBug), # empty access - (['send', 'int' , '' ] , AppArmorBug), # empty peer - ([' ', 'int' , '/foo' ] , AppArmorBug), # whitespace access - (['send', ' ' , '/foo' ] , AppArmorBug), # whitespace signal - (['send', 'int' , ' ' ] , AppArmorBug), # whitespace peer - (['xyxy', 'int' , '/foo' ] , AppArmorException), # invalid access - (['send', 'xyxy', '/foo' ] , AppArmorException), # invalid signal + (('send', '', '/foo'), AppArmorBug), # empty signal + (('', 'int', '/foo'), AppArmorBug), # empty access + (('send', 'int', ''), AppArmorBug), # empty peer + ((' ', 'int', '/foo'), AppArmorBug), # whitespace access + (('send', ' ', '/foo'), AppArmorBug), # whitespace signal + (('send', 'int', ' '), AppArmorBug), # whitespace peer + (('xyxy', 'int', '/foo'), AppArmorException), # invalid access + (('send', 'xyxy', '/foo'), AppArmorException), # invalid signal # XXX is 'invalid peer' possible at all? - ([dict(), 'int' , '/foo' ] , AppArmorBug), # wrong type for access - ([None , 'int' , '/foo' ] , AppArmorBug), # wrong type for access - (['send', dict(), '/foo' ] , AppArmorBug), # wrong type for signal - (['send', None , '/foo' ] , AppArmorBug), # wrong type for signal - (['send', 'int' , dict() ] , AppArmorBug), # wrong type for peer - (['send', 'int' , None ] , AppArmorBug), # wrong type for peer - ] + ((dict(), 'int', '/foo'), AppArmorBug), # wrong type for access + ((None, 'int', '/foo'), AppArmorBug), # wrong type for access + (('send', dict(), '/foo'), AppArmorBug), # wrong type for signal + (('send', None, '/foo'), AppArmorBug), # wrong type for signal + (('send', 'int', dict()), AppArmorBug), # wrong type for peer + (('send', 'int', None), AppArmorBug), # wrong type for peer + ) def _run_test(self, params, expected): with self.assertRaises(expected): - SignalRule(params[0], params[1], params[2]) + SignalRule(*params) def test_missing_params_1(self): with self.assertRaises(TypeError): @@ -178,6 +187,7 @@ class InvalidSignalInit(AATest): with self.assertRaises(TypeError): SignalRule('r', 'int') + class InvalidSignalTest(AATest): def _check_invalid_rawrule(self, rawrule): obj = None @@ -225,32 +235,32 @@ class WriteSignalTestAATest(AATest): self.assertEqual(expected.strip(), clean, 'unexpected clean rule') self.assertEqual(rawrule.strip(), raw, 'unexpected raw rule') - tests = [ - # raw rule clean rule - (' signal , # foo ' , 'signal, # foo'), - (' audit signal send,' , 'audit signal send,'), - (' audit signal (send ),' , 'audit signal send,'), - (' audit signal (send , receive ),' , 'audit signal (receive send),'), - (' deny signal send set=quit,# foo bar' , 'deny signal send set=quit, # foo bar'), - (' deny signal send set=(quit), ' , 'deny signal send set=quit,'), - (' deny signal send set=(int , quit),' , 'deny signal send set=(int quit),'), - (' deny signal send set=(quit, int ),' , 'deny signal send set=(int quit),'), - (' deny signal send ,# foo bar' , 'deny signal send, # foo bar'), - (' allow signal set=int ,# foo bar' , 'allow signal set=int, # foo bar'), - ('signal,' , 'signal,'), - ('signal (receive),' , 'signal receive,'), - ('signal (send),' , 'signal send,'), - ('signal (send receive),' , 'signal (receive send),'), - ('signal r,' , 'signal r,'), - ('signal w,' , 'signal w,'), - ('signal rw,' , 'signal rw,'), - ('signal send set=("hup"),' , 'signal send set=hup,'), - ('signal (receive) set=kill,' , 'signal receive set=kill,'), - ('signal w set=(quit int),' , 'signal w set=(int quit),'), - ('signal receive peer=foo,' , 'signal receive peer=foo,'), - ('signal (send receive) peer=/usr/bin/bar,' , 'signal (receive send) peer=/usr/bin/bar,'), - ('signal wr set=(pipe, usr1) peer=/sbin/baz,' , 'signal wr set=(pipe usr1) peer=/sbin/baz,'), - ] + tests = ( + # raw rule clean rule + (' signal , # foo ', 'signal, # foo'), + (' audit signal send,', 'audit signal send,'), + (' audit signal (send ),', 'audit signal send,'), + (' audit signal (send , receive ),', 'audit signal (receive send),'), + (' deny signal send set=quit,# foo bar', 'deny signal send set=quit, # foo bar'), + (' deny signal send set=(quit), ', 'deny signal send set=quit,'), + (' deny signal send set=(int , quit),', 'deny signal send set=(int quit),'), + (' deny signal send set=(quit, int ),', 'deny signal send set=(int quit),'), + (' deny signal send ,# foo bar', 'deny signal send, # foo bar'), + (' allow signal set=int ,# foo bar', 'allow signal set=int, # foo bar'), + ('signal,', 'signal,'), + ('signal (receive),', 'signal receive,'), + ('signal (send),', 'signal send,'), + ('signal (send receive),', 'signal (receive send),'), + ('signal r,', 'signal r,'), + ('signal w,', 'signal w,'), + ('signal rw,', 'signal rw,'), + ('signal send set=("hup"),', 'signal send set=hup,'), + ('signal (receive) set=kill,', 'signal receive set=kill,'), + ('signal w set=(quit int),', 'signal w set=(int quit),'), + ('signal receive peer=foo,', 'signal receive peer=foo,'), + ('signal (send receive) peer=/usr/bin/bar,', 'signal (receive send) peer=/usr/bin/bar,'), + ('signal wr set=(pipe, usr1) peer=/sbin/baz,', 'signal wr set=(pipe usr1) peer=/sbin/baz,'), + ) def test_write_manually(self): obj = SignalRule('send', 'quit', '/foo', allow_keyword=True) @@ -268,207 +278,223 @@ class SignalCoveredTest(AATest): self.assertTrue(SignalRule.match(param)) - self.assertEqual(obj.is_equal(check_obj), expected[0], 'Mismatch in is_equal, expected %s' % expected[0]) - self.assertEqual(obj.is_equal(check_obj, True), expected[1], 'Mismatch in is_equal/strict, expected %s' % expected[1]) + self.assertEqual( + obj.is_equal(check_obj), expected[0], + 'Mismatch in is_equal, expected %s' % expected[0]) + self.assertEqual( + obj.is_equal(check_obj, True), expected[1], + 'Mismatch in is_equal/strict, expected %s' % expected[1]) + + self.assertEqual( + obj.is_covered(check_obj), expected[2], + 'Mismatch in is_covered, expected %s' % expected[2]) + self.assertEqual( + obj.is_covered(check_obj, True, True), expected[3], + 'Mismatch in is_covered/exact, expected %s' % expected[3]) - self.assertEqual(obj.is_covered(check_obj), expected[2], 'Mismatch in is_covered, expected %s' % expected[2]) - self.assertEqual(obj.is_covered(check_obj, True, True), expected[3], 'Mismatch in is_covered/exact, expected %s' % expected[3]) class SignalCoveredTest_01(SignalCoveredTest): rule = 'signal send,' - tests = [ - # rule equal strict equal covered covered exact - ('signal,' , [ False , False , False , False ]), - ('signal send,' , [ True , True , True , True ]), - ('signal send peer=unconfined,' , [ False , False , True , True ]), - ('signal send, # comment' , [ True , False , True , True ]), - ('allow signal send,' , [ True , False , True , True ]), - ('signal send,' , [ True , False , True , True ]), - ('signal send set=quit,' , [ False , False , True , True ]), - ('signal send set=int,' , [ False , False , True , True ]), - ('audit signal send,' , [ False , False , False , False ]), - ('audit signal,' , [ False , False , False , False ]), - ('signal receive,' , [ False , False , False , False ]), - ('signal set=int,' , [ False , False , False , False ]), - ('audit deny signal send,' , [ False , False , False , False ]), - ('deny signal send,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ('signal,', (False, False, False, False)), + ('signal send,', (True, True, True, True)), + ('signal send peer=unconfined,', (False, False, True, True)), + ('signal send, # comment', (True, False, True, True)), + ('allow signal send,', (True, False, True, True)), + ('signal send,', (True, False, True, True)), + ('signal send set=quit,', (False, False, True, True)), + ('signal send set=int,', (False, False, True, True)), + ('audit signal send,', (False, False, False, False)), + ('audit signal,', (False, False, False, False)), + ('signal receive,', (False, False, False, False)), + ('signal set=int,', (False, False, False, False)), + ('audit deny signal send,', (False, False, False, False)), + ('deny signal send,', (False, False, False, False)), + ) + class SignalCoveredTest_02(SignalCoveredTest): rule = 'audit signal send,' - tests = [ - # rule equal strict equal covered covered exact - ( 'signal send,' , [ False , False , True , False ]), - ('audit signal send,' , [ True , True , True , True ]), - ( 'signal send set=quit,' , [ False , False , True , False ]), - ('audit signal send set=quit,' , [ False , False , True , True ]), - ( 'signal,' , [ False , False , False , False ]), - ('audit signal,' , [ False , False , False , False ]), - ('signal receive,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ( 'signal send,', (False, False, True, False)), + ('audit signal send,', (True, True, True, True)), + ( 'signal send set=quit,', (False, False, True, False)), + ('audit signal send set=quit,', (False, False, True, True)), + ( 'signal,', (False, False, False, False)), + ('audit signal,', (False, False, False, False)), + ('signal receive,', (False, False, False, False)), + ) + class SignalCoveredTest_03(SignalCoveredTest): rule = 'signal send set=quit,' - tests = [ - # rule equal strict equal covered covered exact - ( 'signal send set=quit,' , [ True , True , True , True ]), - ('allow signal send set=quit,' , [ True , False , True , True ]), - ( 'signal send,' , [ False , False , False , False ]), - ( 'signal,' , [ False , False , False , False ]), - ( 'signal send set=int,' , [ False , False , False , False ]), - ('audit signal,' , [ False , False , False , False ]), - ('audit signal send set=quit,' , [ False , False , False , False ]), - ('audit signal set=quit,' , [ False , False , False , False ]), - ( 'signal send,' , [ False , False , False , False ]), - ( 'signal,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ( 'signal send set=quit,', (True, True, True, True)), + ('allow signal send set=quit,', (True, False, True, True)), + ( 'signal send,', (False, False, False, False)), + ( 'signal,', (False, False, False, False)), + ( 'signal send set=int,', (False, False, False, False)), + ('audit signal,', (False, False, False, False)), + ('audit signal send set=quit,', (False, False, False, False)), + ('audit signal set=quit,', (False, False, False, False)), + ( 'signal send,', (False, False, False, False)), + ( 'signal,', (False, False, False, False)), + ) + class SignalCoveredTest_04(SignalCoveredTest): rule = 'signal,' - tests = [ - # rule equal strict equal covered covered exact - ( 'signal,' , [ True , True , True , True ]), - ('allow signal,' , [ True , False , True , True ]), - ( 'signal send,' , [ False , False , True , True ]), - ( 'signal w set=quit,' , [ False , False , True , True ]), - ( 'signal set=int,' , [ False , False , True , True ]), - ( 'signal send set=quit,' , [ False , False , True , True ]), - ('audit signal,' , [ False , False , False , False ]), - ('deny signal,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ( 'signal,', (True, True, True, True)), + ('allow signal,', (True, False, True, True)), + ( 'signal send,', (False, False, True, True)), + ( 'signal w set=quit,', (False, False, True, True)), + ( 'signal set=int,', (False, False, True, True)), + ( 'signal send set=quit,', (False, False, True, True)), + ('audit signal,', (False, False, False, False)), + ('deny signal,', (False, False, False, False)), + ) + class SignalCoveredTest_05(SignalCoveredTest): rule = 'deny signal send,' - tests = [ - # rule equal strict equal covered covered exact - ( 'deny signal send,' , [ True , True , True , True ]), - ('audit deny signal send,' , [ False , False , False , False ]), - ( 'signal send,' , [ False , False , False , False ]), # XXX should covered be true here? - ( 'deny signal receive,' , [ False , False , False , False ]), - ( 'deny signal,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ( 'deny signal send,', (True, True, True, True)), + ('audit deny signal send,', (False, False, False, False)), + ( 'signal send,', (False, False, False, False)), # XXX should covered be true here? + ( 'deny signal receive,', (False, False, False, False)), + ( 'deny signal,', (False, False, False, False)), + ) + class SignalCoveredTest_06(SignalCoveredTest): rule = 'signal send peer=unconfined,' - tests = [ - # rule equal strict equal covered covered exact - ('signal,' , [ False , False , False , False ]), - ('signal send,' , [ False , False , False , False ]), - ('signal send peer=unconfined,' , [ True , True , True , True ]), - ('signal peer=unconfined,' , [ False , False , False , False ]), - ('signal send, # comment' , [ False , False , False , False ]), - ('allow signal send,' , [ False , False , False , False ]), - ('allow signal send peer=unconfined,' , [ True , False , True , True ]), - ('allow signal send peer=/foo/bar,' , [ False , False , False , False ]), - ('allow signal send peer=/**,' , [ False , False , False , False ]), - ('allow signal send peer=**,' , [ False , False , False , False ]), - ('signal send,' , [ False , False , False , False ]), - ('signal send peer=unconfined,' , [ True , False , True , True ]), - ('signal send set=quit,' , [ False , False , False , False ]), - ('signal send set=int peer=unconfined,',[ False , False , True , True ]), - ('audit signal send peer=unconfined,' , [ False , False , False , False ]), - ('audit signal,' , [ False , False , False , False ]), - ('signal receive,' , [ False , False , False , False ]), - ('signal set=int,' , [ False , False , False , False ]), - ('audit deny signal send,' , [ False , False , False , False ]), - ('deny signal send,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ('signal,', (False, False, False, False)), + ('signal send,', (False, False, False, False)), + ('signal send peer=unconfined,', (True, True, True, True)), + ('signal peer=unconfined,', (False, False, False, False)), + ('signal send, # comment', (False, False, False, False)), + ('allow signal send,', (False, False, False, False)), + ('allow signal send peer=unconfined,', (True, False, True, True)), + ('allow signal send peer=/foo/bar,', (False, False, False, False)), + ('allow signal send peer=/**,', (False, False, False, False)), + ('allow signal send peer=**,', (False, False, False, False)), + ('signal send,', (False, False, False, False)), + ('signal send peer=unconfined,', (True, False, True, True)), + ('signal send set=quit,', (False, False, False, False)), + ('signal send set=int peer=unconfined,', (False, False, True, True)), + ('audit signal send peer=unconfined,', (False, False, False, False)), + ('audit signal,', (False, False, False, False)), + ('signal receive,', (False, False, False, False)), + ('signal set=int,', (False, False, False, False)), + ('audit deny signal send,', (False, False, False, False)), + ('deny signal send,', (False, False, False, False)), + ) + class SignalCoveredTest_07(SignalCoveredTest): rule = 'signal send peer=/foo/bar,' - tests = [ - # rule equal strict equal covered covered exact - ('signal,' , [ False , False , False , False ]), - ('signal send,' , [ False , False , False , False ]), - ('signal send peer=/foo/bar,' , [ True , True , True , True ]), - ('signal send peer=/foo/*,' , [ False , False , False , False ]), - ('signal send peer=/**,' , [ False , False , False , False ]), - ('signal send peer=/what/*,' , [ False , False , False , False ]), - ('signal peer=/foo/bar,' , [ False , False , False , False ]), - ('signal send, # comment' , [ False , False , False , False ]), - ('allow signal send,' , [ False , False , False , False ]), - ('allow signal send peer=/foo/bar,' , [ True , False , True , True ]), - ('signal send,' , [ False , False , False , False ]), - ('signal send peer=/foo/bar,' , [ True , False , True , True ]), - ('signal send peer=/what/ever,' , [ False , False , False , False ]), - ('signal send set=quit,' , [ False , False , False , False ]), - ('signal send set=int peer=/foo/bar,' , [ False , False , True , True ]), - ('audit signal send peer=/foo/bar,' , [ False , False , False , False ]), - ('audit signal,' , [ False , False , False , False ]), - ('signal receive,' , [ False , False , False , False ]), - ('signal set=int,' , [ False , False , False , False ]), - ('audit deny signal send,' , [ False , False , False , False ]), - ('deny signal send,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ('signal,', (False, False, False, False)), + ('signal send,', (False, False, False, False)), + ('signal send peer=/foo/bar,', (True, True, True, True)), + ('signal send peer=/foo/*,', (False, False, False, False)), + ('signal send peer=/**,', (False, False, False, False)), + ('signal send peer=/what/*,', (False, False, False, False)), + ('signal peer=/foo/bar,', (False, False, False, False)), + ('signal send, # comment', (False, False, False, False)), + ('allow signal send,', (False, False, False, False)), + ('allow signal send peer=/foo/bar,', (True, False, True, True)), + ('signal send,', (False, False, False, False)), + ('signal send peer=/foo/bar,', (True, False, True, True)), + ('signal send peer=/what/ever,', (False, False, False, False)), + ('signal send set=quit,', (False, False, False, False)), + ('signal send set=int peer=/foo/bar,', (False, False, True, True)), + ('audit signal send peer=/foo/bar,', (False, False, False, False)), + ('audit signal,', (False, False, False, False)), + ('signal receive,', (False, False, False, False)), + ('signal set=int,', (False, False, False, False)), + ('audit deny signal send,', (False, False, False, False)), + ('deny signal send,', (False, False, False, False)), + ) + class SignalCoveredTest_08(SignalCoveredTest): rule = 'signal send peer=**,' - tests = [ - # rule equal strict equal covered covered exact - ('signal,' , [ False , False , False , False ]), - ('signal send,' , [ False , False , False , False ]), - ('signal send peer=/foo/bar,' , [ False , False , True , True ]), - ('signal send peer=/foo/*,' , [ False , False , False , False ]), # TODO: wildcard vs. wildcard never matches in is_covered_aare() - ('signal send peer=/**,' , [ False , False , False , False ]), # TODO: wildcard vs. wildcard never matches in is_covered_aare() - ('signal send peer=/what/*,' , [ False , False , False , False ]), # TODO: wildcard vs. wildcard never matches in is_covered_aare() - ('signal peer=/foo/bar,' , [ False , False , False , False ]), - ('signal send, # comment' , [ False , False , False , False ]), - ('allow signal send,' , [ False , False , False , False ]), - ('allow signal send peer=/foo/bar,' , [ False , False , True , True ]), - ('signal send,' , [ False , False , False , False ]), - ('signal send peer=/foo/bar,' , [ False , False , True , True ]), - ('signal send peer=/what/ever,' , [ False , False , True , True ]), - ('signal send set=quit,' , [ False , False , False , False ]), - ('signal send set=int peer=/foo/bar,' , [ False , False , True , True ]), - ('audit signal send peer=/foo/bar,' , [ False , False , False , False ]), - ('audit signal,' , [ False , False , False , False ]), - ('signal receive,' , [ False , False , False , False ]), - ('signal set=int,' , [ False , False , False , False ]), - ('audit deny signal send,' , [ False , False , False , False ]), - ('deny signal send,' , [ False , False , False , False ]), - ] + tests = ( + # rule equal strict equal covered covered exact + ('signal,', (False, False, False, False)), + ('signal send,', (False, False, False, False)), + ('signal send peer=/foo/bar,', (False, False, True, True)), + ('signal send peer=/foo/*,', (False, False, False, False)), # TODO: wildcard vs. wildcard never matches in is_covered_aare() + ('signal send peer=/**,', (False, False, False, False)), # TODO: wildcard vs. wildcard never matches in is_covered_aare() + ('signal send peer=/what/*,', (False, False, False, False)), # TODO: wildcard vs. wildcard never matches in is_covered_aare() + ('signal peer=/foo/bar,', (False, False, False, False)), + ('signal send, # comment', (False, False, False, False)), + ('allow signal send,', (False, False, False, False)), + ('allow signal send peer=/foo/bar,', (False, False, True, True)), + ('signal send,', (False, False, False, False)), + ('signal send peer=/foo/bar,', (False, False, True, True)), + ('signal send peer=/what/ever,', (False, False, True, True)), + ('signal send set=quit,', (False, False, False, False)), + ('signal send set=int peer=/foo/bar,', (False, False, True, True)), + ('audit signal send peer=/foo/bar,', (False, False, False, False)), + ('audit signal,', (False, False, False, False)), + ('signal receive,', (False, False, False, False)), + ('signal set=int,', (False, False, False, False)), + ('audit deny signal send,', (False, False, False, False)), + ('deny signal send,', (False, False, False, False)), + ) + class SignalCoveredTest_09(SignalCoveredTest): rule = 'signal (send, receive) set=(int, quit),' - tests = [ - # rule equal strict equal covered covered exact - ('signal,' , [ False , False , False , False ]), - ('signal send,' , [ False , False , False , False ]), - ('signal send set=int,' , [ False , False , True , True ]), - ('signal receive set=quit,' , [ False , False , True , True ]), - ('signal (receive,send) set=int,' , [ False , False , True , True ]), - ('signal (receive,send) set=(int quit),',[True , False , True , True ]), - ('signal send set=(quit int),' , [ False , False , True , True ]), - ('signal send peer=/foo/bar,' , [ False , False , False , False ]), - ('signal send peer=/foo/*,' , [ False , False , False , False ]), - ('signal send peer=/**,' , [ False , False , False , False ]), - ('signal send peer=/what/*,' , [ False , False , False , False ]), - ('signal peer=/foo/bar,' , [ False , False , False , False ]), - ('signal send, # comment' , [ False , False , False , False ]), - ('allow signal send,' , [ False , False , False , False ]), - ('allow signal send peer=/foo/bar,' , [ False , False , False , False ]), - ('signal send,' , [ False , False , False , False ]), - ('signal send peer=/foo/bar,' , [ False , False , False , False ]), - ('signal send peer=/what/ever,' , [ False , False , False , False ]), - ('signal send set=quit,' , [ False , False , True , True ]), - ('signal send set=int peer=/foo/bar,' , [ False , False , True , True ]), - ('audit signal send peer=/foo/bar,' , [ False , False , False , False ]), - ('audit signal,' , [ False , False , False , False ]), - ('signal receive,' , [ False , False , False , False ]), - ('signal set=int,' , [ False , False , False , False ]), - ('audit deny signal send,' , [ False , False , False , False ]), - ('deny signal send,' , [ False , False , False , False ]), - ] - + tests = ( + # rule equal strict equal covered covered exact + ('signal,', (False, False, False, False)), + ('signal send,', (False, False, False, False)), + ('signal send set=int,', (False, False, True, True)), + ('signal receive set=quit,', (False, False, True, True)), + ('signal (receive,send) set=int,', (False, False, True, True)), + ('signal (receive,send) set=(int quit),', (True, False, True, True)), + ('signal send set=(quit int),', (False, False, True, True)), + ('signal send peer=/foo/bar,', (False, False, False, False)), + ('signal send peer=/foo/*,', (False, False, False, False)), + ('signal send peer=/**,', (False, False, False, False)), + ('signal send peer=/what/*,', (False, False, False, False)), + ('signal peer=/foo/bar,', (False, False, False, False)), + ('signal send, # comment', (False, False, False, False)), + ('allow signal send,', (False, False, False, False)), + ('allow signal send peer=/foo/bar,', (False, False, False, False)), + ('signal send,', (False, False, False, False)), + ('signal send peer=/foo/bar,', (False, False, False, False)), + ('signal send peer=/what/ever,', (False, False, False, False)), + ('signal send set=quit,', (False, False, True, True)), + ('signal send set=int peer=/foo/bar,', (False, False, True, True)), + ('audit signal send peer=/foo/bar,', (False, False, False, False)), + ('audit signal,', (False, False, False, False)), + ('signal receive,', (False, False, False, False)), + ('signal set=int,', (False, False, False, False)), + ('audit deny signal send,', (False, False, False, False)), + ('deny signal send,', (False, False, False, False)), + ) class SignalCoveredTest_Invalid(AATest): @@ -515,24 +541,26 @@ class SignalCoveredTest_Invalid(AATest): with self.assertRaises(AppArmorBug): obj.is_equal(testobj) + class SignalLogprofHeaderTest(AATest): - tests = [ - ('signal,', [ _('Access mode'), _('ALL'), _('Signal'), _('ALL'), _('Peer'), _('ALL'), ]), - ('signal send,', [ _('Access mode'), 'send', _('Signal'), _('ALL'), _('Peer'), _('ALL'), ]), - ('signal send set=quit,', [ _('Access mode'), 'send', _('Signal'), 'quit', _('Peer'), _('ALL'), ]), - ('deny signal,', [_('Qualifier'), 'deny', _('Access mode'), _('ALL'), _('Signal'), _('ALL'), _('Peer'), _('ALL'), ]), - ('allow signal send,', [_('Qualifier'), 'allow', _('Access mode'), 'send', _('Signal'), _('ALL'), _('Peer'), _('ALL'), ]), - ('audit signal send set=quit,', [_('Qualifier'), 'audit', _('Access mode'), 'send', _('Signal'), 'quit', _('Peer'), _('ALL'), ]), - ('audit deny signal send,', [_('Qualifier'), 'audit deny', _('Access mode'), 'send', _('Signal'), _('ALL'), _('Peer'), _('ALL'), ]), - ('signal set=(int, quit),', [ _('Access mode'), _('ALL'), _('Signal'), 'int quit', _('Peer'), _('ALL'), ]), - ('signal set=( quit, int),', [ _('Access mode'), _('ALL'), _('Signal'), 'int quit', _('Peer'), _('ALL'), ]), - ('signal (send, receive) set=( quit, int) peer=/foo,', [ _('Access mode'), 'receive send', _('Signal'), 'int quit', _('Peer'), '/foo', ]), - ] + tests = ( + ('signal,', [ _('Access mode'), _('ALL'), _('Signal'), _('ALL'), _('Peer'), _('ALL')]), + ('signal send,', [ _('Access mode'), 'send', _('Signal'), _('ALL'), _('Peer'), _('ALL')]), + ('signal send set=quit,', [ _('Access mode'), 'send', _('Signal'), 'quit', _('Peer'), _('ALL')]), + ('deny signal,', [_('Qualifier'), 'deny', _('Access mode'), _('ALL'), _('Signal'), _('ALL'), _('Peer'), _('ALL')]), + ('allow signal send,', [_('Qualifier'), 'allow', _('Access mode'), 'send', _('Signal'), _('ALL'), _('Peer'), _('ALL')]), + ('audit signal send set=quit,', [_('Qualifier'), 'audit', _('Access mode'), 'send', _('Signal'), 'quit', _('Peer'), _('ALL')]), + ('audit deny signal send,', [_('Qualifier'), 'audit deny', _('Access mode'), 'send', _('Signal'), _('ALL'), _('Peer'), _('ALL')]), + ('signal set=(int, quit),', [ _('Access mode'), _('ALL'), _('Signal'), 'int quit', _('Peer'), _('ALL')]), + ('signal set=( quit, int),', [ _('Access mode'), _('ALL'), _('Signal'), 'int quit', _('Peer'), _('ALL')]), + ('signal (send, receive) set=( quit, int) peer=/foo,', [ _('Access mode'), 'receive send', _('Signal'), 'int quit', _('Peer'), '/foo']), + ) def _run_test(self, params, expected): - obj = SignalRule._parse(params) + obj = SignalRule.parse(params) self.assertEqual(obj.logprof_header(), expected) + ## --- tests for SignalRuleset --- # class SignalRulesTest(AATest): @@ -546,10 +574,10 @@ class SignalRulesTest(AATest): def test_ruleset_1(self): ruleset = SignalRuleset() - rules = [ + rules = ( 'signal set=int,', 'signal send,', - ] + ) expected_raw = [ 'signal set=int,', @@ -571,11 +599,11 @@ class SignalRulesTest(AATest): def test_ruleset_2(self): ruleset = SignalRuleset() - rules = [ + rules = ( 'signal send set=int,', 'allow signal send,', 'deny signal set=quit, # example comment', - ] + ) expected_raw = [ ' signal send set=int,', @@ -616,8 +644,10 @@ class SignalGlobTestAATest(AATest): # get_glob_ext is not available for signal rules self.ruleset.get_glob_ext('signal send set=int,') -#class SignalDeleteTestAATest(AATest): -# pass + +# class SignalDeleteTestAATest(AATest): +# pass + setup_all_loops(__name__) if __name__ == '__main__': diff --git a/utils/test/test-translations.py b/utils/test/test-translations.py index e1b91623d..9e7493586 100644 --- a/utils/test/test-translations.py +++ b/utils/test/test-translations.py @@ -9,35 +9,35 @@ # # ------------------------------------------------------------------ -import unittest -from common_test import AATest, setup_all_loops - import gettext import os import subprocess +import unittest from apparmor.ui import CMDS, get_translated_hotkey +from common_test import AATest, setup_all_loops + class TestHotkeyConflicts(AATest): # check if there are any hotkey conflicts in one of the apparmor-utils translations - tests = [ - (['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_GLOB', 'CMD_GLOBEXT', 'CMD_NEW', 'CMD_AUDIT_OFF', 'CMD_ABORT', 'CMD_FINISHED'], True), # aa.py available_buttons() with CMD_AUDIT_OFF - (['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_GLOB', 'CMD_GLOBEXT', 'CMD_NEW', 'CMD_AUDIT_NEW', 'CMD_ABORT', 'CMD_FINISHED'], True), # aa.py available_buttons() with CMD_AUDIT_NEW - (['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_GLOB', 'CMD_GLOBEXT', 'CMD_NEW', 'CMD_AUDIT_OFF', 'CMD_USER_ON', 'CMD_ABORT', 'CMD_FINISHED'], True), # aa.py available_buttons() with CMD_AUDIT_OFF and CMD_USER_ON - (['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_GLOB', 'CMD_GLOBEXT', 'CMD_NEW', 'CMD_AUDIT_OFF', 'CMD_USER_OFF', 'CMD_ABORT', 'CMD_FINISHED'], True), # aa.py available_buttons() with CMD_AUDIT_OFF and CMD_USER_OFF - (['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_GLOB', 'CMD_GLOBEXT', 'CMD_NEW', 'CMD_AUDIT_NEW', 'CMD_USER_ON', 'CMD_ABORT', 'CMD_FINISHED'], True), # aa.py available_buttons() with CMD_AUDIT_NEW and CMD_USER_ON - (['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_GLOB', 'CMD_GLOBEXT', 'CMD_NEW', 'CMD_AUDIT_NEW', 'CMD_USER_OFF', 'CMD_ABORT', 'CMD_FINISHED'], True), # aa.py available_buttons() with CMD_AUDIT_NEW and CMD_USER_OFF - (['CMD_SAVE_CHANGES', 'CMD_SAVE_SELECTED', 'CMD_VIEW_CHANGES', 'CMD_VIEW_CHANGES_CLEAN', 'CMD_ABORT'], True), # aa.py save_profiles() - (['CMD_VIEW_PROFILE', 'CMD_USE_PROFILE', 'CMD_CREATE_PROFILE', 'CMD_ABORT'], True), # aa.py get_profile() - (['CMD_ix', 'CMD_pix', 'CMD_cix', 'CMD_nix', 'CMD_EXEC_IX_OFF', 'CMD_ux', 'CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED'], True), # aa.py build_x_functions() with exec_toggle - (['CMD_ix', 'CMD_cx', 'CMD_px', 'CMD_nx', 'CMD_ux', 'CMD_EXEC_IX_ON', 'CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED'], True), # aa.py build_x_functions() without exec_toggle - (['CMD_ADDHAT', 'CMD_USEDEFAULT', 'CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED'], True), # aa.py ask_addhat() - (['CMD_YES', 'CMD_NO', 'CMD_CANCEL'], True), # ui.py UI_YesNo() and UI_YesNoCancel - (['CMD_SAVE_CHANGES', 'CMD_VIEW_CHANGES', 'CMD_ABORT', 'CMD_IGNORE_ENTRY'], True), # aa-mergeprof act() - (['CMD_ALLOW', 'CMD_ABORT'], True), # aa-mergeprof conflict_mode() - (['CMD_ADDSUBPROFILE', 'CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED'], True), # aa-mergeprof ask_the_questions() - new subprofile - (['CMD_ADDHAT', 'CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED'], True), # aa-mergeprof ask_the_questions() - new hat - ] + tests = ( + (('CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_GLOB', 'CMD_GLOBEXT', 'CMD_NEW', 'CMD_AUDIT_OFF', 'CMD_ABORT', 'CMD_FINISHED'), True), # aa.py available_buttons() with CMD_AUDIT_OFF + (('CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_GLOB', 'CMD_GLOBEXT', 'CMD_NEW', 'CMD_AUDIT_NEW', 'CMD_ABORT', 'CMD_FINISHED'), True), # aa.py available_buttons() with CMD_AUDIT_NEW + (('CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_GLOB', 'CMD_GLOBEXT', 'CMD_NEW', 'CMD_AUDIT_OFF', 'CMD_USER_ON', 'CMD_ABORT', 'CMD_FINISHED'), True), # aa.py available_buttons() with CMD_AUDIT_OFF and CMD_USER_ON + (('CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_GLOB', 'CMD_GLOBEXT', 'CMD_NEW', 'CMD_AUDIT_OFF', 'CMD_USER_OFF', 'CMD_ABORT', 'CMD_FINISHED'), True), # aa.py available_buttons() with CMD_AUDIT_OFF and CMD_USER_OFF + (('CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_GLOB', 'CMD_GLOBEXT', 'CMD_NEW', 'CMD_AUDIT_NEW', 'CMD_USER_ON', 'CMD_ABORT', 'CMD_FINISHED'), True), # aa.py available_buttons() with CMD_AUDIT_NEW and CMD_USER_ON + (('CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_GLOB', 'CMD_GLOBEXT', 'CMD_NEW', 'CMD_AUDIT_NEW', 'CMD_USER_OFF', 'CMD_ABORT', 'CMD_FINISHED'), True), # aa.py available_buttons() with CMD_AUDIT_NEW and CMD_USER_OFF + (('CMD_SAVE_CHANGES', 'CMD_SAVE_SELECTED', 'CMD_VIEW_CHANGES', 'CMD_VIEW_CHANGES_CLEAN', 'CMD_ABORT'), True), # aa.py save_profiles() + (('CMD_VIEW_PROFILE', 'CMD_USE_PROFILE', 'CMD_CREATE_PROFILE', 'CMD_ABORT'), True), # aa.py get_profile() + (('CMD_ix', 'CMD_pix', 'CMD_cix', 'CMD_nix', 'CMD_EXEC_IX_OFF', 'CMD_ux', 'CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED'), True), # aa.py build_x_functions() with exec_toggle + (('CMD_ix', 'CMD_cx', 'CMD_px', 'CMD_nx', 'CMD_ux', 'CMD_EXEC_IX_ON', 'CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED'), True), # aa.py build_x_functions() without exec_toggle + (('CMD_ADDHAT', 'CMD_USEDEFAULT', 'CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED'), True), # aa.py ask_addhat() + (('CMD_YES', 'CMD_NO', 'CMD_CANCEL'), True), # ui.py UI_YesNo() and UI_YesNoCancel + (('CMD_SAVE_CHANGES', 'CMD_VIEW_CHANGES', 'CMD_ABORT', 'CMD_IGNORE_ENTRY'), True), # aa-mergeprof act() + (('CMD_ALLOW', 'CMD_ABORT'), True), # aa-mergeprof conflict_mode() + (('CMD_ADDSUBPROFILE', 'CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED'), True), # aa-mergeprof ask_the_questions() - new subprofile + (('CMD_ADDHAT', 'CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED'), True), # aa-mergeprof ask_the_questions() - new hat + ) def _run_test(self, params, expected): self.createTmpdir() diff --git a/utils/test/test-unix_parse.py b/utils/test/test-unix_parse.py index 2e73b4113..5faadc874 100644 --- a/utils/test/test-unix_parse.py +++ b/utils/test/test-unix_parse.py @@ -11,14 +11,16 @@ import apparmor.aa as aa import unittest -from common_test import AAParseTest, setup_regex_tests, setup_aa + +from common_test import AAParseTest, setup_aa, setup_regex_tests + class AAParseUnixTest(AAParseTest): def setUp(self): self.parse_function = aa.parse_unix_rule - tests = [ + tests = ( ('unix,', 'unix base keyword'), ('unix r,', 'unix r rule'), ('unix w,', 'unix w rule'), @@ -30,9 +32,9 @@ class AAParseUnixTest(AAParseTest): ('unix (rw),', 'unix (rw) rule'), ('unix (send),', 'unix (send) rule'), ('unix (receive),', 'unix (receive) rule'), - ('unix (connect, receive, send) type=stream peer=(label=unconfined,addr="@/tmp/.X11-unix/X[0-9]*"),', - 'complex unix rule'), - ] + ('unix (connect, receive, send) type=stream peer=(label=unconfined,addr="@/tmp/.X11-unix/X[0-9]*"),', 'complex unix rule'), + ) + setup_aa(aa) if __name__ == '__main__': diff --git a/utils/test/test-variable.py b/utils/test/test-variable.py index 95800acbc..8e1c4b4ba 100644 --- a/utils/test/test-variable.py +++ b/utils/test/test-variable.py @@ -17,17 +17,18 @@ import unittest from collections import namedtuple from common_test import AATest, setup_all_loops -from apparmor.rule.variable import VariableRule, VariableRuleset, separate_vars +from apparmor.common import AppArmorBug, AppArmorException from apparmor.rule import BaseRule -from apparmor.common import AppArmorException, AppArmorBug +from apparmor.rule.variable import VariableRule, VariableRuleset, separate_vars from apparmor.translations import init_translation + _ = init_translation() -exp = namedtuple('exp', ['comment', - 'varname', 'mode', 'values']) +exp = namedtuple('exp', ('comment', 'varname', 'mode', 'values')) # --- tests for single VariableRule --- # + class VariableTest(AATest): def _compare_obj(self, obj, expected): # variables don't support the allow, audit or deny keyword @@ -40,26 +41,27 @@ class VariableTest(AATest): self.assertEqual(expected.values, obj.values) self.assertEqual(expected.comment, obj.comment) + class AaTest_separate_vars(AATest): - tests = [ - ('' , set() ), - (' ' , set() ), - (' foo bar' , {'foo', 'bar' }), - ('foo " ' , AppArmorException ), - (' " foo ' , AppArmorException ), # half-quoted - (' foo bar ' , {'foo', 'bar' }), - (' foo bar # comment' , {'foo', 'bar', '#', 'comment'}), # XXX should comments be stripped? - ('foo' , {'foo' }), - ('"foo" "bar baz"' , {'foo', 'bar baz' }), - ('foo "bar baz" xy' , {'foo', 'bar baz', 'xy' }), - ('foo "bar baz ' , AppArmorException ), # half-quoted - (' " foo" bar' , {' foo', 'bar' }), - (' " foo" bar x' , {' foo', 'bar', 'x' }), - ('""' , {'' }), # empty value - ('"" foo' , {'', 'foo' }), # empty value + 'foo' - ('"" foo "bar"' , {'', 'foo', 'bar' }), # empty value + 'foo' + 'bar' (bar has superfluous quotes) - ('"bar"' , {'bar' }), # 'bar' with superfluous quotes - ] + tests = ( + ('', set()), + (' ', set()), + (' foo bar', {'foo', 'bar'}), + ('foo " ', AppArmorException), + (' " foo ', AppArmorException), # half-quoted + (' foo bar ', {'foo', 'bar'}), + (' foo bar # comment', {'foo', 'bar', '#', 'comment'}), # XXX should comments be stripped? + ('foo', {'foo'}), + ('"foo" "bar baz"', {'foo', 'bar baz'}), + ('foo "bar baz" xy', {'foo', 'bar baz', 'xy'}), + ('foo "bar baz ', AppArmorException), # half-quoted + (' " foo" bar', {' foo', 'bar'}), + (' " foo" bar x', {' foo', 'bar', 'x'}), + ('""', {''}), # empty value + ('"" foo', {'', 'foo'}), # empty value + 'foo' + ('"" foo "bar"', {'', 'foo', 'bar'}), # empty value + 'foo' + 'bar' (bar has superfluous quotes) + ('"bar"', {'bar'}), # 'bar' with superfluous quotes + ) def _run_test(self, params, expected): if expected == AppArmorException: @@ -69,18 +71,19 @@ class AaTest_separate_vars(AATest): result = separate_vars(params) self.assertEqual(result, expected) + class VariableTestParse(VariableTest): - tests = [ - # rawrule comment varname mode values - ('@{foo}=/bar', exp('', '@{foo}', '=', {'/bar'} )), - ('@{foo}+=/bar', exp('', '@{foo}', '+=', {'/bar'} )), - (' @{foo} = /bar ', exp('', '@{foo}', '=', {'/bar'} )), - (' @{foo} += /bar', exp('', '@{foo}', '+=', {'/bar'} )), - (' @{foo} = /bar # comment', exp(' # comment', '@{foo}', '=', {'/bar'} )), - (' @{foo} += /bar # comment', exp(' # comment', '@{foo}', '+=', {'/bar'} )), - ('@{foo}=/bar /baz', exp('', '@{foo}', '=', {'/bar', '/baz'} )), - ('@{foo} = "/bar," # comment', exp(' # comment', '@{foo}', '=', {'/bar,'} )), # value with trailing comma, needs to be quoted - ] + tests = ( + # rawrule comment varname mode values + ('@{foo}=/bar', exp('', '@{foo}', '=', {'/bar'})), + ('@{foo}+=/bar', exp('', '@{foo}', '+=', {'/bar'})), + (' @{foo} = /bar ', exp('', '@{foo}', '=', {'/bar'})), + (' @{foo} += /bar', exp('', '@{foo}', '+=', {'/bar'})), + (' @{foo} = /bar # comment', exp(' # comment', '@{foo}', '=', {'/bar'})), + (' @{foo} += /bar # comment', exp(' # comment', '@{foo}', '+=', {'/bar'})), + ('@{foo}=/bar /baz', exp('', '@{foo}', '=', {'/bar', '/baz'})), + ('@{foo} = "/bar," # comment', exp(' # comment', '@{foo}', '=', {'/bar,'})), # value with trailing comma, needs to be quoted + ) def _run_test(self, rawrule, expected): self.assertTrue(VariableRule.match(rawrule)) @@ -88,59 +91,61 @@ class VariableTestParse(VariableTest): self.assertEqual(rawrule.strip(), obj.raw_rule) self._compare_obj(obj, expected) + class VariableTestParseInvalid(VariableTest): - tests = [ - # rawrule matches regex exception - ('@{foo} =', (False, AppArmorException)), - ('@ {foo} = # comment', (False, AppArmorException)), - ('@ {foo} = ', (False, AppArmorException)), - ('@{foo} = /foo,', (True, AppArmorException)), # trailing comma - ('@{foo} = /foo, ', (True, AppArmorException)), # trailing comma - ('@{foo} = /foo, # comment', (True, AppArmorException)), # trailing comma - ('@{foo} = /foo, /bar', (True, AppArmorException)), # trailing comma in first value - ('@{foo = /foo f', (True, AppArmorException)), # variable name broken, missing } - ] + tests = ( + # rawrule matches regex exception + ('@{foo} =', (False, AppArmorException)), + ('@ {foo} = # comment', (False, AppArmorException)), + ('@ {foo} = ', (False, AppArmorException)), + ('@{foo} = /foo,', (True, AppArmorException)), # trailing comma + ('@{foo} = /foo, ', (True, AppArmorException)), # trailing comma + ('@{foo} = /foo, # comment', (True, AppArmorException)), # trailing comma + ('@{foo} = /foo, /bar', (True, AppArmorException)), # trailing comma in first value + ('@{foo = /foo f', (True, AppArmorException)), # variable name broken, missing } + ) def _run_test(self, rawrule, expected): self.assertEqual(VariableRule.match(rawrule), expected[0]) with self.assertRaises(expected[1]): VariableRule.parse(rawrule) + class VariableFromInit(VariableTest): - tests = [ - # VariableRule object comment varname mode values - (VariableRule('@{foo}', '=', {'/bar'}), exp('', '@{foo}', '=', {'/bar'} )), - (VariableRule('@{foo}', '+=', {'/bar'}), exp('', '@{foo}', '+=', {'/bar'} )), - (VariableRule('@{foo}', '=', {'/bar', '/baz'}), exp('', '@{foo}', '=', {'/bar', '/baz'} )), - (VariableRule('@{foo}', '+=', {'/bar', '/baz'}), exp('', '@{foo}', '+=', {'/bar', '/baz'} )), - (VariableRule('@{foo}', '=', {'/bar'}, comment='# cmt'), exp('# cmt', '@{foo}', '=', {'/bar'} )), - (VariableRule('@{foo}', '+=', {'/bar'}, comment='# cmt'), exp('# cmt', '@{foo}', '+=', {'/bar'} )), - ] + tests = ( + # VariableRule object comment varname mode values + (VariableRule('@{foo}', '=', {'/bar'}), exp('', '@{foo}', '=', {'/bar'})), + (VariableRule('@{foo}', '+=', {'/bar'}), exp('', '@{foo}', '+=', {'/bar'})), + (VariableRule('@{foo}', '=', {'/bar', '/baz'}), exp('', '@{foo}', '=', {'/bar', '/baz'})), + (VariableRule('@{foo}', '+=', {'/bar', '/baz'}), exp('', '@{foo}', '+=', {'/bar', '/baz'})), + (VariableRule('@{foo}', '=', {'/bar'}, comment='# cmt'), exp('# cmt', '@{foo}', '=', {'/bar'})), + (VariableRule('@{foo}', '+=', {'/bar'}, comment='# cmt'), exp('# cmt', '@{foo}', '+=', {'/bar'})), + ) def _run_test(self, obj, expected): self._compare_obj(obj, expected) class InvalidVariableInit(AATest): - tests = [ - # init params expected exception - ([None, '=', ['/bar'] ], AppArmorBug), # varname not a str - (['', '=', ['/bar'] ], AppArmorException), # empty varname - (['foo', '=', ['/bar'] ], AppArmorException), # varname not starting with '@{' - (['foo', '=', ['/bar'] ], AppArmorException), # varname not starting with '@{' - - (['@{foo}', '', ['/bar'] ], AppArmorBug), # mode not '=' or '+=' - (['@{foo}', '-=', ['/bar'] ], AppArmorBug), # mode not '=' or '+=' - (['@{foo}', ' ', ['/bar'] ], AppArmorBug), # mode not '=' or '+=' - (['@{foo}', None, ['/bar'] ], AppArmorBug), # mode not '=' or '+=' - - (['@{foo}', '=', None ], AppArmorBug), # values not a set - (['@{foo}', '=', set() ], AppArmorException), # empty values - ] + tests = ( + # init params expected exception + ((None, '=', ['/bar']), AppArmorBug), # varname not a str + (('', '=', ['/bar']), AppArmorException), # empty varname + (('foo', '=', ['/bar']), AppArmorException), # varname not starting with '@{' + (('foo', '=', ['/bar']), AppArmorException), # varname not starting with '@{' + + (('@{foo}', '', ['/bar']), AppArmorBug), # mode not '=' or '+=' + (('@{foo}', '-=', ['/bar']), AppArmorBug), # mode not '=' or '+=' + (('@{foo}', ' ', ['/bar']), AppArmorBug), # mode not '=' or '+=' + (('@{foo}', None, ['/bar']), AppArmorBug), # mode not '=' or '+=' + + (('@{foo}', '=', None), AppArmorBug), # values not a set + (('@{foo}', '=', set()), AppArmorException), # empty values + ) def _run_test(self, params, expected): with self.assertRaises(expected): - VariableRule(params[0], params[1], params[2]) + VariableRule(*params) def test_missing_params_1(self): with self.assertRaises(TypeError): @@ -180,18 +185,18 @@ class InvalidVariableTest(AATest): class WriteVariableTestAATest(AATest): - tests = [ - # raw rule clean rule - (' @{foo} = /bar ', '@{foo} = /bar'), - (' @{foo} = /bar # comment', '@{foo} = /bar'), - (' @{foo} = /bar ""', '@{foo} = "" /bar'), - (' @{foo} += /bar ', '@{foo} += /bar'), - (' @{foo} += /bar # comment', '@{foo} += /bar'), - (' @{foo} += /bar /baz', '@{foo} += /bar /baz'), - (' @{foo} += /bar /baz', '@{foo} += /bar /baz'), - (' @{foo} += /bar @{baz}', '@{foo} += /bar @{baz}'), - (' @{foo} += /bar @{baz}', '@{foo} += /bar @{baz}'), - ] + tests = ( + # raw rule clean rule + (' @{foo} = /bar ', '@{foo} = /bar'), + (' @{foo} = /bar # comment', '@{foo} = /bar'), + (' @{foo} = /bar ""', '@{foo} = "" /bar'), + (' @{foo} += /bar ', '@{foo} += /bar'), + (' @{foo} += /bar # comment', '@{foo} += /bar'), + (' @{foo} += /bar /baz', '@{foo} += /bar /baz'), + (' @{foo} += /bar /baz', '@{foo} += /bar /baz'), + (' @{foo} += /bar @{baz}', '@{foo} += /bar @{baz}'), + (' @{foo} += /bar @{baz}', '@{foo} += /bar @{baz}'), + ) def _run_test(self, rawrule, expected): self.assertTrue(VariableRule.match(rawrule)) @@ -232,53 +237,56 @@ class VariableCoveredTest(AATest): self.assertEqual(obj.is_covered(check_obj), expected[2], 'Mismatch in is_covered, expected %s' % expected[2]) self.assertEqual(obj.is_covered(check_obj, True, True), expected[3], 'Mismatch in is_covered/exact, expected %s' % expected[3]) + class VariableCoveredTest_01(VariableCoveredTest): rule = '@{foo} = /bar' - tests = [ - # rule equal strict equal covered covered exact - (' @{foo} = /bar' , [ True , True , True , True ]), - (' @{foo} += /bar' , [ False , False , False , False ]), - (' @{foo} = /bar # comment' , [ True , False , True , True ]), - (' @{foo} += /bar # comment' , [ False , False , False , False ]), - (' @{foo} = /baz /bar' , [ False , False , False , False ]), - (' @{foo} += /baz /bar' , [ False , False , False , False ]), - (' @{foo} = /baz /bar # cmt' , [ False , False , False , False ]), - (' @{foo} += /baz /bar # cmt' , [ False , False , False , False ]), - (' @{bar} = /bar' , [ False , False , False , False ]), # different variable name - ] + tests = ( + # rule equal strict equal covered covered exact + (' @{foo} = /bar', (True, True, True, True)), + (' @{foo} += /bar', (False, False, False, False)), + (' @{foo} = /bar # comment', (True, False, True, True)), + (' @{foo} += /bar # comment', (False, False, False, False)), + (' @{foo} = /baz /bar', (False, False, False, False)), + (' @{foo} += /baz /bar', (False, False, False, False)), + (' @{foo} = /baz /bar # cmt', (False, False, False, False)), + (' @{foo} += /baz /bar # cmt', (False, False, False, False)), + (' @{bar} = /bar', (False, False, False, False)), # different variable name + ) + class VariableCoveredTest_02(VariableCoveredTest): rule = '@{foo} = /bar /baz' - tests = [ - # rule equal strict equal covered covered exact - (' @{foo} = /bar /baz' , [ True , True , True , True ]), - (' @{foo} += /bar /baz' , [ False , False , False , False ]), - (' @{foo} = /bar /baz # cmt' , [ True , False , True , True ]), - (' @{foo} += /bar /baz # cmt' , [ False , False , False , False ]), + tests = ( + # rule equal strict equal covered covered exact + (' @{foo} = /bar /baz', (True, True, True, True)), + (' @{foo} += /bar /baz', (False, False, False, False)), + (' @{foo} = /bar /baz # cmt', (True, False, True, True)), + (' @{foo} += /bar /baz # cmt', (False, False, False, False)), # changed order of values - (' @{foo} = /baz /bar' , [ True , False , True , True ]), - (' @{foo} += /baz /bar' , [ False , False , False , False ]), - (' @{foo} = /baz /bar # cmt' , [ True , False , True , True ]), - (' @{foo} += /baz /bar # cmt' , [ False , False , False , False ]), + (' @{foo} = /baz /bar', (True, False, True, True)), + (' @{foo} += /baz /bar', (False, False, False, False)), + (' @{foo} = /baz /bar # cmt', (True, False, True, True)), + (' @{foo} += /baz /bar # cmt', (False, False, False, False)), # only one value - (' @{foo} = /bar' , [ False , False , True , True ]), - (' @{foo} += /bar' , [ False , False , False , False ]), - (' @{foo} = /bar # comment' , [ False , False , True , True ]), - (' @{foo} += /bar # comment' , [ False , False , False , False ]), - (' @{bar} = /bar' , [ False , False , False , False ]), # different variable name - ] - -class VariableCoveredTest_Invalid(AATest): -# def test_borked_obj_is_covered_1(self): -# obj = VariableRule.parse('@{foo} = /bar') + (' @{foo} = /bar', (False, False, True, True)), + (' @{foo} += /bar', (False, False, False, False)), + (' @{foo} = /bar # comment', (False, False, True, True)), + (' @{foo} += /bar # comment', (False, False, False, False)), + (' @{bar} = /bar', (False, False, False, False)), # different variable name + ) -# testobj = VariableRule('@{foo}', '=', '/bar') -# testobj.mode = '' -# with self.assertRaises(AppArmorBug): -# obj.is_covered(testobj) +class VariableCoveredTest_Invalid(AATest): + # def test_borked_obj_is_covered_1(self): + # obj = VariableRule.parse('@{foo} = /bar') + # + # testobj = VariableRule('@{foo}', '=', '/bar') + # testobj.mode = '' + # + # with self.assertRaises(AppArmorBug): + # obj.is_covered(testobj) def test_borked_obj_is_covered_2(self): obj = VariableRule.parse('@{foo} = /bar') @@ -305,17 +313,19 @@ class VariableCoveredTest_Invalid(AATest): with self.assertRaises(AppArmorBug): obj.is_equal(testobj) + class VariableLogprofHeaderTest(AATest): - tests = [ - ('@{foo} = /bar', [_('Variable'), '@{foo} = /bar' ]), - ] + tests = ( + ('@{foo} = /bar', [_('Variable'), '@{foo} = /bar']), + ) def _run_test(self, params, expected): - obj = VariableRule._parse(params) + obj = VariableRule.parse(params) self.assertEqual(obj.logprof_header(), expected) # --- tests for VariableRuleset --- # + class VariableRulesTest(AATest): def test_empty_ruleset(self): ruleset = VariableRuleset() @@ -328,12 +338,12 @@ class VariableRulesTest(AATest): def test_ruleset_1(self): ruleset = VariableRuleset() - rules = [ + rules = ( '@{foo} = /bar', '@{baz}= /asdf', '@{foo} += /whatever', '@{foo} += /morestuff', - ] + ) expected_raw = [ '@{foo} = /bar', @@ -383,7 +393,8 @@ class VariableRulesTest(AATest): ruleset.add(VariableRule.parse('@{foo} = /bar')) with self.assertRaises(AppArmorException): ruleset.add(VariableRule.parse('@{foo} = /asdf')) # attempt to redefine @{foo} - self.assertEqual({'=': {'@{foo}': {'/bar'} }, '+=': {}}, ruleset.get_merged_variables()) + self.assertEqual({'=': {'@{foo}': {'/bar'}}, '+=': {}}, ruleset.get_merged_variables()) + class VariableGlobTestAATest(AATest): def setUp(self): @@ -398,9 +409,11 @@ class VariableGlobTestAATest(AATest): # get_glob_ext is not available for change_profile rules self.ruleset.get_glob_ext('@{foo} = /bar') + class VariableDeleteTestAATest(AATest): pass + setup_all_loops(__name__) if __name__ == '__main__': unittest.main(verbosity=1) diff --git a/utils/vim/apparmor.vim.in b/utils/vim/apparmor.vim.in index 89c6d2eab..cf943359c 100644 --- a/utils/vim/apparmor.vim.in +++ b/utils/vim/apparmor.vim.in @@ -109,7 +109,7 @@ syn match sdError /^.*$/ contains=sdComment "highlight all non-valid lines as er " TODO: the sdGlob pattern is not anchored with ^ and $, so it matches all lines matching ^@{...}.* " This allows incorrect lines also and should be checked better. -" This also (accidently ;-) includes variable definitions (@{FOO}=/bar) +" This also (accidentally ;-) includes variable definitions (@{FOO}=/bar) " TODO: make a separate pattern for variable definitions, then mark sdGlob as contained syn match sdGlob /\v\?|\*|\{.*,.*\}|[[^\]]\+\]|\@\{[a-zA-Z][a-zA-Z0-9_]*\}/ @@ -121,7 +121,7 @@ syn cluster sdEntry contains=sdEntryWriteExec,sdEntryR,sdEntryW,sdEntryIX,sdEntr " TODO: support audit and deny keywords for all rules (not only for files) -" TODO: higlight audit and deny keywords everywhere +" TODO: highlight audit and deny keywords everywhere " Capability line diff --git a/utils/vim/create-apparmor.vim.py b/utils/vim/create-apparmor.vim.py index 8a17bb43e..b5bec693e 100644 --- a/utils/vim/create-apparmor.vim.py +++ b/utils/vim/create-apparmor.vim.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python3 # # Copyright (C) 2012 Canonical Ltd. # @@ -9,30 +9,29 @@ # Written by Steve Beattie <steve@nxnw.org>, based on work by # Christian Boltz <apparmor@cboltz.de> -from __future__ import with_statement import re import subprocess import sys # dangerous capabilities -danger_caps = ["audit_control", +danger_caps = ("audit_control", "audit_write", "mac_override", "mac_admin", "setfcap", "sys_admin", "sys_module", - "sys_rawio"] + "sys_rawio") def cmd(command, input=None, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, stdin=None, timeout=None): - '''Try to execute given command (array) and return its stdout, or - return a textual error if it failed.''' + """Try to execute given command (array) and return its stdout, or + return a textual error if it failed.""" try: sp = subprocess.Popen(command, stdin=stdin, stdout=stdout, stderr=stderr, close_fds=True, universal_newlines=True) except OSError as ex: - return [127, str(ex)] + return 127, str(ex) out, outerr = sp.communicate(input) @@ -42,10 +41,11 @@ def cmd(command, input=None, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, s # Handle redirection of stderr if outerr is None: outerr = '' - return [sp.returncode, out, outerr] + return sp.returncode, out, outerr + # get capabilities list -(rc, output, outerr) = cmd(['../../common/list_capabilities.sh']) +(rc, output, outerr) = cmd(('../../common/list_capabilities.sh',)) if rc != 0: sys.stderr.write("make list_capabilities failed: " + output + outerr) exit(rc) @@ -57,7 +57,7 @@ for cap in capabilities: benign_caps.append(cap) # get network protos list -(rc, output, outerr) = cmd(['../../common/list_af_names.sh']) +(rc, output, outerr) = cmd(('../../common/list_af_names.sh',)) if rc != 0: sys.stderr.write("make list_af_names failed: " + output + outerr) exit(rc) @@ -67,7 +67,7 @@ af_pairs = re.sub('AF_', '', output.strip()).lower().split(",") for af_pair in af_pairs: af_name = af_pair.lstrip().split(" ")[0] # skip max af name definition - if len(af_name) > 0 and af_name != "max": + if af_name and af_name != "max": af_names.append(af_name) # TODO: does a "debug" flag exist? Listed in apparmor.vim.in sdFlagKey, @@ -76,7 +76,7 @@ for af_pair in af_pairs: aa_network_types = r'\s+tcp|\s+udp|\s+icmp' -aa_flags = ['complain', +aa_flags = ('complain', 'audit', 'attach_disconnected', 'no_attach_disconnected', @@ -85,7 +85,7 @@ aa_flags = ['complain', 'chroot_relative', 'namespace_relative', 'mediate_deleted', - 'delegate_deleted'] + 'delegate_deleted') filename = r'(\/|\@\{\S*\})\S*' @@ -150,7 +150,7 @@ filerule = filerule + create_file_rule('sdEntryIX', r'(r|m|k|ix)+', 'ix(mr) - filerule = filerule + create_file_rule('sdEntryM', r'(r|m|k)+', 'mr - mmap with PROT_EXEC') filerule = filerule + create_file_rule('sdEntryM', r'(r|m|k|x)+', 'special case: deny x is allowed (does not need to be ix, px, ux or cx)', 1) -#syn match sdEntryM /@@DENYFILE@@(r|m|k|x)+@@EOL@@/ contains=sdGlob,sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude +# syn match sdEntryM /@@DENYFILE@@(r|m|k|x)+@@EOL@@/ contains=sdGlob,sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude filerule = filerule + create_file_rule('sdError', r'\S*(w\S*a|a\S*w)\S*', 'write + append is an error') -- GitLab From 05f74ace14aa097fe5ab2a84a661b9ff8d8c55dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dylan=20A=C3=AFssi?= <dylan.aissi@collabora.com> Date: Mon, 3 Mar 2025 18:29:00 +0100 Subject: [PATCH 2/4] Refresh Apertis patches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Dylan Aïssi <dylan.aissi@collabora.com> --- debian/patches/apertis/9999-use_DEB_HOST_GNU_TYPE.patch | 2 +- .../Allow-for-access-to-the-mutter-shared-keymap.patch | 6 +++--- .../abstractions-base-stop-working-around-LP-359338.patch | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/debian/patches/apertis/9999-use_DEB_HOST_GNU_TYPE.patch b/debian/patches/apertis/9999-use_DEB_HOST_GNU_TYPE.patch index 1e15e7199..3a21c1c84 100644 --- a/debian/patches/apertis/9999-use_DEB_HOST_GNU_TYPE.patch +++ b/debian/patches/apertis/9999-use_DEB_HOST_GNU_TYPE.patch @@ -9,7 +9,7 @@ Forwarded: not-needed, Debian(-derivative)-specific --- a/tests/regression/apparmor/Makefile +++ b/tests/regression/apparmor/Makefile -@@ -145,7 +145,8 @@ +@@ -146,7 +146,8 @@ xattrs_profile.c #only do the ioperm/iopl tests for x86 derived architectures diff --git a/debian/patches/apertis/Allow-for-access-to-the-mutter-shared-keymap.patch b/debian/patches/apertis/Allow-for-access-to-the-mutter-shared-keymap.patch index cad8d3a95..5bcff8e01 100644 --- a/debian/patches/apertis/Allow-for-access-to-the-mutter-shared-keymap.patch +++ b/debian/patches/apertis/Allow-for-access-to-the-mutter-shared-keymap.patch @@ -16,9 +16,9 @@ Signed-off-by: Simon McVittie <simon.mcvittie@collabora.co.uk> --- a/profiles/apparmor.d/abstractions/wayland +++ b/profiles/apparmor.d/abstractions/wayland -@@ -14,5 +14,8 @@ - owner @{run}/user/*/wayland-[0-9]* rw, - owner @{run}/user/*/{mesa,mutter,sdl,wayland-cursor,weston,xwayland}-shared-* rw, +@@ -17,5 +17,8 @@ + #For compositors based on wlroots + owner /dev/shm/wlroots-* rw, + # Needed for mutter, as it passes the keyboard map this way + /tmp/mutter-* rw, diff --git a/debian/patches/apertis/abstractions-base-stop-working-around-LP-359338.patch b/debian/patches/apertis/abstractions-base-stop-working-around-LP-359338.patch index a6e9b4803..9da3120bc 100644 --- a/debian/patches/apertis/abstractions-base-stop-working-around-LP-359338.patch +++ b/debian/patches/apertis/abstractions-base-stop-working-around-LP-359338.patch @@ -12,7 +12,7 @@ This partially reverts commit 464d426095d7fe0a7061c8ad024851ac6278c310. --- a/profiles/apparmor.d/abstractions/base +++ b/profiles/apparmor.d/abstractions/base -@@ -157,23 +157,5 @@ +@@ -158,23 +158,5 @@ # Allow us to getattr, getopt, setop and shutdown on unix sockets unix (getattr, getopt, setopt, shutdown), -- GitLab From 0ec080c3fe95e37fde670d48e37bc9a49979f39a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dylan=20A=C3=AFssi?= <dylan.aissi@collabora.com> Date: Mon, 3 Mar 2025 18:24:46 +0100 Subject: [PATCH 3/4] Release apparmor version 3.1.7-4+apertis1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Dylan Aïssi <dylan.aissi@collabora.com> --- debian/changelog | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/debian/changelog b/debian/changelog index c2d252dd2..4bf45d146 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,31 @@ +apparmor (3.1.7-4+apertis1) apertis; urgency=medium + + * Sync updates from debian/trixie + * Remaining Apertis specific changes: + - Review and import following relevant patches: + - apertis/ptrace-test-include-asm-ptrace.h-on-ARM.patch + - apertis/9901-vivante-and-egl-for-X-abstraction.patch + - apertis/9999-use_DEB_HOST_GNU_TYPE.patch + - apertis/libreoffice-apparmor-profile-fonts.patch + - apertis/abstractions-base-stop-working-around-LP-359338.patch + - apertis/Add-pvr-devices-to-X-abstraction.patch + - apertis/Extend-abstractions-X-to-account-for-Wayland-clients.patch + - apertis/Allow-for-access-to-the-mutter-shared-keymap.patch + - apertis/freedesktop.org-abstractions.patch + - Disable lto optimization + - Disable installation of syscall tests + - debian/control: Forward port remaining apparmor changes from Apertis. + - New binary packages: apparmor-tests, apparmor-utils-tests. + The test scripts are not packaged by Debian which are used by our + automated tests. + - debian/control: Depend on libdbus-1-dev to build the dbus test programs. + - debian/features: Pin features to the ones provided by Apertis kernels. + - debian/rules: Add `-Xsyscall_ioperm -Xsyscall_iop` to dh_install in + override_dh_install-arch to exclude those test programs on + architectures other than i386 and amd64. + + -- Dylan Aïssi <dylan.aissi@collabora.com> Mon, 03 Mar 2025 18:17:43 +0100 + apparmor (3.1.7-4) unstable; urgency=medium * Remove obsolete conffile -- GitLab From a6c678f087caa331f1fb8df9a1b365be7e31d723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dylan=20A=C3=AFssi?= <dylan.aissi@collabora.com> Date: Mon, 3 Mar 2025 17:30:37 +0000 Subject: [PATCH 4/4] Refresh the automatically detected licensing information MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Dylan Aïssi <dylan.aissi@collabora.com> --- debian/apertis/copyright | 369 ++++++++++++++++++++++++++------------- 1 file changed, 252 insertions(+), 117 deletions(-) diff --git a/debian/apertis/copyright b/debian/apertis/copyright index fcaec8b22..28d67fc7b 100644 --- a/debian/apertis/copyright +++ b/debian/apertis/copyright @@ -2,14 +2,9 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Files: * Copyright: no-info-found -License: GPL and/or LGPL +License: GPL or LGPL -Files: binutils/* presentations/* utils/* -Copyright: 1998-2010 Novell/SuSE/Immunix - 2008-2014 Canonical Ltd. -License: GPL-2+ - -Files: libraries/libapparmor/autom4te.cache/* libraries/libapparmor/doc/* libraries/libapparmor/include/* libraries/libapparmor/swig/* libraries/libapparmor/testsuite/* +Files: libraries/libapparmor/doc/* Copyright: 1999-2008 Novell 2009-2013 Canonical Ltd. License: LGPL-2.1+ @@ -33,10 +28,6 @@ Files: binutils/cJSON.c Copyright: 2009-2017, Dave Gamble and cJSON contributors License: Expat -Files: changehat/* -Copyright: no-info-found -License: GPL - Files: changehat/mod_apparmor/* Copyright: 1991, 1999, Free Software Foundation, Inc. License: LGPL-2.1 @@ -55,6 +46,10 @@ Copyright: 2014, Canonical, Ltd. ( 2004-2006, NOVELL ( License: LGPL-2.1 +Files: changehat/pam_apparmor/* +Copyright: no-info-found +License: GPL + Files: changehat/pam_apparmor/Makefile Copyright: 2008-2010, 2016, Canonical, Ltd. 1999-2008, NOVELL ( @@ -69,7 +64,7 @@ Files: changehat/pam_apparmor/pam_apparmor.h Copyright: no-info-found License: BSD-3-clause -Files: changehat/tomcat_apparmor/* +Files: changehat/tomcat_apparmor/tomcat_5_0/* Copyright: 2002-2007, 2011, Novell/SUSE License: GPL-2 @@ -77,11 +72,15 @@ Files: changehat/tomcat_apparmor/tomcat_5_0/Makefile Copyright: 1999-2007, NOVELL ( License: GPL-2 +Files: changehat/tomcat_apparmor/tomcat_5_5/* +Copyright: 2002-2007, 2011, Novell/SUSE +License: GPL-2 + Files: changehat/tomcat_apparmor/tomcat_5_5/Makefile Copyright: 1999-2007, NOVELL ( License: GPL-2 -Files: common/* +Files: common/Make-po.rules Copyright: 2009-2016, Canonical Ltd. 1999-2009, NOVELL ( License: GPL-2 @@ -92,12 +91,12 @@ Copyright: 2010-2015, 2017, 2018, Canonical, Ltd. License: GPL-2 Files: debian/* -Copyright: 2007-2011 Canonical Ltd. - 2014-2022 intrigeri +Copyright: 2014-2022, intrigeri <intrigeri@boum.org> + 2007-2011, Canonical Ltd. License: GPL-2 Files: debian/aa-update-browser -Copyright: 2010-2016, 2018, 2019, Canonical, Ltd. +Copyright: 2010-2016, 2018, 2019, 2021, Canonical, Ltd. License: GPL-2 Files: debian/apparmor.init @@ -118,7 +117,7 @@ Files: debian/lava_run_tests Copyright: 2012, Collabora ltd. License: GPL-2 -Files: documentation/* +Files: documentation/Makefile Copyright: 2009-2020, Canonical Ltd. License: GPL-2 @@ -126,10 +125,6 @@ Files: documentation/logo/* Copyright: 2018, Noah Davis <noahadvs@gmail.com> License: LGPL-2.1 -Files: kernel-patches/* -Copyright: 2009-2020, Canonical Ltd. -License: GPL-2 - Files: kernel-patches/2.6.36.2/* Copyright: 2009-2017, Canonical Ltd. 1998-2009, Novell/SUSE @@ -165,16 +160,28 @@ Copyright: 2009-2017, Canonical Ltd. 1998-2009, Novell/SUSE License: GPL-2 +Files: kernel-patches/3.10/0004-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.patch +Copyright: 2009-2020, Canonical Ltd. +License: GPL-2 + Files: kernel-patches/3.11/0002-UBUNTU-SAUCE-AppArmor-basic-networking-rules.patch Copyright: 2009-2017, Canonical Ltd. 1998-2009, Novell/SUSE License: GPL-2 +Files: kernel-patches/3.11/0004-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.patch +Copyright: 2009-2020, Canonical Ltd. +License: GPL-2 + Files: kernel-patches/3.12/0001-UBUNTU-SAUCE-AppArmor-basic-networking-rules.patch Copyright: 2009-2017, Canonical Ltd. 1998-2009, Novell/SUSE License: GPL-2 +Files: kernel-patches/3.12/0003-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.patch +Copyright: 2009-2020, Canonical Ltd. +License: GPL-2 + Files: kernel-patches/3.2/* Copyright: 2009-2017, Canonical Ltd. 1998-2009, Novell/SUSE @@ -190,61 +197,114 @@ Copyright: 2009-2017, Canonical Ltd. 1998-2009, Novell/SUSE License: GPL-2 +Files: kernel-patches/3.4/0003-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.patch +Copyright: 2009-2020, Canonical Ltd. +License: GPL-2 + Files: kernel-patches/3.5/0002-UBUNTU-SAUCE-AppArmor-basic-networking-rules.patch Copyright: 2009-2017, Canonical Ltd. 1998-2009, Novell/SUSE License: GPL-2 +Files: kernel-patches/3.5/0005-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.patch +Copyright: 2009-2020, Canonical Ltd. +License: GPL-2 + Files: kernel-patches/3.6/0002-UBUNTU-SAUCE-AppArmor-basic-networking-rules.patch Copyright: 2009-2017, Canonical Ltd. 1998-2009, Novell/SUSE License: GPL-2 +Files: kernel-patches/3.6/0005-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.patch +Copyright: 2009-2020, Canonical Ltd. +License: GPL-2 + Files: kernel-patches/3.7/0002-UBUNTU-SAUCE-AppArmor-basic-networking-rules.patch Copyright: 2009-2017, Canonical Ltd. 1998-2009, Novell/SUSE License: GPL-2 +Files: kernel-patches/3.7/0004-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.patch +Copyright: 2009-2020, Canonical Ltd. +License: GPL-2 + Files: kernel-patches/3.8/0002-UBUNTU-SAUCE-AppArmor-basic-networking-rules.patch Copyright: 2009-2017, Canonical Ltd. 1998-2009, Novell/SUSE License: GPL-2 +Files: kernel-patches/3.8/0004-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.patch +Copyright: 2009-2020, Canonical Ltd. +License: GPL-2 + Files: kernel-patches/3.9/0002-UBUNTU-SAUCE-AppArmor-basic-networking-rules.patch Copyright: 2009-2017, Canonical Ltd. 1998-2009, Novell/SUSE License: GPL-2 +Files: kernel-patches/3.9/0004-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.patch +Copyright: 2009-2020, Canonical Ltd. +License: GPL-2 + Files: kernel-patches/4.4/0024-UBUNTU-SAUCE-AppArmor-basic-networking-rules.patch Copyright: 2009-2017, Canonical Ltd. 1998-2009, Novell/SUSE License: GPL-2 +Files: kernel-patches/4.4/0026-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.patch +Copyright: 2009-2020, Canonical Ltd. +License: GPL-2 + Files: kernel-patches/4.5/0024-UBUNTU-SAUCE-AppArmor-basic-networking-rules.patch Copyright: 2009-2017, Canonical Ltd. 1998-2009, Novell/SUSE License: GPL-2 +Files: kernel-patches/4.5/0026-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.patch +Copyright: 2009-2020, Canonical Ltd. +License: GPL-2 + Files: kernel-patches/4.6/0024-UBUNTU-SAUCE-AppArmor-basic-networking-rules.patch Copyright: 2009-2017, Canonical Ltd. 1998-2009, Novell/SUSE License: GPL-2 +Files: kernel-patches/4.6/0026-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.patch +Copyright: 2009-2020, Canonical Ltd. +License: GPL-2 + Files: kernel-patches/4.7/0023-UBUNTU-SAUCE-AppArmor-basic-networking-rules.patch Copyright: 2009-2017, Canonical Ltd. 1998-2009, Novell/SUSE License: GPL-2 +Files: kernel-patches/4.7/0025-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.patch +Copyright: 2009-2020, Canonical Ltd. +License: GPL-2 + Files: kernel-patches/v4.11/0001-UBUNTU-SAUCE-AppArmor-basic-networking-rules.patch Copyright: 2009-2017, Canonical Ltd. 1998-2009, Novell/SUSE License: GPL-2 +Files: kernel-patches/v4.11/0003-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.patch +Copyright: 2009-2020, Canonical Ltd. +License: GPL-2 + Files: kernel-patches/v4.12/0001-UBUNTU-SAUCE-AppArmor-basic-networking-rules.patch Copyright: 2009-2017, Canonical Ltd. 1998-2009, Novell/SUSE License: GPL-2 +Files: kernel-patches/v4.12/0003-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.patch +Copyright: 2009-2020, Canonical Ltd. +License: GPL-2 + +Files: kernel-patches/v4.13/0007-apparmor-add-mount-mediation.patch + kernel-patches/v4.13/0017-UBUNTU-SAUCE-apparmor-af_unix-mediation.patch +Copyright: 2009-2020, Canonical Ltd. +License: GPL-2 + Files: kernel-patches/v4.13/0012-apparmor-add-base-infastructure-for-socket-mediation.patch Copyright: 2009-2017, Canonical Ltd. 1998-2009, Novell/SUSE @@ -255,23 +315,39 @@ Copyright: 2009-2017, Canonical Ltd. 1998-2009, Novell/SUSE License: GPL-2 -Files: kernel-patches/v4.15/0001-apparmor-add-base-infastructure-for-socket-mediation.patch +Files: kernel-patches/v4.14/0002-apparmor-af_unix-mediation.patch +Copyright: 2009-2020, Canonical Ltd. +License: GPL-2 + +Files: kernel-patches/v4.15/* Copyright: 2009-2017, Canonical Ltd. 1998-2009, Novell/SUSE License: GPL-2 +Files: kernel-patches/v4.15/0002-apparmor-af_unix-mediation.patch +Copyright: 2009-2020, Canonical Ltd. +License: GPL-2 + +Files: kernel-patches/v4.17/0002-apparmor-af_unix-mediation.patch +Copyright: 2009-2020, Canonical Ltd. +License: GPL-2 + Files: kernel-patches/v4.8/0001-UBUNTU-SAUCE-AppArmor-basic-networking-rules.patch Copyright: 2009-2017, Canonical Ltd. 1998-2009, Novell/SUSE License: GPL-2 -Files: libraries/* +Files: kernel-patches/v4.8/0003-UBUNTU-SAUCE-apparmor-Add-the-ability-to-mediate-mou.patch +Copyright: 2009-2020, Canonical Ltd. +License: GPL-2 + +Files: libraries/libapparmor/* Copyright: 1991, 1999, Free Software Foundation, Inc. License: LGPL-2.1 Files: libraries/libapparmor/aclocal.m4 Copyright: 1996-2021, Free Software Foundation, Inc. -License: (FSFULLR and/or GPL-2) with Libtool exception +License: (FSFULLR or GPL-2) with Libtool exception Files: libraries/libapparmor/compile libraries/libapparmor/depcomp @@ -305,7 +381,7 @@ License: X11 Files: libraries/libapparmor/ltmain.sh Copyright: 1996-2015, Free Software Foundation, Inc. -License: (GPL-2+ and/or GPL-3+) with Libtool exception +License: (GPL-2+ or GPL-3+) with Libtool exception Files: libraries/libapparmor/src/* Copyright: 2010, 2012-2017, 2020-2022, Canonical, Ltd. ( @@ -346,7 +422,7 @@ Copyright: 2008-2010, 2016, Canonical, Ltd. 1999-2008, NOVELL ( License: GPL-2 -Files: libraries/libapparmor/swig/python/* +Files: libraries/libapparmor/swig/python/test/test_python.py.in Copyright: 2009-2020, Canonical Ltd. License: GPL-2 @@ -355,12 +431,12 @@ Copyright: 1989, 1991, Free Software Foundation, Inc. License: GPL-2 Files: parser/Makefile -Copyright: 2018, Christian Boltz +Copyright: 2018, Christian Boltz <apparmor@cboltz.de> 1999-2002, 2004-2007, NOVELL ( License: GPL-2 Files: parser/aa-teardown.pod -Copyright: 2013-2020, Christian Boltz +Copyright: 2013-2022, Christian Boltz <apparmor@cboltz.de> License: GPL-2 Files: parser/af_rule.cc @@ -427,8 +503,6 @@ Files: parser/frob_slack_rc parser/parser_regex.c parser/parser_symtab.c parser/parser_variable.c - parser/rc.apparmor.debian - parser/rc.apparmor.redhat parser/rc.apparmor.slackware Copyright: 1999-2007, NOVELL ( License: GPL-2 @@ -457,40 +531,50 @@ Copyright: 2009-2020, Canonical Ltd. License: GPL-2 Files: parser/profile-load -Copyright: 2010-2016, 2018, 2019, Canonical, Ltd. +Copyright: 2010-2016, 2018, 2019, 2021, Canonical, Ltd. License: GPL-2 Files: parser/techdoc.tex Copyright: no-info-found -License: GPL and/or LGPL +License: GPL or LGPL -Files: parser/tst/* +Files: parser/tst/caching.py + parser/tst/errors.py + parser/tst/mk_features_file.py + parser/tst/testlib.py + parser/tst/valgrind_simple.py Copyright: 2009-2020, Canonical Ltd. License: GPL-2 Files: parser/tst/dirtest.sh parser/tst/equality.sh - parser/tst/gen-dbus.pl Copyright: 2010, 2012-2017, 2020-2022, Canonical, Ltd. ( License: GPL-2 -Files: parser/tst/errors/* +Files: parser/tst/errors/includes/* Copyright: 2002-2007, 2011, Novell/SUSE License: GPL-2 -Files: parser/tst/simple_tests/* +Files: parser/tst/gen-dbus.py +Copyright: 2021, Christian Boltz <apparmor@cboltz.de> + 2013, Canonical, Ltd. ( +License: GPL-2 + +Files: parser/tst/gen-xtrans.py +Copyright: 2011-2020, Christian Boltz <apparmor@cboltz.de> + 2009-2013, Canonical Ltd. +License: GPL-2 + +Files: parser/tst/simple_tests/includes/base + parser/tst/simple_tests/includes/fonts Copyright: 2002-2007, 2011, Novell/SUSE License: GPL-2 -Files: profiles/* +Files: profiles/Makefile Copyright: 2009-2017, Canonical Ltd. 1998-2009, Novell/SUSE License: GPL-2 -Files: profiles/apparmor.d/abstractions/* -Copyright: 2009-2020, Canonical Ltd. -License: GPL-2 - Files: profiles/apparmor.d/abstractions/X profiles/apparmor.d/abstractions/audio profiles/apparmor.d/abstractions/base @@ -515,8 +599,12 @@ Copyright: 2009-2017, Canonical Ltd. 1998-2009, Novell/SUSE License: GPL-2 +Files: profiles/apparmor.d/abstractions/apparmor_api/* +Copyright: 2009-2020, Canonical Ltd. +License: GPL-2 + Files: profiles/apparmor.d/abstractions/authentication -Copyright: 2019-2021, Christian Boltz +Copyright: 2019-2021, Christian Boltz <apparmor@cboltz.de> 2009-2012, Canonical Ltd 2002-2009, Novell/SUSE License: GPL-2 @@ -534,13 +622,44 @@ Copyright: 2002-2007, 2011, Novell/SUSE License: GPL-2 Files: profiles/apparmor.d/abstractions/crypto -Copyright: 2020, 2021, Christian Boltz - 2009-2011, 2014, Canonical Ltd. +Copyright: 2009-2011, 2014, Canonical Ltd. + 2006, 2020, 2021, Christian Boltz <apparmor@cboltz.de> 2002-2009, Novell/SUSE License: GPL-2 +Files: profiles/apparmor.d/abstractions/cups-client + profiles/apparmor.d/abstractions/dbus + profiles/apparmor.d/abstractions/dbus-accessibility + profiles/apparmor.d/abstractions/dbus-accessibility-strict + profiles/apparmor.d/abstractions/dbus-session + profiles/apparmor.d/abstractions/dbus-session-strict + profiles/apparmor.d/abstractions/dbus-strict + profiles/apparmor.d/abstractions/enchant + profiles/apparmor.d/abstractions/fcitx + profiles/apparmor.d/abstractions/fcitx-strict + profiles/apparmor.d/abstractions/freedesktop.org + profiles/apparmor.d/abstractions/hosts_access + profiles/apparmor.d/abstractions/ibus + profiles/apparmor.d/abstractions/likewise + profiles/apparmor.d/abstractions/mir + profiles/apparmor.d/abstractions/mozc + profiles/apparmor.d/abstractions/p11-kit + profiles/apparmor.d/abstractions/samba + profiles/apparmor.d/abstractions/smbpass + profiles/apparmor.d/abstractions/ssl_keys + profiles/apparmor.d/abstractions/ubuntu-unity7-base + profiles/apparmor.d/abstractions/xdg-desktop +Copyright: 2009-2020, Canonical Ltd. +License: GPL-2 + Files: profiles/apparmor.d/abstractions/dovecot-common -Copyright: 2010-2016, 2018, 2019, Canonical, Ltd. +Copyright: 2010-2016, 2018, 2019, 2021, Canonical, Ltd. +License: GPL-2 + +Files: profiles/apparmor.d/abstractions/groff +Copyright: 2023, SUSE LLC + 2009, Canonical Ltd. + 2002-2009, Novell/SUSE License: GPL-2 Files: profiles/apparmor.d/abstractions/gtk @@ -552,18 +671,22 @@ Copyright: 2015, 2016, Simon Deziel License: GPL-2 Files: profiles/apparmor.d/abstractions/mysql -Copyright: 2013-2019, 2021, Christian Boltz +Copyright: 2013-2019, 2021, Christian Boltz <apparmor@cboltz.de> 2002-2006, Novell/SUSE License: GPL-2 Files: profiles/apparmor.d/abstractions/postfix-common -Copyright: 2019-2021, Christian Boltz +Copyright: 2017, 2019-2021, Christian Boltz <apparmor@cboltz.de> 2015-2018, Canonical, Ltd. 2002-2006, Novell/SUSE License: GPL-2 Files: profiles/apparmor.d/abstractions/samba-rpcd -Copyright: 2022, SUSE LLC +Copyright: 2020, 2022, SUSE LLC +License: GPL-2 + +Files: profiles/apparmor.d/abstractions/ubuntu-browsers.d/chromium-browser +Copyright: 2009-2020, Canonical Ltd. License: GPL-2 Files: profiles/apparmor.d/abstractions/wayland @@ -575,17 +698,31 @@ Copyright: 2014, Canonical Ltd 2002-2006, Novell/SUSE License: GPL-2 +Files: profiles/apparmor.d/bin.ping + profiles/apparmor.d/sbin.klogd + profiles/apparmor.d/sbin.syslogd + profiles/apparmor.d/usr.sbin.identd + profiles/apparmor.d/usr.sbin.mdnsd + profiles/apparmor.d/usr.sbin.nscd + profiles/apparmor.d/usr.sbin.ntpd + profiles/apparmor.d/usr.sbin.traceroute +Copyright: 2009-2017, Canonical Ltd. + 1998-2009, Novell/SUSE +License: GPL-2 + Files: profiles/apparmor.d/samba-dcerpcd profiles/apparmor.d/samba-rpcd profiles/apparmor.d/samba-rpcd-classic profiles/apparmor.d/samba-rpcd-spoolss -Copyright: 2022, SUSE LLC + profiles/apparmor.d/usr.lib.dovecot.director + profiles/apparmor.d/usr.lib.dovecot.doveadm-server +Copyright: 2020, 2022, SUSE LLC License: GPL-2 Files: profiles/apparmor.d/sbin.syslog-ng -Copyright: 2010, Canonical Ltd. - 2006-2009, Novell/SUSE - 2006, Christian Boltz +Copyright: 2009-2011, 2014, Canonical Ltd. + 2006, 2020, 2021, Christian Boltz <apparmor@cboltz.de> + 2002-2009, Novell/SUSE License: GPL-2 Files: profiles/apparmor.d/tunables/* @@ -594,7 +731,7 @@ License: GPL-2 Files: profiles/apparmor.d/tunables/dovecot profiles/apparmor.d/tunables/etc -Copyright: 2013-2020, Christian Boltz +Copyright: 2013-2022, Christian Boltz <apparmor@cboltz.de> License: GPL-2 Files: profiles/apparmor.d/tunables/global @@ -616,17 +753,18 @@ Files: profiles/apparmor.d/usr.lib.dovecot.anvil profiles/apparmor.d/usr.lib.dovecot.log profiles/apparmor.d/usr.lib.dovecot.ssl-params profiles/apparmor.d/usr.lib.dovecot.stats -Copyright: 2013-2020, Christian Boltz + profiles/apparmor.d/zgrep +Copyright: 2013-2022, Christian Boltz <apparmor@cboltz.de> License: GPL-2 Files: profiles/apparmor.d/usr.lib.dovecot.auth profiles/apparmor.d/usr.lib.dovecot.managesieve Copyright: 2014, Christian Wittmer - 2013-2020, Christian Boltz + 2013-2020, Christian Boltz <apparmor@cboltz.de> License: GPL-2 Files: profiles/apparmor.d/usr.lib.dovecot.deliver -Copyright: 2011-2020, Christian Boltz +Copyright: 2011-2020, Christian Boltz <apparmor@cboltz.de> 2009-2014, Canonical Ltd. 2009, Dulmandakh Sukhbaatar <dulmandakh@gmail.com> License: GPL-2 @@ -637,20 +775,26 @@ Files: profiles/apparmor.d/usr.lib.dovecot.dovecot-auth profiles/apparmor.d/usr.lib.dovecot.pop3 profiles/apparmor.d/usr.lib.dovecot.pop3-login profiles/apparmor.d/usr.sbin.dovecot -Copyright: 2011-2020, Christian Boltz +Copyright: 2011-2020, Christian Boltz <apparmor@cboltz.de> 2009-2013, Canonical Ltd. License: GPL-2 Files: profiles/apparmor.d/usr.lib.dovecot.managesieve-login Copyright: 2014, Christian Wittmer - 2013-2020, Christian Boltz + 2013-2020, Christian Boltz <apparmor@cboltz.de> 2009-2011, Canonical Ltd. 2009, Dulmandakh Sukhbaatar <dulmandakh@gmail.com> License: GPL-2 +Files: profiles/apparmor.d/usr.lib.dovecot.replicator +Copyright: 2020, SUSE LLC + 2011-2013, Christian Boltz <apparmor@cboltz.de> + 2009, 2010, Canonical Ltd. +License: GPL-2 + Files: profiles/apparmor.d/usr.lib.dovecot.script-login Copyright: 2020, Michael Hirmke - 2020, Christian Boltz + 2020, Christian Boltz <apparmor@cboltz.de> License: GPL-2 Files: profiles/apparmor.d/usr.sbin.dnsmasq @@ -669,10 +813,15 @@ Files: profiles/apparmor/profiles/extras/bin.netstat profiles/apparmor/profiles/extras/usr.sbin.postalias profiles/apparmor/profiles/extras/usr.sbin.postmap profiles/apparmor/profiles/extras/usr.sbin.vsftpd -Copyright: 2013-2019, 2021, Christian Boltz +Copyright: 2013-2019, 2021, Christian Boltz <apparmor@cboltz.de> 2002-2006, Novell/SUSE License: GPL-2 +Files: profiles/apparmor/profiles/extras/chromium_browser + profiles/apparmor/profiles/extras/firefox +Copyright: 2009-2020, Canonical Ltd. +License: GPL-2 + Files: profiles/apparmor/profiles/extras/etc.cron.daily.logrotate Copyright: 2016, Seth Arnold 2016, Daniel Curtis @@ -697,35 +846,25 @@ License: GPL-2 Files: profiles/apparmor/profiles/extras/postfix-bounce profiles/apparmor/profiles/extras/postfix-cleanup + profiles/apparmor/profiles/extras/postfix-error profiles/apparmor/profiles/extras/postfix-flush + profiles/apparmor/profiles/extras/postfix-lmtp profiles/apparmor/profiles/extras/postfix-local profiles/apparmor/profiles/extras/postfix-master + profiles/apparmor/profiles/extras/postfix-pipe profiles/apparmor/profiles/extras/postfix-proxymap profiles/apparmor/profiles/extras/postfix-showq profiles/apparmor/profiles/extras/postfix-smtp profiles/apparmor/profiles/extras/postfix-smtpd profiles/apparmor/profiles/extras/postfix-trivial-rewrite -Copyright: 2019-2021, Christian Boltz +Copyright: 2017, 2019-2021, Christian Boltz <apparmor@cboltz.de> 2015-2018, Canonical, Ltd. 2002-2006, Novell/SUSE License: GPL-2 Files: profiles/apparmor/profiles/extras/postfix-dnsblog profiles/apparmor/profiles/extras/postfix-postscreen -Copyright: 2010-2016, 2018, 2019, Canonical, Ltd. -License: GPL-2 - -Files: profiles/apparmor/profiles/extras/postfix-error - profiles/apparmor/profiles/extras/postfix-lmtp - profiles/apparmor/profiles/extras/postfix-pipe -Copyright: 2018, Canonical, Ltd. - 2017, Christian Boltz - 2002-2006, Novell/SUSE -License: GPL-2 - -Files: profiles/apparmor/profiles/extras/usr.bin.chromium-browser - profiles/apparmor/profiles/extras/usr.lib.firefox.firefox -Copyright: 2009-2020, Canonical Ltd. +Copyright: 2010-2016, 2018, 2019, 2021, Canonical, Ltd. License: GPL-2 Files: profiles/apparmor/profiles/extras/usr.bin.mlmmj-bounce @@ -743,6 +882,13 @@ Files: profiles/apparmor/profiles/extras/usr.bin.passwd Copyright: 2006, Volker Kuhlmann License: GPL-2 +Files: profiles/apparmor/profiles/extras/usr.bin.pyzorsocket + profiles/apparmor/profiles/extras/usr.bin.razorsocket + profiles/apparmor/profiles/extras/usr.sbin.clamd + profiles/apparmor/profiles/extras/usr.sbin.haproxy +Copyright: 2020, 2022, SUSE LLC +License: GPL-2 + Files: profiles/apparmor/profiles/extras/usr.sbin.sshd Copyright: 2015, 2016, Simon Deziel 2012, Canonical Ltd. @@ -767,6 +913,8 @@ Files: tests/regression/apparmor/aa_exec.sh tests/regression/apparmor/aa_policy_cache.sh tests/regression/apparmor/at_secure.c tests/regression/apparmor/at_secure.sh + tests/regression/apparmor/attach_disconnected.c + tests/regression/apparmor/attach_disconnected.sh tests/regression/apparmor/dbus.inc tests/regression/apparmor/dbus_eavesdrop.sh tests/regression/apparmor/dbus_message.sh @@ -785,6 +933,8 @@ Files: tests/regression/apparmor/aa_exec.sh tests/regression/apparmor/stackonexec.sh tests/regression/apparmor/stackprofile.sh tests/regression/apparmor/transition.c + tests/regression/apparmor/unix_fd_common.c + tests/regression/apparmor/unix_fd_common.h tests/regression/apparmor/unix_socket.c tests/regression/apparmor/unix_socket.inc tests/regression/apparmor/unix_socket_abstract.sh @@ -796,7 +946,7 @@ Files: tests/regression/apparmor/aa_exec.sh tests/regression/apparmor/unix_socket_unnamed.sh tests/regression/apparmor/xattrs_profile.c tests/regression/apparmor/xattrs_profile.sh -Copyright: 2010-2016, 2018, 2019, Canonical, Ltd. +Copyright: 2010-2016, 2018, 2019, 2021, Canonical, Ltd. License: GPL-2 Files: tests/regression/apparmor/dbus_common.c @@ -862,7 +1012,7 @@ Copyright: 2013, Kshitij Gupta <kgupta8592@gmail.com> License: GPL-2 Files: utils/aa-decode -Copyright: 2011-2020, Christian Boltz +Copyright: 2011-2020, Christian Boltz <apparmor@cboltz.de> 2009-2013, Canonical Ltd. License: GPL-2 @@ -874,7 +1024,7 @@ Copyright: 2009-2020, Canonical Ltd. License: GPL-2 Files: utils/aa-mergeprof -Copyright: 2014-2020, Christian Boltz <apparmor@cboltz.de> +Copyright: 2014-2022, Christian Boltz <apparmor@cboltz.de> 2013, Kshitij Gupta <kgupta8592@gmail.com> License: GPL-2 @@ -892,7 +1042,7 @@ Copyright: 2016, Canonical, Ltd. License: GPL-2 Files: utils/apparmor/* -Copyright: 2014-2020, Christian Boltz <apparmor@cboltz.de> +Copyright: 2014-2022, Christian Boltz <apparmor@cboltz.de> 2013, Kshitij Gupta <kgupta8592@gmail.com> License: GPL-2 @@ -907,7 +1057,7 @@ License: GPL-2 Files: utils/apparmor/aare.py utils/apparmor/fail.py utils/apparmor/profile_list.py -Copyright: 2014-2021, Christian Boltz <apparmor@cboltz.de> +Copyright: 2013-2022, Christian Boltz <apparmor@cboltz.de> License: GPL-2 Files: utils/apparmor/common.py @@ -915,28 +1065,19 @@ Copyright: 2013, Kshitij Gupta <kgupta8592@gmail.com> 2012, Canonical Ltd. License: GPL-2 -Files: utils/apparmor/config.py - utils/apparmor/severity.py -Copyright: 2013, Kshitij Gupta <kgupta8592@gmail.com> -License: GPL-2 - Files: utils/apparmor/notify.py -Copyright: 2021, Christian Boltz +Copyright: 2021, Christian Boltz <apparmor@cboltz.de> 2018, 2019, Otto Kekäläinen <otto@kekalainen.net> License: GPL-2 -Files: utils/apparmor/rule/* -Copyright: 2014-2021, Christian Boltz <apparmor@cboltz.de> -License: GPL-2 - -Files: utils/apparmor/rule/__init__.py - utils/apparmor/rule/capability.py - utils/apparmor/rule/change_profile.py - utils/apparmor/rule/network.py - utils/apparmor/rule/rlimit.py - utils/apparmor/rule/variable.py -Copyright: 2014-2020, Christian Boltz <apparmor@cboltz.de> - 2013, Kshitij Gupta <kgupta8592@gmail.com> +Files: utils/apparmor/rule/abi.py + utils/apparmor/rule/alias.py + utils/apparmor/rule/dbus.py + utils/apparmor/rule/file.py + utils/apparmor/rule/include.py + utils/apparmor/rule/ptrace.py + utils/apparmor/rule/signal.py +Copyright: 2013-2022, Christian Boltz <apparmor@cboltz.de> License: GPL-2 Files: utils/apparmor/ui.py @@ -951,13 +1092,13 @@ Copyright: 2009-2017, Canonical Ltd. License: GPL-2 Files: utils/severity.db -Copyright: 2020, 2021, Christian Boltz - 2009-2011, 2014, Canonical Ltd. +Copyright: 2009-2011, 2014, Canonical Ltd. + 2006, 2020, 2021, Christian Boltz <apparmor@cboltz.de> 2002-2009, Novell/SUSE License: GPL-2 Files: utils/test/* -Copyright: 2014-2021, Christian Boltz <apparmor@cboltz.de> +Copyright: 2013-2022, Christian Boltz <apparmor@cboltz.de> License: GPL-2 Files: utils/test/Makefile @@ -968,27 +1109,16 @@ License: GPL-2 Files: utils/test/common_test.py utils/test/test-aare.py utils/test/test-logparser.py -Copyright: 2014-2020, Christian Boltz <apparmor@cboltz.de> +Copyright: 2014-2022, Christian Boltz <apparmor@cboltz.de> 2013, Kshitij Gupta <kgupta8592@gmail.com> License: GPL-2 Files: utils/test/logprof.conf - utils/test/severity.db utils/test/severity_broken.db Copyright: 2009-2017, Canonical Ltd. 1998-2009, Novell/SUSE License: GPL-2 -Files: utils/test/minitools_test.py - utils/test/test-config.py -Copyright: 2013, Kshitij Gupta <kgupta8592@gmail.com> -License: GPL-2 - -Files: utils/test/runtests-py3.sh - utils/test/test-aa.py -Copyright: 2013-2020, Christian Boltz -License: GPL-2 - Files: utils/test/test-aa-cli-bootstrap.py Copyright: 2018, 2019, Otto Kekäläinen <otto@kekalainen.net> License: GPL-2 @@ -1003,17 +1133,22 @@ Copyright: 2009-2020, Canonical Ltd. License: GPL-2 Files: utils/test/test-aa-notify.py -Copyright: 2019, Otto Kekäläinen +Copyright: 2019, Otto Kekäläinen <otto@kekalainen.net> 2011, 2012, Canonical Ltd. License: GPL-2 +Files: utils/test/test-config.py + utils/test/test-minitools.py +Copyright: 2013, Kshitij Gupta <kgupta8592@gmail.com> +License: GPL-2 + Files: utils/test/test-severity.py Copyright: 2015, Christian Boltz <apparmor@cboltz.de> 2014, Canonical, Ltd. 2013, Kshitij Gupta <kgupta8592@gmail.com> License: GPL-2 -Files: utils/vim/* +Files: utils/vim/apparmor.vim.in Copyright: 2006-2012, Christian Boltz. 2005, Novell, Inc. License: GPL-2 @@ -1027,7 +1162,7 @@ Files: utils/vim/create-apparmor.vim.py Copyright: 2009-2020, Canonical Ltd. License: GPL-2 -Files: documentation/AppArmor_Developer_1-Kernel_Notes.odt documentation/AppArmor_Developer_2-policy_layout_and_encoding.odt documentation/AppArmor_Developer_3-HFA.odt documentation/AppArmor_Developer_4-Policy_compilation.odt documentation/AppArmor_Developer_5-extending_apparmor_to_userspace.odt documentation/AppArmor_Policy.odt documentation/Techdoc-eHFA.odt parser/tst/minimize.sh presentations/LSS_apparmor-labeling-2013.odp presentations/LSS_apparmor-userspace-2013.odp presentations/apparmor-opensuse-2018.odp presentations/lss-2014-apparmor-review.odp presentations/lss-apparmor-update-2016.odp presentations/lss-apparmor-update-2017.odp +Files: binutils/aa-enabled.pod binutils/aa-exec.pod binutils/aa-features-abi.pod binutils/aa-status.pod documentation/AppArmor_Developer_1-Kernel_Notes.odt documentation/AppArmor_Developer_2-policy_layout_and_encoding.odt documentation/AppArmor_Developer_3-HFA.odt documentation/AppArmor_Developer_4-Policy_compilation.odt documentation/AppArmor_Developer_5-extending_apparmor_to_userspace.odt documentation/AppArmor_Policy.odt parser/tst/minimize.sh presentations/LSS_apparmor-labeling-2013.odp presentations/LSS_apparmor-userspace-2013.odp presentations/apparmor-opensuse-2018.odp presentations/lss-2014-apparmor-review.odp presentations/lss-apparmor-update-2016.odp presentations/lss-apparmor-update-2017.odp utils/aa-autodep.pod utils/aa-complain.pod utils/aa-disable.pod utils/aa-easyprof.pod utils/aa-enforce.pod utils/aa-genprof.pod utils/aa-logprof.pod utils/aa-notify.pod utils/aa-sandbox.pod utils/aa-unconfined.pod utils/logprof.conf.pod Copyright: 1998-2010 Novell/SuSE/Immunix 2008-2014 Canonical Ltd. License: GPL-2+ @@ -1044,7 +1179,7 @@ Copyright: 2006 SUSE Linux Products GmbH, Nuernberg, Germany 2010 Canonical Ltd. License: BSD-3-clause or GPL-2+ -Files: libraries/libapparmor/INSTALL libraries/libapparmor/Makefile.in libraries/libapparmor/autom4te.cache/traces.0 libraries/libapparmor/doc/Makefile.in libraries/libapparmor/src/Makefile.in libraries/libapparmor/swig/python/Makefile.in libraries/libapparmor/swig/python/test/Makefile.in +Files: libraries/libapparmor/INSTALL libraries/libapparmor/Makefile.in libraries/libapparmor/autom4te.cache/output.0 libraries/libapparmor/autom4te.cache/output.1 libraries/libapparmor/autom4te.cache/output.2 libraries/libapparmor/autom4te.cache/traces.0 libraries/libapparmor/doc/Makefile.in libraries/libapparmor/include/Makefile.in libraries/libapparmor/include/sys/Makefile.in libraries/libapparmor/src/Makefile.in libraries/libapparmor/swig/Makefile.in libraries/libapparmor/swig/perl/Makefile.in libraries/libapparmor/swig/python/Makefile.in libraries/libapparmor/swig/python/test/Makefile.in libraries/libapparmor/swig/ruby/Makefile.in libraries/libapparmor/testsuite/Makefile.in libraries/libapparmor/testsuite/config/Makefile.in libraries/libapparmor/testsuite/lib/Makefile.in libraries/libapparmor/testsuite/libaalogparse.test/Makefile.in Copyright: 1999-2008 Novell 2009-2013 Canonical Ltd. License: LGPL-2.1+ -- GitLab