]> git.proxmox.com Git - mirror_ubuntu-hirsute-kernel.git/commitdiff
selftests/bpf: Make sure optvals > PAGE_SIZE are bypassed
authorStanislav Fomichev <sdf@google.com>
Wed, 17 Jun 2020 01:04:15 +0000 (18:04 -0700)
committerAlexei Starovoitov <ast@kernel.org>
Wed, 17 Jun 2020 17:54:05 +0000 (10:54 -0700)
We are relying on the fact, that we can pass > sizeof(int) optvals
to the SOL_IP+IP_FREEBIND option (the kernel will take first 4 bytes).
In the BPF program we check that we can only touch PAGE_SIZE bytes,
but the real optlen is PAGE_SIZE * 2. In both cases, we override it to
some predefined value and trim the optlen.

Also, let's modify exiting IP_TOS usecase to test optlen=0 case
where BPF program just bypasses the data as is.

Signed-off-by: Stanislav Fomichev <sdf@google.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Link: https://lore.kernel.org/bpf/20200617010416.93086-2-sdf@google.com
tools/testing/selftests/bpf/prog_tests/sockopt_sk.c
tools/testing/selftests/bpf/progs/sockopt_sk.c

index 2061a6beac0ff0e926e80c531e04dcb72ac3a7de..5f54c6aec7f0785098d2218e40b1788b60510f0e 100644 (file)
@@ -13,6 +13,7 @@ static int getsetsockopt(void)
                char cc[16]; /* TCP_CA_NAME_MAX */
        } buf = {};
        socklen_t optlen;
+       char *big_buf = NULL;
 
        fd = socket(AF_INET, SOCK_STREAM, 0);
        if (fd < 0) {
@@ -22,24 +23,31 @@ static int getsetsockopt(void)
 
        /* IP_TOS - BPF bypass */
 
-       buf.u8[0] = 0x08;
-       err = setsockopt(fd, SOL_IP, IP_TOS, &buf, 1);
+       optlen = getpagesize() * 2;
+       big_buf = calloc(1, optlen);
+       if (!big_buf) {
+               log_err("Couldn't allocate two pages");
+               goto err;
+       }
+
+       *(int *)big_buf = 0x08;
+       err = setsockopt(fd, SOL_IP, IP_TOS, big_buf, optlen);
        if (err) {
                log_err("Failed to call setsockopt(IP_TOS)");
                goto err;
        }
 
-       buf.u8[0] = 0x00;
+       memset(big_buf, 0, optlen);
        optlen = 1;
-       err = getsockopt(fd, SOL_IP, IP_TOS, &buf, &optlen);
+       err = getsockopt(fd, SOL_IP, IP_TOS, big_buf, &optlen);
        if (err) {
                log_err("Failed to call getsockopt(IP_TOS)");
                goto err;
        }
 
-       if (buf.u8[0] != 0x08) {
-               log_err("Unexpected getsockopt(IP_TOS) buf[0] 0x%02x != 0x08",
-                       buf.u8[0]);
+       if (*(int *)big_buf != 0x08) {
+               log_err("Unexpected getsockopt(IP_TOS) optval 0x%x != 0x08",
+                       *(int *)big_buf);
                goto err;
        }
 
@@ -78,6 +86,28 @@ static int getsetsockopt(void)
                goto err;
        }
 
+       /* IP_FREEBIND - BPF can't access optval past PAGE_SIZE */
+
+       optlen = getpagesize() * 2;
+       memset(big_buf, 0, optlen);
+
+       err = setsockopt(fd, SOL_IP, IP_FREEBIND, big_buf, optlen);
+       if (err != 0) {
+               log_err("Failed to call setsockopt, ret=%d", err);
+               goto err;
+       }
+
+       err = getsockopt(fd, SOL_IP, IP_FREEBIND, big_buf, &optlen);
+       if (err != 0) {
+               log_err("Failed to call getsockopt, ret=%d", err);
+               goto err;
+       }
+
+       if (optlen != 1 || *(__u8 *)big_buf != 0x55) {
+               log_err("Unexpected IP_FREEBIND getsockopt, optlen=%d, optval=0x%x",
+                       optlen, *(__u8 *)big_buf);
+       }
+
        /* SO_SNDBUF is overwritten */
 
        buf.u32 = 0x01010101;
@@ -124,9 +154,11 @@ static int getsetsockopt(void)
                goto err;
        }
 
+       free(big_buf);
        close(fd);
        return 0;
 err:
+       free(big_buf);
        close(fd);
        return -1;
 }
index d5a5eeb5fb52798f2b989545fd2dff8e56ecbf0a..712df7b49cb1aad7e82a491369500be276f105e3 100644 (file)
@@ -8,6 +8,10 @@
 char _license[] SEC("license") = "GPL";
 __u32 _version SEC("version") = 1;
 
+#ifndef PAGE_SIZE
+#define PAGE_SIZE 4096
+#endif
+
 #define SOL_CUSTOM                     0xdeadbeef
 
 struct sockopt_sk {
@@ -28,12 +32,14 @@ int _getsockopt(struct bpf_sockopt *ctx)
        __u8 *optval = ctx->optval;
        struct sockopt_sk *storage;
 
-       if (ctx->level == SOL_IP && ctx->optname == IP_TOS)
+       if (ctx->level == SOL_IP && ctx->optname == IP_TOS) {
                /* Not interested in SOL_IP:IP_TOS;
                 * let next BPF program in the cgroup chain or kernel
                 * handle it.
                 */
+               ctx->optlen = 0; /* bypass optval>PAGE_SIZE */
                return 1;
+       }
 
        if (ctx->level == SOL_SOCKET && ctx->optname == SO_SNDBUF) {
                /* Not interested in SOL_SOCKET:SO_SNDBUF;
@@ -51,6 +57,26 @@ int _getsockopt(struct bpf_sockopt *ctx)
                return 1;
        }
 
+       if (ctx->level == SOL_IP && ctx->optname == IP_FREEBIND) {
+               if (optval + 1 > optval_end)
+                       return 0; /* EPERM, bounds check */
+
+               ctx->retval = 0; /* Reset system call return value to zero */
+
+               /* Always export 0x55 */
+               optval[0] = 0x55;
+               ctx->optlen = 1;
+
+               /* Userspace buffer is PAGE_SIZE * 2, but BPF
+                * program can only see the first PAGE_SIZE
+                * bytes of data.
+                */
+               if (optval_end - optval != PAGE_SIZE)
+                       return 0; /* EPERM, unexpected data size */
+
+               return 1;
+       }
+
        if (ctx->level != SOL_CUSTOM)
                return 0; /* EPERM, deny everything except custom level */
 
@@ -81,12 +107,14 @@ int _setsockopt(struct bpf_sockopt *ctx)
        __u8 *optval = ctx->optval;
        struct sockopt_sk *storage;
 
-       if (ctx->level == SOL_IP && ctx->optname == IP_TOS)
+       if (ctx->level == SOL_IP && ctx->optname == IP_TOS) {
                /* Not interested in SOL_IP:IP_TOS;
                 * let next BPF program in the cgroup chain or kernel
                 * handle it.
                 */
+               ctx->optlen = 0; /* bypass optval>PAGE_SIZE */
                return 1;
+       }
 
        if (ctx->level == SOL_SOCKET && ctx->optname == SO_SNDBUF) {
                /* Overwrite SO_SNDBUF value */
@@ -112,6 +140,28 @@ int _setsockopt(struct bpf_sockopt *ctx)
                return 1;
        }
 
+       if (ctx->level == SOL_IP && ctx->optname == IP_FREEBIND) {
+               /* Original optlen is larger than PAGE_SIZE. */
+               if (ctx->optlen != PAGE_SIZE * 2)
+                       return 0; /* EPERM, unexpected data size */
+
+               if (optval + 1 > optval_end)
+                       return 0; /* EPERM, bounds check */
+
+               /* Make sure we can trim the buffer. */
+               optval[0] = 0;
+               ctx->optlen = 1;
+
+               /* Usepace buffer is PAGE_SIZE * 2, but BPF
+                * program can only see the first PAGE_SIZE
+                * bytes of data.
+                */
+               if (optval_end - optval != PAGE_SIZE)
+                       return 0; /* EPERM, unexpected data size */
+
+               return 1;
+       }
+
        if (ctx->level != SOL_CUSTOM)
                return 0; /* EPERM, deny everything except custom level */