summaryrefslogtreecommitdiffstats
path: root/src/util/safe-format-string.c
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/safe-format-string.c
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/safe-format-string.c')
-rw-r--r--src/util/safe-format-string.c309
1 files changed, 309 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;
+}