netdev.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [Linux Kernel Bug] KASAN: null-ptr-deref Read in generic_hwtstamp_ioctl_lower
@ 2025-10-29  8:45 Jiaming Zhang
  2025-10-29 10:06 ` Kory Maincent
  0 siblings, 1 reply; 16+ messages in thread
From: Jiaming Zhang @ 2025-10-29  8:45 UTC (permalink / raw)
  To: davem, edumazet, kuba, netdev, pabeni
  Cc: horms, kory.maincent, kuniyu, linux-kernel, sdf, syzkaller

[-- Attachment #1: Type: text/plain, Size: 6733 bytes --]

Dear Linux kernel developers and maintainers,

We are writing to report a null pointer dereference bug discovered in
the net subsystem. This bug is reproducible on the latest version
(v6.18-rc3, commit dcb6fa37fd7bc9c3d2b066329b0d27dedf8becaa).

The root cause is in tsconfig_prepare_data(), where a local
kernel_hwtstamp_config struct (cfg) is initialized using {}, setting
all its members to zero. Consequently, cfg.ifr becomes NULL.

cfg is then passed as: tsconfig_prepare_data() ->
dev_get_hwtstamp_phylib() -> vlan_hwtstamp_get() (via
dev->netdev_ops->ndo_hwtstamp_get) -> generic_hwtstamp_get_lower() ->
generic_hwtstamp_ioctl_lower().

The function generic_hwtstamp_ioctl_lower() assumes cfg->ifr is a
valid pointer and attempts to access cfg->ifr->ifr_ifru. This access
dereferences the NULL pointer, triggering the bug.

As a potential fix, we can declare a local struct ifreq variable in
tsconfig_prepare_data(), zero-initializing it, and then assigning its
address to cfg.ifr before calling dev_get_hwtstamp_phylib(). This
ensures that functions down the call chain receive a valid pointer.

If this solution is acceptable, we are happy to prepare and submit a
patch immediately.

The kernel console output, kernel config, syzkaller reproducer, and C
reproducer are attached to help with analysis. The KASAN report from
v6.18-rc3, formatted by syz-symbolize, is listed below:

---

chnl_net:caif_netlink_parms(): no params data found
bridge0: port 1(bridge_slave_0) entered blocking state
bridge0: port 1(bridge_slave_0) entered disabled state
bridge_slave_0: entered allmulticast mode
bridge_slave_0: entered promiscuous mode
bridge0: port 2(bridge_slave_1) entered blocking state
bridge0: port 2(bridge_slave_1) entered disabled state
bridge_slave_1: entered allmulticast mode
bridge_slave_1: entered promiscuous mode
bond0: (slave bond_slave_0): Enslaving as an active interface with an up link
bond0: (slave bond_slave_1): Enslaving as an active interface with an up link
team0: Port device team_slave_0 added
team0: Port device team_slave_1 added
batman_adv: batadv0: Adding interface: batadv_slave_0
batman_adv: batadv0: The MTU of interface batadv_slave_0 is too small
(1500) to handle the transport of batman-adv packets. Packets going
over this interface will be fragmented on layer2 which could impact
the performance. Setting the MTU to 1532 would solve the problem.
batman_adv: batadv0: Not using interface batadv_slave_0 (retrying
later): interface not active
batman_adv: batadv0: Adding interface: batadv_slave_1
batman_adv: batadv0: The MTU of interface batadv_slave_1 is too small
(1500) to handle the transport of batman-adv packets. Packets going
over this interface will be fragmented on layer2 which could impact
the performance. Setting the MTU to 1532 would solve the problem.
batman_adv: batadv0: Not using interface batadv_slave_1 (retrying
later): interface not active
hsr_slave_0: entered promiscuous mode
hsr_slave_1: entered promiscuous mode
netdevsim netdevsim0 netdevsim0: renamed from eth0
netdevsim netdevsim0 netdevsim1: renamed from eth1
netdevsim netdevsim0 netdevsim2: renamed from eth2
netdevsim netdevsim0 netdevsim3: renamed from eth3
bridge0: port 2(bridge_slave_1) entered blocking state
bridge0: port 2(bridge_slave_1) entered forwarding state
bridge0: port 1(bridge_slave_0) entered blocking state
bridge0: port 1(bridge_slave_0) entered forwarding state
8021q: adding VLAN 0 to HW filter on device bond0
8021q: adding VLAN 0 to HW filter on device team0
8021q: adding VLAN 0 to HW filter on device batadv0
veth0_vlan: entered promiscuous mode
veth1_vlan: entered promiscuous mode
veth0_macvtap: entered promiscuous mode
veth1_macvtap: entered promiscuous mode
batman_adv: batadv0: Interface activated: batadv_slave_0
batman_adv: batadv0: Interface activated: batadv_slave_1
==================================================================
BUG: KASAN: null-ptr-deref in generic_hwtstamp_ioctl_lower+0x190/0x2d0
net/core/dev_ioctl.c:447
Read of size 24 at addr 0000000000000010 by task repro.out/9702

CPU: 1 UID: 0 PID: 9702 Comm: repro.out Not tainted 6.18.0-rc3 #1 PREEMPT(full)
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014
Call Trace:
 <TASK>
 __dump_stack lib/dump_stack.c:94 [inline]
 dump_stack_lvl+0x1c1/0x2a0 lib/dump_stack.c:120
 print_report+0x101/0x810 mm/kasan/report.c:485
 kasan_report+0x147/0x180 mm/kasan/report.c:595
 kasan_check_range+0x2b0/0x2c0 mm/kasan/generic.c:200
 __asan_memcpy+0x29/0x70 mm/kasan/shadow.c:105
 generic_hwtstamp_ioctl_lower+0x190/0x2d0 net/core/dev_ioctl.c:447
 tsconfig_prepare_data+0x12c/0x600 net/ethtool/tsconfig.c:51
 ethnl_default_dump_one+0x2f3/0x7e0 net/ethtool/netlink.c:591
 ethnl_default_dumpit+0x30c/0x600 net/ethtool/netlink.c:628
 genl_dumpit+0x10b/0x1b0 net/netlink/genetlink.c:1027
 netlink_dump+0x6e4/0xe90 net/netlink/af_netlink.c:2327
 __netlink_dump_start+0x5cb/0x7e0 net/netlink/af_netlink.c:2442
 genl_family_rcv_msg_dumpit+0x1e7/0x2c0 net/netlink/genetlink.c:1076
 genl_family_rcv_msg net/netlink/genetlink.c:1192 [inline]
 genl_rcv_msg+0x5cd/0x7a0 net/netlink/genetlink.c:1210
 netlink_rcv_skb+0x208/0x470 net/netlink/af_netlink.c:2552
 genl_rcv+0x28/0x40 net/netlink/genetlink.c:1219
 netlink_unicast_kernel net/netlink/af_netlink.c:1320 [inline]
 netlink_unicast+0x82f/0x9e0 net/netlink/af_netlink.c:1346
 netlink_sendmsg+0x805/0xb30 net/netlink/af_netlink.c:1896
 sock_sendmsg_nosec net/socket.c:727 [inline]
 __sock_sendmsg+0x21c/0x270 net/socket.c:742
 ____sys_sendmsg+0x507/0x840 net/socket.c:2630
 ___sys_sendmsg+0x21f/0x2a0 net/socket.c:2684
 __sys_sendmsg net/socket.c:2716 [inline]
 __do_sys_sendmsg net/socket.c:2721 [inline]
 __se_sys_sendmsg net/socket.c:2719 [inline]
 __x64_sys_sendmsg+0x19b/0x260 net/socket.c:2719
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xf3/0xfa0 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x44dec9
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48
89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d
01 f0 ff ff 73 01 c3 48 c7 c1 c0 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007ffdcb83e678 EFLAGS: 00000206 ORIG_RAX: 000000000000002e
RAX: ffffffffffffffda RBX: 00000000004004b8 RCX: 000000000044dec9
RDX: 0000000000000000 RSI: 0000000020000540 RDI: 0000000000000003
RBP: 00007ffdcb83e690 R08: 000000000049443b R09: 000000000049443b
R10: 0000000000000000 R11: 0000000000000206 R12: 000000000040c030
R13: 0000000000000000 R14: 00000000004c1018 R15: 00000000004004b8
 </TASK>
==================================================================

---

Please let me know if any further information is required.

Best Regards,
Jiaming Zhang

[-- Attachment #2: repro.c --]
[-- Type: text/plain, Size: 39282 bytes --]

// autogenerated by syzkaller (https://github.com/google/syzkaller)

#define _GNU_SOURCE

#include <arpa/inet.h>
#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <netinet/in.h>
#include <sched.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/wait.h>
#include <unistd.h>

#include <linux/capability.h>
#include <linux/genetlink.h>
#include <linux/if_addr.h>
#include <linux/if_ether.h>
#include <linux/if_link.h>
#include <linux/if_tun.h>
#include <linux/in6.h>
#include <linux/ip.h>
#include <linux/neighbour.h>
#include <linux/net.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/tcp.h>
#include <linux/veth.h>

static unsigned long long procid;

static bool write_file(const char* file, const char* what, ...)
{
  char buf[1024];
  va_list args;
  va_start(args, what);
  vsnprintf(buf, sizeof(buf), what, args);
  va_end(args);
  buf[sizeof(buf) - 1] = 0;
  int len = strlen(buf);
  int fd = open(file, O_WRONLY | O_CLOEXEC);
  if (fd == -1)
    return false;
  if (write(fd, buf, len) != len) {
    int err = errno;
    close(fd);
    errno = err;
    return false;
  }
  close(fd);
  return true;
}

struct nlmsg {
  char* pos;
  int nesting;
  struct nlattr* nested[8];
  char buf[4096];
};

static void netlink_init(struct nlmsg* nlmsg, int typ, int flags,
                         const void* data, int size)
{
  memset(nlmsg, 0, sizeof(*nlmsg));
  struct nlmsghdr* hdr = (struct nlmsghdr*)nlmsg->buf;
  hdr->nlmsg_type = typ;
  hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | flags;
  memcpy(hdr + 1, data, size);
  nlmsg->pos = (char*)(hdr + 1) + NLMSG_ALIGN(size);
}

static void netlink_attr(struct nlmsg* nlmsg, int typ, const void* data,
                         int size)
{
  struct nlattr* attr = (struct nlattr*)nlmsg->pos;
  attr->nla_len = sizeof(*attr) + size;
  attr->nla_type = typ;
  if (size > 0)
    memcpy(attr + 1, data, size);
  nlmsg->pos += NLMSG_ALIGN(attr->nla_len);
}

static void netlink_nest(struct nlmsg* nlmsg, int typ)
{
  struct nlattr* attr = (struct nlattr*)nlmsg->pos;
  attr->nla_type = typ;
  nlmsg->pos += sizeof(*attr);
  nlmsg->nested[nlmsg->nesting++] = attr;
}

static void netlink_done(struct nlmsg* nlmsg)
{
  struct nlattr* attr = nlmsg->nested[--nlmsg->nesting];
  attr->nla_len = nlmsg->pos - (char*)attr;
}

static int netlink_send_ext(struct nlmsg* nlmsg, int sock, uint16_t reply_type,
                            int* reply_len, bool dofail)
{
  if (nlmsg->pos > nlmsg->buf + sizeof(nlmsg->buf) || nlmsg->nesting)
    exit(1);
  struct nlmsghdr* hdr = (struct nlmsghdr*)nlmsg->buf;
  hdr->nlmsg_len = nlmsg->pos - nlmsg->buf;
  struct sockaddr_nl addr;
  memset(&addr, 0, sizeof(addr));
  addr.nl_family = AF_NETLINK;
  ssize_t n = sendto(sock, nlmsg->buf, hdr->nlmsg_len, 0,
                     (struct sockaddr*)&addr, sizeof(addr));
  if (n != (ssize_t)hdr->nlmsg_len) {
    if (dofail)
      exit(1);
    return -1;
  }
  n = recv(sock, nlmsg->buf, sizeof(nlmsg->buf), 0);
  if (reply_len)
    *reply_len = 0;
  if (n < 0) {
    if (dofail)
      exit(1);
    return -1;
  }
  if (n < (ssize_t)sizeof(struct nlmsghdr)) {
    errno = EINVAL;
    if (dofail)
      exit(1);
    return -1;
  }
  if (hdr->nlmsg_type == NLMSG_DONE)
    return 0;
  if (reply_len && hdr->nlmsg_type == reply_type) {
    *reply_len = n;
    return 0;
  }
  if (n < (ssize_t)(sizeof(struct nlmsghdr) + sizeof(struct nlmsgerr))) {
    errno = EINVAL;
    if (dofail)
      exit(1);
    return -1;
  }
  if (hdr->nlmsg_type != NLMSG_ERROR) {
    errno = EINVAL;
    if (dofail)
      exit(1);
    return -1;
  }
  errno = -((struct nlmsgerr*)(hdr + 1))->error;
  return -errno;
}

static int netlink_send(struct nlmsg* nlmsg, int sock)
{
  return netlink_send_ext(nlmsg, sock, 0, NULL, true);
}

static int netlink_query_family_id(struct nlmsg* nlmsg, int sock,
                                   const char* family_name, bool dofail)
{
  struct genlmsghdr genlhdr;
  memset(&genlhdr, 0, sizeof(genlhdr));
  genlhdr.cmd = CTRL_CMD_GETFAMILY;
  netlink_init(nlmsg, GENL_ID_CTRL, 0, &genlhdr, sizeof(genlhdr));
  netlink_attr(nlmsg, CTRL_ATTR_FAMILY_NAME, family_name,
               strnlen(family_name, GENL_NAMSIZ - 1) + 1);
  int n = 0;
  int err = netlink_send_ext(nlmsg, sock, GENL_ID_CTRL, &n, dofail);
  if (err < 0) {
    return -1;
  }
  uint16_t id = 0;
  struct nlattr* attr = (struct nlattr*)(nlmsg->buf + NLMSG_HDRLEN +
                                         NLMSG_ALIGN(sizeof(genlhdr)));
  for (; (char*)attr < nlmsg->buf + n;
       attr = (struct nlattr*)((char*)attr + NLMSG_ALIGN(attr->nla_len))) {
    if (attr->nla_type == CTRL_ATTR_FAMILY_ID) {
      id = *(uint16_t*)(attr + 1);
      break;
    }
  }
  if (!id) {
    errno = EINVAL;
    return -1;
  }
  recv(sock, nlmsg->buf, sizeof(nlmsg->buf), 0);
  return id;
}

static int netlink_next_msg(struct nlmsg* nlmsg, unsigned int offset,
                            unsigned int total_len)
{
  struct nlmsghdr* hdr = (struct nlmsghdr*)(nlmsg->buf + offset);
  if (offset == total_len || offset + hdr->nlmsg_len > total_len)
    return -1;
  return hdr->nlmsg_len;
}

static void netlink_add_device_impl(struct nlmsg* nlmsg, const char* type,
                                    const char* name, bool up)
{
  struct ifinfomsg hdr;
  memset(&hdr, 0, sizeof(hdr));
  if (up)
    hdr.ifi_flags = hdr.ifi_change = IFF_UP;
  netlink_init(nlmsg, RTM_NEWLINK, NLM_F_EXCL | NLM_F_CREATE, &hdr,
               sizeof(hdr));
  if (name)
    netlink_attr(nlmsg, IFLA_IFNAME, name, strlen(name));
  netlink_nest(nlmsg, IFLA_LINKINFO);
  netlink_attr(nlmsg, IFLA_INFO_KIND, type, strlen(type));
}

static void netlink_add_device(struct nlmsg* nlmsg, int sock, const char* type,
                               const char* name)
{
  netlink_add_device_impl(nlmsg, type, name, false);
  netlink_done(nlmsg);
  int err = netlink_send(nlmsg, sock);
  if (err < 0) {
  }
}

static void netlink_add_veth(struct nlmsg* nlmsg, int sock, const char* name,
                             const char* peer)
{
  netlink_add_device_impl(nlmsg, "veth", name, false);
  netlink_nest(nlmsg, IFLA_INFO_DATA);
  netlink_nest(nlmsg, VETH_INFO_PEER);
  nlmsg->pos += sizeof(struct ifinfomsg);
  netlink_attr(nlmsg, IFLA_IFNAME, peer, strlen(peer));
  netlink_done(nlmsg);
  netlink_done(nlmsg);
  netlink_done(nlmsg);
  int err = netlink_send(nlmsg, sock);
  if (err < 0) {
  }
}

static void netlink_add_xfrm(struct nlmsg* nlmsg, int sock, const char* name)
{
  netlink_add_device_impl(nlmsg, "xfrm", name, true);
  netlink_nest(nlmsg, IFLA_INFO_DATA);
  int if_id = 1;
  netlink_attr(nlmsg, 2, &if_id, sizeof(if_id));
  netlink_done(nlmsg);
  netlink_done(nlmsg);
  int err = netlink_send(nlmsg, sock);
  if (err < 0) {
  }
}

static void netlink_add_hsr(struct nlmsg* nlmsg, int sock, const char* name,
                            const char* slave1, const char* slave2)
{
  netlink_add_device_impl(nlmsg, "hsr", name, false);
  netlink_nest(nlmsg, IFLA_INFO_DATA);
  int ifindex1 = if_nametoindex(slave1);
  netlink_attr(nlmsg, IFLA_HSR_SLAVE1, &ifindex1, sizeof(ifindex1));
  int ifindex2 = if_nametoindex(slave2);
  netlink_attr(nlmsg, IFLA_HSR_SLAVE2, &ifindex2, sizeof(ifindex2));
  netlink_done(nlmsg);
  netlink_done(nlmsg);
  int err = netlink_send(nlmsg, sock);
  if (err < 0) {
  }
}

static void netlink_add_linked(struct nlmsg* nlmsg, int sock, const char* type,
                               const char* name, const char* link)
{
  netlink_add_device_impl(nlmsg, type, name, false);
  netlink_done(nlmsg);
  int ifindex = if_nametoindex(link);
  netlink_attr(nlmsg, IFLA_LINK, &ifindex, sizeof(ifindex));
  int err = netlink_send(nlmsg, sock);
  if (err < 0) {
  }
}

static void netlink_add_vlan(struct nlmsg* nlmsg, int sock, const char* name,
                             const char* link, uint16_t id, uint16_t proto)
{
  netlink_add_device_impl(nlmsg, "vlan", name, false);
  netlink_nest(nlmsg, IFLA_INFO_DATA);
  netlink_attr(nlmsg, IFLA_VLAN_ID, &id, sizeof(id));
  netlink_attr(nlmsg, IFLA_VLAN_PROTOCOL, &proto, sizeof(proto));
  netlink_done(nlmsg);
  netlink_done(nlmsg);
  int ifindex = if_nametoindex(link);
  netlink_attr(nlmsg, IFLA_LINK, &ifindex, sizeof(ifindex));
  int err = netlink_send(nlmsg, sock);
  if (err < 0) {
  }
}

static void netlink_add_macvlan(struct nlmsg* nlmsg, int sock, const char* name,
                                const char* link)
{
  netlink_add_device_impl(nlmsg, "macvlan", name, false);
  netlink_nest(nlmsg, IFLA_INFO_DATA);
  uint32_t mode = MACVLAN_MODE_BRIDGE;
  netlink_attr(nlmsg, IFLA_MACVLAN_MODE, &mode, sizeof(mode));
  netlink_done(nlmsg);
  netlink_done(nlmsg);
  int ifindex = if_nametoindex(link);
  netlink_attr(nlmsg, IFLA_LINK, &ifindex, sizeof(ifindex));
  int err = netlink_send(nlmsg, sock);
  if (err < 0) {
  }
}

static void netlink_add_geneve(struct nlmsg* nlmsg, int sock, const char* name,
                               uint32_t vni, struct in_addr* addr4,
                               struct in6_addr* addr6)
{
  netlink_add_device_impl(nlmsg, "geneve", name, false);
  netlink_nest(nlmsg, IFLA_INFO_DATA);
  netlink_attr(nlmsg, IFLA_GENEVE_ID, &vni, sizeof(vni));
  if (addr4)
    netlink_attr(nlmsg, IFLA_GENEVE_REMOTE, addr4, sizeof(*addr4));
  if (addr6)
    netlink_attr(nlmsg, IFLA_GENEVE_REMOTE6, addr6, sizeof(*addr6));
  netlink_done(nlmsg);
  netlink_done(nlmsg);
  int err = netlink_send(nlmsg, sock);
  if (err < 0) {
  }
}

#define IFLA_IPVLAN_FLAGS 2
#define IPVLAN_MODE_L3S 2
#undef IPVLAN_F_VEPA
#define IPVLAN_F_VEPA 2

static void netlink_add_ipvlan(struct nlmsg* nlmsg, int sock, const char* name,
                               const char* link, uint16_t mode, uint16_t flags)
{
  netlink_add_device_impl(nlmsg, "ipvlan", name, false);
  netlink_nest(nlmsg, IFLA_INFO_DATA);
  netlink_attr(nlmsg, IFLA_IPVLAN_MODE, &mode, sizeof(mode));
  netlink_attr(nlmsg, IFLA_IPVLAN_FLAGS, &flags, sizeof(flags));
  netlink_done(nlmsg);
  netlink_done(nlmsg);
  int ifindex = if_nametoindex(link);
  netlink_attr(nlmsg, IFLA_LINK, &ifindex, sizeof(ifindex));
  int err = netlink_send(nlmsg, sock);
  if (err < 0) {
  }
}

static void netlink_device_change(struct nlmsg* nlmsg, int sock,
                                  const char* name, bool up, const char* master,
                                  const void* mac, int macsize,
                                  const char* new_name)
{
  struct ifinfomsg hdr;
  memset(&hdr, 0, sizeof(hdr));
  if (up)
    hdr.ifi_flags = hdr.ifi_change = IFF_UP;
  hdr.ifi_index = if_nametoindex(name);
  netlink_init(nlmsg, RTM_NEWLINK, 0, &hdr, sizeof(hdr));
  if (new_name)
    netlink_attr(nlmsg, IFLA_IFNAME, new_name, strlen(new_name));
  if (master) {
    int ifindex = if_nametoindex(master);
    netlink_attr(nlmsg, IFLA_MASTER, &ifindex, sizeof(ifindex));
  }
  if (macsize)
    netlink_attr(nlmsg, IFLA_ADDRESS, mac, macsize);
  int err = netlink_send(nlmsg, sock);
  if (err < 0) {
  }
}

static int netlink_add_addr(struct nlmsg* nlmsg, int sock, const char* dev,
                            const void* addr, int addrsize)
{
  struct ifaddrmsg hdr;
  memset(&hdr, 0, sizeof(hdr));
  hdr.ifa_family = addrsize == 4 ? AF_INET : AF_INET6;
  hdr.ifa_prefixlen = addrsize == 4 ? 24 : 120;
  hdr.ifa_scope = RT_SCOPE_UNIVERSE;
  hdr.ifa_index = if_nametoindex(dev);
  netlink_init(nlmsg, RTM_NEWADDR, NLM_F_CREATE | NLM_F_REPLACE, &hdr,
               sizeof(hdr));
  netlink_attr(nlmsg, IFA_LOCAL, addr, addrsize);
  netlink_attr(nlmsg, IFA_ADDRESS, addr, addrsize);
  return netlink_send(nlmsg, sock);
}

static void netlink_add_addr4(struct nlmsg* nlmsg, int sock, const char* dev,
                              const char* addr)
{
  struct in_addr in_addr;
  inet_pton(AF_INET, addr, &in_addr);
  int err = netlink_add_addr(nlmsg, sock, dev, &in_addr, sizeof(in_addr));
  if (err < 0) {
  }
}

static void netlink_add_addr6(struct nlmsg* nlmsg, int sock, const char* dev,
                              const char* addr)
{
  struct in6_addr in6_addr;
  inet_pton(AF_INET6, addr, &in6_addr);
  int err = netlink_add_addr(nlmsg, sock, dev, &in6_addr, sizeof(in6_addr));
  if (err < 0) {
  }
}

static struct nlmsg nlmsg;

#define DEVLINK_FAMILY_NAME "devlink"

#define DEVLINK_CMD_PORT_GET 5
#define DEVLINK_ATTR_BUS_NAME 1
#define DEVLINK_ATTR_DEV_NAME 2
#define DEVLINK_ATTR_NETDEV_NAME 7

static struct nlmsg nlmsg2;

static void initialize_devlink_ports(const char* bus_name, const char* dev_name,
                                     const char* netdev_prefix)
{
  struct genlmsghdr genlhdr;
  int len, total_len, id, err, offset;
  uint16_t netdev_index;
  int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
  if (sock == -1)
    exit(1);
  int rtsock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
  if (rtsock == -1)
    exit(1);
  id = netlink_query_family_id(&nlmsg, sock, DEVLINK_FAMILY_NAME, true);
  if (id == -1)
    goto error;
  memset(&genlhdr, 0, sizeof(genlhdr));
  genlhdr.cmd = DEVLINK_CMD_PORT_GET;
  netlink_init(&nlmsg, id, NLM_F_DUMP, &genlhdr, sizeof(genlhdr));
  netlink_attr(&nlmsg, DEVLINK_ATTR_BUS_NAME, bus_name, strlen(bus_name) + 1);
  netlink_attr(&nlmsg, DEVLINK_ATTR_DEV_NAME, dev_name, strlen(dev_name) + 1);
  err = netlink_send_ext(&nlmsg, sock, id, &total_len, true);
  if (err < 0) {
    goto error;
  }
  offset = 0;
  netdev_index = 0;
  while ((len = netlink_next_msg(&nlmsg, offset, total_len)) != -1) {
    struct nlattr* attr = (struct nlattr*)(nlmsg.buf + offset + NLMSG_HDRLEN +
                                           NLMSG_ALIGN(sizeof(genlhdr)));
    for (; (char*)attr < nlmsg.buf + offset + len;
         attr = (struct nlattr*)((char*)attr + NLMSG_ALIGN(attr->nla_len))) {
      if (attr->nla_type == DEVLINK_ATTR_NETDEV_NAME) {
        char* port_name;
        char netdev_name[IFNAMSIZ];
        port_name = (char*)(attr + 1);
        snprintf(netdev_name, sizeof(netdev_name), "%s%d", netdev_prefix,
                 netdev_index);
        netlink_device_change(&nlmsg2, rtsock, port_name, true, 0, 0, 0,
                              netdev_name);
        break;
      }
    }
    offset += len;
    netdev_index++;
  }
error:
  close(rtsock);
  close(sock);
}

#define DEV_IPV4 "172.20.20.%d"
#define DEV_IPV6 "fe80::%02x"
#define DEV_MAC 0x00aaaaaaaaaa

static void netdevsim_add(unsigned int addr, unsigned int port_count)
{
  write_file("/sys/bus/netdevsim/del_device", "%u", addr);
  if (write_file("/sys/bus/netdevsim/new_device", "%u %u", addr, port_count)) {
    char buf[32];
    snprintf(buf, sizeof(buf), "netdevsim%d", addr);
    initialize_devlink_ports("netdevsim", buf, "netdevsim");
  }
}

#define WG_GENL_NAME "wireguard"
enum wg_cmd {
  WG_CMD_GET_DEVICE,
  WG_CMD_SET_DEVICE,
};
enum wgdevice_attribute {
  WGDEVICE_A_UNSPEC,
  WGDEVICE_A_IFINDEX,
  WGDEVICE_A_IFNAME,
  WGDEVICE_A_PRIVATE_KEY,
  WGDEVICE_A_PUBLIC_KEY,
  WGDEVICE_A_FLAGS,
  WGDEVICE_A_LISTEN_PORT,
  WGDEVICE_A_FWMARK,
  WGDEVICE_A_PEERS,
};
enum wgpeer_attribute {
  WGPEER_A_UNSPEC,
  WGPEER_A_PUBLIC_KEY,
  WGPEER_A_PRESHARED_KEY,
  WGPEER_A_FLAGS,
  WGPEER_A_ENDPOINT,
  WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL,
  WGPEER_A_LAST_HANDSHAKE_TIME,
  WGPEER_A_RX_BYTES,
  WGPEER_A_TX_BYTES,
  WGPEER_A_ALLOWEDIPS,
  WGPEER_A_PROTOCOL_VERSION,
};
enum wgallowedip_attribute {
  WGALLOWEDIP_A_UNSPEC,
  WGALLOWEDIP_A_FAMILY,
  WGALLOWEDIP_A_IPADDR,
  WGALLOWEDIP_A_CIDR_MASK,
};

static void netlink_wireguard_setup(void)
{
  const char ifname_a[] = "wg0";
  const char ifname_b[] = "wg1";
  const char ifname_c[] = "wg2";
  const char private_a[] =
      "\xa0\x5c\xa8\x4f\x6c\x9c\x8e\x38\x53\xe2\xfd\x7a\x70\xae\x0f\xb2\x0f\xa1"
      "\x52\x60\x0c\xb0\x08\x45\x17\x4f\x08\x07\x6f\x8d\x78\x43";
  const char private_b[] =
      "\xb0\x80\x73\xe8\xd4\x4e\x91\xe3\xda\x92\x2c\x22\x43\x82\x44\xbb\x88\x5c"
      "\x69\xe2\x69\xc8\xe9\xd8\x35\xb1\x14\x29\x3a\x4d\xdc\x6e";
  const char private_c[] =
      "\xa0\xcb\x87\x9a\x47\xf5\xbc\x64\x4c\x0e\x69\x3f\xa6\xd0\x31\xc7\x4a\x15"
      "\x53\xb6\xe9\x01\xb9\xff\x2f\x51\x8c\x78\x04\x2f\xb5\x42";
  const char public_a[] =
      "\x97\x5c\x9d\x81\xc9\x83\xc8\x20\x9e\xe7\x81\x25\x4b\x89\x9f\x8e\xd9\x25"
      "\xae\x9f\x09\x23\xc2\x3c\x62\xf5\x3c\x57\xcd\xbf\x69\x1c";
  const char public_b[] =
      "\xd1\x73\x28\x99\xf6\x11\xcd\x89\x94\x03\x4d\x7f\x41\x3d\xc9\x57\x63\x0e"
      "\x54\x93\xc2\x85\xac\xa4\x00\x65\xcb\x63\x11\xbe\x69\x6b";
  const char public_c[] =
      "\xf4\x4d\xa3\x67\xa8\x8e\xe6\x56\x4f\x02\x02\x11\x45\x67\x27\x08\x2f\x5c"
      "\xeb\xee\x8b\x1b\xf5\xeb\x73\x37\x34\x1b\x45\x9b\x39\x22";
  const uint16_t listen_a = 20001;
  const uint16_t listen_b = 20002;
  const uint16_t listen_c = 20003;
  const uint16_t af_inet = AF_INET;
  const uint16_t af_inet6 = AF_INET6;
  const struct sockaddr_in endpoint_b_v4 = {
      .sin_family = AF_INET,
      .sin_port = htons(listen_b),
      .sin_addr = {htonl(INADDR_LOOPBACK)}};
  const struct sockaddr_in endpoint_c_v4 = {
      .sin_family = AF_INET,
      .sin_port = htons(listen_c),
      .sin_addr = {htonl(INADDR_LOOPBACK)}};
  struct sockaddr_in6 endpoint_a_v6 = {.sin6_family = AF_INET6,
                                       .sin6_port = htons(listen_a)};
  endpoint_a_v6.sin6_addr = in6addr_loopback;
  struct sockaddr_in6 endpoint_c_v6 = {.sin6_family = AF_INET6,
                                       .sin6_port = htons(listen_c)};
  endpoint_c_v6.sin6_addr = in6addr_loopback;
  const struct in_addr first_half_v4 = {0};
  const struct in_addr second_half_v4 = {(uint32_t)htonl(128 << 24)};
  const struct in6_addr first_half_v6 = {{{0}}};
  const struct in6_addr second_half_v6 = {{{0x80}}};
  const uint8_t half_cidr = 1;
  const uint16_t persistent_keepalives[] = {1, 3, 7, 9, 14, 19};
  struct genlmsghdr genlhdr = {.cmd = WG_CMD_SET_DEVICE, .version = 1};
  int sock;
  int id, err;
  sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
  if (sock == -1) {
    return;
  }
  id = netlink_query_family_id(&nlmsg, sock, WG_GENL_NAME, true);
  if (id == -1)
    goto error;
  netlink_init(&nlmsg, id, 0, &genlhdr, sizeof(genlhdr));
  netlink_attr(&nlmsg, WGDEVICE_A_IFNAME, ifname_a, strlen(ifname_a) + 1);
  netlink_attr(&nlmsg, WGDEVICE_A_PRIVATE_KEY, private_a, 32);
  netlink_attr(&nlmsg, WGDEVICE_A_LISTEN_PORT, &listen_a, 2);
  netlink_nest(&nlmsg, NLA_F_NESTED | WGDEVICE_A_PEERS);
  netlink_nest(&nlmsg, NLA_F_NESTED | 0);
  netlink_attr(&nlmsg, WGPEER_A_PUBLIC_KEY, public_b, 32);
  netlink_attr(&nlmsg, WGPEER_A_ENDPOINT, &endpoint_b_v4,
               sizeof(endpoint_b_v4));
  netlink_attr(&nlmsg, WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL,
               &persistent_keepalives[0], 2);
  netlink_nest(&nlmsg, NLA_F_NESTED | WGPEER_A_ALLOWEDIPS);
  netlink_nest(&nlmsg, NLA_F_NESTED | 0);
  netlink_attr(&nlmsg, WGALLOWEDIP_A_FAMILY, &af_inet, 2);
  netlink_attr(&nlmsg, WGALLOWEDIP_A_IPADDR, &first_half_v4,
               sizeof(first_half_v4));
  netlink_attr(&nlmsg, WGALLOWEDIP_A_CIDR_MASK, &half_cidr, 1);
  netlink_done(&nlmsg);
  netlink_nest(&nlmsg, NLA_F_NESTED | 0);
  netlink_attr(&nlmsg, WGALLOWEDIP_A_FAMILY, &af_inet6, 2);
  netlink_attr(&nlmsg, WGALLOWEDIP_A_IPADDR, &first_half_v6,
               sizeof(first_half_v6));
  netlink_attr(&nlmsg, WGALLOWEDIP_A_CIDR_MASK, &half_cidr, 1);
  netlink_done(&nlmsg);
  netlink_done(&nlmsg);
  netlink_done(&nlmsg);
  netlink_nest(&nlmsg, NLA_F_NESTED | 0);
  netlink_attr(&nlmsg, WGPEER_A_PUBLIC_KEY, public_c, 32);
  netlink_attr(&nlmsg, WGPEER_A_ENDPOINT, &endpoint_c_v6,
               sizeof(endpoint_c_v6));
  netlink_attr(&nlmsg, WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL,
               &persistent_keepalives[1], 2);
  netlink_nest(&nlmsg, NLA_F_NESTED | WGPEER_A_ALLOWEDIPS);
  netlink_nest(&nlmsg, NLA_F_NESTED | 0);
  netlink_attr(&nlmsg, WGALLOWEDIP_A_FAMILY, &af_inet, 2);
  netlink_attr(&nlmsg, WGALLOWEDIP_A_IPADDR, &second_half_v4,
               sizeof(second_half_v4));
  netlink_attr(&nlmsg, WGALLOWEDIP_A_CIDR_MASK, &half_cidr, 1);
  netlink_done(&nlmsg);
  netlink_nest(&nlmsg, NLA_F_NESTED | 0);
  netlink_attr(&nlmsg, WGALLOWEDIP_A_FAMILY, &af_inet6, 2);
  netlink_attr(&nlmsg, WGALLOWEDIP_A_IPADDR, &second_half_v6,
               sizeof(second_half_v6));
  netlink_attr(&nlmsg, WGALLOWEDIP_A_CIDR_MASK, &half_cidr, 1);
  netlink_done(&nlmsg);
  netlink_done(&nlmsg);
  netlink_done(&nlmsg);
  netlink_done(&nlmsg);
  err = netlink_send(&nlmsg, sock);
  if (err < 0) {
  }
  netlink_init(&nlmsg, id, 0, &genlhdr, sizeof(genlhdr));
  netlink_attr(&nlmsg, WGDEVICE_A_IFNAME, ifname_b, strlen(ifname_b) + 1);
  netlink_attr(&nlmsg, WGDEVICE_A_PRIVATE_KEY, private_b, 32);
  netlink_attr(&nlmsg, WGDEVICE_A_LISTEN_PORT, &listen_b, 2);
  netlink_nest(&nlmsg, NLA_F_NESTED | WGDEVICE_A_PEERS);
  netlink_nest(&nlmsg, NLA_F_NESTED | 0);
  netlink_attr(&nlmsg, WGPEER_A_PUBLIC_KEY, public_a, 32);
  netlink_attr(&nlmsg, WGPEER_A_ENDPOINT, &endpoint_a_v6,
               sizeof(endpoint_a_v6));
  netlink_attr(&nlmsg, WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL,
               &persistent_keepalives[2], 2);
  netlink_nest(&nlmsg, NLA_F_NESTED | WGPEER_A_ALLOWEDIPS);
  netlink_nest(&nlmsg, NLA_F_NESTED | 0);
  netlink_attr(&nlmsg, WGALLOWEDIP_A_FAMILY, &af_inet, 2);
  netlink_attr(&nlmsg, WGALLOWEDIP_A_IPADDR, &first_half_v4,
               sizeof(first_half_v4));
  netlink_attr(&nlmsg, WGALLOWEDIP_A_CIDR_MASK, &half_cidr, 1);
  netlink_done(&nlmsg);
  netlink_nest(&nlmsg, NLA_F_NESTED | 0);
  netlink_attr(&nlmsg, WGALLOWEDIP_A_FAMILY, &af_inet6, 2);
  netlink_attr(&nlmsg, WGALLOWEDIP_A_IPADDR, &first_half_v6,
               sizeof(first_half_v6));
  netlink_attr(&nlmsg, WGALLOWEDIP_A_CIDR_MASK, &half_cidr, 1);
  netlink_done(&nlmsg);
  netlink_done(&nlmsg);
  netlink_done(&nlmsg);
  netlink_nest(&nlmsg, NLA_F_NESTED | 0);
  netlink_attr(&nlmsg, WGPEER_A_PUBLIC_KEY, public_c, 32);
  netlink_attr(&nlmsg, WGPEER_A_ENDPOINT, &endpoint_c_v4,
               sizeof(endpoint_c_v4));
  netlink_attr(&nlmsg, WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL,
               &persistent_keepalives[3], 2);
  netlink_nest(&nlmsg, NLA_F_NESTED | WGPEER_A_ALLOWEDIPS);
  netlink_nest(&nlmsg, NLA_F_NESTED | 0);
  netlink_attr(&nlmsg, WGALLOWEDIP_A_FAMILY, &af_inet, 2);
  netlink_attr(&nlmsg, WGALLOWEDIP_A_IPADDR, &second_half_v4,
               sizeof(second_half_v4));
  netlink_attr(&nlmsg, WGALLOWEDIP_A_CIDR_MASK, &half_cidr, 1);
  netlink_done(&nlmsg);
  netlink_nest(&nlmsg, NLA_F_NESTED | 0);
  netlink_attr(&nlmsg, WGALLOWEDIP_A_FAMILY, &af_inet6, 2);
  netlink_attr(&nlmsg, WGALLOWEDIP_A_IPADDR, &second_half_v6,
               sizeof(second_half_v6));
  netlink_attr(&nlmsg, WGALLOWEDIP_A_CIDR_MASK, &half_cidr, 1);
  netlink_done(&nlmsg);
  netlink_done(&nlmsg);
  netlink_done(&nlmsg);
  netlink_done(&nlmsg);
  err = netlink_send(&nlmsg, sock);
  if (err < 0) {
  }
  netlink_init(&nlmsg, id, 0, &genlhdr, sizeof(genlhdr));
  netlink_attr(&nlmsg, WGDEVICE_A_IFNAME, ifname_c, strlen(ifname_c) + 1);
  netlink_attr(&nlmsg, WGDEVICE_A_PRIVATE_KEY, private_c, 32);
  netlink_attr(&nlmsg, WGDEVICE_A_LISTEN_PORT, &listen_c, 2);
  netlink_nest(&nlmsg, NLA_F_NESTED | WGDEVICE_A_PEERS);
  netlink_nest(&nlmsg, NLA_F_NESTED | 0);
  netlink_attr(&nlmsg, WGPEER_A_PUBLIC_KEY, public_a, 32);
  netlink_attr(&nlmsg, WGPEER_A_ENDPOINT, &endpoint_a_v6,
               sizeof(endpoint_a_v6));
  netlink_attr(&nlmsg, WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL,
               &persistent_keepalives[4], 2);
  netlink_nest(&nlmsg, NLA_F_NESTED | WGPEER_A_ALLOWEDIPS);
  netlink_nest(&nlmsg, NLA_F_NESTED | 0);
  netlink_attr(&nlmsg, WGALLOWEDIP_A_FAMILY, &af_inet, 2);
  netlink_attr(&nlmsg, WGALLOWEDIP_A_IPADDR, &first_half_v4,
               sizeof(first_half_v4));
  netlink_attr(&nlmsg, WGALLOWEDIP_A_CIDR_MASK, &half_cidr, 1);
  netlink_done(&nlmsg);
  netlink_nest(&nlmsg, NLA_F_NESTED | 0);
  netlink_attr(&nlmsg, WGALLOWEDIP_A_FAMILY, &af_inet6, 2);
  netlink_attr(&nlmsg, WGALLOWEDIP_A_IPADDR, &first_half_v6,
               sizeof(first_half_v6));
  netlink_attr(&nlmsg, WGALLOWEDIP_A_CIDR_MASK, &half_cidr, 1);
  netlink_done(&nlmsg);
  netlink_done(&nlmsg);
  netlink_done(&nlmsg);
  netlink_nest(&nlmsg, NLA_F_NESTED | 0);
  netlink_attr(&nlmsg, WGPEER_A_PUBLIC_KEY, public_b, 32);
  netlink_attr(&nlmsg, WGPEER_A_ENDPOINT, &endpoint_b_v4,
               sizeof(endpoint_b_v4));
  netlink_attr(&nlmsg, WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL,
               &persistent_keepalives[5], 2);
  netlink_nest(&nlmsg, NLA_F_NESTED | WGPEER_A_ALLOWEDIPS);
  netlink_nest(&nlmsg, NLA_F_NESTED | 0);
  netlink_attr(&nlmsg, WGALLOWEDIP_A_FAMILY, &af_inet, 2);
  netlink_attr(&nlmsg, WGALLOWEDIP_A_IPADDR, &second_half_v4,
               sizeof(second_half_v4));
  netlink_attr(&nlmsg, WGALLOWEDIP_A_CIDR_MASK, &half_cidr, 1);
  netlink_done(&nlmsg);
  netlink_nest(&nlmsg, NLA_F_NESTED | 0);
  netlink_attr(&nlmsg, WGALLOWEDIP_A_FAMILY, &af_inet6, 2);
  netlink_attr(&nlmsg, WGALLOWEDIP_A_IPADDR, &second_half_v6,
               sizeof(second_half_v6));
  netlink_attr(&nlmsg, WGALLOWEDIP_A_CIDR_MASK, &half_cidr, 1);
  netlink_done(&nlmsg);
  netlink_done(&nlmsg);
  netlink_done(&nlmsg);
  netlink_done(&nlmsg);
  err = netlink_send(&nlmsg, sock);
  if (err < 0) {
  }

error:
  close(sock);
}

static void initialize_netdevices(void)
{
  char netdevsim[16];
  sprintf(netdevsim, "netdevsim%d", (int)procid);
  struct {
    const char* type;
    const char* dev;
  } devtypes[] = {
      {"ip6gretap", "ip6gretap0"}, {"bridge", "bridge0"}, {"vcan", "vcan0"},
      {"bond", "bond0"},           {"team", "team0"},     {"dummy", "dummy0"},
      {"nlmon", "nlmon0"},         {"caif", "caif0"},     {"batadv", "batadv0"},
      {"vxcan", "vxcan1"},         {"veth", 0},           {"wireguard", "wg0"},
      {"wireguard", "wg1"},        {"wireguard", "wg2"},
  };
  const char* devmasters[] = {"bridge", "bond", "team", "batadv"};
  struct {
    const char* name;
    int macsize;
    bool noipv6;
  } devices[] = {
      {"lo", ETH_ALEN},
      {"sit0", 0},
      {"bridge0", ETH_ALEN},
      {"vcan0", 0, true},
      {"tunl0", 0},
      {"gre0", 0},
      {"gretap0", ETH_ALEN},
      {"ip_vti0", 0},
      {"ip6_vti0", 0},
      {"ip6tnl0", 0},
      {"ip6gre0", 0},
      {"ip6gretap0", ETH_ALEN},
      {"erspan0", ETH_ALEN},
      {"bond0", ETH_ALEN},
      {"veth0", ETH_ALEN},
      {"veth1", ETH_ALEN},
      {"team0", ETH_ALEN},
      {"veth0_to_bridge", ETH_ALEN},
      {"veth1_to_bridge", ETH_ALEN},
      {"veth0_to_bond", ETH_ALEN},
      {"veth1_to_bond", ETH_ALEN},
      {"veth0_to_team", ETH_ALEN},
      {"veth1_to_team", ETH_ALEN},
      {"veth0_to_hsr", ETH_ALEN},
      {"veth1_to_hsr", ETH_ALEN},
      {"hsr0", 0},
      {"dummy0", ETH_ALEN},
      {"nlmon0", 0},
      {"vxcan0", 0, true},
      {"vxcan1", 0, true},
      {"caif0", ETH_ALEN},
      {"batadv0", ETH_ALEN},
      {netdevsim, ETH_ALEN},
      {"xfrm0", ETH_ALEN},
      {"veth0_virt_wifi", ETH_ALEN},
      {"veth1_virt_wifi", ETH_ALEN},
      {"virt_wifi0", ETH_ALEN},
      {"veth0_vlan", ETH_ALEN},
      {"veth1_vlan", ETH_ALEN},
      {"vlan0", ETH_ALEN},
      {"vlan1", ETH_ALEN},
      {"macvlan0", ETH_ALEN},
      {"macvlan1", ETH_ALEN},
      {"ipvlan0", ETH_ALEN},
      {"ipvlan1", ETH_ALEN},
      {"veth0_macvtap", ETH_ALEN},
      {"veth1_macvtap", ETH_ALEN},
      {"macvtap0", ETH_ALEN},
      {"macsec0", ETH_ALEN},
      {"veth0_to_batadv", ETH_ALEN},
      {"veth1_to_batadv", ETH_ALEN},
      {"batadv_slave_0", ETH_ALEN},
      {"batadv_slave_1", ETH_ALEN},
      {"geneve0", ETH_ALEN},
      {"geneve1", ETH_ALEN},
      {"wg0", 0},
      {"wg1", 0},
      {"wg2", 0},
  };
  int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
  if (sock == -1)
    exit(1);
  unsigned i;
  for (i = 0; i < sizeof(devtypes) / sizeof(devtypes[0]); i++)
    netlink_add_device(&nlmsg, sock, devtypes[i].type, devtypes[i].dev);
  for (i = 0; i < sizeof(devmasters) / (sizeof(devmasters[0])); i++) {
    char master[32], slave0[32], veth0[32], slave1[32], veth1[32];
    sprintf(slave0, "%s_slave_0", devmasters[i]);
    sprintf(veth0, "veth0_to_%s", devmasters[i]);
    netlink_add_veth(&nlmsg, sock, slave0, veth0);
    sprintf(slave1, "%s_slave_1", devmasters[i]);
    sprintf(veth1, "veth1_to_%s", devmasters[i]);
    netlink_add_veth(&nlmsg, sock, slave1, veth1);
    sprintf(master, "%s0", devmasters[i]);
    netlink_device_change(&nlmsg, sock, slave0, false, master, 0, 0, NULL);
    netlink_device_change(&nlmsg, sock, slave1, false, master, 0, 0, NULL);
  }
  netlink_add_xfrm(&nlmsg, sock, "xfrm0");
  netlink_device_change(&nlmsg, sock, "bridge_slave_0", true, 0, 0, 0, NULL);
  netlink_device_change(&nlmsg, sock, "bridge_slave_1", true, 0, 0, 0, NULL);
  netlink_add_veth(&nlmsg, sock, "hsr_slave_0", "veth0_to_hsr");
  netlink_add_veth(&nlmsg, sock, "hsr_slave_1", "veth1_to_hsr");
  netlink_add_hsr(&nlmsg, sock, "hsr0", "hsr_slave_0", "hsr_slave_1");
  netlink_device_change(&nlmsg, sock, "hsr_slave_0", true, 0, 0, 0, NULL);
  netlink_device_change(&nlmsg, sock, "hsr_slave_1", true, 0, 0, 0, NULL);
  netlink_add_veth(&nlmsg, sock, "veth0_virt_wifi", "veth1_virt_wifi");
  netlink_add_linked(&nlmsg, sock, "virt_wifi", "virt_wifi0",
                     "veth1_virt_wifi");
  netlink_add_veth(&nlmsg, sock, "veth0_vlan", "veth1_vlan");
  netlink_add_vlan(&nlmsg, sock, "vlan0", "veth0_vlan", 0, htons(ETH_P_8021Q));
  netlink_add_vlan(&nlmsg, sock, "vlan1", "veth0_vlan", 1, htons(ETH_P_8021AD));
  netlink_add_macvlan(&nlmsg, sock, "macvlan0", "veth1_vlan");
  netlink_add_macvlan(&nlmsg, sock, "macvlan1", "veth1_vlan");
  netlink_add_ipvlan(&nlmsg, sock, "ipvlan0", "veth0_vlan", IPVLAN_MODE_L2, 0);
  netlink_add_ipvlan(&nlmsg, sock, "ipvlan1", "veth0_vlan", IPVLAN_MODE_L3S,
                     IPVLAN_F_VEPA);
  netlink_add_veth(&nlmsg, sock, "veth0_macvtap", "veth1_macvtap");
  netlink_add_linked(&nlmsg, sock, "macvtap", "macvtap0", "veth0_macvtap");
  netlink_add_linked(&nlmsg, sock, "macsec", "macsec0", "veth1_macvtap");
  char addr[32];
  sprintf(addr, DEV_IPV4, 14 + 10);
  struct in_addr geneve_addr4;
  if (inet_pton(AF_INET, addr, &geneve_addr4) <= 0)
    exit(1);
  struct in6_addr geneve_addr6;
  if (inet_pton(AF_INET6, "fc00::01", &geneve_addr6) <= 0)
    exit(1);
  netlink_add_geneve(&nlmsg, sock, "geneve0", 0, &geneve_addr4, 0);
  netlink_add_geneve(&nlmsg, sock, "geneve1", 1, 0, &geneve_addr6);
  netdevsim_add((int)procid, 4);
  netlink_wireguard_setup();
  for (i = 0; i < sizeof(devices) / (sizeof(devices[0])); i++) {
    char addr[32];
    sprintf(addr, DEV_IPV4, i + 10);
    netlink_add_addr4(&nlmsg, sock, devices[i].name, addr);
    if (!devices[i].noipv6) {
      sprintf(addr, DEV_IPV6, i + 10);
      netlink_add_addr6(&nlmsg, sock, devices[i].name, addr);
    }
    uint64_t macaddr = DEV_MAC + ((i + 10ull) << 40);
    netlink_device_change(&nlmsg, sock, devices[i].name, true, 0, &macaddr,
                          devices[i].macsize, NULL);
  }
  close(sock);
}
static void initialize_netdevices_init(void)
{
  int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
  if (sock == -1)
    exit(1);
  struct {
    const char* type;
    int macsize;
    bool noipv6;
    bool noup;
  } devtypes[] = {
      {"nr", 7, true},
      {"rose", 5, true, true},
  };
  unsigned i;
  for (i = 0; i < sizeof(devtypes) / sizeof(devtypes[0]); i++) {
    char dev[32], addr[32];
    sprintf(dev, "%s%d", devtypes[i].type, (int)procid);
    sprintf(addr, "172.30.%d.%d", i, (int)procid + 1);
    netlink_add_addr4(&nlmsg, sock, dev, addr);
    if (!devtypes[i].noipv6) {
      sprintf(addr, "fe88::%02x:%02x", i, (int)procid + 1);
      netlink_add_addr6(&nlmsg, sock, dev, addr);
    }
    int macsize = devtypes[i].macsize;
    uint64_t macaddr = 0xbbbbbb +
                       ((unsigned long long)i << (8 * (macsize - 2))) +
                       (procid << (8 * (macsize - 1)));
    netlink_device_change(&nlmsg, sock, dev, !devtypes[i].noup, 0, &macaddr,
                          macsize, NULL);
  }
  close(sock);
}

static long syz_genetlink_get_family_id(volatile long name,
                                        volatile long sock_arg)
{
  int fd = sock_arg;
  if (fd < 0) {
    fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
    if (fd == -1) {
      return -1;
    }
  }
  struct nlmsg nlmsg_tmp;
  int ret = netlink_query_family_id(&nlmsg_tmp, fd, (char*)name, false);
  if ((int)sock_arg < 0)
    close(fd);
  if (ret < 0) {
    return -1;
  }
  return ret;
}

static void setup_binderfs();
static void setup_fusectl();
static void sandbox_common_mount_tmpfs(void)
{
  write_file("/proc/sys/fs/mount-max", "100000");
  if (mkdir("./syz-tmp", 0777))
    exit(1);
  if (mount("", "./syz-tmp", "tmpfs", 0, NULL))
    exit(1);
  if (mkdir("./syz-tmp/newroot", 0777))
    exit(1);
  if (mkdir("./syz-tmp/newroot/dev", 0700))
    exit(1);
  unsigned bind_mount_flags = MS_BIND | MS_REC | MS_PRIVATE;
  if (mount("/dev", "./syz-tmp/newroot/dev", NULL, bind_mount_flags, NULL))
    exit(1);
  if (mkdir("./syz-tmp/newroot/proc", 0700))
    exit(1);
  if (mount("syz-proc", "./syz-tmp/newroot/proc", "proc", 0, NULL))
    exit(1);
  if (mkdir("./syz-tmp/newroot/selinux", 0700))
    exit(1);
  const char* selinux_path = "./syz-tmp/newroot/selinux";
  if (mount("/selinux", selinux_path, NULL, bind_mount_flags, NULL)) {
    if (errno != ENOENT)
      exit(1);
    if (mount("/sys/fs/selinux", selinux_path, NULL, bind_mount_flags, NULL) &&
        errno != ENOENT)
      exit(1);
  }
  if (mkdir("./syz-tmp/newroot/sys", 0700))
    exit(1);
  if (mount("/sys", "./syz-tmp/newroot/sys", 0, bind_mount_flags, NULL))
    exit(1);
  if (mount("/sys/kernel/debug", "./syz-tmp/newroot/sys/kernel/debug", NULL,
            bind_mount_flags, NULL) &&
      errno != ENOENT)
    exit(1);
  if (mount("/sys/fs/smackfs", "./syz-tmp/newroot/sys/fs/smackfs", NULL,
            bind_mount_flags, NULL) &&
      errno != ENOENT)
    exit(1);
  if (mount("/proc/sys/fs/binfmt_misc",
            "./syz-tmp/newroot/proc/sys/fs/binfmt_misc", NULL, bind_mount_flags,
            NULL) &&
      errno != ENOENT)
    exit(1);
  if (mkdir("./syz-tmp/pivot", 0777))
    exit(1);
  if (syscall(SYS_pivot_root, "./syz-tmp", "./syz-tmp/pivot")) {
    if (chdir("./syz-tmp"))
      exit(1);
  } else {
    if (chdir("/"))
      exit(1);
    if (umount2("./pivot", MNT_DETACH))
      exit(1);
  }
  if (chroot("./newroot"))
    exit(1);
  if (chdir("/"))
    exit(1);
  setup_binderfs();
  setup_fusectl();
}

static void setup_fusectl()
{
  if (mount(0, "/sys/fs/fuse/connections", "fusectl", 0, 0)) {
  }
}

static void setup_binderfs()
{
  if (mkdir("/dev/binderfs", 0777)) {
  }
  if (mount("binder", "/dev/binderfs", "binder", 0, NULL)) {
  }
  if (symlink("/dev/binderfs", "./binderfs")) {
  }
}

static void loop();

static void sandbox_common()
{
  prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
  if (getppid() == 1)
    exit(1);
  struct rlimit rlim;
  rlim.rlim_cur = rlim.rlim_max = (200 << 20);
  setrlimit(RLIMIT_AS, &rlim);
  rlim.rlim_cur = rlim.rlim_max = 32 << 20;
  setrlimit(RLIMIT_MEMLOCK, &rlim);
  rlim.rlim_cur = rlim.rlim_max = 136 << 20;
  setrlimit(RLIMIT_FSIZE, &rlim);
  rlim.rlim_cur = rlim.rlim_max = 1 << 20;
  setrlimit(RLIMIT_STACK, &rlim);
  rlim.rlim_cur = rlim.rlim_max = 128 << 20;
  setrlimit(RLIMIT_CORE, &rlim);
  rlim.rlim_cur = rlim.rlim_max = 256;
  setrlimit(RLIMIT_NOFILE, &rlim);
  if (unshare(CLONE_NEWNS)) {
  }
  if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL)) {
  }
  if (unshare(CLONE_NEWIPC)) {
  }
  if (unshare(0x02000000)) {
  }
  if (unshare(CLONE_NEWUTS)) {
  }
  if (unshare(CLONE_SYSVSEM)) {
  }
  typedef struct {
    const char* name;
    const char* value;
  } sysctl_t;
  static const sysctl_t sysctls[] = {
      {"/proc/sys/kernel/shmmax", "16777216"},
      {"/proc/sys/kernel/shmall", "536870912"},
      {"/proc/sys/kernel/shmmni", "1024"},
      {"/proc/sys/kernel/msgmax", "8192"},
      {"/proc/sys/kernel/msgmni", "1024"},
      {"/proc/sys/kernel/msgmnb", "1024"},
      {"/proc/sys/kernel/sem", "1024 1048576 500 1024"},
  };
  unsigned i;
  for (i = 0; i < sizeof(sysctls) / sizeof(sysctls[0]); i++)
    write_file(sysctls[i].name, sysctls[i].value);
}

static int wait_for_loop(int pid)
{
  if (pid < 0)
    exit(1);
  int status = 0;
  while (waitpid(-1, &status, __WALL) != pid) {
  }
  return WEXITSTATUS(status);
}

static void drop_caps(void)
{
  struct __user_cap_header_struct cap_hdr = {};
  struct __user_cap_data_struct cap_data[2] = {};
  cap_hdr.version = _LINUX_CAPABILITY_VERSION_3;
  cap_hdr.pid = getpid();
  if (syscall(SYS_capget, &cap_hdr, &cap_data))
    exit(1);
  const int drop = (1 << CAP_SYS_PTRACE) | (1 << CAP_SYS_NICE);
  cap_data[0].effective &= ~drop;
  cap_data[0].permitted &= ~drop;
  cap_data[0].inheritable &= ~drop;
  if (syscall(SYS_capset, &cap_hdr, &cap_data))
    exit(1);
}

static int do_sandbox_none(void)
{
  if (unshare(CLONE_NEWPID)) {
  }
  int pid = fork();
  if (pid != 0)
    return wait_for_loop(pid);
  sandbox_common();
  drop_caps();
  initialize_netdevices_init();
  if (unshare(CLONE_NEWNET)) {
  }
  write_file("/proc/sys/net/ipv4/ping_group_range", "0 65535");
  initialize_netdevices();
  sandbox_common_mount_tmpfs();
  loop();
  exit(1);
}

uint64_t r[2] = {0xffffffffffffffff, 0xffffffffffffffff};

void loop(void)
{
  intptr_t res = 0;
  if (write(1, "executing program\n", sizeof("executing program\n") - 1)) {
  }
  memcpy((void*)0x200000c0, "ethtool\000", 8);
  res = -1;
  res = syz_genetlink_get_family_id(/*name=*/0x200000c0, /*fd=*/-1);
  if (res != -1)
    r[0] = res;
  res = syscall(__NR_socket, /*domain=*/0x10ul, /*type=*/3ul, /*proto=*/0x10);
  if (res != -1)
    r[1] = res;
  *(uint64_t*)0x20000540 = 0;
  *(uint32_t*)0x20000548 = 0;
  *(uint64_t*)0x20000550 = 0x20000500;
  *(uint64_t*)0x20000500 = 0x20000400;
  memcpy((void*)0x20000400, "\x14\x00\x00\x00", 4);
  *(uint16_t*)0x20000404 = r[0];
  memcpy((void*)0x20000406,
         "\x01\x03\x26\xbd\x70\x00\xff\xdb\xdf\x25\x2e\x00\x00\x00", 14);
  *(uint64_t*)0x20000508 = 0x14;
  *(uint64_t*)0x20000558 = 1;
  *(uint64_t*)0x20000560 = 0;
  *(uint64_t*)0x20000568 = 0;
  *(uint32_t*)0x20000570 = 0;
  syscall(__NR_sendmsg, /*fd=*/r[1], /*msg=*/0x20000540ul, /*f=*/0ul);
}
int main(void)
{
  syscall(__NR_mmap, /*addr=*/0x1ffff000ul, /*len=*/0x1000ul, /*prot=*/0ul,
          /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/ 0x32ul, /*fd=*/-1,
          /*offset=*/0ul);
  syscall(__NR_mmap, /*addr=*/0x20000000ul, /*len=*/0x1000000ul,
          /*prot=PROT_WRITE|PROT_READ|PROT_EXEC*/ 7ul,
          /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/ 0x32ul, /*fd=*/-1,
          /*offset=*/0ul);
  syscall(__NR_mmap, /*addr=*/0x21000000ul, /*len=*/0x1000ul, /*prot=*/0ul,
          /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/ 0x32ul, /*fd=*/-1,
          /*offset=*/0ul);
  const char* reason;
  (void)reason;
  do_sandbox_none();
  return 0;
}

[-- Attachment #3: repro.syz --]
[-- Type: application/octet-stream, Size: 313 bytes --]

r0 = syz_genetlink_get_family_id$ethtool(&(0x7f00000000c0), 0xffffffffffffffff)
r1 = socket$nl_generic(0x10, 0x3, 0x10)
sendmsg$ETHTOOL_MSG_EEE_GET(r1, &(0x7f0000000540)={0x0, 0x0, &(0x7f0000000500)={&(0x7f0000000400)=ANY=[@ANYBLOB="14000000", @ANYRES16=r0, @ANYBLOB="010326bd7000ffdbdf252e000000"], 0x14}}, 0x0)

[-- Attachment #4: .config --]
[-- Type: application/xml, Size: 272384 bytes --]

[-- Attachment #5: report --]
[-- Type: application/octet-stream, Size: 5245 bytes --]

chnl_net:caif_netlink_parms(): no params data found
bridge0: port 1(bridge_slave_0) entered blocking state
bridge0: port 1(bridge_slave_0) entered disabled state
bridge_slave_0: entered allmulticast mode
bridge_slave_0: entered promiscuous mode
bridge0: port 2(bridge_slave_1) entered blocking state
bridge0: port 2(bridge_slave_1) entered disabled state
bridge_slave_1: entered allmulticast mode
bridge_slave_1: entered promiscuous mode
bond0: (slave bond_slave_0): Enslaving as an active interface with an up link
bond0: (slave bond_slave_1): Enslaving as an active interface with an up link
team0: Port device team_slave_0 added
team0: Port device team_slave_1 added
batman_adv: batadv0: Adding interface: batadv_slave_0
batman_adv: batadv0: The MTU of interface batadv_slave_0 is too small (1500) to handle the transport of batman-adv packets. Packets going over this interface will be fragmented on layer2 which could impact the performance. Setting the MTU to 1532 would solve the problem.
batman_adv: batadv0: Not using interface batadv_slave_0 (retrying later): interface not active
batman_adv: batadv0: Adding interface: batadv_slave_1
batman_adv: batadv0: The MTU of interface batadv_slave_1 is too small (1500) to handle the transport of batman-adv packets. Packets going over this interface will be fragmented on layer2 which could impact the performance. Setting the MTU to 1532 would solve the problem.
batman_adv: batadv0: Not using interface batadv_slave_1 (retrying later): interface not active
hsr_slave_0: entered promiscuous mode
hsr_slave_1: entered promiscuous mode
netdevsim netdevsim0 netdevsim0: renamed from eth0
netdevsim netdevsim0 netdevsim1: renamed from eth1
netdevsim netdevsim0 netdevsim2: renamed from eth2
netdevsim netdevsim0 netdevsim3: renamed from eth3
bridge0: port 2(bridge_slave_1) entered blocking state
bridge0: port 2(bridge_slave_1) entered forwarding state
bridge0: port 1(bridge_slave_0) entered blocking state
bridge0: port 1(bridge_slave_0) entered forwarding state
8021q: adding VLAN 0 to HW filter on device bond0
8021q: adding VLAN 0 to HW filter on device team0
8021q: adding VLAN 0 to HW filter on device batadv0
veth0_vlan: entered promiscuous mode
veth1_vlan: entered promiscuous mode
veth0_macvtap: entered promiscuous mode
veth1_macvtap: entered promiscuous mode
batman_adv: batadv0: Interface activated: batadv_slave_0
batman_adv: batadv0: Interface activated: batadv_slave_1
==================================================================
BUG: KASAN: null-ptr-deref in generic_hwtstamp_ioctl_lower+0x190/0x2d0 net/core/dev_ioctl.c:447
Read of size 24 at addr 0000000000000010 by task repro.out/9702

CPU: 1 UID: 0 PID: 9702 Comm: repro.out Not tainted 6.18.0-rc3 #1 PREEMPT(full) 
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014
Call Trace:
 <TASK>
 __dump_stack lib/dump_stack.c:94 [inline]
 dump_stack_lvl+0x1c1/0x2a0 lib/dump_stack.c:120
 print_report+0x101/0x810 mm/kasan/report.c:485
 kasan_report+0x147/0x180 mm/kasan/report.c:595
 kasan_check_range+0x2b0/0x2c0 mm/kasan/generic.c:200
 __asan_memcpy+0x29/0x70 mm/kasan/shadow.c:105
 generic_hwtstamp_ioctl_lower+0x190/0x2d0 net/core/dev_ioctl.c:447
 tsconfig_prepare_data+0x12c/0x600 net/ethtool/tsconfig.c:51
 ethnl_default_dump_one+0x2f3/0x7e0 net/ethtool/netlink.c:591
 ethnl_default_dumpit+0x30c/0x600 net/ethtool/netlink.c:628
 genl_dumpit+0x10b/0x1b0 net/netlink/genetlink.c:1027
 netlink_dump+0x6e4/0xe90 net/netlink/af_netlink.c:2327
 __netlink_dump_start+0x5cb/0x7e0 net/netlink/af_netlink.c:2442
 genl_family_rcv_msg_dumpit+0x1e7/0x2c0 net/netlink/genetlink.c:1076
 genl_family_rcv_msg net/netlink/genetlink.c:1192 [inline]
 genl_rcv_msg+0x5cd/0x7a0 net/netlink/genetlink.c:1210
 netlink_rcv_skb+0x208/0x470 net/netlink/af_netlink.c:2552
 genl_rcv+0x28/0x40 net/netlink/genetlink.c:1219
 netlink_unicast_kernel net/netlink/af_netlink.c:1320 [inline]
 netlink_unicast+0x82f/0x9e0 net/netlink/af_netlink.c:1346
 netlink_sendmsg+0x805/0xb30 net/netlink/af_netlink.c:1896
 sock_sendmsg_nosec net/socket.c:727 [inline]
 __sock_sendmsg+0x21c/0x270 net/socket.c:742
 ____sys_sendmsg+0x507/0x840 net/socket.c:2630
 ___sys_sendmsg+0x21f/0x2a0 net/socket.c:2684
 __sys_sendmsg net/socket.c:2716 [inline]
 __do_sys_sendmsg net/socket.c:2721 [inline]
 __se_sys_sendmsg net/socket.c:2719 [inline]
 __x64_sys_sendmsg+0x19b/0x260 net/socket.c:2719
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xf3/0xfa0 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x44dec9
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 c0 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007ffdcb83e678 EFLAGS: 00000206 ORIG_RAX: 000000000000002e
RAX: ffffffffffffffda RBX: 00000000004004b8 RCX: 000000000044dec9
RDX: 0000000000000000 RSI: 0000000020000540 RDI: 0000000000000003
RBP: 00007ffdcb83e690 R08: 000000000049443b R09: 000000000049443b
R10: 0000000000000000 R11: 0000000000000206 R12: 000000000040c030
R13: 0000000000000000 R14: 00000000004c1018 R15: 00000000004004b8
 </TASK>
==================================================================

[-- Attachment #6: kernel.log --]
[-- Type: application/octet-stream, Size: 201828 bytes --]

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [Linux Kernel Bug] KASAN: null-ptr-deref Read in generic_hwtstamp_ioctl_lower
  2025-10-29  8:45 [Linux Kernel Bug] KASAN: null-ptr-deref Read in generic_hwtstamp_ioctl_lower Jiaming Zhang
@ 2025-10-29 10:06 ` Kory Maincent
  2025-10-29 12:22   ` Jiaming Zhang
                     ` (2 more replies)
  0 siblings, 3 replies; 16+ messages in thread
From: Kory Maincent @ 2025-10-29 10:06 UTC (permalink / raw)
  To: Jiaming Zhang
  Cc: davem, edumazet, kuba, netdev, pabeni, horms, kuniyu,
	linux-kernel, sdf, syzkaller, Vladimir Oltean

Hello Jiaming,

+Vlad

On Wed, 29 Oct 2025 16:45:37 +0800
Jiaming Zhang <r772577952@gmail.com> wrote:

> Dear Linux kernel developers and maintainers,
> 
> We are writing to report a null pointer dereference bug discovered in
> the net subsystem. This bug is reproducible on the latest version
> (v6.18-rc3, commit dcb6fa37fd7bc9c3d2b066329b0d27dedf8becaa).
> 
> The root cause is in tsconfig_prepare_data(), where a local
> kernel_hwtstamp_config struct (cfg) is initialized using {}, setting
> all its members to zero. Consequently, cfg.ifr becomes NULL.
> 
> cfg is then passed as: tsconfig_prepare_data() ->
> dev_get_hwtstamp_phylib() -> vlan_hwtstamp_get() (via
> dev->netdev_ops->ndo_hwtstamp_get) -> generic_hwtstamp_get_lower() ->
> generic_hwtstamp_ioctl_lower().
> 
> The function generic_hwtstamp_ioctl_lower() assumes cfg->ifr is a
> valid pointer and attempts to access cfg->ifr->ifr_ifru. This access
> dereferences the NULL pointer, triggering the bug.

Thanks for spotting this issue!

In the ideal world we would have all Ethernet driver supporting the
hwtstamp_get/set NDOs but that not currently the case.	
Vladimir Oltean was working on this but it is not done yet. 
$ git grep SIOCGHWTSTAMP drivers/net/ethernet | wc -l
16
 
> As a potential fix, we can declare a local struct ifreq variable in
> tsconfig_prepare_data(), zero-initializing it, and then assigning its
> address to cfg.ifr before calling dev_get_hwtstamp_phylib(). This
> ensures that functions down the call chain receive a valid pointer.

If we do that we will have legacy IOCTL path inside the Netlink path and that's
not something we want.
In fact it is possible because the drivers calling
generic_hwtstamp_get/set_lower functions are already converted to hwtstamp NDOs
therefore the NDO check in tsconfig_prepare_data is not working on these case.

IMO the solution is to add a check on the ifr value in the
generic_hwtstamp_set/get_lower functions like that:

int generic_hwtstamp_set_lower(struct net_device *dev,
			       struct kernel_hwtstamp_config *kernel_cfg,
			       struct netlink_ext_ack *extack)
{
...

	/* Netlink path with unconverted lower driver */
	if (!kernel_cfg->ifr)
		return -EOPNOTSUPP;

	/* Legacy path: unconverted lower driver */
	return generic_hwtstamp_ioctl_lower(dev, SIOCSHWTSTAMP, kernel_cfg);
}

Regards,
-- 
Köry Maincent, Bootlin
Embedded Linux and kernel engineering
https://bootlin.com

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [Linux Kernel Bug] KASAN: null-ptr-deref Read in generic_hwtstamp_ioctl_lower
  2025-10-29 10:06 ` Kory Maincent
@ 2025-10-29 12:22   ` Jiaming Zhang
  2025-10-29 16:19   ` Vladimir Oltean
  2025-10-30  9:36   ` [PATCH] net: core: prevent NULL deref in generic_hwtstamp_ioctl_lower() Jiaming Zhang
  2 siblings, 0 replies; 16+ messages in thread
From: Jiaming Zhang @ 2025-10-29 12:22 UTC (permalink / raw)
  To: Kory Maincent
  Cc: davem, edumazet, kuba, netdev, pabeni, horms, kuniyu,
	linux-kernel, sdf, syzkaller, Vladimir Oltean

Hi Kory,

Thank you for the suggestions!

I will prepare a patch and submit it shortly :)

Best regards,
Jiaming Zhang

Kory Maincent <kory.maincent@bootlin.com> 于2025年10月29日周三 18:06写道:
>
> Hello Jiaming,
>
> +Vlad
>
> On Wed, 29 Oct 2025 16:45:37 +0800
> Jiaming Zhang <r772577952@gmail.com> wrote:
>
> > Dear Linux kernel developers and maintainers,
> >
> > We are writing to report a null pointer dereference bug discovered in
> > the net subsystem. This bug is reproducible on the latest version
> > (v6.18-rc3, commit dcb6fa37fd7bc9c3d2b066329b0d27dedf8becaa).
> >
> > The root cause is in tsconfig_prepare_data(), where a local
> > kernel_hwtstamp_config struct (cfg) is initialized using {}, setting
> > all its members to zero. Consequently, cfg.ifr becomes NULL.
> >
> > cfg is then passed as: tsconfig_prepare_data() ->
> > dev_get_hwtstamp_phylib() -> vlan_hwtstamp_get() (via
> > dev->netdev_ops->ndo_hwtstamp_get) -> generic_hwtstamp_get_lower() ->
> > generic_hwtstamp_ioctl_lower().
> >
> > The function generic_hwtstamp_ioctl_lower() assumes cfg->ifr is a
> > valid pointer and attempts to access cfg->ifr->ifr_ifru. This access
> > dereferences the NULL pointer, triggering the bug.
>
> Thanks for spotting this issue!
>
> In the ideal world we would have all Ethernet driver supporting the
> hwtstamp_get/set NDOs but that not currently the case.
> Vladimir Oltean was working on this but it is not done yet.
> $ git grep SIOCGHWTSTAMP drivers/net/ethernet | wc -l
> 16
>
> > As a potential fix, we can declare a local struct ifreq variable in
> > tsconfig_prepare_data(), zero-initializing it, and then assigning its
> > address to cfg.ifr before calling dev_get_hwtstamp_phylib(). This
> > ensures that functions down the call chain receive a valid pointer.
>
> If we do that we will have legacy IOCTL path inside the Netlink path and that's
> not something we want.
> In fact it is possible because the drivers calling
> generic_hwtstamp_get/set_lower functions are already converted to hwtstamp NDOs
> therefore the NDO check in tsconfig_prepare_data is not working on these case.
>
> IMO the solution is to add a check on the ifr value in the
> generic_hwtstamp_set/get_lower functions like that:
>
> int generic_hwtstamp_set_lower(struct net_device *dev,
>                                struct kernel_hwtstamp_config *kernel_cfg,
>                                struct netlink_ext_ack *extack)
> {
> ...
>
>         /* Netlink path with unconverted lower driver */
>         if (!kernel_cfg->ifr)
>                 return -EOPNOTSUPP;
>
>         /* Legacy path: unconverted lower driver */
>         return generic_hwtstamp_ioctl_lower(dev, SIOCSHWTSTAMP, kernel_cfg);
> }
>
> Regards,
> --
> Köry Maincent, Bootlin
> Embedded Linux and kernel engineering
> https://bootlin.com

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [Linux Kernel Bug] KASAN: null-ptr-deref Read in generic_hwtstamp_ioctl_lower
  2025-10-29 10:06 ` Kory Maincent
  2025-10-29 12:22   ` Jiaming Zhang
@ 2025-10-29 16:19   ` Vladimir Oltean
  2025-10-29 16:47     ` Kory Maincent
  2025-10-30  9:36   ` [PATCH] net: core: prevent NULL deref in generic_hwtstamp_ioctl_lower() Jiaming Zhang
  2 siblings, 1 reply; 16+ messages in thread
From: Vladimir Oltean @ 2025-10-29 16:19 UTC (permalink / raw)
  To: Kory Maincent
  Cc: Jiaming Zhang, davem, edumazet, kuba, netdev, pabeni, horms,
	kuniyu, linux-kernel, sdf, syzkaller, Vadim Fedorenko

On Wed, Oct 29, 2025 at 11:06:51AM +0100, Kory Maincent wrote:
> Hello Jiaming,
> 
> +Vlad
> 
> On Wed, 29 Oct 2025 16:45:37 +0800
> Jiaming Zhang <r772577952@gmail.com> wrote:
> 
> > Dear Linux kernel developers and maintainers,
> > 
> > We are writing to report a null pointer dereference bug discovered in
> > the net subsystem. This bug is reproducible on the latest version
> > (v6.18-rc3, commit dcb6fa37fd7bc9c3d2b066329b0d27dedf8becaa).
> > 
> > The root cause is in tsconfig_prepare_data(), where a local
> > kernel_hwtstamp_config struct (cfg) is initialized using {}, setting
> > all its members to zero. Consequently, cfg.ifr becomes NULL.
> > 
> > cfg is then passed as: tsconfig_prepare_data() ->
> > dev_get_hwtstamp_phylib() -> vlan_hwtstamp_get() (via
> > dev->netdev_ops->ndo_hwtstamp_get) -> generic_hwtstamp_get_lower() ->
> > generic_hwtstamp_ioctl_lower().
> > 
> > The function generic_hwtstamp_ioctl_lower() assumes cfg->ifr is a
> > valid pointer and attempts to access cfg->ifr->ifr_ifru. This access
> > dereferences the NULL pointer, triggering the bug.
> 
> Thanks for spotting this issue!
> 
> In the ideal world we would have all Ethernet driver supporting the
> hwtstamp_get/set NDOs but that not currently the case.	
> Vladimir Oltean was working on this but it is not done yet. 
> $ git grep SIOCGHWTSTAMP drivers/net/ethernet | wc -l
> 16

Vadim also took the initiative and submitted (is still submitting?) some
more conversions, whereas I lost all steam.

> > As a potential fix, we can declare a local struct ifreq variable in
> > tsconfig_prepare_data(), zero-initializing it, and then assigning its
> > address to cfg.ifr before calling dev_get_hwtstamp_phylib(). This
> > ensures that functions down the call chain receive a valid pointer.
> 
> If we do that we will have legacy IOCTL path inside the Netlink path and that's
> not something we want.
> In fact it is possible because the drivers calling
> generic_hwtstamp_get/set_lower functions are already converted to hwtstamp NDOs
> therefore the NDO check in tsconfig_prepare_data is not working on these case.

I remember we had this discussion before.

| This is why I mentioned by ndo_hwtstamp_set() conversion, because
| suddenly it is a prerequisite for any further progress to be done.
| You can't convert SIOCSHWTSTAMP to netlink if there are some driver
| implementations which still use ndo_eth_ioctl(). They need to be
| UAPI-agnostic.

https://lore.kernel.org/netdev/20231122140850.li2mvf6tpo3f2fhh@skbuf/

I'm not sure what was your agreement with the netdev maintainer
accepting the tsconfig netlink work with unconverted device drivers left
in the tree.

> IMO the solution is to add a check on the ifr value in the
> generic_hwtstamp_set/get_lower functions like that:
> 
> int generic_hwtstamp_set_lower(struct net_device *dev,
> 			       struct kernel_hwtstamp_config *kernel_cfg,
> 			       struct netlink_ext_ack *extack)
> {
> ...
> 
> 	/* Netlink path with unconverted lower driver */
> 	if (!kernel_cfg->ifr)
> 		return -EOPNOTSUPP;
> 
> 	/* Legacy path: unconverted lower driver */
> 	return generic_hwtstamp_ioctl_lower(dev, SIOCSHWTSTAMP, kernel_cfg);
> }

This plugs one hole (two including _get). How many more are there? If
this is an oversight, the entire tree needs to be reviewed for
ndo_hwtstamp_get() / ndo_hwtstamp_test() pointer tests which were used
as an indication that this net device is netlink ready. Stacked
virtual interfaces are netlink-ready only when the entire chain down to
the physical interface is netlink-ready.

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [Linux Kernel Bug] KASAN: null-ptr-deref Read in generic_hwtstamp_ioctl_lower
  2025-10-29 16:19   ` Vladimir Oltean
@ 2025-10-29 16:47     ` Kory Maincent
  2025-10-29 19:28       ` Vadim Fedorenko
  0 siblings, 1 reply; 16+ messages in thread
From: Kory Maincent @ 2025-10-29 16:47 UTC (permalink / raw)
  To: Vladimir Oltean
  Cc: Jiaming Zhang, davem, edumazet, kuba, netdev, pabeni, horms,
	kuniyu, linux-kernel, sdf, syzkaller, Vadim Fedorenko

On Wed, 29 Oct 2025 18:19:34 +0200
Vladimir Oltean <vladimir.oltean@nxp.com> wrote:

> On Wed, Oct 29, 2025 at 11:06:51AM +0100, Kory Maincent wrote:
> > Hello Jiaming,
> > 
> > +Vlad
> > 
> > On Wed, 29 Oct 2025 16:45:37 +0800
> > Jiaming Zhang <r772577952@gmail.com> wrote:
> >   
> > > Dear Linux kernel developers and maintainers,
> > > 
> > > We are writing to report a null pointer dereference bug discovered in
> > > the net subsystem. This bug is reproducible on the latest version
> > > (v6.18-rc3, commit dcb6fa37fd7bc9c3d2b066329b0d27dedf8becaa).
> > > 
> > > The root cause is in tsconfig_prepare_data(), where a local
> > > kernel_hwtstamp_config struct (cfg) is initialized using {}, setting
> > > all its members to zero. Consequently, cfg.ifr becomes NULL.
> > > 
> > > cfg is then passed as: tsconfig_prepare_data() ->
> > > dev_get_hwtstamp_phylib() -> vlan_hwtstamp_get() (via
> > > dev->netdev_ops->ndo_hwtstamp_get) -> generic_hwtstamp_get_lower() ->
> > > generic_hwtstamp_ioctl_lower().
> > > 
> > > The function generic_hwtstamp_ioctl_lower() assumes cfg->ifr is a
> > > valid pointer and attempts to access cfg->ifr->ifr_ifru. This access
> > > dereferences the NULL pointer, triggering the bug.  
> > 
> > Thanks for spotting this issue!
> > 
> > In the ideal world we would have all Ethernet driver supporting the
> > hwtstamp_get/set NDOs but that not currently the case.	
> > Vladimir Oltean was working on this but it is not done yet. 
> > $ git grep SIOCGHWTSTAMP drivers/net/ethernet | wc -l
> > 16  
> 
> Vadim also took the initiative and submitted (is still submitting?) some
> more conversions, whereas I lost all steam.

Ok no worry I was simply pointing this out, people will convert it when they
want to use the new netlink API.
 
> > > As a potential fix, we can declare a local struct ifreq variable in
> > > tsconfig_prepare_data(), zero-initializing it, and then assigning its
> > > address to cfg.ifr before calling dev_get_hwtstamp_phylib(). This
> > > ensures that functions down the call chain receive a valid pointer.  
> > 
> > If we do that we will have legacy IOCTL path inside the Netlink path and
> > that's not something we want.
> > In fact it is possible because the drivers calling
> > generic_hwtstamp_get/set_lower functions are already converted to hwtstamp
> > NDOs therefore the NDO check in tsconfig_prepare_data is not working on
> > these case.  
> 
> I remember we had this discussion before.
> 
> | This is why I mentioned by ndo_hwtstamp_set() conversion, because
> | suddenly it is a prerequisite for any further progress to be done.
> | You can't convert SIOCSHWTSTAMP to netlink if there are some driver
> | implementations which still use ndo_eth_ioctl(). They need to be
> | UAPI-agnostic.
> 
> https://lore.kernel.org/netdev/20231122140850.li2mvf6tpo3f2fhh@skbuf/
> 
> I'm not sure what was your agreement with the netdev maintainer
> accepting the tsconfig netlink work with unconverted device drivers left
> in the tree.

I did like 21th versions and there was not many people active in the reviews.
No one stand against this work.

> > IMO the solution is to add a check on the ifr value in the
> > generic_hwtstamp_set/get_lower functions like that:
> > 
> > int generic_hwtstamp_set_lower(struct net_device *dev,
> > 			       struct kernel_hwtstamp_config *kernel_cfg,
> > 			       struct netlink_ext_ack *extack)
> > {
> > ...
> > 
> > 	/* Netlink path with unconverted lower driver */
> > 	if (!kernel_cfg->ifr)
> > 		return -EOPNOTSUPP;
> > 
> > 	/* Legacy path: unconverted lower driver */
> > 	return generic_hwtstamp_ioctl_lower(dev, SIOCSHWTSTAMP, kernel_cfg);
> > }  
> 
> This plugs one hole (two including _get). How many more are there? If
> this is an oversight, the entire tree needs to be reviewed for
> ndo_hwtstamp_get() / ndo_hwtstamp_test() pointer tests which were used
> as an indication that this net device is netlink ready. Stacked
> virtual interfaces are netlink-ready only when the entire chain down to
> the physical interface is netlink-ready.

I don't see this as a hole. The legacy ioctl path still works.
If people want to use the new Netlink path on their board, yes they need to
convert all the parts of the chain to hwtstamp NDOs. If they don't they will
get now a EOPNOTSUPP error instead of a null pointer dereference koops.

Regards,
-- 
Köry Maincent, Bootlin
Embedded Linux and kernel engineering
https://bootlin.com

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [Linux Kernel Bug] KASAN: null-ptr-deref Read in generic_hwtstamp_ioctl_lower
  2025-10-29 16:47     ` Kory Maincent
@ 2025-10-29 19:28       ` Vadim Fedorenko
  0 siblings, 0 replies; 16+ messages in thread
From: Vadim Fedorenko @ 2025-10-29 19:28 UTC (permalink / raw)
  To: Kory Maincent, Vladimir Oltean
  Cc: Jiaming Zhang, davem, edumazet, kuba, netdev, pabeni, horms,
	kuniyu, linux-kernel, sdf, syzkaller

On 29/10/2025 16:47, Kory Maincent wrote:
> On Wed, 29 Oct 2025 18:19:34 +0200
> Vladimir Oltean <vladimir.oltean@nxp.com> wrote:
> 
>> On Wed, Oct 29, 2025 at 11:06:51AM +0100, Kory Maincent wrote:
>>> Hello Jiaming,
>>>
>>> +Vlad
>>>
>>> On Wed, 29 Oct 2025 16:45:37 +0800
>>> Jiaming Zhang <r772577952@gmail.com> wrote:
>>>    
>>>> Dear Linux kernel developers and maintainers,
>>>>
>>>> We are writing to report a null pointer dereference bug discovered in
>>>> the net subsystem. This bug is reproducible on the latest version
>>>> (v6.18-rc3, commit dcb6fa37fd7bc9c3d2b066329b0d27dedf8becaa).
>>>>
>>>> The root cause is in tsconfig_prepare_data(), where a local
>>>> kernel_hwtstamp_config struct (cfg) is initialized using {}, setting
>>>> all its members to zero. Consequently, cfg.ifr becomes NULL.
>>>>
>>>> cfg is then passed as: tsconfig_prepare_data() ->
>>>> dev_get_hwtstamp_phylib() -> vlan_hwtstamp_get() (via
>>>> dev->netdev_ops->ndo_hwtstamp_get) -> generic_hwtstamp_get_lower() ->
>>>> generic_hwtstamp_ioctl_lower().
>>>>
>>>> The function generic_hwtstamp_ioctl_lower() assumes cfg->ifr is a
>>>> valid pointer and attempts to access cfg->ifr->ifr_ifru. This access
>>>> dereferences the NULL pointer, triggering the bug.
>>>
>>> Thanks for spotting this issue!
>>>
>>> In the ideal world we would have all Ethernet driver supporting the
>>> hwtstamp_get/set NDOs but that not currently the case.	
>>> Vladimir Oltean was working on this but it is not done yet.
>>> $ git grep SIOCGHWTSTAMP drivers/net/ethernet | wc -l
>>> 16
>>
>> Vadim also took the initiative and submitted (is still submitting?) some
>> more conversions, whereas I lost all steam.
> 
> Ok no worry I was simply pointing this out, people will convert it when they
> want to use the new netlink API.
>   
>>>> As a potential fix, we can declare a local struct ifreq variable in
>>>> tsconfig_prepare_data(), zero-initializing it, and then assigning its
>>>> address to cfg.ifr before calling dev_get_hwtstamp_phylib(). This
>>>> ensures that functions down the call chain receive a valid pointer.
>>>
>>> If we do that we will have legacy IOCTL path inside the Netlink path and
>>> that's not something we want.
>>> In fact it is possible because the drivers calling
>>> generic_hwtstamp_get/set_lower functions are already converted to hwtstamp
>>> NDOs therefore the NDO check in tsconfig_prepare_data is not working on
>>> these case.
>>
>> I remember we had this discussion before.
>>
>> | This is why I mentioned by ndo_hwtstamp_set() conversion, because
>> | suddenly it is a prerequisite for any further progress to be done.
>> | You can't convert SIOCSHWTSTAMP to netlink if there are some driver
>> | implementations which still use ndo_eth_ioctl(). They need to be
>> | UAPI-agnostic.
>>
>> https://lore.kernel.org/netdev/20231122140850.li2mvf6tpo3f2fhh@skbuf/
>>
>> I'm not sure what was your agreement with the netdev maintainer
>> accepting the tsconfig netlink work with unconverted device drivers left
>> in the tree.
> 
> I did like 21th versions and there was not many people active in the reviews.
> No one stand against this work.
> 
>>> IMO the solution is to add a check on the ifr value in the
>>> generic_hwtstamp_set/get_lower functions like that:
>>>
>>> int generic_hwtstamp_set_lower(struct net_device *dev,
>>> 			       struct kernel_hwtstamp_config *kernel_cfg,
>>> 			       struct netlink_ext_ack *extack)
>>> {
>>> ...
>>>
>>> 	/* Netlink path with unconverted lower driver */
>>> 	if (!kernel_cfg->ifr)
>>> 		return -EOPNOTSUPP;
>>>
>>> 	/* Legacy path: unconverted lower driver */
>>> 	return generic_hwtstamp_ioctl_lower(dev, SIOCSHWTSTAMP, kernel_cfg);
>>> }
>>
>> This plugs one hole (two including _get). How many more are there? If
>> this is an oversight, the entire tree needs to be reviewed for
>> ndo_hwtstamp_get() / ndo_hwtstamp_test() pointer tests which were used
>> as an indication that this net device is netlink ready. Stacked
>> virtual interfaces are netlink-ready only when the entire chain down to
>> the physical interface is netlink-ready.
> 
> I don't see this as a hole. The legacy ioctl path still works.
> If people want to use the new Netlink path on their board, yes they need to
> convert all the parts of the chain to hwtstamp NDOs. If they don't they will
> get now a EOPNOTSUPP error instead of a null pointer dereference koops.

I agree with Kory - we don't have many spots in the code calling HW
timestamping configuration. The ones to check is actually phy code and
can drivers. But anyways, we have this interface exposed to UAPI, and we
have ethtool with supports it already. And there is a bug, which can be
fixed with the proposed code.

I'm working right now to finish conversion by the end of this term, both
can and phy will be switched to new API as well as mlx5/ti_netcp
ethernet drivers.

^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH] net: core: prevent NULL deref in generic_hwtstamp_ioctl_lower()
  2025-10-29 10:06 ` Kory Maincent
  2025-10-29 12:22   ` Jiaming Zhang
  2025-10-29 16:19   ` Vladimir Oltean
@ 2025-10-30  9:36   ` Jiaming Zhang
  2025-10-30 10:14     ` Kory Maincent
  2 siblings, 1 reply; 16+ messages in thread
From: Jiaming Zhang @ 2025-10-30  9:36 UTC (permalink / raw)
  To: kory.maincent
  Cc: davem, edumazet, horms, kuba, kuniyu, linux-kernel, netdev,
	pabeni, r772577952, sdf, syzkaller, vladimir.oltean, stable

The ethtool tsconfig Netlink path can trigger a null pointer
dereference. A call chain such as:

  tsconfig_prepare_data() ->
  dev_get_hwtstamp_phylib() ->
  vlan_hwtstamp_get() ->
  generic_hwtstamp_get_lower() ->
  generic_hwtstamp_ioctl_lower()

results in generic_hwtstamp_ioctl_lower() being called with
kernel_cfg->ifr as NULL.

The generic_hwtstamp_ioctl_lower() function does not expect a
NULL ifr and dereferences it, leading to a system crash.

Fix this by adding a NULL check for kernel_cfg->ifr in
generic_hwtstamp_get/set_lower(). If ifr is NULL, return
-EOPNOTSUPP to prevent the call to the legacy IOCTL helper.

Fixes: 6e9e2eed4f39 ("net: ethtool: Add support for tsconfig command to get/set hwtstamp config")
Closes: https://lore.kernel.org/lkml/cd6a7056-fa6d-43f8-b78a-f5e811247ba8@linux.dev/T/#mf5df538e21753e3045de98f25aa18d948be07df3
Signed-off-by: Jiaming Zhang <r772577952@gmail.com>
---
 net/core/dev_ioctl.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/net/core/dev_ioctl.c b/net/core/dev_ioctl.c
index ad54b12d4b4c..39eaf6ba981a 100644
--- a/net/core/dev_ioctl.c
+++ b/net/core/dev_ioctl.c
@@ -474,6 +474,10 @@ int generic_hwtstamp_get_lower(struct net_device *dev,
 		return err;
 	}
 
+	/* Netlink path with unconverted driver */
+	if (!kernel_cfg->ifr)
+		return -EOPNOTSUPP;
+
 	/* Legacy path: unconverted lower driver */
 	return generic_hwtstamp_ioctl_lower(dev, SIOCGHWTSTAMP, kernel_cfg);
 }
@@ -498,6 +502,10 @@ int generic_hwtstamp_set_lower(struct net_device *dev,
 		return err;
 	}
 
+	/* Netlink path with unconverted driver */
+	if (!kernel_cfg->ifr)
+		return -EOPNOTSUPP;
+
 	/* Legacy path: unconverted lower driver */
 	return generic_hwtstamp_ioctl_lower(dev, SIOCSHWTSTAMP, kernel_cfg);
 }
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* Re: [PATCH] net: core: prevent NULL deref in generic_hwtstamp_ioctl_lower()
  2025-10-30  9:36   ` [PATCH] net: core: prevent NULL deref in generic_hwtstamp_ioctl_lower() Jiaming Zhang
@ 2025-10-30 10:14     ` Kory Maincent
  2025-10-30 10:49       ` [PATCH v2] " Jiaming Zhang
  2025-10-30 12:49       ` [PATCH v3 0/1] " Jiaming Zhang
  0 siblings, 2 replies; 16+ messages in thread
From: Kory Maincent @ 2025-10-30 10:14 UTC (permalink / raw)
  To: Jiaming Zhang
  Cc: davem, edumazet, horms, kuba, kuniyu, linux-kernel, netdev,
	pabeni, sdf, syzkaller, vladimir.oltean, stable

On Thu, 30 Oct 2025 09:36:21 +0000
Jiaming Zhang <r772577952@gmail.com> wrote:

> The ethtool tsconfig Netlink path can trigger a null pointer
> dereference. A call chain such as:
> 
>   tsconfig_prepare_data() ->
>   dev_get_hwtstamp_phylib() ->
>   vlan_hwtstamp_get() ->
>   generic_hwtstamp_get_lower() ->
>   generic_hwtstamp_ioctl_lower()
> 
> results in generic_hwtstamp_ioctl_lower() being called with
> kernel_cfg->ifr as NULL.
> 
> The generic_hwtstamp_ioctl_lower() function does not expect a
> NULL ifr and dereferences it, leading to a system crash.
> 
> Fix this by adding a NULL check for kernel_cfg->ifr in
> generic_hwtstamp_get/set_lower(). If ifr is NULL, return
> -EOPNOTSUPP to prevent the call to the legacy IOCTL helper.
> 
> Fixes: 6e9e2eed4f39 ("net: ethtool: Add support for tsconfig command to
> get/set hwtstamp config") Closes:
> https://lore.kernel.org/lkml/cd6a7056-fa6d-43f8-b78a-f5e811247ba8@linux.dev/T/#mf5df538e21753e3045de98f25aa18d948be07df3
> Signed-off-by: Jiaming Zhang <r772577952@gmail.com> ---
>  net/core/dev_ioctl.c | 8 ++++++++
>  1 file changed, 8 insertions(+)
> 
> diff --git a/net/core/dev_ioctl.c b/net/core/dev_ioctl.c
> index ad54b12d4b4c..39eaf6ba981a 100644
> --- a/net/core/dev_ioctl.c
> +++ b/net/core/dev_ioctl.c
> @@ -474,6 +474,10 @@ int generic_hwtstamp_get_lower(struct net_device *dev,
>  		return err;
>  	}
>  
> +	/* Netlink path with unconverted driver */

nit: "lower driver", to be precise.

With this change:
Reviewed-by: Kory Maincent <kory.maincent@bootlin.com>

Thank you!

> +	if (!kernel_cfg->ifr)
> +		return -EOPNOTSUPP;
> +
>  	/* Legacy path: unconverted lower driver */
>  	return generic_hwtstamp_ioctl_lower(dev, SIOCGHWTSTAMP, kernel_cfg);
>  }
> @@ -498,6 +502,10 @@ int generic_hwtstamp_set_lower(struct net_device *dev,
>  		return err;
>  	}
>  
> +	/* Netlink path with unconverted driver */

same.

> +	if (!kernel_cfg->ifr)
> +		return -EOPNOTSUPP;
> +
>  	/* Legacy path: unconverted lower driver */
>  	return generic_hwtstamp_ioctl_lower(dev, SIOCSHWTSTAMP, kernel_cfg);
>  }



-- 
Köry Maincent, Bootlin
Embedded Linux and kernel engineering
https://bootlin.com

^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH v2] net: core: prevent NULL deref in generic_hwtstamp_ioctl_lower()
  2025-10-30 10:14     ` Kory Maincent
@ 2025-10-30 10:49       ` Jiaming Zhang
  2025-10-30 12:49       ` [PATCH v3 0/1] " Jiaming Zhang
  1 sibling, 0 replies; 16+ messages in thread
From: Jiaming Zhang @ 2025-10-30 10:49 UTC (permalink / raw)
  To: kory.maincent
  Cc: davem, edumazet, horms, kuba, kuniyu, linux-kernel, netdev,
	pabeni, r772577952, sdf, stable, syzkaller, vladimir.oltean

The ethtool tsconfig Netlink path can trigger a null pointer
dereference. A call chain such as:

  tsconfig_prepare_data() ->
  dev_get_hwtstamp_phylib() ->
  vlan_hwtstamp_get() ->
  generic_hwtstamp_get_lower() ->
  generic_hwtstamp_ioctl_lower()

results in generic_hwtstamp_ioctl_lower() being called with
kernel_cfg->ifr as NULL.

The generic_hwtstamp_ioctl_lower() function does not expect a
NULL ifr and dereferences it, leading to a system crash.

Fix this by adding a NULL check for kernel_cfg->ifr in
generic_hwtstamp_get/set_lower(). If ifr is NULL, return
-EOPNOTSUPP to prevent the call to the legacy IOCTL helper.

Fixes: 6e9e2eed4f39 ("net: ethtool: Add support for tsconfig command to get/set hwtstamp config")
Closes: https://lore.kernel.org/lkml/cd6a7056-fa6d-43f8-b78a-f5e811247ba8@linux.dev/T/#mf5df538e21753e3045de98f25aa18d948be07df3
Signed-off-by: Jiaming Zhang <r772577952@gmail.com>
---
 net/core/dev_ioctl.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/net/core/dev_ioctl.c b/net/core/dev_ioctl.c
index ad54b12d4b4c..a32e1036f12a 100644
--- a/net/core/dev_ioctl.c
+++ b/net/core/dev_ioctl.c
@@ -474,6 +474,10 @@ int generic_hwtstamp_get_lower(struct net_device *dev,
 		return err;
 	}
 
+	/* Netlink path with unconverted lower driver */
+	if (!kernel_cfg->ifr)
+		return -EOPNOTSUPP;
+
 	/* Legacy path: unconverted lower driver */
 	return generic_hwtstamp_ioctl_lower(dev, SIOCGHWTSTAMP, kernel_cfg);
 }
@@ -498,6 +502,10 @@ int generic_hwtstamp_set_lower(struct net_device *dev,
 		return err;
 	}
 
+	/* Netlink path with unconverted lower driver */
+	if (!kernel_cfg->ifr)
+		return -EOPNOTSUPP;
+
 	/* Legacy path: unconverted lower driver */
 	return generic_hwtstamp_ioctl_lower(dev, SIOCSHWTSTAMP, kernel_cfg);
 }
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH v3 0/1] net: core: prevent NULL deref in generic_hwtstamp_ioctl_lower()
  2025-10-30 10:14     ` Kory Maincent
  2025-10-30 10:49       ` [PATCH v2] " Jiaming Zhang
@ 2025-10-30 12:49       ` Jiaming Zhang
  2025-10-30 12:49         ` [PATCH v3 1/1] " Jiaming Zhang
  1 sibling, 1 reply; 16+ messages in thread
From: Jiaming Zhang @ 2025-10-30 12:49 UTC (permalink / raw)
  To: kory.maincent
  Cc: davem, edumazet, horms, kuba, kuniyu, linux-kernel, netdev,
	pabeni, r772577952, sdf, syzkaller, vladimir.oltean

Hi Kory,

Apologies for the resubmission (v3); I forgot to add Reviewed-by tag in
the v2 patch.

v3:
- Add Kory's Reviewed-by tag.

v2:
- Fix typo in comment ("driver" -> "lower driver")

Best Regards,
Jiaming Zhang

Jiaming Zhang (1):
  net: core: prevent NULL deref in generic_hwtstamp_ioctl_lower()

 net/core/dev_ioctl.c | 8 ++++++++
 1 file changed, 8 insertions(+)

-- 
2.34.1


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH v3 1/1] net: core: prevent NULL deref in generic_hwtstamp_ioctl_lower()
  2025-10-30 12:49       ` [PATCH v3 0/1] " Jiaming Zhang
@ 2025-10-30 12:49         ` Jiaming Zhang
  2025-11-04  1:15           ` Jakub Kicinski
  0 siblings, 1 reply; 16+ messages in thread
From: Jiaming Zhang @ 2025-10-30 12:49 UTC (permalink / raw)
  To: kory.maincent
  Cc: davem, edumazet, horms, kuba, kuniyu, linux-kernel, netdev,
	pabeni, r772577952, sdf, syzkaller, vladimir.oltean

The ethtool tsconfig Netlink path can trigger a null pointer
dereference. A call chain such as:

  tsconfig_prepare_data() ->
  dev_get_hwtstamp_phylib() ->
  vlan_hwtstamp_get() ->
  generic_hwtstamp_get_lower() ->
  generic_hwtstamp_ioctl_lower()

results in generic_hwtstamp_ioctl_lower() being called with
kernel_cfg->ifr as NULL.

The generic_hwtstamp_ioctl_lower() function does not expect a
NULL ifr and dereferences it, leading to a system crash.

Fix this by adding a NULL check for kernel_cfg->ifr in
generic_hwtstamp_get/set_lower(). If ifr is NULL, return
-EOPNOTSUPP to prevent the call to the legacy IOCTL helper.

Fixes: 6e9e2eed4f39 ("net: ethtool: Add support for tsconfig command to get/set hwtstamp config")
Closes: https://lore.kernel.org/lkml/cd6a7056-fa6d-43f8-b78a-f5e811247ba8@linux.dev/T/#mf5df538e21753e3045de98f25aa18d948be07df3
Signed-off-by: Jiaming Zhang <r772577952@gmail.com>
Reviewed-by: Kory Maincent <kory.maincent@bootlin.com>
---
 net/core/dev_ioctl.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/net/core/dev_ioctl.c b/net/core/dev_ioctl.c
index ad54b12d4b4c..a32e1036f12a 100644
--- a/net/core/dev_ioctl.c
+++ b/net/core/dev_ioctl.c
@@ -474,6 +474,10 @@ int generic_hwtstamp_get_lower(struct net_device *dev,
 		return err;
 	}
 
+	/* Netlink path with unconverted lower driver */
+	if (!kernel_cfg->ifr)
+		return -EOPNOTSUPP;
+
 	/* Legacy path: unconverted lower driver */
 	return generic_hwtstamp_ioctl_lower(dev, SIOCGHWTSTAMP, kernel_cfg);
 }
@@ -498,6 +502,10 @@ int generic_hwtstamp_set_lower(struct net_device *dev,
 		return err;
 	}
 
+	/* Netlink path with unconverted lower driver */
+	if (!kernel_cfg->ifr)
+		return -EOPNOTSUPP;
+
 	/* Legacy path: unconverted lower driver */
 	return generic_hwtstamp_ioctl_lower(dev, SIOCSHWTSTAMP, kernel_cfg);
 }
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* Re: [PATCH v3 1/1] net: core: prevent NULL deref in generic_hwtstamp_ioctl_lower()
  2025-10-30 12:49         ` [PATCH v3 1/1] " Jiaming Zhang
@ 2025-11-04  1:15           ` Jakub Kicinski
  2025-11-11 17:36             ` [PATCH v4 0/1] " Jiaming Zhang
  0 siblings, 1 reply; 16+ messages in thread
From: Jakub Kicinski @ 2025-11-04  1:15 UTC (permalink / raw)
  To: Jiaming Zhang
  Cc: kory.maincent, davem, edumazet, horms, kuniyu, linux-kernel,
	netdev, pabeni, sdf, syzkaller, vladimir.oltean

On Thu, 30 Oct 2025 12:49:47 +0000 Jiaming Zhang wrote:
> +	/* Netlink path with unconverted lower driver */
> +	if (!kernel_cfg->ifr)
> +		return -EOPNOTSUPP;
> +
>  	/* Legacy path: unconverted lower driver */
>  	return generic_hwtstamp_ioctl_lower(dev, SIOCGHWTSTAMP, kernel_cfg);
>  }
> @@ -498,6 +502,10 @@ int generic_hwtstamp_set_lower(struct net_device *dev,
>  		return err;
>  	}
>  
> +	/* Netlink path with unconverted lower driver */
> +	if (!kernel_cfg->ifr)
> +		return -EOPNOTSUPP;
> +
>  	/* Legacy path: unconverted lower driver */
>  	return generic_hwtstamp_ioctl_lower(dev, SIOCSHWTSTAMP, kernel_cfg);

Sorry but nit:

instead of adding this to both callers you can add the check in
generic_hwtstamp_ioctl_lower().
-- 
pw-bot: cr

^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH v4 0/1] net: core: prevent NULL deref in generic_hwtstamp_ioctl_lower()
  2025-11-04  1:15           ` Jakub Kicinski
@ 2025-11-11 17:36             ` Jiaming Zhang
  2025-11-11 17:36               ` [PATCH v4 1/1] " Jiaming Zhang
  2025-11-14  1:40               ` [PATCH v4 0/1] " patchwork-bot+netdevbpf
  0 siblings, 2 replies; 16+ messages in thread
From: Jiaming Zhang @ 2025-11-11 17:36 UTC (permalink / raw)
  To: kuba
  Cc: davem, edumazet, horms, kory.maincent, kuniyu, linux-kernel,
	netdev, pabeni, r772577952, sdf, syzkaller, vladimir.oltean

Hi Jakub,

Sorry for the late response. I have updated the patch, NULL check
is moved from get/set caller to generic_hwstamp_ioctl_lower().

Please let me know if any change is needed :)

v4:
- Move NULL check from generic_hwtstamp_get/set_lower()
  to generic_hwtstamp_ioctl_lower()

v3:
- Add Kory's Reviewed-by tag.

v2:
- Fix typo in comment ("driver" -> "lower driver")

Best Regards,
Jiaming Zhang

Jiaming Zhang (1):
  net: core: prevent NULL deref in generic_hwtstamp_ioctl_lower()

 net/core/dev_ioctl.c | 3 +++
 1 file changed, 3 insertions(+)

-- 
2.34.1


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH v4 1/1] net: core: prevent NULL deref in generic_hwtstamp_ioctl_lower()
  2025-11-11 17:36             ` [PATCH v4 0/1] " Jiaming Zhang
@ 2025-11-11 17:36               ` Jiaming Zhang
  2025-11-12  8:45                 ` Kory Maincent
  2025-11-14  1:40               ` [PATCH v4 0/1] " patchwork-bot+netdevbpf
  1 sibling, 1 reply; 16+ messages in thread
From: Jiaming Zhang @ 2025-11-11 17:36 UTC (permalink / raw)
  To: kuba
  Cc: davem, edumazet, horms, kory.maincent, kuniyu, linux-kernel,
	netdev, pabeni, r772577952, sdf, syzkaller, vladimir.oltean

The ethtool tsconfig Netlink path can trigger a null pointer
dereference. A call chain such as:

  tsconfig_prepare_data() ->
  dev_get_hwtstamp_phylib() ->
  vlan_hwtstamp_get() ->
  generic_hwtstamp_get_lower() ->
  generic_hwtstamp_ioctl_lower()

results in generic_hwtstamp_ioctl_lower() being called with
kernel_cfg->ifr as NULL.

The generic_hwtstamp_ioctl_lower() function does not expect a
NULL ifr and dereferences it, leading to a system crash.

Fix this by adding a NULL check for kernel_cfg->ifr in
generic_hwtstamp_ioctl_lower(). If ifr is NULL, return -EINVAL.

Fixes: 6e9e2eed4f39 ("net: ethtool: Add support for tsconfig command to get/set hwtstamp config")
Closes: https://lore.kernel.org/lkml/cd6a7056-fa6d-43f8-b78a-f5e811247ba8@linux.dev/T/#mf5df538e21753e3045de98f25aa18d948be07df3

Signed-off-by: Jiaming Zhang <r772577952@gmail.com>
---
 net/core/dev_ioctl.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/net/core/dev_ioctl.c b/net/core/dev_ioctl.c
index ad54b12d4b4c..8bb71a10dba0 100644
--- a/net/core/dev_ioctl.c
+++ b/net/core/dev_ioctl.c
@@ -443,6 +443,9 @@ static int generic_hwtstamp_ioctl_lower(struct net_device *dev, int cmd,
 	struct ifreq ifrr;
 	int err;
 
+	if (!kernel_cfg->ifr)
+		return -EINVAL;
+
 	strscpy_pad(ifrr.ifr_name, dev->name, IFNAMSIZ);
 	ifrr.ifr_ifru = kernel_cfg->ifr->ifr_ifru;
 
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* Re: [PATCH v4 1/1] net: core: prevent NULL deref in generic_hwtstamp_ioctl_lower()
  2025-11-11 17:36               ` [PATCH v4 1/1] " Jiaming Zhang
@ 2025-11-12  8:45                 ` Kory Maincent
  0 siblings, 0 replies; 16+ messages in thread
From: Kory Maincent @ 2025-11-12  8:45 UTC (permalink / raw)
  To: Jiaming Zhang
  Cc: kuba, davem, edumazet, horms, kuniyu, linux-kernel, netdev,
	pabeni, sdf, syzkaller, vladimir.oltean

On Wed, 12 Nov 2025 01:36:52 +0800
Jiaming Zhang <r772577952@gmail.com> wrote:

> The ethtool tsconfig Netlink path can trigger a null pointer
> dereference. A call chain such as:
> 
>   tsconfig_prepare_data() ->
>   dev_get_hwtstamp_phylib() ->
>   vlan_hwtstamp_get() ->
>   generic_hwtstamp_get_lower() ->
>   generic_hwtstamp_ioctl_lower()
> 
> results in generic_hwtstamp_ioctl_lower() being called with
> kernel_cfg->ifr as NULL.
> 
> The generic_hwtstamp_ioctl_lower() function does not expect a
> NULL ifr and dereferences it, leading to a system crash.
> 
> Fix this by adding a NULL check for kernel_cfg->ifr in
> generic_hwtstamp_ioctl_lower(). If ifr is NULL, return -EINVAL.
> 
> Fixes: 6e9e2eed4f39 ("net: ethtool: Add support for tsconfig command to
> get/set hwtstamp config") Closes:
> https://lore.kernel.org/lkml/cd6a7056-fa6d-43f8-b78a-f5e811247ba8@linux.dev/T/#mf5df538e21753e3045de98f25aa18d948be07df3
> 
> Signed-off-by: Jiaming Zhang <r772577952@gmail.com>

Reviewed-by: Kory Maincent <kory.maincent@bootlin.com>

Thank you!
-- 
Köry Maincent, Bootlin
Embedded Linux and kernel engineering
https://bootlin.com

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v4 0/1] net: core: prevent NULL deref in generic_hwtstamp_ioctl_lower()
  2025-11-11 17:36             ` [PATCH v4 0/1] " Jiaming Zhang
  2025-11-11 17:36               ` [PATCH v4 1/1] " Jiaming Zhang
@ 2025-11-14  1:40               ` patchwork-bot+netdevbpf
  1 sibling, 0 replies; 16+ messages in thread
From: patchwork-bot+netdevbpf @ 2025-11-14  1:40 UTC (permalink / raw)
  To: Jiaming Zhang
  Cc: kuba, davem, edumazet, horms, kory.maincent, kuniyu, linux-kernel,
	netdev, pabeni, sdf, syzkaller, vladimir.oltean

Hello:

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

On Wed, 12 Nov 2025 01:36:51 +0800 you wrote:
> Hi Jakub,
> 
> Sorry for the late response. I have updated the patch, NULL check
> is moved from get/set caller to generic_hwstamp_ioctl_lower().
> 
> Please let me know if any change is needed :)
> 
> [...]

Here is the summary with links:
  - [v4,1/1] net: core: prevent NULL deref in generic_hwtstamp_ioctl_lower()
    https://git.kernel.org/netdev/net/c/f796a8dec9be

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply	[flat|nested] 16+ messages in thread

end of thread, other threads:[~2025-11-14  1:40 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-10-29  8:45 [Linux Kernel Bug] KASAN: null-ptr-deref Read in generic_hwtstamp_ioctl_lower Jiaming Zhang
2025-10-29 10:06 ` Kory Maincent
2025-10-29 12:22   ` Jiaming Zhang
2025-10-29 16:19   ` Vladimir Oltean
2025-10-29 16:47     ` Kory Maincent
2025-10-29 19:28       ` Vadim Fedorenko
2025-10-30  9:36   ` [PATCH] net: core: prevent NULL deref in generic_hwtstamp_ioctl_lower() Jiaming Zhang
2025-10-30 10:14     ` Kory Maincent
2025-10-30 10:49       ` [PATCH v2] " Jiaming Zhang
2025-10-30 12:49       ` [PATCH v3 0/1] " Jiaming Zhang
2025-10-30 12:49         ` [PATCH v3 1/1] " Jiaming Zhang
2025-11-04  1:15           ` Jakub Kicinski
2025-11-11 17:36             ` [PATCH v4 0/1] " Jiaming Zhang
2025-11-11 17:36               ` [PATCH v4 1/1] " Jiaming Zhang
2025-11-12  8:45                 ` Kory Maincent
2025-11-14  1:40               ` [PATCH v4 0/1] " patchwork-bot+netdevbpf

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).