Skip to content

Commit b096a4a

Browse files
nvmmaxNobody
authored and
Nobody
committed
bpf: Support dual-stack sockets in bpf_tcp_check_syncookie
bpf_tcp_gen_syncookie looks at the IP version in the IP header and validates the address family of the socket. It supports IPv4 packets in AF_INET6 dual-stack sockets. On the other hand, bpf_tcp_check_syncookie looks only at the address family of the socket, ignoring the real IP version in headers, and validates only the packet size. This implementation has some drawbacks: 1. Packets are not validated properly, allowing a BPF program to trick bpf_tcp_check_syncookie into handling an IPv6 packet on an IPv4 socket. 2. Dual-stack sockets fail the checks on IPv4 packets. IPv4 clients end up receiving a SYNACK with the cookie, but the following ACK gets dropped. This patch fixes these issues by changing the checks in bpf_tcp_check_syncookie to match the ones in bpf_tcp_gen_syncookie. IP version from the header is taken into account, and it is validated properly with address family. Fixes: 3990408 ("bpf: add helper to check for a valid SYN cookie") Signed-off-by: Maxim Mikityanskiy <[email protected]> Reviewed-by: Tariq Toukan <[email protected]>
1 parent 704f2a0 commit b096a4a

File tree

2 files changed

+72
-23
lines changed

2 files changed

+72
-23
lines changed

net/core/filter.c

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6777,24 +6777,33 @@ BPF_CALL_5(bpf_tcp_check_syncookie, struct sock *, sk, void *, iph, u32, iph_len
67776777
if (!th->ack || th->rst || th->syn)
67786778
return -ENOENT;
67796779

6780+
if (unlikely(iph_len < sizeof(struct iphdr)))
6781+
return -EINVAL;
6782+
67806783
if (tcp_synq_no_recent_overflow(sk))
67816784
return -ENOENT;
67826785

67836786
cookie = ntohl(th->ack_seq) - 1;
67846787

6785-
switch (sk->sk_family) {
6786-
case AF_INET:
6787-
if (unlikely(iph_len < sizeof(struct iphdr)))
6788+
/* Both struct iphdr and struct ipv6hdr have the version field at the
6789+
* same offset so we can cast to the shorter header (struct iphdr).
6790+
*/
6791+
switch (((struct iphdr *)iph)->version) {
6792+
case 4:
6793+
if (sk->sk_family == AF_INET6 && ipv6_only_sock(sk))
67886794
return -EINVAL;
67896795

67906796
ret = __cookie_v4_check((struct iphdr *)iph, th, cookie);
67916797
break;
67926798

67936799
#if IS_BUILTIN(CONFIG_IPV6)
6794-
case AF_INET6:
6800+
case 6:
67956801
if (unlikely(iph_len < sizeof(struct ipv6hdr)))
67966802
return -EINVAL;
67976803

6804+
if (sk->sk_family != AF_INET6)
6805+
return -EINVAL;
6806+
67986807
ret = __cookie_v6_check((struct ipv6hdr *)iph, th, cookie);
67996808
break;
68006809
#endif /* CONFIG_IPV6 */

tools/testing/selftests/bpf/test_tcp_check_syncookie_user.c

Lines changed: 59 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@
1818
#include "bpf_rlimit.h"
1919
#include "cgroup_helpers.h"
2020

21-
static int start_server(const struct sockaddr *addr, socklen_t len)
21+
static int start_server(const struct sockaddr *addr, socklen_t len, bool dual)
2222
{
23+
int mode = !dual;
2324
int fd;
2425

2526
fd = socket(addr->sa_family, SOCK_STREAM, 0);
@@ -28,6 +29,14 @@ static int start_server(const struct sockaddr *addr, socklen_t len)
2829
goto out;
2930
}
3031

32+
if (addr->sa_family == AF_INET6) {
33+
if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&mode,
34+
sizeof(mode)) == -1) {
35+
log_err("Failed to set the dual-stack mode");
36+
goto close_out;
37+
}
38+
}
39+
3140
if (bind(fd, addr, len) == -1) {
3241
log_err("Failed to bind server socket");
3342
goto close_out;
@@ -47,24 +56,17 @@ static int start_server(const struct sockaddr *addr, socklen_t len)
4756
return fd;
4857
}
4958

50-
static int connect_to_server(int server_fd)
59+
static int connect_to_server(const struct sockaddr *addr, socklen_t len)
5160
{
52-
struct sockaddr_storage addr;
53-
socklen_t len = sizeof(addr);
5461
int fd = -1;
5562

56-
if (getsockname(server_fd, (struct sockaddr *)&addr, &len)) {
57-
log_err("Failed to get server addr");
58-
goto out;
59-
}
60-
61-
fd = socket(addr.ss_family, SOCK_STREAM, 0);
63+
fd = socket(addr->sa_family, SOCK_STREAM, 0);
6264
if (fd == -1) {
6365
log_err("Failed to create client socket");
6466
goto out;
6567
}
6668

67-
if (connect(fd, (const struct sockaddr *)&addr, len) == -1) {
69+
if (connect(fd, (const struct sockaddr *)addr, len) == -1) {
6870
log_err("Fail to connect to server");
6971
goto close_out;
7072
}
@@ -116,7 +118,8 @@ static int get_map_fd_by_prog_id(int prog_id, bool *xdp)
116118
return map_fd;
117119
}
118120

119-
static int run_test(int server_fd, int results_fd, bool xdp)
121+
static int run_test(int server_fd, int results_fd, bool xdp,
122+
const struct sockaddr *addr, socklen_t len)
120123
{
121124
int client = -1, srv_client = -1;
122125
int ret = 0;
@@ -142,7 +145,7 @@ static int run_test(int server_fd, int results_fd, bool xdp)
142145
goto err;
143146
}
144147

145-
client = connect_to_server(server_fd);
148+
client = connect_to_server(addr, len);
146149
if (client == -1)
147150
goto err;
148151

@@ -199,12 +202,30 @@ static int run_test(int server_fd, int results_fd, bool xdp)
199202
return ret;
200203
}
201204

205+
static bool get_port(int server_fd, in_port_t *port)
206+
{
207+
struct sockaddr_in addr;
208+
socklen_t len = sizeof(addr);
209+
210+
if (getsockname(server_fd, (struct sockaddr *)&addr, &len)) {
211+
log_err("Failed to get server addr");
212+
return false;
213+
}
214+
215+
/* sin_port and sin6_port are located at the same offset. */
216+
*port = addr.sin_port;
217+
return true;
218+
}
219+
202220
int main(int argc, char **argv)
203221
{
204222
struct sockaddr_in addr4;
205223
struct sockaddr_in6 addr6;
224+
struct sockaddr_in addr4dual;
225+
struct sockaddr_in6 addr6dual;
206226
int server = -1;
207227
int server_v6 = -1;
228+
int server_dual = -1;
208229
int results = -1;
209230
int err = 0;
210231
bool xdp;
@@ -224,25 +245,43 @@ int main(int argc, char **argv)
224245
addr4.sin_family = AF_INET;
225246
addr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
226247
addr4.sin_port = 0;
248+
memcpy(&addr4dual, &addr4, sizeof(addr4dual));
227249

228250
memset(&addr6, 0, sizeof(addr6));
229251
addr6.sin6_family = AF_INET6;
230252
addr6.sin6_addr = in6addr_loopback;
231253
addr6.sin6_port = 0;
232254

233-
server = start_server((const struct sockaddr *)&addr4, sizeof(addr4));
234-
if (server == -1)
255+
memset(&addr6dual, 0, sizeof(addr6dual));
256+
addr6dual.sin6_family = AF_INET6;
257+
addr6dual.sin6_addr = in6addr_any;
258+
addr6dual.sin6_port = 0;
259+
260+
server = start_server((const struct sockaddr *)&addr4, sizeof(addr4),
261+
false);
262+
if (server == -1 || !get_port(server, &addr4.sin_port))
235263
goto err;
236264

237265
server_v6 = start_server((const struct sockaddr *)&addr6,
238-
sizeof(addr6));
239-
if (server_v6 == -1)
266+
sizeof(addr6), false);
267+
if (server_v6 == -1 || !get_port(server_v6, &addr6.sin6_port))
268+
goto err;
269+
270+
server_dual = start_server((const struct sockaddr *)&addr6dual,
271+
sizeof(addr6dual), true);
272+
if (server_dual == -1 || !get_port(server_dual, &addr4dual.sin_port))
273+
goto err;
274+
275+
if (run_test(server, results, xdp,
276+
(const struct sockaddr *)&addr4, sizeof(addr4)))
240277
goto err;
241278

242-
if (run_test(server, results, xdp))
279+
if (run_test(server_v6, results, xdp,
280+
(const struct sockaddr *)&addr6, sizeof(addr6)))
243281
goto err;
244282

245-
if (run_test(server_v6, results, xdp))
283+
if (run_test(server_dual, results, xdp,
284+
(const struct sockaddr *)&addr4dual, sizeof(addr4dual)))
246285
goto err;
247286

248287
printf("ok\n");
@@ -252,6 +291,7 @@ int main(int argc, char **argv)
252291
out:
253292
close(server);
254293
close(server_v6);
294+
close(server_dual);
255295
close(results);
256296
return err;
257297
}

0 commit comments

Comments
 (0)