From nobody Wed Jun 17 06:28:44 2026 Received: from stravinsky.debian.org (stravinsky.debian.org [82.195.75.108]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2663E2EAD15 for ; Mon, 27 Apr 2026 16:48:03 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=82.195.75.108 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777308484; cv=none; b=SK+K/cswAsaor6syOgg5ToEvFGzPoFYsXL1O81sIAw5UkjTQJXmLKBSk0VyRY9tO5aMivXewA8I4ZSrTKZlgseXgG1n7zmbYwlZ2fBe4dfsrcCSdOnLpfsVk4Zq0uYQ2AXrMosOJ1TvccrZZ4ZH9Ab10x8Wcj+8scJo75K6k+w4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777308484; c=relaxed/simple; bh=Kq/pnHYoBqHtvWVNzGGaBcJN2tmGKWl73fEt0qZhnLU=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:To:Cc; b=fEwpKEQgGcUfKmiXH6qcvchxJxGMtImXtOGIxZ//WJoTsGg14I8Zf/PPF+GfdPHYNZmFMPz9eHgLm38w2MvzrnKL750kArDv0DWFS3S1L09xgCNthTMCCLsTTVSy6ETAp/0EzkLgrODXtCp+sWscMEkEwr4ItAub0iq/2AIqOlo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=debian.org; spf=none smtp.mailfrom=debian.org; dkim=pass (2048-bit key) header.d=debian.org header.i=@debian.org header.b=QhVfE3DX; arc=none smtp.client-ip=82.195.75.108 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=debian.org Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=debian.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=debian.org header.i=@debian.org header.b="QhVfE3DX" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=debian.org; s=smtpauto.stravinsky; h=X-Debian-User:Cc:To:Message-Id: Content-Transfer-Encoding:Content-Type:MIME-Version:Subject:Date:From: Reply-To:Content-ID:Content-Description:In-Reply-To:References; bh=3o+5R9Y+SUKYLtZrB3He71xU0GUwKYQZn9oPfY8TsqA=; b=QhVfE3DX0+IHw9WRC0IPjvG1jJ DSmxujtE6ByQc2Bmla0mIvr4QnauaedGwmEpgn8Uotun0GLUwYB73IvodY4WLtKDVVoVjI8HiU8v5 Oh61cHBmR0GFZ+wuHoIexEr4v1lEYMK8Z5vn1t9yqZGSpGW+qqZU3qyjOnTNWh/Lo4yLAUq2XzLp9 7sscqu287tndXWJHST+4S8rUUXAFVeeY10Ska3di25K6tYlpGNjN4dZNSZrgaRMWrz+ajjgfdoAyn Socq3DPH89zecifg8jskUAWVaqBWIst/lSWBWw5fjBdbhiClBJnEkseZo3faEsU+rtg8kZu2fTsAa X6EbIYqQ==; Received: from authenticated user by stravinsky.debian.org with esmtpsa (TLS1.3:ECDHE_X25519__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim 4.96) (envelope-from ) id 1wHP7i-005cff-0O; Mon, 27 Apr 2026 16:47:54 +0000 From: Breno Leitao Date: Mon, 27 Apr 2026 09:47:44 -0700 Subject: [PATCH] workqueue: fix devm_alloc_workqueue() va_list misuse Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260427-wq_fix_chris-v1-1-988649834b79@debian.org> X-B4-Tracking: v=1; b=H4sIADCT72kC/yXMwQqCQBAG4FcZ/rMLOitF+yoiYtuY48F0x0oQ3 z2043f5NpgkFUOgDUk+avoaEajICLFvx6c4fSAQOOdLXvLVfeem07WJfVJzwl58yT7eWo+MMCX pdD27qv7b3vdB4nIc2Pcf8rxIqHAAAAA= X-Change-ID: 20260427-wq_fix_chris-e23e3423c9a3 To: Tejun Heo , Lai Jiangshan , Andy Shevchenko , Krzysztof Kozlowski Cc: clm@fb.com, linux-kernel@vger.kernel.org, kernel-team@meta.com, Breno Leitao X-Mailer: b4 0.16-dev-453a6 X-Developer-Signature: v=1; a=openpgp-sha256; l=5448; i=leitao@debian.org; h=from:subject:message-id; bh=Kq/pnHYoBqHtvWVNzGGaBcJN2tmGKWl73fEt0qZhnLU=; b=owEBbQKS/ZANAwAIATWjk5/8eHdtAcsmYgBp75M2OSbXzl9qoFKZ6IP5oJ0hA3d4QTqrlmcAA AHqbeuAi6aJAjMEAAEIAB0WIQSshTmm6PRnAspKQ5s1o5Of/Hh3bQUCae+TNgAKCRA1o5Of/Hh3 bQK8D/4ih2vB2N42Ggv6uzHgC7uVEMZW8V7AwNdCMWHHiHDnTvC2D32uozYhb+lAVpzx7zG1/hq xmFnclclfeSS5K+okWqtMYGihHCk4ahvrlZd+ybZfqkfbXqxSlkb4bbTzMLftIJpEKHdzKrfnBx nC1GMpVttV1ycTLm7NrmhjF919OjAPQhRZ4otdyKSWGj2+ieedrZof9Oq2imOSLZUFfsercQ63H 7ikelCAjasktItf1CokkZ3XuxlZowvldC1QFJU1t2v7CLq+pdlOACqS6BRy2XSC/MR3B2xAHRMZ Rdw0C7jighUPptMFLcAuG6NxKgkj20HG30eLn6kAFNwQ1DUxNYUktzP4OeyGQaN/OdeyNyZ78iZ wzx/xB8epBh1Upa+DeAgHh591cxMUFkH6JSVYHz7BWdic/F+l5QzUtc3CfnXfJCQidfp2Q4p4e+ k8OmBpFKtYSThcPcDGuNe2qw8rYA15sMZlil8hxW6YZ6k03qM5CakFhQPEhV3RnC16HuN+U3Wtu K4qeFPYfC201HuQICC2iJJK1RvHlgTY2XuwMRaw7PsG2rlMoQaTK1t0qkyDyee5HVsW2Kn86QSY 3dERyFc5ZXU3v4uKZ1Y0Ll2JGWI9HRf95e2wG0Tt0Y0l/qePgVeuGCkkImUBQvhrwWDTGKwutVC GXkkQLNXe4mZcgw== X-Developer-Key: i=leitao@debian.org; a=openpgp; fpr=AC8539A6E8F46702CA4A439B35A3939FFC78776D X-Debian-User: leitao devm_alloc_workqueue() builds a va_list from its own variadic arguments and then passes that va_list as a single positional argument to the variadic alloc_workqueue() macro: va_start(args, max_active); wq =3D alloc_workqueue(fmt, flags, max_active, args); va_end(args); alloc_workqueue() expands to alloc_workqueue_noprof(), which performs its own va_start() over its ... parameters. The inner vsnprintf(wq->name, sizeof(wq->name), fmt, args) inside __alloc_workqueue() therefore receives the outer va_list object as the first variadic slot, not the caller's actual format arguments. On x86-64 SysV a "%s" conversion dereferences the pointer inside that va_list as a char * and copies arbitrary stack bytes from devm_alloc_workqueue()'s register-save area into wq->name. The write is bounded by WQ_NAME_LEN but the read is not, so this is an out-of-bounds stack read whose result also surfaces via show_all_workqueues(), print_worker_info(), the WQ_NAME_LEN pr_warn_once(), and lockdep lock names. C does not allow forwarding a va_list through a ... parameter; a v-style entry point is required. __alloc_workqueue() is exactly that and is already used by alloc_workqueue_noprof(). Split devm_alloc_workqueue() into devm_alloc_workqueue_noprof() plus an alloc_hooks() macro wrapper, mirroring the alloc_workqueue / alloc_workqueue_noprof split. The _noprof function calls __alloc_workqueue() directly with the va_list we already hold, and runs wq_init_lockdep(wq) afterwards to mirror alloc_workqueue_noprof() (otherwise wq->lockdep_map stays NULL and __flush_workqueue()'s on-stack COMPLETION_INITIALIZER_ONSTACK_MAP would NULL-deref it). Keeping the alloc_hooks() wrapper preserves memory allocation profiling attribution under CONFIG_MEM_ALLOC_PROFILING=3Dy: the kzalloc_noprof() and alloc_workqueue_attrs_noprof() inside __alloc_workqueue() are charged to the driver call site rather than to a single line in kernel/workqueue.c. No caller changes are required. Drivers continue to write devm_alloc_workqueue() and devm_alloc_ordered_workqueue() as before; the new macro forwards through alloc_hooks() into the renamed _noprof function transparently. Out-of-tree modules need only be recompiled against the updated header to pick up the renamed export, mirroring the precedent set by the existing alloc_workqueue / alloc_workqueue_noprof split. devm_alloc_ordered_workqueue() in include/linux/workqueue.h is a macro that forwards to devm_alloc_workqueue(), so it inherits this fix automatically. Two in-tree callers actively trigger the broken path on every probe: drivers/power/supply/mt6370-charger.c:889 drivers/power/supply/max77705_charger.c:649 both of which use devm_alloc_ordered_workqueue(dev, "%s", 0, dev_name(dev)). A standalone reproducer module is available at[1]: Link: https://github.com/leitao/debug/blob/main/workqueue/valist/wq_va_test= .c [1] Fixes: 1dfc9d60a69e ("workqueue: devres: Add device-managed allocate workqu= eue") Signed-off-by: Breno Leitao --- include/linux/workqueue.h | 6 ++++-- kernel/workqueue.c | 16 ++++++++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/include/linux/workqueue.h b/include/linux/workqueue.h index ab6cb70ca1a52..6177624539b3b 100644 --- a/include/linux/workqueue.h +++ b/include/linux/workqueue.h @@ -534,8 +534,10 @@ alloc_workqueue_noprof(const char *fmt, unsigned int f= lags, int max_active, ...) * Pointer to the allocated workqueue on success, %NULL on failure. */ __printf(2, 5) struct workqueue_struct * -devm_alloc_workqueue(struct device *dev, const char *fmt, unsigned int fla= gs, - int max_active, ...); +devm_alloc_workqueue_noprof(struct device *dev, const char *fmt, + unsigned int flags, int max_active, ...); +#define devm_alloc_workqueue(...) \ + alloc_hooks(devm_alloc_workqueue_noprof(__VA_ARGS__)) =20 #ifdef CONFIG_LOCKDEP /** diff --git a/kernel/workqueue.c b/kernel/workqueue.c index 5f747f241a5f1..ee5bd627bb52a 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -5932,26 +5932,34 @@ static void devm_workqueue_release(void *res) } =20 __printf(2, 5) struct workqueue_struct * -devm_alloc_workqueue(struct device *dev, const char *fmt, unsigned int fla= gs, - int max_active, ...) +devm_alloc_workqueue_noprof(struct device *dev, const char *fmt, + unsigned int flags, int max_active, ...) { struct workqueue_struct *wq; va_list args; int ret; =20 va_start(args, max_active); - wq =3D alloc_workqueue(fmt, flags, max_active, args); + wq =3D __alloc_workqueue(fmt, flags, max_active, args); va_end(args); if (!wq) return NULL; =20 + /* + * __alloc_workqueue() doesn't initialize wq->lockdep_map; only its + * sibling alloc_workqueue_noprof() does. Mirror that here, otherwise + * __flush_workqueue()'s COMPLETION_INITIALIZER_ONSTACK_MAP would + * dereference a NULL wq->lockdep_map. + */ + wq_init_lockdep(wq); + ret =3D devm_add_action_or_reset(dev, devm_workqueue_release, wq); if (ret) return NULL; =20 return wq; } -EXPORT_SYMBOL_GPL(devm_alloc_workqueue); +EXPORT_SYMBOL_GPL(devm_alloc_workqueue_noprof); =20 #ifdef CONFIG_LOCKDEP __printf(1, 5) --- base-commit: dd6c438c3e64a5ff0b5d7e78f7f9be547803ef1b change-id: 20260427-wq_fix_chris-e23e3423c9a3 Best regards, -- =20 Breno Leitao