Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Date: Wed, 31 May 2023 12:05:17 +0200
From: Jens Gustedt <Jens.Gustedt@...ia.fr>
To: musl@...ts.openwall.com
Subject: [C23 new stdlib 3/3] C23: implement the new strfrom[dfl] functions

These names had been reserved in C17, so it is not necessary to hide
these function in conditionals.

With the exception of strfroml, the implementation is direct because
format strings can be forwarded to snprintf (there is no length
modifier for float or double). For strfroml the format has to be
assembled from the received format to interlace "L".

Because compilers will probably not check their formats for these new
functions for some generations, in general this would be passing an
unsanitized dynamic format string into snprintf. So we do a relatively
simple check before hand, in particular to inhibit appearance of other
"%" specifiers in the string that could be used for attacks.
---
 include/stdlib.h      |  4 ++++
 src/stdlib/strfromd.c | 44 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 48 insertions(+)
 create mode 100644 src/stdlib/strfromd.c

diff --git a/include/stdlib.h b/include/stdlib.h
index 10bdf7f8..72522cd6 100644
--- a/include/stdlib.h
+++ b/include/stdlib.h
@@ -29,6 +29,10 @@ float strtof (const char *__restrict, char **__restrict);
 double strtod (const char *__restrict, char **__restrict);
 long double strtold (const char *__restrict, char **__restrict);
 
+int strfromd(char *restrict, size_t, const char *restrict, double);
+int strfromf(char *restrict, size_t, const char *restrict, float);
+int strfroml(char *restrict, size_t, const char *restrict, long double);
+
 long strtol (const char *__restrict, char **__restrict, int);
 unsigned long strtoul (const char *__restrict, char **__restrict, int);
 long long strtoll (const char *__restrict, char **__restrict, int);
diff --git a/src/stdlib/strfromd.c b/src/stdlib/strfromd.c
new file mode 100644
index 00000000..f5b92956
--- /dev/null
+++ b/src/stdlib/strfromd.c
@@ -0,0 +1,44 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+
+static size_t sanitize(const char*format) {
+	size_t slen = format ? strlen(format) : 0;
+	if (format[0] != '%'
+		|| (slen > 2 && format[1] != '.')
+		|| strchr(&format[1], '%')
+		|| (strspn(&format[slen-1], "aAeEfFgG") != 1)) return 0;
+	else return slen;
+}
+
+int strfromd(char *restrict s, size_t n, const char *restrict format, double fp) {
+	return sanitize(format) ? snprintf(s, n, format, fp) : -1;
+}
+
+int strfromf(char *restrict s, size_t n, const char *restrict format, float fp) {
+	return sanitize(format) ? snprintf(s, n, format, fp) : -1;
+}
+
+int strfroml(char *restrict s, size_t n, const char *restrict format, long double fp) {
+	enum { max_len = 1+sizeof "%.18446744073709551615Lg", };
+	char ff[max_len];
+	size_t slen = sanitize(format);
+	if (!slen) return -1;
+	if (slen < max_len-2) {
+		memcpy(ff, format, slen-1);
+		ff[slen-1] = 'L';
+		ff[slen] = format[slen-1];
+		ff[slen+1] = 0;
+	} else {
+		// If the precision is unreasonably long, fallback to
+		// strtoull to parse it, and squeeze it into a
+		// reasonable length, if possible.
+		int eback = errno;
+		unsigned long long prec = strtoull(format+2, NULL, 10);
+		if (prec == ULLONG_MAX) errno = eback;
+		snprintf(ff, max_len, "%%.%lldL%c", prec, format[slen-1]);
+	}
+	return snprintf(s, n, ff, fp);
+}
-- 
2.34.1

Powered by blists - more mailing lists

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.