Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selftests/bpf: Make sure optvals > PAGE_SIZE are bypassed

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

authored by

Stanislav Fomichev and committed by
Alexei Starovoitov
a0cb12b0 d8fe449a

+91 -9
+39 -7
tools/testing/selftests/bpf/prog_tests/sockopt_sk.c
··· 13 13 char cc[16]; /* TCP_CA_NAME_MAX */ 14 14 } buf = {}; 15 15 socklen_t optlen; 16 + char *big_buf = NULL; 16 17 17 18 fd = socket(AF_INET, SOCK_STREAM, 0); 18 19 if (fd < 0) { ··· 23 22 24 23 /* IP_TOS - BPF bypass */ 25 24 26 - buf.u8[0] = 0x08; 27 - err = setsockopt(fd, SOL_IP, IP_TOS, &buf, 1); 25 + optlen = getpagesize() * 2; 26 + big_buf = calloc(1, optlen); 27 + if (!big_buf) { 28 + log_err("Couldn't allocate two pages"); 29 + goto err; 30 + } 31 + 32 + *(int *)big_buf = 0x08; 33 + err = setsockopt(fd, SOL_IP, IP_TOS, big_buf, optlen); 28 34 if (err) { 29 35 log_err("Failed to call setsockopt(IP_TOS)"); 30 36 goto err; 31 37 } 32 38 33 - buf.u8[0] = 0x00; 39 + memset(big_buf, 0, optlen); 34 40 optlen = 1; 35 - err = getsockopt(fd, SOL_IP, IP_TOS, &buf, &optlen); 41 + err = getsockopt(fd, SOL_IP, IP_TOS, big_buf, &optlen); 36 42 if (err) { 37 43 log_err("Failed to call getsockopt(IP_TOS)"); 38 44 goto err; 39 45 } 40 46 41 - if (buf.u8[0] != 0x08) { 42 - log_err("Unexpected getsockopt(IP_TOS) buf[0] 0x%02x != 0x08", 43 - buf.u8[0]); 47 + if (*(int *)big_buf != 0x08) { 48 + log_err("Unexpected getsockopt(IP_TOS) optval 0x%x != 0x08", 49 + *(int *)big_buf); 44 50 goto err; 45 51 } 46 52 ··· 84 76 if (buf.u8[0] != 0x01) { 85 77 log_err("Unexpected buf[0] 0x%02x != 0x01", buf.u8[0]); 86 78 goto err; 79 + } 80 + 81 + /* IP_FREEBIND - BPF can't access optval past PAGE_SIZE */ 82 + 83 + optlen = getpagesize() * 2; 84 + memset(big_buf, 0, optlen); 85 + 86 + err = setsockopt(fd, SOL_IP, IP_FREEBIND, big_buf, optlen); 87 + if (err != 0) { 88 + log_err("Failed to call setsockopt, ret=%d", err); 89 + goto err; 90 + } 91 + 92 + err = getsockopt(fd, SOL_IP, IP_FREEBIND, big_buf, &optlen); 93 + if (err != 0) { 94 + log_err("Failed to call getsockopt, ret=%d", err); 95 + goto err; 96 + } 97 + 98 + if (optlen != 1 || *(__u8 *)big_buf != 0x55) { 99 + log_err("Unexpected IP_FREEBIND getsockopt, optlen=%d, optval=0x%x", 100 + optlen, *(__u8 *)big_buf); 87 101 } 88 102 89 103 /* SO_SNDBUF is overwritten */ ··· 154 124 goto err; 155 125 } 156 126 127 + free(big_buf); 157 128 close(fd); 158 129 return 0; 159 130 err: 131 + free(big_buf); 160 132 close(fd); 161 133 return -1; 162 134 }
+52 -2
tools/testing/selftests/bpf/progs/sockopt_sk.c
··· 8 8 char _license[] SEC("license") = "GPL"; 9 9 __u32 _version SEC("version") = 1; 10 10 11 + #ifndef PAGE_SIZE 12 + #define PAGE_SIZE 4096 13 + #endif 14 + 11 15 #define SOL_CUSTOM 0xdeadbeef 12 16 13 17 struct sockopt_sk { ··· 32 28 __u8 *optval = ctx->optval; 33 29 struct sockopt_sk *storage; 34 30 35 - if (ctx->level == SOL_IP && ctx->optname == IP_TOS) 31 + if (ctx->level == SOL_IP && ctx->optname == IP_TOS) { 36 32 /* Not interested in SOL_IP:IP_TOS; 37 33 * let next BPF program in the cgroup chain or kernel 38 34 * handle it. 39 35 */ 36 + ctx->optlen = 0; /* bypass optval>PAGE_SIZE */ 40 37 return 1; 38 + } 41 39 42 40 if (ctx->level == SOL_SOCKET && ctx->optname == SO_SNDBUF) { 43 41 /* Not interested in SOL_SOCKET:SO_SNDBUF; ··· 54 48 * let next BPF program in the cgroup chain or kernel 55 49 * handle it. 56 50 */ 51 + return 1; 52 + } 53 + 54 + if (ctx->level == SOL_IP && ctx->optname == IP_FREEBIND) { 55 + if (optval + 1 > optval_end) 56 + return 0; /* EPERM, bounds check */ 57 + 58 + ctx->retval = 0; /* Reset system call return value to zero */ 59 + 60 + /* Always export 0x55 */ 61 + optval[0] = 0x55; 62 + ctx->optlen = 1; 63 + 64 + /* Userspace buffer is PAGE_SIZE * 2, but BPF 65 + * program can only see the first PAGE_SIZE 66 + * bytes of data. 67 + */ 68 + if (optval_end - optval != PAGE_SIZE) 69 + return 0; /* EPERM, unexpected data size */ 70 + 57 71 return 1; 58 72 } 59 73 ··· 107 81 __u8 *optval = ctx->optval; 108 82 struct sockopt_sk *storage; 109 83 110 - if (ctx->level == SOL_IP && ctx->optname == IP_TOS) 84 + if (ctx->level == SOL_IP && ctx->optname == IP_TOS) { 111 85 /* Not interested in SOL_IP:IP_TOS; 112 86 * let next BPF program in the cgroup chain or kernel 113 87 * handle it. 114 88 */ 89 + ctx->optlen = 0; /* bypass optval>PAGE_SIZE */ 115 90 return 1; 91 + } 116 92 117 93 if (ctx->level == SOL_SOCKET && ctx->optname == SO_SNDBUF) { 118 94 /* Overwrite SO_SNDBUF value */ ··· 136 108 137 109 memcpy(optval, "cubic", 5); 138 110 ctx->optlen = 5; 111 + 112 + return 1; 113 + } 114 + 115 + if (ctx->level == SOL_IP && ctx->optname == IP_FREEBIND) { 116 + /* Original optlen is larger than PAGE_SIZE. */ 117 + if (ctx->optlen != PAGE_SIZE * 2) 118 + return 0; /* EPERM, unexpected data size */ 119 + 120 + if (optval + 1 > optval_end) 121 + return 0; /* EPERM, bounds check */ 122 + 123 + /* Make sure we can trim the buffer. */ 124 + optval[0] = 0; 125 + ctx->optlen = 1; 126 + 127 + /* Usepace buffer is PAGE_SIZE * 2, but BPF 128 + * program can only see the first PAGE_SIZE 129 + * bytes of data. 130 + */ 131 + if (optval_end - optval != PAGE_SIZE) 132 + return 0; /* EPERM, unexpected data size */ 139 133 140 134 return 1; 141 135 }