diff options
Diffstat (limited to 'src/util')
-rw-r--r-- | src/util/safe-format-string.c | 309 | ||||
-rw-r--r-- | src/util/safe-format-string.h | 81 |
2 files changed, 390 insertions, 0 deletions
diff --git a/src/util/safe-format-string.c b/src/util/safe-format-string.c new file mode 100644 index 000000000..11532d42e --- /dev/null +++ b/src/util/safe-format-string.c @@ -0,0 +1,309 @@ +/* + * This file originated in the realmd project + * + * Copyright 2013 Red Hat Inc + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2 of the licence or (at + * your option) any later version. + * + * See the included COPYING file for more information. + * + * Author: Stef Walter <stefw@redhat.com> + */ + +/* + * Some snippets of code from gnulib, but have since been refactored + * to within an inch of their life... + * + * vsprintf with automatic memory allocation. + * Copyright (C) 1999, 2002-2003 Free Software Foundation, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Library General Public License as published + * by the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + */ + +#include "config.h" + +#include "safe-format-string.h" + +#include <errno.h> +#include <stdarg.h> +#include <string.h> + +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#ifndef MAX +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif + +static void +safe_padding (int count, + int *total, + void (* copy_fn) (void *, const char *, size_t), + void *data) +{ + char eight[] = " "; + int num; + + while (count > 0) { + num = MIN (count, 8); + copy_fn (data, eight, num); + count -= num; + *total += num; + } +} + +static void +dummy_copy_fn (void *data, + const char *piece, + size_t len) +{ + +} + +int +safe_format_string_cb (void (* copy_fn) (void *, const char *, size_t), + void *data, + const char *format, + const char * const args[], + int num_args) +{ + int at_arg = 0; + const char *cp; + int precision; + int width; + int len; + const char *value; + int total; + int left; + int i; + + if (!copy_fn) + copy_fn = dummy_copy_fn; + + total = 0; + cp = format; + + while (*cp) { + + /* Piece of raw string */ + if (*cp != '%') { + len = strcspn (cp, "%"); + copy_fn (data, cp, len); + total += len; + cp += len; + continue; + } + + cp++; + + /* An literal percent sign? */ + if (*cp == '%') { + copy_fn (data, "%", 1); + total++; + cp++; + continue; + } + + value = NULL; + left = 0; + precision = -1; + width = -1; + + /* Test for positional argument. */ + if (*cp >= '0' && *cp <= '9') { + /* Look-ahead parsing, otherwise skipped */ + if (cp[strspn (cp, "0123456789")] == '$') { + unsigned int n = 0; + for (i = 0; i < 6 && *cp >= '0' && *cp <= '9'; i++, cp++) { + n = 10 * n + (*cp - '0'); + } + /* Positional argument 0 is invalid. */ + if (n == 0) { + errno = EINVAL; + return -1; + } + /* Positional argument N too high */ + if (n > num_args) { + errno = EINVAL; + return -1; + } + value = args[n - 1]; + cp++; /* $ */ + } + } + + /* Read the supported flags. */ + for (; ; cp++) { + if (*cp == '-') + left = 1; + /* Supported but ignored */ + else if (*cp != ' ') + break; + } + + /* Parse the width. */ + if (*cp >= '0' && *cp <= '9') { + width = 0; + for (i = 0; i < 6 && *cp >= '0' && *cp <= '9'; i++, cp++) { + width = 10 * width + (*cp - '0'); + } + } + + /* Parse the precision. */ + if (*cp == '.') { + precision = 0; + for (i = 0, cp++; i < 6 && *cp >= '0' && *cp <= '9'; cp++, i++) { + precision = 10 * precision + (*cp - '0'); + } + } + + /* Read the conversion character. */ + switch (*cp++) { + case 's': + /* Non-positional argument */ + if (value == NULL) { + /* Too many arguments used */ + if (at_arg == num_args) { + errno = EINVAL; + return -1; + } + value = args[at_arg++]; + } + break; + + /* No other conversion characters are supported */ + default: + errno = EINVAL; + return -1; + } + + /* How many characters are we printing? */ + len = strlen (value); + if (precision >= 0) + len = MIN (precision, len); + + /* Do we need padding? */ + safe_padding (left ? 0 : width - len, &total, copy_fn, data); + + /* The actual data */; + copy_fn (data, value, len); + total += len; + + /* Do we need padding? */ + safe_padding (left ? width - len : 0, &total, copy_fn, data); + } + + return total; +} + +static const char ** +valist_to_args (va_list va, + int *num_args) +{ + int alo_args; + const char **args; + const char *arg; + void *mem; + + *num_args = alo_args = 0; + args = NULL; + + for (;;) { + arg = va_arg (va, const char *); + if (arg == NULL) + break; + if (*num_args == alo_args) { + alo_args += 8; + mem = realloc (args, sizeof (const char *) * alo_args); + if (!mem) { + free (args); + return NULL; + } + args = mem; + } + args[(*num_args)++] = arg; + } + + return args; +} + +struct sprintf_ctx { + char *data; + size_t length; + size_t alloc; +}; + +static void +snprintf_copy_fn (void *data, + const char *piece, + size_t length) +{ + struct sprintf_ctx *cx = data; + + /* Don't copy if too much data */ + if (cx->length > cx->alloc) + length = 0; + else if (cx->length + length > cx->alloc) + length = cx->alloc - cx->length; + + if (length > 0) + memcpy (cx->data + cx->length, piece, length); + + /* Null termination happens later */ + cx->length += length; +} + +int +safe_format_string (char *str, + size_t len, + const char *format, + ...) +{ + struct sprintf_ctx cx; + int num_args; + va_list va; + const char **args; + int error = 0; + int ret; + + cx.data = str; + cx.length = 0; + cx.alloc = len; + + va_start (va, format); + args = valist_to_args (va, &num_args); + va_end (va); + + if (args == NULL) { + errno = ENOMEM; + return -1; + } + + if (len) + cx.data[0] = '\0'; + + ret = safe_format_string_cb (snprintf_copy_fn, &cx, format, args, num_args); + if (ret < 0) { + error = errno; + } else if (len > 0) { + cx.data[MIN (cx.length, len - 1)] = '\0'; + } + + free (args); + + if (error) + errno = error; + return ret; +} diff --git a/src/util/safe-format-string.h b/src/util/safe-format-string.h new file mode 100644 index 000000000..2f4796de7 --- /dev/null +++ b/src/util/safe-format-string.h @@ -0,0 +1,81 @@ +/* + * This file originated in the realmd project + * + * Copyright 2013 Red Hat Inc + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2 of the licence or (at + * your option) any later version. + * + * See the included COPYING file for more information. + * + * Author: Stef Walter <stefw@redhat.com> + */ + +#include "config.h" + +#ifndef __SAFE_FORMAT_STRING_H__ +#define __SAFE_FORMAT_STRING_H__ + +#include <stdlib.h> + +/* + * This is a neutered printf variant that can be used with user-provided + * format strings. + * + * Not only are the normal printf functions not safe to use on user-provided + * input (ie: can crash, be abused, etc), they're also very brittle with + * regards to positional arguments: one must consume them all or printf will + * just abort(). This is because arguments of different sizes are accepted + * in the varargs. So obviously the positional code cannot know the offset + * of the relevant varargs if some are not consumed (ie: tagged with a + * field type). + * + * Thus the only accepted field type here is 's'. It's all we need. + * + * In general new code should use a better syntax than printf format strings + * for configuration options. This code is here to facilitate robust processing + * of the full_name_format syntax we already have, which has been documented as + * "printf(3) compatible". + * + * Features: + * - Only string 's' fields are supported + * - All the varargs should be strings, followed by a NULL argument + * - Both positional '%$1s' and non-positional '%s' are supported + * - Field widths '%8s' work as expected + * - Precision '%.8s' works, but precision cannot be read from a field + * - Left alignment flag is supported '%-8s'. + * - The space flag '% 8s' has no effect (it's the default for string fields). + * - No more than six digits are supported for widths, precisions, etc. + * - Percent signs are to be escaped as usual '%%' + * + * Use of other flags or field types will cause the relevant printf call to + * return -1. Using too many arguments or incorrect positional arguments + * will also cause the call to fail. + * + * Functions return -1 on failure and set errno. Otherwise they return + * the full length of the string that would be formatted, with the same + * semantics as snprintf(). + */ + +#ifndef GNUC_NULL_TERMINATED +#if __GNUC__ >= 4 +#define GNUC_NULL_TERMINATED __attribute__((__sentinel__)) +#else +#define GNUC_NULL_TERMINATED +#endif +#endif + +int safe_format_string (char *str, + size_t len, + const char *format, + ...) GNUC_NULL_TERMINATED; + +int safe_format_string_cb (void (* callback) (void *data, const char *piece, size_t len), + void *data, + const char *format, + const char * const args[], + int num_args); + +#endif /* __SAFE_FORMAT_STRING_H__ */ |