Full Labels over Loopback with CIPSO

Perhaps one of the largest shortcomings of the CIPSO network labeling protocol when used with SELinux is the fact that it can only convey the SELinux MLS attributes across the network. There are plenty of good reasons for this: strict conformance with protocol specification, limited space in the IPv4 header, interoperability with non-SELinux systems, etc. However, regardless of the reasons why, there will always be use cases where it would be very nice to have the full SELinux label without the performance, scalability and management overhead of labeled IPsec. While I can't say I have solution for all of those use cases, today I am going to let you in on one of NetLabel's best kept secrets: NetLabel and CIPSO can convey the full SELinux label over local connections, and it has been able to do so for years.

Now, before I got into the details of "how", I just want to be clear that what I'm about to tell you isn't really a secret, it just hasn't been very well publicized. After all, I'm not sure how it could be a secret when the code is available for anyone and everyone to review and inspect. Further, the "how" is even documented in the netlabelctl manpage, so if this capability is NetLabel's best kept secret it has clearly been hiding in plain sight.

Enough of the "secret", let's explain how to get this working. First off, if you're going to try this on your own system (any modern Linux distribution that supports SELinux and NetLabel should work), you might want to grab a copy of the getpeercon_server test tool that I've used in the example below; instructions for building the test tool are at the top of the file. Once you've got everything built and you've verified that your SELinux and NetLabel installation is working as you would expect, you need to start off by configuring the CIPSO Domains Of Interpretation (DOI). For this example we are going to create two DOIs, one using the standard, MLS-only passthrough type and the other using the local-connection-only full SELinux type. Don't forget that you can always check the netlabelctl manpage for more information on the commands below.

# netlabelctl cipsov4 add pass doi:1 tags:1
# netlabelctl cipsov4 add local doi:2
# netlabelctl -p cipsov4 list
Configured CIPSOv4 mappings (2)
 DOI value : 1                                                                  
   mapping type : PASS_THROUGH                                                  
 DOI value : 2                                                                  
   mapping type : LOCAL

After you've setup the CIPSO DOI's you need to configure NetLabel to send traffic using these new DOIs. In our example we are going to configure the system such that traffic sent to 127.0.0.1 (localhost) will use DOI #2, the local-only full SELinux label DOI, and traffic sent to 10.250.2.92 (the system's eth0 address) will use DOI #1, the normal MLS-only DOI. Don't forget that we first need to remove the default unlabeled mapping so we can use the address selectors.

# netlabelctl map del default
# netlabelctl map add default address:0.0.0.0/0 protocol:unlbl                  
                                                        
# netlabelctl map add default address:::/0 protocol:unlbl
# netlabelctl map add default address:127.0.0.1 protocol:cipsov4,2
# netlabelctl map add default address:10.250.2.92 protocol:cipsov4,1
# netlabelctl -p map list
Configured NetLabel domain mappings (1)                                         
 domain: DEFAULT                                                                
   address: 127.0.0.1/32                                                        
    protocol: CIPSOv4, DOI = 2
   address: 10.250.2.92/32
    protocol: CIPSOv4, DOI = 1
   address: 0.0.0.0/0
    protocol: UNLABELED
   address: ::/0
    protocol: UNLABELED

With that we're finished, now it's time to test it out. Testing is quite simple, make sure you have a TCP client like telnet or netcat installed and then start the getpeercon_server test tool you built earlier; in this example getpeercon_server is listening on TCP port 5000.

# ./getpeercon_server 5000
-> running as unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
-> creating socket ... ok
-> listening on TCP port 5000 ... ok
-> waiting ... connect(10.250.2.92,system_u:object_r:netlabel_peer_t:s0)
Connected to 10.250.2.92:5000
-> connection closed
-> waiting ... 
connect(127.0.0.1,unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023)
Connected to 127.0.0.1 5000
-> connection closed
-> waiting ...

It works! When we connected to 10.250.2.92 we saw the familiar "netlabel_peer_t" type, but when we connected to 127.0.0.1 we saw the full SELinux label of our telnet client process, "unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023". Go ahead and try it out for yourself, just remember that you can only use the "local" DOI type on network connections that run over the loopback network interface.