// SPDX-License-Identifier: GPL-2.0 /* * e1000race.c - controlled-window instrument for report defect 2 * (e1000_resume()/e1000_io_resume() bring the device back up without * RTNL; an e1000_reset_task running inside the resume window deadlocks * with the resume thread on the NAPI state machine: * reset_task: e1000_down -> napi_disable parks holding netdev_lock+RTNL * waiting for NAPI_STATE_SCHED to clear, * resume : napi_enable (the only SCHED clearer) blocks on netdev_lock * -> mutual wait, RTNL never released, system-wide network hang). * * Zero kernel source modification. One kprobe + one delayed-work trigger: * * probe @ napi_enable entry, filtered by return address (the call site * inside e1000_up): when armed (after S3 has happened), this is exactly * the resume thread sitting BETWEEN clear_bit(__E1000_DOWN) and * napi_enable - the defect-2 opening point. The handler then * (a) schedules a second ndo_tx_timeout invocation, i.e. puts a real * reset_task to run inside the (now widened) resume window - the * natural precondition of defect 2 (work surviving across S3 / the * AER-same-source scheduling of the report's context B), and * (b) parks the resume thread for stall_napi_ms (busy wait: msleep is * illegal under kprobes) - widening the two-adjacent-statements * window that a >=10ms preemption would hit naturally. * * QEMU's e1000 model completes TX immediately, so real TX hangs (what * makes dev_watchdog call ndo_tx_timeout) never occur naturally here; * the delayed work stands in the dev-core watchdog's place and performs * the exact same indirect call on the netdev. * * Expected outcome: reset_task parks in napi_disable holding * netdev_lock+RTNL; the parked resume thread wakes into napi_enable and * blocks on the same netdev_lock -> deadlock; every later rtnl_lock() * user (e.g. `ip link set eth0 down`) hangs. */ #define pr_fmt(fmt) "E1000RACE: " fmt #include #include #include #include #include #include #include #include #include static int stall_napi_ms = 5000; module_param(stall_napi_ms, int, 0600); MODULE_PARM_DESC(stall_napi_ms, "park the resume thread before napi_enable for this long"); /* * offset of the napi_enable call site's return address, relative to the * address kprobes reports for e1000_up (kallsyms symbol + 4, ENDBR): * build-kasan-7.3: e1000_up @ +0, call napi_enable @ +0x37 (5 bytes) -> * return address @ +0x3c from the symbol -> +0x38 from the probe address. */ static ulong napi_ret_off = 0x38; module_param(napi_ret_off, ulong, 0600); MODULE_PARM_DESC(napi_ret_off, "ra offset of the napi_enable call inside e1000_up, relative to kprobe-reported symbol address (build-kasan-7.3: 0x38)"); static int armed; module_param(armed, int, 0600); MODULE_PARM_DESC(armed, "0: ignore e1000_up napi_enable hits (e.g. from e1000_open); 1: this is the resume hit"); static char *netif_name = "eth0"; module_param(netif_name, charp, 0600); static unsigned long e1000_up_addr; static atomic_t resume_hits = ATOMIC_INIT(0); static atomic_t reset_seen = ATOMIC_INIT(0); static atomic_t near_dbg = ATOMIC_INIT(0); static u64 mono_ns(void) { return ktime_get_mono_fast_ns(); } static void park_ms(unsigned long ms) { u64 t0 = mono_ns(); u64 dur = (u64)ms * 1000000ULL; while ((s64)(mono_ns() - t0) < (s64)dur) cpu_relax(); } /* dev-core watchdog stand-in: performs the exact indirect call the core * would make on a real TX hang; e1000_tx_timeout then schedules the real * reset_task via schedule_work(). Safe from any context. */ static void trigger_tx_timeout(struct work_struct *w) { struct net_device *dev = dev_get_by_name(&init_net, netif_name); if (!dev) { pr_err("trigger: netdev %s not found\n", netif_name); return; } if (!dev->netdev_ops || !dev->netdev_ops->ndo_tx_timeout) { pr_err("trigger: %s has no ndo_tx_timeout\n", netif_name); dev_put(dev); return; } pr_warn("trigger: invoking %s->ndo_tx_timeout -> schedules e1000_reset_task inside the resume window\n", netif_name); dev->netdev_ops->ndo_tx_timeout(dev, 0); dev_put(dev); } static DECLARE_DELAYED_WORK(trigger_work, trigger_tx_timeout); static int napi_enable_pre(struct kprobe *p, struct pt_regs *regs) { unsigned long ra = *(unsigned long *)regs->sp; if (ra != e1000_up_addr + napi_ret_off) { /* near-caller debug: print any ra that lands inside e1000_up */ if (ra > e1000_up_addr - 0x10 && ra < e1000_up_addr + 0x200 && atomic_inc_return(&near_dbg) <= 8) pr_info("near-caller ra=%lx (probe-addr off +0x%lx)\n", ra, ra - e1000_up_addr); return 0; } if (!armed) { pr_info("napi_enable from e1000_up seen while disarmed (open path?) - ignored\n"); return 0; } if (atomic_xchg(&resume_hits, 1)) return 0; /* only the first resume hit is instrumented */ /* (a) put a real reset_task into the widened resume window. * Must run on another CPU while this one is parked: the probe context * has preemption disabled, so queue on the unbound pool. */ queue_delayed_work(system_unbound_wq, &trigger_work, msecs_to_jiffies(50)); /* (b) park the resume thread between clear_bit(DOWN) and napi_enable */ pr_warn("resume thread (pid %d, %s) parked %d ms before napi_enable; clear_bit(__E1000_DOWN) already executed\n", current->pid, current->comm, stall_napi_ms); park_ms(stall_napi_ms); pr_warn("resume stall done; napi_enable now contends netdev_lock (expected: held by reset_task's napi_disable)\n"); return 0; } /* observation-only probe: log every reset_task execution */ static int reset_task_pre(struct kprobe *p, struct pt_regs *regs) { int n = atomic_inc_return(&reset_seen); pr_warn("reset_task execution #%d (work=%p)\n", n, (void *)regs->di); return 0; } /* observation-only probe: log who enters napi_disable (the defect-2 * parking point: reset_task enters it holding netdev_lock+RTNL and never * returns while SCHED stays set) */ static int napi_disable_pre(struct kprobe *p, struct pt_regs *regs) { if (current->flags & PF_WQ_WORKER) pr_warn("napi_disable entered by %s (pid %d) - if this is reset_task it now parks here holding netdev_lock+RTNL\n", current->comm, current->pid); return 0; } static struct kprobe kp_reset = { .symbol_name = "e1000_reset_task", .pre_handler = reset_task_pre, }; static struct kprobe kp_ndis = { .symbol_name = "napi_disable", .pre_handler = napi_disable_pre, }; static struct kprobe kp_napi = { .symbol_name = "napi_enable", .pre_handler = napi_enable_pre, }; static unsigned long sym_addr(const char *name) { struct kprobe probe = { .symbol_name = name, }; unsigned long addr = 0; if (register_kprobe(&probe) == 0) { addr = (unsigned long)probe.addr; unregister_kprobe(&probe); } return addr; } static int __init e1000race_init(void) { int ret; e1000_up_addr = sym_addr("e1000_up"); if (!e1000_up_addr) { pr_err("cannot resolve e1000_up\n"); return -EINVAL; } ret = register_kprobe(&kp_reset); if (ret) { pr_err("register kp_reset failed: %d\n", ret); return ret; } ret = register_kprobe(&kp_ndis); if (ret) { unregister_kprobe(&kp_reset); pr_err("register kp_ndis failed: %d\n", ret); return ret; } ret = register_kprobe(&kp_napi); if (ret) { unregister_kprobe(&kp_ndis); unregister_kprobe(&kp_reset); pr_err("register kp_napi failed: %d\n", ret); return ret; } pr_info("armed-probe installed: e1000_up(kprobe addr)=%lx (+0x%lx -> ra of napi_enable call), e1000_reset_task=%lx\n", e1000_up_addr, napi_ret_off, (unsigned long)kp_reset.addr); pr_info("module loaded disarmed; write 1 to /sys/module/e1000race/parameters/armed right before S3\n"); return 0; } static void __exit e1000race_exit(void) { cancel_delayed_work_sync(&trigger_work); unregister_kprobe(&kp_napi); unregister_kprobe(&kp_ndis); unregister_kprobe(&kp_reset); pr_info("disarmed\n"); } module_init(e1000race_init); module_exit(e1000race_exit); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("e1000 defect-2 controlled-window deadlock instrument");