diff mbox series

[net] net: dsa: fix netdev_priv() dereference before check on non-DSA netdevice events

Message ID 20240110003354.2796778-1-vladimir.oltean@nxp.com (mailing list archive)
State Accepted
Commit 844f104790bd69c2e4dbb9ee3eba46fde1fcea7b
Delegated to: Netdev Maintainers
Headers show
Series [net] net: dsa: fix netdev_priv() dereference before check on non-DSA netdevice events | expand

Checks

Context Check Description
netdev/series_format success Single patches do not need cover letters
netdev/tree_selection success Clearly marked for net
netdev/ynl success SINGLE THREAD; Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag present in non-next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 1113 this patch: 1113
netdev/cc_maintainers success CCed 0 of 0 maintainers
netdev/build_clang success Errors and warnings before: 1140 this patch: 1140
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success Fixes tag looks correct
netdev/build_allmodconfig_warn success Errors and warnings before: 1140 this patch: 1140
netdev/checkpatch success total: 0 errors, 0 warnings, 0 checks, 29 lines checked
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0

Commit Message

Vladimir Oltean Jan. 10, 2024, 12:33 a.m. UTC
After the blamed commit, we started doing this dereference for every
NETDEV_CHANGEUPPER and NETDEV_PRECHANGEUPPER event in the system.

static inline struct dsa_port *dsa_user_to_port(const struct net_device *dev)
{
	struct dsa_user_priv *p = netdev_priv(dev);

	return p->dp;
}

Which is obviously bogus, because not all net_devices have a netdev_priv()
of type struct dsa_user_priv. But struct dsa_user_priv is fairly small,
and p->dp means dereferencing 8 bytes starting with offset 16. Most
drivers allocate that much private memory anyway, making our access not
fault, and we discard the bogus data quickly afterwards, so this wasn't
caught.

But the dummy interface is somewhat special in that it calls
alloc_netdev() with a priv size of 0. So every netdev_priv() dereference
is invalid, and we get this when we emit a NETDEV_PRECHANGEUPPER event
with a VLAN as its new upper:

$ ip link add dummy1 type dummy
$ ip link add link dummy1 name dummy1.100 type vlan id 100
[   43.309174] ==================================================================
[   43.316456] BUG: KASAN: slab-out-of-bounds in dsa_user_prechangeupper+0x30/0xe8
[   43.323835] Read of size 8 at addr ffff3f86481d2990 by task ip/374
[   43.330058]
[   43.342436] Call trace:
[   43.366542]  dsa_user_prechangeupper+0x30/0xe8
[   43.371024]  dsa_user_netdevice_event+0xb38/0xee8
[   43.375768]  notifier_call_chain+0xa4/0x210
[   43.379985]  raw_notifier_call_chain+0x24/0x38
[   43.384464]  __netdev_upper_dev_link+0x3ec/0x5d8
[   43.389120]  netdev_upper_dev_link+0x70/0xa8
[   43.393424]  register_vlan_dev+0x1bc/0x310
[   43.397554]  vlan_newlink+0x210/0x248
[   43.401247]  rtnl_newlink+0x9fc/0xe30
[   43.404942]  rtnetlink_rcv_msg+0x378/0x580

Avoid the kernel oops by dereferencing after the type check, as customary.

Fixes: 4c3f80d22b2e ("net: dsa: walk through all changeupper notifier functions")
Reported-and-tested-by: syzbot+d81bcd883824180500c8@syzkaller.appspotmail.com
Closes: https://lore.kernel.org/netdev/0000000000001d4255060e87545c@google.com/
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
 net/dsa/user.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

Comments

Florian Fainelli Jan. 10, 2024, 2:56 a.m. UTC | #1
On 1/9/2024 4:33 PM, Vladimir Oltean wrote:
> After the blamed commit, we started doing this dereference for every
> NETDEV_CHANGEUPPER and NETDEV_PRECHANGEUPPER event in the system.
> 
> static inline struct dsa_port *dsa_user_to_port(const struct net_device *dev)
> {
> 	struct dsa_user_priv *p = netdev_priv(dev);
> 
> 	return p->dp;
> }
> 
> Which is obviously bogus, because not all net_devices have a netdev_priv()
> of type struct dsa_user_priv. But struct dsa_user_priv is fairly small,
> and p->dp means dereferencing 8 bytes starting with offset 16. Most
> drivers allocate that much private memory anyway, making our access not
> fault, and we discard the bogus data quickly afterwards, so this wasn't
> caught.
> 
> But the dummy interface is somewhat special in that it calls
> alloc_netdev() with a priv size of 0. So every netdev_priv() dereference
> is invalid, and we get this when we emit a NETDEV_PRECHANGEUPPER event
> with a VLAN as its new upper:
> 
> $ ip link add dummy1 type dummy
> $ ip link add link dummy1 name dummy1.100 type vlan id 100
> [   43.309174] ==================================================================
> [   43.316456] BUG: KASAN: slab-out-of-bounds in dsa_user_prechangeupper+0x30/0xe8
> [   43.323835] Read of size 8 at addr ffff3f86481d2990 by task ip/374
> [   43.330058]
> [   43.342436] Call trace:
> [   43.366542]  dsa_user_prechangeupper+0x30/0xe8
> [   43.371024]  dsa_user_netdevice_event+0xb38/0xee8
> [   43.375768]  notifier_call_chain+0xa4/0x210
> [   43.379985]  raw_notifier_call_chain+0x24/0x38
> [   43.384464]  __netdev_upper_dev_link+0x3ec/0x5d8
> [   43.389120]  netdev_upper_dev_link+0x70/0xa8
> [   43.393424]  register_vlan_dev+0x1bc/0x310
> [   43.397554]  vlan_newlink+0x210/0x248
> [   43.401247]  rtnl_newlink+0x9fc/0xe30
> [   43.404942]  rtnetlink_rcv_msg+0x378/0x580
> 
> Avoid the kernel oops by dereferencing after the type check, as customary.
> 
> Fixes: 4c3f80d22b2e ("net: dsa: walk through all changeupper notifier functions")
> Reported-and-tested-by: syzbot+d81bcd883824180500c8@syzkaller.appspotmail.com
> Closes: https://lore.kernel.org/netdev/0000000000001d4255060e87545c@google.com/
> Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>

Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
Eric Dumazet Jan. 10, 2024, 1:19 p.m. UTC | #2
On Wed, Jan 10, 2024 at 1:34 AM Vladimir Oltean <vladimir.oltean@nxp.com> wrote:
>
> After the blamed commit, we started doing this dereference for every
> NETDEV_CHANGEUPPER and NETDEV_PRECHANGEUPPER event in the system.
>
> static inline struct dsa_port *dsa_user_to_port(const struct net_device *dev)
> {
>         struct dsa_user_priv *p = netdev_priv(dev);
>
>         return p->dp;
> }

Reviewed-by: Eric Dumazet <edumazet@google.com>

Thanks for the fix !
patchwork-bot+netdevbpf@kernel.org Jan. 12, 2024, 12:40 a.m. UTC | #3
Hello:

This patch was applied to netdev/net.git (main)
by Jakub Kicinski <kuba@kernel.org>:

On Wed, 10 Jan 2024 02:33:54 +0200 you wrote:
> After the blamed commit, we started doing this dereference for every
> NETDEV_CHANGEUPPER and NETDEV_PRECHANGEUPPER event in the system.
> 
> static inline struct dsa_port *dsa_user_to_port(const struct net_device *dev)
> {
> 	struct dsa_user_priv *p = netdev_priv(dev);
> 
> [...]

Here is the summary with links:
  - [net] net: dsa: fix netdev_priv() dereference before check on non-DSA netdevice events
    https://git.kernel.org/netdev/net/c/844f104790bd

You are awesome, thank you!
diff mbox series

Patch

diff --git a/net/dsa/user.c b/net/dsa/user.c
index b738a466e2dc..b15e71cc342c 100644
--- a/net/dsa/user.c
+++ b/net/dsa/user.c
@@ -2806,13 +2806,14 @@  EXPORT_SYMBOL_GPL(dsa_user_dev_check);
 static int dsa_user_changeupper(struct net_device *dev,
 				struct netdev_notifier_changeupper_info *info)
 {
-	struct dsa_port *dp = dsa_user_to_port(dev);
 	struct netlink_ext_ack *extack;
 	int err = NOTIFY_DONE;
+	struct dsa_port *dp;
 
 	if (!dsa_user_dev_check(dev))
 		return err;
 
+	dp = dsa_user_to_port(dev);
 	extack = netdev_notifier_info_to_extack(&info->info);
 
 	if (netif_is_bridge_master(info->upper_dev)) {
@@ -2865,11 +2866,13 @@  static int dsa_user_changeupper(struct net_device *dev,
 static int dsa_user_prechangeupper(struct net_device *dev,
 				   struct netdev_notifier_changeupper_info *info)
 {
-	struct dsa_port *dp = dsa_user_to_port(dev);
+	struct dsa_port *dp;
 
 	if (!dsa_user_dev_check(dev))
 		return NOTIFY_DONE;
 
+	dp = dsa_user_to_port(dev);
+
 	if (netif_is_bridge_master(info->upper_dev) && !info->linking)
 		dsa_port_pre_bridge_leave(dp, info->upper_dev);
 	else if (netif_is_lag_master(info->upper_dev) && !info->linking)