2
0
mirror of https://gitlab.com/apparmor/apparmor synced 2025-08-22 01:57:43 +00:00

Merge profiles: automate attachment-path checks via test_profile.py

Follow up to MR !1637

`make check-parser` in profiles now verifies that all profiles allow at
least a read access to their attachment path.

This is done with test_profile.py, more robust and therefore replacing
test_profile.sh.

Additionally, fix the permission of 3 profiles, that were not detected by
!1637 due to a bug in a regex

Signed-off-by: Maxime Bélair <maxime.belair@canonical.com>

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1657
Approved-by: Christian Boltz <apparmor@cboltz.de>
Merged-by: John Johansen <john@jjmx.net>
This commit is contained in:
John Johansen 2025-05-12 11:58:51 +00:00
commit 7e0bc91a37
3 changed files with 92 additions and 84 deletions

90
parser/tst/test_profile.py Executable file
View File

@ -0,0 +1,90 @@
#!/usr/bin/env python3
import sys
import subprocess
import re
import os
VERBOSE = bool(os.environ.get('VERBOSE'))
NAME_RE = re.compile(r'^Name:\s*(\S+)')
PERMS_ALL = re.compile(r'^Perms:.*r.*:.*:.*\(/\{?,?\*\*,?}?\)')
ATTACH_RE = re.compile(r'^Attachment:\s*(.+)')
RED = '\033[0;31m'
GREEN = '\033[0;32m'
NORMAL = '\033[0m'
def die(msg):
print(RED + msg + NORMAL, file=sys.stderr)
sys.exit(1)
def check_profile(name, prof_file, skip, attachment, lines):
if skip:
if VERBOSE:
print('Profile "{}" skipped: {}'.format(name, skip))
return
pat = re.compile(r'^Perms:.*r.*:.*:.*\({}\)'.format(re.escape(attachment)))
for l in lines:
if pat.match(l):
if VERBOSE:
print(GREEN + 'Profile {} ({}): OK "{}" found'.format(prof_file, name, attachment) + NORMAL)
return
die('Profile {} ({}): ERROR: no Perms rule for "{}".'.format(prof_file, name, attachment))
def process_profile(profile, extra_args):
cmd = ['../parser/apparmor_parser'] + extra_args + ['-d', profile]
proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
if proc.returncode != 0:
die('ERROR: Failed to parse "{}": {}'.format(profile, proc.stdout.strip()))
lines = proc.stdout.splitlines()
curr_name = ''
attachment = ''
skip = None
in_entries = False
block = []
for line in lines:
m = NAME_RE.match(line)
if m:
if curr_name:
# check previously found profile
check_profile(curr_name, profile, skip, attachment, block)
# remember newly found profile
curr_name = m.group(1)
attachment = ''
skip = None
in_entries = False
block = []
continue
if PERMS_ALL.match(line):
skip = 'All files available'
continue
m = ATTACH_RE.match(line)
if m:
attachment = m.group(1)
if attachment == '<NULL>':
skip = 'no attachment'
continue
if line.strip() == '--- Entries ---':
in_entries = True
continue
if in_entries:
block.append(line)
# Last profile
check_profile(curr_name, profile, skip, attachment, block)
if __name__ == '__main__':
if len(sys.argv) < 2:
print('Usage: {0} <profile-file> [parser_extra_args]'.format(sys.argv[0]))
sys.exit(1)
process_profile(sys.argv[1], sys.argv[2:])

View File

@ -1,84 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# Check if the current profile allow reading its attachment
check_entry() {
local prof_name="$1"
local -n lines_ref="$2"
local attachment="$3"
local found=0
for line in "${lines_ref[@]}"; do
if [[ $line == Perms:*r*:*"($attachment)"* ]]; then
found=1
break
fi
done
if [[ $found -eq 0 ]]; then
echo -e "\e[0;31mProfile $prof_name: ERROR: no Perms rule for '$attachment'.\e[0m"
exit 1
fi
[[ -n "${VERBOSE:-}" ]] && echo -e "\e[0;32mProfile $prof_name: OK '$attachment' found\e[0m" || true
}
# Handle the end of a profile block: either skip it or check for the entry.
finish_profile() {
local name="$1"
local prof_file="$2"
local skip="$3"
local attachment="$4"
local arr_name="$5"
if [[ -n $name ]]; then
if [[ $skip != 0 ]]; then
[[ -n "${VERBOSE:-}" ]] && echo "Profile '$name' skipped: $skip" || true
else
check_entry "$prof_file ($name)" "$arr_name" "$attachment"
fi
fi
}
process_profile() {
local prof_file="$1"
shift
local dump curr_name="" attachment="" skip_profile=0 in_entries=0
local block_lines=()
if ! dump=$(../parser/apparmor_parser $@ -d "$prof_file" 2>&1); then
echo "\e[0;31mERROR: Failed to parse '$prof_file': $dump\e[0m" >&2
exit 1
fi
IFS=$'\n' read -r -d '' -a lines < <(printf '%s\n' "$dump" && printf '\0')
for line in "${lines[@]}"; do
if [[ $line =~ ^[[:space:]]*Name:[[:space:]]*([^[:space:]]+) ]]; then
finish_profile "$curr_name" "$prof_file" "$skip_profile" "$attachment" block_lines
curr_name="${BASH_REMATCH[1]}"
attachment="" skip_profile=0 in_entries=0 block_lines=()
elif [[ $line =~ ^[[:space:]]*Mode:[[:space:]]*unconfined ]]; then
skip_profile="unconfined"
elif [[ $line =~ ^Perms:.*r.*:.*:.*\(/(\{?,?\*\*,*\}?)\) ]]; then
skip_profile="All files available"
elif [[ $line =~ ^[[:space:]]*Attachment:[[:space:]]*(.+) ]]; then
attachment="${BASH_REMATCH[1]}"
[[ $attachment == "<NULL>" ]] && skip_profile="no attachment"
elif [[ $line == ---\ Entries\ --- ]]; then
in_entries=1
elif [[ $in_entries -ne 0 ]]; then
block_lines+=("$line")
fi
done
# Last profile
finish_profile "$curr_name" "$prof_file" "$skip_profile" "$attachment" block_lines
}
if (( $# < 1 )); then
echo "Usage: $0 <profile-file> [parser_extra_args]"
exit 1
fi
process_profile $@

View File

@ -123,12 +123,14 @@ check-parser: test-dependencies
$(Q)for profile in $$(find ${PROFILES_SOURCE} -maxdepth 1 -type f) ; do \
[ -n "${VERBOSE}" ] && echo "Testing $${profile}" ; \
${PARSER} --config-file=../parser/tst/parser.conf -S -b ${PROFILES_SOURCE} $${profile} > /dev/null || exit 1; \
../parser/tst/test_profile.py $${profile} --config-file=../parser/tst/parser.conf -S -b ${PROFILES_SOURCE} || exit 1; \
done
@echo "*** Checking profiles from ${EXTRAS_SOURCE} against apparmor_parser"
$(Q)for profile in $$(find ${EXTRAS_SOURCE} -maxdepth 1 -type f -not -name README) ; do \
[ -n "${VERBOSE}" ] && echo "Testing $${profile}" ; \
${PARSER} --config-file=../parser/tst/parser.conf -S -b ${EXTRAS_SOURCE} -I ${PROFILES_SOURCE} $${profile} > /dev/null || exit 1; \
../parser/tst/test_profile.py $${profile} --config-file=../parser/tst/parser.conf -S -b ${PROFILES_SOURCE} || exit 1; \
done
@echo "*** Checking abstractions from ${ABSTRACTIONS_SOURCE} against apparmor_parser"