2
0
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:
Steve Beattie
2008-11-26 22:16:48 +00:00
parent edfa025814
commit e18d431b0e
2 changed files with 343 additions and 0 deletions

235
tests/stress/parser/stress.rb Executable file
View 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
View 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