Recently, I’ve been working on replacing the per-packet networking controls in SELinux. The kernel patches have been submitted upstream, and are available for use now as of 2.6.17-rc4-mm3.
The main reason for replacing the original controls is that they provided only a rudimentary form of static packet filtering, without utilizing any of the existing infrastructure offered by Netfilter/iptables. They also had a limited set of packet selectors. For an incoming packet, you could select on network interface, source address and source port; similar for an outgoing packet, but instead selecting on destination address and port. These selectors were hard-coded into the kernel labeling and enforcement mechanisms, as well as the security policy language. The latter was generally problematic as the types of security policies that people like to apply to traffic flow did not seem to map readily to the SELinux policy language constructs. While socket-based controls such as name_connect help somewhat, they do not provide a complete solution.
After investigating several approaches which turned out to have major drawbacks, including a major overhaul of the SELinux policy language constructs for networking, and pushing all labeling and access control out to iptables, a clean solution arose out of discussions at the recent SELinux developer summit.
The idea is to separate labeling and enforcement.
Specifically: use iptables to select and label packets, then use SELinux to enforce security policy using these packet labels. This utilizes the expressiveness of iptables rulesets, as well as the flexibility of any its many matches and targets, and powerful components such as connection tracking. At the same time, enforcement of security policy remains the responsibility of the SELinux AVC, and access control rules can be meaningfully analyzed as part of overall SELinux policy analysis. See figure 1 below for a simplified view of the new architecture.
iptables SELinux AVC
-------------------------------- ----------------------------------
| | | |
| [ selection ] [ labeling ] | | [ access control enforcement ] |
| | | |
-------------------------------- ----------------------------------
| | |
>--------x-------------x---------------------> [ allowed? ] -------> accept
packet flow |
v
drop
Fig. 1
To facilitate this, a new field called secmark was added to the sk_buff structure used by the kernel to encapsulate network packets, aka the ‘skb’ or confusingly, skbuff. This is similar to the nfmark field used by the iptables MARK target for implementing a variety of network policy schemes, although secmark is specifically used by security subsystems for implementing security policy. It was added as a separate field to ensure that security policy and network policy (e.g. routing) can be managed independently. The network structures have been put on diets lately, so in adding this new field, DaveM has required me to volunteer to do some further reduction of the sk_buff structure in the future.
This secmark field is not specific to SELinux, or even iptables. It can potentially be used by other security subsystems and labeling mechanisms if required in the future.
For SELinux, we’ve now implemented a new object class, packet, and two permissions for controlling domain access to packets: send and recv. Another permission, relabelto, was added to the packet class, to control how domains can apply specific labels to packets.
Here’s an example of an access control rule with the new scheme:
allow httpd_t httpd_packet_t:packet { recv send };
Translated into English, it means: allow the domain httpd_t to send and receive packets labeled as httpd_packet_t. Where, typically, apache would run in the domain httpd_t. And that’s it entirely, for the SELinux access control rule.
The heavy lifting is done by iptables and the Netfilter framework, in deciding which packets to label as httpd_packet_t.
This is done with standard iptables matches for selecting packets, in conjunction with the new SECMARK and optionally CONNSECMARK targets. The SECMARK target simply labels packets with any valid security context.
iptables -t mangle -A INPUT -p tcp --dport 80 -j SECMARK --selctx system_u:object_r:httpd_packet_t:s0
iptables -t mangle -A OUTPUT -p tcp --sport 80 -j SECMARK --selctx system_u:object_r:httpd_packet_t:s0
In the above rather simplistic view, we are statically labeling packets arriving at and leaving from port 80 as httpd_packet_t, so that SELinux can then apply security policy to the packets based on these labels.
Note that we don’t have to just use the port selector here, we could also specify the interface, and also have different labels to identify different types of httpd packets (e.g. httpd_external_packet_t vs. httpd_internal_packet_t). These selection and labeling decisions can now be entirely specified with iptables syntax, not requiring any change to the SELinux policy language format. Any future selectors made available for iptables can be immediately put to use without changing any of the SELinux mechanism. So, we have labeling flexibility, which is much more in line with the general SELinux/Flask design goal of policy-flexibility.
This example is deliberately simplified, to demonstrate a the basic mechanism of selecting and labeling packets for SELinux. It has several drawbacks, some similar to the old controls, such as not filtering the ephemeral ports used on the connection (e.g. the source port chosen by the kernel of the client), nor handling ICMP messages specific to the connection.
With a relatively simple change to the iptables ruleset, using the new CONNSECMARK target, we can fully leverage the power of conntrack (or stateful inspection), so that only packets which are verified to be part of the connection or related to it are labeled as httpd_packet_t. This increases the security assurance of the new networking controls significantly, as well as making it extremely simple to specify SELinux policy for “complex” protocols such as FTP, which can open up multiple connections and operate in multiple modes (e.g. active vs. passive). As long as there are working conntrack protocol helpers available, SELinux can now leverage them, while knowing nothing about them.
The CONNSECMARK target is similar to the CONNMARK target, in that it deals with labels on connections, instead of just packets. With CONNSECMARK, you can either ‘save’ a packet mark to a connection or ‘restore’ a connection label to a packet. The first is used when a connection is setup, and the latter for all subsequent packets on the connection. The state match can be used to also specify the labeling of ‘related’ packets, such as ICMP packets or various protocol-specific packets such as those on a related FTP data connection.
Here’s an example of an iptables ruleset for vsftpd (derived from these testscripts):
# Ensure the FTP helper is loaded
modprobe ip_conntrack_ftp
# Create a chain for connection setup marking
iptables -t mangle -N SEL_FTPD
# Accept incoming connections, label SYN packets, and copy
# labels to connections.
iptables -t mangle -A INPUT -p tcp --dport 21 -m state --state NEW -j SEL_FTPD
iptables -t mangle -A SEL_FTPD -j SECMARK --selctx system_u:object_r:ftpd_packet_t:s0
iptables -t mangle -A SEL_FTPD -j CONNSECMARK --save
iptables -t mangle -A SEL_FTPD -j ACCEPT
# Common rules which copy connection labels to established
# and related packets.
iptables -t mangle -A INPUT -m state --state ESTABLISHED,RELATED -j CONNSECMARK --restore
iptables -t mangle -A OUTPUT -m state --state ESTABLISHED,RELATED -j CONNSECMARK --restore
Now, while there are a few more rules here, two of which are common to all services, connection tracking is now being utilized, so, we get verification of the TCP handshake, TCP window tracking, labeling only for packets with valid ephemeral ports, and automatic labeling of related ICMP and FTP data connections.
The SELinux policy remains identical. We’re just using iptables/netfilter to determine what an ftpd packet is. SELinux just decides then whether a domain may send and/or receive the packet.
Here is the associated SELinux access control policy for vsftpd network traffic:
allow ftpd_t ftpd_packet_t:packet { recv send };
The full policy module for this, which includes a type definition for ftpd_packet_t and a few required odds and ends is taken from this directory of sample policies.
The SECMARK and CONNSECMARK targets cover both IPv4 and IPv6, so the above policy would also cover both protocols if the iptables rules are configured to label both protocols identically. The only catch for IPv6 at the moment is that the generic conntrack framework is not yet complete, and in fact, you cannot select it in the kernel concurrently with the old IP conntrack code. So, for the time being, you will either need to use the more limited NF conntrack code (which does include TCP and UDP support, but not an FTP helper); or use the IP conntrack code (recommended), which provides full coverage of IPv4, but requires static configuration for IPv6.
It should be very simple to programmatically generate iptables rules for services, and then include them with packages, along with their modular policy files. I can imagine a simple GUI tool, which, for each service, asks which port(s) and protocol(s) the service uses, and generates a standard ruleset. For common services, this information could be built into an internal database, along with known protocol helpers. Then, the admin could add further iptables selectors visually, such as network interface, IP address/mask etc. (And of course, have “revert to last state” and “revert to defaults” buttons).
To use this stuff, the easiest thing is to wait for your distro to roll it out, which will hopefully happen not long after the release of the 2.6.18 kernel, all things going well.
If you want to dive in now, grab the -mm3 kernel (or whichever latest kernel has the patches) and install it. Make sure you select the SECMARK and CONNSECMARK targets. The latter requires you to select ‘Connection tracking security mark support’.
In the SELinux config menu, I’d suggest leaving “NSA SELinux enable new secmark network controls by default” unchecked initially and overriding it manually with the selinux_compat_net boot parameter as required. Set it to ‘0’ to enable the new secmark controls, but not yet! (A related side note: try to avoid submitting a patch which by default, breaks Andrew Morton’s machine — it makes him Hulk angry!) You should now be able to boot into the new kernel with the old networking controls.
The next thing is to download the archive at:
http://people.redhat.com/jmorris/selinux/secmark/
This contains everything you need for userspace support, including patches for iptables, SELinux library patches, sample policies and Netfilter rulesets. You’ll need to apply the patches and rebuild your SELinux toolchain starting with libselinux and checkpolicy, then rebuild your policy and install it. Verify that it has 57 classes (see my previous entry on how to do that).
If this looks ok, you can try enabling the new network controls without any labeling rules or policy (echo 0 > /selinux/compat_net). For this, you need to go into permissive mode (or wait until you have good policy). By default, all traffic will now be marked as unlabeled_t, and you should see some AVC denials. From there, you can experiment with the example iptables rules and SELinux policies, until you have something which will work in enforcing mode.
To allow all unlabeled packets to all domains, the access rule looks like:
allow domain unlabeled_t:packet { send recv };
which is obviously not secure.
Of course, the above is just for people who want to do development and experimentation, not normal users: they will get the new controls and userspace updated properly via a yum or whatever their distro uses, once their distro folk have integrated all of the changes.
Please direct any questions or comments to the SELinux mailing list.