7230df2ed5
- Added the following patches for bsc#1165978. zpcictl --reset only issues a SCLP reset and leaves the PCI function in an error state. Initiate an OS level recovery by calling /sys/bus/devices/<dev>/recover after the SCLP reset. * s390-tools-sles15sp2-01-zpcictl-Initiate-recover-after-reset.patch * s390-tools-sles15sp2-02-zpcictl-Rename-misleading-sysfs_write_data.patch * s390-tools-sles15sp2-03-zpcitctl-Exit-on-error-in-sysfs_report_error.patch - The zipl boot loader may crash during boot. The solution is to implement vsnprintf and make use of it. (bsc#1165317) * s390-tools-sles15sp2-01-zipl-libc-Introduce-vsnprintf.patch * s390-tools-sles15sp2-02-zipl-libc-Fix-potential-buffer-overflow-in-printf.patch * s390-tools-sles15sp2-03-zipl-libc-Replace-sprintf-with-snprintf.patch * s390-tools-sles15sp2-04-zipl-libc-Indicate-truncated-lines-in-printf-with.patch OBS-URL: https://build.opensuse.org/request/show/783519 OBS-URL: https://build.opensuse.org/package/show/Base:System/s390-tools?expand=0&rev=90
320 lines
8.2 KiB
Diff
320 lines
8.2 KiB
Diff
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 | 248 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
1 file changed, 248 insertions(+)
|
|
|
|
--- a/zipl/boot/libc.c
|
|
+++ b/zipl/boot/libc.c
|
|
@@ -218,6 +218,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
|
|
*/
|