From nobody Tue Oct 7 19:50:29 2025 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (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 1271A261571; Mon, 7 Jul 2025 05:06:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751864770; cv=none; b=tO1isKbxZRp5wrkpczKRu4jjQwevX0BW5vc4ANq6ZHVegiurLMYKT91IlwPR19xoPtgiO3EgPuURIl+npDAUJZjmgc/SgIHqlex2INJHsyumh13S50xf70eP6Dif5se9lDvu7tad3Gfq0xHfFiDn3WLoifCQm+S0VTWCjm4KHtA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751864770; c=relaxed/simple; bh=Yezh1PpcDSnMIsQt/1ue+JNou0BqpTIh4TMeGHqSd4o=; h=Date:From:To:Cc:Subject:Message-ID:References:MIME-Version: Content-Type:Content-Disposition:In-Reply-To; b=p1+nLXNE/7luu+bDZG+Viuis0Dk/0cvBGaUUh+S3oeXZMmHarC1x7ivm5y9yraUP4lzkhhk1TD2qEVr/37V5cW84Du4BoRL2OJOlIfgVQFyfaOW7X37JgG/eQj+VJgEbqZDBNltNEO1QylrcFD3Pe5K80env+QqkQOsFf29JGcQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=l4eRU6Zt; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="l4eRU6Zt" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 0821DC4CEEE; Mon, 7 Jul 2025 05:06:09 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1751864769; bh=Yezh1PpcDSnMIsQt/1ue+JNou0BqpTIh4TMeGHqSd4o=; h=Date:From:To:Cc:Subject:References:In-Reply-To:From; b=l4eRU6ZtIjrPZqE1oswfysOMpfN27h8Z6VLlby66doSNl7CaHl5QBDBdXYWc6Yr9q 60ONel3IMGDoF4MLcqJZyRjBE6YKzmlCjUgmCNZOFnoT7L/COa5LlI5V41UKkJVEGj upswcn7pRbgBfXEWEcFm4KFn/80NdG6p2/AsNRBuu6Sen590ehEudEdP+GkOUo34r9 REromnPjwuV/0TxPAq9E3qV9ptD29A0pJRRmaK2x1YulRQ4oij925V03/RxBrglc56 Bd7v0ZlzbpK7kaW5IJmiCgFg8HLmC4xiT+L6eOVH2ZnOspNXnz+jUqyHOYtNQLBVDM Y3aOsOl5BeqZA== Date: Mon, 7 Jul 2025 07:06:08 +0200 From: Alejandro Colomar To: linux-mm@kvack.org, linux-hardening@vger.kernel.org Cc: Alejandro Colomar , Kees Cook , Christopher Bazley , shadow <~hallyn/shadow@lists.sr.ht>, linux-kernel@vger.kernel.org, Andrew Morton , kasan-dev@googlegroups.com, Dmitry Vyukov , Alexander Potapenko , Marco Elver , Christoph Lameter , David Rientjes , Vlastimil Babka , Roman Gushchin , Harry Yoo , Andrew Clayton Subject: [RFC v3 1/7] vsprintf: Add [v]seprintf(), [v]stprintf() Message-ID: <64334f0b94d6b853e6104ec4f89bcf910978db76.1751862634.git.alx@kernel.org> X-Mailer: git-send-email 2.50.0 References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" seprintf() =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D seprintf() is a function similar to stpcpy(3) in the sense that it returns a pointer that is suitable for chaining to other copy operations. It takes a pointer to the end of the buffer as a sentinel for when to truncate, which unlike a size, doesn't need to be updated after every call. This makes it much more ergonomic, avoiding manually calculating the size after each copy, which is error prone. It also makes error handling much easier, by reporting truncation with a null pointer, which is accepted and transparently passed down by subsequent seprintf() calls. This results in only needing to report errors once after a chain of seprintf() calls, unlike snprintf(3), which requires checking after every call. p =3D buf; e =3D buf + countof(buf); p =3D seprintf(p, e, foo); p =3D seprintf(p, e, bar); if (p =3D=3D NULL) goto trunc; vs len =3D 0; size =3D countof(buf); len +=3D snprintf(buf + len, size - len, foo); if (len >=3D size) goto trunc; len +=3D snprintf(buf + len, size - len, bar); if (len >=3D size) goto trunc; And also better than scnprintf() calls: len =3D 0; size =3D countof(buf); len +=3D scnprintf(buf + len, size - len, foo); len +=3D scnprintf(buf + len, size - len, bar); if (len >=3D size) goto trunc; It seems aparent that it's a more elegant approach to string catenation. stprintf() =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D stprintf() is a helper that is needed for implementing seprintf() --although it could be open-coded within vseprintf(), of course--, but it's also useful by itself. It has the same interface properties as strscpy(): that is, it copies with truncation, and reports truncation with -E2BIG. It would be useful to replace some calls to snprintf(3) and scnprintf() which don't need chaining, and where it's simpler to pass a size. It is better than plain snprintf(3), because it results in simpler error detection (it doesn't need a check >=3Dcountof(buf), but rather <0). Cc: Kees Cook Cc: Christopher Bazley Signed-off-by: Alejandro Colomar --- include/linux/sprintf.h | 4 ++ lib/vsprintf.c | 109 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) diff --git a/include/linux/sprintf.h b/include/linux/sprintf.h index 51cab2def9ec..c3dbfd2efd2b 100644 --- a/include/linux/sprintf.h +++ b/include/linux/sprintf.h @@ -11,8 +11,12 @@ __printf(2, 3) int sprintf(char *buf, const char * fmt, = ...); __printf(2, 0) int vsprintf(char *buf, const char *, va_list); __printf(3, 4) int snprintf(char *buf, size_t size, const char *fmt, ...); __printf(3, 0) int vsnprintf(char *buf, size_t size, const char *fmt, va_l= ist args); +__printf(3, 4) int stprintf(char *buf, size_t size, const char *fmt, ...); +__printf(3, 0) int vstprintf(char *buf, size_t size, const char *fmt, va_l= ist args); __printf(3, 4) int scnprintf(char *buf, size_t size, const char *fmt, ...); __printf(3, 0) int vscnprintf(char *buf, size_t size, const char *fmt, va_= list args); +__printf(3, 4) char *seprintf(char *p, const char end[0], const char *fmt,= ...); +__printf(3, 0) char *vseprintf(char *p, const char end[0], const char *fmt= , va_list args); __printf(2, 3) __malloc char *kasprintf(gfp_t gfp, const char *fmt, ...); __printf(2, 0) __malloc char *kvasprintf(gfp_t gfp, const char *fmt, va_li= st args); __printf(2, 0) const char *kvasprintf_const(gfp_t gfp, const char *fmt, va= _list args); diff --git a/lib/vsprintf.c b/lib/vsprintf.c index 01699852f30c..a3efacadb5e5 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -2892,6 +2892,37 @@ int vsnprintf(char *buf, size_t size, const char *fm= t_str, va_list args) } EXPORT_SYMBOL(vsnprintf); =20 +/** + * vstprintf - Format a string and place it in a buffer + * @buf: The buffer to place the result into + * @size: The size of the buffer, including the trailing null space + * @fmt: The format string to use + * @args: Arguments for the format string + * + * The return value is the length of the new string. + * If the string is truncated, the function returns -E2BIG. + * + * If you're not already dealing with a va_list consider using stprintf(). + * + * See the vsnprintf() documentation for format string extensions over C99. + */ +int vstprintf(char *buf, size_t size, const char *fmt, va_list args) +{ + int len; + + len =3D vsnprintf(buf, size, fmt, args); + + // It seems the kernel's vsnprintf() doesn't fail? + //if (unlikely(len < 0)) + // return -E2BIG; + + if (unlikely(len >=3D size)) + return -E2BIG; + + return len; +} +EXPORT_SYMBOL(vstprintf); + /** * vscnprintf - Format a string and place it in a buffer * @buf: The buffer to place the result into @@ -2923,6 +2954,36 @@ int vscnprintf(char *buf, size_t size, const char *f= mt, va_list args) } EXPORT_SYMBOL(vscnprintf); =20 +/** + * vseprintf - Format a string and place it in a buffer + * @p: The buffer to place the result into + * @end: A pointer to one past the last character in the buffer + * @fmt: The format string to use + * @args: Arguments for the format string + * + * The return value is a pointer to the trailing '\0'. + * If @p is NULL, the function returns NULL. + * If the string is truncated, the function returns NULL. + * + * If you're not already dealing with a va_list consider using seprintf(). + * + * See the vsnprintf() documentation for format string extensions over C99. + */ +char *vseprintf(char *p, const char end[0], const char *fmt, va_list args) +{ + int len; + + if (unlikely(p =3D=3D NULL)) + return NULL; + + len =3D vstprintf(p, end - p, fmt, args); + if (unlikely(len < 0)) + return NULL; + + return p + len; +} +EXPORT_SYMBOL(vseprintf); + /** * snprintf - Format a string and place it in a buffer * @buf: The buffer to place the result into @@ -2950,6 +3011,30 @@ int snprintf(char *buf, size_t size, const char *fmt= , ...) } EXPORT_SYMBOL(snprintf); =20 +/** + * stprintf - Format a string and place it in a buffer + * @buf: The buffer to place the result into + * @size: The size of the buffer, including the trailing null space + * @fmt: The format string to use + * @...: Arguments for the format string + * + * The return value is the length of the new string. + * If the string is truncated, the function returns -E2BIG. + */ + +int stprintf(char *buf, size_t size, const char *fmt, ...) +{ + va_list args; + int len; + + va_start(args, fmt); + len =3D vstprintf(buf, size, fmt, args); + va_end(args); + + return len; +} +EXPORT_SYMBOL(stprintf); + /** * scnprintf - Format a string and place it in a buffer * @buf: The buffer to place the result into @@ -2974,6 +3059,30 @@ int scnprintf(char *buf, size_t size, const char *fm= t, ...) } EXPORT_SYMBOL(scnprintf); =20 +/** + * seprintf - Format a string and place it in a buffer + * @p: The buffer to place the result into + * @end: A pointer to one past the last character in the buffer + * @fmt: The format string to use + * @...: Arguments for the format string + * + * The return value is a pointer to the trailing '\0'. + * If @buf is NULL, the function returns NULL. + * If the string is truncated, the function returns NULL. + */ + +char *seprintf(char *p, const char end[0], const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + p =3D vseprintf(p, end, fmt, args); + va_end(args); + + return p; +} +EXPORT_SYMBOL(seprintf); + /** * vsprintf - Format a string and place it in a buffer * @buf: The buffer to place the result into --=20 2.50.0 From nobody Tue Oct 7 19:50:29 2025 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (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 5E02E262FEC; Mon, 7 Jul 2025 05:06:11 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751864771; cv=none; b=psQnDf1EMPU5tMpi2DCycuGWUPDsS3aojduWXXd/8UCaYBENVYsOHjUnf3YZ1BOWvjyT2D//m3go2QDUfDQqrdbufDMkqyFQQ1EkOjkNVxUo2/6wTHaIK9mChFoyoW/QvsaWhKE43UlfY5CBbGz3C05ZCeBnMJYyRl5H8XZf0C8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751864771; c=relaxed/simple; bh=UnukMHnFrNRdetsFMcw3prQ/v4qlktaL9HMhPaNQEX0=; h=Date:From:To:Cc:Subject:Message-ID:References:MIME-Version: Content-Type:Content-Disposition:In-Reply-To; b=fznE2+x7lzqwhWua6VaXGR/2XfSJ0ZYN0I4xQhFfS69dQBhQ0CBBb8SSM2zG6NTKevsFm+6f0Fks6rHvvYyGy/aBvpiHAEhK340CL4YD5eGxlHC5N3/RN4kNvU5CyJ5JEUmPoopEra9Cy7V/SbjT2CJmmR/pKANfCTcYURbwfXo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=sYkHDiS7; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="sYkHDiS7" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 23963C4CEF5; Mon, 7 Jul 2025 05:06:10 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1751864770; bh=UnukMHnFrNRdetsFMcw3prQ/v4qlktaL9HMhPaNQEX0=; h=Date:From:To:Cc:Subject:References:In-Reply-To:From; b=sYkHDiS7fXE2K8y0Y1giKvh2pbltZ7/tva+9LiT/VHcKqFKM3JQky1dhM2jGM1LcA BXF8Cw0R8HcuMxeVIdXjx7pdJfKJPI0hxP1xyqtt12nBF1nxdDD5N3CAQHaV/rPavC XOA113UlxIytk6j+oxrCBignYcCWTvYDU1038YP8IvJEjjQHZykZrdNYqNrIkNG3p0 Of/g8ZVxkD0z/vQkDnNKJzv2SRZ7bMP9J7gi7WJTjZEGImUF1wMOtl2JBikTRlSaON Og0VdMofT7QId7rpsY5mQ8Qwg0NOlPbCeroujc0pWXhQrLC4tB/idCfrfsZnS5S3zc ax8VX82Slh2bQ== Date: Mon, 7 Jul 2025 07:06:09 +0200 From: Alejandro Colomar To: linux-mm@kvack.org, linux-hardening@vger.kernel.org Cc: Alejandro Colomar , Kees Cook , Christopher Bazley , shadow <~hallyn/shadow@lists.sr.ht>, linux-kernel@vger.kernel.org, Andrew Morton , kasan-dev@googlegroups.com, Dmitry Vyukov , Alexander Potapenko , Marco Elver , Christoph Lameter , David Rientjes , Vlastimil Babka , Roman Gushchin , Harry Yoo , Andrew Clayton Subject: [RFC v3 2/7] stacktrace, stackdepot: Add seprintf()-like variants of functions Message-ID: <9c140de9842dd34096928a7c87dab4f45ef18764.1751862634.git.alx@kernel.org> X-Mailer: git-send-email 2.50.0 References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" I think there's an anomaly in stack_depot_s*print(). If we have zero entries, we don't copy anything, which means the string is still not a string. Normally, this function is called surrounded by other calls to s*printf(), which guarantee that there's a '\0', but maybe we should make sure to write a '\0' here? Cc: Kees Cook Cc: Christopher Bazley Signed-off-by: Alejandro Colomar --- include/linux/stackdepot.h | 13 +++++++++++++ include/linux/stacktrace.h | 3 +++ kernel/stacktrace.c | 28 ++++++++++++++++++++++++++++ lib/stackdepot.c | 12 ++++++++++++ 4 files changed, 56 insertions(+) diff --git a/include/linux/stackdepot.h b/include/linux/stackdepot.h index 2cc21ffcdaf9..a7749fc3ac7c 100644 --- a/include/linux/stackdepot.h +++ b/include/linux/stackdepot.h @@ -219,6 +219,19 @@ void stack_depot_print(depot_stack_handle_t stack); int stack_depot_snprint(depot_stack_handle_t handle, char *buf, size_t siz= e, int spaces); =20 +/** + * stack_depot_seprint - Print a stack trace from stack depot into a buffer + * + * @handle: Stack depot handle returned from stack_depot_save() + * @p: Pointer to the print buffer + * @end: Pointer to one past the last element in the buffer + * @spaces: Number of leading spaces to print + * + * Return: Pointer to trailing '\0'; or NULL on truncation + */ +char *stack_depot_seprint(depot_stack_handle_t handle, char *p, + const char end[0], int spaces); + /** * stack_depot_put - Drop a reference to a stack trace from stack depot * diff --git a/include/linux/stacktrace.h b/include/linux/stacktrace.h index 97455880ac41..748936386c89 100644 --- a/include/linux/stacktrace.h +++ b/include/linux/stacktrace.h @@ -67,6 +67,9 @@ void stack_trace_print(const unsigned long *trace, unsign= ed int nr_entries, int spaces); int stack_trace_snprint(char *buf, size_t size, const unsigned long *entri= es, unsigned int nr_entries, int spaces); +char *stack_trace_seprint(char *p, const char end[0], + const unsigned long *entries, unsigned int nr_entries, + int spaces); unsigned int stack_trace_save(unsigned long *store, unsigned int size, unsigned int skipnr); unsigned int stack_trace_save_tsk(struct task_struct *task, diff --git a/kernel/stacktrace.c b/kernel/stacktrace.c index afb3c116da91..65caf9e63673 100644 --- a/kernel/stacktrace.c +++ b/kernel/stacktrace.c @@ -70,6 +70,34 @@ int stack_trace_snprint(char *buf, size_t size, const un= signed long *entries, } EXPORT_SYMBOL_GPL(stack_trace_snprint); =20 +/** + * stack_trace_seprint - Print the entries in the stack trace into a buffer + * @p: Pointer to the print buffer + * @end: Pointer to one past the last element in the buffer + * @entries: Pointer to storage array + * @nr_entries: Number of entries in the storage array + * @spaces: Number of leading spaces to print + * + * Return: Pointer to the trailing '\0'; or NULL on truncation. + */ +char *stack_trace_seprint(char *p, const char end[0], + const unsigned long *entries, unsigned int nr_entries, + int spaces) +{ + unsigned int i; + + if (WARN_ON(!entries)) + return 0; + + for (i =3D 0; i < nr_entries; i++) { + p =3D seprintf(p, end, "%*c%pS\n", 1 + spaces, ' ', + (void *)entries[i]); + } + + return p; +} +EXPORT_SYMBOL_GPL(stack_trace_seprint); + #ifdef CONFIG_ARCH_STACKWALK =20 struct stacktrace_cookie { diff --git a/lib/stackdepot.c b/lib/stackdepot.c index 73d7b50924ef..749496e6a6f1 100644 --- a/lib/stackdepot.c +++ b/lib/stackdepot.c @@ -771,6 +771,18 @@ int stack_depot_snprint(depot_stack_handle_t handle, c= har *buf, size_t size, } EXPORT_SYMBOL_GPL(stack_depot_snprint); =20 +char *stack_depot_seprint(depot_stack_handle_t handle, char *p, + const char end[0], int spaces) +{ + unsigned long *entries; + unsigned int nr_entries; + + nr_entries =3D stack_depot_fetch(handle, &entries); + return nr_entries ? stack_trace_seprint(p, end, entries, nr_entries, + spaces) : p; +} +EXPORT_SYMBOL_GPL(stack_depot_seprint); + depot_stack_handle_t __must_check stack_depot_set_extra_bits( depot_stack_handle_t handle, unsigned int extra_bits) { --=20 2.50.0 From nobody Tue Oct 7 19:50:29 2025 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (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 345A7264616; Mon, 7 Jul 2025 05:06:13 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751864774; cv=none; b=j9TgnBiNWutPpWaPh0TITGVDP6fC5HZ9d3cS5AuyJOZ7cs4MJy1Bja4P/v4dZi3kxgp7GtxyVMFLn2Axdp+vi1+pgSuqUpdAL0Bn+/uUI+Hoz34FXuvg5Lp8bz85/hJyBaJjla6kopMF/0Ycvge4V7WYcr5mK60xe6Ne+mtC+Xg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751864774; c=relaxed/simple; bh=d8iwYb4LX6zpc2QB9uq4ToqQSlCyRkN7qj9+9t9kNow=; h=Date:From:To:Cc:Subject:Message-ID:References:MIME-Version: Content-Type:Content-Disposition:In-Reply-To; b=SMM1E6efYoFEqXtaIdg9Kso/BP3KOKnqqgTYKMynAkTYFK2eNJsfir1ksaHhxDATV46v4RgW7JAGQRZkhWZMZtLAq7eZ2nUie7tvfq+IlkBhQAYMNdClP7Uyr5v4PB7DNDJgjlOSDQ2GpFY7LD1THkhdov95svyUwMvo6DbbcAc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=BE1I5vs3; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="BE1I5vs3" Received: by smtp.kernel.org (Postfix) with ESMTPSA id ED4F3C4CEF3; Mon, 7 Jul 2025 05:06:11 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1751864773; bh=d8iwYb4LX6zpc2QB9uq4ToqQSlCyRkN7qj9+9t9kNow=; h=Date:From:To:Cc:Subject:References:In-Reply-To:From; b=BE1I5vs3/pfUdzSgD4sxEKTLCBAFWAU6W9ONiNRuTQTW5T1Dm9KvdQo8Ua615sAGX 2XX9/tbwMHxe3jA+/++Gf/AEbWANeM2/IkY8FFZ20DENbWaqzXvZw1XF4TVwVwtRUi t53paBWlaBT52gCezkkF40bzwWYYN1aFJY1WTF+urAN1zCMTIviettEs9r72+T1PKn yVcbBogCvwtzKSzzEcNOzx9cSO/WUrIh3W2YJA9T8OMW3UI2FM/HC9MbPq+oSMfYOl aZNHxNNBNUdEIiSw/bcYNd30RonV9QvJNDLgJxAFIamiDcIGRV6DIlpVZboZ0HmsLG kfXJR+8Kcxv0Q== Date: Mon, 7 Jul 2025 07:06:10 +0200 From: Alejandro Colomar To: linux-mm@kvack.org, linux-hardening@vger.kernel.org Cc: Alejandro Colomar , Kees Cook , Christopher Bazley , shadow <~hallyn/shadow@lists.sr.ht>, linux-kernel@vger.kernel.org, Andrew Morton , kasan-dev@googlegroups.com, Dmitry Vyukov , Alexander Potapenko , Marco Elver , Christoph Lameter , David Rientjes , Vlastimil Babka , Roman Gushchin , Harry Yoo , Andrew Clayton , Sven Schnelle , Heiko Carstens , Tvrtko Ursulin , "Huang, Ying" , Lee Schermerhorn , Linus Torvalds , Christophe JAILLET , Hyeonggon Yoo <42.hyeyoo@gmail.com>, Chao Yu Subject: [RFC v3 3/7] mm: Use seprintf() instead of less ergonomic APIs Message-ID: <033bf00f1fcf808245ae150346019aa7b997ea11.1751862634.git.alx@kernel.org> X-Mailer: git-send-email 2.50.0 References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" While doing this, I detected some anomalies in the existing code: mm/kfence/kfence_test.c: - The last call to scnprintf() did increment 'cur', but it's unused after that, so it was dead code. I've removed the dead code in this patch. - 'end' is calculated as end =3D &expect[0][sizeof(expect[0] - 1)]; However, the '-1' doesn't seem to be necessary. When passing $2 to scnprintf(), the size was specified as 'end - cur'. And scnprintf() --just like snprintf(3)--, won't write more than $2 bytes (including the null byte). That means that scnprintf() wouldn't write more than &expect[0][sizeof(expect[0]) - 1] - expect[0] which simplifies to sizeof(expect[0]) - 1 bytes. But we have sizeof(expect[0]) bytes available, so we're wasting one byte entirely. This is a benign off-by-one bug. The two occurrences of this bug will be fixed in a following patch in this series. mm/kmsan/kmsan_test.c: The same benign off-by-one bug calculating the remaining size. mm/mempolicy.c: This file uses the 'p +=3D snprintf()' anti-pattern. That will overflow the pointer on truncation, which has undefined behavior. Using seprintf(), this bug is fixed. As in the previous file, here there was also dead code in the last scnprintf() call, by incrementing a pointer that is not used after the call. I've removed the dead code. mm/page_owner.c: Within print_page_owner(), there are some calls to scnprintf(), which do report truncation. And then there are other calls to snprintf(), where we handle errors (there are two 'goto err'). I've kept the existing error handling, as I trust it's there for a good reason (i.e., we may want to avoid calling print_page_owner_memcg() if we truncated before). Please review if this amount of error handling is the right one, or if we want to add or remove some. For seprintf(), a single test for null after the last call is enough to detect truncation. mm/slub.c: Again, the 'p +=3D snprintf()' anti-pattern. This is UB, and by using seprintf() we've fixed the bug. Fixes: f99e12b21b84 (2021-07-30; "kfence: add function to mask address bits= ") [alx: that commit introduced dead code] Fixes: af649773fb25 (2024-07-17; "mm/numa_balancing: teach mpol_to_str abou= t the balancing mode") [alx: that commit added p+=3Dsnprintf() calls, which are UB] Fixes: 2291990ab36b (2008-04-28; "mempolicy: clean-up mpol-to-str() mempoli= cy formatting") [alx: that commit changed p+=3Dsprintf() into p+=3Dsnprintf(), which is sti= ll UB] Fixes: 948927ee9e4f (2013-11-13; "mm, mempolicy: make mpol_to_str robust an= d always succeed") [alx: that commit changes old code into p+=3Dsnprintf(), which is still UB] [alx: that commit also produced dead code by leaving the last 'p+=3D...'] Fixes: d65360f22406 (2022-09-26; "mm/slub: clean up create_unique_id()") [alx: that commit changed p+=3Dsprintf() into p+=3Dsnprintf(), which is sti= ll UB] Cc: Kees Cook Cc: Christopher Bazley Cc: Sven Schnelle Cc: Marco Elver Cc: Heiko Carstens Cc: Tvrtko Ursulin Cc: "Huang, Ying" Cc: Andrew Morton Cc: Lee Schermerhorn Cc: Linus Torvalds Cc: David Rientjes Cc: Christophe JAILLET Cc: Hyeonggon Yoo <42.hyeyoo@gmail.com> Cc: Chao Yu Cc: Vlastimil Babka Signed-off-by: Alejandro Colomar --- mm/kfence/kfence_test.c | 24 ++++++++++++------------ mm/kmsan/kmsan_test.c | 4 ++-- mm/mempolicy.c | 18 +++++++++--------- mm/page_owner.c | 32 +++++++++++++++++--------------- mm/slub.c | 5 +++-- 5 files changed, 43 insertions(+), 40 deletions(-) diff --git a/mm/kfence/kfence_test.c b/mm/kfence/kfence_test.c index 00034e37bc9f..ff734c514c03 100644 --- a/mm/kfence/kfence_test.c +++ b/mm/kfence/kfence_test.c @@ -113,26 +113,26 @@ static bool report_matches(const struct expect_report= *r) end =3D &expect[0][sizeof(expect[0]) - 1]; switch (r->type) { case KFENCE_ERROR_OOB: - cur +=3D scnprintf(cur, end - cur, "BUG: KFENCE: out-of-bounds %s", + cur =3D seprintf(cur, end, "BUG: KFENCE: out-of-bounds %s", get_access_type(r)); break; case KFENCE_ERROR_UAF: - cur +=3D scnprintf(cur, end - cur, "BUG: KFENCE: use-after-free %s", + cur =3D seprintf(cur, end, "BUG: KFENCE: use-after-free %s", get_access_type(r)); break; case KFENCE_ERROR_CORRUPTION: - cur +=3D scnprintf(cur, end - cur, "BUG: KFENCE: memory corruption"); + cur =3D seprintf(cur, end, "BUG: KFENCE: memory corruption"); break; case KFENCE_ERROR_INVALID: - cur +=3D scnprintf(cur, end - cur, "BUG: KFENCE: invalid %s", + cur =3D seprintf(cur, end, "BUG: KFENCE: invalid %s", get_access_type(r)); break; case KFENCE_ERROR_INVALID_FREE: - cur +=3D scnprintf(cur, end - cur, "BUG: KFENCE: invalid free"); + cur =3D seprintf(cur, end, "BUG: KFENCE: invalid free"); break; } =20 - scnprintf(cur, end - cur, " in %pS", r->fn); + seprintf(cur, end, " in %pS", r->fn); /* The exact offset won't match, remove it; also strip module name. */ cur =3D strchr(expect[0], '+'); if (cur) @@ -144,26 +144,26 @@ static bool report_matches(const struct expect_report= *r) =20 switch (r->type) { case KFENCE_ERROR_OOB: - cur +=3D scnprintf(cur, end - cur, "Out-of-bounds %s at", get_access_typ= e(r)); + cur =3D seprintf(cur, end, "Out-of-bounds %s at", get_access_type(r)); addr =3D arch_kfence_test_address(addr); break; case KFENCE_ERROR_UAF: - cur +=3D scnprintf(cur, end - cur, "Use-after-free %s at", get_access_ty= pe(r)); + cur =3D seprintf(cur, end, "Use-after-free %s at", get_access_type(r)); addr =3D arch_kfence_test_address(addr); break; case KFENCE_ERROR_CORRUPTION: - cur +=3D scnprintf(cur, end - cur, "Corrupted memory at"); + cur =3D seprintf(cur, end, "Corrupted memory at"); break; case KFENCE_ERROR_INVALID: - cur +=3D scnprintf(cur, end - cur, "Invalid %s at", get_access_type(r)); + cur =3D seprintf(cur, end, "Invalid %s at", get_access_type(r)); addr =3D arch_kfence_test_address(addr); break; case KFENCE_ERROR_INVALID_FREE: - cur +=3D scnprintf(cur, end - cur, "Invalid free of"); + cur =3D seprintf(cur, end, "Invalid free of"); break; } =20 - cur +=3D scnprintf(cur, end - cur, " 0x%p", (void *)addr); + seprintf(cur, end, " 0x%p", (void *)addr); =20 spin_lock_irqsave(&observed.lock, flags); if (!report_available()) diff --git a/mm/kmsan/kmsan_test.c b/mm/kmsan/kmsan_test.c index 9733a22c46c1..a062a46b2d24 100644 --- a/mm/kmsan/kmsan_test.c +++ b/mm/kmsan/kmsan_test.c @@ -107,9 +107,9 @@ static bool report_matches(const struct expect_report *= r) cur =3D expected_header; end =3D &expected_header[sizeof(expected_header) - 1]; =20 - cur +=3D scnprintf(cur, end - cur, "BUG: KMSAN: %s", r->error_type); + cur =3D seprintf(cur, end, "BUG: KMSAN: %s", r->error_type); =20 - scnprintf(cur, end - cur, " in %s", r->symbol); + seprintf(cur, end, " in %s", r->symbol); /* The exact offset won't match, remove it; also strip module name. */ cur =3D strchr(expected_header, '+'); if (cur) diff --git a/mm/mempolicy.c b/mm/mempolicy.c index b28a1e6ae096..c696e4a6f4c2 100644 --- a/mm/mempolicy.c +++ b/mm/mempolicy.c @@ -3359,6 +3359,7 @@ int mpol_parse_str(char *str, struct mempolicy **mpol) void mpol_to_str(char *buffer, int maxlen, struct mempolicy *pol) { char *p =3D buffer; + char *e =3D buffer + maxlen; nodemask_t nodes =3D NODE_MASK_NONE; unsigned short mode =3D MPOL_DEFAULT; unsigned short flags =3D 0; @@ -3384,33 +3385,32 @@ void mpol_to_str(char *buffer, int maxlen, struct m= empolicy *pol) break; default: WARN_ON_ONCE(1); - snprintf(p, maxlen, "unknown"); + seprintf(p, e, "unknown"); return; } =20 - p +=3D snprintf(p, maxlen, "%s", policy_modes[mode]); + p =3D seprintf(p, e, "%s", policy_modes[mode]); =20 if (flags & MPOL_MODE_FLAGS) { - p +=3D snprintf(p, buffer + maxlen - p, "=3D"); + p =3D seprintf(p, e, "=3D"); =20 /* * Static and relative are mutually exclusive. */ if (flags & MPOL_F_STATIC_NODES) - p +=3D snprintf(p, buffer + maxlen - p, "static"); + p =3D seprintf(p, e, "static"); else if (flags & MPOL_F_RELATIVE_NODES) - p +=3D snprintf(p, buffer + maxlen - p, "relative"); + p =3D seprintf(p, e, "relative"); =20 if (flags & MPOL_F_NUMA_BALANCING) { if (!is_power_of_2(flags & MPOL_MODE_FLAGS)) - p +=3D snprintf(p, buffer + maxlen - p, "|"); - p +=3D snprintf(p, buffer + maxlen - p, "balancing"); + p =3D seprintf(p, e, "|"); + p =3D seprintf(p, e, "balancing"); } } =20 if (!nodes_empty(nodes)) - p +=3D scnprintf(p, buffer + maxlen - p, ":%*pbl", - nodemask_pr_args(&nodes)); + seprintf(p, e, ":%*pbl", nodemask_pr_args(&nodes)); } =20 #ifdef CONFIG_SYSFS diff --git a/mm/page_owner.c b/mm/page_owner.c index cc4a6916eec6..5811738e3320 100644 --- a/mm/page_owner.c +++ b/mm/page_owner.c @@ -496,7 +496,7 @@ void pagetypeinfo_showmixedcount_print(struct seq_file = *m, /* * Looking for memcg information and print it out */ -static inline int print_page_owner_memcg(char *kbuf, size_t count, int ret, +static inline char *print_page_owner_memcg(char *p, const char end[0], struct page *page) { #ifdef CONFIG_MEMCG @@ -511,8 +511,7 @@ static inline int print_page_owner_memcg(char *kbuf, si= ze_t count, int ret, goto out_unlock; =20 if (memcg_data & MEMCG_DATA_OBJEXTS) - ret +=3D scnprintf(kbuf + ret, count - ret, - "Slab cache page\n"); + p =3D seprintf(p, end, "Slab cache page\n"); =20 memcg =3D page_memcg_check(page); if (!memcg) @@ -520,7 +519,7 @@ static inline int print_page_owner_memcg(char *kbuf, si= ze_t count, int ret, =20 online =3D (memcg->css.flags & CSS_ONLINE); cgroup_name(memcg->css.cgroup, name, sizeof(name)); - ret +=3D scnprintf(kbuf + ret, count - ret, + p =3D seprintf(p, end, "Charged %sto %smemcg %s\n", PageMemcgKmem(page) ? "(via objcg) " : "", online ? "" : "offline ", @@ -529,7 +528,7 @@ static inline int print_page_owner_memcg(char *kbuf, si= ze_t count, int ret, rcu_read_unlock(); #endif /* CONFIG_MEMCG */ =20 - return ret; + return p; } =20 static ssize_t @@ -538,14 +537,16 @@ print_page_owner(char __user *buf, size_t count, unsi= gned long pfn, depot_stack_handle_t handle) { int ret, pageblock_mt, page_mt; - char *kbuf; + char *kbuf, *p, *e; =20 count =3D min_t(size_t, count, PAGE_SIZE); kbuf =3D kmalloc(count, GFP_KERNEL); if (!kbuf) return -ENOMEM; =20 - ret =3D scnprintf(kbuf, count, + p =3D kbuf; + e =3D kbuf + count; + p =3D seprintf(p, e, "Page allocated via order %u, mask %#x(%pGg), pid %d, tgid %d (%s), ts = %llu ns\n", page_owner->order, page_owner->gfp_mask, &page_owner->gfp_mask, page_owner->pid, @@ -555,7 +556,7 @@ print_page_owner(char __user *buf, size_t count, unsign= ed long pfn, /* Print information relevant to grouping pages by mobility */ pageblock_mt =3D get_pageblock_migratetype(page); page_mt =3D gfp_migratetype(page_owner->gfp_mask); - ret +=3D scnprintf(kbuf + ret, count - ret, + p =3D seprintf(p, e, "PFN 0x%lx type %s Block %lu type %s Flags %pGp\n", pfn, migratetype_names[page_mt], @@ -563,22 +564,23 @@ print_page_owner(char __user *buf, size_t count, unsi= gned long pfn, migratetype_names[pageblock_mt], &page->flags); =20 - ret +=3D stack_depot_snprint(handle, kbuf + ret, count - ret, 0); - if (ret >=3D count) - goto err; + p =3D stack_depot_seprint(handle, p, e, 0); + if (p =3D=3D NULL) + goto err; // XXX: Should we remove this error handling? =20 if (page_owner->last_migrate_reason !=3D -1) { - ret +=3D scnprintf(kbuf + ret, count - ret, + p =3D seprintf(p, e, "Page has been migrated, last migrate reason: %s\n", migrate_reason_names[page_owner->last_migrate_reason]); } =20 - ret =3D print_page_owner_memcg(kbuf, count, ret, page); + p =3D print_page_owner_memcg(p, e, page); =20 - ret +=3D snprintf(kbuf + ret, count - ret, "\n"); - if (ret >=3D count) + p =3D seprintf(p, e, "\n"); + if (p =3D=3D NULL) goto err; =20 + ret =3D p - kbuf; if (copy_to_user(buf, kbuf, ret)) ret =3D -EFAULT; =20 diff --git a/mm/slub.c b/mm/slub.c index be8b09e09d30..b67c6ca0d0f7 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -7451,6 +7451,7 @@ static char *create_unique_id(struct kmem_cache *s) { char *name =3D kmalloc(ID_STR_LENGTH, GFP_KERNEL); char *p =3D name; + char *e =3D name + ID_STR_LENGTH; =20 if (!name) return ERR_PTR(-ENOMEM); @@ -7475,9 +7476,9 @@ static char *create_unique_id(struct kmem_cache *s) *p++ =3D 'A'; if (p !=3D name + 1) *p++ =3D '-'; - p +=3D snprintf(p, ID_STR_LENGTH - (p - name), "%07u", s->size); + p =3D seprintf(p, e, "%07u", s->size); =20 - if (WARN_ON(p > name + ID_STR_LENGTH - 1)) { + if (WARN_ON(p =3D=3D NULL)) { kfree(name); return ERR_PTR(-EINVAL); } --=20 2.50.0 From nobody Tue Oct 7 19:50:29 2025 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (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 C7F9C264612; Mon, 7 Jul 2025 05:06:15 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751864775; cv=none; b=SqN4smxyAmnhzScKEq/OMdPmWiSF/4X2+2nev2jSzeROJ61RuIch3cnZjg2P3Uv4qnruwvJ9ABfnnoJSpXwZvFpCGjsASUbQ0pZnQkXnoilnsQ7+ogWPKbFzINqiN5+zzLfn0MubF2tBcUXkk05opXCMHjg0rFboAZLR8SUv2xU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751864775; c=relaxed/simple; bh=kVIe30VQ/GPPK/rhv9AuQ5VCmpa6qJ5Bp1dxjM4sv6Y=; h=Date:From:To:Cc:Subject:Message-ID:References:MIME-Version: Content-Type:Content-Disposition:In-Reply-To; b=RLlLWTQEqaCwGdzwkh/KXQ9ylVS6czZsh+oEoluWa8dp90N1vRI0DsqtMmAL+TAMhNnOUGK5p2RY773yNRUYKsb4h7KTSO0L32LmMmYLFf0YJJm04JTrRCLpB2L9SgIA8xZZPf9ccV78EA7ELmCBPPKdtRmGDr+f4w1SqS0zbNk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=rWb0ois3; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="rWb0ois3" Received: by smtp.kernel.org (Postfix) with ESMTPSA id CC2AEC4CEF9; Mon, 7 Jul 2025 05:06:14 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1751864775; bh=kVIe30VQ/GPPK/rhv9AuQ5VCmpa6qJ5Bp1dxjM4sv6Y=; h=Date:From:To:Cc:Subject:References:In-Reply-To:From; b=rWb0ois3NFzBE2GH+RlVu3hYmfrQ8fuH3x8CqDoOXTyrtAygWEct/pYcctBP28El1 ILNB5V6cEWR77rM1KuYlyBmvm1YixklNCKOGUBfBcYotD0WfmFe5RySkee6jnmhATa neP0/GQpNkRw+Jr4jLzhY919tGflrBfUXnp24QhRieMEdgMeUpt1g5DSrUR+3nmlwN KFHJdM2tw5uYdzB/0PCOmWoAo8+O9abOVDCNsecIV5T5t6zsXP10OoR+N8MYn+nD3f 9op5UmA4tALDcGD2xd6tJMxA4zyTIh9kJvx9lT2UZgXPNkouxB1VZJLfQc9VPS6Bk1 nkKlmluSCaYDQ== Date: Mon, 7 Jul 2025 07:06:13 +0200 From: Alejandro Colomar To: linux-mm@kvack.org, linux-hardening@vger.kernel.org Cc: Alejandro Colomar , Kees Cook , Christopher Bazley , shadow <~hallyn/shadow@lists.sr.ht>, linux-kernel@vger.kernel.org, Andrew Morton , kasan-dev@googlegroups.com, Dmitry Vyukov , Alexander Potapenko , Marco Elver , Christoph Lameter , David Rientjes , Vlastimil Babka , Roman Gushchin , Harry Yoo , Andrew Clayton Subject: [RFC v3 4/7] array_size.h: Add ENDOF() Message-ID: X-Mailer: git-send-email 2.50.0 References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This macro is useful to calculate the second argument to seprintf(), avoiding off-by-one bugs. Cc: Kees Cook Cc: Christopher Bazley Signed-off-by: Alejandro Colomar --- include/linux/array_size.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/linux/array_size.h b/include/linux/array_size.h index 06d7d83196ca..781bdb70d939 100644 --- a/include/linux/array_size.h +++ b/include/linux/array_size.h @@ -10,4 +10,10 @@ */ #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + __must_be_array(= arr)) =20 +/** + * ENDOF - get a pointer to one past the last element in array @a + * @a: array + */ +#define ENDOF(a) (a + ARRAY_SIZE(a)) + #endif /* _LINUX_ARRAY_SIZE_H */ --=20 2.50.0 From nobody Tue Oct 7 19:50:29 2025 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (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 16B8A26528F; Mon, 7 Jul 2025 05:06:18 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751864778; cv=none; b=ug04O5fpP+g81c2Xi2mjp7HZf/Y6GvvMJ5qGge6sbzw0aFb16UhXo9a9nlsrPqaR8xPDTsGS3TXtd3J4H1mheXAeg3CdZJ3Mbn4g1ktgUyyk9TqTMYjIZO5WwUlbbaqpkgssWar/zJijSpOnWpM6wAqs0vdQQlILtM70rdGjE5k= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751864778; c=relaxed/simple; bh=NHyBpShfNxuRT7LHWtM7yTeZwYuB33fj2KjVFAvh+ps=; h=Date:From:To:Cc:Subject:Message-ID:References:MIME-Version: Content-Type:Content-Disposition:In-Reply-To; b=NDMkkbLFB8bPJ7KK73JkO/cMp0641WwGDpty8MqzKhqkm1jxtx/v36T9YO96BYH33XykSr/29kMRdtSUohoUQIJuYRCF5ltrZku1wMx69pTG0LABOPJUr7B17khqm0+4zan/ttC+BRpuEwhUkTjVz/MKSoHERR8m9X4V8q+WZXY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=jZKs1LG3; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="jZKs1LG3" Received: by smtp.kernel.org (Postfix) with ESMTPSA id C0ED7C4CEF3; Mon, 7 Jul 2025 05:06:16 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1751864778; bh=NHyBpShfNxuRT7LHWtM7yTeZwYuB33fj2KjVFAvh+ps=; h=Date:From:To:Cc:Subject:References:In-Reply-To:From; b=jZKs1LG3kb0nVPIQgR1hXQuPVNnzQN5m23O/yq0nZ9XTycpu03iDudYeDmtJnPC+a wJgFLf2bbflGQtVT2GqtWxcUwJQ8oGWUfXCjXy3FbAZ4LTGKK0WD0RH1NGH2XaXBja eg9kUR6n8QaU7IzjMtYDJ8qD7iT952AAkB636Zym1YRRB5auZKCCpBOoT8vZA+KCd3 j03WUE5Lr3EEzWfAtxzlWNiA1p+bAJGzhmBMXzFe0Ll04XxVG2Fy2ghiFpP4Kn33Sb SoqZbtPvMJv08ueHyE/wR+2fJ9luwRlrBU3PN4DxETUsJSYV0gXtukozRbdrIDN3RQ 7PJ07tG+x8ihw== Date: Mon, 7 Jul 2025 07:06:15 +0200 From: Alejandro Colomar To: linux-mm@kvack.org, linux-hardening@vger.kernel.org Cc: Alejandro Colomar , Kees Cook , Christopher Bazley , shadow <~hallyn/shadow@lists.sr.ht>, linux-kernel@vger.kernel.org, Andrew Morton , kasan-dev@googlegroups.com, Dmitry Vyukov , Alexander Potapenko , Marco Elver , Christoph Lameter , David Rientjes , Vlastimil Babka , Roman Gushchin , Harry Yoo , Andrew Clayton , Jann Horn , Linus Torvalds Subject: [RFC v3 5/7] mm: Fix benign off-by-one bugs Message-ID: <740755c1a888ae27de3f127c27bf925a91e9b264.1751862634.git.alx@kernel.org> X-Mailer: git-send-email 2.50.0 References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" We were wasting a byte due to an off-by-one bug. s[c]nprintf() doesn't write more than $2 bytes including the null byte, so trying to pass 'size-1' there is wasting one byte. Now that we use seprintf(), the situation isn't different: seprintf() will stop writing *before* 'end' --that is, at most the terminating null byte will be written at 'end-1'--. Fixes: bc8fbc5f305a (2021-02-26; "kfence: add test suite") Fixes: 8ed691b02ade (2022-10-03; "kmsan: add tests for KMSAN") Cc: Kees Cook Cc: Christopher Bazley Cc: Alexander Potapenko Cc: Marco Elver Cc: Dmitry Vyukov Cc: Alexander Potapenko Cc: Jann Horn Cc: Andrew Morton Cc: Linus Torvalds Signed-off-by: Alejandro Colomar Acked-by: Marco Elver --- mm/kfence/kfence_test.c | 4 ++-- mm/kmsan/kmsan_test.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mm/kfence/kfence_test.c b/mm/kfence/kfence_test.c index ff734c514c03..f02c3e23638a 100644 --- a/mm/kfence/kfence_test.c +++ b/mm/kfence/kfence_test.c @@ -110,7 +110,7 @@ static bool report_matches(const struct expect_report *= r) =20 /* Title */ cur =3D expect[0]; - end =3D &expect[0][sizeof(expect[0]) - 1]; + end =3D ENDOF(expect[0]); switch (r->type) { case KFENCE_ERROR_OOB: cur =3D seprintf(cur, end, "BUG: KFENCE: out-of-bounds %s", @@ -140,7 +140,7 @@ static bool report_matches(const struct expect_report *= r) =20 /* Access information */ cur =3D expect[1]; - end =3D &expect[1][sizeof(expect[1]) - 1]; + end =3D ENDOF(expect[1]); =20 switch (r->type) { case KFENCE_ERROR_OOB: diff --git a/mm/kmsan/kmsan_test.c b/mm/kmsan/kmsan_test.c index a062a46b2d24..882500807db8 100644 --- a/mm/kmsan/kmsan_test.c +++ b/mm/kmsan/kmsan_test.c @@ -105,7 +105,7 @@ static bool report_matches(const struct expect_report *= r) =20 /* Title */ cur =3D expected_header; - end =3D &expected_header[sizeof(expected_header) - 1]; + end =3D ENDOF(expected_header); =20 cur =3D seprintf(cur, end, "BUG: KMSAN: %s", r->error_type); =20 --=20 2.50.0 From nobody Tue Oct 7 19:50:29 2025 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (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 5949E265CA7; Mon, 7 Jul 2025 05:06:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751864780; cv=none; b=FsSd+CsjEgBzdDxSpduK7ne02bNl1V0U9h4dLduPnuiC5yp8SvNRr3GtnEyF1sSuzn6bMVBfxD4lU7pgE8B7KMWEC8U35QWKhETer6LzoNgYnkS43Wd44htphEQaNY0P8Fytzqd2tCIpdnTpt9imJh/6Gdy6otY/eq1AJY8UcuU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751864780; c=relaxed/simple; bh=tcpGh4dHgjGALEI/7Qt9HZzvd2bDQOcYewOF2ERkXuM=; h=Date:From:To:Cc:Subject:Message-ID:References:MIME-Version: Content-Type:Content-Disposition:In-Reply-To; b=SXlJcDLX6TtYVYihJZmj5j/nrKiTzF7/ry/s8ziHV4jhvaM0PUoquIExv9Nop46YqCtrHFnca5JAZXIA3YUpYhwk507PR8dhMo4H3XUIC+Y57rxY5wnBIPOY+CVZnewaXn6F7kTjV8oBkvMMtubRRlb6sUVRfJMm2RRz2X58z0k= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=VVDyUZ1Z; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="VVDyUZ1Z" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 19BC6C4CEE3; Mon, 7 Jul 2025 05:06:19 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1751864780; bh=tcpGh4dHgjGALEI/7Qt9HZzvd2bDQOcYewOF2ERkXuM=; h=Date:From:To:Cc:Subject:References:In-Reply-To:From; b=VVDyUZ1ZiTo6UBehB938a6N3GMEKaKiHMvld72jrlj1TqZ3xCOzo3JKajIzRjPTvU YcxQxX924fozYDgLqQfVrlKDrNL5Cu4+cOKGTsFIXwdpOa0focWXfCy2i6VaAPGgwK MnmrnuWsHxG+jZOhrIxkfPYYMvOmeL1J5BZWkzk7vNg4j3DWn9FmfZ4a5aEmKK6nKl GXdyl8dBz5++Jjn7PuZ1r4Pgr2GSp+jJwn6dqQTlr+IsAo1PQDLUFzDcJ3qbHq7ZqP eQyN+arIdJ3Z/7/3VIFTRQ9/8Lju3Rh0pzz8rmIJ7UuMhp1WnN5u3sWZKApD/LjvPt ZxuY4Iblzr+WA== Date: Mon, 7 Jul 2025 07:06:18 +0200 From: Alejandro Colomar To: linux-mm@kvack.org, linux-hardening@vger.kernel.org Cc: Alejandro Colomar , Kees Cook , Christopher Bazley , shadow <~hallyn/shadow@lists.sr.ht>, linux-kernel@vger.kernel.org, Andrew Morton , kasan-dev@googlegroups.com, Dmitry Vyukov , Alexander Potapenko , Marco Elver , Christoph Lameter , David Rientjes , Vlastimil Babka , Roman Gushchin , Harry Yoo , Andrew Clayton Subject: [RFC v3 6/7] sprintf: Add [V]STPRINTF() Message-ID: <44d05559398c523d119afecdb3e748d37433fe9e.1751862634.git.alx@kernel.org> X-Mailer: git-send-email 2.50.0 References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" These macros take the array size argument implicitly to avoid programmer mistakes. This guarantees that the input is an array, unlike the common call snprintf(buf, sizeof(buf), ...); which is dangerous if the programmer passes a pointer. These macros are essentially the same as the 2-argument version of strscpy(), but with a formatted string. Signed-off-by: Alejandro Colomar --- include/linux/sprintf.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/linux/sprintf.h b/include/linux/sprintf.h index c3dbfd2efd2b..6080d3732055 100644 --- a/include/linux/sprintf.h +++ b/include/linux/sprintf.h @@ -4,6 +4,10 @@ =20 #include #include +#include + +#define STPRINTF(a, fmt, ...) stprintf(a, ARRAY_SIZE(a), fmt, ##__VA_ARGS= __) +#define VSTPRINTF(a, fmt, ap) vstprintf(a, ARRAY_SIZE(a), fmt, ap) =20 int num_to_str(char *buf, int size, unsigned long long num, unsigned int w= idth); =20 --=20 2.50.0 From nobody Tue Oct 7 19:50:29 2025 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (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 E72BC266B56; Mon, 7 Jul 2025 05:06:22 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751864783; cv=none; b=m3nYRsldKr8kroXqw6HHUz6euV6Qp9eYrbKUrQ7yPhE76Mud7CJzJDlspWHFcPBDp+YJIW5ETdNklUDX8k2pyDdp3fTUIAOalVGy+FzBip+pZ7ktPp0XB7klXxjWrPZ/CUMQOFOxF43tHsr4MlHjvySIiBE0qHdkEDXEa33hhJM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1751864783; c=relaxed/simple; bh=EaXLT+CGpKJNtEk9hexswkbKNeTZZeAMwu/sC3gXADA=; h=Date:From:To:Cc:Subject:Message-ID:References:MIME-Version: Content-Type:Content-Disposition:In-Reply-To; b=XV32xRtP0Swy2vkH1gip9fhL5fH0jQJ5m1+pMHegMDnpDU0ds7XC6ETSLvEYhq1I9s506Bfm8mOtwRqUu6C3XZIXPoiZAnmseB3zjxS8abBwHeTrg3ytJcT2yqfgIWG1kdXJs6WxTGhBo4ueunye+q4uO+XdEzVeh6nRYupjqLU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=SSvChMM4; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="SSvChMM4" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 29016C4CEE3; Mon, 7 Jul 2025 05:06:21 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1751864782; bh=EaXLT+CGpKJNtEk9hexswkbKNeTZZeAMwu/sC3gXADA=; h=Date:From:To:Cc:Subject:References:In-Reply-To:From; b=SSvChMM4SgLmmvpULy0SkRZpz2mCGzmQiZ8Ov/5O+ovn83peJjLqhVkQNdCGRJK5j lfIbYmewgE/X7QNfQ1MTOnp9eL/u3seltD5/ZWTZ6HKRlTk1gAFwH+JDx5sNDzorVg BXW8TW7ptQlP037WO8II4SojdlIBY+vwgBllRSWTfhJWRXoNyb0WTpQx1K7avcUCU7 eePzPHAMxzgKBarPvVNcDaezX70fQRrut1P+kr1nnOv4qhVCI/+W9HkNosA2O+edKn FKF0PPLI8tKDeouni/npT35VK23IzGevvZLThoE5CchDBx0gwfjMLoicGCYvnP+Ea+ pi3RaoTCPuJtQ== Date: Mon, 7 Jul 2025 07:06:20 +0200 From: Alejandro Colomar To: linux-mm@kvack.org, linux-hardening@vger.kernel.org Cc: Alejandro Colomar , Kees Cook , Christopher Bazley , shadow <~hallyn/shadow@lists.sr.ht>, linux-kernel@vger.kernel.org, Andrew Morton , kasan-dev@googlegroups.com, Dmitry Vyukov , Alexander Potapenko , Marco Elver , Christoph Lameter , David Rientjes , Vlastimil Babka , Roman Gushchin , Harry Yoo , Andrew Clayton Subject: [RFC v3 7/7] mm: Use [V]STPRINTF() to avoid specifying the array size Message-ID: X-Mailer: git-send-email 2.50.0 References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Signed-off-by: Alejandro Colomar --- mm/backing-dev.c | 2 +- mm/cma.c | 4 ++-- mm/cma_debug.c | 2 +- mm/hugetlb.c | 3 +-- mm/hugetlb_cgroup.c | 2 +- mm/hugetlb_cma.c | 2 +- mm/kasan/report.c | 3 +-- mm/memblock.c | 4 ++-- mm/percpu.c | 2 +- mm/shrinker_debug.c | 2 +- mm/zswap.c | 2 +- 11 files changed, 13 insertions(+), 15 deletions(-) diff --git a/mm/backing-dev.c b/mm/backing-dev.c index 783904d8c5ef..408fdf52ee5d 100644 --- a/mm/backing-dev.c +++ b/mm/backing-dev.c @@ -1090,7 +1090,7 @@ int bdi_register_va(struct backing_dev_info *bdi, con= st char *fmt, va_list args) if (bdi->dev) /* The driver needs to use separate queues per device */ return 0; =20 - vsnprintf(bdi->dev_name, sizeof(bdi->dev_name), fmt, args); + VSTPRINTF(bdi->dev_name, fmt, args); dev =3D device_create(&bdi_class, NULL, MKDEV(0, 0), bdi, bdi->dev_name); if (IS_ERR(dev)) return PTR_ERR(dev); diff --git a/mm/cma.c b/mm/cma.c index c04be488b099..49c54a74d6ce 100644 --- a/mm/cma.c +++ b/mm/cma.c @@ -237,9 +237,9 @@ static int __init cma_new_area(const char *name, phys_a= ddr_t size, cma_area_count++; =20 if (name) - snprintf(cma->name, CMA_MAX_NAME, "%s", name); + STPRINTF(cma->name, "%s", name); else - snprintf(cma->name, CMA_MAX_NAME, "cma%d\n", cma_area_count); + STPRINTF(cma->name, "cma%d\n", cma_area_count); =20 cma->available_count =3D cma->count =3D size >> PAGE_SHIFT; cma->order_per_bit =3D order_per_bit; diff --git a/mm/cma_debug.c b/mm/cma_debug.c index fdf899532ca0..ae94b7ae6710 100644 --- a/mm/cma_debug.c +++ b/mm/cma_debug.c @@ -186,7 +186,7 @@ static void cma_debugfs_add_one(struct cma *cma, struct= dentry *root_dentry) rangedir =3D debugfs_create_dir("ranges", tmp); for (r =3D 0; r < cma->nranges; r++) { cmr =3D &cma->ranges[r]; - snprintf(rdirname, sizeof(rdirname), "%d", r); + STPRINTF(rdirname, "%d", r); dir =3D debugfs_create_dir(rdirname, rangedir); debugfs_create_file("base_pfn", 0444, dir, &cmr->base_pfn, &cma_debugfs_fops); diff --git a/mm/hugetlb.c b/mm/hugetlb.c index 6a3cf7935c14..6d0bd88eeba9 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -4780,8 +4780,7 @@ void __init hugetlb_add_hstate(unsigned int order) for (i =3D 0; i < MAX_NUMNODES; ++i) INIT_LIST_HEAD(&h->hugepage_freelists[i]); INIT_LIST_HEAD(&h->hugepage_activelist); - snprintf(h->name, HSTATE_NAME_LEN, "hugepages-%lukB", - huge_page_size(h)/SZ_1K); + STPRINTF(h->name, "hugepages-%lukB", huge_page_size(h)/SZ_1K); =20 parsed_hstate =3D h; } diff --git a/mm/hugetlb_cgroup.c b/mm/hugetlb_cgroup.c index 58e895f3899a..8f5ffe35d16d 100644 --- a/mm/hugetlb_cgroup.c +++ b/mm/hugetlb_cgroup.c @@ -822,7 +822,7 @@ hugetlb_cgroup_cfttypes_init(struct hstate *h, struct c= ftype *cft, for (i =3D 0; i < tmpl_size; cft++, tmpl++, i++) { *cft =3D *tmpl; /* rebuild the name */ - snprintf(cft->name, MAX_CFTYPE_NAME, "%s.%s", buf, tmpl->name); + STPRINTF(cft->name, "%s.%s", buf, tmpl->name); /* rebuild the private */ cft->private =3D MEMFILE_PRIVATE(idx, tmpl->private); /* rebuild the file_offset */ diff --git a/mm/hugetlb_cma.c b/mm/hugetlb_cma.c index e0f2d5c3a84c..c28d09e0ce68 100644 --- a/mm/hugetlb_cma.c +++ b/mm/hugetlb_cma.c @@ -211,7 +211,7 @@ void __init hugetlb_cma_reserve(int order) =20 size =3D round_up(size, PAGE_SIZE << order); =20 - snprintf(name, sizeof(name), "hugetlb%d", nid); + STPRINTF(name, "hugetlb%d", nid); /* * Note that 'order per bit' is based on smallest size that * may be returned to CMA allocator in the case of diff --git a/mm/kasan/report.c b/mm/kasan/report.c index 8357e1a33699..62a9bcff236a 100644 --- a/mm/kasan/report.c +++ b/mm/kasan/report.c @@ -486,8 +486,7 @@ static void print_memory_metadata(const void *addr) char buffer[4 + (BITS_PER_LONG / 8) * 2]; char metadata[META_BYTES_PER_ROW]; =20 - snprintf(buffer, sizeof(buffer), - (i =3D=3D 0) ? ">%px: " : " %px: ", row); + STPRINTF(buffer, (i =3D=3D 0) ? ">%px: " : " %px: ", row); =20 /* * We should not pass a shadow pointer to generic diff --git a/mm/memblock.c b/mm/memblock.c index 0e9ebb8aa7fe..20d3928a6b13 100644 --- a/mm/memblock.c +++ b/mm/memblock.c @@ -2021,7 +2021,7 @@ static void __init_memblock memblock_dump(struct memb= lock_type *type) flags =3D rgn->flags; #ifdef CONFIG_NUMA if (numa_valid_node(memblock_get_region_node(rgn))) - snprintf(nid_buf, sizeof(nid_buf), " on node %d", + STPRINTF(nid_buf, " on node %d", memblock_get_region_node(rgn)); #endif pr_info(" %s[%#x]\t[%pa-%pa], %pa bytes%s flags: %#x\n", @@ -2379,7 +2379,7 @@ int reserve_mem_release_by_name(const char *name) =20 start =3D phys_to_virt(map->start); end =3D start + map->size - 1; - snprintf(buf, sizeof(buf), "reserve_mem:%s", name); + STPRINTF(buf, "reserve_mem:%s", name); free_reserved_area(start, end, 0, buf); map->size =3D 0; =20 diff --git a/mm/percpu.c b/mm/percpu.c index b35494c8ede2..8d5b5ac7dbef 100644 --- a/mm/percpu.c +++ b/mm/percpu.c @@ -3186,7 +3186,7 @@ int __init pcpu_page_first_chunk(size_t reserved_size= , pcpu_fc_cpu_to_node_fn_t int upa; int nr_g0_units; =20 - snprintf(psize_str, sizeof(psize_str), "%luK", PAGE_SIZE >> 10); + STPRINTF(psize_str, "%luK", PAGE_SIZE >> 10); =20 ai =3D pcpu_build_alloc_info(reserved_size, 0, PAGE_SIZE, NULL); if (IS_ERR(ai)) diff --git a/mm/shrinker_debug.c b/mm/shrinker_debug.c index 20eaee3e97f7..7194f2de8594 100644 --- a/mm/shrinker_debug.c +++ b/mm/shrinker_debug.c @@ -176,7 +176,7 @@ int shrinker_debugfs_add(struct shrinker *shrinker) return id; shrinker->debugfs_id =3D id; =20 - snprintf(buf, sizeof(buf), "%s-%d", shrinker->name, id); + STPRINTF(buf, "%s-%d", shrinker->name, id); =20 /* create debugfs entry */ entry =3D debugfs_create_dir(buf, shrinker_debugfs_root); diff --git a/mm/zswap.c b/mm/zswap.c index 204fb59da33c..01c96cb5e84f 100644 --- a/mm/zswap.c +++ b/mm/zswap.c @@ -271,7 +271,7 @@ static struct zswap_pool *zswap_pool_create(char *type,= char *compressor) return NULL; =20 /* unique name for each pool specifically required by zsmalloc */ - snprintf(name, 38, "zswap%x", atomic_inc_return(&zswap_pools_count)); + STPRINTF(name, "zswap%x", atomic_inc_return(&zswap_pools_count)); pool->zpool =3D zpool_create_pool(type, name, gfp); if (!pool->zpool) { pr_err("%s zpool not available\n", type); --=20 2.50.0