mirror of
https://gitlab.com/apparmor/apparmor
synced 2025-08-31 14:25:52 +00:00
Resurrect a random profile generator for stress testing the parser.
Needs love in the form of enhancements to support regexs, all the added features in upcoming 2.3 release, etc. Could also stand a bit of refactoring to make the ruby program not suck up so much ram by writing out profiles as things go along rather than generating a bunch of large objects and keeping them around.
This commit is contained in:
235
tests/stress/parser/stress.rb
Executable file
235
tests/stress/parser/stress.rb
Executable file
@@ -0,0 +1,235 @@
|
||||
#! /usr/bin/env ruby
|
||||
#
|
||||
|
||||
require 'getoptlong'
|
||||
require 'tmpdir'
|
||||
|
||||
$my_version = '$Id$'
|
||||
$random_length = 32
|
||||
$prefix = "stress"
|
||||
$max_rules = 200
|
||||
$min_rules = 5
|
||||
|
||||
def get_random_name(len=$random_length)
|
||||
return sprintf("%0#{len}x", rand(2 ** (4 * len)))
|
||||
end
|
||||
|
||||
def get_random_path()
|
||||
out = ""
|
||||
0.upto(rand(20)) do
|
||||
out = "#{out}/#{get_random_name(4)}"
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
def get_random_mode()
|
||||
case rand(10)
|
||||
when 0..4
|
||||
return "r"
|
||||
when 5..7
|
||||
return "rw"
|
||||
when 8
|
||||
return "Px"
|
||||
when 9
|
||||
return "rix"
|
||||
end
|
||||
end
|
||||
|
||||
# Abstract class, though may become a real class for generation of
|
||||
# random types of rules
|
||||
class Rule
|
||||
end
|
||||
|
||||
class FileRule < Rule
|
||||
def initialize(path=get_random_path(), mode=get_random_mode())
|
||||
@path = path
|
||||
@mode = mode
|
||||
end
|
||||
|
||||
def to_s
|
||||
return " #{@path} #{@mode},"
|
||||
end
|
||||
end
|
||||
|
||||
class CapRule < Rule
|
||||
CAP_LIST = [
|
||||
"chown",
|
||||
"dac_override",
|
||||
"dac_read_search",
|
||||
"fowner",
|
||||
"fsetid",
|
||||
"kill",
|
||||
"setgid",
|
||||
"setuid",
|
||||
"setpcap",
|
||||
"linux_immutable",
|
||||
"net_bind_service",
|
||||
"net_broadcast",
|
||||
"net_admin",
|
||||
"net_raw",
|
||||
"ipc_lock",
|
||||
"ipc_owner",
|
||||
"sys_module",
|
||||
"sys_rawio",
|
||||
"sys_chroot",
|
||||
"sys_ptrace",
|
||||
"sys_pacct",
|
||||
"sys_admin",
|
||||
"sys_boot",
|
||||
"sys_nice",
|
||||
"sys_resource",
|
||||
"sys_time",
|
||||
"sys_tty_config",
|
||||
"mknod",
|
||||
"lease",
|
||||
"audit_write",
|
||||
"audit_control"
|
||||
]
|
||||
|
||||
def initialize()
|
||||
@cap = CAP_LIST[rand(CAP_LIST.length)]
|
||||
end
|
||||
|
||||
def to_s
|
||||
return " capability #{@cap},"
|
||||
end
|
||||
end
|
||||
|
||||
def prefix_to_s(name)
|
||||
out = []
|
||||
out << "#"
|
||||
out << "# prefix for #{name}"
|
||||
out << "# generated by #{__FILE__} #{$my_version}"
|
||||
out << "#include <tunables/global>"
|
||||
out << "#"
|
||||
end
|
||||
|
||||
class Profile
|
||||
attr_reader :rvalue
|
||||
attr_reader :name
|
||||
|
||||
def initialize()
|
||||
@rvalue = get_random_name()
|
||||
@name = "/does/not/exist/#{@rvalue}"
|
||||
@rules = []
|
||||
end
|
||||
|
||||
def generate_rules
|
||||
@rules << FileRule.new(@name, "rm")
|
||||
0.upto(rand($max_rules - $min_rules) + $min_rules) do |x|
|
||||
case rand(100)
|
||||
when 0..19
|
||||
@rules << CapRule.new
|
||||
when 19..100
|
||||
@rules << FileRule.new
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def to_s
|
||||
out = []
|
||||
out << "#"
|
||||
out << "# profile for #{@name}"
|
||||
out << "# generated by #{__FILE__} #{$my_version}"
|
||||
out << "#"
|
||||
out << "#{@name} {"
|
||||
out << " #include <abstractions/base>"
|
||||
out << ""
|
||||
@rules.each { |r| out << r.to_s }
|
||||
out << "}"
|
||||
out << ""
|
||||
end
|
||||
end
|
||||
|
||||
def showUsage
|
||||
warn "#{$my_version}"
|
||||
warn "usage: #{__FILE__} count"
|
||||
exit 1
|
||||
end
|
||||
|
||||
def gen_profiles_dir(profiles)
|
||||
# Mu, no secure tmpdir creation in base ruby
|
||||
begin
|
||||
dirname = "#{Dir.tmpdir}/#{$prefix}-#{get_random_name(32)}"
|
||||
Dir.mkdir(dirname, 0755)
|
||||
rescue Errno::EEXIST
|
||||
retry
|
||||
end
|
||||
|
||||
profiles.each do |p|
|
||||
open("#{dirname}/#{p.rvalue}", File::CREAT|File::EXCL|File::WRONLY, 0644) do |file|
|
||||
file.puts(prefix_to_s(p.name))
|
||||
file.puts(p.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
return dirname
|
||||
end
|
||||
|
||||
def gen_profiles_file(profiles)
|
||||
# Mu, no secure tempfile creation in base ruby
|
||||
begin
|
||||
filename = "#{Dir.tmpdir}/#{$prefix}-#{get_random_name(32)}"
|
||||
File.open(filename, File::CREAT|File::EXCL|File::WRONLY, 0644) do |file|
|
||||
file.puts(prefix_to_s(filename))
|
||||
profiles.each { |p| file.puts(p.to_s) }
|
||||
end
|
||||
rescue Errno::EEXIST
|
||||
retry
|
||||
end
|
||||
|
||||
return filename
|
||||
end
|
||||
|
||||
if __FILE__ == $0
|
||||
|
||||
keep_files = true
|
||||
opts = GetoptLong.new(
|
||||
[ '--help', '-h', GetoptLong::NO_ARGUMENT ],
|
||||
[ '--seed', '-s', GetoptLong::REQUIRED_ARGUMENT ],
|
||||
[ '--keep-files', '-k', GetoptLong::NO_ARGUMENT ],
|
||||
[ '--max-rules', '-M', GetoptLong::REQUIRED_ARGUMENT ],
|
||||
[ '--min-rules', '-m', GetoptLong::REQUIRED_ARGUMENT ]
|
||||
)
|
||||
|
||||
opts.each do |opt, arg|
|
||||
case opt
|
||||
when '--help'
|
||||
showUsage
|
||||
when '--seed'
|
||||
srand(arg.to_i)
|
||||
when '--keep-files'
|
||||
keep_files = true
|
||||
when '--max-rules'
|
||||
$max_rules = arg.to_i
|
||||
when '--min-rules'
|
||||
$min_rules = arg.to_i
|
||||
end
|
||||
end
|
||||
|
||||
showUsage if ARGV.length != 1
|
||||
count = ARGV.shift.to_i
|
||||
showUsage if count < 1
|
||||
profiles = []
|
||||
while profiles.length < count do
|
||||
profiles << Profile.new()
|
||||
end
|
||||
profiles.each { |p| p.generate_rules }
|
||||
|
||||
begin
|
||||
profiles_dir = gen_profiles_dir(profiles)
|
||||
profile_single = gen_profiles_file(profiles)
|
||||
|
||||
ensure
|
||||
if (keep_files == false)
|
||||
Dir.foreach(profiles_dir) do |filename|
|
||||
File.delete("#{profiles_dir}/#{filename}") if (filename != '.' and filename != '..')
|
||||
end
|
||||
Dir.rmdir(profiles_dir)
|
||||
File.delete(profile_single)
|
||||
else
|
||||
puts "PROFILEDIR=#{profiles_dir}; export PROFILEDIR"
|
||||
puts "PROFILESINGLE=#{profile_single}; export PROFILESINGLE"
|
||||
end
|
||||
end
|
||||
end
|
108
tests/stress/parser/stress.sh
Executable file
108
tests/stress/parser/stress.sh
Executable file
@@ -0,0 +1,108 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# $Id$
|
||||
#
|
||||
|
||||
PROFILE_COUNT=1000
|
||||
KEEP_FILES=0
|
||||
LOAD_PROFILES=0
|
||||
STRESS_ARGS=
|
||||
APPARMOR_PARSER="/sbin/apparmor_parser"
|
||||
|
||||
usage () {
|
||||
echo 'stress.sh [-klh] [-c count] [-s seed] [-p parser]'
|
||||
echo ' -c count generate _count_ number of profiles'
|
||||
echo ' -h usage (this message)'
|
||||
echo ' -k keep files after completion'
|
||||
echo ' -l attempt to load profiles into kernel'
|
||||
echo ' -p parser use _parser_ instead of /sbin/apparmor_parser'
|
||||
echo ' -s seed use _seed_ as random seed value'
|
||||
echo '$Id$'
|
||||
exit 0
|
||||
}
|
||||
|
||||
while getopts 'klc:s:p:h' OPTION ; do
|
||||
case $OPTION in
|
||||
k) KEEP_FILES=1
|
||||
;;
|
||||
l) LOAD_PROFILES=1
|
||||
;;
|
||||
c) PROFILE_COUNT=$OPTARG
|
||||
;;
|
||||
s) STRESS_ARGS="${STRESS_ARGS} -s ${OPTARG}"
|
||||
;;
|
||||
p) APPARMOR_PARSER=$OPTARG
|
||||
;;
|
||||
h) usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# stress.rb exports the profile locations it generatees in PROFILEDIR
|
||||
# and PROFILESINGLE
|
||||
echo "Generating ${PROFILE_COUNT} profiles..."
|
||||
eval $(./stress.rb ${STRESS_ARGS} ${PROFILE_COUNT})
|
||||
|
||||
if [ ! -d "${PROFILEDIR}" -o ! -f "${PROFILESINGLE}" ] ; then
|
||||
echo "Generated profiles don't exist! Aborting...."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cleanup () {
|
||||
if [ ${KEEP_FILES} == 0 ] ; then
|
||||
rm -rf "${PROFILEDIR}"
|
||||
rm "${PROFILESINGLE}"
|
||||
else
|
||||
echo "Files kept in ${PROFILEDIR} and ${PROFILESINGLE}"
|
||||
fi
|
||||
}
|
||||
|
||||
timedir () {
|
||||
COMMAND=$1
|
||||
DESCRIPTION=$2
|
||||
echo "$DESCRIPTION"
|
||||
time for profile in ${PROFILEDIR}/* ; do
|
||||
${COMMAND} ${profile} > /dev/null
|
||||
done
|
||||
}
|
||||
|
||||
timesingle () {
|
||||
COMMAND=$1
|
||||
DESCRIPTION=$2
|
||||
echo "$DESCRIPTION"
|
||||
time ${COMMAND} ${PROFILESINGLE} > /dev/null
|
||||
}
|
||||
|
||||
remove_profiles () {
|
||||
echo "Unloading profiles..."
|
||||
(for profile in $(grep "^/does/not/exist" /sys/kernel/security/apparmor/profiles | cut -d " " -f 1); do
|
||||
echo "${profile} {} "
|
||||
done) | apparmor_parser -R > /dev/null
|
||||
}
|
||||
|
||||
# load files into buffer cache
|
||||
timedir "cat" "Loading directory of profiles into buffer cache"
|
||||
timedir "apparmor_parser -p" "Running preprocess only parser on directory of profiles"
|
||||
timedir "apparmor_parser -S" "Running full parser on directory of profiles"
|
||||
if [ "${LOAD_PROFILES}" == 1 ] ; then
|
||||
if [ "$(whoami)" == 'root' ] ; then
|
||||
timedir "apparmor_parser" "Parsing/loading directory of profiles"
|
||||
remove_profiles
|
||||
else
|
||||
echo "Not root, skipping load test..."
|
||||
fi
|
||||
fi
|
||||
|
||||
timesingle "cat" "Loading equivalent profile into buffer cache"
|
||||
timesingle "apparmor_parser -p" "Running preprocess only parser on single equiv profile"
|
||||
timesingle "apparmor_parser -S" "Running full parser on single equivalent profile"
|
||||
if [ "${LOAD_PROFILES}" == 1 ] ; then
|
||||
if [ "$(whoami)" == 'root' ] ; then
|
||||
timesingle "apparmor_parser" "Parsing/loading single file of profiles"
|
||||
remove_profiles
|
||||
else
|
||||
echo "Not root, skipping load test..."
|
||||
fi
|
||||
fi
|
||||
|
||||
cleanup
|
Reference in New Issue
Block a user