Running GUI Applications in a container with SELinux

As I work more and more with containers, I find myself wanting to make more use of them to segregate running third party apps. Taking the lead of Jessie Frazelle I figured I would try to run the Minecraft client in a Container on Fedora 25. As expected, it was a learning experience, but I got it. Here’s the summary:

I started with Wakaru Himura’s docker-minecraft-client Dockerfile. which was written for Ubuntu. When it didn’t work for me, I started trying for a Fedora based one. It took a couple iterations.

The error indicated that the container was having trouble connecting to, or communicating with, the Unix domain socket used by the X server. It was returjned bythe Java code, and here is an abbreviated version of the stack trace.

You can customize the options to run it in the run.sh and rebuilding the image
No protocol specified
Exception in thread "main" java.lang.InternalError: Can't connect to X11 window server using ':1' as the value of the DISPLAY variable.
	at sun.awt.X11GraphicsEnvironment.initDisplay(Native Method)
	at sun.awt.X11GraphicsEnvironment.access$200(X11GraphicsEnvironment.java:65)
	at sun.awt.X11GraphicsEnvironment$1.run(X11GraphicsEnvironment.java:110)
	at java.security.AccessController.doPrivileged(Native Method)
	at sun.awt.X11GraphicsEnvironment.(X11GraphicsEnvironment.java:74)
        . . .
	at net.minecraft.bootstrap.Bootstrap.main(Bootstrap.java:378)

Wakaru’s version uses the Oracle JDK. I’ve been running the OpenJDK from Fedora with Minecraft with few problesm, so I simplified the Java install.

I ended up doing a lot of trial and error to get the X authorization code to find the right information from the parent. One thing I tried was the creation of a user inside the container, to mirror my account outside the container, using Wakaru’s code, but modifying it for my personal account.

FROM index.docker.io/fedora:25
MAINTAINER Adam Young <adam@younglogic.com>

RUN dnf -y install java-1.8.0-openjdk java-1.8.0-openjdk-devel java-1.8.0-openjdk-headless
RUN dnf -y  install strace  xorg-x11-xauth
RUN  dnf -y clean all
COPY Minecraft.jar ./

RUN export uid=14370 gid=14370 && \
    mkdir -p /home/ayoung && \
    echo "ayoung:x:${uid}:${gid}:ayoung,,,:/home/ayoung:/bin/bash" >> /etc/passwd && \
    echo "ayoung:x:${uid}:" >> /etc/group  && \
    chown ${uid}:${gid} -R /home/ayoung


CMD XAUTHORITY=~/.Xauthority  /usr/bin/java -jar ./Minecraft.jar

Running that still gave the error from the X server connection. THe audit log shows that SELinux was denying the connection.

$  sudo tail -f  /var/log/audit/audit.log | grep avc
type=AVC msg=audit(1485401508.386:1616): avc:  denied  { connectto } for  pid=18405 comm="java" path=002F746D702F2E5831312D756E69782F5831 scontext=system_u:system_r:container_t:s0:c151,c769 tcontext=unconfined_u:unconfined_r:xserver_t:s0-s0:c0.c1023 tclass=unix_stream_socket permissive=0

Disable SELinux (For now) to see if we can get a success.

sudo setenforce permissive

And run like this

 docker run -ti --rm -e DISPLAY --user ayoung:ayoung -v /run/user/14370/gdm/Xauthority:/run/user/14370/gdm/Xauthority -v /tmp/.X11-unix:/tmp/.X11-unix -v /home/ayoung/.Xauthority:/home/ayoung/.Xauthority --net=host minecraft

Et, Viola!:

OK. Let’s deal with SELinux. First, re-enable, and confirm.

$ sudo setenforce enforcing
$ getenforce 
Enforcing

Let’s break apart the AVC: Gentoo’s page has a good summary of the pieces:

Log part Meaning
denied { connectto } The attempt to connect to was denied
for pid=18405 The ID of the process that triggered the AVC. Not useful now, as the process has since been terminated.
comm=”java” The command exec’ed by the process that triggered the AVC. This comes from the line run in the container: CMD XAUTHORITY=~/.Xauthority /usr/bin/java -jar ./Minecraft.jar
path=002F746D702F2E5831312D756E69782F5831 path=/tmp/.X11-unix/X1 in Hex. Can be deciphered using:
/bin/python3 -c ‘print(bytearray.fromhex(“‘$1′”).decode())’
scontext= Security context of the process. IN parts…
— system_u: The system user, since dockerd is running from systemd
— container_t: The container target. Docker specific resources have this label
— s0: User is Level 0
— c151,c769 User context.
tcontext= The security context of the target unix Domain Socket
— unconfined_u: Unconfined User. Me. since it was run from me booting my system, which came from a user prompt.
— unconfined_r: Unconfied Role.
— xserver_t: X server lable, to keep all of X’s resources labeled the same way.
— s0-s0: Target is Level 0
— c0.c1023 Target is Context Subcontext.
tclass=unix_stream_socket Target Class shows it is a Domain socket
permissive=0 SELinux was not running in permissive mode.

More information on the Levels and contexts is available for those who wish to understand them, but I didn’t need them for this. They are used by other access control tools, and we are not going to bother with it for the Fedora desktop system.

When dealing with SELinux problems, we have a couple tools in the toolkit.

  • We can change the context of the caller
  • We can change the labels
  • we can change the policy

Of the three, changing the policy is most common. We don’t want to break existing policy, so we need a new rule that says that containers can talk to domains sockets for the xserver. That policy looks like this:

(allow container_t xserver_t (unix_stream_socket (connectto)))

Else where, we’ve seen that the connection reads the X rules in the Xauthority file, which I have pointing to ~/.Xauthority so a second rule makes that part happy. Here is my complete mycontainer.cil file

(allow container_t xserver_t (unix_stream_socket (connectto)))
(allow container_t user_home_t (dir (read)))

Add that to the systems policy with:

sudo  semodule -i mycontainer.cil

Re-enable SELinux enforeing and run the docker file, and it all works.

It took a lot of troubleshooting to get to that point. Special thanks to grift in #selinux for helping with the policy.

Below this is my raw notes and logs, mostly kept for my own historical perspective. There were a few comasnds I used that I will want to look at again. I have the IORC log and out put from more commands below, too.

The file in question is:

$ ls -Z /tmp/.X11-unix/
 system_u:object_r:user_tmp_t:s0 X0 unconfined_u:object_r:user_tmp_t:s0 X1

And we’ll relabel X1.  Since  this is a test, we’ll do a temporary relabel.  Worst case, everything locks up and we have to reboot.

First I better save this post.  I might get locked out….

$ sudo chcon -t container_t /tmp/.X11-unix/X1
[sudo] password for ayoung: 
chcon: failed to change context of '/tmp/.X11-unix/X1' to ‘unconfined_u:object_r:container_t:s0’: Permission denied

Hmmm.  In the Audit log:

type=AVC msg=audit(1485434178.292:1753): avc: denied { relabelto } for pid=28244 comm="chcon" name="X1" dev="tmpfs" ino=47315 scontext=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 tcontext=unconfined_u:object_r:container_t:s0 tclass=sock_file permissive=0

To test, I am going to disable SELinux, relabel, then re-enable it.

 

$ chcon -t container_t /tmp/.X11-unix/X1
$ sudo ls -Z /tmp/.X11-unix/
     system_u:object_r:user_tmp_t:s0 X0  unconfined_u:object_r:container_t:s0 X1
$ sudo setenforce enforcing
$ docker run -ti --rm -e DISPLAY --user ayoung:ayoung -v /run/user/14370/gdm/Xauthority:/run/user/14370/gdm/Xauthority -v /tmp/.X11-unix:/tmp/.X11-unix -v /home/ayoung/.Xauthority:/home/ayoung/.Xauthority --net=host minecraft
Exception in thread "main" java.awt.AWTError: Can't connect to X11 window server using ':1' as the value of the DISPLAY variable.

and in the audit log:

type=AVC msg=audit(1485434417.111:1789): avc: denied { connectto } for pid=28511 comm="java" path=002F746D702F2E5831312D756E69782F5831 scontext=system_u:system_r:container_t:s0:c124,c220 tcontext=unconfined_u:unconfined_r:xserver_t:s0-s0:c0.c1023 tclass=unix_stream_socket permissive=0

It still has the xserver_t label. Looking at it via ls:

$ sudo ls -Z /tmp/.X11-unix/
ls: cannot access '/tmp/.X11-unix/X1': Permission denied
system_u:object_r:user_tmp_t:s0 X0 (null) X1

Ooops. Probably got lucky there we didn’t crash. Lets reset it,

$ sudo setenforce permissive
$ sudo ls -Z /tmp/.X11-unix/
     system_u:object_r:user_tmp_t:s0 X0  unconfined_u:object_r:container_t:s0 X1
$ chcon -t user_tmp_t /tmp/.X11-unix/X1
$ sudo ls -Z /tmp/.X11-unix/
    system_u:object_r:user_tmp_t:s0 X0	unconfined_u:object_r:user_tmp_t:s0 X1
$ sudo setenforce enforcing
$ sudo ls -Z /tmp/.X11-unix/
    system_u:object_r:user_tmp_t:s0 X0	unconfined_u:object_r:user_tmp_t:s0 X1

Since we can’t do ls, it is safe to assume that user launched X processes will also not be able to connect to the socket. But, since the labels don’t match, I am going to assume, also, that we are looking at the wrong file. The target that was denied had a label of xserver_t, and this had user_tmp_t.

Perhaps it was something in /run/user/14370/gdm/Xauthority ? Let’s look.

$ ls -Z /run/user/14370/gdm/Xauthority
unconfined_u:object_r:user_tmp_t:s0 /run/user/14370/gdm/Xauthority

Nope. That path=002F746D702F2E5831312D756E69782F5831 must be pointing somewhere else. Let’s see if it is an inode.

$  find / -inum 002F746D702F2E5831312D756E69782F5831
find: invalid argument `002F746D702F2E5831312D756E69782F5831' to `-inum'

Nope. What is it?

Here is a hint:

$ sudo netstat -xa | grep X11-unix
unix  2      [ ACC ]     STREAM     LISTENING     29431    @/tmp/.X11-unix/X0
unix  2      [ ACC ]     STREAM     LISTENING     47313    @/tmp/.X11-unix/X1
unix  2      [ ACC ]     STREAM     LISTENING     47314    /tmp/.X11-unix/X1
unix  2      [ ACC ]     STREAM     LISTENING     29432    /tmp/.X11-unix/X0
unix  3      [ ]         STREAM     CONNECTED     1612587  @/tmp/.X11-unix/X1
unix  3      [ ]         STREAM     CONNECTED     40628    @/tmp/.X11-unix/X1
unix  3      [ ]         STREAM     CONNECTED     42628    @/tmp/.X11-unix/X1
unix  3      [ ]         STREAM     CONNECTED     37201    @/tmp/.X11-unix/X0
unix  3      [ ]         STREAM     CONNECTED     30693    @/tmp/.X11-unix/X0
unix  3      [ ]         STREAM     CONNECTED     395122   @/tmp/.X11-unix/X1
unix  3      [ ]         STREAM     CONNECTED     38481    @/tmp/.X11-unix/X1
unix  3      [ ]         STREAM     CONNECTED     1614636  @/tmp/.X11-unix/X1
unix  3      [ ]         STREAM     CONNECTED     38798    @/tmp/.X11-unix/X1
unix  3      [ ]         STREAM     CONNECTED     1714036  @/tmp/.X11-unix/X1
unix  3      [ ]         STREAM     CONNECTED     353557   @/tmp/.X11-unix/X1
unix  3      [ ]         STREAM     CONNECTED     30688    @/tmp/.X11-unix/X0
unix  3      [ ]         STREAM     CONNECTED     42436    @/tmp/.X11-unix/X1
unix  3      [ ]         STREAM     CONNECTED     36697    @/tmp/.X11-unix/X0
unix  3      [ ]         STREAM     CONNECTED     42430    @/tmp/.X11-unix/X1
unix  3      [ ]         STREAM     CONNECTED     42363    @/tmp/.X11-unix/X1
unix  3      [ ]         STREAM     CONNECTED     37936    @/tmp/.X11-unix/X0
unix  3      [ ]         STREAM     CONNECTED     3713614  @/tmp/.X11-unix/X1
unix  3      [ ]         STREAM     CONNECTED     55540    @/tmp/.X11-unix/X1
unix  3      [ ]         STREAM     CONNECTED     48315    @/tmp/.X11-unix/X1
unix  3      [ ]         STREAM     CONNECTED     38629    @/tmp/.X11-unix/X1
unix  3      [ ]         STREAM     CONNECTED     3204892  @/tmp/.X11-unix/X1
unix  3      [ ]         STREAM     CONNECTED     49616    @/tmp/.X11-unix/X1
unix  3      [ ]         STREAM     CONNECTED     1612589  @/tmp/.X11-unix/X1
unix  3      [ ]         STREAM     CONNECTED     1709056  @/tmp/.X11-unix/X1

Resorting to IRC, got some help in #selinux:

grift: path=/tmp/.X11-unix/X1
       /bin/python3 -c 'print(bytearray.fromhex("'$1'").decode())'
       thats now selinux deals with stream connect
        the "connectto" check is done on the process listening on the socket
        basically stream connect/dgram sendto is a two step thing
        1. step on connectto sendto process listening on the socket respectively
        2. step two writing the actual sock file
        allow container_t xserver_t:unix_stream_socket connectto;
        allow container_t user_tmp_t:sock_file write
        you might want to run semodule -DB before you try it
        fedora is kind of quick to hide events

Here is some of the output from the audit log:

    type=AVC msg=audit(1485447098.800:1983): avc:  denied  { connectto } for  pid=1455 comm="java" path=002F746D702F2E5831312D756E69782F5831 scontext=system_u:system_r:container_t:s0:c250,c486 tcontext=unconfined_u:unconfined_r:xserver_t:s0-s0:c0.c1023 tclass=unix_stream_socket permissive=1
    type=AVC msg=audit(1485447098.800:1984): avc:  denied  { read } for  pid=1455 comm="java" name=".Xauthority" dev="dm-1" ino=393222 scontext=system_u:system_r:container_t:s0:c250,c486 tcontext=system_u:object_r:user_home_t:s0 tclass=dir permissive=1

The convo continued:

grift: echo "(allow container_t xserver_t (unix_stream_socket (connectto)))" > mycontainer.cil
       theres another one that might be related (or not)
       where "java" lists ~/.Xauthority
       echo " (allow container_t user_home_t (dir (read)))" >> mycontainer.cil && semodule -i mycontainer.cil
       run semodule -B to hide it again
       if everything works now for you

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.