必要がなくなっている、というか特定の条件では CAP_NET_RAW は不要という話。

ある講義で Capability について話す機会があり、いつもどおり CAP_NET_RAW が必要な ping を使った実験をしていたところ、環境によっては CAP_NET_RAW File Capability を与えなくても ping が実行できることに気づき、少し調べてみました。

どうも随分前に net.ipv4.ping_group_range というカーネルパラメータが追加されていたらしく、これを設定することで ICMP ソケットを扱うグループを指定できるため、 CAP_NET_RAWsetuid なしで実行できるようになっていたようでした。

以下、調べたことを書いておきます。


ubuntu 18.04 では CAP_NET_RAW なしでは動かないのに対し、ubuntu 20.04 では CAP_NET_RAW がなくても動くようでした。

まずは ubuntu 18.04 で確認してみます。

ubuntu@18.04:~$ uname -a
Linux 18.04 4.15.0-109-generic #110-Ubuntu SMP Tue Jun 23 02:39:32 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
ubuntu@18.04:~$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=18.04
DISTRIB_CODENAME=bionic
DISTRIB_DESCRIPTION="Ubuntu 18.04.4 LTS"

ubuntu@18.04:~$ cp `which ping` myping
ubuntu@18.04:~$ ls -l myping
-rwxr-xr-x 1 ubuntu ubuntu 64424 Nov  9 11:21 myping

# CAP_NET_RAW がないので失敗する
ubuntu@20.04:~$ getcap myping
ubuntu@18.04:~$ ./myping -c 1 8.8.8.8
ping: socket: Operation not permitted

# CAP_NET_RAW を付与すると成功する
ubuntu@18.04:~$ sudo setcap 'cap_net_raw+ep' myping
ubuntu@18.04:~$ ./myping -c 1 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=254 time=26.5 ms

--- 8.8.8.8 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 26.584/26.584/26.584/0.000 ms

次に ubuntu 20.04 で確認してみます。

ubuntu@20.04:~$ uname -a
Linux 20.04 5.4.0-47-generic #51-Ubuntu SMP Fri Sep 4 19:50:52 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
ubuntu@20.04:~$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=20.04
DISTRIB_CODENAME=focal
DISTRIB_DESCRIPTION="Ubuntu 20.04.1 LTS"

ubuntu@20.04:~$ cp `which ping` myping
ubuntu@20.04:~$ ls -l myping
-rwxr-xr-x 1 ubuntu ubuntu 72776 Nov  9 11:22 myping

# CAP_NET_RAW はないが成功する
ubuntu@20.04:~$ getcap myping
ubuntu@20.04:~$ ./myping -c 1 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=254 time=28.3 ms

--- 8.8.8.8 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 28.282/28.282/28.282/0.000 ms

strace をしても SOCK_DGRAM を使っており、 CAP_NET_RAW は必要なはずです。

そこでコードを見ると次のようなコメントがあります。

	/* Attempt to create a ping socket if requested. Attempt to create a raw
	 * socket otherwise or as a fallback. Well known errno values follow.
	 *
	 * 1) EACCES
	 *
	 * Kernel returns EACCES for all ping socket creation attempts when the
	 * user isn't allowed to use ping socket. A range of group ids is
	 * configured using the `net.ipv4.ping_group_range` sysctl. Fallback
	 * to raw socket is necessary.
	 *
	 * Kernel returns EACCES for all raw socket creation attempts when the
	 * process doesn't have the `CAP_NET_RAW` capability.
	 *
	 * 2) EAFNOSUPPORT
	 *
	 * Kernel returns EAFNOSUPPORT for IPv6 ping or raw socket creation
	 * attempts when run with IPv6 support disabled (e.g. via `ipv6.disable=1`
	 * kernel command-line option.
	 *
	 * https://github.com/iputils/iputils/issues/32
	 *
	 * OpenVZ 2.6.32-042stab113.11 and possibly other older kernels return
	 * EAFNOSUPPORT for all IPv4 ping socket creation attempts due to lack
	 * of support in the kernel. Fallback to raw socket is necessary.
	 *
	 * https://github.com/iputils/iputils/issues/54
	 *
	 * 3) EPROTONOSUPPORT
	 *
	 * OpenVZ 2.6.32-042stab113.11 and possibly other older kernels return
	 * EPROTONOSUPPORT for all IPv6 ping socket creation attempts due to lack
	 * of support in the kernel [1]. Debian 9.5 based container with kernel 4.10
	 * returns EPROTONOSUPPORT also for IPv4 [2]. Fallback to raw socket is
	 * necessary.
	 *
	 * [1] https://github.com/iputils/iputils/issues/54
	 * [2] https://github.com/iputils/iputils/issues/129
	 */

net.ipv4.ping_group_range というカーネルパラメータがあるようです。

icmp(7) には次のようにあります。

       ping_group_range (two integers; default: see below; since Linux
       2.6.39)
              Range of the group IDs (minimum and maximum group IDs,
              inclusive) that are allowed to create ICMP Echo sockets.  The
              default is "1 0", which means no group is allowed to create
              ICMP Echo sockets.

ICMP Echo ソケットを作成できるグループの範囲を設定でき、 IPPROTO_ICMP なソケットを作れるようです。

この値をそれぞれの環境でみてみましょう。

# ubuntu 18.04
ubuntu@18.04:~$ sysctl net.ipv4.ping_group_range
net.ipv4.ping_group_range = 1   0

# ubuntu 20.04
ubuntu@20.04:~$ sysctl net.ipv4.ping_group_range
net.ipv4.ping_group_range = 0   2147483647

これで謎が解けました。試しに ubuntu 18.04 でこの値を変更してみます。

ubuntu@18.04:~$ id
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),108(lxd),114(netdev),999(docker)
ubuntu@18.04:~$ getcap myping
ubuntu@18.04:~$ sudo sysctl -w net.ipv4.ping_group_range="0 1000"
net.ipv4.ping_group_range = 0 1000
ubuntu@18.04:~$ ./myping -c 1 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=254 time=26.4 ms

--- 8.8.8.8 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 26.457/26.457/26.457/0.000 ms

CAP_NET_RAW なしで実行できました。

さて、ここまでくると ubuntu のカーネルでデフォルト値が変更されたのかと気になるところなので、調べてみます。
答えは https://tenforward.hatenablog.com/entry/2019/11/27/022547 の補足に書かれていて、どうも systemd v243 からそのような設定がされているようです。

/usr/lib/sysctl.d/50-default.conf を見ると確かにそのような設定がされています。

ubuntu@20.04:~$ cat /usr/lib/sysctl.d/50-default.conf | grep ping_group_range
-net.ipv4.ping_group_range = 0 2147483647

少し前に CVE-2020-14386 という CAP_NET_RAW を持っていると権限昇格ができる脆弱性が見つかりました。
Docker コンテナや Kubernetes で作成される Pod のコンテナは、デフォルトでこれが付与されているため、少し話題になったように思います。

CAP_NET_RAW が付与されているのは、コンテナでネットワークのデバッグをする際に ping などで ICMP パケットを使いたいというユースケースがあるからだと思いますが、この機能を使えば、少なくとも ICMP パケットを作成する分には CAP_NET_RAW を DROP しても良さそうに思えます。

ubuntu@20.04:~$ docker run --sysctl net.ipv4.ping_group_range="0 2147483647" --cap-drop net_raw --rm -it ubuntu:20.04 bash
root@16a8fadb7632:/# apt update ; apt install iputils-ping
...
root@16a8fadb7632:/# ping 8.8.8.8
bash: /usr/bin/ping: Operation not permitted

root@16a8fadb7632:/# setcap 'cap_net_raw-ep' `which ping`
root@16a8fadb7632:/# ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=113 time=18.0 ms
^C
--- 8.8.8.8 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 18.042/18.042/18.042/0.000 ms

便利だー。でも File Capability について説明するのに簡単な例がなくなってしまって困った!

References