Technical Mechanism and Root Cause Analysis
1. Crash Manifestation
The reported lockdep warning (INFO: trying to register non-static key) triggers in assign_lock_key() via register_lock_class() when try_to_wake_up() attempts to acquire p->pi_lock:
scoped_guard (raw_spinlock_irqsave, &p->pi_lock) { ... }
During registration of the lock class, lock->key is NULL. assign_lock_key() verifies whether the lock is per-CPU or a static object (static_obj(lock)). Because task_struct (and thus p->pi_lock) is dynamically allocated on the heap, static_obj() returns false, leading lockdep to report an uninitialized key on the lock.
2. Lifecycle and Invariant Analysis
task_struct->pi_lock invariant: In kernel/fork.c, every task created via copy_process() explicitly initializes p->pi_lock using rt_mutex_init_task(p) -> raw_spin_lock_init(&p->pi_lock). The init task statically initializes it in init/init_task.c via __RAW_SPIN_LOCK_UNLOCKED. Consequently, every valid, live struct task_struct in the Linux kernel has an initialized pi_lock associated with a static class key.
- Poll waitqueue lifecycle:
- In
fs/select.c, poll/select syscalls (do_select(), do_sys_poll()) allocate struct poll_wqueues table on their stack frame and initialize it via poll_initwait(&table), which records pwq->polling_task = current.
- When waitqueue entries are registered via
__pollwait(), init_waitqueue_func_entry(&entry->wait, pollwake) configures the callback and assigns entry->wait.private = pwq.
- When socket data arrives in
unix_dgram_sendmsg() / sock_def_readable(), it wakes up waiters on &wq->wait:
sock_def_readable()
wake_up_interruptible_sync_poll(&wq->wait, ...)
__wake_up_common()
curr->func() -> pollwake()
__pollwake()
default_wake_function(&dummy_wait, ...)
try_to_wake_up(curr->private, ...)
- In
__pollwake(), pwq = wait->private is retrieved, and a dummy waitqueue entry is passed to default_wake_function() with dummy_wait.private = pwq->polling_task.
3. Root Cause and Failure Mechanism
Because p->pi_lock is strictly guaranteed to have an initialized static lockdep key on any valid task_struct, hitting lock->key == NULL in register_lock_class() proves that try_to_wake_up() received a pointer p pointing to:
- Memory that has been freed or zeroed (e.g.,
pwq or task_struct use-after-free), or
- Memory belonging to an object that was overwritten or corrupted.
Specifically, poll_table_entry wait-queue entries should be detached by poll_freewait() before table goes out of scope when select() / poll() completes. If a wait-queue entry remains registered on sk->sk_wq->wait due to a race or missing removal, a subsequent sock_def_readable() invocation traverses the dangling wait-queue entry and dereferences a stale pwq and/or pwq->polling_task, causing try_to_wake_up() to operate on an invalid task_struct pointer.