From nobody Thu Oct 2 20:42:07 2025 Received: from mail-yx1-f49.google.com (mail-yx1-f49.google.com [74.125.224.49]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A60CB27F160 for ; Fri, 12 Sep 2025 05:28:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.224.49 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757654921; cv=none; b=G0HEgNIpHxogOIFAW9IERDhLJvAR26swp+SzZ4flx87H9/mlEBT2Ji+HAbvzdzYCDykOxjQHBKgnhTvxE2dGf8/HqRw+fooZuFwezDc8IGpgjXQDztI5q7N7/iwNzb8nQkmyZm7+iY0GC9iBRWZLYFdXfP41lnMkf09LdWWeEQs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757654921; c=relaxed/simple; bh=iXk5LtD8VQud/p8QuIvkPtAyIAC7E2tqnLsWZY8OouY=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Njc9JtmIWryIjzquxAblwNb86g/UZ9aBiOlDE7WJQHuZUnso7ZSTcVdS5wFsVKTSi7dd4XzJJCmODtbt7onXXA3y66E82KmhlkEo8OPA+HQ4vB81mSPze72PS6u5tgqgSfEvZZICbQSfHTH2WF9yXvk51Nlf0fgun8P+OVo14Bk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=AuvnMtbo; arc=none smtp.client-ip=74.125.224.49 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="AuvnMtbo" Received: by mail-yx1-f49.google.com with SMTP id 956f58d0204a3-6071dbcf3fcso394363d50.0 for ; Thu, 11 Sep 2025 22:28:39 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1757654918; x=1758259718; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=TBxJ9bgU3ETsPO/br9jSR8/72uyQYfX4epNE3oPxRwc=; b=AuvnMtbovYW8BgT/4UfNtvBEEaYvYkdVZABqAlA726TUWkCeMlmXW6UMit0S2d7Pfi YbJfLRX5uU3zXeK5vUr9uz1hj76TNplY6UqXlK5eEkaSkg649iLyFFry9jee72RBwqXO 9FU7IA6B11ZlVvmqM0IAlOaLh7qPu1ukV/RQI1Yi/9uF+gRviP4yF8EsffsfOHJn16N3 VRngVjqCRBF2cdZzJft1ynrhzvlWK/U/SGjyFi2efkVisvB9tjm0FAY5JsGDJGg8Snto bF17tXBkLjkQrCOhZwzmFKplaEMTIWC9CwJsXrTWRX4dEW/evub3U+AsUSt0g8TEBLN9 i4Qg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1757654918; x=1758259718; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=TBxJ9bgU3ETsPO/br9jSR8/72uyQYfX4epNE3oPxRwc=; b=tjqgC2/MUTn0rTqffba0POIIXcMxADgf31adQ+LO+R0jGzDK9qTI6CjDgOypqINe75 pjNegOGIjxjHJlUtsYXoGaRCpi0/5j20eh6C6YUMWDGUQ19AlZFewN/BTdM8tYZW4gm/ xZZOWs0/nTjzYE1oyEeaxCmSEpZaraRwKxVr91iAW3WL43aZ4SyUt/yQyLcGn2UtCryr XZvlw1dUagJYxbsGIShnNxw16IBaERUhkV8yj9Rff+rbVLtdDGwrmGU46mKUMK/z3X/k /LmL/Psk/+hoMu/TwAwzBverqd2+Vr/iykrAnp7baEdTdtJTAHMEAfmDC3JWOtx6BUVZ GHpA== X-Forwarded-Encrypted: i=1; AJvYcCVlNdVN6dz+Fd6ikmp+3EYLddntG4dNK/MbzN6keQW89xt/Q5wTykAZsNlvsHvuqVAVaIQOr92WEyVsc5g=@vger.kernel.org X-Gm-Message-State: AOJu0YwxdH0QG45cQGT5UYrXYfhloWAvPBURqqFWir9LgutmLGUVCFDa tZ6KnHy8mbmBd0Jd1ufzQVx76sM4dyLOYsJW37B1+IAkgFsH4UXw1Teu X-Gm-Gg: ASbGncuXAuTZmMtCz+OcAgTnHSC7Jkt+dM4lyHWzwZwZC1WmaHVVWn8OE7r8aGzQ9AD +t90EVTGXxBmWK22wSoiBJmjEN10T5AeuaqUeBKRFDRGmdR/xfKXOwwFn6aRkXuhM/2d2IbjdbT 8cDlzH6eeAyR3d4j0Y9XN2eWHx1o5zeBW5+hwhZm9QPD66SeMI4pUroGo/4+Y0oExlfZ/E5if/C A1KjTShHHyt1Ma7M/YgZykavvBud4mbE+N5tz66yLd8+5wIKzhvlJgkcLgQttzjyJJ16LL+xhvY 0wy8USpVxcg/olIgIFz19nUTidInfz3iNlELT7kGoZ0vMeGBZgA/VA4oeRUSQ4TUh/KkT1ocs0l wn+LbywlsTbye6G4+4Qrj6Vhy4hggPHFF1/VygXhPmHA= X-Google-Smtp-Source: AGHT+IEvNdoERnjqUX3gWMOkOVdUpABgLqadOZKxTupu5VHNH+oEciO0ODN2lDoQyjjHn43fJYnwgg== X-Received: by 2002:a05:690c:a96:b0:726:697b:9e1f with SMTP id 00721157ae682-73065abe03amr15986057b3.54.1757654918519; Thu, 11 Sep 2025 22:28:38 -0700 (PDT) Received: from localhost ([2a03:2880:25ff:73::]) by smtp.gmail.com with ESMTPSA id 00721157ae682-72f76238482sm8652877b3.12.2025.09.11.22.28.37 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 11 Sep 2025 22:28:37 -0700 (PDT) From: Bobby Eshleman Date: Thu, 11 Sep 2025 22:28:17 -0700 Subject: [PATCH net-next v2 3/3] net: ethtool: prevent user from breaking devmem single-binding rule Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250911-scratch-bobbyeshleman-devmem-tcp-token-upstream-v2-3-c80d735bd453@meta.com> References: <20250911-scratch-bobbyeshleman-devmem-tcp-token-upstream-v2-0-c80d735bd453@meta.com> In-Reply-To: <20250911-scratch-bobbyeshleman-devmem-tcp-token-upstream-v2-0-c80d735bd453@meta.com> To: "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , Kuniyuki Iwashima , Willem de Bruijn , Neal Cardwell , David Ahern Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, Stanislav Fomichev , Mina Almasry , Bobby Eshleman X-Mailer: b4 0.13.0 From: Bobby Eshleman Prevent the user from breaking devmem's single-binding rule by rejecting ethtool TCP/IP requests to modify or delete rules that will redirect a devmem socket to a queue with a different dmabuf binding. This is done in a "best effort" approach because not all steering rule types are validated. If an ethtool_rxnfc flow steering rule evaluates true for: 1) matching a devmem socket's ip addr 2) selecting a queue with a different dmabuf binding 3) is TCP/IP (v4 or v6) ... then reject the ethtool_rxnfc request with -EBUSY to indicate a devmem socket is using the current rules that steer it to its dmabuf binding. Non-TCP/IP rules are completely ignored, and if they do match a devmem flow then they can still break devmem sockets. For example, bytes 0 and 1 of L2 headers, etc... it is still unknown to me if these are possible to evaluate at the time of the ethtool call, and so are left to future work (or never, if not possible). FLOW_RSS rules which guide flows to an RSS context are also not evaluated yet. This seems feasible, but the correct path towards retrieving the RSS context and scanning the queues for dmabuf bindings seems unclear and maybe overkill (re-use parts of ethtool_get_rxnfc?). Signed-off-by: Bobby Eshleman --- include/net/sock.h | 1 + net/ethtool/ioctl.c | 144 ++++++++++++++++++++++++++++++++++++++++++++++++= ++++ net/ipv4/tcp.c | 9 ++++ net/ipv4/tcp_ipv4.c | 6 +++ 4 files changed, 160 insertions(+) diff --git a/include/net/sock.h b/include/net/sock.h index 304aad494764..73a1ff59dcde 100644 --- a/include/net/sock.h +++ b/include/net/sock.h @@ -579,6 +579,7 @@ struct sock { struct net_devmem_dmabuf_binding *binding; atomic_t *urefs; } sk_user_frags; + struct list_head sk_devmem_list; =20 #if IS_ENABLED(CONFIG_PROVE_LOCKING) && IS_ENABLED(CONFIG_MODULES) struct module *sk_owner; diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c index 0b2a4d0573b3..99676ac9bbaa 100644 --- a/net/ethtool/ioctl.c +++ b/net/ethtool/ioctl.c @@ -29,11 +29,16 @@ #include #include #include +#include #include #include #include #include #include "common.h" +#include "../core/devmem.h" + +extern struct list_head devmem_sockets_list; +extern spinlock_t devmem_sockets_lock; =20 /* State held across locks and calls for commands which have devlink fallb= ack */ struct ethtool_devlink_compat { @@ -1169,6 +1174,142 @@ ethtool_get_rxfh_fields(struct net_device *dev, u32= cmd, void __user *useraddr) return ethtool_rxnfc_copy_to_user(useraddr, &info, info_size, NULL); } =20 +static bool +__ethtool_rx_flow_spec_breaks_devmem_sk(struct ethtool_rx_flow_spec *fs, + struct net_device *dev, + struct sock *sk) +{ + struct in6_addr saddr6, smask6, daddr6, dmask6; + struct sockaddr_storage saddr, daddr; + struct sockaddr_in6 *src6, *dst6; + struct sockaddr_in *src4, *dst4; + struct netdev_rx_queue *rxq; + __u32 flow_type; + + if (dev !=3D __sk_dst_get(sk)->dev) + return false; + + src6 =3D (struct sockaddr_in6 *)&saddr; + dst6 =3D (struct sockaddr_in6 *)&daddr; + src4 =3D (struct sockaddr_in *)&saddr; + dst4 =3D (struct sockaddr_in *)&daddr; + + if (sk->sk_family =3D=3D AF_INET6) { + src6->sin6_port =3D inet_sk(sk)->inet_sport; + src6->sin6_addr =3D inet6_sk(sk)->saddr; + dst6->sin6_port =3D inet_sk(sk)->inet_dport; + dst6->sin6_addr =3D sk->sk_v6_daddr; + } else { + src4->sin_port =3D inet_sk(sk)->inet_sport; + src4->sin_addr.s_addr =3D inet_sk(sk)->inet_saddr; + dst4->sin_port =3D inet_sk(sk)->inet_dport; + dst4->sin_addr.s_addr =3D inet_sk(sk)->inet_daddr; + } + + flow_type =3D fs->flow_type & ~(FLOW_EXT | FLOW_MAC_EXT | FLOW_RSS); + + rxq =3D __netif_get_rx_queue(dev, fs->ring_cookie); + if (!rxq) + return false; + + /* If the requested binding and the sk binding is equal then we know + * this rule can't redirect to a different binding. + */ + if (rxq->mp_params.mp_priv =3D=3D sk->sk_user_frags.binding) + return false; + + /* Reject rules that redirect RX devmem sockets to a queue with a + * different dmabuf binding. Because these sockets are on the RX side + * (registered in the recvmsg() path), we compare the opposite + * endpoints: the socket source with the rule destination, and the + * socket destination with the rule source. + * + * Only perform checks on the simplest rules to check, that is, IP/TCP + * rules. Flow hash options are not verified, so may still break TCP + * devmem flows in theory (VLAN tag, bytes 0 and 1 of L4 header, + * etc...). The author of this function was simply not sure how + * to validate these at the time of the ethtool call. + */ + switch (flow_type) { + case IPV4_USER_FLOW: { + const struct ethtool_usrip4_spec *v4_usr_spec, *v4_usr_m_spec; + + v4_usr_spec =3D &fs->h_u.usr_ip4_spec; + v4_usr_m_spec =3D &fs->m_u.usr_ip4_spec; + + if (((v4_usr_spec->ip4src ^ dst4->sin_addr.s_addr) & v4_usr_m_spec->ip4s= rc) || + (v4_usr_spec->ip4dst ^ src4->sin_addr.s_addr) & v4_usr_m_spec->ip4ds= t) { + return true; + } + + return false; + } + case TCP_V4_FLOW: { + const struct ethtool_tcpip4_spec *v4_spec, *v4_m_spec; + + v4_spec =3D &fs->h_u.tcp_ip4_spec; + v4_m_spec =3D &fs->m_u.tcp_ip4_spec; + + if (((v4_spec->ip4src ^ dst4->sin_addr.s_addr) & v4_m_spec->ip4src) || + ((v4_spec->ip4dst ^ src4->sin_addr.s_addr) & v4_m_spec->ip4dst)) + return true; + + return false; + } + case IPV6_USER_FLOW: { + const struct ethtool_usrip6_spec *v6_usr_spec, *v6_usr_m_spec; + + v6_usr_spec =3D &fs->h_u.usr_ip6_spec; + v6_usr_m_spec =3D &fs->m_u.usr_ip6_spec; + + memcpy(&daddr6, v6_usr_spec->ip6dst, sizeof(daddr6)); + memcpy(&dmask6, v6_usr_m_spec->ip6dst, sizeof(dmask6)); + memcpy(&saddr6, v6_usr_spec->ip6src, sizeof(saddr6)); + memcpy(&smask6, v6_usr_m_spec->ip6src, sizeof(smask6)); + + return !ipv6_masked_addr_cmp(&saddr6, &smask6, &dst6->sin6_addr) && + !ipv6_masked_addr_cmp(&daddr6, &dmask6, &src6->sin6_addr); + } + case TCP_V6_FLOW: { + const struct ethtool_tcpip6_spec *v6_spec, *v6_m_spec; + + v6_spec =3D &fs->h_u.tcp_ip6_spec; + v6_m_spec =3D &fs->m_u.tcp_ip6_spec; + + memcpy(&daddr6, v6_spec->ip6dst, sizeof(daddr6)); + memcpy(&dmask6, v6_m_spec->ip6dst, sizeof(dmask6)); + memcpy(&saddr6, v6_spec->ip6src, sizeof(saddr6)); + memcpy(&smask6, v6_m_spec->ip6src, sizeof(smask6)); + + return !ipv6_masked_addr_cmp(&daddr6, &dmask6, &src6->sin6_addr) && + !ipv6_masked_addr_cmp(&saddr6, &smask6, &dst6->sin6_addr); + } + default: + return false; + } +} + +static bool +ethtool_rx_flow_spec_breaks_devmem_sk(struct ethtool_rx_flow_spec *fs, + struct net_device *dev) +{ + struct sock *sk; + bool ret; + + ret =3D false; + + spin_lock_bh(&devmem_sockets_lock); + list_for_each_entry(sk, &devmem_sockets_list, sk_devmem_list) { + if (__ethtool_rx_flow_spec_breaks_devmem_sk(fs, dev, sk)) { + ret =3D true; + break; + } + } + spin_unlock_bh(&devmem_sockets_lock); + + return ret; +} + static noinline_for_stack int ethtool_set_rxnfc(struct net_device *dev, u32 cmd, void __user *useraddr) { @@ -1197,6 +1338,9 @@ static noinline_for_stack int ethtool_set_rxnfc(struc= t net_device *dev, return -EINVAL; } =20 + if (ethtool_rx_flow_spec_breaks_devmem_sk(&info.fs, dev)) + return -EBUSY; + rc =3D ops->set_rxnfc(dev, &info); if (rc) return rc; diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index 438b8132ed89..3f57e658ea80 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -311,6 +311,12 @@ DEFINE_STATIC_KEY_FALSE(tcp_have_smc); EXPORT_SYMBOL(tcp_have_smc); #endif =20 +struct list_head devmem_sockets_list; +EXPORT_SYMBOL_GPL(devmem_sockets_list); + +DEFINE_SPINLOCK(devmem_sockets_lock); +EXPORT_SYMBOL_GPL(devmem_sockets_lock); + /* * Current number of TCP sockets. */ @@ -5229,4 +5235,7 @@ void __init tcp_init(void) BUG_ON(tcp_register_congestion_control(&tcp_reno) !=3D 0); tcp_tsq_work_init(); mptcp_init(); + + spin_lock_init(&devmem_sockets_lock); + INIT_LIST_HEAD(&devmem_sockets_list); } diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c index 68ebf96d06f8..a3213c97aed9 100644 --- a/net/ipv4/tcp_ipv4.c +++ b/net/ipv4/tcp_ipv4.c @@ -92,6 +92,9 @@ =20 #include =20 +extern struct list_head devmem_sockets_list; +extern spinlock_t devmem_sockets_lock; + #ifdef CONFIG_TCP_MD5SIG static int tcp_v4_md5_hash_hdr(char *md5_hash, const struct tcp_md5sig_key= *key, __be32 daddr, __be32 saddr, const struct tcphdr *th); @@ -2559,6 +2562,9 @@ static void tcp_release_user_frags(struct sock *sk) sk->sk_user_frags.binding =3D NULL; kvfree(sk->sk_user_frags.urefs); sk->sk_user_frags.urefs =3D NULL; + spin_lock_bh(&devmem_sockets_lock); + list_del(&sk->sk_devmem_list); + spin_unlock_bh(&devmem_sockets_lock); #endif } =20 --=20 2.47.3