public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/3] tools/nolibc: add support for asprintf()
@ 2026-04-01 15:07 Thomas Weißschuh
  2026-04-01 15:07 ` [PATCH 1/3] tools/nolibc: use __builtin_offsetof() Thomas Weißschuh
                   ` (2 more replies)
  0 siblings, 3 replies; 10+ messages in thread
From: Thomas Weißschuh @ 2026-04-01 15:07 UTC (permalink / raw)
  To: Willy Tarreau; +Cc: linux-kernel, Thomas Weißschuh

Add support for dynamically allocating formatted strings through
asprintf() and vasprintf().

Plus some preparatory patches.

Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
---
Thomas Weißschuh (3):
      tools/nolibc: use __builtin_offsetof()
      selftests/nolibc: test the memory allocator
      tools/nolibc: add support for asprintf()

 tools/include/nolibc/stddef.h                |  2 +-
 tools/include/nolibc/stdio.h                 | 50 ++++++++++++++++++++++++++++
 tools/testing/selftests/nolibc/nolibc-test.c | 49 +++++++++++++++++++++++++++
 3 files changed, 100 insertions(+), 1 deletion(-)
---
base-commit: 6285f0881ec68034399d13552f7243e69e6e37bf
change-id: 20260401-nolibc-asprintf-a0a8b6115dc5

Best regards,
--  
Thomas Weißschuh <linux@weissschuh.net>


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH 1/3] tools/nolibc: use __builtin_offsetof()
  2026-04-01 15:07 [PATCH 0/3] tools/nolibc: add support for asprintf() Thomas Weißschuh
@ 2026-04-01 15:07 ` Thomas Weißschuh
  2026-04-04  8:34   ` Willy Tarreau
  2026-04-01 15:07 ` [PATCH 2/3] selftests/nolibc: test the memory allocator Thomas Weißschuh
  2026-04-01 15:07 ` [PATCH 3/3] tools/nolibc: add support for asprintf() Thomas Weißschuh
  2 siblings, 1 reply; 10+ messages in thread
From: Thomas Weißschuh @ 2026-04-01 15:07 UTC (permalink / raw)
  To: Willy Tarreau; +Cc: linux-kernel, Thomas Weißschuh

The current custom implementation of offsetof() fails UBSAN:
runtime error: member access within null pointer of type 'struct ...'
This means that all its users, including container_of(), free() and
realloc(), fail.

Use __builtin_offsetof() instead which does not have this issue and
has been available since GCC 4 and clang 4.

Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
---
 tools/include/nolibc/stddef.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tools/include/nolibc/stddef.h b/tools/include/nolibc/stddef.h
index ecbd13eab1f5..a3976341afdd 100644
--- a/tools/include/nolibc/stddef.h
+++ b/tools/include/nolibc/stddef.h
@@ -18,7 +18,7 @@
 #endif
 
 #ifndef offsetof
-#define offsetof(TYPE, FIELD) ((size_t) &((TYPE *)0)->FIELD)
+#define offsetof(TYPE, FIELD) __builtin_offsetof(TYPE, FIELD)
 #endif
 
 #endif /* _NOLIBC_STDDEF_H */

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 2/3] selftests/nolibc: test the memory allocator
  2026-04-01 15:07 [PATCH 0/3] tools/nolibc: add support for asprintf() Thomas Weißschuh
  2026-04-01 15:07 ` [PATCH 1/3] tools/nolibc: use __builtin_offsetof() Thomas Weißschuh
@ 2026-04-01 15:07 ` Thomas Weißschuh
  2026-04-04  8:51   ` Willy Tarreau
  2026-04-01 15:07 ` [PATCH 3/3] tools/nolibc: add support for asprintf() Thomas Weißschuh
  2 siblings, 1 reply; 10+ messages in thread
From: Thomas Weißschuh @ 2026-04-01 15:07 UTC (permalink / raw)
  To: Willy Tarreau; +Cc: linux-kernel, Thomas Weißschuh

The memory allocator has not seen any testing so far.

Add a simple testcase for it.

Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
---
 tools/testing/selftests/nolibc/nolibc-test.c | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c
index 1efd10152e83..c888e13c6bd8 100644
--- a/tools/testing/selftests/nolibc/nolibc-test.c
+++ b/tools/testing/selftests/nolibc/nolibc-test.c
@@ -1554,6 +1554,30 @@ int test_time_types(void)
 	return 0;
 }
 
+int test_malloc(void)
+{
+	void *ptr1, *ptr2, *ptr3;
+
+	ptr1 = malloc(100);
+	if (!ptr1)
+		return 1;
+
+	ptr2 = realloc(ptr1, 200);
+	if (!ptr2) {
+		free(ptr1);
+		return 2;
+	}
+
+	ptr3 = realloc(ptr2, 2 * getpagesize());
+	if (!ptr3) {
+		free(ptr2);
+		return 3;
+	}
+
+	free(ptr3);
+	return 0;
+}
+
 int run_stdlib(int min, int max)
 {
 	int test;
@@ -1680,6 +1704,7 @@ int run_stdlib(int min, int max)
 		CASE_TEST(memchr_foobar6_o);        EXPECT_STREQ(1, memchr("foobar", 'o', 6), "oobar"); break;
 		CASE_TEST(memchr_foobar3_b);        EXPECT_STRZR(1, memchr("foobar", 'b', 3)); break;
 		CASE_TEST(time_types);              EXPECT_ZR(is_nolibc, test_time_types()); break;
+		CASE_TEST(malloc);                  EXPECT_ZR(1, test_malloc()); break;
 
 		case __LINE__:
 			return ret; /* must be last */

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 3/3] tools/nolibc: add support for asprintf()
  2026-04-01 15:07 [PATCH 0/3] tools/nolibc: add support for asprintf() Thomas Weißschuh
  2026-04-01 15:07 ` [PATCH 1/3] tools/nolibc: use __builtin_offsetof() Thomas Weißschuh
  2026-04-01 15:07 ` [PATCH 2/3] selftests/nolibc: test the memory allocator Thomas Weißschuh
@ 2026-04-01 15:07 ` Thomas Weißschuh
  2026-04-04  8:53   ` Willy Tarreau
  2026-04-04 15:34   ` David Laight
  2 siblings, 2 replies; 10+ messages in thread
From: Thomas Weißschuh @ 2026-04-01 15:07 UTC (permalink / raw)
  To: Willy Tarreau; +Cc: linux-kernel, Thomas Weißschuh

Add support for dynamically allocating formatted strings through
asprintf() and vasprintf().

Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
---
 tools/include/nolibc/stdio.h                 | 50 ++++++++++++++++++++++++++++
 tools/testing/selftests/nolibc/nolibc-test.c | 24 +++++++++++++
 2 files changed, 74 insertions(+)

diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h
index 8f7e1948a651..1c9287b558f0 100644
--- a/tools/include/nolibc/stdio.h
+++ b/tools/include/nolibc/stdio.h
@@ -787,6 +787,56 @@ int sprintf(char *buf, const char *fmt, ...)
 	return ret;
 }
 
+static __attribute__((unused, format(printf, 2, 0)))
+int __nolibc_vasprintf(char **strp, const char *fmt, va_list args1, va_list args2)
+{
+	char *buf;
+	int len;
+
+	len = vsnprintf(NULL, 0, fmt, args1);
+	if (len < 0)
+		return -1;
+
+	buf = malloc(len + 1);
+	if (!buf)
+		return -1;
+
+	len = vsnprintf(buf, len + 1, fmt, args2);
+	if (len < 0) {
+		free(buf);
+		return -1;
+	}
+
+	*strp = buf;
+	return len;
+}
+
+static __attribute__((unused, format(printf, 2, 0)))
+int vasprintf(char **strp, const char *fmt, va_list args)
+{
+	va_list args2;
+	int ret;
+
+	va_copy(args2, args);
+	ret = __nolibc_vasprintf(strp, fmt, args, args2);
+	va_end(args2);
+
+	return ret;
+}
+
+static __attribute__((unused, format(printf, 2, 3)))
+int asprintf(char **strp, const char *fmt, ...)
+{
+	va_list args;
+	int ret;
+
+	va_start(args, fmt);
+	ret = vasprintf(strp, fmt, args);
+	va_end(args);
+
+	return ret;
+}
+
 static __attribute__((unused))
 int vsscanf(const char *str, const char *format, va_list args)
 {
diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c
index c888e13c6bd8..98070b805e49 100644
--- a/tools/testing/selftests/nolibc/nolibc-test.c
+++ b/tools/testing/selftests/nolibc/nolibc-test.c
@@ -1859,6 +1859,29 @@ static int test_printf_error(void)
 	return 0;
 }
 
+int test_asprintf(void)
+{
+	char *str;
+	int ret;
+
+	ret = asprintf(&str, "foo%s", "bar");
+	if (ret == -1)
+		return 1;
+
+	if (ret != 6) {
+		free(str);
+		return 2;
+	}
+
+	if (memcmp(str, "foobar", 6) != 0) {
+		free(str);
+		return 3;
+	}
+
+	free(str);
+	return 0;
+}
+
 static int run_printf(int min, int max)
 {
 	int test;
@@ -1921,6 +1944,7 @@ static int run_printf(int min, int max)
 		CASE_TEST(errno-neg);    errno = -22; EXPECT_VFPRINTF(is_nolibc, "errno=-22   ", "%-12m"); break;
 		CASE_TEST(scanf);        EXPECT_ZR(1, test_scanf()); break;
 		CASE_TEST(printf_error); EXPECT_ZR(1, test_printf_error()); break;
+		CASE_TEST(asprintf);     EXPECT_ZR(1, test_asprintf()); break;
 		case __LINE__:
 			return ret; /* must be last */
 		/* note: do not set any defaults so as to permit holes above */

-- 
2.53.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [PATCH 1/3] tools/nolibc: use __builtin_offsetof()
  2026-04-01 15:07 ` [PATCH 1/3] tools/nolibc: use __builtin_offsetof() Thomas Weißschuh
@ 2026-04-04  8:34   ` Willy Tarreau
  2026-04-04 15:29     ` David Laight
  0 siblings, 1 reply; 10+ messages in thread
From: Willy Tarreau @ 2026-04-04  8:34 UTC (permalink / raw)
  To: Thomas Weißschuh; +Cc: linux-kernel

Hi Thomas,

On Wed, Apr 01, 2026 at 05:07:27PM +0200, Thomas Weißschuh wrote:
> The current custom implementation of offsetof() fails UBSAN:
> runtime error: member access within null pointer of type 'struct ...'
> This means that all its users, including container_of(), free() and
> realloc(), fail.
> 
> Use __builtin_offsetof() instead which does not have this issue and
> has been available since GCC 4 and clang 4.

Yeah, that's a place where I find the standard ambiguous and ridiculously
absurd (since there's no dereference, only an address calculations), but I
had to do the same in haproxy recently for the same reasons, and I didn't
remember that we had it in nolibc as well. So that's an obvious ack!

Acked-by: Willy Tarreau <w@1wt.eu>

thanks,
Willy

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH 2/3] selftests/nolibc: test the memory allocator
  2026-04-01 15:07 ` [PATCH 2/3] selftests/nolibc: test the memory allocator Thomas Weißschuh
@ 2026-04-04  8:51   ` Willy Tarreau
  0 siblings, 0 replies; 10+ messages in thread
From: Willy Tarreau @ 2026-04-04  8:51 UTC (permalink / raw)
  To: Thomas Weißschuh; +Cc: linux-kernel

On Wed, Apr 01, 2026 at 05:07:28PM +0200, Thomas Weißschuh wrote:
> The memory allocator has not seen any testing so far.

Oh indeed, I thought we already had such a test!

> Add a simple testcase for it.
> 
> Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
> ---
>  tools/testing/selftests/nolibc/nolibc-test.c | 25 +++++++++++++++++++++++++
>  1 file changed, 25 insertions(+)
> 
> diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c
> index 1efd10152e83..c888e13c6bd8 100644
> --- a/tools/testing/selftests/nolibc/nolibc-test.c
> +++ b/tools/testing/selftests/nolibc/nolibc-test.c
> @@ -1554,6 +1554,30 @@ int test_time_types(void)
>  	return 0;
>  }
>  
> +int test_malloc(void)
> +{
> +	void *ptr1, *ptr2, *ptr3;
> +
> +	ptr1 = malloc(100);
> +	if (!ptr1)
> +		return 1;
> +
> +	ptr2 = realloc(ptr1, 200);
> +	if (!ptr2) {
> +		free(ptr1);
> +		return 2;
> +	}
> +
> +	ptr3 = realloc(ptr2, 2 * getpagesize());
> +	if (!ptr3) {
> +		free(ptr2);
> +		return 3;
> +	}
> +
> +	free(ptr3);
> +	return 0;
> +}
> +

I think we could enhance the test, because there are two key points
that it doesn't cover:
  - realloc must preserve contents
  - we need to make sure that more than one area exists (e.g. a naive
    implementation always returning the same pointer to a buffer would
    succeed).

What about something like this which would test malloc/calloc/realloc
and free (untested, sorry if I got some indexes wrong, but you get the
idea) ?

int test_malloc(void)
{
	int *array1, *array2, *array3;
	int idx;

	/* 1000 to allocate less than a page */
	array1 = malloc(1000 * sizeof(*array1));
	if (!array1)
		return 1;
	for (idx = 0; idx < 1000; idx++)
		array1[idx] = idx;

	/* 2000 to allocate more than a page */
	array2 = calloc(2000, sizeof(*array2));
	if (!array2) {
		free(array1);
		return 2;
	}
	for (idx = 0; idx < 2000; idx++)
		array1[idx] = idx + 1000;

	/* resize array1 into array3 and append array2 at the end,
	 * this requires 3 pages. On success, array1 is freed.
	 */
	array3 = realloc(array1, 3000 * sizeof(*array3));
	if (!array3) {
		free(array2);
		free(array1);
		return 3;
	}
	memcpy(array3 + 1000, array2, sizeof(*array2) * 2000);
	free(array2);

	/* the contents must be contiguous now */
	for (idx = 0; idx < 3000; idx++)
		if (array3[idx] != idx)
			return 4;
	free(array3);
	return 0;
}

>  int run_stdlib(int min, int max)
>  {
>  	int test;
> @@ -1680,6 +1704,7 @@ int run_stdlib(int min, int max)
>  		CASE_TEST(memchr_foobar6_o);        EXPECT_STREQ(1, memchr("foobar", 'o', 6), "oobar"); break;
>  		CASE_TEST(memchr_foobar3_b);        EXPECT_STRZR(1, memchr("foobar", 'b', 3)); break;
>  		CASE_TEST(time_types);              EXPECT_ZR(is_nolibc, test_time_types()); break;
> +		CASE_TEST(malloc);                  EXPECT_ZR(1, test_malloc()); break;
>  
>  		case __LINE__:
>  			return ret; /* must be last */

So it's as you prefer, in any case, I'm obviously OK with the extra test.

Acked-by: Willy Tarreau <w@1wt.eu>

Willy

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH 3/3] tools/nolibc: add support for asprintf()
  2026-04-01 15:07 ` [PATCH 3/3] tools/nolibc: add support for asprintf() Thomas Weißschuh
@ 2026-04-04  8:53   ` Willy Tarreau
  2026-04-04 15:34   ` David Laight
  1 sibling, 0 replies; 10+ messages in thread
From: Willy Tarreau @ 2026-04-04  8:53 UTC (permalink / raw)
  To: Thomas Weißschuh; +Cc: linux-kernel

On Wed, Apr 01, 2026 at 05:07:29PM +0200, Thomas Weißschuh wrote:
> Add support for dynamically allocating formatted strings through
> asprintf() and vasprintf().
> 
> Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>

I see how this can be convenient in test or init tools.

Acked-by: Willy Tarreau <w@1wt.eu>
Willy

> ---
>  tools/include/nolibc/stdio.h                 | 50 ++++++++++++++++++++++++++++
>  tools/testing/selftests/nolibc/nolibc-test.c | 24 +++++++++++++
>  2 files changed, 74 insertions(+)
> 
> diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h
> index 8f7e1948a651..1c9287b558f0 100644
> --- a/tools/include/nolibc/stdio.h
> +++ b/tools/include/nolibc/stdio.h
> @@ -787,6 +787,56 @@ int sprintf(char *buf, const char *fmt, ...)
>  	return ret;
>  }
>  
> +static __attribute__((unused, format(printf, 2, 0)))
> +int __nolibc_vasprintf(char **strp, const char *fmt, va_list args1, va_list args2)
> +{
> +	char *buf;
> +	int len;
> +
> +	len = vsnprintf(NULL, 0, fmt, args1);
> +	if (len < 0)
> +		return -1;
> +
> +	buf = malloc(len + 1);
> +	if (!buf)
> +		return -1;
> +
> +	len = vsnprintf(buf, len + 1, fmt, args2);
> +	if (len < 0) {
> +		free(buf);
> +		return -1;
> +	}
> +
> +	*strp = buf;
> +	return len;
> +}
> +
> +static __attribute__((unused, format(printf, 2, 0)))
> +int vasprintf(char **strp, const char *fmt, va_list args)
> +{
> +	va_list args2;
> +	int ret;
> +
> +	va_copy(args2, args);
> +	ret = __nolibc_vasprintf(strp, fmt, args, args2);
> +	va_end(args2);
> +
> +	return ret;
> +}
> +
> +static __attribute__((unused, format(printf, 2, 3)))
> +int asprintf(char **strp, const char *fmt, ...)
> +{
> +	va_list args;
> +	int ret;
> +
> +	va_start(args, fmt);
> +	ret = vasprintf(strp, fmt, args);
> +	va_end(args);
> +
> +	return ret;
> +}
> +
>  static __attribute__((unused))
>  int vsscanf(const char *str, const char *format, va_list args)
>  {
> diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c
> index c888e13c6bd8..98070b805e49 100644
> --- a/tools/testing/selftests/nolibc/nolibc-test.c
> +++ b/tools/testing/selftests/nolibc/nolibc-test.c
> @@ -1859,6 +1859,29 @@ static int test_printf_error(void)
>  	return 0;
>  }
>  
> +int test_asprintf(void)
> +{
> +	char *str;
> +	int ret;
> +
> +	ret = asprintf(&str, "foo%s", "bar");
> +	if (ret == -1)
> +		return 1;
> +
> +	if (ret != 6) {
> +		free(str);
> +		return 2;
> +	}
> +
> +	if (memcmp(str, "foobar", 6) != 0) {
> +		free(str);
> +		return 3;
> +	}
> +
> +	free(str);
> +	return 0;
> +}
> +
>  static int run_printf(int min, int max)
>  {
>  	int test;
> @@ -1921,6 +1944,7 @@ static int run_printf(int min, int max)
>  		CASE_TEST(errno-neg);    errno = -22; EXPECT_VFPRINTF(is_nolibc, "errno=-22   ", "%-12m"); break;
>  		CASE_TEST(scanf);        EXPECT_ZR(1, test_scanf()); break;
>  		CASE_TEST(printf_error); EXPECT_ZR(1, test_printf_error()); break;
> +		CASE_TEST(asprintf);     EXPECT_ZR(1, test_asprintf()); break;
>  		case __LINE__:
>  			return ret; /* must be last */
>  		/* note: do not set any defaults so as to permit holes above */
> 
> -- 
> 2.53.0

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH 1/3] tools/nolibc: use __builtin_offsetof()
  2026-04-04  8:34   ` Willy Tarreau
@ 2026-04-04 15:29     ` David Laight
  0 siblings, 0 replies; 10+ messages in thread
From: David Laight @ 2026-04-04 15:29 UTC (permalink / raw)
  To: Willy Tarreau; +Cc: Thomas Weißschuh, linux-kernel

On Sat, 4 Apr 2026 10:34:24 +0200
Willy Tarreau <w@1wt.eu> wrote:

> Hi Thomas,
> 
> On Wed, Apr 01, 2026 at 05:07:27PM +0200, Thomas Weißschuh wrote:
> > The current custom implementation of offsetof() fails UBSAN:
> > runtime error: member access within null pointer of type 'struct ...'
> > This means that all its users, including container_of(), free() and
> > realloc(), fail.
> > 
> > Use __builtin_offsetof() instead which does not have this issue and
> > has been available since GCC 4 and clang 4.  
> 
> Yeah, that's a place where I find the standard ambiguous and ridiculously
> absurd (since there's no dereference, only an address calculations), but I
> had to do the same in haproxy recently for the same reasons, and I didn't
> remember that we had it in nolibc as well. So that's an obvious ack!

My guess it is all because the C standard allows NULL to have any
bit-pattern. If it isn't the 'all zero' pattern then the arithmetic
on the NULL pointer gives the wrong answer.

Trouble is pretty much all C assumes that NULL is 0.
Even the C for ICL's VME-B used 0 for NULL although the 'native' value
would have been ~0. Too much code would have been broken.

So it is technically not portable, but will always generate the
correct answer.

	David

> 
> Acked-by: Willy Tarreau <w@1wt.eu>
> 
> thanks,
> Willy
> 


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH 3/3] tools/nolibc: add support for asprintf()
  2026-04-01 15:07 ` [PATCH 3/3] tools/nolibc: add support for asprintf() Thomas Weißschuh
  2026-04-04  8:53   ` Willy Tarreau
@ 2026-04-04 15:34   ` David Laight
  2026-04-05 15:39     ` Thomas Weißschuh
  1 sibling, 1 reply; 10+ messages in thread
From: David Laight @ 2026-04-04 15:34 UTC (permalink / raw)
  To: Thomas Weißschuh; +Cc: Willy Tarreau, linux-kernel

On Wed, 01 Apr 2026 17:07:29 +0200
Thomas Weißschuh <linux@weissschuh.net> wrote:

> Add support for dynamically allocating formatted strings through
> asprintf() and vasprintf().
> 
> Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
> ---
>  tools/include/nolibc/stdio.h                 | 50 ++++++++++++++++++++++++++++
>  tools/testing/selftests/nolibc/nolibc-test.c | 24 +++++++++++++
>  2 files changed, 74 insertions(+)
> 
> diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h
> index 8f7e1948a651..1c9287b558f0 100644
> --- a/tools/include/nolibc/stdio.h
> +++ b/tools/include/nolibc/stdio.h
> @@ -787,6 +787,56 @@ int sprintf(char *buf, const char *fmt, ...)
>  	return ret;
>  }
>  
> +static __attribute__((unused, format(printf, 2, 0)))
> +int __nolibc_vasprintf(char **strp, const char *fmt, va_list args1, va_list args2)
> +{
> +	char *buf;
> +	int len;
> +
> +	len = vsnprintf(NULL, 0, fmt, args1);
> +	if (len < 0)
> +		return -1;

vsnprintf() can never fail.

> +
> +	buf = malloc(len + 1);
> +	if (!buf)
> +		return -1;
> +
> +	len = vsnprintf(buf, len + 1, fmt, args2);

It is possible to get a different length.
Even without threads data might come from an mmap()ed file that
is changed by another process.
So you need to check that the length doesn't increase.

	David

> +	if (len < 0) {
> +		free(buf);
> +		return -1;
> +	}
> +
> +	*strp = buf;
> +	return len;
> +}
> +
> +static __attribute__((unused, format(printf, 2, 0)))
> +int vasprintf(char **strp, const char *fmt, va_list args)
> +{
> +	va_list args2;
> +	int ret;
> +
> +	va_copy(args2, args);
> +	ret = __nolibc_vasprintf(strp, fmt, args, args2);
> +	va_end(args2);
> +
> +	return ret;
> +}
> +
> +static __attribute__((unused, format(printf, 2, 3)))
> +int asprintf(char **strp, const char *fmt, ...)
> +{
> +	va_list args;
> +	int ret;
> +
> +	va_start(args, fmt);
> +	ret = vasprintf(strp, fmt, args);
> +	va_end(args);
> +
> +	return ret;
> +}
> +
>  static __attribute__((unused))
>  int vsscanf(const char *str, const char *format, va_list args)
>  {
> diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c
> index c888e13c6bd8..98070b805e49 100644
> --- a/tools/testing/selftests/nolibc/nolibc-test.c
> +++ b/tools/testing/selftests/nolibc/nolibc-test.c
> @@ -1859,6 +1859,29 @@ static int test_printf_error(void)
>  	return 0;
>  }
>  
> +int test_asprintf(void)
> +{
> +	char *str;
> +	int ret;
> +
> +	ret = asprintf(&str, "foo%s", "bar");
> +	if (ret == -1)
> +		return 1;
> +
> +	if (ret != 6) {
> +		free(str);
> +		return 2;
> +	}
> +
> +	if (memcmp(str, "foobar", 6) != 0) {
> +		free(str);
> +		return 3;
> +	}
> +
> +	free(str);
> +	return 0;
> +}
> +
>  static int run_printf(int min, int max)
>  {
>  	int test;
> @@ -1921,6 +1944,7 @@ static int run_printf(int min, int max)
>  		CASE_TEST(errno-neg);    errno = -22; EXPECT_VFPRINTF(is_nolibc, "errno=-22   ", "%-12m"); break;
>  		CASE_TEST(scanf);        EXPECT_ZR(1, test_scanf()); break;
>  		CASE_TEST(printf_error); EXPECT_ZR(1, test_printf_error()); break;
> +		CASE_TEST(asprintf);     EXPECT_ZR(1, test_asprintf()); break;
>  		case __LINE__:
>  			return ret; /* must be last */
>  		/* note: do not set any defaults so as to permit holes above */
> 


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH 3/3] tools/nolibc: add support for asprintf()
  2026-04-04 15:34   ` David Laight
@ 2026-04-05 15:39     ` Thomas Weißschuh
  0 siblings, 0 replies; 10+ messages in thread
From: Thomas Weißschuh @ 2026-04-05 15:39 UTC (permalink / raw)
  To: David Laight; +Cc: Willy Tarreau, linux-kernel

On 2026-04-04 16:34:59+0100, David Laight wrote:
> On Wed, 01 Apr 2026 17:07:29 +0200
> Thomas Weißschuh <linux@weissschuh.net> wrote:
> 
> > Add support for dynamically allocating formatted strings through
> > asprintf() and vasprintf().
> > 
> > Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
> > ---
> >  tools/include/nolibc/stdio.h                 | 50 ++++++++++++++++++++++++++++
> >  tools/testing/selftests/nolibc/nolibc-test.c | 24 +++++++++++++
> >  2 files changed, 74 insertions(+)
> > 
> > diff --git a/tools/include/nolibc/stdio.h b/tools/include/nolibc/stdio.h
> > index 8f7e1948a651..1c9287b558f0 100644
> > --- a/tools/include/nolibc/stdio.h
> > +++ b/tools/include/nolibc/stdio.h
> > @@ -787,6 +787,56 @@ int sprintf(char *buf, const char *fmt, ...)
> >  	return ret;
> >  }
> >  
> > +static __attribute__((unused, format(printf, 2, 0)))
> > +int __nolibc_vasprintf(char **strp, const char *fmt, va_list args1, va_list args2)
> > +{
> > +	char *buf;
> > +	int len;
> > +
> > +	len = vsnprintf(NULL, 0, fmt, args1);
> > +	if (len < 0)
> > +		return -1;
> 
> vsnprintf() can never fail.

The one in nolibc not, according to the specification it could.
So to be on the safe side, also against future changes in nolibc
I'd like to keep the check.
The asnprintf()/malloc() implementation will make any performance
considerations moot anyways.

> > +
> > +	buf = malloc(len + 1);
> > +	if (!buf)
> > +		return -1;
> > +
> > +	len = vsnprintf(buf, len + 1, fmt, args2);
> 
> It is possible to get a different length.
> Even without threads data might come from an mmap()ed file that
> is changed by another process.
> So you need to check that the length doesn't increase.

Good point, I plan to apply the following fixup:

--- a/tools/include/nolibc/stdio.h
+++ b/tools/include/nolibc/stdio.h
@@ -798,8 +798,8 @@ int sprintf(char *buf, const char *fmt, ...)
 static __attribute__((unused, format(printf, 2, 0)))
 int __nolibc_vasprintf(char **strp, const char *fmt, va_list args1, va_list args2)
 {
+       int len, len2;
        char *buf;
-       int len;

        len = vsnprintf(NULL, 0, fmt, args1);
        if (len < 0)
@@ -809,8 +809,8 @@ int __nolibc_vasprintf(char **strp, const char *fmt, va_list args1, va_list args
        if (!buf)
                return -1;

-       len = vsnprintf(buf, len + 1, fmt, args2);
-       if (len < 0) {
+       len2 = vsnprintf(buf, len + 1, fmt, args2);
+       if (len2 < 0) {
                free(buf);
                return -1;
        }

^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2026-04-05 15:39 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-01 15:07 [PATCH 0/3] tools/nolibc: add support for asprintf() Thomas Weißschuh
2026-04-01 15:07 ` [PATCH 1/3] tools/nolibc: use __builtin_offsetof() Thomas Weißschuh
2026-04-04  8:34   ` Willy Tarreau
2026-04-04 15:29     ` David Laight
2026-04-01 15:07 ` [PATCH 2/3] selftests/nolibc: test the memory allocator Thomas Weißschuh
2026-04-04  8:51   ` Willy Tarreau
2026-04-01 15:07 ` [PATCH 3/3] tools/nolibc: add support for asprintf() Thomas Weißschuh
2026-04-04  8:53   ` Willy Tarreau
2026-04-04 15:34   ` David Laight
2026-04-05 15:39     ` Thomas Weißschuh

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox