Implementing SELinux as a Linux Security Module | ||
---|---|---|
<<< Previous | Next >>> |
This section summarizes the changes between the original SELinux kernel patch and the LSM-based SELinux security module. At a high level, the LSM-based SELinux security module provides equivalent security functionality to the original SELinux kernel patch. However, there have been some changes to the specific controls, partly driven by design constraints imposed by LSM and partly based on further review of the original SELinux controls. There have also been significant changes in the underlying implementation, likewise partly driven by differences in LSM and partly based on a review of the original SELinux implementation. The following subsections summarize the changes, grouped by category.
This subsection describes general changes between the original SELinux kernel patch and the LSM-based SELinux security module. These changes include adding a new level of indirection, dynamically allocating security fields, handling pre-existing subjects and objects, stacking with the capabilities module, reimplementing the extended system calls, and leveraging the existing Linux functions for checking permissions.
The original SELinux kernel patch provided clean separation between the policy enforcement code and the policy decision-making code by using the Flask security architecture and interfaces. The policy enforcement code was directly inserted into the kernel code at appropriate points, and the policy decision-making code was encapsulated in the security server, with a well-defined interface between the two components. Similarly, policy-independent data types for security information were directly inserted into kernel data structures, and only the security server could interpret these data types. This level of separation permitted many different kinds of nondiscretionary access control policies to be implemented in the security server without any changes to the policy enforcement code.
The LSM kernel patch inserts calls to hook functions on kernel objects into the kernel code at appropriate points, and it inserts void* security fields into the kernel data structures for kernel objects. In the LSM-based SELinux security module, the policy enforcement code is implemented in the hook functions, and the policy-independent data types are stored using the security fields in the kernel data structures. Internally, the SELinux code continues to use the Flask architecture and interfaces, and the security server remains as a separate component of the module. Hence, LSM introduces an additional level of indirection for the SELinux code and data. The internal architecture of the SELinux security module is discussed further in the Section called Internal Architecture.
In the original SELinux kernel patch, fields for security data were inserted directly into the appropriate kernel objects and were allocated and freed with the kernel object. Since LSM inserts only a single void* security field into each kernel object, the LSM-based SELinux security module must manage a dynamically allocated security structure for each kernel object unless it only needs to store a single word of security data. At present, the SELinux security module does directly store a single word (a single SID) in the security field of one of the kernel data structures, the struct linux_binprm structure, but this may be changed in the future. This is discussed further in the Section called Managing Binprm Security Fields. The SELinux security module uses a dynamically-allocated security structure for the security fields of the other kernel data structures.
With the original SELinux kernel patch, it was possible to ensure that all subjects and objects are labeled when they are initialized or created. The LSM-based SELinux security module must handle subjects and objects in the system that were created prior to module initialization. Some tasks and objects (e.g. the procfs root inode) are created prior to module initialization even when the module is compiled into the kernel, so there are always some pre-existing subjects and objects that must be handled. When the module is dynamically loaded into a kernel, the situation is even more complicated.
The LSM-based SELinux security module addresses this problem by defining a precondition function for each kernel object that dynamically handles the allocation and initialization of the corresponding security structure if it is not already set, and by calling this precondition function prior to any attempts to dereference the security field. These functions are described in general in the Section called Precondition Helper Functions and in more detail in the individual hook subsections. However, these functions can not always retroactively determine the correct security information for a pre-existing subject or object, so it is recommended that the SELinux security module always be built into the kernel.
The original SELinux kernel patch added the SELinux nondiscretionary access controls as additional restrictions to the existing Linux access control logic. This left the existing Linux logic intact and unchanged, including the discretionary access control logic and the capabilities logic. LSM moves most of the capabilities logic into an optional capabilities security module and provides a dummy security module that implements traditional superuser logic. Hence, the LSM-based SELinux security module provides support for stacking with either the capabilities module or the dummy module. Since some existing applications (e.g. named, sendmail) expect capabilities to be present in Linux, it is recommended that the SELinux module always be stacked with the capabilities module. The stacking support is discussed further in the Section called Stacking with Other Modules.
In the original SELinux kernel patch, extended system calls such as execve_secure and stat_secure were implemented by extending the internal kernel functions to optionally pass and process SID parameters. In the LSM-based SELinux security module, these extended system calls were implemented by passing SID parameters to and from the hook functions via fields in the current task's security structure. This is discussed further in the Section called New System Calls.
The original SELinux kernel patch directly inserted its own permission checks throughout the kernel code rather than trying to leverage existing Linux permission functions such as permission and ipcperms due to the coarse-grained permissions supported by these functions and the need to perform permission checks in many locations where no Linux check already existed. The one notable exception to this practice in the original SELinux kernel patch was the insertion of a SELinux permission check into the existing capable kernel function so that SELinux could perform a parallel check for the large number of existing calls to capable.
In contrast, LSM inserts hook calls into all of the existing Linux permission functions in order to leverage these functions. In some cases, LSM also inserts additional hook calls in specific operations to provide finer-grained control, but in other cases, it merely relies on a hook in one of the existing Linux permission functions to control an operation. The LSM-based SELinux security module uses the hooks in the existing Linux permission functions to perform a parallel check for each Linux permission check. These parallel checks for the Linux permission checks ensure that every Linux access control is also controlled by SELinux. They also reduce the risk that future changes to Linux will introduce operations that are completely uncontrolled by SELinux.
Using these hooks required defining some additional coarse-grained permissions for SELinux. These permissions are discussed further in the Section called Leveraging permission and in the Section called Leveraging ipcperms. Whenever possible, the LSM-based SELinux security module leverages these hooks to provide control. When SELinux requires finer-grained control, the module implements these finer-grained SELinux controls using the additional LSM hooks.
This subsection describes general changes between the original SELinux kernel patch and the LSM-based SELinux security module related to program execution. These changes include replacing the process execute permission with a new file execute_no_trans permission, changing the file descriptor inheritance controls, and changing the controls over process tracing and state sharing when a new program is executed. Each of these changes is described below.
In the original SELinux kernel patch, the file execute permission controlled the ability to initiate the execution of a program, while the process execute permission controlled the ability to execute code from an executable image. The distinction was necessary because the SID of a task can be changed by program execution, so the SID of the initiator may differ from the SID of the transformed process. However, the process execute permission was redundant with the process entrypoint permission when the SID of the task was changing, so it only served a useful purpose when the task SID was left unchanged. Furthermore, since this permission was between a task SID and a program file SID, it properly belonged in the file class, not the process class.
Hence, the process execute permission was replaced by a new file execute_no_trans permission in the LSM-based SELinux security module. Unlike the original process execute permission, the file execute_no_trans permission is only checked when the SID of the task would remain unchanged. The process entrypoint permission was also moved into the file class for consistency. The file execute and process transition permissions were left unchanged. These checks are described further in the Section called selinux_bprm_set_security.
The file descriptor inheritance permission checks during program execution were revised for the LSM-based SELinux security module. This is discussed in the Section called File Descriptor Permissions.
In the original SELinux kernel patch, checks for process tracing and sharing process state when the SID was changed were inserted into the compute_creds kernel function with the existing Linux tests for these conditions for setuid programs. However, this function can not return an error, so SELinux merely left the task SID unchanged if these checks failed, just as Linux leaves the uid unchanged if its tests fail. Additionally, the original SELinux kernel patch used a hardcoded test for process 1 to permit the kernel to transition to a new SID for init even though it was sharing state. In the LSM-based SELinux security module, the ptrace and share checks were changed to also send a SIGKILL to the task to terminate it upon a permission failure, and a new process share permission was added to provide configurable control over process state sharing across SID transitions. This is described further in the Section called selinux_bprm_compute_creds.
This subsection describes changes between the original SELinux kernel patch and the LSM-based SELinux security module related to the filesystem. These changes include extending the persistent label mapping to be filesystem-independent, reimplementing file labeling support for pseudo filesystem types, leveraging the hook in the existing permission function, revising the file descriptor permission checks, changing the open_secure interface, and eliminating the pipe security class. Each change is described below.
In the original SELinux kernel patch, the persistent label mapping in each filesystem stored a mapping from persistent security identifiers (PSIDs) to security contexts, and a PSID was stored in a spare field of the on-disk ext2 inode. Since LSM provides all of its file-related hooks in the VFS layer and does not provide any filesystem-specific hooks, the SELinux persistent label mapping was changed to maintain the inode-to-PSID mapping in a regular file rather than using a spare field in the ext2 on-disk inode. This change should allow SELinux to support other file system types more easily, but has disadvantages in terms of performance and consistency. Naturally, if support for extended attributes becomes integrated into the mainstream Linux kernel, SELinux will be modified to take advantage of it when extended attributes are supported by the filesystem.
In the original SELinux kernel patch, code was directly inserted into the procfs and devpts pseudo filesystem implementations to provide appropriate file labeling behaviors. Since LSM does not provide filesystem-specific hooks, the LSM-based SELinux security module had to reimplement this functionality using the hooks in the VFS layer. In addition to reimplementing the labeling functionality for these filesystem types, labeling support for the tmpfs and devfs filesystems was also added to the LSM-based SELinux security module. The handling for these pseudo filesystem types is described in the Section called inode_precondition.
As discussed in the Section called Leveraging Linux Permission Functions, LSM inserts a hook into the existing Linux functions for permission checking, including the permission function for checking access to objects represented by inodes. The LSM-based SELinux security module leverages this hook to perform a parallel check for each existing Linux inode permission check. The use of this hook posed a problem for preserving the SELinux distinction between opening a file with append access vs. opening a file with write access, requiring an additional change to the Linux kernel that was incorporated into the LSM kernel patch.
The use of this hook also posed a problem for the SELinux directory permissions, which partition traditional write access into separate permissions for adding entries (add_name), removing entries (remove_name), and reparenting the directory (reparent). Since these distinctions are not possible in the selinux_inode_permission hook called by the permission kernel function, a directory write permission was added to SELinux. This permission is checked by this hook when write access is requested, and the finer-grained directory permissions are checked by the additional hooks that are called when a directory operation is performed.
Hence, directory modifications require both a write permission and the appropriate finer-grained permission to the directory. Whenever one of the finer-grained permissions is granted in the policy, the write permission should also be granted in the policy. The write permission check on directories could be omitted, but it is present to ensure that all directory write accesses are controlled by SELinux.
In the original SELinux kernel patch, distinct file descriptor permissions were defined for getting the file offset or flags (getattr), setting the file offset or flags (setattr), inheriting the descriptor across an execve (inherit), and receiving the descriptor via socket IPC (receive). These permissions were reduced to a single use permission in the LSM-based SELinux security module that is checked whenever the descriptor is inherited, received, or used.
Additionally, in the original SELinux kernel patch, only the inherit or receive permissions were checked when a descriptor was inherited or received. The other descriptor permissions and the appropriate file permissions were only checked when an attempt was made to use the descriptor. In the LSM-based SELinux security module, the use permission and the appropriate file permissions are checked whenever the descriptor is inherited, received, or used.
These changes to the SELinux file descriptor permission checks bring SELinux into conformity with the base Linux control model, where possession of a descriptor implies the right to use it in accordance with its mode and flags. This reduces the risk of misuse of a descriptor by a process, and also reduces the risk that future changes to Linux will open vulnerabilities in the SELinux control model. With these changes, the SELinux permission checks on calls such as read and write are only necessary to support revocation of access for relabeled files or policy changes.
In the original SELinux kernel patch, the open_secure system call had two optional SID parameters, one to specify the SID of the file when a file is created and one to specify the SID of the file descriptor. However, calls such as stat_secure only returned the SID of the file, not the file descriptor, and no calls were provided to change the SID of an existing descriptor. For the LSM-based SELinux security module, file descriptors always inherit the SID of the opening process, and the open_secure system call only takes a single SID parameter to specify the SID of a new file. Hence, SIDs on file descriptors are completely invisible to applications, but are still used to control shared access to the file offset and flags.
In the original SELinux kernel patch, a separate security class was defined for pipes, although this security class merely inherited the common file permissions. In the LSM-based SELinux security module, this class was eliminated, and the fifo_file security class is used for both pipes and for named FIFOs. This has no impact on the ability to control pipe operations distinctly, since pipes are still labeled with the SID of the creating task while named FIFOs are labeled in the same manner as other files.
This subsection describes changes between the original SELinux kernel patch and the LSM-based SELinux security module related to socket IPC and networking. These changes include storing socket security information in the associated inode security field, reimplementing the SELinux access controls using minimally invasive hooks, changing the file descriptor transfer controls, omitting some of the low-level ioctl controls, and implementing the extended socket calls.
The original SELinux kernel patch added security fields to the sock structure for socket security data, and also mirrored the SID and security class of the socket in the inode structure associated with the socket. LSM also provides a security field within the kernel socket data structure, and SELinux uses this field to store security data for new connections during connection setup, when no user socket (and no inode) is available yet. However, the LSM-based SELinux security module stores the socket security data in the security field of the associated inode once the user socket is established. This is discussed further in the Section called Socket Hook Functions and the Section called IPv4 Networking Hook Functions.
Since the original SELinux kernel patch added security fields to the lower-level struct sock structure, most of the SELinux changes were inserted directly into the specific protocol family implementations (e.g. the AF_INET and AF_UNIX code). The original SELinux kernel patch was fairly invasive in inserting SELinux processing throughout the protocol family implementations, and did not try to leverage the existing Linux packet filtering support.
LSM provides a set of hooks in the abstract socket layer for controlling socket operations at a high level, and leverages the Linux NetFilter support for hooking network operations. The LSM-based SELinux security module implements as many of the SELinux socket and network controls as possible using these socket layer hooks and NetFilter-based hooks. Hence, NetFilter support should be enabled in the kernel configuration when using SELinux. The SELinux network access controls required one additional hook (sock_rcv_skb) in two locations for controlling connection establishment and packet receipt on a socket. Another hook was added (tcp_create_openreq_child so that security data can be saved in the struct sock during connection establishment.
For the SELinux Unix domain IPC controls, the LSM-based SELinux security module leverages the hooks in the existing Linux permission functions but also required two additional hooks in the Unix domain protocol implementation due to the abstract namespace. These three additional hooks have been accepted into the LSM kernel patch, so the LSM hooks are adequate for SELinux. The SELinux socket access controls are described in the Section called Controlling Socket Operations and the SELinux network layer access controls are described in the Section called IPv4 Networking Hook Functions.
The file descriptor transfer permission checks during socket IPC were revised for the LSM-based SELinux security module. This is discussed in the Section called File Descriptor Permissions.
In the original SELinux kernel patch, a small set of controls were implemented in low-level ioctl routines to support fine-grained control over configuring network devices, accessing the kernel routing table, and accessing the kernel ARP and RARP tables. During the development of LSM, the feasibility of providing hooks to support these controls was explored, but it was determined that providing hooks in every location necessary to control configuring network devices would be too invasive, and the other controls offered little benefit over the existing capable calls. Hence, the LSM-based SELinux security module does not implement these controls, and control over these operations is handled based on the capable calls.
In the original SELinux kernel patch, a set of extended socket calls were implemented. The implementation of these calls for the LSM-based SELinux module is not yet complete and several unresolved issues still remain. A separate kernel configuration option has been defined for these calls and the corresponding hook function processing. Disabling this option has no impact on the enforcement of the network policy by the kernel, and no applications have been modified yet to use these calls, so the option can be disabled without harm. It is expected that further changes to the LSM kernel patch will be necessary to fully support the extended socket calls. The extended socket call processing is discussed further in the Section called New System Calls and the Section called Extended Socket Call Processing.
This subsection describes changes between the original SELinux kernel patch and the LSM-based SELinux security module related to System V IPC. Since the System V IPC security enhancements were never ported from the 2.2 series to the 2.4 series prior to the transition to using LSM, the LSM-based SELinux security module had to adapt the implementation of the SELinux security enhancements to the 2.4 series. In addition to this adaptation, the changes include an easier solution for storing the IPC security data and leveraging the hook in the existing ipcperms function.
In the original SELinux kernel patch for the 2.2 series, it was difficult to add security data to the semaphore and message queue structures because the kernel exported the same data structure that it used internally to applications. Hence, the original SELinux kernel patch wrapped these data structures with private kernel data structures that contained both the original structure and the additional security data. This required extensive changes to the IPC code to dereference fields in the original structure. In the 2.4 series, the IPC code was rewritten to use private kernel data structures for all of the IPC objects, and each of these structures included a struct kern_ipc_perm structure with common information. Hence, LSM was able to add a single security field to this common structure and a single security field to the structure for individual messages. This is discussed further in the Section called Managing System V IPC Security Fields.
As discussed in the Section called Leveraging Linux Permission Functions, LSM inserts a hook into the existing Linux functions for permission checking, including the ipcperms function for checking access to IPC objects. The LSM-based SELinux security module leverages this hook to perform a parallel check for each existing Linux IPC permission check. However, since the SELinux IPC permissions are much finer-grained than the Linux concepts of read or write access to IPC objects, new unix_read and unix_write permissions were defined to correspond with the Linux permissions. These new permissions are checked by the hook called by ipcperms, and the finer-grained SELinux permissions are checked by the other IPC hooks. Hence, IPC operations require the unix_read or unix_write permission and the appropriate finer-grained permission. The coarse-grained permission checks could be omitted, but they are present to ensure that all IPC accesses are controlled by SELinux. These checks are discussed in the Section called selinux_ipc_permission.
In addition to the changes described above, the LSM-based SELinux security module had to reimplement the approach for controlling the sysctl call, as described in the Section called System Hook Function for sysctl. It also added new controls for some system operations that were not specifically addressed in the original SELinux kernel patch. New controls have been defined for quotactl, syslog, swapon, nfsservctl, and bdflush. These controls are discussed in the Section called System Hook Functions. In the original SELinux kernel patch, these operations were merely controlled via the coarse-grained capable controls.
<<< Previous | Home | Next >>> |
SELinux Basic Concepts | Internal Architecture |