Subject: [PATCH] [BZ 184060] zipl/libc: Introduce vsnprintf
From: Philipp Rudo <prudo@linux.ibm.com>

Description:   zipl/libc: Fix potential buffer overflow in printf
Symptom:       Crash of the zipl boot loader during boot.
Problem:       The zipl boot loaders have their own minimalistic libc
               implementation. In it printf and sprintf use vsprintf for string
               formatting. Per definition vsprintf assumes that the buffer it
               writes to is large enough to contain the formatted string and
               performs no size checks. This is problematic for the boot
               loaders because the buffer they use are often allocated on the
               stack. Thus even small changes to the string format can
               potentially cause buffer overflows on the stack.
Solution:      Implement vsnprintf and make use of it.
Reproduction:  Use printf to print a string with >81 characters (exact number
               depends on the stack layout/compiler used).
Upstream-ID:   6fe9e6c55c69c14971dca55551009f5060418aae
Problem-ID:    184060

Upstream-Description:

              zipl/libc: Introduce vsnprintf

              The zipl boot loaders have their own minimalistic libc implementation.
              In it printf and sprintf use vsprintf for string formatting. Per
              definition vsprintf assumes that the buffer it writes to is large enough
              to contain the formatted string and performs no size checks. This is
              problematic for the boot loaders because the buffer they use are often
              allocated on the stack. Thus even small changes to the string format can
              potentially cause buffer overflows on the stack with the well known
              consequences. Protect against such errors by implementing vsnprintf.
              Later patches will make use of it.

              This implementation of vsnprintf only supports a small subset of format
              options defined in the C standard. In particular it allows the
              specifiers:
              	* %s (strings)
              	* %o (unsigned int octal)
              	* %u (unsigned int decimal)
              	* %x (unsigned int hexadecimal)

              Integer specifiers (o, u, and x) always use the long form, i.e. assume the
              argument to be of type 'unsigned long int'. The length modified 'l' can
              be given but is ignored.

              Furthermore, it is possible to provide the optional field width (aligned
              to the right only) and precision as decimal integer (i.e. not via '*')
              as well as the flag for zero padding integers (i.e. '0').

              The implementation was heavily inspired by the implementation in
              lib/vsprintf.c from the Linux kernel tree.

              Signed-off-by: Philipp Rudo <prudo@linux.ibm.com>
              Reviewed-by: Stefan Haberland <sth@linux.ibm.com>
              Signed-off-by: Jan Hoeppner <hoeppner@linux.ibm.com>


Signed-off-by: Philipp Rudo <prudo@linux.ibm.com>
---
 zipl/boot/libc.c |  268 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 zipl/boot/libc.h |    1 
 2 files changed, 269 insertions(+)

--- a/zipl/boot/libc.c
+++ b/zipl/boot/libc.c
@@ -57,6 +57,26 @@ void *memcpy(void *dest, const void *src
 }
 
 /*
+ * Move @n bytes of memory from @src to @dest. The memory regions may overlap.
+ */
+void *memmove(void *dest, const void *src, unsigned long n)
+{
+	const char *s = src;
+	char *d = dest;
+
+	if (s < d) {
+		d += n;
+		s += n;
+		while (n--)
+			*--d = *--s;
+	} else {
+		while (n--)
+			*d++ = *s++;
+	}
+	return dest;
+}
+
+/*
  * Copy string
  */
 char *strcpy(char *dest, const char *src)
@@ -196,6 +216,254 @@ unsigned long ebcstrtoul(char *nptr, cha
 	return val;
 }
 
+static int skip_atoi(const char **c)
+{
+	int i = 0;
+
+	do {
+		i = i*10 + *((*c)++) - '0';
+	} while (isdigit(**c));
+
+	return i;
+}
+
+enum format_type {
+	FORMAT_TYPE_NONE,
+	FORMAT_TYPE_STR,
+	FORMAT_TYPE_ULONG,
+};
+
+struct printf_spec {
+	unsigned int	type:8;		/* format_type enum */
+	signed int	field_width:24;	/* width of output field */
+	unsigned int	zeropad:1;	/* pad numbers with zero */
+	unsigned int	base:8;		/* number base, 8, 10 or 16 only */
+	signed int	precision:16;	/* # of digits/chars */
+};
+
+#define FIELD_WIDTH_MAX ((1 << 23) - 1)
+
+static int format_decode(const char *fmt, struct printf_spec *spec)
+{
+	const char *start = fmt;
+
+	spec->type = FORMAT_TYPE_NONE;
+	while (*fmt) {
+		if (*fmt == '%')
+			break;
+		fmt++;
+	}
+
+	/* return current non-format string */
+	if (fmt != start || !*fmt)
+		return fmt - start;
+
+	/* first char is '%', skip it */
+	fmt++;
+	if (*fmt == '0') {
+		spec->zeropad = 1;
+		fmt++;
+	}
+
+	spec->field_width = -1;
+	if (isdigit(*fmt))
+		spec->field_width = skip_atoi(&fmt);
+
+	spec->precision = -1;
+	if (*fmt == '.') {
+		fmt++;
+		if (isdigit(*fmt))
+			spec->precision = skip_atoi(&fmt);
+	}
+
+	/* always use long form, i.e. ignore long qualifier */
+	if (*fmt == 'l')
+		fmt++;
+
+	switch (*fmt) {
+	case 's':
+		spec->type = FORMAT_TYPE_STR;
+		break;
+
+	case 'o':
+		spec->base = 8;
+		spec->type = FORMAT_TYPE_ULONG;
+		break;
+
+	case 'u':
+		spec->base = 10;
+		spec->type = FORMAT_TYPE_ULONG;
+		break;
+
+	case 'x':
+		spec->base = 16;
+		spec->type = FORMAT_TYPE_ULONG;
+		break;
+
+	default:
+		libc_stop(EINTERNAL);
+	}
+
+	return ++fmt - start;
+}
+
+static char *string(char *buf, char *end, const char *s,
+		    struct printf_spec *spec)
+{
+	int limit = spec->precision;
+	int len = 0;
+	int spaces;
+
+	/* Copy string to buffer */
+	while (limit--) {
+		char c = *s++;
+		if (!c)
+			break;
+		if (buf < end)
+			*buf = c;
+		buf++;
+		len++;
+	}
+
+	/* right align if necessary */
+	if (len < spec->field_width && buf < end) {
+		spaces = spec->field_width - len;
+		if (spaces >= end - buf)
+			spaces = end - buf;
+		memmove(buf + spaces, buf, len);
+		memset(buf, ' ', spaces);
+		buf += spaces;
+	}
+
+	return buf;
+}
+
+static char *number(char *buf, char *end, unsigned long val,
+		    struct printf_spec *spec)
+{
+	/* temporary buffer to prepare the string.
+	 * Worst case: base = 8 -> 3 bits per char -> 2.67 chars per byte */
+	char tmp[3 * sizeof(val)];
+	static const char vec[] = {'0', '1', '2', '3', '4', '5', '6', '7',
+				   '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+	int field_width = spec->field_width;
+	int precision = spec->precision;
+	int len;
+
+	/* prepare string in reverse order */
+	len = 0;
+	while (val) {
+		tmp[len++] = vec[val % spec->base];
+		val /= spec->base;
+	}
+
+	if (len > precision)
+		precision = len;
+
+	field_width -= precision;
+	while (field_width-- > 0) {
+		char c = spec->zeropad ? '0' : ' ';
+		if (buf < end)
+			*buf = c;
+		buf++;
+	}
+
+	/* needed if no field width but a precision is given */
+	while (len < precision--) {
+		if (buf < end)
+			*buf = '0';
+		buf++;
+	}
+
+	while (len-- > 0) {
+		if (buf < end)
+			*buf = tmp[len];
+		buf++;
+	}
+
+	return buf;
+}
+
+/*
+ * vsnprintf - Format string and place in a buffer
+ *
+ * This funcion only supports a subset of format options defined in the
+ * C standard, i.e.
+ * specifiers:
+ *	* %s (strings)
+ *	* %o (unsigned int octal)
+ *	* %u (unsigned int decimal)
+ *	* %x (unsigned int hexadecimal)
+ *
+ * length modifier:
+ *	* 'l' (ignored, see below)
+ *
+ * flag:
+ *	* '0' (zero padding for integers)
+ *
+ * precision and field width as integers, i.e. _not_ by asterix '*'.
+ *
+ * The integer specifiers (o, u and, x) always use the long form, i.e.
+ * assume the argument to be of type 'unsigned long int'.
+ *
+ * Returns the number of characters the function would have generated for
+ * the given input (excluding the trailing '\0'. If the return value is
+ * greater than or equal @size the resulting string is trunctuated.
+ */
+static int vsnprintf(char *buf, unsigned long size, const char *fmt,
+		     va_list args)
+{
+	struct printf_spec spec = {0};
+	char *str, *end;
+
+	str = buf;
+	end = buf + size;
+
+	/* use negative (large positive) buffer sizes as indication for
+	 * unknown/unlimited buffer sizes. */
+	if (end < buf) {
+		end = ((void *)-1);
+		size = end - buf;
+	}
+
+	while (*fmt) {
+		const char *old_fmt = fmt;
+		int read = format_decode(fmt, &spec);
+		int copy;
+
+		fmt += read;
+
+		switch (spec.type) {
+		case FORMAT_TYPE_NONE:
+			copy = read;
+			if (str < end) {
+				if (copy > end - str)
+					copy = end - str;
+				memcpy(str, old_fmt, copy);
+			}
+			str += read;
+			break;
+
+		case FORMAT_TYPE_STR:
+			str = string(str, end, va_arg(args, char *), &spec);
+			break;
+
+		case FORMAT_TYPE_ULONG:
+			str = number(str, end, va_arg(args, unsigned long),
+				     &spec);
+			break;
+		}
+	}
+
+	if (size) {
+		if (str < end)
+			*str = '\0';
+		else
+			end[-1] = '\0';
+	}
+	return str - buf;
+}
+
 /*
  * Convert string to number with given base
  */
--- a/zipl/boot/libc.h
+++ b/zipl/boot/libc.h
@@ -50,6 +50,7 @@ typedef unsigned char uint8_t;
 void printf(const char *, ...);
 void sprintf(char *, const char *, ...);
 void *memcpy(void *, const void *, unsigned long);
+void *memmove(void *, const void *, unsigned long);
 void *memset(void *, int c, unsigned long);
 char *strcat(char *, const char *);
 int strncmp(const char *, const char *, unsigned long);
