From 7e213cfb50e9db6838bf6f27867ed3ab5af67529 Mon Sep 17 00:00:00 2001 From: Alejandro Colomar Date: Mon, 30 Jan 2023 00:19:56 +0100 Subject: [PATCH] Add stpeprintf() [v]stpeprintf() are similar to [v]snprintf(3), but they allow chaining. [v]snprintf(3) are very dangerous for catenating strings, since the obvious ways to do it invoke Undefined Behavior, and the ways that avoid UB are very error-prone. Cc: Iker Pedrosa Signed-off-by: Alejandro Colomar --- configure.ac | 2 +- lib/stpeprintf.h | 119 +++++++++++++++++++++++++++++++++++++++++++ libmisc/Makefile.am | 1 + libmisc/stpeprintf.c | 25 +++++++++ 4 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 lib/stpeprintf.h create mode 100644 libmisc/stpeprintf.c diff --git a/configure.ac b/configure.ac index 8e978cf5..89fc1b26 100644 --- a/configure.ac +++ b/configure.ac @@ -50,7 +50,7 @@ AC_CHECK_FUNCS(arc4random_buf futimes \ initgroups lckpwdf lutimes \ setgroups updwtmp updwtmpx innetgr \ getspnam_r \ - memset_explicit explicit_bzero) + memset_explicit explicit_bzero stpeprintf) AC_SYS_LARGEFILE dnl Checks for typedefs, structures, and compiler characteristics. diff --git a/lib/stpeprintf.h b/lib/stpeprintf.h new file mode 100644 index 00000000..4929019a --- /dev/null +++ b/lib/stpeprintf.h @@ -0,0 +1,119 @@ +/* + * SPDX-FileCopyrightText: 2022 - 2023, Alejandro Colomar + * + * SPDX-License-Identifier: BSD-3-Clause + */ + + +#ifndef SHADOW_INCLUDE_LIB_STPEPRINTF_H_ +#define SHADOW_INCLUDE_LIB_STPEPRINTF_H_ + + +#include + +#if !defined(HAVE_STPEPRINTF) + +#include +#include +#include + +#include "defines.h" + + +format_attr(printf, 3, 4) +inline char *stpeprintf(char *dst, char *end, const char *restrict fmt, ...); + +format_attr(printf, 3, 0) +inline char *vstpeprintf(char *dst, char *end, const char *restrict fmt, + va_list ap); + + +/* + * SYNOPSIS + * [[gnu::format(printf, 3, 4)]] + * char *_Nullable stpeprintf(char *_Nullable dst, char end[0], + * const char *restrict fmt, ...); + * + * [[gnu::format(printf, 3, 0)]] + * char *_Nullable vstpeprintf(char *_Nullable dst, char end[0], + * const char *restrict fmt, va_list ap); + * + * + * ARGUMENTS + * dst Destination buffer where to write a string. + * + * end Pointer to one after the last element of the buffer + * pointed to by `dst`. Usually, it should be calculated + * as `dst + NITEMS(dst)`. + * + * fmt Format string + * + * ... + * ap Variadic argument list + * + * DESCRIPTION + * These functions are very similar to [v]snprintf(3). + * + * The destination buffer is limited by a pointer to its end --one + * after its last element-- instead of a size. This allows + * chaining calls to it safely, unlike [v]snprintf(3), which is + * difficult to chain without invoking Undefined Behavior. + * + * RETURN VALUE + * dst + strlen(dst) + * • On success, these functions return a pointer to the + * terminating NUL byte. + * + * end + * • If this call truncated the resulting string. + * • If `dst == end` (a previous chained call to these + * functions truncated). + * NULL + * • If this function failed (see ERRORS). + * • If `dst == NULL` (a previous chained call to these + * functions failed). + * + * ERRORS + * These functions may fail for the same reasons as vsnprintf(3). + */ + + +inline char * +stpeprintf(char *dst, char *end, const char *restrict fmt, ...) +{ + char *p; + va_list ap; + + va_start(ap, fmt); + p = vstpeprintf(dst, end, fmt, ap); + va_end(ap); + + return p; +} + + +inline char * +vstpeprintf(char *dst, char *end, const char *restrict fmt, va_list ap) +{ + int len; + ptrdiff_t size; + + if (dst == end) + return end; + if (dst == NULL) + return NULL; + + size = end - dst; + len = vsnprintf(dst, size, fmt, ap); + + if (len == -1) + return NULL; + if (len >= size) + return end; + + return dst + len; +} + + +#endif // !HAVE_STPEPRINTF +#endif // include guard diff --git a/libmisc/Makefile.am b/libmisc/Makefile.am index ab363f54..a74de975 100644 --- a/libmisc/Makefile.am +++ b/libmisc/Makefile.am @@ -61,6 +61,7 @@ libmisc_la_SOURCES = \ setugid.c \ setupenv.c \ shell.c \ + stpeprintf.c \ strtoday.c \ sub.c \ sulog.c \ diff --git a/libmisc/stpeprintf.c b/libmisc/stpeprintf.c new file mode 100644 index 00000000..f3238eaf --- /dev/null +++ b/libmisc/stpeprintf.c @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2022 - 2023, Alejandro Colomar + * + * SPDX-License-Identifier: BSD-3-Clause + */ + + +#include + +#if !defined(HAVE_STPEPRINTF) + +#ident "$Id$" + +#include "stpeprintf.h" + +#include + + +extern inline char *stpeprintf(char *dst, char *end, const char *restrict fmt, + ...); +extern inline char *vstpeprintf(char *dst, char *end, const char *restrict fmt, + va_list ap); + + +#endif // !HAVE_STPEPRINTF