Transitioning to Secmark

UPDATE: Laszlo Beres has been kind enough to provide a Hungarian translation, although the link appears to be dead in 2019.

Back in the 2.6.18 timeframe James Morris developed a replacement for the now-named “compat_net” SELinux access controls which filtered packets based on network attributes, the replacement was called Secmark. Secmark was introduced to fix two major issues with the aging compat_net access controls; the first problem being that they were not as flexible as iptables/netfilter rules and the second, very related problem, was that the compat_net controls were slow and likely would always be slow due to fundamental design issues. The solution to both these problems was to leverage the existing iptables/netfilter mechanism to label packets and replace the crude packet matching mechanisms of the compat_net design. As of 2.6.29 release the compat_net functionality is deprecated and patches completely removing it from the kernel have been merged into the 2.6.30 release candidates.

Secmark works by using iptables/netfilter to assign a label, or “security mark” aka Secmark, to specific packets which are later used by SELinux when the per-packet access controls are applied. This approach allows administrators to match packets based not only on the existing compat_net attributes such as port and host, but also any network attribute supported by iptables/netfilter, including stateful connection matching. The article by James Morris (linked above) is an excellent introduction to Secmark and for those of you looking to get the most out of the new functionality I encourage you to head over there first. What I hope to do here is not duplicate James’ article, but rather provide a quick guide on how to duplicate basic compat_net functionality using the new Secmark controls.

Before we start it is important to first identify if the system you are using has Secmark enabled, you can do this by looking at the value in “/selinux/compat_net”. If the file does not exist on your system and you have SELinux enabled then you are either using a very old kernel which does not support Secmark, or a new kernel (2.6.30 or greater) that only supports Secmark. However, for those systems that do have the file, if the contents are “0” then Secmark is enabled, otherwise you are still using the older compat_net controls. If you want to enable Secmark you can do so by writing a “0” to the file but you may first want to ensure that your SELinux policy and iptables/netfilter toolchain are up to date and provides Secmark support.

# cat /selinux/compat_net
0

The first step in using Secmark is to define a new SELinux label for the network traffic we are labeling and write the corresponding SELinux policy to handle the newly labeled traffic. This highlights the major difference between compat_net and Secmark: with the compat_net controls you assign labels to ports and hosts, but with Secmark you label the packets themselves. The second step is to determine which network attributes you want to match on when you are labeling packets. Both compat_net and Secmark can match on any combination of port and host so you should be able to transition all of your existing compat_net rules to Secmark; the only difference here is that with compat_net each port and host entry received its own label but with Secmark each combination of port and host receives a label. In the example below, we are going to configure Secmark to label SSH packets from host foo.lan with the label “foo_ssh_packet_t” and allow it to connect to the SSH daemon running on our local system with label “sshd_t”. Careful observers will note that I currently have the MLS policy installed but the same procedure will work equally well with the default targeted policy.

Since we using a custom SELinux label the first thing we need to do is write a SELinux policy module to define the new type and the policy allowing this type to be received by the SSH daemon over the network. The policy we are using is shown below:

# policy header
policy_module(secmark_example,0.1.0)
gen_require(`
        type sshd_t;
')

# our new secmark packet type
type foo_ssh_packet_t;

# allow sshd_t to receive our new packet type
allow sshd_t foo_ssh_packet_t:packet recv;

We can quickly compile and install our new policy module with the following commands:

# cp /usr/share/selinux/devel/Makefile .
# make
Compiling mls secmark_example module
/usr/bin/checkmodule:  loading policy configuration from tmp/secmark_example.tmp
/usr/bin/checkmodule:  policy configuration loaded
/usr/bin/checkmodule:  writing binary representation (version 10) to 
tmp/secmark_example.mod
           
Creating mls secmark_example.pp policy package
rm tmp/secmark_example.mod tmp/secmark_example.mod.fc
# ls
Makefile            secmark_example.if  secmark_example.te
secmark_example.fc  secmark_example.pp  tmp
# semodule -i secmark_example.pp
# semodule -l | grep secmark_example
secmark_example 0.1.0

The next and final step is to setup the iptables/netfilter Secmark rules to label the packets correctly:

# host foo.lan
foo.lan has address 192.168.0.16
# iptables -t mangle -A INPUT -p tcp --src 192.168.0.16 --dport 22 -j SECMARK --selctx system_u:object_r:foo_ssh_packet_t:s0
# iptables -t mangle -L
Chain PREROUTING (policy ACCEPT)
target     prot opt source          destination

Chain INPUT (policy ACCEPT)
target     prot opt source          destination
SECMARK    tcp  --  foo.lan         anywhere     tcp dpt:ssh SECMARK selctx system_u:object_r:foo_ssh_packet_t:s0

Chain FORWARD (policy ACCEPT)
target     prot opt source          destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source          destination

Chain POSTROUTING (policy ACCEPT)
target     prot opt source          destination

At this point we are finished, packets coming from foo.lan and destined for port TCP/22 on our system will be labeled as “foo_ssh_packet_t” with SELinux providing assurance that only “sshd_t” can read “foo_ssh_packet_t” packets. You can verify this quite easily be removing the allow rule from the custom SELinux policy and watching SSH traffic from foo.lan stop, you will also see new SELinux AVC denial messages with the “foo_ssh_packet_t” type.

One final note, remember that with modern Linux Kernels there are two types of SELinux security labels assigned to a packet, the Secmark labels described here and the peer labels described previously. These two types of packet labels operate differently and subject to their own, independent set of SELinux access controls. The Secmark packet labels are used to represent the network attributes of a packet such as IP addresses and ports, while the peer packet labels are used to represent the security attributes of the sender such as the SELinux label of the process which generated the network packet.

NetLabel Address Selectors

One of the biggest differences between NetLabel and the labeled networking mechanisms of existing Trusted OSs is how outbound traffic is selected for labeling. Ever since NetLabel was first introduced in kernel 2.6.19 the on-the-wire outbound labeling protocol was determined by the label of the sending application’s socket. Despite the departure from legacy approaches, this was a concious choice designed to make the implementation smaller and less invasive in an attempt to gain acceptance into the mainstream Linux Kernel. While ultimately this approach proved to be successful, NetLabel was accepted, it did have its drawbacks. The most significant was that all traffic from a single socket, or application if it was not label aware, was limited to the same on-the-wire label; if you think about this for a minute you quickly realize how limiting this could become. Thankfully with kernel 2.6.28 this is no longer the case.

In kernel 2.6.28 we introduced the concept of address selectors to NetLabel. NetLabel address selectors allow administrators to specify the on-the-wire label format based on both the sending application and the destination address. This is a huge usability boost as administrators are no longer forced to use the same on-the-wire label format for a single application, making deployment much easier. To help make things a bit more concrete, let’s examine the following configuration:

# netlabelctl cipsov4 add pass doi:16 tags:1
# netlabelctl map add domain:apache_t protocol:cipsov4,16
# netlabelctl -p map list
Configured NetLabel domain mappings (2)
 domain: "apache_t"
   protocol: CIPSOv4, DOI = 16
 domain: DEFAULT
   protocol: UNLABELED

Before kernel 2.6.28 this is how you used to enabled NetLabel based labeling for an application. In this particular example we are configuring Apache (apache_t) to label outbound traffic with a CIPSO label using CIPSO DOI 16. This configuration would apply CIPSO labeling to all traffic regardless of destination; DNS queries, Windows clients, other Linux hosts, everything would receive CIPSO labeled traffic. However, starting with Linux Kernel 2.6.28 and NetLabel Tools 0.19 there are some new parameters to the netlabelctl map command:

# netlabelctl cipsov4 add pass doi:17 tags:5
# netlabelctl map add domain:firefox_t address:0.0.0.0/0 protocol:unlbl
# netlabelctl map add domain:firefox_t address:10.0.0.0/8 protocol:cipsov4,17
# netlabelctl map add domain:firefox_t address:192.168.4.5 protocol:cipsov4,16
# netlabelctl -p map list
Configured NetLabel domain mappings (3)
 domain: "apache_t"
   protocol: CIPSOv4, DOI = 16
 domain: "firefox_t"
   address: 192.168.4.5/32
    protocol: CIPSOv4, DOI = 16
   address: 10.0.0.0/8
    protocol: CIPSOv4, DOI = 17
   address: 0.0.0.0/0
    protocol: UNLABELED
 domain: DEFAULT
   protocol: UNLABELED

Using the new address option to the netlabelctl map command allows us to add a destination address selector to the NetLabel LSM domain mapping configuration. In this particular example we’ve configured Firefox (firefox_t) to send CIPSO DOI 16 labeled packets to host 192.168.4.5 and CIPSO DOI 17 labeled packets to the entire 10.0.0.0/8 network while everyone else, specified by 0.0.0.0/0, is sent unlabeled packets. You will also notice that the new address selectors can coexist with the existing configuration, e.g. our Apache configuration is untouched, but you can only add address selectors to domains which make use of address selectors, e.g. you can’t add address selectors to the above Apache configuration. If you want to add address selectors to an existing domain configuration you first need to delete it and recreate it using address selectors as show below:

# netlabelctl map add domain:apache_t address:192.168.3.5 protocol:unlbl
netlabelctl: error, invalid argument or parameter
# netlabelctl map del domain:apache_t
# netlabelctl map add domain:apache_t address:0.0.0.0/0 protocol:cipsov4,16
# netlabelctl map add domain:apache_t address:192.168.3.5 protocol:unlbl
# netlabelctl -p map list
Configured NetLabel domain mappings (3)
 domain: "apache_t"
   address: 192.168.3.5/32
    protocol: UNLABELED
   address: 0.0.0.0/0
    protocol: CIPSOv4, DOI = 16
 domain: "firefox_t"
   address: 192.168.4.5/32
    protocol: CIPSOv4, DOI = 16
   address: 10.0.0.0/8
    protocol: CIPSOv4, DOI = 17
   address: 0.0.0.0/0
    protocol: UNLABELED
 domain: DEFAULT
   protocol: UNLABELED

That covers the new NetLabel address selectors in kernel 2.6.28, however there are a few things you should make note of before you start playing with this new feature. The most important is that the address selectors require NetLabel Tools version 0.19 or higher, information on where to get version 0.19 can be found here. The next thing to keep in mind is that when you are configuring NetLabel using address selectors you will almost always want to have a 0.0.0.0/0 address configured as a “catch all” address, otherwise you could run into problems if you try to send traffic to an address not explicitly configured. Other than that, have fun and let me know if you have any problems in the comments.

Network Ingress/Egress Controls

I’ve already talked about Fallback Labels and Network Peer Controls now I’m going to explain the last major labeled networking feature included in the 2.6.25 release of the Linux Kernel, the network ingress/egress controls. One of the longstanding problems with the SELinux network access controls was that they lacked any ability to control packets at the network interface level, limiting our ability to provide access control based on the physical network and making it impossible to provide access control for forwarded packets. The network ingress/egress controls were designed to solve these problems by placing SELinux network access controls at the network interface level.

The new ingress/egress controls are fairly simple: each packet entering the system must pass an ingress access control and each packet leaving the system must pass an egress access control. Forwarded packets must also pass an additional forwarding access control, but I’m going to leave that for another time and focus on the ingress/egress cases. In both the ingress and egress access controls the network packet is subjected to network interface and network address checks. In order to allow incoming network traffic to enter the system you need the following two allow rules (where network_if_t is the network interface’s label, network_addr_t is the remote host’s network address label and peer_t is the traffic’s peer label):

allow peer_t network_if_t:netif ingress;
allow peer_t network_addr_t:node recvfrom;

Network traffic leaving the system requires similar allow rules:

allow peer_t network_if_t:netif egress;
allow peer_t network_addr_t:node sendto;

As a reminder the peer labels, peer_t in the above examples, are derived from the original sender’s security label. In the case of incoming network traffic the peer label is taken from the network labeling protocol, if available, and in the case of outgoing network traffic the peer label is taken from the application/socket which generated the traffic. The network interface and address labels are defined as part of the SELinux policy and can be modified on-the-fly using the semanage tool.

Moving on to a more concrete example, imagine a web server running Apache with the eth0 network interface labeled as “wwwsrv_if_t”, no explicitly labeled network addresses (which means addresses will be represented by the default address label, “node_t”) and a “private_net_t” fallback label defined for addresses that match 192.168.0.0/16. If we want to allow network traffic from 192.168.0.0/16 over the eth0 interface into the system and allow responses back from Apache we need to write the following allow rules:

allow private_net_t wwwsrv_if_t:netif ingress;
allow private_net_t node_t:node recvfrom;
allow apache_t wwwsrv_if_t:netif egress;
allow apache_t node_t:node sendto;

Like the new network peer controls, the ingress/egress controls are currently not enabled, protected by the same policy capability flag which protect the peer controls, “network_peer_controls”. I am currently testing a small patch which should solve the remaining policy issues and allow us to finally enable this new functionality. In the meantime if you want to try it yourself you need to uncomment the “policycap network_peer_controls;” line in the policy_capabilities file found in the SELinux Reference Policy sources and rebuild the policy. Good luck, and if you hit any snags don’t hesitate to drop a comment below and I’ll help you sort it out.