mirror of
https://gitlab.com/apparmor/apparmor.git
synced 2025-03-04 08:24:42 +01:00

exec rules that specify an specific target profile generate an entry in the xtable. The test entries containing " -> b" are an example of this. Currently the parser allocates the xtable entry before priorities are applied in the backend, or minimization is done. Further more the parser does not ref count the xtable entry to know what it is no longer referenced. The equality tests generate rules that are designed to completely override and remove a lower priority rule, and remove it. Eg. /t { priority=1 /* ux, /f px -> b, } and then compares the generated profile to the functionaly equivalent profile eg. /t { priority=1 /* ux, } To verify the overridden rule has been completely removed. Unfortunately the compilation is not removing the unused xtable entry for the specified transition, causing the equality comparison to fail. Ideally the parser should be fixed so unused xtable entries are removed, but that should be done in a different MR, and have its own test. To fix the current tests, and another rule that adds an xtable entry to the same target that can not be overriden by the x rule using pivot_root. The parser will dedup the xtable entry resulting in the known and test profile both having the same xtable. So the test will pass and meet the original goal of verifying the x rule being overriden and eliminated. Signed-off-by: John Johansen <john.johansen@canonical.com>
1189 lines
45 KiB
Bash
Executable file
1189 lines
45 KiB
Bash
Executable file
#!/bin/bash
|
|
#
|
|
# 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.
|
|
#
|
|
|
|
# Tests for post-parser equality among multiple profiles. These tests are
|
|
# useful to verify that keyword aliases, formatting differences, etc., all
|
|
# result in the same parser output.
|
|
|
|
set -o pipefail
|
|
|
|
_SCRIPTDIR=$(dirname "${BASH_SOURCE[0]}" )
|
|
|
|
APPARMOR_PARSER="${APPARMOR_PARSER:-${_SCRIPTDIR}/../apparmor_parser}"
|
|
fails=0
|
|
errors=0
|
|
verbose="${VERBOSE:-}"
|
|
default_features_file="features.all"
|
|
features_file=$default_features_file
|
|
retain=0
|
|
dumpdfa=0
|
|
testtype=""
|
|
description="Manually run test"
|
|
tmpdir=$(mktemp -d /tmp/eq.$$-XXXXXX)
|
|
chmod 755 ${tmpdir}
|
|
export tmpdir
|
|
|
|
map_priority()
|
|
{
|
|
if [ -z "$1" -o "$1" == "priority=0" ] ; then
|
|
echo "0";
|
|
elif [ "$1" == "priority=-1" ] ; then
|
|
echo "-1"
|
|
elif [ "$1" == "priority=1" ] ;then
|
|
echo "1"
|
|
else
|
|
echo "unknown priority '$1'"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
priority_eq()
|
|
{
|
|
local p1=$(map_priority "$1")
|
|
local p2=$(map_priority "$2")
|
|
|
|
if [ $p1 -eq $p2 ] ; then
|
|
return 0
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
priority_lt()
|
|
{
|
|
local p1=$(map_priority "$1")
|
|
local p2=$(map_priority "$2")
|
|
|
|
if [ $p1 -lt $p2 ] ; then
|
|
return 0
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
priority_gt()
|
|
{
|
|
local p1=$(map_priority "$1")
|
|
local p2=$(map_priority "$2")
|
|
|
|
if [ $p1 -gt $p2 ] ; then
|
|
return 0
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
hash_binary_policy()
|
|
{
|
|
local hash="parser_failure"
|
|
local dump="/dev/null"
|
|
local flags="-QKSq"
|
|
local rc=0
|
|
|
|
if [ $dumpdfa -ne 0 ] ; then
|
|
flags="$flags -D rule-exprs -D dfa-states"
|
|
dump="${tmpdir}/$1.state"
|
|
fi
|
|
|
|
printf %s "$2" | ${APPARMOR_PARSER} --features-file "${_SCRIPTDIR}/features_files/$features_file" ${flags} > "$tmpdir/$1.bin" 2>"$dump"
|
|
rc=$?
|
|
if [ $rc -eq 0 ] ; then
|
|
hash=$(md5sum "${tmpdir}/$1.bin" | cut -d ' ' -f 1)
|
|
rc=$?
|
|
fi
|
|
|
|
printf %s $hash
|
|
if [ $retain -eq 0 -a $rc -ne 0 ] ; then
|
|
rm ${tmpdir}/*
|
|
else
|
|
mv "${tmpdir}/$1.bin" "${tmpdir}/$1.bin.$hash"
|
|
if [ $dumpdfa -ne 0 ] ; then
|
|
mv "${tmpdir}/$1.state" "$tmpdir/$1.state.$hash"
|
|
fi
|
|
fi
|
|
|
|
return $rc
|
|
}
|
|
|
|
check_retain()
|
|
{
|
|
if [ ${retain} -ne 0 ] ; then
|
|
printf " files retained in \"%s/\"\n" ${tmpdir} 1>&2
|
|
exit $ret
|
|
fi
|
|
}
|
|
|
|
# verify_binary - compares the binary policy of multiple profiles
|
|
# $1: Test type (equality or inequality)
|
|
# $2: A short description of the test
|
|
# $3: The known-good profile
|
|
# $4..$n: The profiles to compare against $3
|
|
#
|
|
# Upon failure/error, prints out the test description and profiles that failed
|
|
# and increments $fails or $errors for each failure and error, respectively
|
|
verify_binary()
|
|
{
|
|
local t=$1
|
|
local desc=$2
|
|
local good_profile=$3
|
|
local good_hash
|
|
local ret=0
|
|
|
|
shift
|
|
shift
|
|
shift
|
|
|
|
if [ "$t" != "equality" ] && [ "$t" != "inequality" ] && \
|
|
[ "$t" != "xequality" ] && [ "$t" != "xinequality" ]
|
|
then
|
|
printf "\nERROR: Unknown test mode:\n%s\n\n" "$t" 1>&2
|
|
((errors++))
|
|
return $((ret + 1))
|
|
fi
|
|
rm -f $tmpdir/*
|
|
|
|
if [ -n "$verbose" ] ; then printf "Binary %s %s" "$t" "$desc" ; fi
|
|
if ! good_hash=$(hash_binary_policy "known" "$good_profile") ; then
|
|
if [ -z "$verbose" ] ; then printf "Binary %s %s" "$t" "$desc" ; fi
|
|
printf "\nERROR: Error hashing the following \"known-good\" profile:\n%s\n\n" \
|
|
"$good_profile" 1>&2
|
|
((errors++))
|
|
rm -f ${tmpdir}/*
|
|
return $((ret + 1))
|
|
fi
|
|
|
|
for profile in "$@"
|
|
do
|
|
if ! hash=$(hash_binary_policy "test" "$profile")
|
|
then
|
|
if [ -z "$verbose" ] ; then printf "Binary %s %s" "$t" "$desc" ; fi
|
|
printf "\nERROR: Error hashing the following profile:\n%s\n\n" \
|
|
"$profile" 1>&2
|
|
((errors++))
|
|
((ret++))
|
|
elif [ "$t" == "equality" ] && [ "$hash" != "$good_hash" ]
|
|
then
|
|
if [ -z "$verbose" ] ; then printf "Binary %s %s" "$t" "$desc" ; fi
|
|
printf "\nFAIL: Hash values do not match\n" 1>&2
|
|
printf "parser: %s -QKSq --features-file=%s\n" "${APPARMOR_PARSER}" "${_SCRIPTDIR}/features_files/$features_file" 1>&2
|
|
printf "known-good (%s) != profile-under-test (%s) for the following profiles:\nknown-good %s\nprofile-under-test %s\n\n" \
|
|
"$good_hash" "$hash" "$good_profile" "$profile" 1>&2
|
|
((fails++))
|
|
((ret++))
|
|
check_retain
|
|
elif [ "$t" == "xequality" ] && [ "$hash" == "$good_hash" ]
|
|
then
|
|
if [ -z "$verbose" ] ; then printf "Binary %s %s" "$t" "$desc" ; fi
|
|
printf "\nunexpected PASS: equality test with known problem, Hash values match\n" 1>&2
|
|
printf "parser: %s -QKSq --features-file=%s\n" "${APPARMOR_PARSER}" "${_SCRIPTDIR}/features_files/$features_file" 1>&2
|
|
printf "known-good (%s) == profile-under-test (%s) for the following profile:\nknown-good %s\nprofile-under-test %s\n\n" \
|
|
"$good_hash" "$hash" "$good_profile" "$profile" 1>&2
|
|
((fails++))
|
|
((ret++))
|
|
check_retain
|
|
elif [ "$t" == "xequality" ] && [ "$hash" != "$good_hash" ]
|
|
then
|
|
printf "\nknown problem %s %s: unchanged" "$t" "$desc" 1>&2
|
|
elif [ "$t" == "inequality" ] && [ "$hash" == "$good_hash" ]
|
|
then
|
|
if [ -z "$verbose" ] ; then printf "Binary %s %s" "$t" "$desc" ; fi
|
|
printf "\nFAIL: Hash values match\n" 1>&2
|
|
printf "parser: %s -QKSq --features-file=%s\n" "${APPARMOR_PARSER}" "${_SCRIPTDIR}/features_files/$features_file" 1>&2
|
|
printf "known-good (%s) == profile-under-test (%s) for the following profiles:\nknown-good %s\nprofile-under-test %s\n\n" \
|
|
"$good_hash" "$hash" "$good_profile" "$profile" 1>&2
|
|
((fails++))
|
|
((ret++))
|
|
check_retain
|
|
elif [ "$t" == "xinequality" ] && [ "$hash" != "$good_hash" ]
|
|
then
|
|
if [ -z "$verbose" ] ; then printf "Binary %s %s" "$t" "$desc" ; fi
|
|
printf "\nunexpected PASS: inequality test with known problem, Hash values do not match\n" 1>&2
|
|
printf "parser: %s -QKSq --features-file %s\n" "${APPARMOR_PARSER}" "${_SCRIPTDIR}/features_files/$features_file" 1>&2
|
|
printf "known-good (%s) != profile-under-test (%s) for the following profile:\nknown-good %s\nprofile-under-test %s\n\n" \
|
|
"$good_hash" "$hash" "$good_profile" "$profile" 1>&2
|
|
((fails++))
|
|
((ret++))
|
|
check_retain
|
|
elif [ "$t" == "xinequality" ] && [ "$hash" == "$good_hash" ]
|
|
then
|
|
printf "\nknown problem %s %s: unchanged" "$t" "$desc" 1>&2
|
|
printf "parser: %s -QKSq --features-file=%s\n" "${APPARMOR_PARSER}" "${_SCRIPTDIR}/features_files/$features_file" 1>&2
|
|
fi
|
|
rm -f ${tmpdir}/test*
|
|
done
|
|
|
|
if [ $ret -eq 0 ]
|
|
then
|
|
if [ -z "$verbose" ] ; then
|
|
printf "."
|
|
else
|
|
printf " ok\n"
|
|
|
|
fi
|
|
fi
|
|
return $ret
|
|
}
|
|
|
|
verify_binary_equality()
|
|
{
|
|
verify_binary "equality" "$@"
|
|
}
|
|
|
|
# test we want to be equal but is currently a known problem
|
|
verify_binary_xequality()
|
|
{
|
|
verify_binary "xequality" "$@"
|
|
}
|
|
|
|
verify_binary_inequality()
|
|
{
|
|
verify_binary "inequality" "$@"
|
|
}
|
|
|
|
# test we want to be not equal but is currently a know problem
|
|
verify_binary_xinequality()
|
|
{
|
|
verify_binary "xinequality" "$@"
|
|
}
|
|
|
|
# kernel_features - test whether path(s) are present
|
|
# $@: feature path(s) to test
|
|
# Returns: 0 and outputs "true" if all paths exist
|
|
# 1 and error message if features dir is not available
|
|
# 2 and error message if path does not exist
|
|
kernel_features()
|
|
{
|
|
features_dir="/sys/kernel/security/apparmor/features/"
|
|
if [ ! -e "$features_dir" ] ; then
|
|
echo "Kernel feature masks not supported."
|
|
return 1;
|
|
fi
|
|
|
|
for f in $@ ; do
|
|
if [ ! -e "$features_dir/$f" ] ; then
|
|
# check if feature is in file
|
|
feature=$(basename "$features_dir/$f")
|
|
file=$(dirname "$features_dir/$f")
|
|
if [ -f $file ]; then
|
|
if ! grep -q $feature $file; then
|
|
echo "Required feature '$f' not available."
|
|
return 2;
|
|
fi
|
|
else
|
|
echo "Required feature '$f' not available."
|
|
return 3;
|
|
fi
|
|
fi
|
|
done
|
|
|
|
echo "true"
|
|
return 0;
|
|
}
|
|
|
|
##########################################################################
|
|
### wrapper fn, should be indented but isn't to reduce wrap
|
|
verify_set()
|
|
{
|
|
local p1="$1"
|
|
local p2="$2"
|
|
[ -n "${verbose}" ] && echo -e "\n equality $e of '$p1' vs '$p2'\n"
|
|
|
|
verify_binary_equality "'$p1'x'$p2' dbus send" \
|
|
"/t { $p1 dbus send, }" \
|
|
"/t { $p2 dbus write, }" \
|
|
"/t { $p2 dbus w, }"
|
|
|
|
verify_binary_equality "'$p1'x'$p2' dbus receive" \
|
|
"/t { $p1 dbus receive, }" \
|
|
"/t { $p2 dbus read, }" \
|
|
"/t { $p2 dbus r, }"
|
|
|
|
verify_binary_equality "'$p1'x'$p2' dbus send + receive" \
|
|
"/t { $p1 dbus (send, receive), }" \
|
|
"/t { $p2 dbus (read, write), }" \
|
|
"/t { $p2 dbus (r, w), }" \
|
|
"/t { $p2 dbus (rw), }" \
|
|
"/t { $p2 dbus rw, }" \
|
|
|
|
verify_binary_equality "'$p1'x'$p2' dbus all accesses" \
|
|
"/t { $p1 dbus (send, receive, bind, eavesdrop), }" \
|
|
"/t { $p2 dbus (read, write, bind, eavesdrop), }" \
|
|
"/t { $p2 dbus (r, w, bind, eavesdrop), }" \
|
|
"/t { $p2 dbus (rw, bind, eavesdrop), }" \
|
|
"/t { $p2 dbus (), }" \
|
|
"/t { $p2 dbus, }" \
|
|
|
|
verify_binary_equality "'$p1'x'$p2' dbus implied accesses with a bus conditional" \
|
|
"/t { $p1 dbus (send, receive, bind, eavesdrop) bus=session, }" \
|
|
"/t { $p2 dbus (read, write, bind, eavesdrop) bus=session, }" \
|
|
"/t { $p2 dbus (r, w, bind, eavesdrop) bus=session, }" \
|
|
"/t { $p2 dbus (rw, bind, eavesdrop) bus=session, }" \
|
|
"/t { $p2 dbus () bus=session, }" \
|
|
"/t { $p2 dbus bus=session, }" \
|
|
|
|
verify_binary_equality "'$p1'x'$p2' dbus implied accesses for services" \
|
|
"/t { $p1 dbus bind name=com.foo, }" \
|
|
"/t { $p2 dbus name=com.foo, }"
|
|
|
|
verify_binary_equality "'$p1'x'$p2' dbus implied accesses for messages" \
|
|
"/t { $p1 dbus (send, receive) path=/com/foo interface=org.foo, }" \
|
|
"/t { $p2 dbus path=/com/foo interface=org.foo, }"
|
|
|
|
verify_binary_equality "'$p1'x'$p2' dbus implied accesses for messages with peer names" \
|
|
"/t { $p1 dbus (send, receive) path=/com/foo interface=org.foo peer=(name=com.foo), }" \
|
|
"/t { $p2 dbus path=/com/foo interface=org.foo peer=(name=com.foo), }" \
|
|
"/t { $p2 dbus (send, receive) path=/com/foo interface=org.foo peer=(name=(com.foo)), }" \
|
|
"/t { $p2 dbus path=/com/foo interface=org.foo peer=(name=(com.foo)), }"
|
|
|
|
verify_binary_equality "'$p1'x'$p2' dbus implied accesses for messages with peer labels" \
|
|
"/t { $p1 dbus (send, receive) path=/com/foo interface=org.foo peer=(label=/usr/bin/app), }" \
|
|
"/t { $p2 dbus path=/com/foo interface=org.foo peer=(label=/usr/bin/app), }"
|
|
|
|
verify_binary_equality "'$p1'x'$p2' dbus element parsing" \
|
|
"/t { $p1 dbus bus=b path=/ interface=i member=m peer=(name=n label=l), }" \
|
|
"/t { $p2 dbus bus=\"b\" path=\"/\" interface=\"i\" member=\"m\" peer=(name=\"n\" label=\"l\"), }" \
|
|
"/t { $p2 dbus bus=(b) path=(/) interface=(i) member=(m) peer=(name=(n) label=(l)), }" \
|
|
"/t { $p2 dbus bus=(\"b\") path=(\"/\") interface=(\"i\") member=(\"m\") peer=(name=(\"n\") label=(\"l\")), }" \
|
|
"/t { $p2 dbus bus =b path =/ interface =i member =m peer =(name =n label =l), }" \
|
|
"/t { $p2 dbus bus= b path= / interface= i member= m peer= (name= n label= l), }" \
|
|
"/t { $p2 dbus bus = b path = / interface = i member = m peer = ( name = n label = l ), }"
|
|
|
|
verify_binary_equality "'$p1'x'$p2' dbus access parsing" \
|
|
"/t { $p1 dbus, }" \
|
|
"/t { $p2 dbus (), }" \
|
|
"/t { $p2 dbus (send, receive, bind, eavesdrop), }" \
|
|
"/t { $p2 dbus (send receive bind eavesdrop), }" \
|
|
"/t { $p2 dbus (send, receive bind, eavesdrop), }" \
|
|
"/t { $p2 dbus (send,receive,bind,eavesdrop), }" \
|
|
"/t { $p2 dbus (send,receive,,,,,,,,,,,,,,,,bind,eavesdrop), }" \
|
|
"/t { $p2 dbus (send,send,send,send send receive,bind eavesdrop), }" \
|
|
|
|
verify_binary_equality "'$p1'x'$p2' dbus variable expansion" \
|
|
"/t { $p1 dbus (send, receive) path=/com/foo member=spork interface=org.foo peer=(name=com.foo label=/com/foo), }" \
|
|
"@{FOO}=foo
|
|
/t { $p2 dbus (send, receive) path=/com/@{FOO} member=spork interface=org.@{FOO} peer=(name=com.@{FOO} label=/com/@{FOO}), }" \
|
|
"@{FOO}=foo
|
|
@{SPORK}=spork
|
|
/t { $p2 dbus (send, receive) path=/com/@{FOO} member=@{SPORK} interface=org.@{FOO} peer=(name=com.@{FOO} label=/com/@{FOO}), }" \
|
|
"@{FOO}=/com/foo
|
|
/t { $p2 dbus (send, receive) path=@{FOO} member=spork interface=org.foo peer=(name=com.foo label=@{FOO}), }" \
|
|
"@{FOO}=com
|
|
/t { $p2 dbus (send, receive) path=/@{FOO}/foo member=spork interface=org.foo peer=(name=@{FOO}.foo label=/@{FOO}/foo), }"
|
|
|
|
verify_binary_equality "'$p1'x'$p2' dbus variable expansion, multiple values/rules" \
|
|
"/t { $p1 dbus (send, receive) path=/com/foo, $p1 dbus (send, receive) path=/com/bar, }" \
|
|
"/t { $p2 dbus (send, receive) path=/com/{foo,bar}, }" \
|
|
"/t { $p2 dbus (send, receive) path={/com/foo,/com/bar}, }" \
|
|
"@{FOO}=foo
|
|
/t { $p2 dbus (send, receive) path=/com/@{FOO}, $p2 dbus (send, receive) path=/com/bar, }" \
|
|
"@{FOO}=foo bar
|
|
/t { $p2 dbus (send, receive) path=/com/@{FOO}, }" \
|
|
"@{FOO}=bar foo
|
|
/t { $p2 dbus (send, receive) path=/com/@{FOO}, }" \
|
|
"@{FOO}={bar,foo}
|
|
/t { $p2 dbus (send, receive) path=/com/@{FOO}, }" \
|
|
"@{FOO}=foo
|
|
@{BAR}=bar
|
|
/t { $p2 dbus (send, receive) path=/com/{@{FOO},@{BAR}}, }" \
|
|
|
|
verify_binary_equality "'$p1'x'$p2' dbus variable expansion, ensure rule de-duping occurs" \
|
|
"/t { $p1 dbus (send, receive) path=/com/foo, $p1 dbus (send, receive) path=/com/bar, }" \
|
|
"/t { $p2 dbus (send, receive) path=/com/foo, $p2 dbus (send, receive) path=/com/bar, dbus (send, receive) path=/com/bar, }" \
|
|
"@{FOO}=bar foo bar foo
|
|
/t { $p2 dbus (send, receive) path=/com/@{FOO}, }" \
|
|
"@{FOO}=bar foo bar foo
|
|
/t { $p2 dbus (send, receive) path=/com/@{FOO}, $p2 dbus (send, receive) path=/com/@{FOO}, }"
|
|
|
|
verify_binary_equality "'$p1'x'$p2' dbus minimization with all perms" \
|
|
"/t { $p1 dbus, }" \
|
|
"/t { $p2 dbus bus=session, $p2 dbus, }" \
|
|
"/t { $p2 dbus (send, receive, bind, eavesdrop), $p2 dbus, }"
|
|
|
|
verify_binary_equality "'$p1'x'$p2' dbus minimization with bind" \
|
|
"/t { $p1 dbus bind, }" \
|
|
"/t { $p2 dbus bind bus=session, $p2 dbus bind, }" \
|
|
"/t { $p2 dbus bind bus=system name=com.foo, $p2 dbus bind, }"
|
|
|
|
verify_binary_equality "'$p1'x'$p2' dbus minimization with send and a bus conditional" \
|
|
"/t { $p1 dbus send bus=system, }" \
|
|
"/t { $p2 dbus send bus=system path=/com/foo interface=com.foo member=bar, dbus send bus=system, }" \
|
|
"/t { $p2 dbus send bus=system peer=(label=/usr/bin/foo), $p2 dbus send bus=system, }"
|
|
|
|
verify_binary_equality "'$p1'x'$p2' dbus minimization with an audit modifier" \
|
|
"/t { $p1 audit dbus eavesdrop, }" \
|
|
"/t { $p2 audit dbus eavesdrop bus=session, $p2 audit dbus eavesdrop, }"
|
|
|
|
verify_binary_equality "'$p1'x'$p2' dbus minimization with a deny modifier" \
|
|
"/t { $p1 deny dbus send bus=system peer=(name=com.foo), }" \
|
|
"/t { $p2 deny dbus send bus=system peer=(name=com.foo label=/usr/bin/foo), $p2 deny dbus send bus=system peer=(name=com.foo), }" \
|
|
|
|
verify_binary_equality "'$p1'x'$p2' dbus minimization found in dbus abstractions" \
|
|
"/t { $p1 dbus send bus=session, }" \
|
|
"/t { $p2 dbus send
|
|
bus=session
|
|
path=/org/freedesktop/DBus
|
|
interface=org.freedesktop.DBus
|
|
member={Hello,AddMatch,RemoveMatch,GetNameOwner,NameHasOwner,StartServiceByName}
|
|
peer=(name=org.freedesktop.DBus),
|
|
$p2 dbus send bus=session, }"
|
|
|
|
# verify slash filtering for dbus paths.
|
|
verify_binary_equality "'$p1'x'$p2' dbus slash filtering for paths" \
|
|
"/t { $p1 dbus (send, receive) path=/com/foo, $p1 dbus (send, receive) path=/com/bar, }" \
|
|
"/t { $p2 dbus (send, receive) path=/com///foo, $p2 dbus (send, receive) path=///com/bar, }" \
|
|
"/t { $p2 dbus (send, receive) path=/com//{foo,bar}, }" \
|
|
"/t { $p2 dbus (send, receive) path={//com/foo,/com//bar}, }" \
|
|
"@{FOO}=/foo
|
|
/t { $p2 dbus (send, receive) path=/com/@{FOO}, $p2 dbus (send, receive) path=/com/bar, }" \
|
|
"@{FOO}=/foo /bar
|
|
/t { $p2 dbus (send, receive) path=/com/@{FOO}, }" \
|
|
"@{FOO}=/bar //foo
|
|
/t { $p2 dbus (send, receive) path=/com/@{FOO}, }" \
|
|
"@{FOO}=//{bar,foo}
|
|
/t { $p2 dbus (send, receive) path=/com/@{FOO}, }" \
|
|
"@{FOO}=/foo
|
|
@{BAR}=bar
|
|
/t { $p2 dbus (send, receive) path=/com/@{FOO}, $p2 dbus (send, receive) path=/com//@{BAR}, }"
|
|
|
|
# Rules compatible with audit, deny, and audit deny
|
|
# note: change_profile does not support audit/allow/deny atm
|
|
for rule in "capability" "capability mac_admin" \
|
|
"mount" "mount /a" "mount /a -> /b" "mount options in (ro) /a -> b" \
|
|
"remount" "remount /a" \
|
|
"umount" "umount /a" \
|
|
"pivot_root" "pivot_root /a" "pivot_root oldroot=/" \
|
|
"pivot_root oldroot=/ /a" "pivot_root oldroot=/ /a -> foo" \
|
|
"ptrace" "ptrace trace" "ptrace (readby,tracedby) peer=unconfined" \
|
|
"signal" "signal (send,receive)" "signal peer=unconfined" \
|
|
"signal receive set=(kill)" \
|
|
"dbus" "dbus send" "dbus bus=system" "dbus bind name=foo" \
|
|
"dbus peer=(label=foo)" "dbus eavesdrop" \
|
|
"unix" "unix (create, listen, accept)" "unix addr=@*" "unix addr=none" \
|
|
"unix peer=(label=foo)" \
|
|
"/f r" "/f w" "/f rwmlk" "/** r" "/**/ w" \
|
|
"file /f r" "file /f w" "file /f rwmlk" \
|
|
"link /a -> /b" "link subset /a -> /b" \
|
|
"l /a -> /b" "l subset /a -> /b" \
|
|
"file l /a -> /b" "l subset /a -> /b"
|
|
do
|
|
verify_binary_equality "'$p1'x'$p2' allow modifier for \"${rule}\"" \
|
|
"/t { $p1 ${rule}, }" \
|
|
"/t { $p2 allow ${rule}, }"
|
|
|
|
verify_binary_equality "'$p1'x'$p2' audit allow modifier for \"${rule}\"" \
|
|
"/t { $p1 audit ${rule}, }" \
|
|
"/t { $p2 audit allow ${rule}, }"
|
|
|
|
verify_binary_inequality "'$p1'x'$p2' audit, deny, and audit deny modifiers for \"${rule}\"" \
|
|
"/t { $p1 ${rule}, }" \
|
|
"/t { $p2 audit ${rule}, }" \
|
|
"/t { $p2 audit allow ${rule}, }" \
|
|
"/t { $p2 deny ${rule}, }" \
|
|
"/t { $p2 audit deny ${rule}, }"
|
|
|
|
verify_binary_inequality "'$p1'x'$p2' audit vs deny and audit deny modifiers for \"${rule}\"" \
|
|
"/t { $p1 audit ${rule}, }" \
|
|
"/t { $p2 deny ${rule}, }" \
|
|
"/t { $p2 audit deny ${rule}, }"
|
|
|
|
verify_binary_inequality "'$p1'x'$p2' deny and audit deny modifiers for \"${rule}\"" \
|
|
"/t { $p1 deny ${rule}, }" \
|
|
"/t { $p2 audit deny ${rule}, }"
|
|
done
|
|
|
|
####### special case for network TODO: for network above when network
|
|
####### rules fixed
|
|
for rule in "network" "network tcp" "network inet6 tcp"
|
|
do
|
|
verify_binary_equality "allow modifier for \"${rule}\"" \
|
|
"/t { ${rule}, }" \
|
|
"/t { allow ${rule}, }"
|
|
|
|
verify_binary_equality "audit allow modifier for \"${rule}\"" \
|
|
"/t { audit ${rule}, }" \
|
|
"/t { audit allow ${rule}, }"
|
|
|
|
verify_binary_inequality "audit, deny, and audit deny modifiers for \"${rule}\"" \
|
|
"/t { ${rule}, }" \
|
|
"/t { audit ${rule}, }" \
|
|
"/t { audit allow ${rule}, }" \
|
|
"/t { deny ${rule}, }" \
|
|
"/t { audit deny ${rule}, }"
|
|
|
|
verify_binary_inequality "audit vs deny and audit deny modifiers for \"${rule}\"" \
|
|
"/t { audit ${rule}, }" \
|
|
"/t { deny ${rule}, }" \
|
|
"/t { audit deny ${rule}, }"
|
|
|
|
verify_binary_inequality "deny and audit deny modifiers for \"${rule}\"" \
|
|
"/t { deny ${rule}, }" \
|
|
"/t { audit deny ${rule}, }"
|
|
done
|
|
|
|
# Rules that need special treatment for the deny modifier
|
|
for rule in "/f ux" "/f Ux" "/f px" "/f Px" "/f cx" "/f Cx" "/f ix" \
|
|
"/f pux" "/f Pux" "/f pix" "/f Pix" \
|
|
"/f cux" "/f Cux" "/f cix" "/f Cix" \
|
|
"/* ux" "/* Ux" "/* px" "/* Px" "/* cx" "/* Cx" "/* ix" \
|
|
"/* pux" "/* Pux" "/* pix" "/* Pix" \
|
|
"/* cux" "/* Cux" "/* cix" "/* Cix" \
|
|
"/f px -> b " "/f Px -> b" "/f cx -> b" "/f Cx -> b" \
|
|
"/f pux -> b" "/f Pux -> b" "/f pix -> b" "/f Pix -> b" \
|
|
"/f cux -> b" "/f Cux -> b" "/f cix -> b" "/f Cix -> b" \
|
|
"/* px -> b" "/* Px -> b" "/* cx -> b" "/* Cx -> b" \
|
|
"/* pux -> b" "/* Pux -> b" "/* pix -> b" "/* Pix -> b" \
|
|
"/* cux -> b" "/* Cux -> b" "/* cix -> b" "/* Cix -> b" \
|
|
"file /f ux" "file /f Ux" "file /f px" "file /f Px" \
|
|
"file /f cx" "file /f Cx" "file /f ix" \
|
|
"file /f pux" "file /f Pux" "file /f pix" "file /f Pix" \
|
|
"/f cux" "/f Cux" "/f cix" "/f Cix" \
|
|
"file /* ux" "file /* Ux" "file /* px" "file /* Px" \
|
|
"file /* cx" "file /* Cx" "file /* ix" \
|
|
"file /* pux" "file /* Pux" "file /* pix" "file /* Pix" \
|
|
"file /* cux" "file /* Cux" "file /* cix" "file /* Cix" \
|
|
"file /f px -> b " "file /f Px -> b" "file /f cx -> b" "file /f Cx -> b" \
|
|
"file /f pux -> b" "file /f Pux -> b" "file /f pix -> b" "file /f Pix -> b" \
|
|
"file /f cux -> b" "file /f Cux -> b" "file /f cix -> b" "file /f Cix -> b" \
|
|
"file /* px -> b" "file /* Px -> b" "file /* cx -> b" "file /* Cx -> b" \
|
|
"file /* pux -> b" "file /* Pux -> b" "file /* pix -> b" "file /* Pix -> b" \
|
|
"file /* cux -> b" "file /* Cux -> b" "file /* cix -> b" "file /* Cix -> b"
|
|
|
|
do
|
|
verify_binary_equality "'$p1'x'$p2' allow modifier for \"${rule}\"" \
|
|
"/t { $p1 ${rule}, }" \
|
|
"/t { $p2 allow ${rule}, }"
|
|
|
|
verify_binary_equality "'$p1'x'$p2' audit allow modifier for \"${rule}\"" \
|
|
"/t { $p1 audit ${rule}, }" \
|
|
"/t { $p2 audit allow ${rule}, }"
|
|
|
|
# skip rules that don't end with x perm
|
|
if [ -n "${rule##*x}" ] ; then continue ; fi
|
|
|
|
verify_binary_inequality "'$p1'x'$p2' deny, audit deny modifier for \"${rule}\"" \
|
|
"/t { $p1 ${rule}, }" \
|
|
"/t { $p2 audit ${rule}, }" \
|
|
"/t { $p2 audit allow ${rule}, }" \
|
|
"/t { $p2 deny ${rule% *} x, }" \
|
|
"/t { $p2 audit deny ${rule% *} x, }"
|
|
|
|
verify_binary_inequality "'$p1'x'$p2' audit vs deny and audit deny modifiers for \"${rule}\"" \
|
|
"/t { $p1 audit ${rule}, }" \
|
|
"/t { $p2 deny ${rule% *} x, }" \
|
|
"/t { $p2 audit deny ${rule% *} x, }"
|
|
|
|
done
|
|
|
|
# verify deny and audit deny differ for x perms
|
|
for prefix in "/f" "/*" "file /f" "file /*" ; do
|
|
verify_binary_inequality "'$p1'x'$p2' deny and audit deny x modifiers for \"${prefix}\"" \
|
|
"/t { $p1 deny ${prefix} x, }" \
|
|
"/t { $p2 audit deny ${prefix} x, }"
|
|
done
|
|
|
|
#Test equality of leading and trailing file permissions
|
|
for audit in "" "audit" ; do
|
|
for allow in "" "allow" "deny" ; do
|
|
for owner in "" "owner" ; do
|
|
for f in "" "file" ; do
|
|
prefix="$audit $allow $owner $f"
|
|
for perm in "r" "w" "a" "l" "k" "m" "rw" "ra" \
|
|
"rl" "rk" "rm" "wl" "wk" "wm" \
|
|
"rwl" "rwk" "rwm" "ral" "rak" \
|
|
"ram" "rlk" "rlm" "rkm" "wlk" \
|
|
"wlm" "wkm" "alk" "alm" "akm" \
|
|
"lkm" "rwlk" "rwlm" "rwkm" \
|
|
"ralk" "ralm" "wlkm" "alkm" \
|
|
"rwlkm" "ralkm" ; do
|
|
verify_binary_equality "'$p1'x'$p2' leading and trailing perms for \"${perm}\"" \
|
|
"/t { $p1 ${prefix} /f ${perm}, }" \
|
|
"/t { $p2 ${prefix} ${perm} /f, }"
|
|
done
|
|
if [ "$allow" == "deny" ] ; then continue ; fi
|
|
for perm in "ux" "Ux" "px" "Px" "cx" "Cx" \
|
|
"ix" "pux" "Pux" "pix" "Pix" \
|
|
"cux" "Cux" "cix" "Cix"
|
|
do
|
|
verify_binary_equality "'$p1'x'$p2' leading and trailing perms for \"${perm}\"" \
|
|
"/t { $p1 ${prefix} /f ${perm}, }" \
|
|
"/t { $p2 ${prefix} ${perm} /f, }"
|
|
done
|
|
for perm in "px" "Px" "cx" "Cx" \
|
|
"pux" "Pux" "pix" "Pix" \
|
|
"cux" "Cux" "cix" "Cix"
|
|
do
|
|
verify_binary_equality "'$p1'x'$p2' leading and trailing perms for x-transition \"${perm}\"" \
|
|
"/t { $p1 ${prefix} /f ${perm} -> b, }" \
|
|
"/t { $p2 ${prefix} ${perm} /f -> b, }"
|
|
done
|
|
done
|
|
done
|
|
done
|
|
done
|
|
|
|
#Test rule overlap for x most specific match
|
|
for perm1 in "ux" "Ux" "px" "Px" "cx" "Cx" "ix" "pux" "Pux" \
|
|
"pix" "Pix" "cux" "Cux" "cix" "Cix" "px -> b" \
|
|
"Px -> b" "cx -> b" "Cx -> b" "pux -> b" "Pux ->b" \
|
|
"pix -> b" "Pix -> b" "cux -> b" "Cux -> b" \
|
|
"cix -> b" "Cix -> b"
|
|
do
|
|
for perm2 in "ux" "Ux" "px" "Px" "cx" "Cx" "ix" "pux" "Pux" \
|
|
"pix" "Pix" "cux" "Cux" "cix" "Cix" "px -> b" \
|
|
"Px -> b" "cx -> b" "Cx -> b" "pux -> b" "Pux ->b" \
|
|
"pix -> b" "Pix -> b" "cux -> b" "Cux -> b" \
|
|
"cix -> b" "Cix -> b"
|
|
do
|
|
|
|
# Fixme: have to do special handling for -> b, as this
|
|
# creates an entry in the transition table. However
|
|
# priority rules can make it so the reference to the
|
|
# transition table is removed, but the parser still keeps
|
|
# the tranition. This can lead to a situation where the
|
|
# test dfa with a "-> b" transition is functionally equivalent
|
|
# but will fail equality comparison.
|
|
# fix this by adding a pivot_root -> b, rule which add
|
|
# add an xtable entry that is deduped with the xrule
|
|
if [ "$perm1" == "$perm2" ] || priority_gt "$p1" "" ; then
|
|
verify_binary_equality "'$p1'x'$p2' Exec perm \"${perm1}\" - most specific match: same as glob" \
|
|
"/t { $p1 /* ${perm1}, /f ${perm2}, pivot_root -> b, }" \
|
|
"/t { $p2 /* ${perm1}, pivot_root -> b, }"
|
|
else
|
|
verify_binary_inequality "'$p1'x'$p2' Exec \"${perm1}\" vs \"${perm2}\" - most specific match: different from glob" \
|
|
"/t { $p1 /* ${perm1}, /f ${perm2}, pivot_root -> b, }" \
|
|
"/t { $p2 /* ${perm1}, pivot_root -> b, }"
|
|
fi
|
|
done
|
|
verify_binary_inequality "'$p1'x'$p2' Exec \"${perm1}\" vs deny x - most specific match: different from glob" \
|
|
"/t { $p1 /* ${perm1}, audit deny /f x, }" \
|
|
"/t { $p2 /* ${perm1}, }"
|
|
|
|
done
|
|
|
|
#Test deny carves out permission
|
|
verify_binary_inequality "'$p1'x'$p2' Deny removes r perm" \
|
|
"/t { $p1 /foo/[abc] r, audit deny /foo/b r, }" \
|
|
"/t { $p2 /foo/[abc] r, }"
|
|
|
|
verify_binary_equality "'$p1'x'$p2' Deny removes r perm" \
|
|
"/t { $p1 /foo/[abc] r, audit deny /foo/b r, }" \
|
|
"/t { $p2 /foo/[ac] r, }"
|
|
|
|
#this one may not be true in the future depending on if the compiled profile
|
|
#is explicitly including deny permissions for dynamic composition
|
|
verify_binary_equality "'$p1'x'$p2' Deny of ungranted perm" \
|
|
"/t { $p1 /foo/[abc] r, audit deny /foo/b w, }" \
|
|
"/t { $p2 /foo/[abc] r, }"
|
|
|
|
|
|
verify_binary_equality "'$p1'x'$p2' change_profile == change_profile -> **" \
|
|
"/t { $p1 change_profile, }" \
|
|
"/t { $p2 change_profile -> **, }"
|
|
|
|
verify_binary_equality "'$p1'x'$p2' change_profile /** == change_profile /** -> **" \
|
|
"/t { $p1 change_profile /**, }" \
|
|
"/t { $p2 change_profile /** -> **, }"
|
|
|
|
verify_binary_equality "'$p1'x'$p2' change_profile /** == change_profile /** -> **" \
|
|
"/t { $p1 change_profile unsafe /**, }" \
|
|
"/t { $p2 change_profile unsafe /** -> **, }"
|
|
|
|
verify_binary_equality "'$p1'x'$p2' change_profile /** == change_profile /** -> **" \
|
|
"/t { $p1 change_profile /**, }" \
|
|
"/t { $p2 change_profile safe /** -> **, }"
|
|
|
|
verify_binary_inequality "'$p1'x'$p2' change_profile /** == change_profile /** -> **" \
|
|
"/t { $p1 change_profile /**, }" \
|
|
"/t { $p2 change_profile unsafe /**, }"
|
|
|
|
verify_binary_equality "'$p1'x'$p2' profile name is hname in rule" \
|
|
":ns:/hname { $p1 signal peer=/hname, }" \
|
|
":ns:/hname { $p2 signal peer=@{profile_name}, }"
|
|
|
|
verify_binary_inequality "'$p1'x'$p2' profile name is NOT fq name in rule" \
|
|
":ns:/hname { $p1 signal peer=:ns:/hname, }" \
|
|
":ns:/hname { $p2 signal peer=@{profile_name}, }"
|
|
|
|
verify_binary_equality "'$p1'x'$p2' profile name is hname in sub pofile rule" \
|
|
":ns:/hname { profile child { $p1 signal peer=/hname//child, } }" \
|
|
":ns:/hname { profile child { $p2 signal peer=@{profile_name}, } }"
|
|
|
|
verify_binary_inequality "'$p1'x'$p2' profile name is NOT fq name in sub profile rule" \
|
|
":ns:/hname { profile child { $p1 signal peer=:ns:/hname//child, } }" \
|
|
":ns:/hname { profile child { $p2 signal peer=@{profile_name}, } }"
|
|
|
|
verify_binary_equality "'$p1'x'$p2' profile name is hname in hat rule" \
|
|
":ns:/hname { ^child { $p1 signal peer=/hname//child, } }" \
|
|
":ns:/hname { ^child { $p2 signal peer=@{profile_name}, } }"
|
|
|
|
verify_binary_inequality "'$p1'x'$p2' profile name is NOT fq name in hat rule" \
|
|
":ns:/hname { ^child { $p1 signal peer=:ns:/hname//child, } }" \
|
|
":ns:/hname { ^child { $p2 signal peer=@{profile_name}, } }"
|
|
|
|
verify_binary_equality "'$p1'x'$p2' @{profile_name} is literal in peer" \
|
|
"/{a,b} { $p1 signal peer=/\{a,b\}, }" \
|
|
"/{a,b} { $p2 signal peer=@{profile_name}, }"
|
|
|
|
verify_binary_equality "'$p1'x'$p2' @{profile_name} is literal in peer with pattern" \
|
|
"/{a,b} { $p1 signal peer={/\{a,b\},c}, }" \
|
|
"/{a,b} { $p2 signal peer={@{profile_name},c}, }"
|
|
|
|
verify_binary_inequality "'$p1'x'$p2' @{profile_name} is not pattern in peer" \
|
|
"/{a,b} { $p1 signal peer=/{a,b}, }" \
|
|
"/{a,b} { $p2 signal peer=@{profile_name}, }"
|
|
|
|
verify_binary_equality "'$p1'x'$p2' @{profile_name} is literal in peer with esc sequence" \
|
|
"/\\\\a { $p1 signal peer=/\\\\a, }" \
|
|
"/\\\\a { $p2 signal peer=@{profile_name}, }"
|
|
|
|
verify_binary_equality "'$p1'x'$p2' @{profile_name} is literal in peer with esc alt sequence" \
|
|
"/\\{a,b\\},c { $p1 signal peer=/\\{a,b\\},c, }" \
|
|
"/\\{a,b\\},c { $p2 signal peer=@{profile_name}, }"
|
|
|
|
|
|
|
|
# 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 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
|
|
# merging permissions of rules.
|
|
# If the parser isn't adding the rules "append" is not equivalent to
|
|
# the "write" permission in the second profile and the test will fail.
|
|
# If the parser is adding the change_hat proc attr rules then the
|
|
# rules should merge and be equivalent.
|
|
#
|
|
# if priorities are different then the implied rule priority then the
|
|
# implied rule will completely override or completely be overriden.
|
|
# (the change_hat implied rule has a priority of 0)
|
|
# because of the difference in 'a' vs 'w' permission the two rules should
|
|
# only be equal when the append rule has the same priority as the implied
|
|
# rule (allowing them to combine) AND the other rule is not overridden by
|
|
# the implied rule, or both being overridden by the implied rule
|
|
# the implied rule
|
|
if { priority_lt "$p1" "" && priority_lt "$p2" "" ; } ||
|
|
{ priority_eq "$p1" "" && ! priority_lt "$p2" "" ; }; then
|
|
verify_binary_equality "'$p1'x'$p2' change_hat rules automatically inserted"\
|
|
"/t { $p1 owner /proc/[0-9]*/attr/{apparmor/,}current a, ^test { $p1 owner /proc/[0-9]*/attr/{apparmor/,}current a, /f r, }}" \
|
|
"/t { $p2 owner /proc/[0-9]*/attr/{apparmor/,}current w, ^test { $p2 owner /proc/[0-9]*/attr/{apparmor/,}current w, /f r, }}"
|
|
else
|
|
verify_binary_inequality "'$p1'x'$p2' change_hat rules automatically inserted"\
|
|
"/t { $p1 owner /proc/[0-9]*/attr/{apparmor/,}current a, ^test { $p1 owner /proc/[0-9]*/attr/{apparmor/,}current a, /f r, }}" \
|
|
"/t { $p2 owner /proc/[0-9]*/attr/{apparmor/,}current w, ^test { $p2 owner /proc/[0-9]*/attr/{apparmor/,}current w, /f r, }}"
|
|
fi
|
|
|
|
# verify slash filtering for unix socket address paths.
|
|
# see https://bugs.launchpad.net/apparmor/+bug/1856738
|
|
verify_binary_equality "'$p1'x'$p2' unix rules addr conditional" \
|
|
"/t { $p1 unix bind addr=@/a/bar, }" \
|
|
"/t { $p2 unix bind addr=@/a//bar, }" \
|
|
"/t { $p2 unix bind addr=@//a/bar, }" \
|
|
"/t { $p2 unix bind addr=@/a///bar, }" \
|
|
"@{HOME}=/a/
|
|
/t { $p2 unix bind addr=@@{HOME}/bar, }" \
|
|
"@{HOME}=/a/
|
|
/t { $p2 unix bind addr=@//@{HOME}bar, }" \
|
|
"@{HOME}=/a/
|
|
/t { $p2 unix bind addr=@/@{HOME}/bar, }"
|
|
|
|
verify_binary_equality "'$p1'x'$p2' unix rules peer addr conditional" \
|
|
"/t { $p1 unix peer=(addr=@/a/bar), }" \
|
|
"/t { $p2 unix peer=(addr=@/a//bar), }" \
|
|
"/t { $p2 unix peer=(addr=@//a/bar), }" \
|
|
"/t { $p2 unix peer=(addr=@/a///bar), }" \
|
|
"@{HOME}=/a/
|
|
/t { $p2 unix peer=(addr=@@{HOME}/bar), }" \
|
|
"@{HOME}=/a/
|
|
/t { $p2 unix peer=(addr=@//@{HOME}bar), }" \
|
|
"@{HOME}=/a/
|
|
/t { $p2 unix peer=(addr=@/@{HOME}/bar), }"
|
|
|
|
# verify slash filtering for mount rules
|
|
verify_binary_equality "'$p1'x'$p2' mount rules slash filtering" \
|
|
"/t { $p1 mount /dev/foo -> /mnt/bar, }" \
|
|
"/t { $p2 mount ///dev/foo -> /mnt/bar, }" \
|
|
"/t { $p2 mount /dev/foo -> /mnt//bar, }" \
|
|
"/t { $p2 mount /dev///foo -> ////mnt/bar, }" \
|
|
"@{MNT}=/mnt/
|
|
/t { $p2 mount /dev///foo -> @{MNT}/bar, }" \
|
|
"@{FOO}=/foo
|
|
/t { $p2 mount /dev//@{FOO} -> /mnt/bar, }"
|
|
|
|
# verify slash filtering for link rules
|
|
verify_binary_equality "'$p1'x'$p2' link rules slash filtering" \
|
|
"/t { $p1 link /dev/foo -> /mnt/bar, }" \
|
|
"/t { $p2 link ///dev/foo -> /mnt/bar, }" \
|
|
"/t { $p2 link /dev/foo -> /mnt//bar, }" \
|
|
"/t { $p2 link /dev///foo -> ////mnt/bar, }" \
|
|
"@{BAR}=/mnt/
|
|
/t { $p2 link /dev///foo -> @{BAR}/bar, }" \
|
|
"@{FOO}=/dev/
|
|
/t { $p2 link @{FOO}//foo -> /mnt/bar, }" \
|
|
"@{FOO}=/dev/
|
|
@{BAR}=/mnt/
|
|
/t { $p2 link @{FOO}/foo -> @{BAR}/bar, }"
|
|
|
|
verify_binary_equality "'$p1'x'$p2' attachment slash filtering" \
|
|
"/t /bin/foo { }" \
|
|
"/t /bin//foo { }" \
|
|
"@{BAR}=/bin/
|
|
/t @{BAR}/foo { }" \
|
|
"@{FOO}=/foo
|
|
/t /bin/@{FOO} { }" \
|
|
"@{BAR}=/bin/
|
|
@{FOO}=/foo
|
|
/t @{BAR}/@{FOO} { }"
|
|
|
|
# verify comment at end of variable assignment is not treated as a value
|
|
verify_binary_equality "comment at end of set var" \
|
|
"/t { /bin/ r, }" \
|
|
"@{BAR}=/bin/ #a tail comment
|
|
/t { @{BAR} r, }"
|
|
|
|
verify_binary_equality "value like comment at end of set var" \
|
|
"/t { /{bin/,#value} r, }" \
|
|
"@{BAR}=bin/ \#value
|
|
/t { /@{BAR} r, }"
|
|
|
|
|
|
# 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 "'$p1'x'$p2' mount specific deny doesn't affect non-overlapping" \
|
|
"/t { $p1 mount options=bind /e/ -> /**, }" \
|
|
"/t { $p2 audit deny mount /s/** -> /**,
|
|
mount options=bind /e/ -> /**, }"
|
|
|
|
if [ $fails -ne 0 ] || [ $errors -ne 0 ]
|
|
then
|
|
printf "ERRORS: %d\nFAILS: %d\n" $errors $fails 1>&2
|
|
exit $((fails + errors))
|
|
fi
|
|
|
|
|
|
## priority override equivalence tests
|
|
## compare single rule, to multi-rule profile where one rule overrides
|
|
## the other rule via priority.
|
|
|
|
|
|
verify_binary_equality "'$p1'x'$p2' dbus variable expansion, multiple values/rules" \
|
|
"/t { dbus (send, receive) path=/com/foo, }" \
|
|
"/t { $p1 dbus (send, receive) path=/com/foo, $p2 dbus (send, receive) path=/com/foo, }" \
|
|
"@{FOO}=foo
|
|
/t { $p1 dbus (send, receive) path=/com/@{FOO}, $p2 dbus (send, receive) path=/com/foo, }" \
|
|
|
|
verify_binary_equality "'$p1'x'$p2' dbus variable expansion, ensure rule de-duping occurs" \
|
|
"/t { $p1 dbus (send, receive) path=/com/foo, dbus (send, receive) path=/com/bar, }" \
|
|
"/t { $p2 dbus (send, receive) path=/com/foo, dbus (send, receive) path=/com/bar, dbus (send, receive) path=/com/bar, }" \
|
|
"@{FOO}=bar foo bar foo
|
|
/t { $p2 dbus (send, receive) path=/com/@{FOO}, }" \
|
|
"@{FOO}=bar foo bar foo
|
|
/t { $p2 dbus (send, receive) path=/com/@{FOO}, dbus (send, receive) path=/com/@{FOO}, }"
|
|
|
|
verify_binary_equality "'$p1'x'$p2' dbus minimization with all perms" \
|
|
"/t { $p1 dbus, }" \
|
|
"/t { $p2 dbus bus=session, $p2 dbus, }" \
|
|
"/t { $p2 dbus (send, receive, bind, eavesdrop), $p2 dbus, }"
|
|
|
|
verify_binary_equality "'$p1'x'$p2' dbus minimization with bind" \
|
|
"/t { $p1 dbus bind, }" \
|
|
"/t { $p2 dbus bind bus=session, $p2 dbus bind, }" \
|
|
"/t { $p2 dbus bind bus=system name=com.foo, $p2 dbus bind, }"
|
|
|
|
verify_binary_equality "'$p1'x'$p2' dbus minimization with send and a bus conditional" \
|
|
"/t { $p1 dbus send bus=system, }" \
|
|
"/t { $p2 dbus send bus=system path=/com/foo interface=com.foo member=bar, dbus send bus=system, }" \
|
|
"/t { $p2 dbus send bus=system peer=(label=/usr/bin/foo), $p2 dbus send bus=system, }"
|
|
|
|
verify_binary_equality "'$p1'x'$p2' dbus minimization with an audit modifier" \
|
|
"/t { $p1 audit dbus eavesdrop, }" \
|
|
"/t { $p2 audit dbus eavesdrop bus=session, $p2 audit dbus eavesdrop, }"
|
|
|
|
verify_binary_equality "'$p1'x'$p2' dbus minimization with a deny modifier" \
|
|
"/t { $p1 deny dbus send bus=system peer=(name=com.foo), }" \
|
|
"/t { $p2 deny dbus send bus=system peer=(name=com.foo label=/usr/bin/foo), $p2 deny dbus send bus=system peer=(name=com.foo), }" \
|
|
|
|
verify_binary_equality "'$p1'x'$p2' dbus minimization found in dbus abstractions" \
|
|
"/t { $p1 dbus send bus=session, }" \
|
|
"/t { $p2 dbus send
|
|
bus=session
|
|
path=/org/freedesktop/DBus
|
|
interface=org.freedesktop.DBus
|
|
member={Hello,AddMatch,RemoveMatch,GetNameOwner,NameHasOwner,StartServiceByName}
|
|
peer=(name=org.freedesktop.DBus),
|
|
$p2 dbus send bus=session, }"
|
|
|
|
# verify slash filtering for dbus paths.
|
|
verify_binary_equality "'$p1'x'$p2' dbus slash filtering for paths" \
|
|
"/t { $p1 dbus (send, receive) path=/com/foo, dbus (send, receive) path=/com/bar, }" \
|
|
"/t { $p2 dbus (send, receive) path=/com///foo, dbus (send, receive) path=///com/bar, }" \
|
|
"/t { $p2 dbus (send, receive) path=/com//{foo,bar}, }" \
|
|
"/t { $p2 dbus (send, receive) path={//com/foo,/com//bar}, }" \
|
|
"@{FOO}=/foo
|
|
/t { $p2 dbus (send, receive) path=/com/@{FOO}, $p2 dbus (send, receive) path=/com/bar, }" \
|
|
"@{FOO}=/foo /bar
|
|
/t { $p2 dbus (send, receive) path=/com/@{FOO}, }" \
|
|
"@{FOO}=/bar //foo
|
|
/t { $p2 dbus (send, receive) path=/com/@{FOO}, }" \
|
|
"@{FOO}=//{bar,foo}
|
|
/t { $p2 dbus (send, receive) path=/com/@{FOO}, }" \
|
|
"@{FOO}=/foo
|
|
@{BAR}=bar
|
|
/t { $p2 dbus (send, receive) path=/com/@{FOO}, $p2 dbus (send, receive) path=/com//@{BAR}, }"
|
|
|
|
|
|
|
|
#### end of wrapper fn
|
|
}
|
|
|
|
|
|
run_tests()
|
|
{
|
|
printf "Equality Tests:\n"
|
|
|
|
#rules that don't support priority
|
|
|
|
# verify rlimit data conversions
|
|
verify_binary_equality "set rlimit rttime <= 12 weeks" \
|
|
"/t { set rlimit rttime <= 12 weeks, }" \
|
|
"/t { set rlimit rttime <= $((12 * 7)) days, }" \
|
|
"/t { set rlimit rttime <= $((12 * 7 * 24)) hours, }" \
|
|
"/t { set rlimit rttime <= $((12 * 7 * 24 * 60)) minutes, }" \
|
|
"/t { set rlimit rttime <= $((12 * 7 * 24 * 60 * 60)) seconds, }" \
|
|
"/t { set rlimit rttime <= $((12 * 7 * 24 * 60 * 60 * 1000)) ms, }" \
|
|
"/t { set rlimit rttime <= $((12 * 7 * 24 * 60 * 60 * 1000 * 1000)) us, }" \
|
|
"/t { set rlimit rttime <= $((12 * 7 * 24 * 60 * 60 * 1000 * 1000)), }"
|
|
|
|
verify_binary_equality "set rlimit cpu <= 42 weeks" \
|
|
"/t { set rlimit cpu <= 42 weeks, }" \
|
|
"/t { set rlimit cpu <= $((42 * 7)) days, }" \
|
|
"/t { set rlimit cpu <= $((42 * 7 * 24)) hours, }" \
|
|
"/t { set rlimit cpu <= $((42 * 7 * 24 * 60)) minutes, }" \
|
|
"/t { set rlimit cpu <= $((42 * 7 * 24 * 60 * 60)) seconds, }" \
|
|
"/t { set rlimit cpu <= $((42 * 7 * 24 * 60 * 60)), }"
|
|
|
|
verify_binary_equality "set rlimit memlock <= 2GB" \
|
|
"/t { set rlimit memlock <= 2GB, }" \
|
|
"/t { set rlimit memlock <= $((2 * 1024)) MB, }" \
|
|
"/t { set rlimit memlock <= $((2 * 1024 * 1024)) KB, }" \
|
|
"/t { set rlimit memlock <= $((2 * 1024 * 1024 * 1024)) , }"
|
|
|
|
run_port_range=$(kernel_features network_v8/af_inet)
|
|
if [ "$run_port_range" != "true" ]; then
|
|
echo -e "\nSkipping network af_inet tests. $run_port_range\n"
|
|
else
|
|
# network port range
|
|
# select features file that contains netv8 af_inet
|
|
features_file="features.af_inet"
|
|
verify_binary_equality "network port range" \
|
|
"/t { network port=3456-3460, }" \
|
|
"/t { network port=3456, \
|
|
network port=3457, \
|
|
network port=3458, \
|
|
network port=3459, \
|
|
network port=3460, }"
|
|
|
|
verify_binary_equality "network peer port range" \
|
|
"/t { network peer=(port=3456-3460), }" \
|
|
"/t { network peer=(port=3456), \
|
|
network peer=(port=3457), \
|
|
network peer=(port=3458), \
|
|
network peer=(port=3459), \
|
|
network peer=(port=3460), }"
|
|
|
|
verify_binary_inequality "network port range allows more than single port" \
|
|
"/t { network port=3456-3460, }" \
|
|
"/t { network port=3456, }"
|
|
|
|
verify_binary_inequality "network peer port range allows more than single port" \
|
|
"/t { network peer=(port=3456-3460), }" \
|
|
"/t { network peer=(port=3456), }"
|
|
# return to default
|
|
features_file=$default_features_file
|
|
fi
|
|
|
|
# Equality tests that set explicit priority level
|
|
# TODO: priority handling for file paths is currently broken
|
|
|
|
# This test is not actually correct due to two subtle
|
|
# interactions: - /* is special-cased to expand to /[^/\x00]+
|
|
# with at least one character - Quieting of [^a] in the DFA is
|
|
# different and cannot be manually fixed
|
|
|
|
#verify_binary_xequality "file rule carveout regex vs priority" \
|
|
# "/t { deny /[^a]* rwxlk, /a r, }" \
|
|
# "/t { priority=-1 deny /* rwxlk, /a r, }" \
|
|
|
|
# Not grouping all three together because parser correctly handles
|
|
# the equivalence of carveout regex and default audit deny
|
|
verify_binary_equality "file rule carveout regex vs priority (audit)" \
|
|
"/t { audit deny /[^a]* rwxlk, /a r, }" \
|
|
"/t { priority=-1 audit deny /* rwxlk, /a r, }"
|
|
|
|
verify_binary_equality "file rule default audit deny vs audit priority carveout" \
|
|
"/t { /a r, }" \
|
|
"/t { priority=-1 audit deny /* rwxlk, /a r, }"
|
|
|
|
# verify combinations of different priority levels
|
|
# for single rule comparisons, rules should keep same expected result
|
|
# even when the priorities are different.
|
|
# different priorities within a profile comparison resulting in
|
|
# different permission could affected expected results
|
|
|
|
|
|
priorities="none 0 1 -1"
|
|
|
|
for pri1 in $priorities ; do
|
|
if [ "$pri1" = "none" ] ; then
|
|
priority1=""
|
|
else
|
|
priority1="priority=$pri1"
|
|
fi
|
|
for pri2 in $priorities ; do
|
|
if [ "$pri2" = "none" ] ; then
|
|
priority2=""
|
|
else
|
|
priority2="priority=$pri2"
|
|
fi
|
|
|
|
verify_set "$priority1" "$priority2"
|
|
done
|
|
done
|
|
|
|
[ -z "${verbose}" ] && printf "\n"
|
|
printf "PASS\n"
|
|
exit 0
|
|
}
|
|
|
|
|
|
usage()
|
|
{
|
|
local progname="$0"
|
|
local rc="$1"
|
|
local msg="usage: ${progname} [Options]
|
|
|
|
Run the equality tests if no options given, otherwise run as directed
|
|
by the options.
|
|
|
|
Options:
|
|
-h, --help display this help
|
|
-e base args run an equality test on the following args
|
|
-n base args run an inequality test on the following args
|
|
-xequality run a known proble equality test
|
|
-xinequality run a known proble inequality test
|
|
-r on failure retain failed test output and abort
|
|
-d include dfa dumps with failed test output
|
|
-f arg features file to use
|
|
-p arg parser to invoke
|
|
--description description to print with test
|
|
-v verbose
|
|
examples:
|
|
$ equality.sh
|
|
...
|
|
$ equality.sh -r
|
|
....
|
|
inary equality 'priority=1'x'' Exec perm \"ux\" - most specific match: same as glob
|
|
FAIL: Hash values do not match
|
|
parser: ../apparmor_parser --config-file=./parser.conf --features-file=./features_files/features.all
|
|
known-good (0344cd377ccb239aba4cce768b818010961d68091d8c7fae72c755cfcb48d4a2) != profile-under-test (33fdf4575322a036c2acb75f93a7154179036f1189ef68ab9f1ae98e7f865780) for the following profiles:
|
|
known-good /t { priority=1 /* ux, /f px -> b, }
|
|
profile-under-test /t { /* ux, }
|
|
|
|
$ equality.sh -e \"/t { priority=1 /* Px -> b, /f Px, }\" \"/t { /* Px, }\"
|
|
$ equality.sh -e \"/t { priority=1 /* Px -> b, /f Px, }\" \"/t { /* Px, }\""
|
|
|
|
echo "$msg"
|
|
}
|
|
|
|
|
|
POSITIONAL_ARGS=()
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
-e|--equality)
|
|
testtype="equality"
|
|
shift # past argument
|
|
;;
|
|
--xequality)
|
|
testtype="xequality"
|
|
shift # past argument
|
|
;;
|
|
-n|--inequality)
|
|
testtype="inequality"
|
|
shift # past argument
|
|
;;
|
|
--xinequality)
|
|
testtype="xinequality"
|
|
shift # past argument
|
|
;;
|
|
-d|--dfa)
|
|
dumpdfa=1
|
|
shift # past argument
|
|
;;
|
|
-r|--retain)
|
|
retain=1
|
|
shift # past argument
|
|
;;
|
|
-v|--verbose)
|
|
verbos=1
|
|
shift # past argument
|
|
;;
|
|
-f|--feature-file)
|
|
features_file="$2"
|
|
shift # past argument
|
|
shift # past option
|
|
;;
|
|
--description)
|
|
description="$2"
|
|
shift # past argument
|
|
shift # past option
|
|
;;
|
|
-p|--parser)
|
|
APPARMOR_PARSER="$2"
|
|
shift # past argument
|
|
shift # past option
|
|
;;
|
|
-*|--*)
|
|
echo "Unknown option $1"
|
|
exit 1
|
|
;;
|
|
*)
|
|
POSITIONAL_ARGS+=("$1") # save positional arg
|
|
shift # past argument
|
|
;;
|
|
esac
|
|
done
|
|
|
|
set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters
|
|
|
|
if [ $# -eq 0 -o -z $testtype] ; then
|
|
run_tests "$@"
|
|
exit $?
|
|
fi
|
|
|
|
for profile in "$@" ; do
|
|
verify_binary "$testtype" "$description" "$known" "$profile"
|
|
done
|