mirror of
https://gitlab.com/apparmor/apparmor.git
synced 2025-03-04 00:14:44 +01:00

The current behavior of priority rules can be non-intuitive with higher priority rules completely overriding lower priority rules even in permissions not held in common. This behavior does have use cases but its can be very confusing, and does not normal policy behavior Eg. priority=0 allow r /**, priority=1 deny w /**, will result in no allowed permissions even though the deny rule is only removing the w permission, beause the higher priority rule completely over ride lower priority permissions sets (including none shared permissions). Instead move to tracking the priority at a per permission level. This allows the w permission to still override at priority 1, while the read permission is allowed at priority 0. The final constructed state will still drop priority for the final permission set on the state. Note: this patch updates the equality tests for the cases where the complete override behavior was being tested for. The complete override behavior will be reintroduced in a future patch with a keyword extension, enabling that behavior to be used for ordered blocks etc. Signed-off-by: John Johansen <john.johansen@canonical.com>
1228 lines
47 KiB
Bash
Executable file
1228 lines
47 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=$(sha256sum "${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 two none overlapping x rules to add
|
|
# xtable entries
|
|
# /c -> /t//b, for cx rules being converted to px -> /t//b
|
|
# /a -> b, for px rules
|
|
# the rules must come last guarantee xtable order
|
|
if [ "$perm1" == "$perm2" ] || priority_gt "$p1" "" ; then
|
|
verify_binary_equality "'$p1'x'$p2' Exec perm \"${perm1}\" - most specific match: same as glob" \
|
|
"/t { $p1 /f* ${perm1}, /f ${perm2}, /a px -> b, /c px -> /t//b, }" \
|
|
"/t { $p2 /f* ${perm1}, /a px -> b, /c px -> /t//b, }"
|
|
else
|
|
verify_binary_inequality "'$p1'x'$p2' Exec \"${perm1}\" vs \"${perm2}\" - most specific match: different from glob" \
|
|
"/t { $p1 /f* ${perm1}, /f ${perm2}, /a px -> b, /c px -> /t//b, }" \
|
|
"/t { $p2 /f* ${perm1}, /a px -> b, /c px -> /t//b, }"
|
|
fi
|
|
done
|
|
if priority_gt "$p1" "" ; then
|
|
# priority stops permission carve out
|
|
verify_binary_equality "'$p1'x'$p2' Exec \"${perm1}\" vs deny x - most specific match: different from glob" \
|
|
"/t { $p1 /* ${perm1}, audit deny /f x, }" \
|
|
"/t { $p2 /* ${perm1}, }"
|
|
else
|
|
# deny rule carves out some of the match
|
|
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}, }"
|
|
fi
|
|
|
|
done
|
|
|
|
#Test deny carves out permission
|
|
if priority_gt "$p1" "" ; then
|
|
verify_binary_equality "'$p1'x'$p2' Deny removes r perm" \
|
|
"/t { $p1 /foo/[abc] r, audit deny /foo/b r, }" \
|
|
"/t { $p2 /foo/[abc] r, }"
|
|
|
|
verify_binary_inequality "'$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, }"
|
|
elif priority_eq "$p1" "" ; then
|
|
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, }"
|
|
else
|
|
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, }"
|
|
fi
|
|
|
|
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_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, }}"
|
|
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
|