Using TrustedBSD | July 2003 |
by Evan Sarmiento
TrustedBSD provides a set of trusted operating system extensions to FreeBSD. Currently, these extensions can be downloaded from http://www.TrustedBSD.org, but most of them (ACL and MAC Mandatory Access Control) have been integrated into the FreeBSD-current tree. The MAC framework allows security policies to be set dynamically at runtime. The MAC Framework essentially gives the developer the access to define a security policy that works by positioning the developer's code within kernel functions. The developer's security policy can either augment the traditional FreeBSD discretionary access control policy, or replace it entirely. Essentially, the MAC framework allows the systems administrator to give fine-grained privileges to each user through modifying the existing security policy to match the needs of the given system. TrustedBSD comes prepackaged with a few MAC security modules, which augment the security policies of the given system or add new features Biba Integrity Policy and File System Firewall Policy, among others. TrustedBSD also provides the concept of labels. Labels are extraneous pieces of information that are grafted onto various kernel structures. These labels can be filled with information that can be used by MAC modules to determine the outcome of a security check. Access Control List (ACL) functionality is also provided. In this article, Ill introduce TrustedBSD's ACL functionality and describes its MAC framework by detailing the use of three previously listed MAC modules. Ill also explain how systems administrators can design their own security policies for a given system. Installation The following article presumes that the reader has FreeBSD 5.0-CURRENT or DP2 installed. The TrustedBSD development branch and the FreeBSD 5.0-CURRENT branch are developed at different intervals; however, the functionality from TrustedBSD included in FreeBSD 5.0-CURRENT is adequate for the purposes of this article. The following options should be added to the kernel and recompiled: options UFS_ACL options MACTo enable ACLs, the following steps must be completed. If you are running a UFS1 filesystem, be sure support for extended attributes is enabled within your kernel using options UFS_EXTATTR and options
UFS_EXTATTR_AUTOSTART . If you are running UFS1, you must also
complete the following operations after these options have been compiled
into the kernel:
mkdir -p /.attribute/system cd /.attribute/system extattrctl initattr -p / 388 posix1e.acl_access extattrctl initattr -p / 388 posix1e.acl_defaultIf you are running either UFS1 or the UFS2 filesystem, you can enable ACLs after the capability has been compiled into the kernel by using the command tunefs -a enable /dev/ad6s1a (or your respective
filesystem) or editing /etc/fstab to include acl within
the options column on the desired filesystem.
Access Control Lists
ACLs allow the administrator to set fine-grained restrictions on files
and directories. The manipulation of ACLs in FreeBSD is done by getfacl
and setfacl . The setfacl utility sets ACL properties on a file and directory,
while getfacl displays them. Using ACLs, an administrator can set permissions
for a file that allow only user A and B read and execute access, but,
allow group C read, write, and execute access.
Usage of setfacl is quite simple. To set access controls on a file
using setfacl , execute the following command:
setfacl -m u::r fileThis allows the owner of file to read file .
You can execute similar commands such as:
setfacl -m u:evms:rwx,g:politburo:rw file2The above allows the user "evms" read, write, and execute access to file2, while it allows the group politburo' only read and write access to file2. The following command duplicates the ACL entries contained in file1 onto file2: setfacl -M file1 file2If you want to delete an entry from the ACL table associated with a specific file, the following command will remove politburo's read and write access to file2: setfacl -x g:politburo:rw file2The following removes all ACL entries from file2: setfacl -bn file2The command getfacl file2 displays the ACL entries for
file2.
MAC modules
The TrustedBSD system comes with various MAC modules that implement
security policies. Two of these modules are Biba and the File System
Firewall Policy. I will briefly describe the features of Biba, but will
give a more detailed explanation of the File System Fire Wall Policy
because it has a powerful mechanism for providing fine-grained access
controls to users, groups, and various domains.
Biba
The Biba Data Integrity Policy can be loaded by issuing the command
kldload mac_biba , or placing mac_biba_load="YES"
in your loader.conf. The mac_biba policy module protects the integrity
of system objects by assigning each system object a specified label, containing
a grade. These grades are placed in a hierarchy. The grades are usually
in a range between 0 to 65535.
There are also special labels that exist biba/low, biba/equal, biba/high.
The biba/high label is assigned to system objects that could potentially
affect the security of the entire system. The Biba module protects the
integrity of the system by forcing all operations to conform to the following
security policy:
kldload mac_bsdextended or placing mac_bsdextend_load="YES"
in loader.conf. This module implements a security policy and a few system
calls that are utilized by the userland program ugidfw. The ugidfw utility
provides an ipfw-like interface to manage accesses to the file system
objects by UID and GID, supported by the mac_bsdextended mac policy."
The general syntax for using ugidfw is:
ugidfw set rulenum subject [not] [uid uid] [gid gid] object [not] [uid uid] [gid gid] mode arswxnThere are modes that are not typical to Unix systems n', a', and s'. These modes allow the subject to execute no operations on the object, to gain access to administrative operations regarding the object, and to access its file attributes, respectively. The following example allows the user with the uid of ten' to access all objects whose owner has the uid of twenty': ugidfw set 1 subject uid 10 object uid 20 rwThe following commands list and remove rules: ugidfw list ugidfw remove rulenumThe interface is rather intuitive, but consult the manpage if you want to learn more about ugidfw .
Designing MAC Security Modules
None of these security policies can adequately meet the specific needs
of your system. You may need to customize your system by writing your
own MAC security policy, either to replace or augment a previous security
policy. The following section will briefly cover how the MAC framework
operates, and then describe how to write a module that implements a security
policy. Knowledge of the C programming language is necessary to understand
the examples in this section.
The MAC framework consists of entry points positioned throughout the
kernel. These entry points are basically function pointers that execute
the user-defined security policy directly from the loaded module. The
kernel function cr_cansee() (within sys/kern/kern_prot.c) takes two arguments
that are both pointers to ucred structures.
The cr_cansee() function determines whether the credentials
of process u1 are enough to see the subject specified by
u2 . Within this function is a peculiar line:
int cr_cansee(struct ucred *u1, struct ucred *u2) { ... #ifdef MAC if ((error = mac_check_cred_visible(u1, u2))) return (error); #endif ... }The function mac_check_cred_visible(u1, u2) calls the function
defined within a loaded MAC security policy, previously constructed by
the administrator.
Now it is evident how the MAC framework can extend the security policy
within the kernel. Entry points are placed at various points throughout
the kernel code to allow for extensibility.
TrustedBSD also introduces the concept of labels'. Every kernel object
on the system contains a struct label', which can be filled with any
information. There are separate entry points for user-defined code that
manipulates labels. A label has the following format:
struct label { int l_flags; union { void *l_ptr; long l_long; } l_perpolicy[MAC_MAX_POLICIES]; };For example, within the kernel there is a function vfs_nmount() ,
which attempts to mount a filesystem. This function contains the MAC entry
point mac_init_mount() . When vfs_nmount() executes
mac_init_mount() (kern/kern_mac.c), it executes the user-defined
code within the module, but also executes a second entry point called
init_mount_fs_label'. This entry point calls upon the user-defined function,
which initializes the label on the mount structure.
Labels are not required to build your own MAC module, but they are useful.
A developer can design an access control policy that only uses mandatory
access controls and not labels, as will shown in the following example.
Building your own MAC Module
When constructing a security policy for your system through a MAC
module, it's probably easier to start by looking at the source code
for the module mac_none (in /usr/src/sys/security/mac_none/mac_none.c).
The mac_none module is a bare-bones module that defines all entry points.
All the administrator is required to do is rename the module, fill in
code where appropriate, and recompile. The example below is trivial;
it will increment a static counter whenever a process returns from kernel
space to user space.
#include <sys/types.h> #include <sys/param.h> #include <sys/acl.h> #include <sys/conf.h> #include <sys/extattr.h> #include <sys/kernel.h> #include <sys/mac.h> #include <sys/mount.h> #include <sys/proc.h> #include <sys/systm.h> #include <sys/sysproto.h> #include <sys/sysent.h> #include <sys/vnode.h> #include <sys/file.h> #include <sys/socket.h> #include <sys/socketvar.h> #include <sys/pipe.h> #include <sys/sysctl.h> #include <vm/vm.h> #include <sys/mac_policy.h> int example_counter; static void mac_counter_init(struct mac_policy_conf *conf) { example_counter=0; return; } static void mac_counter_userret(struct thread *td) { ++example_counter; return; } static int mac_counter_syscall(struct thread *td, int call, void *arg) { printf("Counter: %d\n", example_counter); } static struct mac_policy_ops mac_counter_ops = { .mpo_init = mac_counter_init, .mpo_thread_userret = mac_counter_userret, .mpo_syscall = mac_counter_syscall, }; MAC_POLICY_SET(&mac_counter_ops, mac_counter, "SysAdmin MAC example", MPC_LOADTIME_FLAG_UNLOADOK, NULL);When the MAC kernel module is loaded, mac_counter_init()
is executed first. This function sets the counter to zero. When a process
returns from kernel space to user space, mac_counter_userret()
increments the counter by one. When a user executes the system call mac_counter_syscall() ,
it prints the value of the counter.
The mac_policy_ops structure is registered with the kernel. Whenever
a kernel function reaches an entry point, it finds this structure and
executes the appropriate user-defined function.
MAC_POLICY_SET() macro are the last instructions that
actually load the module. The Makefile for both examples is the following:
KMOD= mac SRCS= vnode_if.h \ example1.c OR example2.c .include <bsd.kmod.mk>This second module will:
(example1.c) #include <sys/types.h> #include <sys/param.h> #include <sys/acl.h> #include <sys/conf.h> #include <sys/extattr.h> #include <sys/kernel.h> #include <sys/mac.h> #include <sys/mount.h> #include <sys/proc.h> #include <sys/systm.h> #include <sys/sysproto.h> #include <sys/sysent.h> #include <sys/vnode.h> #include <sys/file.h> #include <sys/socket.h> #include <sys/socketvar.h> #include <sys/pipe.h> #include <sys/sysctl.h> #include <sys/imgact.h> #include <fs/devfs/devfs.h> #include <net/bpfdesc.h> #include <net/if.h> #include <net/if_types.h> #include <net/if_var.h> #include <netinet/in.h> #include <netinet/ip_var.h> #include <vm/vm.h> #include <sys/mac_policy.h> #define WARNING 2 #define NORMAL 1 static void mac_example_init(struct mac_policy_conf *conf) { printf("Example MAC module loaded.\n"); } /* Initailizes the label on the ucred structure */ static void mac_example_cred_init_label(struct label *label) { label->l_flags = NORMAL; } /* Will prevent user with GID 5 of accessing any local sockets. This function is called when a socket is being connected to. */ static int mac_example_check_socket_connect(struct ucred *cred, struct socket *socket, struct label *socketlabel, struct sockaddr *sockaddr) { if (cred->cr_rgid == (gid_t)5) { if ( sockaddr->sa_family == AF_LOCAL ) { cred->cr_label.l_flags = WARNING; return EPERM; } } return (0); } /* Will prevent user from executing any file owned by them This function is called when a file is being executed. */ static int mac_example_check_vnode_exec(struct ucred *cred, struct vnode *vp, struct label *label, struct image_params *imgp, struct label *execlabel) { if (cred->cr_label.l_flags == WARNING) return EPERM; if (cred->cr_uid == 0) return (0); if (imgp->attr->va_uid == cred->cr_uid) return EPERM; return (0); } static struct mac_policy_ops mac_example_ops = { .mpo_init = mac_example_init, .mpo_check_vnode_exec = mac_example_check_vnode_exec, .mpo_check_socket_connect = mac_example_check_socket_connect, .mpo_init_cred_label = mac_example_cred_init_label, }; MAC_POLICY_SET(&mac_example_ops, mac_example, "SysAdmin MAC/Example", MPC_LOADTIME_FLAG_UNLOADOK, NULL);When a new ucred structure is created, mac_example_cred_init_label()
is executed and the l_flags element on the label structure
is filled with the value of "NORMAL", which is 0. Whenever a user attempts
to connect to a socket, the function mac_example_check_socket_connect()
is executed. If a user attempts to connect to a socket that is local to
the system, the label on his ucred object is changed to "WARNING"
and a permissions error (EPERM) is returned. Otherwise, the function returns
0.
Whenever a program is executed, mac_example_check_vnode()
is executed, which first checks to see whether the current user has
a WARNING label. If the user has a WARNING label, he is unable to execute
anything. Otherwise, the function checks whether the program about to
be executed (the program binary is abstracted with the image_params
structure) is owned by the user. If the program about to be executed
is owned by the user, then the user is not allowed to execute the program
and an EPERM error is returned.
There are thousands of places to extract information about a current
user from the kernel. When designing a security policy, be sure to look
at the definitions for the ucred structure. Most likely,
one or more of your functions will deal with examining an element from
the ucred structure and determining the outcome of a security
check from the value of that element. In the FreeBSD Developer's Handbook
there is a list of all the MAC entry points within the kernel:
http://www.freebsd.org/doc/en_US.ISO8859-1/books/developers-handbook/mac-entry-point-reference.html
This simple module below creates a security policy that does not allow
a user to change his directory or open files that are not owned either
by the user or group:
(example2.c) #include <sys/types.h> #include <sys/param.h> #include <sys/acl.h> #include <sys/conf.h> #include <sys/extattr.h> #include <sys/kernel.h> #include <sys/mac.h> #include <sys/mount.h> #include <sys/proc.h> #include <sys/systm.h> #include <sys/sysproto.h> #include <sys/sysent.h> #include <sys/vnode.h> #include <sys/file.h> #include <sys/socket.h> #include <sys/socketvar.h> #include <sys/pipe.h> #include <sys/sysctl.h> #include <vm/vm.h> #include <sys/mac_policy.h> static void mac_tres_init(struct mac_policy_conf *conf) { return; } static int mac_tres_check_vnode_chdir(struct ucred *cred, struct vnode *dvp, \ struct label *dlabel) { struct vattr vap; if (cred->cr_uid == 0) return (0); /* Allow root to change directories */ VOP_GETATTR(dvp, &vap, cred, curthread); if ((vap.va_uid != cred->cr_uid) && (vap.va_gid != cred->cr_gid)) return EPERM; return 0; } static int mac_tres_check_vnode_open(struct ucred *cred, struct vnode *vp, struct \ label *label, int acc_mode) { struct vattr vap; if (cred->cr_uid == 0) return (0); VOP_GETATTR(vp, &vap, cred, curthread); if ((vap.va_uid != cred->cr_uid) && (vap.va_gid != cred->cr_gid)) return EPERM; return 0; } static struct mac_policy_ops mac_tres_ops = { .mpo_init = mac_tres_init, .mpo_check_vnode_chdir = mac_tres_check_vnode_chdir, .mpo_check_vnode_open = mac_tres_check_vnode_open, }; MAC_POLICY_SET(&mac_tres_ops, mac_tres, "SysAdmin MAC/Example 3", MPC_LOADTIME_FLAG_UNLOADOK, NULL); Stacking
The most powerful feature of the MAC framework is stacking. Security
modules can be stacked to enhance the flexibility of previously loaded
modules. For example, suppose that I like the functionality of the LoMAC
module, but I want to add extra checks to Conclusion The MAC Framework makes FreeBSD highly extensible. It's very easy (with a simple knowledge of C) to curtail the access of your users and secure your system in a way not possible before. Labels, in particular, are useful to protecting the integrity of objects. An administrator can use labels to prevent backdoors. The administrator can create a database, containing the MD5 or SHA-1 hash of every vital executable of the system, which is loaded into the kernel upon bootup.
Before a file is executed, code can be placed within the Evan Sarmiento is a senior at Boston University Academy. He enjoys FreeBSD kernel hacking and network administration. He can be reached at [email protected]. |