apparmor/parser/rc.apparmor.functions

337 lines
8.8 KiB
Text
Raw Normal View History

#!/bin/sh
# ----------------------------------------------------------------------
# Copyright (c) 1999-2008 NOVELL (All rights reserved)
# Copyright (c) 2009-2018 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 Novell, Inc.
# ----------------------------------------------------------------------
# rc.apparmor.functions by Steve Beattie
#
# NOTE: rc.apparmor initscripts that source this file need to implement
# the following set of functions:
# aa_action
# aa_log_action_start
# aa_log_action_end
# aa_log_success_msg
# aa_log_warning_msg
# aa_log_failure_msg
# aa_log_skipped_msg
# aa_log_daemon_msg
# aa_log_end_msg
# Some nice defines that we use
PARSER=/sbin/apparmor_parser
PARSER_OPTS=
# Suppress warnings when booting in quiet mode
if [ "${QUIET:-no}" = yes ] || [ "${quiet:-n}" = y ]; then
PARSER_OPTS="$PARSER_OPTS --quiet"
fi
if [ -d /etc/apparmor.d ] ; then
PROFILE_DIRS=/etc/apparmor.d
else
aa_log_warning_msg "Unable to find profiles directory, installation problem?"
fi
# Eg. snapd policy might need this on some systems if loading policy
# during early boot if not using the snapd unit file
ADDITIONAL_PROFILE_DIR=
if [ -n "$ADDITIONAL_PROFILE_DIR" ] && [ -d "$ADDITIONAL_PROFILE_DIR" ]; then
PROFILE_DIRS="$PROFILE_DIRS $ADDITIONAL_PROFILE_DIR"
fi
AA_STATUS=/usr/sbin/aa-status
SECURITYFS=/sys/kernel/security
SFS_MOUNTPOINT="${SECURITYFS}/apparmor"
# keep exit status from parser during profile load. 0 is good, 1 is bad
STATUS=0
# Test if the apparmor "module" is present.
is_apparmor_present() {
[ -d /sys/module/apparmor ]
}
# Checks to see if the current container is capable of having internal AppArmor
# profiles that should be loaded. Callers of this function should have already
# verified that they're running inside of a container environment with
# something like `systemd-detect-virt --container`.
#
# The only known container environments capable of supporting internal policy
# are LXD and LXC environments, and Windows Subsystem for Linux.
#
# Returns 0 if the container environment is capable of having its own internal
# policy and non-zero otherwise.
#
# IMPORTANT: This function will return 0 in the case of a non-LXD/non-LXC
# system container technology being nested inside of a LXD/LXC container that
# utilized an AppArmor namespace and profile stacking. The reason 0 will be
# returned is because .ns_stacked will be "yes" and .ns_name will still match
# "lx[dc]-*" since the nested system container technology will not have set up
# a new AppArmor profile namespace. This will result in the nested system
# container's boot process to experience failed policy loads but the boot
# process should continue without any loss of functionality. This is an
# unsupported configuration that cannot be properly handled by this function.
is_container_with_internal_policy() {
set SFS_MOUNTPOINT in is_container_with_internal_policy() is_container_with_internal_policy() is called independently of apparmor_*() in the systemd unit and potentially other consumers of rc.apparmor.functions. When the unit and rc.apparmor.functions functions were rewritten, they were written so that SFS_MOUNTPOINT was only set in is_apparmor_loaded(), but this is only called in apparmor_start(), remove_profiles(), apparmor_kill(), apparmor_restart(), apparmor_try_restart() and apparmor_status() and not is_container_with_internal_policy(). While it is clear that is_container_with_internal_policy() is meant to be called before apparmor_start(), is is unclear why SFS_MOUNTPOINT is only defined in is_apparmor_loaded(). There are several ways to fix this: 1. update is_container_with_internal_policy() to call is_apparmor_loaded() 2. identify the callers of is_container_with_internal_policy() and have them call is_apparmor_loaded() 3. reorganize the code to remove duplicate calls and assignments 4. define SFS_MOUNTPOINT along with SECURITYFS and MODULE, at the top level 5. also define SFS_MOUNTPOINT in is_container_with_internal_policy() '1' would result in redundant calls in many common cases since the systemd unit would call is_apparmor_loaded() both in is_container_with_internal_policy() and prior to other calls. '2' would like break consumers of rc.apparmor.funcions, like Debian/Ubuntu's profile-load. '3' is perhaps ok, but requires more effort and is regression-prone. '4' seems the simplest, most correct fix '5' is what this patch implements, which is as simple as '4' but tries to maintain the original author's intent of when to set SFS_MOUNTPOINT. PR: https://gitlab.com/apparmor/apparmor/merge_requests/363 Signed-off-by: Jamie Strandboge <jamie@strandboge.com> Signed-off-by: John Johansen <john.johansen@canonical.com>
2019-04-15 16:42:15 -05:00
# this function is sometimes called independently of
# is_apparmor_loaded(), so also define this here.
local ns_stacked_path="${SFS_MOUNTPOINT}/.ns_stacked"
local ns_name_path="${SFS_MOUNTPOINT}/.ns_name"
local ns_stacked
local ns_name
# WSL needs to be detected explicitly
if [ "$(systemd-detect-virt --container)" = "wsl" ]; then
return 0
fi
if ! [ -f "$ns_stacked_path" ] || ! [ -f "$ns_name_path" ]; then
return 1
fi
read -r ns_stacked < "$ns_stacked_path"
if [ "$ns_stacked" != "yes" ]; then
return 1
fi
# LXD and LXC set up AppArmor namespaces starting with "lxd-" and
# "lxc-", respectively. Return non-zero for all other namespace
# identifiers.
read -r ns_name < "$ns_name_path"
if [ "${ns_name#lxd-*}" = "$ns_name" ] && \
[ "${ns_name#lxc-*}" = "$ns_name" ]; then
return 1
fi
return 0
}
__parse_profiles_dir() {
local parser_cmd="$1"
local profile_dir="$2"
local status=0
if [ ! -d "$profile_dir" ]; then
aa_log_failure_msg "Profile directory not found: $profile_dir"
return 1
fi
if [ -z "$(ls "$profile_dir"/)" ]; then
aa_log_failure_msg "No profiles found in $profile_dir"
2006-08-04 17:16:47 +00:00
return 1
fi
# shellcheck disable=SC2086
if ! "$PARSER" $PARSER_OPTS "$parser_cmd" -- "$profile_dir"; then
status=1
aa_log_failure_msg "At least one profile failed to load"
fi
return "$status"
}
parse_profiles() {
# get parser arg
case "$1" in
load)
PARSER_CMD="--add"
PARSER_MSG="Loading AppArmor profiles "
;;
reload)
PARSER_CMD="--replace"
PARSER_MSG="Reloading AppArmor profiles "
;;
*)
aa_log_failure_msg "required 'load' or 'reload'"
exit 1
;;
esac
aa_log_action_start "$PARSER_MSG"
# run the parser on all of the apparmor profiles
if [ ! -f "$PARSER" ]; then
aa_log_failure_msg "AppArmor parser not found"
exit 1
fi
for profile_dir in $PROFILE_DIRS; do
__parse_profiles_dir "$PARSER_CMD" "$profile_dir" || STATUS=$?
done
aa_log_action_end "$STATUS"
return "$STATUS"
}
is_apparmor_loaded() {
if ! is_securityfs_mounted ; then
mount_securityfs
fi
if [ -f "${SFS_MOUNTPOINT}/profiles" ]; then
return 0
fi
is_apparmor_present
return $?
}
is_securityfs_mounted() {
test -d "$SECURITYFS" -a -d /sys/fs/cgroup/systemd || grep -q securityfs /proc/filesystems && grep -q securityfs /proc/mounts
return $?
}
mount_securityfs() {
if grep -q securityfs /proc/filesystems ; then
aa_action "Mounting securityfs on $SECURITYFS" \
mount -t securityfs securityfs "$SECURITYFS"
return $?
fi
return 0
}
apparmor_start() {
aa_log_daemon_msg "Starting AppArmor"
if ! is_apparmor_present ; then
aa_log_failure_msg "Starting AppArmor - failed, To enable AppArmor, ensure your kernel is configured with CONFIG_SECURITY_APPARMOR=y then add 'security=apparmor apparmor=1' to the kernel command line"
aa_log_end_msg 1
return 1
elif ! is_apparmor_loaded ; then
aa_log_failure_msg "Starting AppArmor - AppArmor control files aren't available under /sys/kernel/security/, please make sure securityfs is mounted."
aa_log_end_msg 1
return 1
fi
if [ ! -w "$SFS_MOUNTPOINT/.load" ] ; then
aa_log_failure_msg "Loading AppArmor profiles - failed, Do you have the correct privileges?"
aa_log_end_msg 1
return 1
fi
# if there is anything in the profiles file don't load
if ! read -r _line < "$SFS_MOUNTPOINT/profiles"; then
parse_profiles load
else
aa_log_skipped_msg ": already loaded with profiles."
return 0
fi
aa_log_end_msg 0
return 0
}
remove_profiles() {
# removing profiles as we directly read from apparmorfs
# doesn't work, since we are removing entries which screws up
# our position. Lets hope there are never enough profiles to
# overflow the variable
if ! is_apparmor_loaded ; then
aa_log_failure_msg "AppArmor module is not loaded"
return 1
fi
if [ ! -w "$SFS_MOUNTPOINT/.remove" ] ; then
aa_log_failure_msg "Root privileges not available"
return 1
fi
if [ ! -x "$PARSER" ] ; then
aa_log_failure_msg "Unable to execute AppArmor parser"
return 1
fi
retval=0
Attached is a patch to make the initscript not fail if /tmp is full by converting the comm(1) usage on temporary files to an embedded awk script. On both Ubuntu and OpenSUSE, a version of awk (mawk in Ubuntu, gawk in OpenSUSE) is either a direct or indirect dependency on the minimal or base package set, and the original reporter also mentioned that an awk-based solution would be palatable in a way that converting to bash, or using perl or python here would not be. In the embedded awk script, I've tried to avoid gawk or mawk specific behaviors or extensions; e.g. this is the reason for the call to sort on the output of the awk script, rather than using gawk's asort(). But please let me know if you see anything that shouldn't be portable across awk implementations. An additional issue that is fixed in both scripts is handling child profiles (e.g. hats) during reload. If child profiles are filtered out (via grep -v '//') of the list to consider, then on reloading a profile where a child profile has been removed or renamed, that child profile will continue to stick around. However, if the profile containing child profiles is removed entirely, if the initscript attempts to unload the child profiles after the parent is removed, this will fail because they were unloaded when the parent was unloaded. Thus I removed any filtering of child profiles out, but do a post-awk reverse sort which guarantees that any child profiles will be removed before their parent is. I also added the LC_COLLATE=C (based on the Ubuntu version) to the sort call to ensure a consistent sort order. To restate, the problem with the existing code is that it creates temporary files in $TMPDIR (by default /tmp) and if that partition is full, problems with the reload action ensue. Alternate solutions include switching the initscript to use bash and its <$() extension or setting TMPDIR to /dev/shm/. The former is unpalatable to some (particularly for an initscript), and for the latter, /dev/shm is only guaranteed to exist on GNU libc based systems (glibc apparently expects /dev/shm to exist for its POSIX shared memory implementation; see shm_overview(7)). So to me, awk (sans GNU extensions) looks to be the least bad option here. Bug: https://launchpad.net/bugs/775785
2011-08-26 15:55:43 -07:00
# We filter child profiles as removing the parent will remove
# the children
sed -e "s/ (\(enforce\|complain\))$//" "$SFS_MOUNTPOINT/profiles" | \
LC_COLLATE=C sort | grep -v // | {
while read -r profile ; do
printf "%s" "$profile" > "$SFS_MOUNTPOINT/.remove"
rc=$?
if [ "$rc" -ne 0 ] ; then
retval=$rc
fi
done
return "$retval"
}
}
apparmor_stop() {
aa_log_daemon_msg "Unloading AppArmor profiles"
remove_profiles
rc=$?
aa_log_end_msg "$rc"
return "$rc"
}
apparmor_kill() {
if ! is_apparmor_loaded ; then
aa_log_failure_msg "AppArmor module is not loaded"
return 1
fi
aa_log_failure_msg "apparmor_kill() is no longer supported because AppArmor can't be built as a module"
return 1
}
__apparmor_restart() {
if [ ! -w "$SFS_MOUNTPOINT/.load" ] ; then
aa_log_failure_msg "Loading AppArmor profiles - failed, Do you have the correct privileges?"
return 4
fi
aa_log_daemon_msg "Restarting AppArmor"
parse_profiles reload
Attached is a patch to make the initscript not fail if /tmp is full by converting the comm(1) usage on temporary files to an embedded awk script. On both Ubuntu and OpenSUSE, a version of awk (mawk in Ubuntu, gawk in OpenSUSE) is either a direct or indirect dependency on the minimal or base package set, and the original reporter also mentioned that an awk-based solution would be palatable in a way that converting to bash, or using perl or python here would not be. In the embedded awk script, I've tried to avoid gawk or mawk specific behaviors or extensions; e.g. this is the reason for the call to sort on the output of the awk script, rather than using gawk's asort(). But please let me know if you see anything that shouldn't be portable across awk implementations. An additional issue that is fixed in both scripts is handling child profiles (e.g. hats) during reload. If child profiles are filtered out (via grep -v '//') of the list to consider, then on reloading a profile where a child profile has been removed or renamed, that child profile will continue to stick around. However, if the profile containing child profiles is removed entirely, if the initscript attempts to unload the child profiles after the parent is removed, this will fail because they were unloaded when the parent was unloaded. Thus I removed any filtering of child profiles out, but do a post-awk reverse sort which guarantees that any child profiles will be removed before their parent is. I also added the LC_COLLATE=C (based on the Ubuntu version) to the sort call to ensure a consistent sort order. To restate, the problem with the existing code is that it creates temporary files in $TMPDIR (by default /tmp) and if that partition is full, problems with the reload action ensue. Alternate solutions include switching the initscript to use bash and its <$() extension or setting TMPDIR to /dev/shm/. The former is unpalatable to some (particularly for an initscript), and for the latter, /dev/shm is only guaranteed to exist on GNU libc based systems (glibc apparently expects /dev/shm to exist for its POSIX shared memory implementation; see shm_overview(7)). So to me, awk (sans GNU extensions) looks to be the least bad option here. Bug: https://launchpad.net/bugs/775785
2011-08-26 15:55:43 -07:00
rc=$?
aa_log_end_msg "$rc"
return "$rc"
}
apparmor_restart() {
if ! is_apparmor_loaded ; then
apparmor_start
rc=$?
return "$rc"
fi
__apparmor_restart
return $?
}
apparmor_try_restart() {
if ! is_apparmor_loaded ; then
return 0
fi
__apparmor_restart
return $?
}
apparmor_status () {
if test -x "$AA_STATUS" ; then
"$AA_STATUS" --verbose
return $?
fi
if ! is_apparmor_loaded ; then
echo "AppArmor is not loaded."
rc=1
else
echo "AppArmor is enabled."
rc=0
fi
echo "Install the apparmor-utils package to receive more detailed"
echo "status information here (or examine $SFS_MOUNTPOINT directly)."
return "$rc"
}