diff mbox series

[bpf,v2,2/4] bpf: Support dual-stack sockets in bpf_tcp_check_syncookie

Message ID 20220124151146.376446-3-maximmi@nvidia.com (mailing list archive)
State Changes Requested
Delegated to: BPF
Headers show
Series Bugfixes for syncookie BPF helpers | expand

Checks

Context Check Description
bpf/vmtest-bpf success VM_Test
bpf/vmtest-bpf-PR success PR summary
netdev/tree_selection success Clearly marked for bpf
netdev/fixes_present success Fixes tag present in non-next series
netdev/subject_prefix success Link
netdev/cover_letter success Series has a cover letter
netdev/patch_count success Link
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 27 this patch: 27
netdev/cc_maintainers success CCed 13 of 13 maintainers
netdev/build_clang success Errors and warnings before: 22 this patch: 22
netdev/module_param success Was 0 now: 0
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/verify_fixes success Fixes tag looks correct
netdev/build_allmodconfig_warn success Errors and warnings before: 32 this patch: 32
netdev/checkpatch success total: 0 errors, 0 warnings, 0 checks, 37 lines checked
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0

Commit Message

Maxim Mikityanskiy Jan. 24, 2022, 3:11 p.m. UTC
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: 399040847084 ("bpf: add helper to check for a valid SYN cookie")
Signed-off-by: Maxim Mikityanskiy <maximmi@nvidia.com>
Reviewed-by: Tariq Toukan <tariqt@nvidia.com>
---
 net/core/filter.c | 17 +++++++++++++----
 1 file changed, 13 insertions(+), 4 deletions(-)

Comments

John Fastabend Jan. 25, 2022, 7:04 a.m. UTC | #1
Maxim Mikityanskiy wrote:
> 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.

These programs are all CAP_NET_ADMIN I believe so not so sure this is
critical from a BPF program might trick the helper, but consistency
is nice.

> 
> 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.

Agree we need to fix this. Also would be nice to add a test to capture
this case so we don't break it again later. Its a bit subtle so might
not be caught right away without a selftest.

> 
> 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.

Code looks good, would be nice to have a test.

Acked-by: John Fastabend <john.fastabend@gmail.com>

> 
> Fixes: 399040847084 ("bpf: add helper to check for a valid SYN cookie")
> Signed-off-by: Maxim Mikityanskiy <maximmi@nvidia.com>
> Reviewed-by: Tariq Toukan <tariqt@nvidia.com>
> ---
>  net/core/filter.c | 17 +++++++++++++----
Lorenz Bauer Jan. 26, 2022, 9:49 a.m. UTC | #2
On Mon, 24 Jan 2022 at 15:13, Maxim Mikityanskiy <maximmi@nvidia.com> wrote:
>
> 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: 399040847084 ("bpf: add helper to check for a valid SYN cookie")
> Signed-off-by: Maxim Mikityanskiy <maximmi@nvidia.com>
> Reviewed-by: Tariq Toukan <tariqt@nvidia.com>
> ---
>  net/core/filter.c | 17 +++++++++++++----
>  1 file changed, 13 insertions(+), 4 deletions(-)
>
> diff --git a/net/core/filter.c b/net/core/filter.c
> index 05efa691b796..780e635fb52a 100644
> --- a/net/core/filter.c
> +++ b/net/core/filter.c
> @@ -6774,24 +6774,33 @@ BPF_CALL_5(bpf_tcp_check_syncookie, struct sock *, sk, void *, iph, u32, iph_len
>         if (!th->ack || th->rst || th->syn)
>                 return -ENOENT;
>
> +       if (unlikely(iph_len < sizeof(struct iphdr)))
> +               return -EINVAL;
> +
>         if (tcp_synq_no_recent_overflow(sk))
>                 return -ENOENT;
>
>         cookie = ntohl(th->ack_seq) - 1;
>
> -       switch (sk->sk_family) {
> -       case AF_INET:
> -               if (unlikely(iph_len < sizeof(struct iphdr)))
> +       /* Both struct iphdr and struct ipv6hdr have the version field at the
> +        * same offset so we can cast to the shorter header (struct iphdr).
> +        */
> +       switch (((struct iphdr *)iph)->version) {
> +       case 4:
> +               if (sk->sk_family == AF_INET6 && ipv6_only_sock(sk))
>                         return -EINVAL;

Wouldn't this allow an arbitrary value for sk->sk_family, since there
is no further check that sk_family is AF_INET?
Maxim Mikityanskiy Jan. 31, 2022, 1:38 p.m. UTC | #3
On 2022-01-26 11:49, Lorenz Bauer wrote:
> On Mon, 24 Jan 2022 at 15:13, Maxim Mikityanskiy <maximmi@nvidia.com> wrote:
>>
>> 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: 399040847084 ("bpf: add helper to check for a valid SYN cookie")
>> Signed-off-by: Maxim Mikityanskiy <maximmi@nvidia.com>
>> Reviewed-by: Tariq Toukan <tariqt@nvidia.com>
>> ---
>>   net/core/filter.c | 17 +++++++++++++----
>>   1 file changed, 13 insertions(+), 4 deletions(-)
>>
>> diff --git a/net/core/filter.c b/net/core/filter.c
>> index 05efa691b796..780e635fb52a 100644
>> --- a/net/core/filter.c
>> +++ b/net/core/filter.c
>> @@ -6774,24 +6774,33 @@ BPF_CALL_5(bpf_tcp_check_syncookie, struct sock *, sk, void *, iph, u32, iph_len
>>          if (!th->ack || th->rst || th->syn)
>>                  return -ENOENT;
>>
>> +       if (unlikely(iph_len < sizeof(struct iphdr)))
>> +               return -EINVAL;
>> +
>>          if (tcp_synq_no_recent_overflow(sk))
>>                  return -ENOENT;
>>
>>          cookie = ntohl(th->ack_seq) - 1;
>>
>> -       switch (sk->sk_family) {
>> -       case AF_INET:
>> -               if (unlikely(iph_len < sizeof(struct iphdr)))
>> +       /* Both struct iphdr and struct ipv6hdr have the version field at the
>> +        * same offset so we can cast to the shorter header (struct iphdr).
>> +        */
>> +       switch (((struct iphdr *)iph)->version) {
>> +       case 4:
>> +               if (sk->sk_family == AF_INET6 && ipv6_only_sock(sk))
>>                          return -EINVAL;
> 
> Wouldn't this allow an arbitrary value for sk->sk_family, since there
> is no further check that sk_family is AF_INET?

It relies on the assumption that sk_family is either AF_INET or 
AF_INET6, when sk_protocol is IPPROTO_TCP (checked above). The same 
assumption is used in bpf_tcp_gen_syncookie. Do you think there are 
cases when it doesn't hold, and we must verify sk_family? If yes, then 
bpf_tcp_gen_syncookie should also be fixed.
diff mbox series

Patch

diff --git a/net/core/filter.c b/net/core/filter.c
index 05efa691b796..780e635fb52a 100644
--- a/net/core/filter.c
+++ b/net/core/filter.c
@@ -6774,24 +6774,33 @@  BPF_CALL_5(bpf_tcp_check_syncookie, struct sock *, sk, void *, iph, u32, iph_len
 	if (!th->ack || th->rst || th->syn)
 		return -ENOENT;
 
+	if (unlikely(iph_len < sizeof(struct iphdr)))
+		return -EINVAL;
+
 	if (tcp_synq_no_recent_overflow(sk))
 		return -ENOENT;
 
 	cookie = ntohl(th->ack_seq) - 1;
 
-	switch (sk->sk_family) {
-	case AF_INET:
-		if (unlikely(iph_len < sizeof(struct iphdr)))
+	/* Both struct iphdr and struct ipv6hdr have the version field at the
+	 * same offset so we can cast to the shorter header (struct iphdr).
+	 */
+	switch (((struct iphdr *)iph)->version) {
+	case 4:
+		if (sk->sk_family == AF_INET6 && ipv6_only_sock(sk))
 			return -EINVAL;
 
 		ret = __cookie_v4_check((struct iphdr *)iph, th, cookie);
 		break;
 
 #if IS_BUILTIN(CONFIG_IPV6)
-	case AF_INET6:
+	case 6:
 		if (unlikely(iph_len < sizeof(struct ipv6hdr)))
 			return -EINVAL;
 
+		if (sk->sk_family != AF_INET6)
+			return -EINVAL;
+
 		ret = __cookie_v6_check((struct ipv6hdr *)iph, th, cookie);
 		break;
 #endif /* CONFIG_IPV6 */