apparmor/parser/rc.apparmor.functions
Steve Beattie 3186b09035 Merge from trunk revision 1805:
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.

Nominated-By: Steve Beattie <sbeattie@ubuntu.com>
Acked-By: John Johansen <john.johansen@canonical.com>

Bug: https://launchpad.net/bugs/775785
2011-08-26 16:03:03 -07:00

535 lines
13 KiB
Bash

#!/bin/sh
# ----------------------------------------------------------------------
# Copyright (c) 1999-2008 NOVELL (All rights reserved)
# Copyright (c) 2009-2011 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
CONFIG_DIR=/etc/apparmor
MODULE=apparmor
OLD_MODULE=subdomain
if [ -f "${CONFIG_DIR}/${MODULE}.conf" ] ; then
APPARMOR_CONF="${CONFIG_DIR}/${MODULE}.conf"
elif [ -f "${CONFIG_DIR}/${OLD_MODULE}.conf" ] ; then
APPARMOR_CONF="${CONFIG_DIR}/${OLD_MODULE}.conf"
elif [ -f "/etc/immunix/subdomain.conf" ] ; then
aa_log_warning_msg "/etc/immunix/subdomain.conf is deprecated, use ${CONFIG_DIR}/subdomain.conf instead"
APPARMOR_CONF="/etc/immunix/subdomain.conf"
elif [ -f "/etc/subdomain.conf" ] ; then
aa_log_warning_msg "/etc/subdomain.conf is deprecated, use ${CONFIG_DIR}/subdomain.conf instead"
APPARMOR_CONF="/etc/subdomain.conf"
else
aa_log_warning_msg "Unable to find config file in ${CONFIG_DIR}, installation problem?"
fi
# Read configuration options from /etc/subdomain.conf, default is to
# warn if subdomain won't load.
SUBDOMAIN_MODULE_PANIC="warn"
SUBDOMAIN_ENABLE_OWLSM="no"
APPARMOR_ENABLE_AAEVENTD="no"
if [ -f "${APPARMOR_CONF}" ] ; then
#parse the conf file to see what we should do
. "${APPARMOR_CONF}"
fi
PARSER=/sbin/apparmor_parser
# SUBDOMAIN_DIR and APPARMOR_DIR might be defined in subdomain.conf|apparmor.conf
if [ -d "${APPARMOR_DIR}" ] ; then
PROFILE_DIR=${APPARMOR_DIR}
elif [ -d "${SUBDOMAIN_DIR}" ] ; then
PROFILE_DIR=${SUBDOMAIN_DIR}
elif [ -d /etc/apparmor.d ] ; then
PROFILE_DIR=/etc/apparmor.d
elif [ -d /etc/subdomain.d ] ; then
PROFILE_DIR=/etc/subdomain.d
fi
ABSTRACTIONS="-I${PROFILE_DIR}"
AA_EV_BIN=/usr/sbin/aa-eventd
AA_EV_PIDFILE=/var/run/aa-eventd.pid
AA_STATUS=/usr/sbin/aa-status
SD_EV_BIN=/usr/sbin/sd-event-dispatch.pl
SD_EV_PIDFILE=/var/run/sd-event-dispatch.init.pid
SD_STATUS=/usr/sbin/subdomain_status
SECURITYFS=/sys/kernel/security
SUBDOMAINFS_MOUNTPOINT=$(grep subdomainfs /etc/fstab | \
sed -e 's|^[[:space:]]*[^[:space:]]\+[[:space:]]\+\(/[^[:space:]]*\)[[:space:]]\+subdomainfs.*$|\1|' 2> /dev/null)
# 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() {
local modules=$1
shift
while [ $# -gt 0 ] ; do
modules="$modules|$1"
shift
done
# check for subdomainfs version of module
grep -qE "^($modules)[[:space:]]" /proc/modules
if [ $? -ne 0 ] ; then
ls /sys/module/apparmor 2>/dev/null | grep -qE "^($modules)"
fi
return $?
}
# This set of patterns to skip needs to be kept in sync with
# AppArmor.pm::isSkippableFile()
# returns 0 if profile should NOT be skipped
# returns 1 on verbose skip
# returns 2 on silent skip
skip_profile() {
local profile=$1
if [ "${profile%.rpmnew}" != "${profile}" -o \
"${profile%.rpmsave}" != "${profile}" -o \
-e "${PROFILE_DIR}/disable/`basename ${profile}`" -o \
"${profile%\~}" != "${profile}" ] ; then
return 1
fi
# Silently ignore the dpkg files
if [ "${profile%.dpkg-new}" != "${profile}" -o \
"${profile%.dpkg-old}" != "${profile}" -o \
"${profile%.dpkg-dist}" != "${profile}" -o \
"${profile%.dpkg-bak}" != "${profile}" ] ; then
return 2
fi
return 0
}
force_complain() {
local profile=$1
# if profile not in complain mode
if ! egrep -q "^/.*[ \t]+flags[ \t]*=[ \t]*\([ \t]*complain[ \t]*\)[ \t]+{" $profile ; then
local link="${PROFILE_DIR}/force-complain/`basename ${profile}`"
if [ -e "$link" ] ; then
aa_log_warning_msg "found $link, forcing complain mode"
return 0
fi
fi
return 1
}
parse_profiles() {
# get parser arg
case "$1" in
load)
PARSER_ARGS="--add"
PARSER_MSG="Loading AppArmor profiles "
;;
reload)
PARSER_ARGS="--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"
aa_log_action_end 1
exit 1
fi
if [ ! -d "$PROFILE_DIR" ]; then
aa_log_failure_msg "Profile directory not found"
aa_log_action_end 1
exit 1
fi
if [ -z "$(ls $PROFILE_DIR/)" ]; then
aa_log_failure_msg "No profiles found"
aa_log_action_end 1
return 1
fi
for profile in $PROFILE_DIR/*; do
skip_profile "${profile}"
skip=$?
# Ignore skip status == 2 (silent skip)
if [ "$skip" -eq 1 ] ; then
aa_log_skipped_msg "$profile"
logger -t "AppArmor(init)" -p daemon.warn "Skipping profile $profile"
STATUS=2
continue
elif [ "$skip" -ne 0 ]; then
continue
fi
if [ -f "${profile}" ] ; then
COMPLAIN=""
if force_complain "${profile}" ; then
COMPLAIN="-C"
fi
$PARSER $ABSTRACTIONS $PARSER_ARGS $COMPLAIN "$profile" > /dev/null
if [ $? -ne 0 ]; then
aa_log_failure_msg "$profile failed to load"
STATUS=1
fi
fi
done
if [ $STATUS -eq 2 ]; then
STATUS=0
fi
aa_log_action_end "$STATUS"
return $STATUS
}
profiles_names_list() {
# run the parser on all of the apparmor profiles
if [ ! -f "$PARSER" ]; then
aa_log_failure_msg "- AppArmor parser not found"
exit 1
fi
if [ ! -d "$PROFILE_DIR" ]; then
aa_log_failure_msg "- Profile directory not found"
exit 1
fi
for profile in $PROFILE_DIR/*; do
if skip_profile "${profile}" && [ -f "${profile}" ] ; then
LIST_ADD=$($PARSER $ABSTRACTIONS -N "$profile" )
if [ $? -eq 0 ]; then
echo "$LIST_ADD"
fi
fi
done
}
failstop_system() {
level=$(runlevel | cut -d" " -f2)
if [ $level -ne "1" ] ; then
aa_log_failure_msg "- could not start AppArmor. Changing to runlevel 1"
telinit 1;
return -1;
fi
aa_log_failure_msg "- could not start AppArmor."
return -1
}
module_panic() {
# the module failed to load, determine what action should be taken
case "$SUBDOMAIN_MODULE_PANIC" in
"warn"|"WARN")
return 1 ;;
"panic"|"PANIC") failstop_system
rc=$?
return $rc ;;
*) aa_log_failure_msg "- invalid AppArmor module fail option"
return -1 ;;
esac
}
is_apparmor_loaded() {
if ! is_securityfs_mounted ; then
mount_securityfs
fi
mount_subdomainfs
if [ -f "${SECURITYFS}/${MODULE}/profiles" ]; then
SFS_MOUNTPOINT="${SECURITYFS}/${MODULE}"
return 0
fi
if [ -f "${SECURITYFS}/${OLD_MODULE}/profiles" ]; then
SFS_MOUNTPOINT="${SECURITYFS}/${OLD_MODULE}"
return 0
fi
if [ -f "${SUBDOMAINFS_MOUNTPOINT}/profiles" ]; then
SFS_MOUNTPOINT=${SUBDOMAINFS_MOUNTPOINT}
return 0
fi
# check for subdomainfs version of module
is_apparmor_present apparmor subdomain
return $?
}
is_securityfs_mounted() {
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
}
mount_subdomainfs() {
# for backwords compatibility
if grep -q subdomainfs /proc/filesystems && \
! grep -q subdomainfs /proc/mounts && \
[ -n "${SUBDOMAINFS_MOUNTPOINT}" ]; then
aa_action "Mounting subdomainfs on ${SUBDOMAINFS_MOUNTPOINT}" \
mount "${SUBDOMAINFS_MOUNTPOINT}"
return $?
fi
return 0
}
unmount_subdomainfs() {
SUBDOMAINFS=$(grep subdomainfs /proc/mounts | cut -d" " -f2 2> /dev/null)
if [ -n "${SUBDOMAINFS}" ]; then
aa_action "Unmounting subdomainfs" umount ${SUBDOMAINFS}
fi
}
load_module() {
local rc=0
if modinfo -F filename apparmor > /dev/null 2>&1 ; then
MODULE=apparmor
elif modinfo -F filename ${OLD_MODULE} > /dev/null 2>&1 ; then
MODULE=${OLD_MODULE}
fi
if ! is_apparmor_present apparmor subdomain ; then
aa_action "Loading AppArmor module" /sbin/modprobe -q $MODULE $1
rc=$?
if [ $rc -ne 0 ] ; then
module_panic
rc=$?
if [ $rc -ne 0 ] ; then
exit $rc
fi
fi
fi
if ! is_apparmor_loaded ; then
return 1
fi
return $rc
}
apparmor_start() {
aa_log_daemon_msg "Starting AppArmor"
if ! is_apparmor_loaded ; then
load_module
rc=$?
if [ $rc -ne 0 ] ; then
aa_log_end_msg $rc
return $rc
fi
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
configure_owlsm
# if there is anything in the profiles file don't load
cat "$SFS_MOUNTPOINT/profiles" | if ! read line ; then
parse_profiles load
else
aa_log_skipped_msg "AppArmor already loaded with profiles."
fi
aa_log_end_msg 0
return 0
}
remove_profiles() {
# removing profiles as we directly read from subdomainfs
# 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
# 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 profile ; do
echo -n "$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() {
aa_log_daemon_msg "Unloading AppArmor modules "
if ! is_apparmor_loaded ; then
aa_log_failure_msg "AppArmor module is not loaded"
return 1
fi
unmount_subdomainfs
if is_apparmor_present apparmor ; then
MODULE=apparmor
elif is_apparmor_present subdomain ; then
MODULE=subdomain
else
aa_log_failure_msg "AppArmor is builtin"
return 1
fi
/sbin/modprobe -qr $MODULE
rc=$?
aa_log_end_msg $rc
return $rc
}
__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
configure_owlsm
parse_profiles reload
# Clean out running profiles not associated with the current profile
# set, excluding the libvirt dynamically generated profiles.
# Note that we reverse sort the list of profiles to remove to
# ensure that child profiles (e.g. hats) are removed before the
# parent. We *do* need to remove the child profile and not rely
# on removing the parent profile when the profile has had its
# child profile names changed.
profiles_names_list | awk '
BEGIN {
while (getline < "'${SFS_MOUNTPOINT}'/profiles" ) {
str = sub(/ \((enforce|complain)\)$/, "", $0);
if (match($0, /^libvirt-[0-9a-f\-]+$/) == 0)
arr[$str] = $str
}
}
{ if (length(arr[$0]) > 0) { delete arr[$0] } }
END {
for (key in arr)
if (length(arr[key]) > 0) {
printf("%s\n", arr[key])
}
}
' | LC_COLLATE=C sort -r | while IFS= read profile ; do
echo -n "$profile" > "$SFS_MOUNTPOINT/.remove"
done
return 0
}
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 $?
}
configure_owlsm () {
if [ "${SUBDOMAIN_ENABLE_OWLSM}" = "yes" -a -f ${SFS_MOUNTPOINT}/control/owlsm ] ; then
# Sigh, the "sh -c" is necessary for the SuSE aa_action
# and it can't be abstracted out as a seperate function, as
# that breaks under RedHat's action, which needs a
# binary to invoke.
aa_action "Enabling OWLSM extension" sh -c "echo -n \"1\" > \"${SFS_MOUNTPOINT}/control/owlsm\""
elif [ -f "${SFS_MOUNTPOINT}/control/owlsm" ] ; then
aa_action "Disabling OWLSM extension" sh -c "echo -n \"0\" > \"${SFS_MOUNTPOINT}/control/owlsm\""
fi
}
apparmor_status () {
if test -x ${AA_STATUS} ; then
${AA_STATUS} --verbose
return $?
fi
if test -x ${SD_STATUS} ; then
${SD_STATUS} --verbose
return $?
fi
if ! is_apparmor_present apparmor subdomain ; 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
}