diff options
Diffstat (limited to 'lib/util/regress')
61 files changed, 4438 insertions, 0 deletions
diff --git a/lib/util/regress/closefrom/closefrom_test.c b/lib/util/regress/closefrom/closefrom_test.c new file mode 100644 index 0000000..375bb51 --- /dev/null +++ b/lib/util/regress/closefrom/closefrom_test.c @@ -0,0 +1,121 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2022 Todd C. Miller <Todd.Miller@sudo.ws> + * + * 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 <config.h> + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define SUDO_ERROR_WRAP 0 + +#include "sudo_compat.h" +#include "sudo_fatal.h" +#include "sudo_util.h" + +sudo_dso_public int main(int argc, char *argv[]); + +/* + * Test that sudo_closefrom() works as expected. + */ + +int +main(int argc, char *argv[]) +{ + int ch, fds[2], flag, maxfd, minfd, errors = 0, ntests = 0; + initprogname(argc > 0 ? argv[0] : "closefrom_test"); + + while ((ch = getopt(argc, argv, "v")) != -1) { + switch (ch) { + case 'v': + /* ignore */ + break; + default: + fprintf(stderr, "usage: %s [-v]\n", getprogname()); + return EXIT_FAILURE; + } + } + argc -= optind; + argv += optind; + + /* We use pipe() because it doesn't rely on the filesystem. */ + ntests++; + if (pipe(fds) == -1) { + sudo_warn("%s", "pipe"); + errors++; + goto done; + } + maxfd = MAX(fds[0], fds[1]); + minfd = MIN(fds[0], fds[1]); + + /* Close any fds greater than fds[0] and fds[1]. */ + sudo_closefrom(maxfd + 1); + + /* Verify that sudo_closefrom() didn't close fds[0] or fds[1]. */ + ntests++; + if (fcntl(fds[0], F_GETFL, 0) == -1) { + sudo_warnx("fd %d closed prematurely", fds[0]); + errors++; + goto done; + } + ntests++; + if (fcntl(fds[1], F_GETFL, 0) == -1) { + sudo_warnx("fd %d closed prematurely", fds[1]); + errors++; + goto done; + } + + /* Close fds[0], fds[1] and above. */ + sudo_closefrom(minfd); + + /* Verify that sudo_closefrom() closed both fds. */ + ntests++; + flag = fcntl(fds[0], F_GETFD, 0); +#ifdef __APPLE__ + /* We only set the close-on-exec flag on macOS. */ + if (flag == 1) + flag = -1; +#endif + if (flag != -1) { + sudo_warnx("fd %d still open", fds[0]); + errors++; + goto done; + } + ntests++; + flag = fcntl(fds[1], F_GETFD, 0); +#ifdef __APPLE__ + /* We only set the close-on-exec flag on macOS. */ + if (flag == 1) + flag = -1; +#endif + if (flag != -1) { + sudo_warnx("fd %d still open", fds[1]); + errors++; + goto done; + } + +done: + if (ntests != 0) { + printf("%s: %d tests run, %d errors, %d%% success rate\n", + getprogname(), ntests, errors, (ntests - errors) * 100 / ntests); + } + return errors; +} diff --git a/lib/util/regress/corpus/seed/sudo_conf/sudo.conf.1 b/lib/util/regress/corpus/seed/sudo_conf/sudo.conf.1 new file mode 100644 index 0000000..1a58c87 --- /dev/null +++ b/lib/util/regress/corpus/seed/sudo_conf/sudo.conf.1 @@ -0,0 +1,116 @@ +# +# Default /etc/sudo.conf file +# +# Sudo plugins: +# Plugin plugin_name plugin_path plugin_options ... +# +# The plugin_path is relative to /usr/local/libexec/sudo unless +# fully qualified. +# The plugin_name corresponds to a global symbol in the plugin +# that contains the plugin interface structure. +# The plugin_options are optional. +# +# The sudoers plugin is used by default if no Plugin lines are present. +#Plugin sudoers_policy sudoers.so +#Plugin sudoers_io sudoers.so +#Plugin sudoers_audit sudoers.so + +# +# Sudo askpass: +# Path askpass /path/to/askpass +# +# An askpass helper program may be specified to provide a graphical +# password prompt for "sudo -A" support. Sudo does not ship with its +# own askpass program but can use the OpenSSH askpass. +# +# Use the OpenSSH askpass +#Path askpass /usr/X11R6/bin/ssh-askpass +# +# Use the Gnome OpenSSH askpass +#Path askpass /usr/libexec/openssh/gnome-ssh-askpass + +# +# Sudo device search path: +# Path devsearch /dev/path1:/dev/path2:/dev +# +# A colon-separated list of paths to check when searching for a user's +# terminal device. +# +#Path devsearch /dev/pts:/dev/vt:/dev/term:/dev/zcons:/dev/pty:/dev + +# +# Sudo noexec: +# Path noexec /path/to/sudo_noexec.so +# +# Path to a shared library containing replacements for the execv(), +# execve() and fexecve() library functions that just return an error. +# This is used to implement the "noexec" functionality on systems that +# support LD_PRELOAD or its equivalent. +# +# The compiled-in value is usually sufficient and should only be changed +# if you rename or move the sudo_noexec.so file. +# +#Path noexec /usr/local/libexec/sudo/sudo_noexec.so + +# +# Sudo plugin directory: +# Path plugin_dir /path/to/plugins +# +# The default directory to use when searching for plugins that are +# specified without a fully qualified path name. +# +#Path plugin_dir /usr/local/libexec/sudo + +# +# Core dumps: +# Set disable_coredump true|false +# +# By default, sudo disables core dumps while it is executing (they +# are re-enabled for the command that is run). +# To aid in debugging sudo problems, you may wish to enable core +# dumps by setting "disable_coredump" to false. +# +#Set disable_coredump false + +# +# User groups: +# Set group_source static|dynamic|adaptive +# +# Sudo passes the user's group list to the policy plugin. +# If the user is a member of the maximum number of groups (usually 16), +# sudo will query the group database directly to be sure to include +# the full list of groups. +# +# On some systems, this can be expensive so the behavior is configurable. +# The "group_source" setting has three possible values: +# static - use the user's list of groups returned by the kernel. +# dynamic - query the group database to find the list of groups. +# adaptive - if user is in less than the maximum number of groups. +# use the kernel list, else query the group database. +# +#Set group_source static + +# +# Sudo interface probing: +# Set probe_interfaces true|false +# +# By default, sudo will probe the system's network interfaces and +# pass the IP address of each enabled interface to the policy plugin. +# On systems with a large number of virtual interfaces this may take +# a noticeable amount of time. +# +#Set probe_interfaces false + +# +# Sudo debug files: +# Debug program /path/to/debug_log subsystem@priority[,subsyste@priority] +# +# Sudo and related programs support logging debug information to a file. +# The program is typically sudo, sudoers.so, sudoreplay or visudo. +# +# Subsystems vary based on the program; "all" matches all subsystems. +# Priority may be crit, err, warn, notice, diag, info, trace or debug. +# Multiple subsystem@priority may be specified, separated by a comma. +# +#Debug sudo /var/log/sudo_debug all@debug +#Debug sudoers.so /var/log/sudoers_debug all@debug diff --git a/lib/util/regress/corpus/seed/sudo_conf/sudo.conf.2 b/lib/util/regress/corpus/seed/sudo_conf/sudo.conf.2 new file mode 100644 index 0000000..05039a5 --- /dev/null +++ b/lib/util/regress/corpus/seed/sudo_conf/sudo.conf.2 @@ -0,0 +1,116 @@ +# +# Default /etc/sudo.conf file +# +# Sudo plugins: +# Plugin plugin_name plugin_path plugin_options ... +# +# The plugin_path is relative to /usr/local/libexec/sudo unless +# fully qualified. +# The plugin_name corresponds to a global symbol in the plugin +# that contains the plugin interface structure. +# The plugin_options are optional. +# +# The sudoers plugin is used by default if no Plugin lines are present. +Plugin sudoers_policy sudoers.so +Plugin sudoers_io sudoers.so +Plugin sudoers_audit sudoers.so + +# +# Sudo askpass: +# Path askpass /path/to/askpass +# +# An askpass helper program may be specified to provide a graphical +# password prompt for "sudo -A" support. Sudo does not ship with its +# own askpass program but can use the OpenSSH askpass. +# +# Use the OpenSSH askpass +Path askpass /usr/X11R6/bin/ssh-askpass +# +# Use the Gnome OpenSSH askpass +Path askpass /usr/libexec/openssh/gnome-ssh-askpass + +# +# Sudo device search path: +# Path devsearch /dev/path1:/dev/path2:/dev +# +# A colon-separated list of paths to check when searching for a user's +# terminal device. +# +Path devsearch /dev/pts:/dev/vt:/dev/term:/dev/zcons:/dev/pty:/dev + +# +# Sudo noexec: +# Path noexec /path/to/sudo_noexec.so +# +# Path to a shared library containing replacements for the execv(), +# execve() and fexecve() library functions that just return an error. +# This is used to implement the "noexec" functionality on systems that +# support LD_PRELOAD or its equivalent. +# +# The compiled-in value is usually sufficient and should only be changed +# if you rename or move the sudo_noexec.so file. +# +Path noexec /usr/local/libexec/sudo/sudo_noexec.so + +# +# Sudo plugin directory: +# Path plugin_dir /path/to/plugins +# +# The default directory to use when searching for plugins that are +# specified without a fully qualified path name. +# +Path plugin_dir /usr/local/libexec/sudo + +# +# Core dumps: +# Set disable_coredump true|false +# +# By default, sudo disables core dumps while it is executing (they +# are re-enabled for the command that is run). +# To aid in debugging sudo problems, you may wish to enable core +# dumps by setting "disable_coredump" to false. +# +Set disable_coredump false + +# +# User groups: +# Set group_source static|dynamic|adaptive +# +# Sudo passes the user's group list to the policy plugin. +# If the user is a member of the maximum number of groups (usually 16), +# sudo will query the group database directly to be sure to include +# the full list of groups. +# +# On some systems, this can be expensive so the behavior is configurable. +# The "group_source" setting has three possible values: +# static - use the user's list of groups returned by the kernel. +# dynamic - query the group database to find the list of groups. +# adaptive - if user is in less than the maximum number of groups. +# use the kernel list, else query the group database. +# +Set group_source static + +# +# Sudo interface probing: +# Set probe_interfaces true|false +# +# By default, sudo will probe the system's network interfaces and +# pass the IP address of each enabled interface to the policy plugin. +# On systems with a large number of virtual interfaces this may take +# a noticeable amount of time. +# +Set probe_interfaces false + +# +# Sudo debug files: +# Debug program /path/to/debug_log subsystem@priority[,subsyste@priority] +# +# Sudo and related programs support logging debug information to a file. +# The program is typically sudo, sudoers.so, sudoreplay or visudo. +# +# Subsystems vary based on the program; "all" matches all subsystems. +# Priority may be crit, err, warn, notice, diag, info, trace or debug. +# Multiple subsystem@priority may be specified, separated by a comma. +# +Debug sudo /var/log/sudo_debug all@debug +Debug sudoers.so /var/log/sudoers_debug all@debug diff --git a/lib/util/regress/corpus/seed/sudo_conf/sudo.conf.3 b/lib/util/regress/corpus/seed/sudo_conf/sudo.conf.3 new file mode 100644 index 0000000..bcfafb2 --- /dev/null +++ b/lib/util/regress/corpus/seed/sudo_conf/sudo.conf.3 @@ -0,0 +1,126 @@ +# +# Default /etc/sudo.conf file +# +# Sudo plugins: +# Plugin plugin_name plugin_path plugin_options ... +# +# The plugin_path is relative to /usr/local/libexec/sudo unless +# fully qualified. +# The plugin_name corresponds to a global symbol in the plugin +# that contains the plugin interface structure. +# The plugin_options are optional. +# +# The sudoers plugin is used by default if no Plugin lines are present. +Plugin sudoers_policy sudoers.so +Plugin sudoers_io sudoers.so +Plugin sudoers_audit sudoers.so + +# +# Sudo askpass: +# Path askpass /path/to/askpass +# +# An askpass helper program may be specified to provide a graphical +# password prompt for "sudo -A" support. Sudo does not ship with its +# own askpass program but can use the OpenSSH askpass. +# +# Use the OpenSSH askpass +Path askpass /usr/X11R6/bin/ssh-askpass +# +# Use the Gnome OpenSSH askpass +Path askpass /usr/libexec/openssh/gnome-ssh-askpass + +# +# Sudo device search path: +# Path devsearch /dev/path1:/dev/path2:/dev +# +# A colon-separated list of paths to check when searching for a user's +# terminal device. +# +Path devsearch /dev/pts:/dev/vt:/dev/term:/dev/zcons:/dev/pty:/dev + +# +# Sudo noexec: +# Path noexec /path/to/sudo_noexec.so +# +# Path to a shared library containing replacements for the execv(), +# execve() and fexecve() library functions that just return an error. +# This is used to implement the "noexec" functionality on systems that +# support LD_PRELOAD or its equivalent. +# +# The compiled-in value is usually sufficient and should only be changed +# if you rename or move the sudo_noexec.so file. +# +Path noexec /usr/local/libexec/sudo/sudo_noexec.so + +# +# Sudo plugin directory: +# Path plugin_dir /path/to/plugins +# +# The default directory to use when searching for plugins that are +# specified without a fully qualified path name. +# +Path plugin_dir /usr/local/libexec/sudo + +# +# Path to the sesh binary for SELinux support +# +Path sesh /usr/local/libexec/sudo/sesh + +# +# Core dumps: +# Set disable_coredump true|false +# +# By default, sudo disables core dumps while it is executing (they +# are re-enabled for the command that is run). +# To aid in debugging sudo problems, you may wish to enable core +# dumps by setting "disable_coredump" to false. +# +Set disable_coredump true + +# +# User groups: +# Set group_source static|dynamic|adaptive +# +# Sudo passes the user's group list to the policy plugin. +# If the user is a member of the maximum number of groups (usually 16), +# sudo will query the group database directly to be sure to include +# the full list of groups. +# +# On some systems, this can be expensive so the behavior is configurable. +# The "group_source" setting has three possible values: +# static - use the user's list of groups returned by the kernel. +# dynamic - query the group database to find the list of groups. +# adaptive - if user is in less than the maximum number of groups. +# use the kernel list, else query the group database. +# +Set group_source dynamic + +# +# Maximum number of groups to use +# +Set max_groups 8 + +# +# Sudo interface probing: +# Set probe_interfaces true|false +# +# By default, sudo will probe the system's network interfaces and +# pass the IP address of each enabled interface to the policy plugin. +# On systems with a large number of virtual interfaces this may take +# a noticeable amount of time. +# +Set probe_interfaces true + +# +# Sudo debug files: +# Debug program /path/to/debug_log subsystem@priority[,subsyste@priority] +# +# Sudo and related programs support logging debug information to a file. +# The program is typically sudo, sudoers.so, sudoreplay or visudo. +# +# Subsystems vary based on the program; "all" matches all subsystems. +# Priority may be crit, err, warn, notice, diag, info, trace or debug. +# Multiple subsystem@priority may be specified, separated by a comma. +# +Debug sudo /var/log/sudo_debug all@debug +Debug sudoers.so /var/log/sudoers_debug all@debug diff --git a/lib/util/regress/fnmatch/fnm_test.c b/lib/util/regress/fnmatch/fnm_test.c new file mode 100644 index 0000000..126683b --- /dev/null +++ b/lib/util/regress/fnmatch/fnm_test.c @@ -0,0 +1,92 @@ +/* $OpenBSD: fnm_test.c,v 1.1 2008/10/01 23:04:58 millert Exp $ */ + +/* + * Public domain, 2008, Todd C. Miller <Todd.Miller@sudo.ws> + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#ifdef HAVE_FNMATCH +# include <fnmatch.h> +#else +# include "compat/fnmatch.h" +#endif + +#include "sudo_compat.h" +#include "sudo_util.h" + +sudo_dso_public int main(int argc, char *argv[]); + +int +main(int argc, char *argv[]) +{ + FILE *fp = stdin; + char pattern[1024], string[1024], flagstr[1024]; + int ch, errors = 0, ntests = 0, flags, got, want; + + initprogname(argc > 0 ? argv[0] : "fnm_test"); + + while ((ch = getopt(argc, argv, "v")) != -1) { + switch (ch) { + case 'v': + /* ignore */ + break; + default: + fprintf(stderr, "usage: %s [-v]\n", getprogname()); + return EXIT_FAILURE; + } + } + argc -= optind; + argv += optind; + + if (argc > 0) { + if ((fp = fopen(argv[0], "r")) == NULL) { + perror(argv[0]); + return EXIT_FAILURE; + } + } + + /* + * Read in test file, which is formatted thusly: + * + * pattern string flags expected_result + * + */ + for (;;) { + got = fscanf(fp, "%s %s %s %d\n", pattern, string, flagstr, + &want); + if (got == EOF) + break; + if (got == 4) { + flags = 0; + if (strcmp(flagstr, "FNM_NOESCAPE") == 0) + flags |= FNM_NOESCAPE; + else if (strcmp(flagstr, "FNM_PATHNAME") == 0) + flags |= FNM_PATHNAME; + else if (strcmp(flagstr, "FNM_PERIOD") == 0) + flags |= FNM_PERIOD; + else if (strcmp(flagstr, "FNM_LEADING_DIR") == 0) + flags |= FNM_LEADING_DIR; + else if (strcmp(flagstr, "FNM_CASEFOLD") == 0) + flags |= FNM_CASEFOLD; + got = fnmatch(pattern, string, flags); + if (got != want) { + fprintf(stderr, + "fnmatch: %s %s %d: want %d, got %d\n", + pattern, string, flags, want, got); + errors++; + } + ntests++; + } + } + if (ntests != 0) { + printf("fnmatch: %d test%s run, %d errors, %d%% success rate\n", + ntests, ntests == 1 ? "" : "s", errors, + (ntests - errors) * 100 / ntests); + } + return errors; +} diff --git a/lib/util/regress/fnmatch/fnm_test.in b/lib/util/regress/fnmatch/fnm_test.in new file mode 100644 index 0000000..3f53f93 --- /dev/null +++ b/lib/util/regress/fnmatch/fnm_test.in @@ -0,0 +1,6 @@ +/bin/[[:alpha:][:alnum:]]* /bin/ls FNM_PATHNAME 0 +/bin/[[:alpha:][:alnum:]]* /bin/LS FNM_CASEFOLD 0 +/bin/[[:opper:][:alnum:]]* /bin/ls NONE 1 +[[:alpha:][:alnum:]]*.c foo1.c FNM_PERIOD 0 +[[:upper:]]* FOO NONE 0 +[![:space:]]* bar NONE 0 diff --git a/lib/util/regress/fuzz/fuzz_sudo_conf.c b/lib/util/regress/fuzz/fuzz_sudo_conf.c new file mode 100644 index 0000000..4e70086 --- /dev/null +++ b/lib/util/regress/fuzz/fuzz_sudo_conf.c @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2021 Todd C. Miller <Todd.Miller@sudo.ws> + * + * 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 <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <unistd.h> +#if defined(HAVE_STDINT_H) +# include <stdint.h> +#elif defined(HAVE_INTTYPES_H) +# include <inttypes.h> +#endif + +#define SUDO_ERROR_WRAP 0 + +#include "sudo_compat.h" +#include "sudo_conf.h" +#include "sudo_debug.h" +#include "sudo_fatal.h" +#include "sudo_plugin.h" +#include "sudo_util.h" + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); + +static int +fuzz_conversation(int num_msgs, const struct sudo_conv_message msgs[], + struct sudo_conv_reply replies[], struct sudo_conv_callback *callback) +{ + int n; + + for (n = 0; n < num_msgs; n++) { + const struct sudo_conv_message *msg = &msgs[n]; + + switch (msg->msg_type & 0xff) { + case SUDO_CONV_PROMPT_ECHO_ON: + case SUDO_CONV_PROMPT_MASK: + case SUDO_CONV_PROMPT_ECHO_OFF: + /* input not supported */ + return -1; + case SUDO_CONV_ERROR_MSG: + case SUDO_CONV_INFO_MSG: + /* no output for fuzzers */ + break; + default: + return -1; + } + } + return 0; +} + +int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + struct plugin_info_list *plugins = sudo_conf_plugins(); + struct sudo_conf_debug_list *debug_list = sudo_conf_debugging(); + struct sudo_conf_debug_file_list *debug_files; + char tempfile[] = "/tmp/sudo_conf.XXXXXX"; + struct sudo_conf_debug *debug_spec; + struct sudo_debug_file *debug_file; + struct plugin_info *info; + size_t nwritten; + int fd; + + initprogname("fuzz_sudo_conf"); + if (getenv("SUDO_FUZZ_VERBOSE") == NULL) + sudo_warn_set_conversation(fuzz_conversation); + + /* sudo_conf_read() uses a conf file path, not an open file. */ + fd = mkstemp(tempfile); + if (fd == -1) + return 0; + nwritten = write(fd, data, size); + if (nwritten != size) { + close(fd); + return 0; + } + close(fd); + + /* sudo_conf_read() will re-init and free old data each time it runs. */ + sudo_conf_clear_paths(); + sudo_conf_read(tempfile, SUDO_CONF_ALL); + + /* Path settings. */ + if (sudo_conf_askpass_path() != NULL) + sudo_warnx("Path askpass %s", sudo_conf_askpass_path()); + if (sudo_conf_sesh_path() != NULL) + sudo_warnx("Path sesh %s", sudo_conf_sesh_path()); + if (sudo_conf_intercept_path() != NULL) + sudo_warnx("Path intercept %s", sudo_conf_intercept_path()); + if (sudo_conf_noexec_path() != NULL) + sudo_warnx("Path noexec %s", sudo_conf_noexec_path()); + if (sudo_conf_plugin_dir_path() != NULL) + sudo_warnx("Path plugin_dir %s", sudo_conf_plugin_dir_path()); + + /* Other settings. */ + sudo_warnx("Set disable_coredump %s", + sudo_conf_disable_coredump() ? "true" : "false"); + sudo_warnx("Set group_source %s", + sudo_conf_group_source() == GROUP_SOURCE_ADAPTIVE ? "adaptive" : + sudo_conf_group_source() == GROUP_SOURCE_STATIC ? "static" : "dynamic"); + sudo_warnx("Set max_groups %d", sudo_conf_max_groups()); + sudo_warnx("Set probe_interfaces %s", + sudo_conf_probe_interfaces() ? "true" : "false"); + + /* Plugins. */ + plugins = sudo_conf_plugins(); + TAILQ_FOREACH(info, plugins, entries) { + /* We don't bother with the plugin options. */ + sudo_warnx("Plugin %s %s", info->symbol_name, info->path); + } + + /* Debug settings. */ + debug_list = sudo_conf_debugging(); + TAILQ_FOREACH(debug_spec, debug_list, entries) { + TAILQ_FOREACH(debug_file, &debug_spec->debug_files, entries) { + sudo_warnx("Debug %s %s %s", debug_spec->progname, + debug_file->debug_file, debug_file->debug_flags); + } + } + + debug_files = sudo_conf_debug_files(getprogname()); + if (debug_files != NULL) { + TAILQ_FOREACH(debug_file, debug_files, entries) { + sudo_warnx("Debug %s %s %s", getprogname(), + debug_file->debug_file, debug_file->debug_flags); + } + } + + unlink(tempfile); + + fflush(stdout); + + return 0; +} diff --git a/lib/util/regress/fuzz/fuzz_sudo_conf.dict b/lib/util/regress/fuzz/fuzz_sudo_conf.dict new file mode 100644 index 0000000..4b26917 --- /dev/null +++ b/lib/util/regress/fuzz/fuzz_sudo_conf.dict @@ -0,0 +1,18 @@ +# sudo.conf keywords +"Debug" +"Path" +"Plugin" +"Set" + +# Paths +"askpass" +"sesh" +"noexec" +"plugin_dir" +"devsearch" + +# Variables +"disable_coredump" +"group_source" +"max_groups" +"probe_interfaces" diff --git a/lib/util/regress/getdelim/getdelim_test.c b/lib/util/regress/getdelim/getdelim_test.c new file mode 100644 index 0000000..2e77fd2 --- /dev/null +++ b/lib/util/regress/getdelim/getdelim_test.c @@ -0,0 +1,185 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2019-2020 Todd C. Miller <Todd.Miller@sudo.ws> + * + * 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 <config.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef HAVE_STDBOOL_H +# include <stdbool.h> +#else +# include "compat/stdbool.h" +#endif +#include <limits.h> +#include <unistd.h> + +#include "sudo_compat.h" +#include "sudo_fatal.h" +#include "sudo_util.h" + +sudo_dso_public int main(int argc, char *argv[]); + +ssize_t sudo_getdelim(char **bufp, size_t *bufsizep, int delim, FILE *fp); + +/* + * Test that sudo_getdelim() works as expected. + */ + +struct getdelim_test { + const char *input; + const char *output[4]; + int delim; +}; + +static char longstr[LINE_MAX * 4]; +static struct getdelim_test test_data[] = { + { "a\nb\nc\n", { "a\n", "b\n", "c\n", NULL }, '\n' }, + { "a\nb\nc", { "a\n", "b\n", "c", NULL }, '\n' }, + { "a\tb\tc\t", { "a\t", "b\t", "c\t", NULL }, '\t' }, + { "a\tb\tc", { "a\t", "b\t", "c", NULL }, '\t' }, + { longstr, { longstr, NULL }, '\n' }, + { NULL, { NULL }, '\0' } +}; + +static int errors = 0, ntests = 0; + +static void +runtests(char **buf, size_t *buflen) +{ + int i, j, sv[2]; + pid_t pid; + FILE *fp; + + /* Exercise realloc case by injecting an entry > LINE_MAX. */ + memset(longstr, 'A', sizeof(longstr) - 2); + longstr[sizeof(longstr) - 2] = '\n'; + longstr[sizeof(longstr) - 1] = '\0'; + + for (i = 0; test_data[i].input != NULL; i++) { + if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1) + sudo_fatal_nodebug("socketpair"); + + switch ((pid = fork())) { + case -1: + sudo_fatal_nodebug("fork"); + case 0: + /* child */ + close(sv[0]); + if (send(sv[1], test_data[i].input, strlen(test_data[i].input), 0) == -1) { + sudo_warn_nodebug("send"); + _exit(127); + } + _exit(EXIT_SUCCESS); + break; + default: + /* parent */ + break; + } + + close(sv[1]); + if ((fp = fdopen(sv[0], "r")) == NULL) + sudo_fatal_nodebug("fdopen"); + + for (j = 0; test_data[i].output[j] != NULL; j++) { + ntests++; + alarm(10); + if (sudo_getdelim(buf, buflen, test_data[i].delim, fp) == -1) + sudo_fatal_nodebug("sudo_getdelim"); + alarm(0); + if (strcmp(*buf, test_data[i].output[j]) != 0) { + sudo_warnx_nodebug("failed test #%d: expected %s, got %s", + ntests, test_data[i].output[j], *buf); + errors++; + } + } + + /* test EOF */ + ntests++; + alarm(30); + if (sudo_getdelim(buf, buflen, test_data[i].delim, fp) != -1) { + sudo_warnx_nodebug("failed test #%d: expected EOF, got %s", + ntests, *buf); + errors++; + } else { + if (!feof(fp)) { + sudo_warn_nodebug("failed test #%d: expected EOF, got error", + ntests); + errors++; + } + } + + /* test error by closing the underlying fd. */ + clearerr(fp); + close(fileno(fp)); + ntests++; + alarm(30); + if (sudo_getdelim(buf, buflen, test_data[i].delim, fp) != -1) { + sudo_warnx_nodebug("failed test #%d: expected error, got %s", + ntests, *buf); + errors++; + } else { + /* Use feof(3), not ferror(3) so we can detect out of memory. */ + if (feof(fp)) { + sudo_warn_nodebug("failed test #%d: expected error, got EOF", + ntests); + errors++; + } + } + + fclose(fp); + waitpid(pid, NULL, 0); + alarm(0); + } +} + +int +main(int argc, char *argv[]) +{ + size_t buflen = 0; + char *buf = NULL; + int ch; + + initprogname(argc > 0 ? argv[0] : "getdelim_test"); + + while ((ch = getopt(argc, argv, "v")) != -1) { + switch (ch) { + case 'v': + /* ignore */ + break; + default: + fprintf(stderr, "usage: %s [-v]\n", getprogname()); + return EXIT_FAILURE; + } + } + argc -= optind; + argv += optind; + + runtests(&buf, &buflen); + free(buf); + + /* XXX - redo tests with preallocated buffer filled with junk */ + if (ntests != 0) { + printf("%s: %d tests run, %d errors, %d%% success rate\n", + getprogname(), ntests, errors, (ntests - errors) * 100 / ntests); + } + return errors; +} diff --git a/lib/util/regress/getgrouplist/getgids.c b/lib/util/regress/getgrouplist/getgids.c new file mode 100644 index 0000000..f366f83 --- /dev/null +++ b/lib/util/regress/getgrouplist/getgids.c @@ -0,0 +1,91 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2021 Todd C. Miller <Todd.Miller@sudo.ws> + * + * 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 <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef HAVE_STDBOOL_H +# include <stdbool.h> +#else +# include "compat/stdbool.h" +#endif +#include <unistd.h> +#include <pwd.h> +#include <grp.h> + +#define SUDO_ERROR_WRAP 0 + +#include "sudo_compat.h" +#include "sudo_fatal.h" +#include "sudo_util.h" + +sudo_dso_public int main(int argc, char *argv[]); + +/* + * Implement "id -G" using sudo_getgrouplist2(). + */ + +int +main(int argc, char *argv[]) +{ + char *username = NULL; + GETGROUPS_T *groups = NULL; + struct passwd *pw; + int ch, i, ngroups; + gid_t basegid; + + initprogname(argc > 0 ? argv[0] : "getgids"); + + while ((ch = getopt(argc, argv, "v")) != -1) { + switch (ch) { + case 'v': + /* ignore */ + break; + default: + fprintf(stderr, "usage: %s [-v] [user]\n", getprogname()); + return EXIT_FAILURE; + } + } + argc -= optind; + argv += optind; + + if (argc > 0) + username = argv[0]; + + if (username != NULL) { + if ((pw = getpwnam(username)) == NULL) + sudo_fatalx("unknown user name %s", username); + } else { + if ((pw = getpwuid(getuid())) == NULL) + sudo_fatalx("unknown user ID %u", (unsigned int)getuid()); + } + basegid = pw->pw_gid; + if ((username = strdup(pw->pw_name)) == NULL) + sudo_fatal(NULL); + + if (sudo_getgrouplist2(username, basegid, &groups, &ngroups) == -1) + sudo_fatal("sudo_getgroulist2"); + + for (i = 0; i < ngroups; i++) { + printf("%s%u", i ? " " : "", (unsigned int)groups[i]); + } + putchar('\n'); + return EXIT_SUCCESS; +} diff --git a/lib/util/regress/getgrouplist/getgrouplist_test.c b/lib/util/regress/getgrouplist/getgrouplist_test.c new file mode 100644 index 0000000..8429528 --- /dev/null +++ b/lib/util/regress/getgrouplist/getgrouplist_test.c @@ -0,0 +1,117 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2018 Todd C. Miller <Todd.Miller@sudo.ws> + * + * 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 <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef HAVE_STDBOOL_H +# include <stdbool.h> +#else +# include "compat/stdbool.h" +#endif +#include <pwd.h> +#include <grp.h> +#include <unistd.h> + +#include "sudo_compat.h" +#include "sudo_fatal.h" +#include "sudo_util.h" + +sudo_dso_public int main(int argc, char *argv[]); + +/* + * Test that sudo_getgrouplist2() works as expected. + */ + +int +main(int argc, char *argv[]) +{ + int errors = 0; +#ifndef HAVE_GETGROUPLIST_2 + GETGROUPS_T *groups = NULL; + struct passwd *pw; + struct group *grp; + char *username; + int ch, i, j, ntests = 0; + int ngroups; + gid_t basegid; + + initprogname(argc > 0 ? argv[0] : "getgrouplist_test"); + + while ((ch = getopt(argc, argv, "v")) != -1) { + switch (ch) { + case 'v': + /* ignore */ + break; + default: + fprintf(stderr, "usage: %s [-v]\n", getprogname()); + return EXIT_FAILURE; + } + } + argc -= optind; + argv += optind; + + if ((pw = getpwuid(0)) == NULL) + sudo_fatal_nodebug("getpwuid(0)"); + basegid = pw->pw_gid; + if ((username = strdup(pw->pw_name)) == NULL) + sudo_fatal_nodebug(NULL); + + if (sudo_getgrouplist2(username, basegid, &groups, &ngroups) == -1) + sudo_fatal_nodebug("sudo_getgroulist2"); + + for (i = 0; i < ngroups; i++) { + ntests++; + + /* Verify group ID exists. */ + if ((grp = getgrgid(groups[i])) == NULL) { + sudo_warnx_nodebug("unable to look up group ID %u", + (unsigned int)groups[i]); + errors++; + continue; + } + + /* Check user's primary gid from the passwd file. */ + if (grp->gr_gid == basegid) + continue; + + /* Verify group membership. */ + for (j = 0; grp->gr_mem[j] != NULL; j++) { + if (strcmp(username, grp->gr_mem[j]) == 0) { + /* match */ + break; + } + } + if (grp->gr_mem[j] == NULL) { + sudo_warnx_nodebug("unable to find %s in group %s", + username, grp->gr_name); + errors++; + continue; + } + } + if (ntests != 0) { + printf("%s: %d tests run, %d errors, %d%% success rate\n", + getprogname(), ntests, errors, (ntests - errors) * 100 / ntests); + } + free(username); + free(groups); +#endif /* HAVE_GETGROUPLIST_2 */ + return errors; +} diff --git a/lib/util/regress/glob/files b/lib/util/regress/glob/files new file mode 100644 index 0000000..c5e92aa --- /dev/null +++ b/lib/util/regress/glob/files @@ -0,0 +1,47 @@ +fake/bin/[ +fake/bin/cat +fake/bin/chgrp +fake/bin/chio +fake/bin/chmod +fake/bin/cksum +fake/bin/cp +fake/bin/cpio +fake/bin/csh +fake/bin/date +fake/bin/dd +fake/bin/df +fake/bin/domainname +fake/bin/echo +fake/bin/ed +fake/bin/eject +fake/bin/expr +fake/bin/hostname +fake/bin/kill +fake/bin/ksh +fake/bin/ln +fake/bin/ls +fake/bin/md5 +fake/bin/mkdir +fake/bin/mt +fake/bin/mv +fake/bin/pax +fake/bin/ps +fake/bin/pwd +fake/bin/rcp +fake/bin/rksh +fake/bin/rm +fake/bin/rmail +fake/bin/rmd160 +fake/bin/rmdir +fake/bin/sh +fake/bin/sha1 +fake/bin/sha256 +fake/bin/sha384 +fake/bin/sha512 +fake/bin/sleep +fake/bin/stty +fake/bin/sum +fake/bin/sync +fake/bin/systrace +fake/bin/tar +fake/bin/test diff --git a/lib/util/regress/glob/globtest.c b/lib/util/regress/glob/globtest.c new file mode 100644 index 0000000..866535a --- /dev/null +++ b/lib/util/regress/glob/globtest.c @@ -0,0 +1,225 @@ +/* $OpenBSD: globtest.c,v 1.1 2008/10/01 23:04:36 millert Exp $ */ + +/* + * Public domain, 2008, Todd C. Miller <Todd.Miller@sudo.ws> + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef HAVE_GLOB +# include <glob.h> +#else +# include "compat/glob.h" +#endif +#include <errno.h> +#include <unistd.h> + +#include "sudo_compat.h" +#include "sudo_util.h" + +#define MAX_RESULTS 256 + +struct gl_entry { + int flags; + int nresults; + char pattern[1024]; + char *results[MAX_RESULTS]; +}; + +int test_glob(struct gl_entry *); +sudo_dso_public int main(int argc, char *argv[]); + +int +main(int argc, char **argv) +{ + FILE *fp = stdin; + char buf[2048], *cp, *ep; + int ch, errors = 0, ntests = 0, lineno; + struct gl_entry entry; + size_t len; + + initprogname(argc > 0 ? argv[0] : "globtest"); + + while ((ch = getopt(argc, argv, "v")) != -1) { + switch (ch) { + case 'v': + /* ignore */ + break; + default: + fprintf(stderr, "usage: %s [-v]\n", getprogname()); + return EXIT_FAILURE; + } + } + argc -= optind; + argv += optind; + + if (argc > 0) { + if ((fp = fopen(argv[0], "r")) == NULL) { + perror(argv[0]); + return EXIT_FAILURE; + } + } + + /* + * Read in test file, which is formatted thusly: + * + * [pattern] <flags> + * result1 + * result2 + * result3 + * ... + * + */ + lineno = 0; + memset(&entry, 0, sizeof(entry)); + while (fgets(buf, sizeof(buf), fp) != NULL) { + lineno++; + len = strlen(buf); + if (len > 0) { + if (buf[len - 1] != '\n') { + fprintf(stderr, + "globtest: missing newline at EOF\n"); + return EXIT_FAILURE; + } + buf[--len] = '\0'; + } + if (len == 0) + continue; /* blank line */ + + if (buf[0] == '[') { + /* check previous pattern */ + if (entry.pattern[0]) { + errors += test_glob(&entry); + ntests++; + } + + /* start new entry */ + if ((cp = strrchr(buf + 1, ']')) == NULL) { + fprintf(stderr, + "globtest: invalid entry on line %d\n", + lineno); + return EXIT_FAILURE; + } + len = cp - buf - 1; + if (len >= sizeof(entry.pattern)) { + fprintf(stderr, + "globtest: pattern too big on line %d\n", + lineno); + return EXIT_FAILURE; + } + memcpy(entry.pattern, buf + 1, len); + entry.pattern[len] = '\0'; + + cp += 2; + if (*cp++ != '<') { + fprintf(stderr, + "globtest: invalid entry on line %d\n", + lineno); + return EXIT_FAILURE; + } + ep = strchr(cp, '>'); + if (ep == NULL) { + fprintf(stderr, + "globtest: invalid entry on line %d\n", + lineno); + return EXIT_FAILURE; + } + *ep = '\0'; + entry.flags = 0; + for ((cp = strtok_r(cp, "|", &ep)); cp != NULL; (cp = strtok_r(NULL, "|", &ep))) { + if (strcmp(cp, "GLOB_APPEND") == 0) + entry.flags |= GLOB_APPEND; + else if (strcmp(cp, "GLOB_DOOFFS") == 0) + entry.flags |= GLOB_DOOFFS; + else if (strcmp(cp, "GLOB_ERR") == 0) + entry.flags |= GLOB_ERR; + else if (strcmp(cp, "GLOB_MARK") == 0) + entry.flags |= GLOB_MARK; + else if (strcmp(cp, "GLOB_NOCHECK") == 0) + entry.flags |= GLOB_NOCHECK; + else if (strcmp(cp, "GLOB_NOSORT") == 0) + entry.flags |= GLOB_NOSORT; + else if (strcmp(cp, "GLOB_NOESCAPE") == 0) + entry.flags |= GLOB_NOESCAPE; + else if (strcmp(cp, "GLOB_BRACE") == 0) + entry.flags |= GLOB_BRACE; + else if (strcmp(cp, "GLOB_TILDE") == 0) + entry.flags |= GLOB_TILDE; + else if (strcmp(cp, "NONE") != 0) { + fprintf(stderr, + "globtest: invalid flags on line %d\n", + lineno); + return EXIT_FAILURE; + } + } + entry.nresults = 0; + continue; + } + if (!entry.pattern[0]) { + fprintf(stderr, "globtest: missing entry on line %d\n", + lineno); + return EXIT_FAILURE; + } + + if (entry.nresults + 1 > MAX_RESULTS) { + fprintf(stderr, + "globtest: too many results for %s, max %d\n", + entry.pattern, MAX_RESULTS); + return EXIT_FAILURE; + } + entry.results[entry.nresults++] = strdup(buf); + } + if (entry.pattern[0]) { + errors += test_glob(&entry); /* test last pattern */ + ntests++; + } + if (ntests != 0) { + printf("glob: %d test%s run, %d errors, %d%% success rate\n", + ntests, ntests == 1 ? "" : "s", errors, + (ntests - errors) * 100 / ntests); + } + return errors; +} + +static int +test_glob(struct gl_entry *entry) +{ + glob_t gl; + char **ap; + int nmatches = 0, i = 0; + + if (glob(entry->pattern, entry->flags, NULL, &gl) != 0) { + fprintf(stderr, "glob failed: %s: %s\n", entry->pattern, + strerror(errno)); + return 1; + } + + for (ap = gl.gl_pathv; *ap != NULL; ap++) + nmatches++; + + if (nmatches != entry->nresults) + goto mismatch; + + for (i = 0; i < entry->nresults; i++) { + if (strcmp(gl.gl_pathv[i], entry->results[i]) != 0) + goto mismatch; + free(entry->results[i]); + } + return 0; + mismatch: + if (nmatches != entry->nresults) { + fprintf(stderr, + "globtest: mismatch in number of results (found %d, expected %d) for pattern %s\n", + nmatches, entry->nresults, entry->pattern); + } else { + fprintf(stderr, "globtest: mismatch for pattern %s, flags 0x%x " + "(found \"%s\", expected \"%s\")\n", entry->pattern, entry->flags, + gl.gl_pathv[i], entry->results[i]); + while (i < entry->nresults) + free(entry->results[i++]); + } + return 1; +} diff --git a/lib/util/regress/glob/globtest.in b/lib/util/regress/glob/globtest.in new file mode 100644 index 0000000..20a86c1 --- /dev/null +++ b/lib/util/regress/glob/globtest.in @@ -0,0 +1,64 @@ +[fake/bin/[[:alpha:]]*] <NONE> +fake/bin/cat +fake/bin/chgrp +fake/bin/chio +fake/bin/chmod +fake/bin/cksum +fake/bin/cp +fake/bin/cpio +fake/bin/csh +fake/bin/date +fake/bin/dd +fake/bin/df +fake/bin/domainname +fake/bin/echo +fake/bin/ed +fake/bin/eject +fake/bin/expr +fake/bin/hostname +fake/bin/kill +fake/bin/ksh +fake/bin/ln +fake/bin/ls +fake/bin/md5 +fake/bin/mkdir +fake/bin/mt +fake/bin/mv +fake/bin/pax +fake/bin/ps +fake/bin/pwd +fake/bin/rcp +fake/bin/rksh +fake/bin/rm +fake/bin/rmail +fake/bin/rmd160 +fake/bin/rmdir +fake/bin/sh +fake/bin/sha1 +fake/bin/sha256 +fake/bin/sha384 +fake/bin/sha512 +fake/bin/sleep +fake/bin/stty +fake/bin/sum +fake/bin/sync +fake/bin/systrace +fake/bin/tar +fake/bin/test + +[fake/bin/rm{,dir,ail}] <GLOB_BRACE> +fake/bin/rm +fake/bin/rmdir +fake/bin/rmail + +[fake/bin/sha[[:digit:]]] <NONE> +fake/bin/sha1 + +[fake/bin/sha[[:digit:]]*] <NONE> +fake/bin/sha1 +fake/bin/sha256 +fake/bin/sha384 +fake/bin/sha512 + +[fake/bin/ca[a-z]] <NONE> +fake/bin/cat diff --git a/lib/util/regress/harness.in b/lib/util/regress/harness.in new file mode 100755 index 0000000..05fd298 --- /dev/null +++ b/lib/util/regress/harness.in @@ -0,0 +1,109 @@ +#!/bin/sh +# +# Copyright (c) 2022 Todd C. Miller <Todd.Miller@sudo.ws> +# +# 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. +# +# Simple test harness for libsudo_util tests. +# usage: harness [-v] test_group [test_name ...] +# +srcdir="@abs_srcdir@" +builddir="@abs_builddir@" +SHELL=@SHELL@ +verbose=0 +rval=0 +ntests=0 +errors=0 + +umask 022 + +if [ "$1" = "-v" ]; then + verbose=1 + shift +fi + +if [ $# -eq 0 ]; then + echo "usage: harness test_group [test_name ...]" >&2 + exit 1 +fi +group="$1" +shift +srcdir=${srcdir%"/regress"} +builddir=${builddir%"/regress"} + +cd $srcdir || exit 1 + +if [ ! -d "regress/$group" ]; then + echo "missing test group: regress/$group" >&2 + exit 1 +fi + +mkdir -p "$builddir/regress/$group" +if [ $# -eq 0 ]; then + tests= + for t in regress/$group/*.in; do + tests="$tests `basename $t .in`" + done + set -- $tests +fi + + +while [ $# -ne 0 ]; do + test="$1" + shift + in="regress/$group/${test}.in" + out="$builddir/regress/$group/${test}.out" + out_ok="regress/$group/${test}.out.ok" + err="$builddir/regress/$group/${test}.err" + err_ok="regress/$group/${test}.err.ok" + + if [ "$group" = "sudo_conf" ]; then + $builddir/conf_test $in >$out 2>$err + else + $builddir/parseln_test <$in >$out 2>$err + fi + + ntests=`expr $ntests + 1` + if cmp $out $out_ok >/dev/null; then + if [ $verbose -eq 1 ]; then + echo "$group/$test: OK" + fi + else + errors=`expr $errors + 1` + echo "$group/$test: FAIL" + diff $out $out_ok || true + fi + + ntests=`expr $ntests + 1` + if test -s $err_ok; then + if cmp $err $err_ok >/dev/null; then + if [ $verbose -eq 1 ]; then + echo "$group/$test (stderr): OK" + fi + else + errors=`expr $errors + 1` + echo "$group/$test (stderr): FAIL" + diff $err $err_ok || true + fi + elif test -s $err; then + errors=`expr $errors + 1` + echo "$group/$test (stderr): FAIL" + fi +done +${AWK-awk} -v group=$group -v ntests=$ntests -v errors=$errors \ + 'END {printf("%s: %d tests run, %d errors, %d%% success rate\n", group, ntests, errors, (ntests - errors) * 100 / ntests)}' < /dev/null +if test $errors -ne 0; then + rval=`expr $rval + $errors` +fi + +exit $rval diff --git a/lib/util/regress/hexchar/hexchar_test.c b/lib/util/regress/hexchar/hexchar_test.c new file mode 100644 index 0000000..a51bf7f --- /dev/null +++ b/lib/util/regress/hexchar/hexchar_test.c @@ -0,0 +1,81 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2014-2015, 2023 Todd C. Miller <Todd.Miller@sudo.ws> + * + * 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 <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define SUDO_ERROR_WRAP 0 + +#include "sudo_compat.h" +#include "sudo_util.h" + +sudo_dso_public int main(int argc, char *argv[]); + +struct hexchar_test { + char hex[3]; + int value; +}; + +int +main(int argc, char *argv[]) +{ + struct hexchar_test *test_data; + int i, ntests, result, errors = 0; + static const char xdigs_lower[] = "0123456789abcdef"; + static const char xdigs_upper[] = "0123456789ABCDEF"; + + initprogname(argc > 0 ? argv[0] : "hexchar_test"); + + /* Build up test data. */ + ntests = 256 + 256 + 3; + test_data = calloc(sizeof(*test_data), ntests); + for (i = 0; i < 256; i++) { + /* lower case */ + test_data[i].value = i; + test_data[i].hex[1] = xdigs_lower[ (i & 0x0f)]; + test_data[i].hex[0] = xdigs_lower[((i & 0xf0) >> 4)]; + /* upper case */ + test_data[i + 256].value = i; + test_data[i + 256].hex[1] = xdigs_upper[ (i & 0x0f)]; + test_data[i + 256].hex[0] = xdigs_upper[((i & 0xf0) >> 4)]; + } + /* Also test invalid data */ + test_data[ntests - 3].hex[0] = '\0'; + test_data[ntests - 3].value = -1; + strlcpy(test_data[ntests - 2].hex, "AG", sizeof(test_data[ntests - 2].hex)); + test_data[ntests - 2].value = -1; + strlcpy(test_data[ntests - 1].hex, "-1", sizeof(test_data[ntests - 1].hex)); + test_data[ntests - 1].value = -1; + + for (i = 0; i < ntests; i++) { + result = sudo_hexchar(test_data[i].hex); + if (result != test_data[i].value) { + fprintf(stderr, "%s: expected %d, got %d\n", getprogname(), + test_data[i].value, result); + 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/lib/util/regress/json/json_test.c b/lib/util/regress/json/json_test.c new file mode 100644 index 0000000..8dc6719 --- /dev/null +++ b/lib/util/regress/json/json_test.c @@ -0,0 +1,235 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2022 Todd C. Miller <Todd.Miller@sudo.ws> + * + * 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 <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <limits.h> +#include <string.h> +#include <unistd.h> + +#define SUDO_ERROR_WRAP 0 + +#include "sudo_compat.h" +#include "sudo_json.h" +#include "sudo_util.h" +#include "sudo_fatal.h" + +sudo_dso_public int main(int argc, char *argv[]); + +/* Expected JSON output */ +const char outbuf[] = "\n" + " \"test1\": {\n" + " \"string1\": \"test\\\\\\b\\f\\n\\r\\t string1\",\n" + " \"id1\": 4294967295,\n" + " \"number1\": -1,\n" + " \"bool1\": true,\n" + " \"bool2\": false,\n" + " \"null1\": null,\n" + " \"array1\": [\n" + " \"string2\": \"test\\f\\u0011string2\",\n" + " \"number2\": -9223372036854775808,\n" + " \"number3\": 9223372036854775807\n" + " ]\n" + " }"; + +/* + * Simple tests for sudo json functions() + */ +int +main(int argc, char *argv[]) +{ + struct json_container jsonc; + struct json_value value; + int ch, errors = 0, ntests = 0; + + initprogname(argc > 0 ? argv[0] : "json_test"); + + while ((ch = getopt(argc, argv, "v")) != -1) { + switch (ch) { + case 'v': + /* ignore */ + break; + default: + fprintf(stderr, "usage: %s [-v]\n", getprogname()); + return EXIT_FAILURE; + } + } + argc -= optind; + argv += optind; + + ntests++; + if (!sudo_json_init(&jsonc, 4, false, true, true)) { + sudo_warnx("unable to initialize json"); + errors++; + goto done; + } + + /* Open main JSON object. */ + ntests++; + if (!sudo_json_open_object(&jsonc, "test1")) { + sudo_warnx("unable to open json object"); + errors++; + goto done; + } + + /* Verify invalid value is detected. */ + value.type = -1; + value.u.string = NULL; + ntests++; + if (sudo_json_add_value(&jsonc, "bogus1", &value)) { + /* should have failed, not a fatal error */ + sudo_warnx("should not be able to add bogus type value"); + errors++; + } + + /* Verify that adding an array is not allowed. */ + value.type = JSON_ARRAY; + value.u.string = NULL; + ntests++; + if (sudo_json_add_value(&jsonc, "bogus2", &value)) { + /* should have failed, not a fatal error */ + sudo_warnx("should not be able to add array type value"); + errors++; + } + + /* Verify that adding an object is not allowed. */ + value.type = JSON_OBJECT; + value.u.string = NULL; + ntests++; + if (sudo_json_add_value(&jsonc, "bogus3", &value)) { + /* should have failed, not a fatal error */ + sudo_warnx("should not be able to add object type value"); + errors++; + } + + value.type = JSON_STRING; + value.u.string = "test\\\b\f\n\r\t string1"; + ntests++; + if (!sudo_json_add_value(&jsonc, "string1", &value)) { + /* not a fatal error */ + sudo_warnx("unable to add string value (string1)"); + errors++; + } + + value.type = JSON_ID; + value.u.id = 0xffffffff; + ntests++; + if (!sudo_json_add_value(&jsonc, "id1", &value)) { + /* not a fatal error */ + sudo_warnx("unable to add ID value (0xffffffff)"); + errors++; + } + + value.type = JSON_NUMBER; + value.u.number = -1; + ntests++; + if (!sudo_json_add_value(&jsonc, "number1", &value)) { + /* not a fatal error */ + sudo_warnx("unable to add number value (-1)"); + errors++; + } + + value.type = JSON_BOOL; + value.u.boolean = true; + ntests++; + if (!sudo_json_add_value(&jsonc, "bool1", &value)) { + /* not a fatal error */ + sudo_warnx("unable to add bool value (true)"); + errors++; + } + value.u.boolean = false; + ntests++; + if (!sudo_json_add_value(&jsonc, "bool2", &value)) { + /* not a fatal error */ + sudo_warnx("unable to add bool value (false)"); + errors++; + } + + value.type = JSON_NULL; + ntests++; + if (!sudo_json_add_value(&jsonc, "null1", &value)) { + /* not a fatal error */ + sudo_warnx("unable to add null value"); + errors++; + } + + /* Open JSON array. */ + ntests++; + if (!sudo_json_open_array(&jsonc, "array1")) { + sudo_warnx("unable to open json array"); + errors++; + goto done; + } + + value.type = JSON_STRING; + value.u.string = "test\x0c\x11string2"; + ntests++; + if (!sudo_json_add_value(&jsonc, "string2", &value)) { + /* not a fatal error */ + sudo_warnx("unable to add string value (string2)"); + errors++; + } + + value.type = JSON_NUMBER; + value.u.number = LLONG_MIN; + ntests++; + if (!sudo_json_add_value(&jsonc, "number2", &value)) { + /* not a fatal error */ + sudo_warnx("unable to add number value (LLONG_MIN)"); + errors++; + } + value.u.number = LLONG_MAX; + ntests++; + if (!sudo_json_add_value(&jsonc, "number3", &value)) { + /* not a fatal error */ + sudo_warnx("unable to add number value (LLONG_MAX)"); + errors++; + } + + /* Close JSON array. */ + if (!sudo_json_close_array(&jsonc)) { + sudo_warnx("unable to close json array"); + errors++; + goto done; + } + + /* Close main JSON object. */ + if (!sudo_json_close_object(&jsonc)) { + sudo_warnx("unable to close json object"); + errors++; + goto done; + } + + if (strcmp(outbuf, jsonc.buf) != 0) { + fprintf(stderr, "Expected:\n%s\n", outbuf); + fprintf(stderr, "Received:\n%s\n", jsonc.buf); + } + +done: + sudo_json_free(&jsonc); + + if (ntests != 0) { + printf("%s: %d tests run, %d errors, %d%% success rate\n", + getprogname(), ntests, errors, (ntests - errors) * 100 / ntests); + } + + return errors; +} diff --git a/lib/util/regress/mktemp/mktemp_test.c b/lib/util/regress/mktemp/mktemp_test.c new file mode 100644 index 0000000..134f89e --- /dev/null +++ b/lib/util/regress/mktemp/mktemp_test.c @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2010 Philip Guenther <guenther@openbsd.org> + * + * Public domain. + * + * Verify that mkdtemp() and mkstemps() doesn't overrun or underrun + * the template buffer and that it can generate names that don't + * contain any X's + */ + +#include <config.h> + +#include <sys/mman.h> +#include <sys/stat.h> + +#include <errno.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> + +#define SUDO_ERROR_WRAP 0 + +#include "sudo_compat.h" +#include "sudo_util.h" +#include "sudo_fatal.h" + +#ifndef MAP_ANON +# if defined(MAP_ANONYMOUS) +# define MAP_ANON MAP_ANONYMOUS +# endif +#endif + +#ifndef MAP_FAILED +# define MAP_FAILED ((void *) -1) +#endif + +#define MAX_TEMPLATE_LEN 10 +#define MAX_TRIES 100 +#define MIN_Xs 6 + +#define SUFFIX ".suff" +#define SLEN (sizeof SUFFIX - 1) + +sudo_dso_public int main(int argc, char *argv[]); + +/* + * verify that a path generated by mkdtemp() or mkstemp() looks like a + * reasonable expansion of the template and matches the fd. Returns true + * if all the X's were replaced with non-X's + */ +static int +check(int fd, char const *kind, char const *path, char const *prefix, + size_t plen, char const *suffix, size_t slen, int tlen) +{ + struct stat sb, fsb; + char const *p; + + if (tlen < MIN_Xs) { + if (fd != -1) + sudo_fatalx("%s(%s) succeed with too few Xs", kind, path); + if (errno != EINVAL) + sudo_fatal("%s(%s) failed with wrong errno: %d", kind, path, errno); + return 1; + } + if (fd == -1) + sudo_fatal("%s(%s)", kind, path); + if (stat(path, &sb)) + sudo_fatal("%s: stat(%s)", kind, path); + if (fd >= 0) { + if (fstat(fd, &fsb)) + sudo_fatal("%s: fstat(%d==%s)", kind, fd, path); + if (sb.st_dev != fsb.st_dev || sb.st_ino != fsb.st_ino) + sudo_fatalx("%s: stat mismatch", kind); + } + if (memcmp(path, prefix, plen) != 0) + sudo_fatalx("%s: prefix changed! %s vs %s", kind, prefix, path); + if (memcmp(path + plen + tlen, suffix, slen + 1) != 0) + sudo_fatalx("%s: suffix changed! %s vs %s", kind, suffix, path); + for (p = path + plen; p < path + plen + tlen; p++) + if (*p == '\0') + sudo_fatalx("%s: unexpected truncation", kind); + else if (*p == 'X') + return 0; + return 1; +} + +static void +try_mkdtemp(char *p, char const *prefix, int len) +{ + size_t plen = strlen(prefix); + int fd, tries, ok; + + for (tries = 0; tries < MAX_TRIES; tries++) { + memcpy(p, prefix, plen); + memset(p + plen, 'X', len); + p[plen + len] = '\0'; + fd = mkdtemp(p) ? -2 : -1; + ok = check(fd, "mkdtemp", p, prefix, plen, "", 0, len); + rmdir(p); + if (ok) + return; + } + sudo_fatalx("mkdtemp: exceeded MAX_TRIES"); +} + +static void +try_mkstemps(char *p, char const *prefix, int len, char const *suffix) +{ + size_t plen = strlen(prefix); + size_t slen = strlen(suffix); + int tries, fd, ok; + + for (tries = 0; tries < MAX_TRIES; tries++) { + memcpy(p, prefix, plen); + memset(p + plen, 'X', len); + memcpy(p + plen + len, suffix, slen + 1); + fd = mkstemps(p, slen); + ok = check(fd, "mkstemp", p, prefix, plen, suffix, slen, len); + close(fd); + unlink(p); + if (ok) + return; + } + sudo_fatalx("mkstemps: exceeded MAX_TRIES"); +} + +int +main(int argc, char *argv[]) +{ + char cwd[PATH_MAX + 1]; + char *p; + size_t clen; + long pg; + int ch, i; + + initprogname(argc > 0 ? argv[0] : "mktemp_test"); + + while ((ch = getopt(argc, argv, "v")) != -1) { + switch (ch) { + case 'v': + /* ignore */ + break; + default: + fprintf(stderr, "usage: %s [-v]\n", getprogname()); + return EXIT_FAILURE; + } + } + argc -= optind; + argv += optind; + + pg = sysconf(_SC_PAGESIZE); + if (getcwd(cwd, sizeof cwd - 1) == NULL) + sudo_fatal("getcwd"); + clen = strlen(cwd); + cwd[clen++] = '/'; + cwd[clen] = '\0'; +#ifdef MAP_ANON + p = mmap(NULL, pg * 3, PROT_READ | PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0); +#else + i = open("/dev/zero", O_RDWR); + if (i == -1) + sudo_fatal("/dev/zero"); + p = mmap(NULL, pg * 3, PROT_READ | PROT_WRITE, MAP_PRIVATE, i, 0); +#endif + if (p == MAP_FAILED) + sudo_fatal("mmap"); + if (mprotect(p, pg, PROT_NONE) || mprotect(p + pg * 2, pg, PROT_NONE)) + sudo_fatal("mprotect"); + p += pg; + + i = MAX_TEMPLATE_LEN + 1; + while (i-- > 0) { + /* try first at the start of a page, no prefix */ + try_mkdtemp(p, "", i); + /* now at the end of the page, no prefix */ + try_mkdtemp(p + pg - i - 1, "", i); + /* start of the page, prefixed with the cwd */ + try_mkdtemp(p, cwd, i); + /* how about at the end of the page, prefixed with cwd? */ + try_mkdtemp(p + pg - clen - i - 1, cwd, i); + + /* again, with mkstemps() and an empty suffix */ + /* try first at the start of a page, no prefix */ + try_mkstemps(p, "", i, ""); + /* now at the end of the page, no prefix */ + try_mkstemps(p + pg - i - 1, "", i, ""); + /* start of the page, prefixed with the cwd */ + try_mkstemps(p, cwd, i, ""); + /* how about at the end of the page, prefixed with cwd? */ + try_mkstemps(p + pg - clen - i - 1, cwd, i, ""); + + /* mkstemps() and a non-empty suffix */ + /* try first at the start of a page, no prefix */ + try_mkstemps(p, "", i, SUFFIX); + /* now at the end of the page, no prefix */ + try_mkstemps(p + pg - i - SLEN - 1, "", i, SUFFIX); + /* start of the page, prefixed with the cwd */ + try_mkstemps(p, cwd, i, SUFFIX); + /* how about at the end of the page, prefixed with cwd? */ + try_mkstemps(p + pg - clen - i - SLEN - 1, cwd, i, SUFFIX); + } + + return 0; +} diff --git a/lib/util/regress/multiarch/multiarch_test.c b/lib/util/regress/multiarch/multiarch_test.c new file mode 100644 index 0000000..1c60aff --- /dev/null +++ b/lib/util/regress/multiarch/multiarch_test.c @@ -0,0 +1,184 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2022 Todd C. Miller <Todd.Miller@sudo.ws> + * + * 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 <config.h> + +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define SUDO_ERROR_WRAP 0 + +#include "sudo_compat.h" +#include "sudo_fatal.h" +#include "sudo_util.h" + +sudo_dso_public int main(int argc, char *argv[]); + +#ifdef __linux__ +# include <sys/utsname.h> + +# if defined(__ILP32__) +# define ARCH_LIB "libx32" +# elif defined(__LP64__) +# define ARCH_LIB "lib64" +# else +# define ARCH_LIB "lib32" +# endif + +struct multiarch_test { + const char *inpath; + char *outpath; +}; + +static struct multiarch_test * +make_test_data(void) +{ + struct multiarch_test *test_data; + struct utsname unamebuf; + int i; + + if (uname(&unamebuf) == -1) + return NULL; + + test_data = calloc(7, sizeof(*test_data)); + if (test_data == NULL) + return NULL; + + test_data[0].inpath = "/usr/" ARCH_LIB "/libfoo.so"; + i = asprintf(&test_data[0].outpath, "/usr/lib/%s-linux-gnu/libfoo.so", + unamebuf.machine); + if (i == -1) { + test_data[0].outpath = NULL; + goto bad; + } + + test_data[1].inpath = "/usr/lib/something.so"; + i = asprintf(&test_data[1].outpath, "/usr/lib/%s-linux-gnu/something.so", + unamebuf.machine); + if (i == -1) { + test_data[1].outpath = NULL; + goto bad; + } + + test_data[2].inpath = "/usr/libexec/libbar.so"; + i = asprintf(&test_data[2].outpath, "/usr/libexec/%s-linux-gnu/libbar.so", + unamebuf.machine); + if (i == -1) { + test_data[2].outpath = NULL; + goto bad; + } + + test_data[3].inpath = "/usr/local/lib/sudo/libsudo_util.so"; + i = asprintf(&test_data[3].outpath, "/usr/local/lib/%s-linux-gnu/sudo/libsudo_util.so", + unamebuf.machine); + if (i == -1) { + test_data[3].outpath = NULL; + goto bad; + } + + test_data[4].inpath = "/opt/sudo/lib/sudoers.so"; + i = asprintf(&test_data[4].outpath, "/opt/sudo/lib/%s-linux-gnu/sudoers.so", + unamebuf.machine); + if (i == -1) { + test_data[4].outpath = NULL; + goto bad; + } + + i = asprintf(&test_data[5].outpath, "/usr/lib/%s-linux-gnu/something.so", + unamebuf.machine); + if (i == -1) { + test_data[5].outpath = NULL; + goto bad; + } + test_data[5].inpath = test_data[5].outpath; + test_data[5].outpath = NULL; + + return test_data; +bad: + for (i = 0; test_data[i].outpath != NULL; i++) + free(test_data[i].outpath); + free(test_data); + return NULL; +} +#endif /* __linux__ */ + +int +main(int argc, char *argv[]) +{ + int ch, errors = 0; +#ifdef __linux__ + int ntests = 0; + struct multiarch_test *test_data; +#endif + + initprogname(argc > 0 ? argv[0] : "multiarch_test"); + + while ((ch = getopt(argc, argv, "v")) != -1) { + switch (ch) { + case 'v': + /* ignore */ + break; + default: + fprintf(stderr, "usage: %s [-v]\n", getprogname()); + return EXIT_FAILURE; + } + } + argc -= optind; + argv += optind; + +#ifdef __linux__ + test_data = make_test_data(); + if (test_data == NULL) { + sudo_warnx("%s", "failed to generate test data"); + return EXIT_FAILURE; + } + + for (ch = 0; test_data[ch].inpath != NULL; ch++) { + char *outpath = sudo_stat_multiarch(test_data[ch].inpath, NULL); + ntests++; + if (outpath == NULL) { + if (test_data[ch].outpath != NULL) { + sudo_warnx("%s: sudo_stat_multiarch failed", + test_data[ch].inpath); + errors++; + } + } else if (strcmp(outpath, test_data[ch].outpath) != 0) { + sudo_warnx("%s: expected %s got %s", test_data[ch].inpath, + test_data[ch].outpath, outpath); + errors++; + } + /* For test_data[5], inpath is allocated and outpath is NULL. */ + if (test_data[ch].outpath != NULL) + free(test_data[ch].outpath); + else + free((char *)test_data[ch].inpath); + free(outpath); + } + free(test_data); + + if (ntests != 0) { + printf("%s: %d tests run, %d errors, %d%% success rate\n", + getprogname(), ntests, errors, (ntests - errors) * 100 / ntests); + } +#endif /* __linux__ */ + return errors; +} diff --git a/lib/util/regress/open_parent_dir/open_parent_dir_test.c b/lib/util/regress/open_parent_dir/open_parent_dir_test.c new file mode 100644 index 0000000..95f1a09 --- /dev/null +++ b/lib/util/regress/open_parent_dir/open_parent_dir_test.c @@ -0,0 +1,166 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2022 Todd C. Miller <Todd.Miller@sudo.ws> + * + * 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 <config.h> + +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define SUDO_ERROR_WRAP 0 + +#include "sudo_compat.h" +#include "sudo_fatal.h" +#include "sudo_util.h" + +sudo_dso_public int main(int argc, char *argv[]); + +static int errors = 0, ntests = 0; + +static int +run_test(const char *tdir, const char *path, uid_t uid, gid_t gid) +{ + char *cp, fullpath[PATH_MAX]; + struct stat sb1, sb2; + int dfd, len; + int ret = -1; + + /* Test creating full path. */ + len = snprintf(fullpath, sizeof(fullpath), "%s/%s", tdir, path); + if (len < 0 || len >= ssizeof(fullpath)) { + errno = ENAMETOOLONG; + sudo_fatal("%s/%s", tdir, path); + } + ntests++; + dfd = sudo_open_parent_dir(fullpath, uid, gid, 0700, false); + if (dfd == -1) { + errors++; + goto done; + } + + /* Verify that we only created the parent dir, not full path. */ + ntests++; + if (stat(fullpath, &sb1) == 0) { + sudo_warnx("created full path \"%s\", not just parent dir", + fullpath); + errors++; + goto done; + } + + /* Verify that dfd refers to the parent dir. */ + ntests++; + cp = strrchr(fullpath, '/'); + *cp = '\0'; + if (stat(fullpath, &sb1) == -1) { + sudo_warn("%s", fullpath); + errors++; + goto done; + } + if (fstat(dfd, &sb2) == -1) { + sudo_warn("%s", fullpath); + errors++; + goto done; + } + if (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino) { + sudo_warn("%s: sudo_open_parent_dir fd mismatch", fullpath); + errors++; + goto done; + } + +done: + if (dfd != -1) + close(dfd); + return ret; +} + +int +main(int argc, char *argv[]) +{ + char tdir[] = "open_parent_dir.XXXXXXXX"; + int ch, dfd, fd, len; + char cmd[1024]; + uid_t uid; + gid_t gid; + + initprogname(argc > 0 ? argv[0] : "open_parent_dir_test"); + + while ((ch = getopt(argc, argv, "v")) != -1) { + switch (ch) { + case 'v': + /* ignore */ + break; + default: + fprintf(stderr, "usage: %s [-v]\n", getprogname()); + return EXIT_FAILURE; + } + } + argc -= optind; + argv += optind; + + uid = geteuid(); + gid = getegid(); + + /* All tests relative to tdir. */ + if (mkdtemp(tdir) == NULL) + sudo_fatal("%s", tdir); + + /* Test creating new path. */ + dfd = run_test(tdir, "level1/level2/level3", uid, gid); + + /* Verify we can create a new file in the new parent dir. */ + if (dfd != -1) { + ntests++; + fd = openat(dfd, "testfile", O_WRONLY|O_CREAT|O_EXCL, 0600); + if (fd == -1) { + errors++; + } else { + close(fd); + } + close(dfd); + dfd = -1; + } + + /* Test exiting path when final component exists. */ + dfd = run_test(tdir, "level1/level2/testfile", uid, gid); + if (dfd != -1) { + unlinkat(dfd, "testfile", 0); + close(dfd); + } + + /* Test exiting path when final component doesn't exist. */ + dfd = run_test(tdir, "level1/level2/testfile", uid, gid); + if (dfd != -1) + close(dfd); + + /* Cleanup */ + len = snprintf(cmd, sizeof(cmd), "rm -rf \"%s\"", tdir); + if (len < 0 || len >= ssizeof(cmd)) { + errno = ENAMETOOLONG; + sudo_fatalx("rm -rf %s", tdir); + } + ignore_result(system(cmd)); + + if (ntests != 0) { + printf("%s: %d tests run, %d errors, %d%% success rate\n", + getprogname(), ntests, errors, (ntests - errors) * 100 / ntests); + } + return errors; +} diff --git a/lib/util/regress/parse_gids/parse_gids_test.c b/lib/util/regress/parse_gids/parse_gids_test.c new file mode 100644 index 0000000..241e81b --- /dev/null +++ b/lib/util/regress/parse_gids/parse_gids_test.c @@ -0,0 +1,123 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2015 Todd C. Miller <Todd.Miller@sudo.ws> + * + * 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 <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "sudo_compat.h" +#include "sudo_fatal.h" +#include "sudo_util.h" + +sudo_dso_public int main(int argc, char *argv[]); + +/* + * Test that sudo_parse_gids() works as expected. + */ + +struct parse_gids_test { + const char *gids; + gid_t *baseptr; + gid_t basegid; + int ngids; + const GETGROUPS_T *gidlist; +}; + +static const GETGROUPS_T test1_out[] = { 0, 1, 2, 3, 4 }; +static const GETGROUPS_T test2_out[] = { 1, 2, 3, 4 }; +static const GETGROUPS_T test3_out[] = { 0, 1, (gid_t)-2, 3, 4 }; + +/* XXX - test syntax errors too */ +static struct parse_gids_test test_data[] = { + { "1,2,3,4", &test_data[0].basegid, 0, 5, test1_out }, + { "1,2,3,4", NULL, 0, 4, test2_out }, + { "1,-2,3,4", &test_data[2].basegid, 0, 5, test3_out }, + { NULL, false, 0, 0, NULL } +}; + +static void +dump_gids(const char *prefix, int ngids, const GETGROUPS_T *gidlist) +{ + int i; + + fprintf(stderr, "%s: %s: ", getprogname(), prefix); + for (i = 0; i < ngids; i++) { + fprintf(stderr, "%s%d", i ? ", " : "", (int)gidlist[i]); + } + fputc('\n', stderr); +} + +int +main(int argc, char *argv[]) +{ + GETGROUPS_T *gidlist = NULL; + int i, j, errors = 0, ntests = 0; + int ch, ngids; + + initprogname(argc > 0 ? argv[0] : "parse_gids_test"); + + while ((ch = getopt(argc, argv, "v")) != -1) { + switch (ch) { + case 'v': + /* ignore */ + break; + default: + fprintf(stderr, "usage: %s [-v]\n", getprogname()); + return EXIT_FAILURE; + } + } + argc -= optind; + argv += optind; + + for (i = 0; test_data[i].gids != NULL; i++) { + free(gidlist); + gidlist = NULL; + ngids = sudo_parse_gids(test_data[i].gids, test_data[i].baseptr, &gidlist); + if (ngids == -1) + sudo_fatal_nodebug("sudo_parse_gids"); + ntests++; + if (ngids != test_data[i].ngids) { + sudo_warnx_nodebug("test #%d: expected %d gids, got %d", + ntests, test_data[i].ngids, ngids); + dump_gids("expected", test_data[i].ngids, test_data[i].gidlist); + dump_gids("received", ngids, gidlist); + errors++; + continue; + } + ntests++; + for (j = 0; j < ngids; j++) { + if (test_data[i].gidlist[j] != gidlist[j]) { + sudo_warnx_nodebug("test #%d: gid mismatch", ntests); + dump_gids("expected", test_data[i].ngids, test_data[i].gidlist); + dump_gids("received", ngids, gidlist); + errors++; + break; + } + } + } + free(gidlist); + + if (ntests != 0) { + printf("%s: %d tests run, %d errors, %d%% success rate\n", + getprogname(), ntests, errors, (ntests - errors) * 100 / ntests); + } + return errors; +} diff --git a/lib/util/regress/progname/progname_test.c b/lib/util/regress/progname/progname_test.c new file mode 100644 index 0000000..0a88faf --- /dev/null +++ b/lib/util/regress/progname/progname_test.c @@ -0,0 +1,67 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2014 Todd C. Miller <Todd.Miller@sudo.ws> + * + * 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 <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "sudo_compat.h" +#include "sudo_util.h" + +sudo_dso_public int main(int argc, char *argv[]); + +/* + * Test that getprogname() returns the expected result. + * On some systems (AIX), we may have issues with symbolic links. + */ + +int +main(int argc, char *argv[]) +{ + const char *progbase = "progname_test"; + int ch; + + if (argc > 0) + progbase = sudo_basename(argv[0]); + initprogname(progbase); + + while ((ch = getopt(argc, argv, "v")) != -1) { + switch (ch) { + case 'v': + /* ignore */ + break; + default: + fprintf(stderr, "usage: %s [-v]\n", progbase); + return EXIT_FAILURE; + } + } + argc -= optind; + argv += optind; + + /* Make sure getprogname() matches basename of argv[0]. */ + if (strcmp(getprogname(), progbase) != 0) { + printf("%s: FAIL: incorrect program name \"%s\"\n", + progbase, getprogname()); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/lib/util/regress/regex/regex_test.c b/lib/util/regress/regex/regex_test.c new file mode 100644 index 0000000..ed6986e --- /dev/null +++ b/lib/util/regress/regex/regex_test.c @@ -0,0 +1,126 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2022 Todd C. Miller <Todd.Miller@sudo.ws> + * + * 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 <config.h> + +#include <sys/types.h> +#include <regex.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef HAVE_STDBOOL_H +# include <stdbool.h> +#else +# include "compat/stdbool.h" +#endif + +#define SUDO_ERROR_WRAP 0 + +#include "sudo_compat.h" +#include "sudo_fatal.h" +#include "sudo_util.h" + +sudo_dso_public int main(int argc, char *argv[]); + +struct regex_test { + const char *pattern; + bool result; +}; + +static struct regex_test test_data[] = { + { "ab++", false }, + { "ab\\++", true }, + { "ab+\\+", true }, + { "ab**", false }, + { "ab\\**", true }, + { "ab*\\*", true }, + { "ab??", false }, + { "ab\\??", true }, + { "ab?\\?", true }, + { "ab{1}{1}", false }, + { "ab{1}{1,1}", false }, + { "ab{1}{,1}", false }, + { "ab{1}{1,}", false }, + { "ab{1}\\{1}", true }, + { "ab{1}\\{1,1}", true }, + { "ab{1}\\{,1}", true }, + { "ab{1}\\{1,}", true }, + { "ab+*", false }, + { "ab\\+*", true }, + { "ab+\\*", true }, + { "ab*+", false }, + { "ab\\*+", true }, + { "ab*\\+", true }, + { "ab?*", false }, + { "ab\\?*", true }, + { "ab?\\*", true }, + { "ab{1}*", false }, + { "ab\\{1}*", true }, + { "ab{1}\\*", true }, + { "ab{256}", false }, + { "ab{,256}", false }, + { "ab{256,}", false }, + { "ab{1,256}", false }, + { "ab{1,\\256}", false }, + { "ab{1,2\\56}", false }, + { "ab{256,1}", false }, + { "ab{\\256,1}", false }, + { "ab{2\\56,1}", false }, + { NULL } +}; + +int +main(int argc, char *argv[]) +{ + struct regex_test *td; + const char *errstr; + int errors = 0, ntests = 0; + bool result; + int ch; + + initprogname(argc > 0 ? argv[0] : "regex_test"); + + while ((ch = getopt(argc, argv, "v")) != -1) { + switch (ch) { + case 'v': + /* ignore */ + break; + default: + fprintf(stderr, "usage: %s [-v]\n", getprogname()); + return EXIT_FAILURE; + } + } + argc -= optind; + argv += optind; + + for (td = test_data; td->pattern != NULL; td++) { + ntests++; + result = sudo_regex_compile(NULL, td->pattern, &errstr); + if (result != td->result) { + sudo_warnx("%s: expected %d, got %d", td->pattern, (int)td->result, + (int)result); + errors++; + } + } + + if (ntests != 0) { + printf("%s: %d tests run, %d errors, %d%% success rate\n", + getprogname(), ntests, errors, (ntests - errors) * 100 / ntests); + } + return errors; +} diff --git a/lib/util/regress/strsig/strsig_test.c b/lib/util/regress/strsig/strsig_test.c new file mode 100644 index 0000000..30aaa2d --- /dev/null +++ b/lib/util/regress/strsig/strsig_test.c @@ -0,0 +1,319 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2019-2020 Todd C. Miller <Todd.Miller@sudo.ws> + * + * 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 <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <string.h> +#include <unistd.h> + +#include "sudo_compat.h" +#include "sudo_util.h" +#include "sudo_fatal.h" + +sudo_dso_public int main(int argc, char *argv[]); + +/* + * Note: we do not test SIGUNUSED as it may not appear in sys_sigabbrev[] + * on Linux. FreeBSD is missing SIGLWP (aka SIGTHR) in sys_signame[]. + */ +static struct signal_data { + int rval; + int signo; + const char *sigstr; + const char *altstr; +} signal_data[] = { +#ifdef SIGHUP + { 0, SIGHUP, "HUP", NULL }, +#endif +#ifdef SIGINT + { 0, SIGINT, "INT", NULL }, +#endif +#ifdef SIGQUIT + { 0, SIGQUIT, "QUIT", NULL }, +#endif +#ifdef SIGILL + { 0, SIGILL, "ILL", NULL }, +#endif +#ifdef SIGTRAP + { 0, SIGTRAP, "TRAP", NULL }, +#endif +#ifdef SIGABRT + { 0, SIGABRT, "ABRT", "IOT" }, +#endif +#ifdef SIGIOT + { 0, SIGIOT, "IOT", "ABRT" }, +#endif +#ifdef SIGEMT + { 0, SIGEMT, "EMT", NULL }, +#endif +#ifdef SIGFPE + { 0, SIGFPE, "FPE", NULL }, +#endif +#ifdef SIGKILL + { 0, SIGKILL, "KILL", NULL }, +#endif +#ifdef SIGBUS + { 0, SIGBUS, "BUS", NULL }, +#endif +#ifdef SIGSEGV + { 0, SIGSEGV, "SEGV", NULL }, +#endif +#ifdef SIGSYS + { 0, SIGSYS, "SYS", NULL }, +#endif +#ifdef SIGPIPE + { 0, SIGPIPE, "PIPE", NULL }, +#endif +#ifdef SIGALRM + { 0, SIGALRM, "ALRM", NULL }, +#endif +#ifdef SIGTERM + { 0, SIGTERM, "TERM", NULL }, +#endif +#ifdef SIGSTKFLT + { 0, SIGSTKFLT, "STKFLT", NULL }, +#endif +#ifdef SIGIO + { 0, SIGIO, "IO", "POLL"}, +#endif +#ifdef SIGXCPU + { 0, SIGXCPU, "XCPU", NULL }, +#endif +#ifdef SIGXFSZ + { 0, SIGXFSZ, "XFSZ", NULL }, +#endif +#ifdef SIGVTALRM + { 0, SIGVTALRM, "VTALRM", NULL }, +#endif +#ifdef SIGPROF + { 0, SIGPROF, "PROF", NULL }, +#endif +#ifdef SIGWINCH + { 0, SIGWINCH, "WINCH", NULL }, +#endif +#ifdef SIGLOST + { 0, SIGLOST, "LOST", NULL }, +#endif +#ifdef SIGUSR1 + { 0, SIGUSR1, "USR1", NULL }, +#endif +#ifdef SIGUSR2 + { 0, SIGUSR2, "USR2", NULL }, +#endif +#ifdef SIGPWR + { 0, SIGPWR, "PWR", NULL }, +#endif +#ifdef SIGPOLL + { 0, SIGPOLL, "POLL", "IO" }, +#endif +#ifdef SIGSTOP + { 0, SIGSTOP, "STOP", NULL }, +#endif +#ifdef SIGTSTP + { 0, SIGTSTP, "TSTP", NULL }, +#endif +#ifdef SIGCONT + { 0, SIGCONT, "CONT", NULL }, +#endif +#ifdef SIGCHLD + { 0, SIGCHLD, "CHLD", "CLD" }, +#endif +#ifdef SIGCLD + { 0, SIGCLD, "CLD", "CHLD" }, +#endif +#ifdef SIGTTIN + { 0, SIGTTIN, "TTIN", NULL }, +#endif +#ifdef SIGTTOU + { 0, SIGTTOU, "TTOU", NULL }, +#endif +#ifdef SIGINFO + { 0, SIGINFO, "INFO", NULL }, +#endif +#ifdef SIGURG + { 0, SIGURG, "URG", NULL }, +#endif +#ifdef SIGWAITING + { 0, SIGWAITING, "WAITING", NULL }, +#endif +#if defined(SIGLWP) && !defined(__FreeBSD__) + { 0, SIGLWP, "LWP", NULL }, +#endif +#ifdef SIGFREEZE + { 0, SIGFREEZE, "FREEZE", NULL }, +#endif +#ifdef SIGTHAW + { 0, SIGTHAW, "THAW", NULL }, +#endif +#ifdef SIGCANCEL + { 0, SIGCANCEL, "CANCEL", NULL }, +#endif +#if defined(SIGRTMIN) && defined(SIGRTMAX) + { 0, -1, "RTMIN", NULL }, + { 0, -1, "RTMIN+1", NULL }, + { 0, -1, "RTMIN+2", NULL }, + { 0, -1, "RTMIN+3", NULL }, + { 0, -1, "RTMAX-3", NULL }, + { 0, -1, "RTMAX-2", NULL }, + { 0, -1, "RTMAX-1", NULL }, + { 0, -1, "RTMAX", NULL }, +#endif + { -1, 1024, "QWERT", NULL }, /* invalid */ + { -1, 0, NULL, NULL } +}; + +#ifndef HAVE_SIG2STR +static int +test_sig2str(int *ntests) +{ + struct signal_data *d; + int rval, errors = 0; + char sigstr[SIG2STR_MAX]; + + for (d = signal_data; d->signo != 0; d++) { + (*ntests)++; + rval = sudo_sig2str(d->signo, sigstr); + if (rval != d->rval) { + sudo_warnx_nodebug("FAIL: sig2str(SIG%s): %d != %d", + d->sigstr, rval, d->rval); + errors++; + continue; + } + if (rval != 0) + continue; + if (strcmp(sigstr, d->sigstr) != 0 && + (d->altstr != NULL && strcmp(sigstr, d->altstr) != 0)) { + sudo_warnx_nodebug("FAIL: signal %d: %s != %s", d->signo, + sigstr, d->sigstr); + errors++; + continue; + } + } + + return errors; +} +#else +static int +test_sig2str(int *ntests) +{ + return 0; +} +#endif /* HAVE_SIG2STR */ + +#ifndef HAVE_STR2SIG +static int +test_str2sig(int *ntests) +{ + struct signal_data *d; + int rval, errors = 0; + int signo; + + for (d = signal_data; d->sigstr != NULL; d++) { + (*ntests)++; + rval = sudo_str2sig(d->sigstr, &signo); + if (rval != d->rval) { + sudo_warnx_nodebug("FAIL: str2sig(SIG%s): %d != %d", + d->sigstr, rval, d->rval); + errors++; + continue; + } + if (rval != 0) + continue; + if (signo != d->signo) { + sudo_warnx_nodebug("FAIL: signal SIG%s: %d != %d", d->sigstr, + signo, d->signo); + errors++; + continue; + } + } + + return errors; +} +#else +static int +test_str2sig(int *ntests) +{ + return 0; +} +#endif /* HAVE_STR2SIG */ + +#if defined(SIGRTMIN) && defined(SIGRTMAX) +static +void init_sigrt(void) +{ + int i; + + /* Initialize real-time signal values. */ + for (i = 0; signal_data[i].signo != -1; i++) + continue; + signal_data[i++].signo = SIGRTMIN; + signal_data[i++].signo = SIGRTMIN + 1; + signal_data[i++].signo = SIGRTMIN + 2; + signal_data[i++].signo = SIGRTMIN + 3; + signal_data[i++].signo = SIGRTMAX - 3; + signal_data[i++].signo = SIGRTMAX - 2; + signal_data[i++].signo = SIGRTMAX - 1; + signal_data[i++].signo = SIGRTMAX; + +} +#else +static +void init_sigrt(void) +{ + /* No real-time signals. */ + return; +} +#endif + +/* + * Simple tests for sig2str() and str2sig(). + */ +int +main(int argc, char *argv[]) +{ + int ch, errors = 0, ntests = 0; + + initprogname(argc > 0 ? argv[0] : "strsig_test"); + + while ((ch = getopt(argc, argv, "v")) != -1) { + switch (ch) { + case 'v': + /* ignore */ + break; + default: + fprintf(stderr, "usage: %s [-v]\n", getprogname()); + return EXIT_FAILURE; + } + } + argc -= optind; + argv += optind; + + init_sigrt(); + errors += test_sig2str(&ntests); + errors += test_str2sig(&ntests); + + if (ntests != 0) { + printf("%s: %d tests run, %d errors, %d%% success rate\n", + getprogname(), ntests, errors, (ntests - errors) * 100 / ntests); + } + + return errors; +} diff --git a/lib/util/regress/strsplit/strsplit_test.c b/lib/util/regress/strsplit/strsplit_test.c new file mode 100644 index 0000000..7f0e071 --- /dev/null +++ b/lib/util/regress/strsplit/strsplit_test.c @@ -0,0 +1,117 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2015 Todd C. Miller <Todd.Miller@sudo.ws> + * + * 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 <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "sudo_compat.h" +#include "sudo_fatal.h" +#include "sudo_util.h" + +sudo_dso_public int main(int argc, char *argv[]); + +/* + * Test that sudo_strsplit() works as expected. + */ + +struct strsplit_test { + const char *input; + size_t input_len; + const char **output; +}; + +static const char test1_in[] = " vi "; +static const char *test1_out[] = { "vi", NULL }; +static const char test2_in[] = "vi -r "; +static const char *test2_out[] = { "vi", "-r", NULL }; +static const char test3_in[] = "vi -r -R abc\tdef "; +static const char *test3_out[] = { "vi", "-r", "-R", "abc", "def", NULL }; +static const char test4_in[] = "vi -r -R abc\tdef "; +static const char *test4_out[] = { "vi", "-r", "-R", "abc", NULL }; +static const char test5_in[] = ""; +static const char *test5_out[] = { NULL }; + +static struct strsplit_test test_data[] = { + { test1_in, sizeof(test1_in) - 1, test1_out }, + { test2_in, sizeof(test2_in) - 1, test2_out }, + { test3_in, sizeof(test3_in) - 1, test3_out }, + { test4_in, sizeof(test4_in) - 5, test4_out }, + { test5_in, sizeof(test5_in) - 1, test5_out }, + { NULL, 0, NULL } +}; + +int +main(int argc, char *argv[]) +{ + const char *cp, *ep, *input_end; + int ch, i, j, errors = 0, ntests = 0; + size_t len; + + initprogname(argc > 0 ? argv[0] : "strsplit_test"); + + while ((ch = getopt(argc, argv, "v")) != -1) { + switch (ch) { + case 'v': + /* ignore */ + break; + default: + fprintf(stderr, "usage: %s [-v]\n", getprogname()); + return EXIT_FAILURE; + } + } + argc -= optind; + argv += optind; + + for (i = 0; test_data[i].input != NULL; i++) { + input_end = test_data[i].input + test_data[i].input_len; + cp = sudo_strsplit(test_data[i].input, input_end, " \t", &ep); + for (j = 0; test_data[i].output[j] != NULL; j++) { + ntests++; + len = strlen(test_data[i].output[j]); + if ((size_t)(ep - cp) != len) { + sudo_warnx_nodebug("failed test #%d: bad length, expected " + "%zu, got %zu", ntests, len, (size_t)(ep - cp)); + errors++; + continue; + } + ntests++; + if (strncmp(cp, test_data[i].output[j], len) != 0) { + sudo_warnx_nodebug("failed test #%d: expected %s, got %.*s", + ntests, test_data[i].output[j], (int)(ep - cp), cp); + errors++; + continue; + } + cp = sudo_strsplit(NULL, input_end, " \t", &ep); + } + ntests++; + if (cp != NULL) { + sudo_warnx_nodebug("failed test #%d: extra tokens \"%.*s\"", + ntests, (int)(input_end - cp), cp); + 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/lib/util/regress/strtofoo/strtobool_test.c b/lib/util/regress/strtofoo/strtobool_test.c new file mode 100644 index 0000000..5b5a6cf --- /dev/null +++ b/lib/util/regress/strtofoo/strtobool_test.c @@ -0,0 +1,98 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2014-2020 Todd C. Miller <Todd.Miller@sudo.ws> + * + * 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 <config.h> + +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_STDBOOL_H +# include <stdbool.h> +#else +# include "compat/stdbool.h" +#endif +#include <unistd.h> + +#include "sudo_compat.h" +#include "sudo_util.h" +#include "sudo_fatal.h" + +sudo_dso_public int main(int argc, char *argv[]); + +/* sudo_strtobool() tests */ +static struct strtobool_data { + const char *bool_str; + int value; +} strtobool_data[] = { + { "true", true }, + { "false", false }, + { "TrUe", true }, + { "fAlSe", false }, + { "1", true }, + { "0", false }, + { "on", true }, + { "off", false }, + { "yes", true }, + { "no", false }, + { "nope", -1 }, + { "10", -1 }, + { "one", -1 }, + { "zero", -1 }, + { NULL, 0 } +}; + +/* + * Simple tests for sudo_strtobool() + */ +int +main(int argc, char *argv[]) +{ + struct strtobool_data *d; + int errors = 0, ntests = 0; + int ch, value; + + initprogname(argc > 0 ? argv[0] : "strtobool_test"); + + while ((ch = getopt(argc, argv, "v")) != -1) { + switch (ch) { + case 'v': + /* ignore */ + break; + default: + fprintf(stderr, "usage: %s [-v]\n", getprogname()); + return EXIT_FAILURE; + } + } + argc -= optind; + argv += optind; + + for (d = strtobool_data; d->bool_str != NULL; d++) { + ntests++; + value = sudo_strtobool(d->bool_str); + if (value != d->value) { + sudo_warnx_nodebug("FAIL: %s != %d", d->bool_str, d->value); + errors++; + } + } + + if (ntests != 0) { + printf("%s: %d tests run, %d errors, %d%% success rate\n", + getprogname(), ntests, errors, (ntests - errors) * 100 / ntests); + } + + return errors; +} diff --git a/lib/util/regress/strtofoo/strtoid_test.c b/lib/util/regress/strtofoo/strtoid_test.c new file mode 100644 index 0000000..eec9d04 --- /dev/null +++ b/lib/util/regress/strtofoo/strtoid_test.c @@ -0,0 +1,118 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2014-2020 Todd C. Miller <Todd.Miller@sudo.ws> + * + * 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 <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> + +#include "sudo_compat.h" +#include "sudo_util.h" +#include "sudo_fatal.h" + +sudo_dso_public int main(int argc, char *argv[]); + +/* sudo_strtoidx() tests */ +static struct strtoidx_data { + const char *idstr; + id_t id; + const char *sep; + const char *ep; + int errnum; +} strtoidx_data[] = { + { "0,1", 0, ",", ",", 0 }, + { "10", 10, NULL, NULL, 0 }, + { "-1", 0, NULL, NULL, EINVAL }, + { "4294967295", 0, NULL, NULL, EINVAL }, + { "4294967296", 0, NULL, NULL, ERANGE }, + { "-2147483649", 0, NULL, NULL, ERANGE }, + { "-2", -2, NULL, NULL, 0 }, +#if SIZEOF_ID_T != SIZEOF_LONG_LONG + { "-2", (id_t)4294967294U, NULL, NULL, 0 }, +#endif + { "4294967294", (id_t)4294967294U, NULL, NULL, 0 }, + { NULL, 0, NULL, NULL, 0 } +}; + +/* + * Simple tests for sudo_strtoidx() + */ +int +main(int argc, char *argv[]) +{ + int ch, errors = 0, ntests = 0; + struct strtoidx_data *d; + const char *errstr; + char *ep; + id_t value; + + initprogname(argc > 0 ? argv[0] : "strtoid_test"); + + while ((ch = getopt(argc, argv, "v")) != -1) { + switch (ch) { + case 'v': + /* ignore */ + break; + default: + fprintf(stderr, "usage: %s [-v]\n", getprogname()); + return EXIT_FAILURE; + } + } + argc -= optind; + argv += optind; + + for (d = strtoidx_data; d->idstr != NULL; d++) { + ntests++; + errstr = "some error"; + value = sudo_strtoidx(d->idstr, d->sep, &ep, &errstr); + if (d->errnum != 0) { + if (errstr == NULL) { + sudo_warnx_nodebug("FAIL: %s: missing errstr for errno %d", + d->idstr, d->errnum); + errors++; + } else if (value != 0) { + sudo_warnx_nodebug("FAIL: %s should return 0 on error", + d->idstr); + errors++; + } else if (errno != d->errnum) { + sudo_warnx_nodebug("FAIL: %s: errno mismatch, %d != %d", + d->idstr, errno, d->errnum); + errors++; + } + } else if (errstr != NULL) { + sudo_warnx_nodebug("FAIL: %s: %s", d->idstr, errstr); + errors++; + } else if (value != d->id) { + sudo_warnx_nodebug("FAIL: %s != %u", d->idstr, (unsigned int)d->id); + errors++; + } else if (d->ep != NULL && ep[0] != d->ep[0]) { + sudo_warnx_nodebug("FAIL: ep[0] %d != %d", (int)(unsigned char)ep[0], + (int)(unsigned char)d->ep[0]); + errors++; + } + } + + if (ntests != 0) { + printf("%s: %d tests run, %d errors, %d%% success rate\n", + getprogname(), ntests, errors, (ntests - errors) * 100 / ntests); + } + + return errors; +} diff --git a/lib/util/regress/strtofoo/strtomode_test.c b/lib/util/regress/strtofoo/strtomode_test.c new file mode 100644 index 0000000..5e4d862 --- /dev/null +++ b/lib/util/regress/strtofoo/strtomode_test.c @@ -0,0 +1,91 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2014-2020 Todd C. Miller <Todd.Miller@sudo.ws> + * + * 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 <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "sudo_compat.h" +#include "sudo_util.h" +#include "sudo_fatal.h" + +sudo_dso_public int main(int argc, char *argv[]); + +/* sudo_strtomode() tests */ +static struct strtomode_data { + const char *mode_str; + mode_t mode; +} strtomode_data[] = { + { "755", 0755 }, + { "007", 007 }, + { "7", 7 }, + { "8", (mode_t)-1 }, + { NULL, 0 } +}; + +/* + * Simple tests for sudo_strtomode(). + */ +int +main(int argc, char *argv[]) +{ + struct strtomode_data *d; + const char *errstr; + int ch, errors = 0, ntests = 0; + mode_t mode; + + initprogname(argc > 0 ? argv[0] : "strtomode_test"); + + while ((ch = getopt(argc, argv, "v")) != -1) { + switch (ch) { + case 'v': + /* ignore */ + break; + default: + fprintf(stderr, "usage: %s [-v]\n", getprogname()); + return EXIT_FAILURE; + } + } + argc -= optind; + argv += optind; + + for (d = strtomode_data; d->mode_str != NULL; d++) { + ntests++; + errstr = "some error"; + mode = sudo_strtomode(d->mode_str, &errstr); + if (errstr != NULL) { + if (d->mode != (mode_t)-1) { + sudo_warnx_nodebug("FAIL: %s: %s", d->mode_str, errstr); + errors++; + } + } else if (mode != d->mode) { + sudo_warnx_nodebug("FAIL: %s != 0%o", d->mode_str, + (unsigned int) d->mode); + errors++; + } + } + + if (ntests != 0) { + printf("%s: %d tests run, %d errors, %d%% success rate\n", + getprogname(), ntests, errors, (ntests - errors) * 100 / ntests); + } + + return errors; +} diff --git a/lib/util/regress/strtofoo/strtonum_test.c b/lib/util/regress/strtofoo/strtonum_test.c new file mode 100644 index 0000000..8aca97d --- /dev/null +++ b/lib/util/regress/strtofoo/strtonum_test.c @@ -0,0 +1,135 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2019-2020 Todd C. Miller <Todd.Miller@sudo.ws> + * + * 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 <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> +#include <errno.h> +#include <unistd.h> + +#include "sudo_compat.h" +#include "sudo_util.h" +#include "sudo_fatal.h" + +sudo_dso_public int main(int argc, char *argv[]); + +/* sudo_strtonum() tests */ +static struct strtonum_data { + const char *str; + long long minval; + long long maxval; + long long retval; + int errnum; +} strtonum_data[] = { + { "0,1", LLONG_MIN, LLONG_MAX, 0, EINVAL }, + { "0", INT_MAX, INT_MIN, 0, EINVAL }, + { "", 0, UINT_MAX, 0, EINVAL }, + { " ", 0, UINT_MAX, 0, EINVAL }, + { "-1 ", 0, UINT_MAX, 0, EINVAL }, + { "9223372036854775808X", LLONG_MIN, LLONG_MAX, 0, EINVAL }, + { "-9223372036854775809X", LLONG_MIN, LLONG_MAX, 0, EINVAL }, + + { "10", 0, 255, 10, 0 }, + { "-1", 0, UINT_MAX, 0, ERANGE }, + + { "-40", -100, -50, 0, ERANGE }, + { "-60", -100, -50, -60, 0 }, + { "-200", -100, -50, 0, ERANGE }, + + { "42", 42, 42, 42, 0 }, + { "-42", -42, -42, -42, 0 }, + + { "4294967295", 0, UINT_MAX, UINT_MAX, 0 }, + { "4294967295", INT_MIN, INT_MAX, 0, ERANGE }, + { "4294967296", 0, UINT_MAX, 0, ERANGE }, + + { "2147483647", INT_MIN, INT_MAX, INT_MAX, 0 }, + { "-2147483648", INT_MIN, INT_MAX, INT_MIN, 0 }, + { "2147483648", INT_MIN, INT_MAX, 0, ERANGE }, + { "-2147483649", INT_MIN, INT_MAX, 0, ERANGE }, + + { "9223372036854775807", LLONG_MIN, LLONG_MAX, LLONG_MAX, 0 }, + { "-9223372036854775808", LLONG_MIN, LLONG_MAX, LLONG_MIN, 0 }, + { "9223372036854775808", LLONG_MIN, LLONG_MAX, 0, ERANGE }, + { "-9223372036854775809", LLONG_MIN, LLONG_MAX, 0, ERANGE }, + + { NULL, 0, 0, 0, 0 } +}; + +/* + * Simple tests for sudo_strtonum() + */ +int +main(int argc, char *argv[]) +{ + int ch, errors = 0, ntests = 0; + struct strtonum_data *d; + const char *errstr; + long long value; + + initprogname(argc > 0 ? argv[0] : "strtonum_test"); + + while ((ch = getopt(argc, argv, "v")) != -1) { + switch (ch) { + case 'v': + /* ignore */ + break; + default: + fprintf(stderr, "usage: %s [-v]\n", getprogname()); + return EXIT_FAILURE; + } + } + argc -= optind; + argv += optind; + + for (d = strtonum_data; d->str != NULL; d++) { + ntests++; + errstr = "some error"; + value = sudo_strtonum(d->str, d->minval, d->maxval, &errstr); + if (d->errnum != 0) { + if (errstr == NULL) { + sudo_warnx_nodebug("FAIL: \"%s\": missing errstr for errno %d", + d->str, d->errnum); + errors++; + } else if (value != 0) { + sudo_warnx_nodebug("FAIL: %s should return 0 on error", + d->str); + errors++; + } else if (errno != d->errnum) { + sudo_warnx_nodebug("FAIL: \"%s\": errno mismatch, %d != %d", + d->str, errno, d->errnum); + errors++; + } + } else if (errstr != NULL) { + sudo_warnx_nodebug("FAIL: \"%s\": %s", d->str, errstr); + errors++; + } else if (value != d->retval) { + sudo_warnx_nodebug("FAIL: %s != %lld", d->str, d->retval); + errors++; + } + } + + if (ntests != 0) { + printf("%s: %d tests run, %d errors, %d%% success rate\n", + getprogname(), ntests, errors, (ntests - errors) * 100 / ntests); + } + + return errors; +} diff --git a/lib/util/regress/sudo_conf/conf_test.c b/lib/util/regress/sudo_conf/conf_test.c new file mode 100644 index 0000000..1d9c1b6 --- /dev/null +++ b/lib/util/regress/sudo_conf/conf_test.c @@ -0,0 +1,125 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2013-2022 Todd C. Miller <Todd.Miller@sudo.ws> + * + * 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 <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "sudo_compat.h" +#include "sudo_conf.h" +#include "sudo_debug.h" +#include "sudo_util.h" + +static void sudo_conf_dump(void); + +sudo_dso_public int main(int argc, char *argv[]); + +/* Awful hack for macOS where the default group source is dynamic. */ +#ifdef __APPLE__ +# undef GROUP_SOURCE_ADAPTIVE +# define GROUP_SOURCE_ADAPTIVE GROUP_SOURCE_DYNAMIC +#endif + +static void +usage(void) +{ + fprintf(stderr, "usage: %s [-v] conf_file\n", getprogname()); + exit(EXIT_FAILURE); +} + +/* + * Simple test driver for sudo_conf(). + * Parses the given configuration file and dumps the resulting + * sudo_conf_data struct to the standard output. + */ +int +main(int argc, char *argv[]) +{ + int ch; + + initprogname(argc > 0 ? argv[0] : "conf_test"); + + while ((ch = getopt(argc, argv, "v")) != -1) { + switch (ch) { + case 'v': + /* ignore */ + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (argc != 1) + usage(); + + sudo_conf_clear_paths(); + if (sudo_conf_read(argv[0], SUDO_CONF_ALL) == -1) + return EXIT_FAILURE; + sudo_conf_dump(); + + return EXIT_SUCCESS; +} + +static void +sudo_conf_dump(void) +{ + struct plugin_info_list *plugins = sudo_conf_plugins(); + struct sudo_conf_debug_list *debug_list = sudo_conf_debugging(); + struct sudo_conf_debug *debug_spec; + struct sudo_debug_file *debug_file; + struct plugin_info *info; + + printf("Set disable_coredump %s\n", + sudo_conf_disable_coredump() ? "true" : "false"); + printf("Set group_source %s\n", + sudo_conf_group_source() == GROUP_SOURCE_ADAPTIVE ? "adaptive" : + sudo_conf_group_source() == GROUP_SOURCE_STATIC ? "static" : "dynamic"); + printf("Set max_groups %d\n", sudo_conf_max_groups()); + printf("Set probe_interfaces %s\n", + sudo_conf_probe_interfaces() ? "true" : "false"); + if (sudo_conf_askpass_path() != NULL) + printf("Path askpass %s\n", sudo_conf_askpass_path()); + if (sudo_conf_sesh_path() != NULL) + printf("Path sesh %s\n", sudo_conf_sesh_path()); + if (sudo_conf_intercept_path() != NULL) + printf("Path intercept %s\n", sudo_conf_intercept_path()); + if (sudo_conf_noexec_path() != NULL) + printf("Path noexec %s\n", sudo_conf_noexec_path()); + if (sudo_conf_plugin_dir_path() != NULL) + printf("Path plugin_dir %s\n", sudo_conf_plugin_dir_path()); + TAILQ_FOREACH(info, plugins, entries) { + printf("Plugin %s %s", info->symbol_name, info->path); + if (info->options) { + char * const * op; + for (op = info->options; *op != NULL; op++) + printf(" %s", *op); + } + putchar('\n'); + } + TAILQ_FOREACH(debug_spec, debug_list, entries) { + TAILQ_FOREACH(debug_file, &debug_spec->debug_files, entries) { + printf("Debug %s %s %s\n", debug_spec->progname, + debug_file->debug_file, debug_file->debug_flags); + } + } +} diff --git a/lib/util/regress/sudo_conf/test1.in b/lib/util/regress/sudo_conf/test1.in new file mode 100644 index 0000000..bc9f626 --- /dev/null +++ b/lib/util/regress/sudo_conf/test1.in @@ -0,0 +1,73 @@ +# +# Sample /etc/sudo.conf file +# +# Format: +# Plugin plugin_name plugin_path plugin_options ... +# Path askpass /path/to/askpass +# Path noexec /path/to/sudo_noexec.so +# Debug sudo /var/log/sudo_debug all@warn +# Set disable_coredump true +# +# Sudo plugins: +# +# The plugin_path is relative to ${prefix}/libexec unless fully qualified. +# The plugin_name corresponds to a global symbol in the plugin +# that contains the plugin interface structure. +# The plugin_options are optional. +# +# The sudoers plugin is used by default if no Plugin lines are present. +Plugin sudoers_policy sudoers.so +Plugin sudoers_io sudoers.so + +# +# Sudo askpass: +# +# An askpass helper program may be specified to provide a graphical +# password prompt for "sudo -A" support. Sudo does not ship with its +# own askpass program but can use the OpenSSH askpass. +# +# Use the OpenSSH askpass +Path askpass /usr/X11R6/bin/ssh-askpass +# +# Use the Gnome OpenSSH askpass +#Path askpass /usr/libexec/openssh/gnome-ssh-askpass + +# +# Sudo noexec: +# +# Path to a shared library containing replacements for the execv(), +# execve() and fexecve() library functions that just return an error. +# This is used to implement the "noexec" functionality on systems that +# support LD_PRELOAD or its equivalent. +# The compiled-in value is usually sufficient and should only be changed +# if you rename or move the sudo_noexec.so file. +# +Path noexec /usr/local/libexec/sudo_noexec.so +Path noexec /usr/libexec/sudo_noexec.so + +# +# Core dumps: +# +# By default, sudo disables core dumps while it is executing (they +# are re-enabled for the command that is run). +# To aid in debugging sudo problems, you may wish to enable core +# dumps by setting "disable_coredump" to false. +# +Set disable_coredump false + +# +# User groups: +# +# Sudo passes the user's group list to the policy plugin. +# If the user is a member of the maximum number of groups (usually 16), +# sudo will query the group database directly to be sure to include +# the full list of groups. +# +# On some systems, this can be expensive so the behavior is configurable. +# The "group_source" setting has three possible values: +# static - use the user's list of groups returned by the kernel. +# dynamic - query the group database to find the list of groups. +# adaptive - if user is in less than the maximum number of groups. +# use the kernel list, else query the group database. +# +Set group_source static diff --git a/lib/util/regress/sudo_conf/test1.out.ok b/lib/util/regress/sudo_conf/test1.out.ok new file mode 100644 index 0000000..d5b784c --- /dev/null +++ b/lib/util/regress/sudo_conf/test1.out.ok @@ -0,0 +1,8 @@ +Set disable_coredump false +Set group_source static +Set max_groups -1 +Set probe_interfaces true +Path askpass /usr/X11R6/bin/ssh-askpass +Path noexec /usr/libexec/sudo_noexec.so +Plugin sudoers_policy sudoers.so +Plugin sudoers_io sudoers.so diff --git a/lib/util/regress/sudo_conf/test2.in b/lib/util/regress/sudo_conf/test2.in new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/util/regress/sudo_conf/test2.in diff --git a/lib/util/regress/sudo_conf/test2.out.ok b/lib/util/regress/sudo_conf/test2.out.ok new file mode 100644 index 0000000..cfd8a08 --- /dev/null +++ b/lib/util/regress/sudo_conf/test2.out.ok @@ -0,0 +1,4 @@ +Set disable_coredump true +Set group_source adaptive +Set max_groups -1 +Set probe_interfaces true diff --git a/lib/util/regress/sudo_conf/test3.in b/lib/util/regress/sudo_conf/test3.in new file mode 100644 index 0000000..b111a23 --- /dev/null +++ b/lib/util/regress/sudo_conf/test3.in @@ -0,0 +1,2 @@ +Plugin sudoers_policy sudoers.so sudoers_file=/etc/sudoers sudoers_mode=0400 sudoers_gid=0 sudoers_uid=0 +Plugin sudoers_io sudoers.so diff --git a/lib/util/regress/sudo_conf/test3.out.ok b/lib/util/regress/sudo_conf/test3.out.ok new file mode 100644 index 0000000..3ff2284 --- /dev/null +++ b/lib/util/regress/sudo_conf/test3.out.ok @@ -0,0 +1,6 @@ +Set disable_coredump true +Set group_source adaptive +Set max_groups -1 +Set probe_interfaces true +Plugin sudoers_policy sudoers.so sudoers_file=/etc/sudoers sudoers_mode=0400 sudoers_gid=0 sudoers_uid=0 +Plugin sudoers_io sudoers.so diff --git a/lib/util/regress/sudo_conf/test4.err.ok b/lib/util/regress/sudo_conf/test4.err.ok new file mode 100644 index 0000000..2d68831 --- /dev/null +++ b/lib/util/regress/sudo_conf/test4.err.ok @@ -0,0 +1 @@ +conf_test: invalid value for disable_coredump "foo" in regress/sudo_conf/test4.in, line 1 diff --git a/lib/util/regress/sudo_conf/test4.in b/lib/util/regress/sudo_conf/test4.in new file mode 100644 index 0000000..a60236a --- /dev/null +++ b/lib/util/regress/sudo_conf/test4.in @@ -0,0 +1 @@ +Set disable_coredump foo diff --git a/lib/util/regress/sudo_conf/test4.out.ok b/lib/util/regress/sudo_conf/test4.out.ok new file mode 100644 index 0000000..cfd8a08 --- /dev/null +++ b/lib/util/regress/sudo_conf/test4.out.ok @@ -0,0 +1,4 @@ +Set disable_coredump true +Set group_source adaptive +Set max_groups -1 +Set probe_interfaces true diff --git a/lib/util/regress/sudo_conf/test5.err.ok b/lib/util/regress/sudo_conf/test5.err.ok new file mode 100644 index 0000000..85ef46b --- /dev/null +++ b/lib/util/regress/sudo_conf/test5.err.ok @@ -0,0 +1 @@ +conf_test: invalid max groups "0" in regress/sudo_conf/test5.in, line 1 diff --git a/lib/util/regress/sudo_conf/test5.in b/lib/util/regress/sudo_conf/test5.in new file mode 100644 index 0000000..3a20495 --- /dev/null +++ b/lib/util/regress/sudo_conf/test5.in @@ -0,0 +1 @@ +Set max_groups 0 diff --git a/lib/util/regress/sudo_conf/test5.out.ok b/lib/util/regress/sudo_conf/test5.out.ok new file mode 100644 index 0000000..cfd8a08 --- /dev/null +++ b/lib/util/regress/sudo_conf/test5.out.ok @@ -0,0 +1,4 @@ +Set disable_coredump true +Set group_source adaptive +Set max_groups -1 +Set probe_interfaces true diff --git a/lib/util/regress/sudo_conf/test6.in b/lib/util/regress/sudo_conf/test6.in new file mode 100644 index 0000000..537fa57 --- /dev/null +++ b/lib/util/regress/sudo_conf/test6.in @@ -0,0 +1 @@ +Set max_groups 16 diff --git a/lib/util/regress/sudo_conf/test6.out.ok b/lib/util/regress/sudo_conf/test6.out.ok new file mode 100644 index 0000000..674ae38 --- /dev/null +++ b/lib/util/regress/sudo_conf/test6.out.ok @@ -0,0 +1,4 @@ +Set disable_coredump true +Set group_source adaptive +Set max_groups 16 +Set probe_interfaces true diff --git a/lib/util/regress/sudo_conf/test7.in b/lib/util/regress/sudo_conf/test7.in new file mode 100644 index 0000000..7438131 --- /dev/null +++ b/lib/util/regress/sudo_conf/test7.in @@ -0,0 +1,4 @@ +Debug sudo /var/log/sudo_debug all@info +Debug sudo /var/log/sudo_debug util@debug +Debug visudo /var/log/sudo_debug match@debug +Debug sudoers.so /var/log/sudoers_debug match@debug,nss@info diff --git a/lib/util/regress/sudo_conf/test7.out.ok b/lib/util/regress/sudo_conf/test7.out.ok new file mode 100644 index 0000000..7ec856d --- /dev/null +++ b/lib/util/regress/sudo_conf/test7.out.ok @@ -0,0 +1,8 @@ +Set disable_coredump true +Set group_source adaptive +Set max_groups -1 +Set probe_interfaces true +Debug sudo /var/log/sudo_debug all@info +Debug sudo /var/log/sudo_debug util@debug +Debug visudo /var/log/sudo_debug match@debug +Debug sudoers.so /var/log/sudoers_debug match@debug,nss@info diff --git a/lib/util/regress/sudo_parseln/parseln_test.c b/lib/util/regress/sudo_parseln/parseln_test.c new file mode 100644 index 0000000..8cd7294 --- /dev/null +++ b/lib/util/regress/sudo_parseln/parseln_test.c @@ -0,0 +1,64 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2013 Todd C. Miller <Todd.Miller@sudo.ws> + * + * 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 <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "sudo_compat.h" +#include "sudo_util.h" + +sudo_dso_public int main(int argc, char *argv[]); + +/* + * Simple test driver for sudo_parseln(). + * Behaves similarly to "cat -n" but with comment removal + * and line continuation. + */ + +int +main(int argc, char *argv[]) +{ + unsigned int lineno = 0; + size_t linesize = 0; + char *line = NULL; + int ch; + + initprogname(argc > 0 ? argv[0] : "parseln_test"); + + while ((ch = getopt(argc, argv, "v")) != -1) { + switch (ch) { + case 'v': + /* ignore */ + break; + default: + fprintf(stderr, "usage: %s [-v]\n", getprogname()); + return EXIT_FAILURE; + } + } + argc -= optind; + argv += optind; + + while (sudo_parseln(&line, &linesize, &lineno, stdin, 0) != -1) + printf("%6u\t%s\n", lineno, line); + free(line); + return EXIT_SUCCESS; +} diff --git a/lib/util/regress/sudo_parseln/test1.in b/lib/util/regress/sudo_parseln/test1.in new file mode 100644 index 0000000..8f417dd --- /dev/null +++ b/lib/util/regress/sudo_parseln/test1.in @@ -0,0 +1,72 @@ +# +# Sample /etc/sudo.conf file +# +# Format: +# Plugin plugin_name plugin_path plugin_options ... +# Path askpass /path/to/askpass +# Path noexec /path/to/sudo_noexec.so +# Debug sudo /var/log/sudo_debug all@warn +# Set disable_coredump true +# +# Sudo plugins: +# +# The plugin_path is relative to ${prefix}/libexec unless fully qualified. +# The plugin_name corresponds to a global symbol in the plugin +# that contains the plugin interface structure. +# The plugin_options are optional. +# +# The sudoers plugin is used by default if no Plugin lines are present. +Plugin sudoers_policy sudoers.so +Plugin sudoers_io sudoers.so + +# +# Sudo askpass: +# +# An askpass helper program may be specified to provide a graphical +# password prompt for "sudo -A" support. Sudo does not ship with its +# own askpass program but can use the OpenSSH askpass. +# +# Use the OpenSSH askpass +#Path askpass /usr/X11R6/bin/ssh-askpass +# +# Use the Gnome OpenSSH askpass +#Path askpass /usr/libexec/openssh/gnome-ssh-askpass + +# +# Sudo noexec: +# +# Path to a shared library containing replacements for the execv(), +# execve() and fexecve() library functions that just return an error. +# This is used to implement the "noexec" functionality on systems that +# support LD_PRELOAD or its equivalent. +# The compiled-in value is usually sufficient and should only be changed +# if you rename or move the sudo_noexec.so file. +# +#Path noexec /usr/libexec/sudo_noexec.so + +# +# Core dumps: +# +# By default, sudo disables core dumps while it is executing (they +# are re-enabled for the command that is run). +# To aid in debugging sudo problems, you may wish to enable core +# dumps by setting "disable_coredump" to false. +# +#Set disable_coredump false + +# +# User groups: +# +# Sudo passes the user's group list to the policy plugin. +# If the user is a member of the maximum number of groups (usually 16), +# sudo will query the group database directly to be sure to include +# the full list of groups. +# +# On some systems, this can be expensive so the behavior is configurable. +# The "group_source" setting has three possible values: +# static - use the user's list of groups returned by the kernel. +# dynamic - query the group database to find the list of groups. +# adaptive - if user is in less than the maximum number of groups. +# use the kernel list, else query the group database. +# +#Set group_source static diff --git a/lib/util/regress/sudo_parseln/test1.out.ok b/lib/util/regress/sudo_parseln/test1.out.ok new file mode 100644 index 0000000..c98ca77 --- /dev/null +++ b/lib/util/regress/sudo_parseln/test1.out.ok @@ -0,0 +1,72 @@ + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 Plugin sudoers_policy sudoers.so + 20 Plugin sudoers_io sudoers.so + 21 + 22 + 23 + 24 + 25 + 26 + 27 + 28 + 29 + 30 + 31 + 32 + 33 + 34 + 35 + 36 + 37 + 38 + 39 + 40 + 41 + 42 + 43 + 44 + 45 + 46 + 47 + 48 + 49 + 50 + 51 + 52 + 53 + 54 + 55 + 56 + 57 + 58 + 59 + 60 + 61 + 62 + 63 + 64 + 65 + 66 + 67 + 68 + 69 + 70 + 71 + 72 diff --git a/lib/util/regress/sudo_parseln/test2.in b/lib/util/regress/sudo_parseln/test2.in new file mode 100644 index 0000000..49166ee --- /dev/null +++ b/lib/util/regress/sudo_parseln/test2.in @@ -0,0 +1,8 @@ +this \ +is all \ +one line +# this is a comment, and does not get continued\ +trim the \ + leading \ + white \ +space diff --git a/lib/util/regress/sudo_parseln/test2.out.ok b/lib/util/regress/sudo_parseln/test2.out.ok new file mode 100644 index 0000000..d921968 --- /dev/null +++ b/lib/util/regress/sudo_parseln/test2.out.ok @@ -0,0 +1,3 @@ + 3 this is all one line + 4 + 8 trim the leading white space diff --git a/lib/util/regress/sudo_parseln/test3.in b/lib/util/regress/sudo_parseln/test3.in new file mode 100644 index 0000000..e372c07 --- /dev/null +++ b/lib/util/regress/sudo_parseln/test3.in @@ -0,0 +1 @@ +line continuation at EOF \ diff --git a/lib/util/regress/sudo_parseln/test3.out.ok b/lib/util/regress/sudo_parseln/test3.out.ok new file mode 100644 index 0000000..2e8d16d --- /dev/null +++ b/lib/util/regress/sudo_parseln/test3.out.ok @@ -0,0 +1 @@ + 1 line continuation at EOF diff --git a/lib/util/regress/sudo_parseln/test4.in b/lib/util/regress/sudo_parseln/test4.in new file mode 100644 index 0000000..3583f3b --- /dev/null +++ b/lib/util/regress/sudo_parseln/test4.in @@ -0,0 +1,4 @@ +line contin\ +uation raw +line contin\ + uation indented diff --git a/lib/util/regress/sudo_parseln/test4.out.ok b/lib/util/regress/sudo_parseln/test4.out.ok new file mode 100644 index 0000000..38afbeb --- /dev/null +++ b/lib/util/regress/sudo_parseln/test4.out.ok @@ -0,0 +1,2 @@ + 2 line continuation raw + 4 line continuation indented diff --git a/lib/util/regress/sudo_parseln/test5.in b/lib/util/regress/sudo_parseln/test5.in new file mode 100644 index 0000000..57ddad2 --- /dev/null +++ b/lib/util/regress/sudo_parseln/test5.in @@ -0,0 +1 @@ +\ diff --git a/lib/util/regress/sudo_parseln/test5.out.ok b/lib/util/regress/sudo_parseln/test5.out.ok new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/util/regress/sudo_parseln/test5.out.ok diff --git a/lib/util/regress/sudo_parseln/test6.in b/lib/util/regress/sudo_parseln/test6.in new file mode 100644 index 0000000..95cac84 --- /dev/null +++ b/lib/util/regress/sudo_parseln/test6.in @@ -0,0 +1,3 @@ + leading and trailing white space + # a comment +\ diff --git a/lib/util/regress/sudo_parseln/test6.out.ok b/lib/util/regress/sudo_parseln/test6.out.ok new file mode 100644 index 0000000..340765e --- /dev/null +++ b/lib/util/regress/sudo_parseln/test6.out.ok @@ -0,0 +1,2 @@ + 1 leading and trailing white space + 2 diff --git a/lib/util/regress/tailq/hltq_test.c b/lib/util/regress/tailq/hltq_test.c new file mode 100644 index 0000000..2a1b2bb --- /dev/null +++ b/lib/util/regress/tailq/hltq_test.c @@ -0,0 +1,205 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2013 Todd C. Miller <Todd.Miller@sudo.ws> + * + * 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 <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <unistd.h> + +#include "sudo_compat.h" +#include "sudo_fatal.h" +#include "sudo_queue.h" +#include "sudo_util.h" + +sudo_dso_public int main(int argc, char *argv[]); + +/* + * Note: HLTQ_ENTRY is intentionally in the middle of the struct + * to catch bad assumptions in the PREV/NEXT macros. + */ +struct test_data { + int a; + HLTQ_ENTRY(test_data) entries; + char b; +}; + +TAILQ_HEAD(test_data_list, test_data); + +/* + * Simple tests for headless tail queue macros. + */ +int +main(int argc, char *argv[]) +{ + struct test_data d1, d2, d3; + struct test_data *hltq; + struct test_data_list tq; + int ch, errors = 0, ntests = 0; + + initprogname(argc > 0 ? argv[0] : "hltq_test"); + + while ((ch = getopt(argc, argv, "v")) != -1) { + switch (ch) { + case 'v': + /* ignore */ + break; + default: + fprintf(stderr, "usage: %s [-v]\n", getprogname()); + return EXIT_FAILURE; + } + } + argc -= optind; + argv += optind; + + /* + * Initialize three data elements and concatenate them in order. + */ + HLTQ_INIT(&d1, entries); + d1.a = 1; + d1.b = 'a'; + if (HLTQ_FIRST(&d1) != &d1) { + sudo_warnx_nodebug("FAIL: HLTQ_FIRST(1 entry) doesn't return first element: got %p, expected %p", HLTQ_FIRST(&d1), &d1); + errors++; + } + ntests++; + if (HLTQ_LAST(&d1, test_data, entries) != &d1) { + sudo_warnx_nodebug("FAIL: HLTQ_LAST(1 entry) doesn't return first element: got %p, expected %p", HLTQ_LAST(&d1, test_data, entries), &d1); + errors++; + } + ntests++; + if (HLTQ_PREV(&d1, test_data, entries) != NULL) { + sudo_warnx_nodebug("FAIL: HLTQ_PREV(1 entry) doesn't return NULL: got %p", HLTQ_PREV(&d1, test_data, entries)); + errors++; + } + ntests++; + + HLTQ_INIT(&d2, entries); + d2.a = 2; + d2.b = 'b'; + + HLTQ_INIT(&d3, entries); + d3.a = 3; + d3.b = 'c'; + + HLTQ_CONCAT(&d1, &d2, entries); + HLTQ_CONCAT(&d1, &d3, entries); + hltq = &d1; + + /* + * Verify that HLTQ_FIRST, HLTQ_LAST, HLTQ_NEXT, HLTQ_PREV + * work as expected. + */ + if (HLTQ_FIRST(hltq) != &d1) { + sudo_warnx_nodebug("FAIL: HLTQ_FIRST(3 entries) doesn't return first element: got %p, expected %p", HLTQ_FIRST(hltq), &d1); + errors++; + } + ntests++; + if (HLTQ_LAST(hltq, test_data, entries) != &d3) { + sudo_warnx_nodebug("FAIL: HLTQ_LAST(3 entries) doesn't return third element: got %p, expected %p", HLTQ_LAST(hltq, test_data, entries), &d3); + errors++; + } + ntests++; + + if (HLTQ_NEXT(&d1, entries) != &d2) { + sudo_warnx_nodebug("FAIL: HLTQ_NEXT(&d1) doesn't return &d2: got %p, expected %p", HLTQ_NEXT(&d1, entries), &d2); + errors++; + } + ntests++; + if (HLTQ_NEXT(&d2, entries) != &d3) { + sudo_warnx_nodebug("FAIL: HLTQ_NEXT(&d2) doesn't return &d3: got %p, expected %p", HLTQ_NEXT(&d2, entries), &d3); + errors++; + } + ntests++; + if (HLTQ_NEXT(&d3, entries) != NULL) { + sudo_warnx_nodebug("FAIL: HLTQ_NEXT(&d3) doesn't return NULL: got %p", HLTQ_NEXT(&d3, entries)); + errors++; + } + ntests++; + + if (HLTQ_PREV(&d1, test_data, entries) != NULL) { + sudo_warnx_nodebug("FAIL: HLTQ_PREV(&d1) doesn't return NULL: got %p", HLTQ_PREV(&d1, test_data, entries)); + errors++; + } + ntests++; + if (HLTQ_PREV(&d2, test_data, entries) != &d1) { + sudo_warnx_nodebug("FAIL: HLTQ_PREV(&d2) doesn't return &d1: got %p, expected %p", HLTQ_PREV(&d2, test_data, entries), &d1); + errors++; + } + ntests++; + if (HLTQ_PREV(&d3, test_data, entries) != &d2) { + sudo_warnx_nodebug("FAIL: HLTQ_PREV(&d3) doesn't return &d2: got %p, expected %p", HLTQ_PREV(&d3, test_data, entries), &d2); + errors++; + } + ntests++; + + /* Test conversion to TAILQ. */ + HLTQ_TO_TAILQ(&tq, hltq, entries); + + if (TAILQ_FIRST(&tq) != &d1) { + sudo_warnx_nodebug("FAIL: TAILQ_FIRST(&tq) doesn't return first element: got %p, expected %p", TAILQ_FIRST(&tq), &d1); + errors++; + } + ntests++; + if (TAILQ_LAST(&tq, test_data_list) != &d3) { + sudo_warnx_nodebug("FAIL: TAILQ_LAST(&tq) doesn't return third element: got %p, expected %p", TAILQ_LAST(&tq, test_data_list), &d3); + errors++; + } + ntests++; + + if (TAILQ_NEXT(&d1, entries) != &d2) { + sudo_warnx_nodebug("FAIL: TAILQ_NEXT(&d1) doesn't return &d2: got %p, expected %p", TAILQ_NEXT(&d1, entries), &d2); + errors++; + } + ntests++; + if (TAILQ_NEXT(&d2, entries) != &d3) { + sudo_warnx_nodebug("FAIL: TAILQ_NEXT(&d2) doesn't return &d3: got %p, expected %p", TAILQ_NEXT(&d2, entries), &d3); + errors++; + } + ntests++; + if (TAILQ_NEXT(&d3, entries) != NULL) { + sudo_warnx_nodebug("FAIL: TAILQ_NEXT(&d3) doesn't return NULL: got %p", TAILQ_NEXT(&d3, entries)); + errors++; + } + ntests++; + + if (TAILQ_PREV(&d1, test_data_list, entries) != NULL) { + sudo_warnx_nodebug("FAIL: TAILQ_PREV(&d1) doesn't return NULL: got %p", TAILQ_PREV(&d1, test_data_list, entries)); + errors++; + } + ntests++; + if (TAILQ_PREV(&d2, test_data_list, entries) != &d1) { + sudo_warnx_nodebug("FAIL: TAILQ_PREV(&d2) doesn't return &d1: got %p, expected %p", TAILQ_PREV(&d2, test_data_list, entries), &d1); + errors++; + } + ntests++; + if (TAILQ_PREV(&d3, test_data_list, entries) != &d2) { + sudo_warnx_nodebug("FAIL: TAILQ_PREV(&d3) doesn't return &d2: got %p, expected %p", TAILQ_PREV(&d3, test_data_list, entries), &d2); + errors++; + } + ntests++; + + 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/lib/util/regress/uuid/uuid_test.c b/lib/util/regress/uuid/uuid_test.c new file mode 100644 index 0000000..37ef417 --- /dev/null +++ b/lib/util/regress/uuid/uuid_test.c @@ -0,0 +1,105 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2021 Todd C. Miller <Todd.Miller@sudo.ws> + * + * 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 <config.h> + +#include <stdlib.h> +#if defined(HAVE_STDINT_H) +# include <stdint.h> +#elif defined(HAVE_INTTYPES_H) +# include <inttypes.h> +#endif +#include <string.h> +#include <unistd.h> + +#define SUDO_ERROR_WRAP 0 + +#include "sudo_compat.h" +#include "sudo_fatal.h" +#include "sudo_util.h" + +sudo_dso_public int main(int argc, char *argv[]); + +/* + * Test that sudo_uuid_create() generates a variant 1, version 4 uuid. + */ + +/* From RFC 4122. */ +struct uuid { + uint32_t time_low; + uint16_t time_mid; + uint16_t time_hi_and_version; + uint8_t clock_seq_hi_and_reserved; + uint8_t clock_seq_low; + uint8_t node[6]; +}; + +int +main(int argc, char *argv[]) +{ + int ch, errors = 0, ntests = 0; + union { + struct uuid id; + unsigned char u8[16]; + } uuid; + + initprogname(argc > 0 ? argv[0] : "uuid_test"); + + while ((ch = getopt(argc, argv, "v")) != -1) { + switch (ch) { + case 'v': + /* ignore */ + break; + default: + fprintf(stderr, "usage: %s [-v]\n", getprogname()); + return EXIT_FAILURE; + } + } + argc -= optind; + argv += optind; + + /* Do 16 passes. */ + for (ntests = 0; ntests < 16; ntests++) { + sudo_uuid_create(uuid.u8); + + /* Variant: two most significant bits (6 and 7) are 0 and 1. */ + if (ISSET(uuid.id.clock_seq_hi_and_reserved, (1 << 6))) { + sudo_warnx("uuid bit 6 set, should be clear"); + errors++; + continue; + } + if (!ISSET(uuid.id.clock_seq_hi_and_reserved, (1 << 7))) { + sudo_warnx("uuid bit 7 clear, should be set"); + errors++; + continue; + } + + /* Version: bits 12-15 are 0010. */ + if ((uuid.id.time_hi_and_version & 0xf000) != 0x4000) { + sudo_warnx("bad version: 0x%x", uuid.id.time_hi_and_version & 0xf000); + errors++; + continue; + } + } + + if (ntests != 0) { + printf("%s: %d tests run, %d errors, %d%% success rate\n", + getprogname(), ntests, errors, (ntests - errors) * 100 / ntests); + } + return errors; +} |