Granting Capabilities Using capsh

On Linux it sometimes is useful to grant privileged capabilities to binaries without running them as root. Usually this is achieved by setting capabilities on the file through setcap. However, this has some complications with environment variables and capabilities inheritance of child processes. We can use capsh instead of setcap to achieve what we want using ambient capabilities.

Using setcap, we might add a capability to a file as follows:

$ sudo setcap cap_net_raw+epi /path/to/wine 

However, for security reasons, when a file has capabilities set, all insecure environment variables are removed upon executing it. This includes variables such as LD_LIBRARY_PATH. Binaries depending on this variable to find dynamic libraries will fail to execute. This is often the case with OpenGL drivers. I encountered this when attempting to play a game through Wine that used ICMP—having set cap_net_raw+epi on the wine binary (or wine-preloader), OpenGL could no longer be found, resulting in:

0009:err:wgl:X11DRV_WineGL_InitOpenglInfo  couldn't initialize OpenGL, expect problems
failed to initialise opengl

This can be fixed ad-hoc by patching the binary with patchelf and adding the OpenGL library location, for example:

$ patchelf --print-rpath /path/to/wine
/some/lib:/some/other/lib

$ echo $LD_LIBRARY_PATH
/run/opengl-driver/lib:/run/opengl-driver-32/lib

$ patchelf --set-rpath "/run/opengl-driver/lib:/run/opengl-driver-32/lib:/some/lib:/some/other/lib" /path/to/wine

However, it is a pain to do this for all binaries you might want to give capabilities. The Linux kernel allows ambient capabilities where all child processes inherit the capabilities implicitly, removing the need to set them on binaries. Using capsh we can get a shell with ambient capabilities set:

$ sudo capsh --caps="cap_net_raw+eip cap_setpcap,cap_setuid,cap_setgid+ep" --keep=1 --user=thomas --addamb=cap_net_raw --
bash: /root/.bashrc: Permission denied

$ capsh --print
capsh --print
Current: = cap_net_raw+eip
Bounding set =...
Ambient set =cap_net_raw
Securebits: 00/0x0/1'b0
 secure-noroot: no (unlocked)
 secure-no-suid-fixup: no (unlocked)
 secure-keep-caps: no (unlocked)
 secure-no-ambient-raise: no (unlocked)
uid=1000(thomas)
gid=100(users)
groups=1(wheel),57(networkmanager),100(users)

$ whoami
thomas

$ echo $USER
root

We now have cap_net_raw+eip in our capabilities, and it is also in our ambient set. Something unfortunate occurs here with environment variables, though. We are user thomas, but we have inherited the environment variables of root. This is easily solved. We can export our current environment variables before performing capsh, and then source them back once in the privileged shell:

$ export -p > ./savedenv
$ sudo capsh --caps="cap_net_raw+eip cap_setpcap,cap_setuid,cap_setgid+ep" --keep=1 --user=thomas --addamb=cap_net_raw -- -c "source ./savedenv; rm ./savedenv; /usr/bin/env bash"

$ whoami
thomas

$ echo $USER
thomas

This works, but admittedly the way the environment variables are restored is quite hacky. In any case, you can now launch processes from this shell, and they’ll inherit your capabilities.

$ wine ping example.com
Pinging example.com [93.184.216.34] with 32 bytes of data:
Reply from 93.184.216.34: bytes=32 time=87ms TTL=57
Reply from 93.184.216.34: bytes=32 time=90ms TTL=57
Reply from 93.184.216.34: bytes=32 time=87ms TTL=57
Reply from 93.184.216.34: bytes=32 time=89ms TTL=57

Ping statistics for 93.184.216.34
	Packets: Sent = 4, Received = 4, Lost = 0 (0% loss)
Approximate round trip times in milli-seconds:
	Minimum = 87ms, Maximum = 90ms, Average = 88ms

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.