diff options
-rw-r--r-- | Makefile.am | 16 | ||||
-rw-r--r-- | src/tests/safe-format-tests.c | 244 | ||||
-rw-r--r-- | src/util/safe-format-string.c | 309 | ||||
-rw-r--r-- | src/util/safe-format-string.h | 81 |
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__ */ |