⬆️ ⬇️

Development of SELinux-module for the application

Once upon a time, in a far-away country



... the NSA's public service developed a security system for the Linux kernel and environment, and called it SELinux. And since then people have been divided into two categories: disabled / permissive and enforcing. Today, I will show you the path of the Force and transfer everyone to the other side.



Assumptions



The text will contain a lot of technical information, so the author assumes that the reader:





Is this all about you? Then let's go!

')

Base types



As a test subject, I took jnode — a fairly typical application that communicates over the network, goes to the database, reads configs, writes its data and tmp-files, and monitors its state (cpu, mem, disk).



Create a jnode.te file (te = Type Enforcement)



How to start writing a module? From the description of the basic types:



policy_module(jnode, 1.0.0) #    type jnode_t; #     type jnode_exec_t; #   - type jnode_conf_t; #    (  /var/cache/ ) type jnode_cache_t; #   - type jnode_log_t; #     type jnode_tmp_t; #   ,   jnode (  binkp ) type binkp_port_t; 


Why so much? Because these are different access categories for the system, for example:





Lyrical digression



Theoretically, almost at this place (adding only the transition rule) you can stop writing the module, compile it, install it into the system and mark the file system with chcon. After that, collect logs with the audit2allow utility and get a few hundred absolutely incomprehensible lines that will allow something. From them in the future we get a module that will even work. But understanding it will not add to you, is it?



Therefore, I suggest another way: read the header files and choose what you need there. In / usr / share / selinux / devel / include / you can find several hundred .if files that contain standard SELinux baseline macros. Unfortunately, you have to use grep and cat yourself, I will only show a few basic macros and how they make life easier.



Attribute macros



To make life easier in SELinux, there is the concept of attribute , a certain type container, which can also be assigned access rights. Thus, by adding your new type to one or another attribute, we automatically give it standard permissions for this attribute. In order not to memorize all these attributes, there are already ready macros that mark types by attributes (often by several). See:



 #  - files_config_file(jnode_conf_t) #  -  files_type(jnode_cache_t) #  - logging_log_file(jnode_log_t) #    files_tmp_file(jnode_tmp_t) #    corenet_port(binkp_port_t) 


Standard Permissions Macros



When types are defined, you can assign standard behaviors for the application. To do this, we also use macros, they are fairly easy to find by keywords and make the code human-readable:



 #  :   jnode_t    #       jnode_exec_t application_domain(jnode_t, jnode_exec_t) #  :   jnode_t   , #     systemd #   :  systemd     jnode_exec_t, #     jnode_t init_daemon_domain(jnode_t, jnode_exec_t) #   jnode_t    ( /bin, /usr/bin ) corecmd_exec_bin(jnode_t) #   jnode_t   libs_use_ld_so(jnode_t) #   jnode_t    ( cpu, memory ) kernel_read_system_state(jnode_t) #   jnode_t   /tmp files_rw_generic_tmp_dir(jnode_t) #   jnode_t    ( /etc/resolv.conf  ) sysnet_read_config(jnode_t) #   jnode_t     /dev/(u)random dev_read_rand(jnode_t) #   jnode_t     (   ) fs_getattr_xattr_fs(jnode_t) #   jnode_t  dns resolve sysnet_dns_name_resolve(jnode_t) #   jnode_t   /var/log ( r/o ) logging_search_logs(jnode_t) #  : ,    jnode_t, #    jnode_log_t logging_log_filetrans(jnode_t, jnode_log_t, file) #  : tmp-,    jnode_t, #    jnode_tmp_t files_poly_member_tmp(jnode_t, jnode_tmp_t) #  jnode_t  bind()    corenet_tcp_bind_generic_node(jnode_t) #  jnode_t   postgresql  unix- postgresql_stream_connect(jnode_t) #  jnode_t   postgresql   corenet_tcp_connect_postgresql_port(jnode_t) 


Context file



Now it's time to bind the created types to the file system. Create a file jnode.fc (fc = File Context).



 #   /opt/jnode/jnode.run -- gen_context(system_u:object_r:jnode_exec_t) #   r/o    "" /opt/jnode(/.*)? gen_context(system_u:object_r:jnode_conf_t) /opt/jnode/jar(/.*) gen_context(system_u:object_r:jnode_conf_t) #     /opt/jnode/point/.*\.cfg gen_context(system_u:object_r:jnode_conf_t) #      (    ) /opt/jnode/fileechoes(/.*)? gen_context(system_u:object_r:jnode_cache_t) /opt/jnode/point(/.*)? gen_context(system_u:object_r:jnode_cache_t) #          /opt/jnode/(inbound|temp)(/.*)? gen_context(system_u:object_r:jnode_tmp_t) #      /var/log/jnode(/.*)? gen_context(system_u:object_r:jnode_log_t) 


Assembly and installation



Let's create some folder and put jnode.te and jnode.fc files there.

Go there and do the build:



 [root@jnode jnode]# make -f /usr/share/selinux/devel/Makefile Compiling targeted jnode module /usr/bin/checkmodule: loading policy configuration from tmp/jnode.tmp /usr/bin/checkmodule: policy configuration loaded /usr/bin/checkmodule: writing binary representation (version 17) to tmp/jnode.mod Creating targeted jnode.pp policy package rm tmp/jnode.mod.fc tmp/jnode.mod 


Install the module with the semodule -i jnode.pp command and enable it with the semodule -e jnode command .



Assign the port number for the binkp_port_t type: semanage port -a -t binkp_port_t -p tcp 24554 .



Now you need to reassign contexts in accordance with the context file:

restoreconn -Rv / opt / jnode . We start service through systemctl and we start to wait.



Final audit2allow



After some time (hour, day - depends on the service activity), you can execute the command audit2allow -b -r -t jnode_t and see what else the application asks besides what was already given to it. There will be few permissions - maybe 10-15 lines, and not all of them are really needed. Here you have to decide what to leave and what to remove. In the “unnecessary” part, replace allow with dontaudit - this will eliminate the recurring garbage in the logs. By the way, update the version of the module - this will allow the kernel to understand that it needs to be updated.



setenforce 1



When audit2allow shows “empty”, it means that everything works according to plan and you can enable enforcing. Congratulations, you found the Force. Dispose of it wisely.



useful links



selinuxproject.org/page/AVCRules - description of allow rules

selinuxproject.org/page/TypeRules - type_ rules description

selinuxproject.org/page/ObjectClassesPerms - a list of access classes and permissions

danwalsh.livejournal.com - SELinux “Father” blog at RedHat and the refpolicy itself

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



All Articles