selftests/bpf: Ensure cgroup/connect{4,6} programs can bind unpriv ICMP ping
authorYiFei Zhu <zhuyifei@google.com>
Fri, 9 Sep 2022 00:49:41 +0000 (00:49 +0000)
committerMartin KaFai Lau <martin.lau@kernel.org>
Fri, 9 Sep 2022 17:40:45 +0000 (10:40 -0700)
This tests that when an unprivileged ICMP ping socket connects,
the hooks are actually invoked. We also ensure that if the hook does
not call bpf_bind(), the bound address is unmodified, and if the
hook calls bpf_bind(), the bound address is exactly what we provided
to the helper.

A new netns is used to enable ping_group_range in the test without
affecting ouside of the test, because by default, not even root is
permitted to use unprivileged ICMP ping...

Signed-off-by: YiFei Zhu <zhuyifei@google.com>
Link: https://lore.kernel.org/r/086b227c1b97f4e94193e58aae7576d0261b68a4.1662682323.git.zhuyifei@google.com
Signed-off-by: Martin KaFai Lau <martin.lau@kernel.org>
tools/testing/selftests/bpf/prog_tests/connect_ping.c [new file with mode: 0644]
tools/testing/selftests/bpf/progs/connect_ping.c [new file with mode: 0644]

diff --git a/tools/testing/selftests/bpf/prog_tests/connect_ping.c b/tools/testing/selftests/bpf/prog_tests/connect_ping.c
new file mode 100644 (file)
index 0000000..289218c
--- /dev/null
@@ -0,0 +1,178 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ * Copyright 2022 Google LLC.
+ */
+
+#define _GNU_SOURCE
+#include <sys/mount.h>
+
+#include "test_progs.h"
+#include "cgroup_helpers.h"
+#include "network_helpers.h"
+
+#include "connect_ping.skel.h"
+
+/* 2001:db8::1 */
+#define BINDADDR_V6 { { { 0x20,0x01,0x0d,0xb8,0,0,0,0,0,0,0,0,0,0,0,1 } } }
+static const struct in6_addr bindaddr_v6 = BINDADDR_V6;
+
+static void subtest(int cgroup_fd, struct connect_ping *skel,
+                   int family, int do_bind)
+{
+       struct sockaddr_in sa4 = {
+               .sin_family = AF_INET,
+               .sin_addr.s_addr = htonl(INADDR_LOOPBACK),
+       };
+       struct sockaddr_in6 sa6 = {
+               .sin6_family = AF_INET6,
+               .sin6_addr = IN6ADDR_LOOPBACK_INIT,
+       };
+       struct sockaddr *sa;
+       socklen_t sa_len;
+       int protocol;
+       int sock_fd;
+
+       switch (family) {
+       case AF_INET:
+               sa = (struct sockaddr *)&sa4;
+               sa_len = sizeof(sa4);
+               protocol = IPPROTO_ICMP;
+               break;
+       case AF_INET6:
+               sa = (struct sockaddr *)&sa6;
+               sa_len = sizeof(sa6);
+               protocol = IPPROTO_ICMPV6;
+               break;
+       }
+
+       memset(skel->bss, 0, sizeof(*skel->bss));
+       skel->bss->do_bind = do_bind;
+
+       sock_fd = socket(family, SOCK_DGRAM, protocol);
+       if (!ASSERT_GE(sock_fd, 0, "sock-create"))
+               return;
+
+       if (!ASSERT_OK(connect(sock_fd, sa, sa_len), "connect"))
+               goto close_sock;
+
+       if (!ASSERT_EQ(skel->bss->invocations_v4, family == AF_INET ? 1 : 0,
+                      "invocations_v4"))
+               goto close_sock;
+       if (!ASSERT_EQ(skel->bss->invocations_v6, family == AF_INET6 ? 1 : 0,
+                      "invocations_v6"))
+               goto close_sock;
+       if (!ASSERT_EQ(skel->bss->has_error, 0, "has_error"))
+               goto close_sock;
+
+       if (!ASSERT_OK(getsockname(sock_fd, sa, &sa_len),
+                      "getsockname"))
+               goto close_sock;
+
+       switch (family) {
+       case AF_INET:
+               if (!ASSERT_EQ(sa4.sin_family, family, "sin_family"))
+                       goto close_sock;
+               if (!ASSERT_EQ(sa4.sin_addr.s_addr,
+                              htonl(do_bind ? 0x01010101 : INADDR_LOOPBACK),
+                              "sin_addr"))
+                       goto close_sock;
+               break;
+       case AF_INET6:
+               if (!ASSERT_EQ(sa6.sin6_family, AF_INET6, "sin6_family"))
+                       goto close_sock;
+               if (!ASSERT_EQ(memcmp(&sa6.sin6_addr,
+                                     do_bind ? &bindaddr_v6 : &in6addr_loopback,
+                                     sizeof(sa6.sin6_addr)),
+                              0, "sin6_addr"))
+                       goto close_sock;
+               break;
+       }
+
+close_sock:
+       close(sock_fd);
+}
+
+void test_connect_ping(void)
+{
+       struct connect_ping *skel;
+       int cgroup_fd;
+
+       if (!ASSERT_OK(unshare(CLONE_NEWNET | CLONE_NEWNS), "unshare"))
+               return;
+
+       /* overmount sysfs, and making original sysfs private so overmount
+        * does not propagate to other mntns.
+        */
+       if (!ASSERT_OK(mount("none", "/sys", NULL, MS_PRIVATE, NULL),
+                      "remount-private-sys"))
+               return;
+       if (!ASSERT_OK(mount("sysfs", "/sys", "sysfs", 0, NULL),
+                      "mount-sys"))
+               return;
+       if (!ASSERT_OK(mount("bpffs", "/sys/fs/bpf", "bpf", 0, NULL),
+                      "mount-bpf"))
+               goto clean_mount;
+
+       if (!ASSERT_OK(system("ip link set dev lo up"), "lo-up"))
+               goto clean_mount;
+       if (!ASSERT_OK(system("ip addr add 1.1.1.1 dev lo"), "lo-addr-v4"))
+               goto clean_mount;
+       if (!ASSERT_OK(system("ip -6 addr add 2001:db8::1 dev lo"), "lo-addr-v6"))
+               goto clean_mount;
+       if (write_sysctl("/proc/sys/net/ipv4/ping_group_range", "0 0"))
+               goto clean_mount;
+
+       cgroup_fd = test__join_cgroup("/connect_ping");
+       if (!ASSERT_GE(cgroup_fd, 0, "cg-create"))
+               goto clean_mount;
+
+       skel = connect_ping__open_and_load();
+       if (!ASSERT_OK_PTR(skel, "skel-load"))
+               goto close_cgroup;
+       skel->links.connect_v4_prog =
+               bpf_program__attach_cgroup(skel->progs.connect_v4_prog, cgroup_fd);
+       if (!ASSERT_OK_PTR(skel->links.connect_v4_prog, "cg-attach-v4"))
+               goto skel_destroy;
+       skel->links.connect_v6_prog =
+               bpf_program__attach_cgroup(skel->progs.connect_v6_prog, cgroup_fd);
+       if (!ASSERT_OK_PTR(skel->links.connect_v6_prog, "cg-attach-v6"))
+               goto skel_destroy;
+
+       /* Connect a v4 ping socket to localhost, assert that only v4 is called,
+        * and called exactly once, and that the socket's bound address is
+        * original loopback address.
+        */
+       if (test__start_subtest("ipv4"))
+               subtest(cgroup_fd, skel, AF_INET, 0);
+
+       /* Connect a v4 ping socket to localhost, assert that only v4 is called,
+        * and called exactly once, and that the socket's bound address is
+        * address we explicitly bound.
+        */
+       if (test__start_subtest("ipv4-bind"))
+               subtest(cgroup_fd, skel, AF_INET, 1);
+
+       /* Connect a v6 ping socket to localhost, assert that only v6 is called,
+        * and called exactly once, and that the socket's bound address is
+        * original loopback address.
+        */
+       if (test__start_subtest("ipv6"))
+               subtest(cgroup_fd, skel, AF_INET6, 0);
+
+       /* Connect a v6 ping socket to localhost, assert that only v6 is called,
+        * and called exactly once, and that the socket's bound address is
+        * address we explicitly bound.
+        */
+       if (test__start_subtest("ipv6-bind"))
+               subtest(cgroup_fd, skel, AF_INET6, 1);
+
+skel_destroy:
+       connect_ping__destroy(skel);
+
+close_cgroup:
+       close(cgroup_fd);
+
+clean_mount:
+       umount2("/sys", MNT_DETACH);
+}
diff --git a/tools/testing/selftests/bpf/progs/connect_ping.c b/tools/testing/selftests/bpf/progs/connect_ping.c
new file mode 100644 (file)
index 0000000..6017819
--- /dev/null
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ * Copyright 2022 Google LLC.
+ */
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_endian.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+
+/* 2001:db8::1 */
+#define BINDADDR_V6 { { { 0x20,0x01,0x0d,0xb8,0,0,0,0,0,0,0,0,0,0,0,1 } } }
+
+__u32 do_bind = 0;
+__u32 has_error = 0;
+__u32 invocations_v4 = 0;
+__u32 invocations_v6 = 0;
+
+SEC("cgroup/connect4")
+int connect_v4_prog(struct bpf_sock_addr *ctx)
+{
+       struct sockaddr_in sa = {
+               .sin_family = AF_INET,
+               .sin_addr.s_addr = bpf_htonl(0x01010101),
+       };
+
+       __sync_fetch_and_add(&invocations_v4, 1);
+
+       if (do_bind && bpf_bind(ctx, (struct sockaddr *)&sa, sizeof(sa)))
+               has_error = 1;
+
+       return 1;
+}
+
+SEC("cgroup/connect6")
+int connect_v6_prog(struct bpf_sock_addr *ctx)
+{
+       struct sockaddr_in6 sa = {
+               .sin6_family = AF_INET6,
+               .sin6_addr = BINDADDR_V6,
+       };
+
+       __sync_fetch_and_add(&invocations_v6, 1);
+
+       if (do_bind && bpf_bind(ctx, (struct sockaddr *)&sa, sizeof(sa)))
+               has_error = 1;
+
+       return 1;
+}
+
+char _license[] SEC("license") = "GPL";