📜 ⬆️ ⬇️

Features of embedding in key mechanisms of Linux kernel using LSM

The embedding into a software system is understood as the process of introducing into it additional (third-party) program elements carried out in such a way that, on the one hand, its functioning is preserved, and on the other, its functionality is expanded or changed.

Among the ways of embedding into the Linux kernel, it is worth noting a method based on the use of the Linux Security Modules framework (hereinafter referred to as LSM), designed to integrate various security models that serve to extend the basic Linux discretionary security model (DAC).

About LSM implementation in the Linux kernel


The features of the LSM implementation, which will be discussed later, allow using this subsystem to be embedded in key mechanisms of the Linux kernel. At the same time, it is necessary to keep in mind that the main purpose of LSM is to provide the user with a unified extension interface for the current Linux security model and, therefore, has associated limitations.

The LSM subsystem architecture is notable for its simplicity. Technically, it is a set of hooks installed in the OS kernel that provide an interface for implementing your own handlers that are called by the system during various operations. The basic security model description structure is the security_operations structure, which can be found in the include / linux / security.h file:
')
 struct security_operations { char name[SECURITY_NAME_MAX + 1]; ... int inode_permission(struct inode * inode, int mask); ... } 


As you can see, the security model is described by the following parameters: the name and set of pointers to handler functions, the essence of which constitutes the main feature of the security model implemented - whether it is a mandatory, role-playing or other model. The main thing here is the availability of interception control in the course of executing those parts of the kernel code that are responsible for the implementation of subjects access to objects.

Being placed in key locations of the system, LSM-hooks are specially designed calls to a particular function of the current security model, while leaving the possibility of its change. Technically, this is implemented by introducing an additional entity — a pointer to the current active security model of security_ops :

 static struct security_o perations * security_ops; static struct security_o perations default_security_ops = { . name = "default", }; 


The initial value of this pointer at system startup corresponds to the standard security model described by the default_security_ops structure and includes the implementation of the system default behavior. At the same time, the content of handler functions is minimal. However, if during the operation of the system it becomes necessary to activate another model, then this can be done by simply replacing the contents of this pointer. Below is an example of using security_ops in the corresponding hook :

 int security_inode_permission(struct inode *inode, int mask) { if (unlikely(IS_PRIVATE(inode))) return 0; return security_ops->inode_permission(inode, mask); } 


As you can see, the inode_permission handler inode_permission is inode_permission indirectly using the value of the security_ops pointer, which corresponds to what was said. A good example would be the illustration of the implementation of access control using the example of inode_permission , a key function of the virtual file system (VFS) of the kernel that controls the access of the current process (subject) to the file system object:

 int inode_permission(struct inode *inode, int mask) { int retval; retval = sb_permission(inode->i_sb, inode, mask); if (retval) return retval; return __inode_permission(inode, mask); } int __inode_permission(struct inode *inode, int mask) { int retval; if (unlikely(mask & MAY_WRITE)) { /* * Nobody gets write access to an immutable file. */ if (IS_IMMUTABLE(inode)) return -EACCES; } retval = do_inode_permission(inode, mask); if (retval) return retval; retval = devcgroup_inode_permission(inode, mask); if (retval) return retval; return security_inode_permission(inode, mask); } 


LSM-hook security_inode_permission terminates a function and is the last frontier at the stage of verifying the accessibility of a process (subject) to the file (object) represented by the inode structure of the file system. Thus, it will only work if all the previous checks pass successfully, which corresponds to the LSM concept, in which the security module does not replace the basic system model, but only complements and expands it.

Summing up the brief description of LSM, it is worthwhile to separately dwell on interesting statistics reflecting the dynamics of the development of this framework in different periods of time. Information gathered specifically for this purpose is presented in a graph reflecting the total number of LSM hooks, depending on the kernel version:



First, their number attracts attention. Indeed, if the kernel version v2.6.11 accounted for 134 hooks, then the kernel version 3.8 accounted for 189, which is an indicator of the degree of LSM integration into the kernel. Secondly, the dynamics of changes in the number of hooks indicates the rapid development of LSM to version v2.6.34 and some stabilization after, which is an indicator of the subsystem maturity, its relative completeness and architectural stability.

About embedding using LSM


As a means of extending the underlying security model of the Linux kernel, LSM is not an integral component of it. The presence of this framework in the core is determined by the state of the CONFIG_SECURITY configuration variable and is set at the compilation stage. In practice, this means that the core of the target system can be assembled without LSM. However, in other cases, the use of this possibility for embedding seems to be sufficiently practical, but it has its own characteristics, primarily related to the architecture of LSM itself. Indeed, embedding capabilities are determined by the completeness of the LSM-hooks of the kernel code. In other words, it is possible to be built in only in those places in which it is provided .

In accordance with the architecture, the key Linux kernel object that provides the LSM mechanism is the security_ops pointer, which is a kernel memory cell that stores the address of the descriptor structure of the current (active) LSM model. At the same time, due to the fact that all operations associated with accessing the model are carried out through this pointer, it can be argued that for embedding it is necessary and sufficient to have the ability to control this value.

Speaking about the implementation of the LSM model by the kernel module, it is worth noting that there are features associated with the phased introduction of restrictions on LSM into the Linux kernel. Thus, for security reasons, the interfaces were sequentially excluded from the kernel, allowing the current security model to be replaced during the operation of the system, namely:



These features are designed to limit the scope of LSM for embedding, however, it is worth noting that in fact they are not critical and there is a way to circumvent them, which will be discussed later.

Practical aspects of embedding


It was noted above that, starting with the v2.6.34 kernel, v2.6.34 no regular possibility of changing the current security model during the system operation. However, this circumstance can be overcome if a way to determine the address of the current model pointer is found - security_ops . One of the simple and reliable ways to determine it is disassembling the kernel code, namely, one of the functions where this pointer is used. Indeed, it is worth considering as an example the listing of the security_sb_copy_data export function hook, obtained for a 64-bit x86 system using the gdb debugger:

 int security_sb_copy_data(char *orig, char *copy) { return security_ops->sb_copy_data(orig, copy); } EXPORT_SYMBOL(security_sb_copy_data); 

 # gdb vmlinux /proc/kcore (gdb) x/7i security_sb_copy_data 0xffffffff811f61b0: push %rbp 0xffffffff811f61b1: mov %rsp,%rbp 0xffffffff811f61b4: data32 data32 data32 xchg %ax,%ax 0xffffffff811f61b9: mov 0x881690(%rip),%rax # 0xffffffff81a77850 0xffffffff811f61c0: callq *0x98(%rax) 0xffffffff811f61c6: pop %rbp 0xffffffff811f61c7: retq (gdb) x/s* 0xffffffff81a77850 0xffffffff81850fa0: "default" 


As you can see, the dispatching instruction callq *0x98(%rax) calls the handler function sb_copy_data at offset 0x98 , relative to the pointer stored in %rax . Namely %rax and contains security_ops . Thus, in order to obtain this pointer, it is necessary, sequentially, instruction by instruction, to scan commands from the beginning of this function until finding the command mov 0x881690(%rip),%rax , loading the desired value into %rax and, having performed a simple analysis of its operands , get the required pointer. Below is the code that allows this for x86 using the udis86 disassembler:

 static void * get_lsm_entry(void) { /* this one is exported */ return (void *)&security_sb_copy_data; } static struct security_operations ** get_lsm_sop(void) { ud_t ud; void * entry = get_lsm_entry(), * result = NULL; ud_initialize(&ud, BITS_PER_LONG, UD_VENDOR_ANY, entry, 128); while (ud_disassemble(&ud) && ud.mnemonic != UD_Iret) { if (ud.mnemonic == UD_Imov && \ ud.operand[0].type == UD_OP_REG && ud.operand[1].type == UD_OP_MEM) { #ifdef CONFIG_X86_64 result = entry + ud_insn_off(&ud) + ud_insn_len(&ud); #endif result = result + ud.operand[1].lval.sdword; break; } } return result; } 


Thus, having received a pointer to the security_ops pointer, you can replace the active security model with your own implementation by simply replacing the value of this pointer. As an example, I cite the code for the fakelsm project, which replaces the inode_permission function.

Conclusion


In conclusion, the following should be said. Despite the consistently introduced limitations of using LSM outside the kernel, this framework is of interest because provides great opportunities for embedding in the core. It should be understood, however, that its capabilities are limited by the extent to which the kernel code is covered by LSM hooks. And although there are a huge amount of such kernels of modern ones, these limitations are obvious.

In addition, the fact that the concept of the LSM architecture, being simple and effective, is not secure is important, since in fact, the entire extended kernel security model is based on one single pointer — security_ops !

On read


  1. Linux security modules
  2. udis86's documentation
  3. Criticism LSM from grsecurity and RSBAC

Source: https://habr.com/ru/post/196372/


All Articles