summaryrefslogtreecommitdiffstats
path: root/src/util
diff options
context:
space:
mode:
authorStef Walter <stefw@redhat.com>2014-01-07 14:44:11 +0100
committerJakub Hrozek <jhrozek@redhat.com>2014-01-12 15:24:26 +0100
commitb9d8c6172e48a2633ebe196b2e88bebdf9523c20 (patch)
tree3ea801f4dbba0a859cbe44f8c3e1affe5fb108ba /src/util
parent4b8021779e4db2a212a8214c17e778e843ae2b3a (diff)
downloadsssd-b9d8c6172e48a2633ebe196b2e88bebdf9523c20.tar.gz
sssd-b9d8c6172e48a2633ebe196b2e88bebdf9523c20.tar.xz
sssd-b9d8c6172e48a2633ebe196b2e88bebdf9523c20.zip
util: A safe printf for user provided format strings
Since the default printf(3) implementation cannot safely be used on user (or admin) provided input, this is a safe implementation. This will be used in later patches by the full_name_format option The implementation came from realmd, but only has libc dependencies. The number of fields is pre-defined, and safe printf fails if an invalid field is accessed. Only string fields are supported, and only flags relevant to string fields are supported. Width and precision work as expected, but precision cannot read from a field. Tests are included, and ported to the check based testing that sssd uses.
Diffstat (limited to 'src/util')
-rw-r--r--src/util/safe-format-string.c309
-rw-r--r--src/util/safe-format-string.h81
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__ */