diff --git a/aa_log_extract_tokens.pl b/aa_log_extract_tokens.pl deleted file mode 100755 index bca2451b1be9a2b9f711106a71d5e5a78192aca5..0000000000000000000000000000000000000000 --- a/aa_log_extract_tokens.pl +++ /dev/null @@ -1,228 +0,0 @@ -#!/usr/bin/env perl -# vim: set sts=4 sw=4 et tw=80 : -# -# Copyright (c) 2006 Novell, Inc. All Rights Reserved. -# Copyright (c) 2010 Canonical, Ltd. -# Copyright (c) 2012-2015 Collabora 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 as published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, contact Novell, Inc. -# -# To contact Novell about this file by physical or electronic mail, -# you may find current contact information at www.novell.com. - -# Example script to extract tokens from apparmor events in audit.log -# We use auditd because otherwise messages are lost if they come too quickly -# http://wiki.apparmor.net/index.php/AppArmorMonitoring#Realtime_information -# -# -# Example complaint event: -# type=AVC msg=audit(1344659645.579:153): apparmor="ALLOWED" operation="open" -# parent=15215 profile="/home/chaiwala/bin/*" name="/etc/group" pid=15216 -# comm="ls" requested_mask="r" denied_mask="r" fsuid=0 ouid=0 -# -# Example audit event: -# type=AVC msg=audit(1344735566.384:827): apparmor="AUDIT" operation="getattr" -# parent=16769 profile="/home/chaiwala/bin/*" name="/etc/localtime" pid=16770 -# comm="ls" requested_mask="r" fsuid=1000 ouid=0 -# - -use strict; -use warnings; - -use LibAppArmor qw(); - -use constant DEBUG => $ENV{AA_LOG_DEBUG}; - -my $event; -my $record; -my $value; -my $profile_wanted; -my @event_modes; -my @skip_names; -my %records_order; - -sub usage { - print("Usage: $0 <event mode> [event mode]...\n"); - print("And pipe logs to it\n"); - print("Valid values for [event mode] are: PERMITTING, REJECTING, AUDITING\n"); -} - -# Ensure there are some arguments -if ($#ARGV < 0) { - usage(); - exit(1); -} - -# Ensure that the arguments are all valid -foreach (@ARGV) { - SWITCH: { - if (/PERMITTING/) { last SWITCH; } - if (/REJECTING/) { last SWITCH; } - if (/AUDITING/) { last SWITCH; } - usage(); - exit(1); - } -} - -# Valid values we'll use: PERMITTING, REJECTING, AUDITING -# Valid values that we're not interested in: STATUS, ERROR, UNKNOWN, HINT -# PERMITTING is emitted when: -# * AppArmor is in "normal" audit mode[1] -# * A profile loaded in complain mode matches something -# REJECTING is emitted when: -# * AppArmor is in "normal" audit mode[1] -# * A profile loaded in enforce mode matches something -# AUDITING is emitted when: -# * AppArmor is in "all" audit mode[1] -# -# 1. http://wiki.apparmor.net/index.php/AppArmor_Failures#Module_audit_settings -@event_modes = @ARGV; - -# We're being lazy and getting arguments from the environment instead of doing -# getopt parsing -$profile_wanted = $ENV{'AA_PROFILE_WANTED'}; - -# Regex of file paths to skip accesses to -@skip_names=( - "/etc/ld\.so\.cache", - "/lib/i386-linux-gnu/libc.*\.so", - "/dev/pts/.*", - "/dev/(null|zero|random|urandom)", -); - -# Arbitrary order, but we want to keep it constant. -# chaiwala-apparmor-tests rely on the following order. -%records_order = ( - 'profile' => 1, - 'sdmode' => 2, - 'denied_mask' => 3, - 'operation' => 4, - 'name' => 5, - 'request_mask' => 6, - ); - -sub records_sort { - if (exists $records_order{$a} && exists $records_order{$b}) - { - return ($records_order{$a}) <=> ($records_order{$b}); - } - - if (! exists $records_order{$a} && !exists $records_order{$b}) - { - return $a cmp $b; - } - - return exists $records_order{$a} ? -1 : 1; -} - -# Simplified from Immunix::AppArmor. -sub stringify_sdmode { - my $sdmode = shift; - - return 'ERROR' if $sdmode == 1; - return 'AUDITING' if $sdmode == 2; - return 'PERMITTING' if $sdmode == 3; - return 'REJECTING' if $sdmode == 4; - return 'HINT' if $sdmode == 5; - return 'STATUS' if $sdmode == 6; - - # no idea - output *something* - return $sdmode; -} - -# Simplified from Immunix::AppArmor. We're not using its parse_event -# because that only works properly for events whose mask is something -# like r or w, not for events whose mask is more like receive or send; -# this version is more "raw", and hopefully a little more robust against -# AA changes. -sub parse_event { - my %ev = (); - my $msg = shift; - chomp($msg); - my $event = LibAppArmor::parse_record($msg); - - $ev{resource} = LibAppArmor::aa_log_record::swig_info_get($event); - $ev{active_hat} = LibAppArmor::aa_log_record::swig_active_hat_get($event); - $ev{sdmode} = LibAppArmor::aa_log_record::swig_event_get($event); - $ev{time} = LibAppArmor::aa_log_record::swig_epoch_get($event); - $ev{operation} = LibAppArmor::aa_log_record::swig_operation_get($event); - $ev{profile} = LibAppArmor::aa_log_record::swig_profile_get($event); - $ev{name} = LibAppArmor::aa_log_record::swig_name_get($event); - $ev{name2} = LibAppArmor::aa_log_record::swig_name2_get($event); - $ev{attr} = LibAppArmor::aa_log_record::swig_attribute_get($event); - $ev{parent} = LibAppArmor::aa_log_record::swig_parent_get($event); - $ev{pid} = LibAppArmor::aa_log_record::swig_pid_get($event); - $ev{task} = LibAppArmor::aa_log_record::swig_task_get($event); - $ev{info} = LibAppArmor::aa_log_record::swig_info_get($event); - $ev{denied_mask} = LibAppArmor::aa_log_record::swig_denied_mask_get($event); - $ev{request_mask} = LibAppArmor::aa_log_record::swig_requested_mask_get($event); - $ev{magic_token} = LibAppArmor::aa_log_record::swig_magic_token_get($event); - $ev{family} = LibAppArmor::aa_log_record::swig_net_family_get($event); - $ev{protocol} = LibAppArmor::aa_log_record::swig_net_protocol_get($event); - $ev{sock_type} = LibAppArmor::aa_log_record::swig_net_sock_type_get($event); - - LibAppArmor::free_record($event); - - if (! $ev{sdmode}) { - return undef; - } - - $ev{sdmode} = stringify_sdmode($ev{sdmode}); - - if (DEBUG) { - print STDERR "/----\n"; - foreach my $k (sort keys %ev) { - if (defined $ev{$k}) { - print STDERR "\t", $k, "\t", $ev{$k}, "\n"; - } - } - print STDERR "\\----\n"; - } - - return \%ev; -} - -EVENT: while (<STDIN>) { - # This doesn't give us all the tokens, unfortunately. - # Only gives us: profile, sdmode, time, denied_mask, pid, operation, - # parent (parent pid), name (filename), request_mask - # (FIXME: it could now give more if we want them) - $event = parse_event($_); - - if (!$event || - !(grep { $_ eq $$event{'sdmode'} } @event_modes) || - ($profile_wanted && ($$event{'profile'} ne $profile_wanted))) - { - next EVENT; - } - - foreach (@skip_names) { - if (defined $$event{'name'} && $$event{'name'} =~ /$_/) { - next EVENT; - } - } - - print "====\n"; - # This is pretty much it. Go forth and use this hash in whatever way. - foreach $record (sort records_sort keys %$event) { - $value = $$event{$record}; - next unless $value; - # Skip records that keep changing with every invocation - SWITCH: { - if ($record eq "time") { last SWITCH; } - if ($record eq "pid") { last SWITCH; } - if ($record eq "parent") { last SWITCH; } - print "$record:$value\n"; - } - } -} diff --git a/common-apparmor.sh b/common-apparmor.sh new file mode 100644 index 0000000000000000000000000000000000000000..f6a94977038e82708ec2f870dde7df9f396828f6 --- /dev/null +++ b/common-apparmor.sh @@ -0,0 +1,43 @@ +# Need to source this file. + +# Pass in audit string, returns formatted block as `aa_log_extract_tokens.pl` +# would have provided. Need to return: +# +# profile:/usr/bin/busctl +# sdmode:REJECTING +# denied_mask:r +# operation:open +# name:/proc/879/stat +# request_mask:r +# +# Note: apparmor uses the term "DENIED" rather than "REJECTING", but our +# expected values want "REJECTING", so tweak for now. +apparmor_parse_journal() { + awk -v event=$2 ' + { + for (i = 1; i <= NF; i++) { + n = index($i, "="); + if(n) { + vars[substr($i, 1, n - 1)] = substr($i, n + 1) + # Strip quotes + gsub("\"","",vars[substr($i, 1, n - 1)]) + } + } + } + { + if (vars["apparmor"] == event) { + print "====" + print "profile:"vars["profile"] + # Match old nomenclature + if (vars["apparmor"] == "DENIED") { + vars["apparmor"] = "REJECTING" + } + print "sdmode:"vars["apparmor"] + print "denied_mask:"vars["denied_mask"] + print "operation:"vars["operation"] + print "name:"vars["name"] + print "request_mask:"vars["requested_mask"] + } + } + ' $1 +} diff --git a/run-aa-test b/run-aa-test index 59399401325f12883307958c7d5c432f659221b3..ad6ec39bd77d976c4606991e96913e9b71141800 100755 --- a/run-aa-test +++ b/run-aa-test @@ -8,6 +8,8 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. +. common/common-apparmor.sh + ALTERNATIVE_SEPARATOR="## alternative ##" END=2 @@ -52,7 +54,6 @@ if [ ! -x $1 ]; then exit 1 fi -SOMETHING_FAILED="False" # typically "normal.expected" or "malicious.expected" TEST_TITLE=$( basename ${EXPECT_FILE} ) @@ -112,7 +113,8 @@ else echo "# ${CMDLINE} exited ${RET}" # typically "normal.expected_underlying_tests: fail" echo "${TEST_TITLE}_underlying_tests: fail" - SOMETHING_FAILED="True" + + exit 1 fi # Give journal time to log the entries. @@ -124,11 +126,11 @@ journalctl -S "${START_TIME}" -t audit -o cat > ${AUDIT_FILE} echo "#=== ${TEST_TITLE} ===" -echo "#---8<--- raw output in audit log" +echo "#---8<--- raw apparmor output from journal" cat ${AUDIT_FILE} | sed 's/^/# /' echo "#--->8---" -echo "#---8<--- expected output from aa_log_extract_tokens.pl" +echo "#---8<--- expected parsed apparmor output from journal" cat ${EXPECT_FILE} | sed 's/^/# /' echo "#--->8---" @@ -141,29 +143,12 @@ done PARSE_FILE="${TMP_DIR}/PARSE" -RET=$( cat ${AUDIT_FILE} | common/aa_log_extract_tokens.pl REJECTING > ${PARSE_FILE} ) - -if [ "${RET}" != "0" ]; then - echo "# aa_log_extract_tokens.pl failed, trying line-by-line..." - - LINES=$(wc -l ${AUDIT_FILE} | cut -d ' ' -f1 ) - - cat ${AUDIT_FILE} | while read LINE; do - echo ${LINE} | common/aa_log_extract_tokens.pl REJECTING 2>${TMP_DIR}/STDERR > ${TMP_DIR}/STDOUT - RET=$? - cat ${TMP_DIR}/STDOUT >> ${TMP_DIR}/ERRPARSE - - cat ${TMP_DIR}/STDERR | sed 's/^/E: /' >> ${TMP_DIR}/ERRPARSE +# TODO: There is potential for other processes to cause messages to appear in +# the journal that may lead to false failures. If this is found to be an +# issue in practice, then additional filtering of results may be required +apparmor_parse_journal ${AUDIT_FILE} DENIED > ${PARSE_FILE} - if [ "$RET" != "0" ]; then - echo -n "^ original line: ${LINE}" >> ${TMP_DIR}/ERRPARSE - fi - done - - mv ${TMP_DIR}/ERRPARSE ${PARSE_FILE} -fi - -echo "#---8<--- actual output from aa_log_extract_tokens.pl" +echo "#---8<--- actual parsed apparmor output from journal" cat ${PARSE_FILE} | sed 's/^/# /' echo "#--->8---" @@ -189,10 +174,6 @@ else diff -urN ${TMP_DIR}/EXPECT${NUM} ${PARSE_FILE} echo "#--->8---" echo "${TEST_TITLE}: fail" - SOMETHING_FAILED="True" -fi - -if [ "${SOMETHING_FAILED}" = "True" ]; then exit 1 fi