Root Cause Analysis
1. Failure Manifestation and Detection Mechanism
The reported kernel panic:
Kernel panic - not syncing: corrupted stack end detected inside scheduler
Workqueue: 0x0 (wg-crypt-wg1)
Call trace:
...
vpanic+0x594/0xaf8 kernel/panic.c:651
schedule_debug kernel/sched/core.c:6103 [inline]
__schedule+0x18a0/0x2b00 kernel/sched/core.c:7133
__schedule_loop kernel/sched/core.c:7361 [inline]
schedule+0x13c/0x20c kernel/sched/core.c:7376
worker_thread+0x924/0xbd0 kernel/workqueue.c:3575
kthread+0x304/0x3d4 kernel/kthread.c:436
The check is performed in schedule_debug() (kernel/sched/core.c):
#ifdef CONFIG_SCHED_STACK_END_CHECK
if (task_stack_end_corrupted(prev))
panic("corrupted stack end detected inside scheduler\n");
#endif
task_stack_end_corrupted(task) evaluates *(end_of_stack(task)) != STACK_END_MAGIC. On architectures with downward-growing stacks (such as arm64), end_of_stack(task) is situated at the lowest address of the allocated kernel stack area.
When the worker thread in worker_thread() (kernel/workqueue.c:3575) completes processing work items via process_scheduled_works(), it marks itself as idle (__set_current_state(TASK_IDLE)) and invokes schedule(). Notice that worker->current_func and worker->current_pwq are cleared upon work completion in process_scheduled_works() (kernel/workqueue.c:3447-3449), but worker->desc still holds the workqueue name "wg-crypt-wg1", which is why print_worker_info() outputs Workqueue: 0x0 (wg-crypt-wg1).
2. WireGuard Workqueue Execution and Kernel Stack Exhaustion
WireGuard allocates its packet cryptography and transmission workqueue in drivers/net/wireguard/device.c:
wg->packet_crypt_wq = alloc_workqueue("wg-crypt-%s",
WQ_CPU_INTENSIVE | WQ_MEM_RECLAIM | WQ_PERCPU, 0,
dev->name);
Three work handlers execute on this workqueue:
wg_packet_encrypt_worker (drivers/net/wireguard/send.c)
wg_packet_decrypt_worker (drivers/net/wireguard/receive.c)
wg_packet_tx_worker (drivers/net/wireguard/send.c)
Investigation of these paths revealed:
Because this entire stack of crypto operations, UDP tunneling encapsulation, routing lookup, egress queuing, and hardware/virtual driver transmission executes in the synchronous context of the wg-crypt-%s kworker, cumulative stack frame consumption descended into the bottom boundary of the task stack, overwriting STACK_END_MAGIC. Because the stack pointer did not cross into the guard page beneath the stack (or CONFIG_VMAP_STACK was either bordered or just touched the canary word), execution successfully returned to worker_thread(), where schedule() caught the corrupted canary during the next context switch.