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

net: fix dev_ifsioc_locked() race condition

dev_ifsioc_locked() is called with only RCU read lock, so when
there is a parallel writer changing the mac address, it could
get a partially updated mac address, as shown below:

Thread 1 Thread 2
// eth_commit_mac_addr_change()
memcpy(dev->dev_addr, addr->sa_data, ETH_ALEN);
// dev_ifsioc_locked()
memcpy(ifr->ifr_hwaddr.sa_data,
dev->dev_addr,...);

Close this race condition by guarding them with a RW semaphore,
like netdev_get_name(). We can not use seqlock here as it does not
allow blocking. The writers already take RTNL anyway, so this does
not affect the slow path. To avoid bothering existing
dev_set_mac_address() callers in drivers, introduce a new wrapper
just for user-facing callers on ioctl and rtnetlink paths.

Note, bonding also changes slave mac addresses but that requires
a separate patch due to the complexity of bonding code.

Fixes: 3710becf8a58 ("net: RCU locking for simple ioctl()")
Reported-by: "Gong, Sishuai" <sishuai@purdue.edu>
Cc: Eric Dumazet <eric.dumazet@gmail.com>
Cc: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Cong Wang <cong.wang@bytedance.com>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Cong Wang and committed by
David S. Miller
3b23a32a 7867299c

+58 -21
+3 -4
drivers/net/tap.c
··· 1090 1090 return -ENOLINK; 1091 1091 } 1092 1092 ret = 0; 1093 - u = tap->dev->type; 1093 + dev_get_mac_address(&sa, dev_net(tap->dev), tap->dev->name); 1094 1094 if (copy_to_user(&ifr->ifr_name, tap->dev->name, IFNAMSIZ) || 1095 - copy_to_user(&ifr->ifr_hwaddr.sa_data, tap->dev->dev_addr, ETH_ALEN) || 1096 - put_user(u, &ifr->ifr_hwaddr.sa_family)) 1095 + copy_to_user(&ifr->ifr_hwaddr, &sa, sizeof(sa))) 1097 1096 ret = -EFAULT; 1098 1097 tap_put_tap_dev(tap); 1099 1098 rtnl_unlock(); ··· 1107 1108 rtnl_unlock(); 1108 1109 return -ENOLINK; 1109 1110 } 1110 - ret = dev_set_mac_address(tap->dev, &sa, NULL); 1111 + ret = dev_set_mac_address_user(tap->dev, &sa, NULL); 1111 1112 tap_put_tap_dev(tap); 1112 1113 rtnl_unlock(); 1113 1114 return ret;
+2 -3
drivers/net/tun.c
··· 3107 3107 3108 3108 case SIOCGIFHWADDR: 3109 3109 /* Get hw address */ 3110 - memcpy(ifr.ifr_hwaddr.sa_data, tun->dev->dev_addr, ETH_ALEN); 3111 - ifr.ifr_hwaddr.sa_family = tun->dev->type; 3110 + dev_get_mac_address(&ifr.ifr_hwaddr, net, tun->dev->name); 3112 3111 if (copy_to_user(argp, &ifr, ifreq_len)) 3113 3112 ret = -EFAULT; 3114 3113 break; 3115 3114 3116 3115 case SIOCSIFHWADDR: 3117 3116 /* Set hw address */ 3118 - ret = dev_set_mac_address(tun->dev, &ifr.ifr_hwaddr, NULL); 3117 + ret = dev_set_mac_address_user(tun->dev, &ifr.ifr_hwaddr, NULL); 3119 3118 break; 3120 3119 3121 3120 case TUNGETSNDBUF:
+3
include/linux/netdevice.h
··· 3902 3902 struct netlink_ext_ack *extack); 3903 3903 int dev_set_mac_address(struct net_device *dev, struct sockaddr *sa, 3904 3904 struct netlink_ext_ack *extack); 3905 + int dev_set_mac_address_user(struct net_device *dev, struct sockaddr *sa, 3906 + struct netlink_ext_ack *extack); 3907 + int dev_get_mac_address(struct sockaddr *sa, struct net *net, char *dev_name); 3905 3908 int dev_change_carrier(struct net_device *, bool new_carrier); 3906 3909 int dev_get_phys_port_id(struct net_device *dev, 3907 3910 struct netdev_phys_item_id *ppid);
+42
net/core/dev.c
··· 8937 8937 } 8938 8938 EXPORT_SYMBOL(dev_set_mac_address); 8939 8939 8940 + static DECLARE_RWSEM(dev_addr_sem); 8941 + 8942 + int dev_set_mac_address_user(struct net_device *dev, struct sockaddr *sa, 8943 + struct netlink_ext_ack *extack) 8944 + { 8945 + int ret; 8946 + 8947 + down_write(&dev_addr_sem); 8948 + ret = dev_set_mac_address(dev, sa, extack); 8949 + up_write(&dev_addr_sem); 8950 + return ret; 8951 + } 8952 + EXPORT_SYMBOL(dev_set_mac_address_user); 8953 + 8954 + int dev_get_mac_address(struct sockaddr *sa, struct net *net, char *dev_name) 8955 + { 8956 + size_t size = sizeof(sa->sa_data); 8957 + struct net_device *dev; 8958 + int ret = 0; 8959 + 8960 + down_read(&dev_addr_sem); 8961 + rcu_read_lock(); 8962 + 8963 + dev = dev_get_by_name_rcu(net, dev_name); 8964 + if (!dev) { 8965 + ret = -ENODEV; 8966 + goto unlock; 8967 + } 8968 + if (!dev->addr_len) 8969 + memset(sa->sa_data, 0, size); 8970 + else 8971 + memcpy(sa->sa_data, dev->dev_addr, 8972 + min_t(size_t, size, dev->addr_len)); 8973 + sa->sa_family = dev->type; 8974 + 8975 + unlock: 8976 + rcu_read_unlock(); 8977 + up_read(&dev_addr_sem); 8978 + return ret; 8979 + } 8980 + EXPORT_SYMBOL(dev_get_mac_address); 8981 + 8940 8982 /** 8941 8983 * dev_change_carrier - Change device carrier 8942 8984 * @dev: device
+7 -13
net/core/dev_ioctl.c
··· 123 123 ifr->ifr_mtu = dev->mtu; 124 124 return 0; 125 125 126 - case SIOCGIFHWADDR: 127 - if (!dev->addr_len) 128 - memset(ifr->ifr_hwaddr.sa_data, 0, 129 - sizeof(ifr->ifr_hwaddr.sa_data)); 130 - else 131 - memcpy(ifr->ifr_hwaddr.sa_data, dev->dev_addr, 132 - min(sizeof(ifr->ifr_hwaddr.sa_data), 133 - (size_t)dev->addr_len)); 134 - ifr->ifr_hwaddr.sa_family = dev->type; 135 - return 0; 136 - 137 126 case SIOCGIFSLAVE: 138 127 err = -EINVAL; 139 128 break; ··· 263 274 case SIOCSIFHWADDR: 264 275 if (dev->addr_len > sizeof(struct sockaddr)) 265 276 return -EINVAL; 266 - return dev_set_mac_address(dev, &ifr->ifr_hwaddr, NULL); 277 + return dev_set_mac_address_user(dev, &ifr->ifr_hwaddr, NULL); 267 278 268 279 case SIOCSIFHWBROADCAST: 269 280 if (ifr->ifr_hwaddr.sa_family != dev->type) ··· 407 418 */ 408 419 409 420 switch (cmd) { 421 + case SIOCGIFHWADDR: 422 + dev_load(net, ifr->ifr_name); 423 + ret = dev_get_mac_address(&ifr->ifr_hwaddr, net, ifr->ifr_name); 424 + if (colon) 425 + *colon = ':'; 426 + return ret; 410 427 /* 411 428 * These ioctl calls: 412 429 * - can be done by all. ··· 422 427 case SIOCGIFFLAGS: 423 428 case SIOCGIFMETRIC: 424 429 case SIOCGIFMTU: 425 - case SIOCGIFHWADDR: 426 430 case SIOCGIFSLAVE: 427 431 case SIOCGIFMAP: 428 432 case SIOCGIFINDEX:
+1 -1
net/core/rtnetlink.c
··· 2660 2660 sa->sa_family = dev->type; 2661 2661 memcpy(sa->sa_data, nla_data(tb[IFLA_ADDRESS]), 2662 2662 dev->addr_len); 2663 - err = dev_set_mac_address(dev, sa, extack); 2663 + err = dev_set_mac_address_user(dev, sa, extack); 2664 2664 kfree(sa); 2665 2665 if (err) 2666 2666 goto errout;