diff --git a/MANIFEST b/MANIFEST index 21c9caad2..0954560c1 100644 --- a/MANIFEST +++ b/MANIFEST @@ -799,6 +799,7 @@ plugins/sudoers/regress/parser/check_digest.out.ok plugins/sudoers/regress/parser/check_fill.c plugins/sudoers/regress/parser/check_gentime.c plugins/sudoers/regress/parser/check_hexchar.c +plugins/sudoers/regress/serialize_list/check_serialize_list.c plugins/sudoers/regress/starttime/check_starttime.c plugins/sudoers/regress/sudoers/test1.in plugins/sudoers/regress/sudoers/test1.json.ok @@ -1011,6 +1012,7 @@ plugins/sudoers/regress/visudo/test8.out.ok plugins/sudoers/regress/visudo/test8.sh plugins/sudoers/regress/visudo/test9.out.ok plugins/sudoers/regress/visudo/test9.sh +plugins/sudoers/serialize_list.c plugins/sudoers/set_perms.c plugins/sudoers/solaris_audit.c plugins/sudoers/solaris_audit.h @@ -1047,6 +1049,7 @@ plugins/sudoers/toke_util.c plugins/sudoers/tsdump.c plugins/sudoers/tsgetgrpw.c plugins/sudoers/tsgetgrpw.h +plugins/sudoers/unesc_str.c plugins/sudoers/visudo.c plugins/system_group/Makefile.in plugins/system_group/system_group.c diff --git a/plugins/sudoers/Makefile.in b/plugins/sudoers/Makefile.in index e17fb13cf..a61ba3c3f 100644 --- a/plugins/sudoers/Makefile.in +++ b/plugins/sudoers/Makefile.in @@ -158,8 +158,8 @@ PROGS = sudoers.la visudo sudoreplay cvtsudoers testsudoers # Regression tests TEST_PROGS = check_addr check_base64 check_digest check_editor \ check_env_pattern check_exptilde check_fill check_gentime \ - check_hexchar check_iolog_plugin check_starttime \ - check_unesc @SUDOERS_TEST_PROGS@ + check_hexchar check_iolog_plugin check_serialize_list \ + check_starttime check_unesc @SUDOERS_TEST_PROGS@ # Fuzzers LIB_FUZZING_ENGINE = @FUZZ_ENGINE@ @@ -185,9 +185,9 @@ SUDOERS_OBJS = $(AUTH_OBJS) audit.lo boottime.lo check.lo editor.lo env.lo \ sudoers_hooks.lo env_pattern.lo file.lo find_path.lo \ fmtsudoers.lo gc.lo goodpath.lo group_plugin.lo interfaces.lo \ iolog.lo iolog_path_escapes.lo locale.lo log_client.lo \ - logging.lo parse.lo policy.lo prompt.lo set_perms.lo \ - starttime.lo strlcpy_unesc.lo strvec_join.lo sudo_nss.lo \ - sudoers.lo timestamp.lo @SUDOERS_OBJS@ + logging.lo parse.lo policy.lo prompt.lo serialize_list.lo \ + set_perms.lo starttime.lo strlcpy_unesc.lo strvec_join.lo \ + sudo_nss.lo sudoers.lo timestamp.lo unesc_str.lo @SUDOERS_OBJS@ SUDOERS_IOBJS = $(SUDOERS_OBJS:.lo=.i) @@ -241,17 +241,22 @@ CHECK_HEXCHAR_OBJS = check_hexchar.o hexchar.lo sudoers_debug.lo CHECK_IOLOG_PLUGIN_OBJS = check_iolog_plugin.o iolog.lo log_client.lo \ locale.lo pwutil.lo pwutil_impl.lo redblack.lo \ - strlist.lo sudoers_debug.lo + strlist.lo sudoers_debug.lo unesc_str.lo CHECK_SYMBOLS_OBJS = check_symbols.o CHECK_STARTTIME_OBJS = check_starttime.o starttime.lo sudoers_debug.lo -CHECK_UNESC_OBJS = check_unesc.o strlcpy_unesc.lo strvec_join.lo sudoers_debug.lo +CHECK_UNESC_OBJS = check_unesc.o strlcpy_unesc.lo strvec_join.lo \ + sudoers_debug.lo unesc_str.lo + +CHECK_SERIALIZE_LIST_OBJS = check_serialize_list.lo serialize_list.lo \ + sudoers_debug.lo FUZZ_POLICY_OBJS = editor.lo env.lo env_pattern.lo fuzz_policy.o fuzz_stubs.o \ gc.lo iolog_path_escapes.lo locale.lo policy.lo \ - strlcpy_unesc.lo strvec_join.lo sudoers.lo sudoers_hooks.lo + serialize_list.lo strlcpy_unesc.lo strvec_join.lo \ + sudoers.lo sudoers_hooks.lo FUZZ_POLICY_CORPUS = $(srcdir)/regress/corpus/seed/policy/policy.* @@ -386,6 +391,9 @@ check_hexchar: $(CHECK_HEXCHAR_OBJS) $(LIBUTIL) check_iolog_plugin: $(CHECK_IOLOG_PLUGIN_OBJS) $(LIBUTIL) $(LIBIOLOG) $(LIBLOGSRV) $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(CHECK_IOLOG_PLUGIN_OBJS) $(LDFLAGS) $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(LIBIOLOG) $(LIBLOGSRV) @LIBTLS@ +check_serialize_list: $(CHECK_SERIALIZE_LIST_OBJS) $(LIBUTIL) + $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(CHECK_SERIALIZE_LIST_OBJS) $(LDFLAGS) $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(LIBS) + check_starttime: $(CHECK_STARTTIME_OBJS) $(LIBUTIL) $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(CHECK_STARTTIME_OBJS) $(LDFLAGS) $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(LIBS) @@ -659,6 +667,7 @@ check: $(TEST_PROGS) visudo testsudoers cvtsudoers check-fuzzer ./check_hexchar || rval=`expr $$rval + $$?`; \ mkdir -p regress/iolog_plugin; \ ./check_iolog_plugin regress/iolog_plugin/iolog || rval=`expr $$rval + $$?`; \ + ./check_serialize_list || rval=`expr $$rval + $$?`; \ ./check_starttime || rval=`expr $$rval + $$?`; \ ./check_unesc || rval=`expr $$rval + $$?`; \ if test -f check_symbols; then \ @@ -1259,6 +1268,34 @@ check_iolog_plugin.i: $(srcdir)/regress/iolog_plugin/check_iolog_plugin.c \ $(CC) -E -o $@ $(CPPFLAGS) $< check_iolog_plugin.plog: check_iolog_plugin.i rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/iolog_plugin/check_iolog_plugin.c --i-file $< --output-file $@ +check_serialize_list.lo: \ + $(srcdir)/regress/serialize_list/check_serialize_list.c \ + $(devdir)/def_data.h $(incdir)/compat/stdbool.h \ + $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \ + $(incdir)/sudo_debug.h $(incdir)/sudo_eventlog.h \ + $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \ + $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \ + $(incdir)/sudo_util.h $(srcdir)/defaults.h \ + $(srcdir)/logging.h $(srcdir)/parse.h \ + $(srcdir)/sudo_nss.h $(srcdir)/sudoers.h \ + $(srcdir)/sudoers_debug.h $(top_builddir)/config.h \ + $(top_builddir)/pathnames.h + $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/regress/serialize_list/check_serialize_list.c +check_serialize_list.i: \ + $(srcdir)/regress/serialize_list/check_serialize_list.c \ + $(devdir)/def_data.h $(incdir)/compat/stdbool.h \ + $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \ + $(incdir)/sudo_debug.h $(incdir)/sudo_eventlog.h \ + $(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \ + $(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \ + $(incdir)/sudo_util.h $(srcdir)/defaults.h \ + $(srcdir)/logging.h $(srcdir)/parse.h \ + $(srcdir)/sudo_nss.h $(srcdir)/sudoers.h \ + $(srcdir)/sudoers_debug.h $(top_builddir)/config.h \ + $(top_builddir)/pathnames.h + $(CC) -E -o $@ $(CPPFLAGS) $< +check_serialize_list.plog: check_serialize_list.i + rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/serialize_list/check_serialize_list.c --i-file $< --output-file $@ check_starttime.o: $(srcdir)/regress/starttime/check_starttime.c \ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ $(incdir)/sudo_fatal.h $(incdir)/sudo_plugin.h \ @@ -2671,6 +2708,30 @@ securid5.i: $(authdir)/securid5.c $(authdir)/sudo_auth.h $(devdir)/def_data.h \ $(CC) -E -o $@ $(CPPFLAGS) $< securid5.plog: securid5.i rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(authdir)/securid5.c --i-file $< --output-file $@ +serialize_list.lo: $(srcdir)/serialize_list.c $(devdir)/def_data.h \ + $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ + $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \ + $(incdir)/sudo_eventlog.h $(incdir)/sudo_fatal.h \ + $(incdir)/sudo_gettext.h $(incdir)/sudo_plugin.h \ + $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \ + $(srcdir)/defaults.h $(srcdir)/logging.h $(srcdir)/parse.h \ + $(srcdir)/sudo_nss.h $(srcdir)/sudoers.h \ + $(srcdir)/sudoers_debug.h $(top_builddir)/config.h \ + $(top_builddir)/pathnames.h + $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/serialize_list.c +serialize_list.i: $(srcdir)/serialize_list.c $(devdir)/def_data.h \ + $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ + $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \ + $(incdir)/sudo_eventlog.h $(incdir)/sudo_fatal.h \ + $(incdir)/sudo_gettext.h $(incdir)/sudo_plugin.h \ + $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \ + $(srcdir)/defaults.h $(srcdir)/logging.h $(srcdir)/parse.h \ + $(srcdir)/sudo_nss.h $(srcdir)/sudoers.h \ + $(srcdir)/sudoers_debug.h $(top_builddir)/config.h \ + $(top_builddir)/pathnames.h + $(CC) -E -o $@ $(CPPFLAGS) $< +serialize_list.plog: serialize_list.i + rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/serialize_list.c --i-file $< --output-file $@ set_perms.lo: $(srcdir)/set_perms.c $(devdir)/def_data.h \ $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \ @@ -3213,6 +3274,12 @@ tsgetgrpw.i: $(srcdir)/tsgetgrpw.c $(devdir)/def_data.h \ $(CC) -E -o $@ $(CPPFLAGS) $< tsgetgrpw.plog: tsgetgrpw.i rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/tsgetgrpw.c --i-file $< --output-file $@ +unesc_str.lo: $(srcdir)/unesc_str.c + $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/unesc_str.c +unesc_str.i: $(srcdir)/unesc_str.c + $(CC) -E -o $@ $(CPPFLAGS) $< +unesc_str.plog: unesc_str.i + rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/unesc_str.c --i-file $< --output-file $@ visudo.o: $(srcdir)/visudo.c $(devdir)/def_data.h $(devdir)/gram.h \ $(incdir)/compat/getopt.h $(incdir)/compat/stdbool.h \ $(incdir)/sudo_compat.h $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \ diff --git a/plugins/sudoers/iolog.c b/plugins/sudoers/iolog.c index eac5dbaef..9c261456b 100644 --- a/plugins/sudoers/iolog.c +++ b/plugins/sudoers/iolog.c @@ -211,7 +211,6 @@ free_iolog_details(void) /* * Convert a comma-separated list to a string list. - * XXX - handle escaped commas */ static struct sudoers_str_list * deserialize_stringlist(const char *s) @@ -235,6 +234,7 @@ deserialize_stringlist(const char *s) free(str); goto bad; } + unescape_string(str->str); STAILQ_INSERT_TAIL(strlist, str, entries); } if (STAILQ_EMPTY(strlist)) @@ -265,9 +265,9 @@ set_passprompt_regex(const char *cstr) goto bad; } - /* XXX - handle escaped commas */ for ((cp = strtok_r(str, ",", &last)); cp != NULL; (cp = strtok_r(NULL, ",", &last))) { + unescape_string(cp); if (!iolog_pwfilt_add(handle, cp)) goto bad; } diff --git a/plugins/sudoers/policy.c b/plugins/sudoers/policy.c index c75e818c5..26c4be3fe 100644 --- a/plugins/sudoers/policy.c +++ b/plugins/sudoers/policy.c @@ -572,46 +572,6 @@ bad: debug_return_int(MODE_ERROR); } -/* - * Convert struct list_members to a comma-separated string with - * the given variable name. - * XXX - escape commas in member values - */ -static char * -serialize_list(const char *varname, struct list_members *members) -{ - struct list_member *lm, *next; - size_t len, result_size; - char *result; - debug_decl(serialize_list, SUDOERS_DEBUG_PLUGIN); - - result_size = strlen(varname) + 1; - SLIST_FOREACH(lm, members, entries) { - result_size += strlen(lm->value) + 1; - } - if ((result = malloc(result_size)) == NULL) - goto bad; - /* No need to check len for overflow here. */ - len = strlcpy(result, varname, result_size); - result[len++] = '='; - result[len] = '\0'; - SLIST_FOREACH_SAFE(lm, members, entries, next) { - len = strlcat(result, lm->value, result_size); - if (len + (next != NULL) >= result_size) { - sudo_warnx(U_("internal error, %s overflow"), __func__); - goto bad; - } - if (next != NULL) { - result[len++] = ','; - result[len] = '\0'; - } - } - debug_return_str(result); -bad: - free(result); - debug_return_str(NULL); -} - /* * Store the execution environment and other front-end settings. * Builds up the command_info list and sets argv and envp. diff --git a/plugins/sudoers/regress/serialize_list/check_serialize_list.c b/plugins/sudoers/regress/serialize_list/check_serialize_list.c new file mode 100644 index 000000000..7abf4761e --- /dev/null +++ b/plugins/sudoers/regress/serialize_list/check_serialize_list.c @@ -0,0 +1,82 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2022 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include + +#define SUDO_ERROR_WRAP 0 + +#include "sudoers.h" + +sudo_dso_public int main(int argc, char *argv[]); + +static void +test_serialize_list(int *ntests_out, int *errors_out) +{ + int ntests = *ntests_out; + int errors = *errors_out; + const char *expected = "myvar=a value with spaces,this\\,and\\,that,\\,"; + struct list_members members = SLIST_HEAD_INITIALIZER(members); + struct list_member lm1, lm2, lm3; + char *result; + + lm1.value = "a value with spaces"; + lm2.value = "this,and,that"; + lm3.value = ","; + SLIST_INSERT_HEAD(&members, &lm3, entries); + SLIST_INSERT_HEAD(&members, &lm2, entries); + SLIST_INSERT_HEAD(&members, &lm1, entries); + + ntests++; + result = serialize_list("myvar", &members); + if (result == NULL) { + sudo_warnx("serialize_list returns NULL"); + ++errors; + goto done; + } + ntests++; + if (strcmp(result, expected) != 0) { + sudo_warnx("got \"%s\", expected \"%s\"", result, expected); + ++errors; + goto done; + } + +done: + *ntests_out = ntests; + *errors_out = errors; +} + +int +main(int argc, char *argv[]) +{ + int ntests = 0, errors = 0; + + initprogname(argc > 0 ? argv[0] : "check_serialize_list"); + + test_serialize_list(&ntests, &errors); + + if (ntests != 0) { + printf("%s: %d tests run, %d errors, %d%% success rate\n", + getprogname(), ntests, errors, (ntests - errors) * 100 / ntests); + } + + exit(errors); +} diff --git a/plugins/sudoers/regress/unescape/check_unesc.c b/plugins/sudoers/regress/unescape/check_unesc.c index 26fcd7788..6b97f5588 100644 --- a/plugins/sudoers/regress/unescape/check_unesc.c +++ b/plugins/sudoers/regress/unescape/check_unesc.c @@ -1,7 +1,7 @@ /* * SPDX-License-Identifier: ISC * - * Copyright (c) 2021 Todd C. Miller + * Copyright (c) 2021-2022 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -31,7 +31,10 @@ struct test_data { char *result; size_t result_len; size_t bufsize; -} test_data[] = { +}; + +/* strlcpy_unescape() does not unescape whitespace */ +static struct test_data strlcpy_unescape_test_data[] = { { "\\\0ABC", "\\", 1, 2 }, /* 1 */ { "\\ \\;", "\\ ;", 3, 4 }, /* 2 */ { "\\\t\\;", "\\\t;", 3, 4 }, /* 3 */ @@ -43,6 +46,20 @@ struct test_data { { NULL } }; +/* unescape_string() _does_ unescape whitespace */ +static struct test_data unescape_string_test_data[] = { + { "foo\\ bar", "foo bar", 7, 8 }, /* 1 */ + { "foo\\,bar", "foo,bar", 7, 8 }, /* 2 */ + { "baz \\", "baz \\", 5, 5 }, /* 3 */ + { "\\foo", "foo", 3, 4 }, /* 4 */ + { "var=aaa,b\\,b", "var=aaa,b,b", 11, 12 }, /* 5 */ + { "\\a\\ b\\ c\\\\", "a b c\\", 6, 10 }, /* 6 */ + { "\\", "\\", 1, 1 }, /* 7 */ + { "foo", "foo", 3, 3 }, /* 8 */ + { "", "", 0, 0 }, /* 9 */ + { NULL } +}; + sudo_dso_public int main(int argc, char *argv[]); static void @@ -54,7 +71,7 @@ test_strlcpy_unescape(int *ntests_out, int *errors_out) char buf[1024]; size_t len; - for (td = test_data; td->input != NULL; td++) { + for (td = strlcpy_unescape_test_data; td->input != NULL; td++) { ntests++; memset(buf, 'A', sizeof(buf)); len = strlcpy_unescape(buf, td->input, td->bufsize); @@ -85,6 +102,31 @@ test_strlcpy_unescape(int *ntests_out, int *errors_out) *errors_out = errors; } +static void +test_unescape_string(int *ntests_out, int *errors_out) +{ + int ntests = *ntests_out; + int errors = *errors_out; + struct test_data *td; + char buf[1024]; + + for (td = unescape_string_test_data; td->input != NULL; td++) { + ntests++; + memset(buf, 'A', sizeof(buf)); + memcpy(buf, td->input, td->bufsize); + buf[td->bufsize] = '\0'; + unescape_string(buf); + if (strcmp(td->result, buf) != 0) { + sudo_warnx("%d: \"%s\": got \"%s\", expected \"%s\"", + ntests, td->input, buf, td->result); + errors++; + } + } + + *ntests_out = ntests; + *errors_out = errors; +} + static void test_strvec_join(char sep, int *ntests_out, int *errors_out) { @@ -132,6 +174,9 @@ main(int argc, char *argv[]) /* strlcpy_unescape tests */ test_strlcpy_unescape(&ntests, &errors); + /* unescape_string test */ + test_unescape_string(&ntests, &errors); + /* strvec_join test */ test_strvec_join(' ', &ntests, &errors); test_strvec_join('\n', &ntests, &errors); diff --git a/plugins/sudoers/serialize_list.c b/plugins/sudoers/serialize_list.c new file mode 100644 index 000000000..2c1f91637 --- /dev/null +++ b/plugins/sudoers/serialize_list.c @@ -0,0 +1,82 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2019, 2022 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include + +#include +#include +#include + +#include "sudoers.h" + +/* + * Convert struct list_members to a comma-separated string with + * the given variable name. Escapes backslashes and commas. + */ +char * +serialize_list(const char *varname, struct list_members *members) +{ + struct list_member *lm, *next; + size_t len, result_size; + char *cp, *result; + debug_decl(serialize_list, SUDOERS_DEBUG_PLUGIN); + + result_size = strlen(varname) + 1; + SLIST_FOREACH(lm, members, entries) { + for (cp = lm->value; *cp != '\0'; cp++) { + result_size++; + if (*cp == '\\' || *cp == ',') + result_size++; + } + result_size++; + } + if ((result = malloc(result_size)) == NULL) + goto bad; + /* No need to check len for overflow here. */ + len = strlcpy(result, varname, result_size); + result[len++] = '='; + SLIST_FOREACH_SAFE(lm, members, entries, next) { + for (cp = lm->value; *cp != '\0'; cp++) { + bool escape = (*cp == '\\' || *cp == ','); + if (len + 1 + escape >= result_size) { + sudo_warnx(U_("internal error, %s overflow"), __func__); + goto bad; + } + if (escape) + result[len++] = '\\'; + result[len++] = *cp; + } + if (next != NULL) { + if (len + 1 >= result_size) { + sudo_warnx(U_("internal error, %s overflow"), __func__); + goto bad; + } + result[len++] = ','; + } + result[len] = '\0'; + } + debug_return_str(result); +bad: + free(result); + debug_return_str(NULL); +} diff --git a/plugins/sudoers/sudoers.h b/plugins/sudoers/sudoers.h index a5dfb2de4..f1592bab9 100644 --- a/plugins/sudoers/sudoers.h +++ b/plugins/sudoers/sudoers.h @@ -467,4 +467,10 @@ size_t strlcpy_unescape(char *dst, const char *src, size_t size); /* strvec_join.c */ char *strvec_join(char *const argv[], char sep, size_t (*cpy)(char *, const char *, size_t)); +/* unesc_str.c */ +void unescape_string(char *str); + +/* serialize_list.c */ +char *serialize_list(const char *varname, struct list_members *members); + #endif /* SUDOERS_SUDOERS_H */ diff --git a/plugins/sudoers/unesc_str.c b/plugins/sudoers/unesc_str.c new file mode 100644 index 000000000..763e1ceac --- /dev/null +++ b/plugins/sudoers/unesc_str.c @@ -0,0 +1,42 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2022 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include + +/* + * Remove backslash escape chars. + */ +void +unescape_string(char *str) +{ + char *cp = str; + char *ep = str + strlen(str); + + while ((cp = strchr(cp, '\\')) != NULL) { + if (cp[1] == '\0') + break; + memmove(cp, cp + 1, (size_t)(ep - cp)); + cp++; + ep--; + } +}