Technical Analysis of the Kernel Crash
1. Root Cause Analysis
The defect is located in drivers/net/netdevsim/bus.c and drivers/net/netdevsim/dev.c.
In drivers/net/netdevsim/bus.c, device-level sysfs attributesβincluding new_port, del_port, and sriov_numvfsβare registered as part of nsim_bus_dev_type.groups on nsim_bus_dev->dev. The write handler new_port_store() attempts to guard against premature access by checking:
if (!smp_load_acquire(&nsim_bus_dev->init))
return -EBUSY;
However, this check is insufficient for device and driver lifecycle management:
- Driver Unbind Lifecycle:
When a user unbinds the driver (e.g. by writing the device name to
/sys/bus/netdevsim/drivers/netdevsim/unbind), the driver core calls nsim_bus_remove() -> nsim_drv_remove(). In nsim_drv_remove(), the driver resources and devlink instances are torn down, and dev_set_drvdata(&nsim_bus_dev->dev, NULL) clears the driver private data. Crucially, the underlying nsim_bus_dev device remains registered on nsim_bus, its sysfs attribute files remain accessible, and nsim_bus_dev->init remains true (it is only cleared during del_device_store() -> nsim_bus_dev_del()).
- Missing Synchronization / Driver-Bound Check:
new_port_store() does not acquire device_lock(&nsim_bus_dev->dev) nor does it verify that dev->driver is bound or dev_get_drvdata(&nsim_bus_dev->dev) is non-NULL before invoking nsim_drv_port_add(nsim_bus_dev, ...).
- Probe Failure & Concurrency Races:
Similarly, if
nsim_drv_probe() encounters an error during device initialization or if removal occurs concurrently with port creation, dev_get_drvdata(&nsim_bus_dev->dev) is NULL.
When nsim_drv_port_add() executes with drvdata == NULL:
int nsim_drv_port_add(struct nsim_bus_dev *nsim_bus_dev, enum nsim_dev_port_type type,
unsigned int port_index, u8 perm_addr[ETH_ALEN])
{
struct nsim_dev *nsim_dev = dev_get_drvdata(&nsim_bus_dev->dev);
int err;
devl_lock(priv_to_devlink(nsim_dev));
...
nsim_dev is NULL. The helper priv_to_devlink() calculates:
container_of(priv, struct devlink, priv);
Since priv is NULL (0x0), subtracting offsetof(struct devlink, priv) (0x460 or 1120 bytes) yields a wrapped/negative pointer (struct devlink *)-0x460.
Then devl_lock() calls mutex_lock(&devlink->lock).
In struct devlink:
lock is located at offset 0x2b8 (696 bytes).
- On the
PREEMPT_RT kernel build with CONFIG_DEBUG_LOCK_ALLOC (as seen in the report), struct mutex contains struct rt_mutex_base rtmutex (size 0x58 / 88 bytes) followed by struct lockdep_map dep_map.
lock_acquire(&lock->dep_map, ...) is called with &devlink->lock.dep_map, which computes:
$$-0x460 + 0x2b8 + 0x58 = -0x150 = \texttt{0xfffffffffffffeb0}$$
Inside __lock_acquire() at kernel/locking/lockdep.c:5140:
if (unlikely(lock->key == &__lockdep_no_track__))
The trapping instruction mov (%rdi), %rax attempts to dereference lock->key at address 0xfffffffffffffeb0, causing an immediate kernel page fault crash (#PF supervisor read access in kernel mode on an unmapped page).
2. Runtime Consequences in Production
- Without Sanitizers and RT Debugging:
In a production non-RT kernel without
lockdep, mutex_lock(&devlink->lock) calls atomic_long_try_cmpxchg_acquire(&lock->owner, ...) targeting &devlink->lock.owner at offset $$-0x460 + 0x2b8 = -0x1a8 = \texttt{0xfffffffffffffe58}$$. The CPU attempts a supervisor write to that unmapped high address, triggering a kernel Oops/panic.
- Memory Safety Impact:
The fault is a deterministic NULL pointer dereference with a constant struct offset (underflowing to the unmapped canonical high address
0xfffffffffffffeb0 / 0xfffffffffffffe58). No arbitrary memory or valid slab object is corrupted, overwritten, or freed; hence, MemoryCorruption = false.
- Information Leak:
Execution terminates immediately at lock acquisition without reading or disclosing kernel data to userspace (
InfoLeak = none).
- Denial of Service:
Any interaction with the sysfs attribute after driver unbinding triggers an unrecoverable kernel panic / denial of service (
RawDenialOfService = true).
- Subsystem Classification:
The bug resides in
drivers/net/netdevsim/, which provides a simulated network device driver in the network subsystem (IsNetCandidate = true), and does not involve virtualization or hypervisors (IsKVMCandidate = false).