From 25f50e2f07f0f49237cf652733474f07c33d2f8c Mon Sep 17 00:00:00 2001 From: "Todd C. Miller" Date: Mon, 15 Feb 2021 13:17:26 -0700 Subject: [PATCH] Add simple fuzzer for sudo.conf parser. --- MANIFEST | 4 + lib/util/Makefile.in | 36 ++++- lib/util/regress/corpus/sudo_conf/sudo.conf.1 | 124 ++++++++++++++++ lib/util/regress/corpus/sudo_conf/sudo.conf.2 | 124 ++++++++++++++++ lib/util/regress/corpus/sudo_conf/sudo.conf.3 | 134 ++++++++++++++++++ lib/util/regress/fuzz/fuzz_sudo_conf.c | 59 ++++++++ 6 files changed, 479 insertions(+), 2 deletions(-) create mode 100644 lib/util/regress/corpus/sudo_conf/sudo.conf.1 create mode 100644 lib/util/regress/corpus/sudo_conf/sudo.conf.2 create mode 100644 lib/util/regress/corpus/sudo_conf/sudo.conf.3 create mode 100644 lib/util/regress/fuzz/fuzz_sudo_conf.c diff --git a/MANIFEST b/MANIFEST index 6ce9da93c..03fb14da7 100644 --- a/MANIFEST +++ b/MANIFEST @@ -211,8 +211,12 @@ lib/util/progname.c lib/util/pw_dup.c lib/util/pwrite.c lib/util/reallocarray.c +lib/util/regress/corpus/sudo_conf/sudo.conf.1 +lib/util/regress/corpus/sudo_conf/sudo.conf.2 +lib/util/regress/corpus/sudo_conf/sudo.conf.3 lib/util/regress/fnmatch/fnm_test.c lib/util/regress/fnmatch/fnm_test.in +lib/util/regress/fuzz/fuzz_sudo_conf.c lib/util/regress/getdelim/getdelim_test.c lib/util/regress/getgrouplist/getgrouplist_test.c lib/util/regress/glob/files diff --git a/lib/util/Makefile.in b/lib/util/Makefile.in index 6daff0c13..729a51b30 100644 --- a/lib/util/Makefile.in +++ b/lib/util/Makefile.in @@ -109,6 +109,13 @@ TEST_PROGS = conf_test hltq_test parseln_test progname_test strsplit_test \ TEST_LIBS = @LIBS@ TEST_LDFLAGS = @LDFLAGS@ +# Fuzzers +LIBFUZZSTUB = $(top_builddir)/lib/fuzzstub/libsudo_fuzzstub.la +LIB_FUZZING_ENGINE = @FUZZ_ENGINE@ +FUZZ_PROGS = fuzz_sudo_conf +FUZZ_LIBS = @LIBS@ $(LIB_FUZZING_ENGINE) +FUZZ_LDFLAGS = @LDFLAGS@ + # User and group ids the installed files should be "owned" by install_uid = 0 install_gid = 0 @@ -166,6 +173,8 @@ STRSIG_TEST_OBJS = strsig_test.lo sig2str.lo str2sig.lo @SIGNAME@ VSYSLOG_TEST_OBJS = vsyslog_test.lo vsyslog.lo +FUZZ_SUDO_CONF_OBJS = fuzz_sudo_conf.lo + all: libsudo_util.la pvs-log-files: $(POBJS) @@ -279,6 +288,9 @@ strtoid_test: $(STRTOID_TEST_OBJS) libsudo_util.la vsyslog_test: $(VSYSLOG_TEST_OBJS) libsudo_util.la $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(VSYSLOG_TEST_OBJS) libsudo_util.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(TEST_LDFLAGS) $(TEST_LIBS) +fuzz_sudo_conf: $(FUZZ_SUDO_CONF_OBJS) $(LIBFUZZSTUB) libsudo_util.la + $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(FUZZ_SUDO_CONF_OBJS) libsudo_util.la $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(FUZZ_LDFLAGS) $(FUZZ_LIBS) + pre-install: install: install-dirs @@ -313,10 +325,16 @@ cppcheck: pvs-log-files: $(POBJS) -fuzz: +fuzz: $(FUZZ_PROGS) + @if test X"$(cross_compiling)" != X"yes"; then \ + MALLOC_OPTIONS=S; export MALLOC_OPTIONS; \ + MALLOC_CONF="abort:true,junk:true"; export MALLOC_CONF; \ + echo "fuzz_sudo_conf: verifying corpus"; \ + ./fuzz_sudo_conf $(srcdir)/regress/corpus/sudo_conf/sudo.conf.*; \ + fi # Note: some regress checks are run from srcdir for consistent error messages -check: $(TEST_PROGS) +check: $(TEST_PROGS) fuzz @if test X"$(cross_compiling)" != X"yes"; then \ MALLOC_OPTIONS=S; export MALLOC_OPTIONS; \ MALLOC_CONF="abort:true,junk:true"; export MALLOC_CONF; \ @@ -647,6 +665,20 @@ fstatat.i: $(srcdir)/fstatat.c $(incdir)/sudo_compat.h $(top_builddir)/config.h $(CC) -E -o $@ $(CPPFLAGS) $< fstatat.plog: fstatat.i rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/fstatat.c --i-file $< --output-file $@ +fuzz_sudo_conf.lo: $(srcdir)/regress/fuzz/fuzz_sudo_conf.c \ + $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ + $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \ + $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \ + $(top_builddir)/config.h + $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/regress/fuzz/fuzz_sudo_conf.c +fuzz_sudo_conf.i: $(srcdir)/regress/fuzz/fuzz_sudo_conf.c \ + $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \ + $(incdir)/sudo_conf.h $(incdir)/sudo_debug.h \ + $(incdir)/sudo_queue.h $(incdir)/sudo_util.h \ + $(top_builddir)/config.h + $(CC) -E -o $@ $(CPPFLAGS) $< +fuzz_sudo_conf.plog: fuzz_sudo_conf.i + rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/regress/fuzz/fuzz_sudo_conf.c --i-file $< --output-file $@ getaddrinfo.lo: $(srcdir)/getaddrinfo.c $(incdir)/compat/getaddrinfo.h \ $(incdir)/sudo_compat.h $(top_builddir)/config.h $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/getaddrinfo.c diff --git a/lib/util/regress/corpus/sudo_conf/sudo.conf.1 b/lib/util/regress/corpus/sudo_conf/sudo.conf.1 new file mode 100644 index 000000000..4d0787536 --- /dev/null +++ b/lib/util/regress/corpus/sudo_conf/sudo.conf.1 @@ -0,0 +1,124 @@ +# +# 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 + +# +# Sudo developer mode: +# Set developer_mode true|false +# +# Allow loading of plugins that are owned by non-root or are writable +# by "group" or "other". Should only be used during plugin development. +#Set developer_mode true + +# +# 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/sudo_conf/sudo.conf.2 b/lib/util/regress/corpus/sudo_conf/sudo.conf.2 new file mode 100644 index 000000000..c99e623a7 --- /dev/null +++ b/lib/util/regress/corpus/sudo_conf/sudo.conf.2 @@ -0,0 +1,124 @@ +# +# 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 + +# +# Sudo developer mode: +# Set developer_mode true|false +# +# Allow loading of plugins that are owned by non-root or are writable +# by "group" or "other". Should only be used during plugin development. +Set developer_mode true + +# +# 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/sudo_conf/sudo.conf.3 b/lib/util/regress/corpus/sudo_conf/sudo.conf.3 new file mode 100644 index 000000000..52f9c8500 --- /dev/null +++ b/lib/util/regress/corpus/sudo_conf/sudo.conf.3 @@ -0,0 +1,134 @@ +# +# 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 + +# +# Sudo developer mode: +# Set developer_mode true|false +# +# Allow loading of plugins that are owned by non-root or are writable +# by "group" or "other". Should only be used during plugin development. +Set developer_mode true + +# +# 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/fuzz/fuzz_sudo_conf.c b/lib/util/regress/fuzz/fuzz_sudo_conf.c new file mode 100644 index 000000000..9d849adc1 --- /dev/null +++ b/lib/util/regress/fuzz/fuzz_sudo_conf.c @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2021 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include +#include +#if defined(HAVE_STDINT_H) +# include +#elif defined(HAVE_INTTYPES_H) +# include +#endif + +#include "sudo_compat.h" +#include "sudo_conf.h" +#include "sudo_debug.h" +#include "sudo_util.h" + +int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + char tempfile[] = "/tmp/sudo_conf.XXXXXX"; + size_t nwritten; + int fd; + + /* 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); + + unlink(tempfile); + + return 0; +}