summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile.am16
-rw-r--r--src/tests/safe-format-tests.c244
-rw-r--r--src/util/safe-format-string.c309
-rw-r--r--src/util/safe-format-string.h81
4 files changed, 649 insertions, 1 deletions
diff --git a/Makefile.am b/Makefile.am
index 2733d4f6d..9c155d68c 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -136,7 +136,8 @@ if HAVE_CHECK
debug-tests \
ipa_hbac-tests \
sss_idmap-tests \
- responder_socket_access-tests
+ responder_socket_access-tests \
+ safe-format-tests
if BUILD_SSH
non_interactive_check_based_tests += sysdb_ssh-tests
@@ -401,6 +402,7 @@ dist_noinst_HEADERS = \
src/util/util.h \
src/util/io.h \
src/util/util_errors.h \
+ src/util/safe-format-string.h \
src/util/strtonum.h \
src/util/sss_endian.h \
src/util/sss_nss.h \
@@ -573,6 +575,7 @@ libsss_util_la_SOURCES = \
src/sbus/sssd_dbus_server.c \
src/util/util.c \
src/util/memory.c \
+ src/util/safe-format-string.c \
src/util/server.c \
src/util/signal.c \
src/util/usertools.c \
@@ -1180,6 +1183,17 @@ util_tests_LDADD = \
$(SSSD_INTERNAL_LTLIBS) \
libsss_test_common.la
+safe_format_tests_SOURCES = \
+ src/tests/safe-format-tests.c
+safe_format_tests_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(CHECK_CFLAGS)
+safe_format_tests_LDADD = \
+ $(SSSD_LIBS) \
+ $(CHECK_LIBS) \
+ $(SSSD_INTERNAL_LTLIBS) \
+ libsss_test_common.la
+
debug_tests_SOURCES = \
src/tests/debug-tests.c \
src/tests/common.c
diff --git a/src/tests/safe-format-tests.c b/src/tests/safe-format-tests.c
new file mode 100644
index 000000000..25b917bc0
--- /dev/null
+++ b/src/tests/safe-format-tests.c
@@ -0,0 +1,244 @@
+/*
+ * This file originated in realmd
+ *
+ * Copyright 2012 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"
+
+#include "src/util/safe-format-string.h"
+
+#include <check.h>
+#include <popt.h>
+#include <string.h>
+#include <talloc.h>
+
+typedef struct {
+ const char *format;
+ const char *args[8];
+ const char *result;
+} Fixture;
+
+static const Fixture fixtures[] = {
+ {
+ /* Just a bog standard string */
+ "%s", { "blah", NULL, },
+ "blah"
+ },
+ {
+ /* Empty to print */
+ "%s", { "", NULL, },
+ ""
+ },
+ {
+ /* Nothing to print */
+ "", { "blah", NULL, },
+ ""
+ },
+ {
+ /* Width right aligned */
+ "%8s", { "blah", NULL, },
+ " blah"
+ },
+ {
+ /* Width left aligned */
+ "whoop %-8s doo", { "dee", NULL, },
+ "whoop dee doo"
+ },
+ {
+ /* Width space aligned (ignored) */
+ "whoop % 8s doo", { "dee", NULL, },
+ "whoop dee doo"
+ },
+ {
+ /* Width left space aligned (ignored) */
+ "whoop % -8s doo", { "dee", NULL, },
+ "whoop dee doo"
+ },
+ {
+ /* Precision 1 digit */
+ "whoop %.3s doo", { "deedle-dee", NULL, },
+ "whoop dee doo"
+ },
+ {
+ /* Precision, N digits */
+ "whoop %.10s doo", { "deedle-dee-deedle-do-deedle-dum", NULL, },
+ "whoop deedle-dee doo"
+ },
+ {
+ /* Precision, zero digits */
+ "whoop %.s doo", { "deedle", NULL, },
+ "whoop doo"
+ },
+ {
+ /* Multiple simple arguments */
+ "space %s %s", { "man", "dances", NULL, },
+ "space man dances"
+ },
+ {
+ /* Literal percent */
+ "100%% of space folk dance", { NULL, },
+ "100% of space folk dance"
+ },
+ {
+ /* Multiple simple arguments */
+ "space %2$s %1$s", { "dances", "man", NULL, },
+ "space man dances"
+ },
+ {
+ /* Skipping an argument (not supported by standard printf) */
+ "space %2$s dances", { "dances", "man", NULL, },
+ "space man dances"
+ },
+
+ /* Failures start here */
+
+ {
+ /* Unsupported conversion */
+ "%x", { "blah", NULL, },
+ NULL
+ },
+ {
+ /* Bad positional argument */
+ "space %55$s dances", { "dances", "man", NULL, },
+ NULL
+ },
+ {
+ /* Zero positional argument */
+ "space %0$s dances", { "dances", "man", NULL, },
+ NULL
+ },
+ {
+ /* Too many args used */
+ "%s %s dances", { "space", NULL, },
+ NULL
+ },
+ {
+ /* Too many digits used */
+ "%1234567890s dances", { "space", NULL, },
+ NULL
+ },
+};
+
+
+static void
+callback(void *data, const char *piece, size_t len)
+{
+ char **str = data;
+ *str = talloc_strndup_append(*str, piece, len);
+}
+
+START_TEST(test_safe_format_string_cb)
+{
+ const Fixture *fixture;
+ char *out;
+ int num_args;
+ int ret;
+ void *mem_ctx;
+
+ fixture = &fixtures[_i];
+ mem_ctx = talloc_init("safe-printf");
+
+ for (num_args = 0; fixture->args[num_args] != NULL; )
+ num_args++;
+
+ out = talloc_strdup(mem_ctx, "");
+ ret = safe_format_string_cb(callback, &out, fixture->format,
+ (const char * const*)fixture->args, num_args);
+ if (fixture->result) {
+ ck_assert_int_ge(ret, 0);
+ ck_assert_str_eq(out, fixture->result);
+ ck_assert_int_eq(ret, strlen(out));
+ } else {
+ ck_assert_int_lt(ret, 0);
+ }
+
+ talloc_free(mem_ctx);
+}
+END_TEST
+
+START_TEST(test_safe_format_string)
+{
+ char buffer[8];
+ int ret;
+
+ ret = safe_format_string(buffer, 8, "%s", "space", "man", NULL);
+ ck_assert_int_eq(ret, 5);
+ ck_assert_str_eq(buffer, "space");
+
+ ret = safe_format_string(buffer, 8, "", "space", "man", NULL);
+ ck_assert_int_eq(ret, 0);
+ ck_assert_str_eq(buffer, "");
+
+ ret = safe_format_string(buffer, 8, "the %s %s dances away", "space", "man", NULL);
+ ck_assert_int_eq(ret, 25);
+ ck_assert_str_eq(buffer, "the spa");
+
+ ret = safe_format_string(NULL, 0, "the %s %s dances away", "space", "man", NULL);
+ ck_assert_int_eq(ret, 25);
+
+ ret = safe_format_string(buffer, 8, "%5$s", NULL);
+ ck_assert_int_lt(ret, 0);
+}
+END_TEST
+
+static Suite *
+create_safe_format_suite(void)
+{
+ Suite *s = suite_create("safe-format");
+ TCase *tc_format = tcase_create("safe-format-string");
+
+ /* One for each fixture */
+ tcase_add_loop_test(tc_format, test_safe_format_string_cb, 0,
+ (sizeof (fixtures) / sizeof (fixtures[0])));
+
+ tcase_add_test(tc_format, test_safe_format_string);
+
+ suite_add_tcase(s, tc_format);
+
+ return s;
+}
+
+int
+main(int argc, const char *argv[])
+{
+ int opt;
+ poptContext pc;
+ int failure_count;
+ Suite *suite;
+ SRunner *sr;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_TABLEEND
+ };
+
+ pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ switch(opt) {
+ default:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ return 1;
+ }
+ }
+ poptFreeContext(pc);
+
+ suite = create_safe_format_suite();
+ sr = srunner_create(suite);
+ /* If CK_VERBOSITY is set, use that, otherwise it defaults to CK_NORMAL */
+ srunner_run_all(sr, CK_ENV);
+ failure_count = srunner_ntests_failed(sr);
+ srunner_free(sr);
+ return (failure_count==0 ? EXIT_SUCCESS : EXIT_FAILURE);
+}
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__ */