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