A month ago was FOSSASIA 2018 and I got to give another talk about SELinux. Last years talk was only about the basics of how things SELinux works and labels and allow rules. This year covered more of the policy side of things. I went over now to write a Reference Policy module and did a quick demo.
I started off showing the different parts of a policy as far as the kernel and
other tools are concerned, /etc/selinux/strict/policy/policy.31
is the big
blob that gets loaded into the kernel when you boot. Then there are the file
contexts which live in /etc/selinux/strict/contexts/files/
. These don't get
loaded in the kernel but are used by various userspace tools to lookup labels.
For example when you restorecon something it will look there, or maybe a daemon
like udev needs to know what fcontext should be applied to a device node when
you plug something in. There are three main files with the fcontexts:
file_contexts
, file_contexts.home_dirs
, file_contexts.local
The
base one has all the main labels for your entire system as defined by the
distros policy. home_dirs is generated whenever the policy is loaded and
contains the contexts customized for each user on the system. Any fcontexts
that start with HOME_DIR
are automatically re-written for each user based
on their selinux user and role. It also supports rewriting based on USERID
or USERNAME
which is usually needed for things in /run/user/. Finally, the
.local file is where any fcontexts that are added using semanage fcontext end
up.
Then I showed how the entire policy above is compiled from separate modules.
Reference Policy (aka refpolicy) is how most selinux modules are written. It is
made up of a bunch of m4 macros to simplify things that happen together often.
For example reading a file requires maybe getattr'ing it then opening then
finally a read system call. We simplify this down this set to
read_file_perms
. They all follow a consisten naming scheme so you quickly
know what each does. They are <perm>_<class>_perms
, where perm is something
like setattr, read, write, manage, and class is something like file, dir,
lnk_file.
Patterns are the next building block. Lots of things require many other operations to actually work, for example creating a file requires being able to access the directory that it's in. There are patterns like:
define(`write_files_pattern',`
allow $1 $2:dir search_dir_perms;
allow $1 $3:file write_file_perms;
')
You call it like write_files_pattern(app_t, var_log_t, app_log_t)
and it will
grant you the minimum accesses you need to var_log_t dirs and then give you
write access to app_log_t.
Each module that makes up an SELinux policy is supposed to be completely
self-contained and can only refer to things it itself defines. In order to make
rules relating to other modules, each refpolicy module has a ton of interfaces.
These look like: modulename[_modifier]_verb_predicate()
, for example:
apache_append_log(app_t)
or logging_log_filetrans(app_t, app_log_t, file)
The last part i covered in the slides was a small tool written by Sven Vermeulen to find and show interfaces and defines. They are defined here.
I did pass around a VM image so people could follow along and play with the small demo policy I made. I won't be uploading the image here since it was purposefully made to be insecure and that seems like a bad idea. You can use any Gentoo or redhat VM with SELinux enabled and just use the policy below.:
The slides from the presentation are here. The recording is here.
myapp.te
policy_module(myapp, 0.1)
###########################
#
# declarations
#
type myapp_t;
type myapp_exec_t;
userdom_user_application_domain(myapp_t, myapp_exec_t)
type myapp_log_t;
logging_log_file(myapp_log_t)
###########################
#
# myapp_t local policy
#
allow myapp_t self:tcp_socket create_stream_socket_perms;
allow myapp_t myapp_log_t:file manage_file_perms;
logging_log_filetrans(myapp_t, myapp_log_t, file)
corecmd_exec_bin(myapp_t)
corenet_tcp_bind_generic_node(myapp_t)
corenet_tcp_bind_http_cache_port(myapp_t)
files_read_etc_files(myapp_t)
files_read_etc_runtime(myapp_t)
sysnet_read_config(myapp_t)
domain_use_interactive_fds(myapp_t)
miscfiles_read_localization(myapp_t)
userdom_use_inherited_user_terminals(myapp_t)
userdom_read_user_home_content_files(myapp_t)
# this should properly be in staff.te
gen_require(`
type staff_t, sysadm_t;
role staff_r, sysadm_r;
')
myapp_role(staff_r, staff_t)
myapp_role(sysadm_r, sysadm_t)
myapp.if
## <summary>Myapp client</summary>
#######################################
## <summary>
## The role for using the myapp client.
## </summary>
## <param name="role">
## <summary>
## The role associated with the user domain.
## </summary>
## </param>
## <param name="domain">
## <summary>
## The user domain.
## </summary>
## </param>
#
interface(`myapp_role',`
gen_require(`
type myapp_t;
type myapp_exec_t;
')
role $1 types myapp_t;
domtrans_pattern($2, myapp_exec_t, myapp_t)
allow $2 myapp_t:process { ptrace signal_perms };
ps_process_pattern($2, myapp_t)
')
myapp.fc
/usr/local/bin/server.py -- gen_context(system_u:object_r:myapp_exec_t,s0)