]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
- Fix that unbound-checkconf checks if an auth-zone download
authorW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Wed, 17 Jun 2026 13:11:42 +0000 (15:11 +0200)
committerW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Wed, 17 Jun 2026 13:11:42 +0000 (15:11 +0200)
  can overwrite another file, by filename collision.
  Thanks to Qifan Zhang, Palo Alto Networks, for the report.

config.h.in
configure
configure.ac
doc/Changelog
smallapp/unbound-checkconf.c

index b601dbe61d5ce78da0af4d767756ce6b29132dda..1785ae5c0e3c770d29804680078f32305c3747c6 100644 (file)
 /* Define to 1 if you have the `FIPS_mode' function. */
 #undef HAVE_FIPS_MODE
 
+/* Define to 1 if you have the `fnmatch' function. */
+#undef HAVE_FNMATCH
+
+/* Define to 1 if you have the <fnmatch.h> header file. */
+#undef HAVE_FNMATCH_H
+
 /* Define to 1 if you have the `fork' function. */
 #undef HAVE_FORK
 
index 64c784fac77814b49bc7fdcaf71a1b58b35005f1..71eb3a3b2c3069dc7b291d462cd7590a35b423df 100755 (executable)
--- a/configure
+++ b/configure
@@ -16005,6 +16005,13 @@ if test "x$ac_cv_header_glob_h" = xyes
 then :
   printf "%s\n" "#define HAVE_GLOB_H 1" >>confdefs.h
 
+fi
+ac_fn_c_check_header_compile "$LINENO" "fnmatch.h" "ac_cv_header_fnmatch_h" "$ac_includes_default
+"
+if test "x$ac_cv_header_fnmatch_h" = xyes
+then :
+  printf "%s\n" "#define HAVE_FNMATCH_H 1" >>confdefs.h
+
 fi
 ac_fn_c_check_header_compile "$LINENO" "grp.h" "ac_cv_header_grp_h" "$ac_includes_default
 "
@@ -23848,6 +23855,12 @@ if test "x$ac_cv_func_glob" = xyes
 then :
   printf "%s\n" "#define HAVE_GLOB 1" >>confdefs.h
 
+fi
+ac_fn_c_check_func "$LINENO" "fnmatch" "ac_cv_func_fnmatch"
+if test "x$ac_cv_func_fnmatch" = xyes
+then :
+  printf "%s\n" "#define HAVE_FNMATCH 1" >>confdefs.h
+
 fi
 ac_fn_c_check_func "$LINENO" "initgroups" "ac_cv_func_initgroups"
 if test "x$ac_cv_func_initgroups" = xyes
index 212b45f28c80c7ec0d23b58f71c3b37c1ed50795..7cdf5803fc8440b9def09274045426583cabc371 100644 (file)
@@ -483,7 +483,7 @@ PKG_PROG_PKG_CONFIG
 fi
 
 # Checks for header files.
-AC_CHECK_HEADERS([stdarg.h stdbool.h netinet/in.h netinet/tcp.h sys/param.h sys/select.h sys/socket.h sys/un.h sys/uio.h sys/resource.h arpa/inet.h syslog.h netdb.h sys/wait.h pwd.h glob.h grp.h login_cap.h winsock2.h ws2tcpip.h endian.h sys/endian.h libkern/OSByteOrder.h sys/ipc.h sys/shm.h ifaddrs.h poll.h],,, [AC_INCLUDES_DEFAULT])
+AC_CHECK_HEADERS([stdarg.h stdbool.h netinet/in.h netinet/tcp.h sys/param.h sys/select.h sys/socket.h sys/un.h sys/uio.h sys/resource.h arpa/inet.h syslog.h netdb.h sys/wait.h pwd.h glob.h fnmatch.h grp.h login_cap.h winsock2.h ws2tcpip.h endian.h sys/endian.h libkern/OSByteOrder.h sys/ipc.h sys/shm.h ifaddrs.h poll.h],,, [AC_INCLUDES_DEFAULT])
 # net/if.h portability for Darwin see:
 # https://www.gnu.org/software/autoconf/manual/autoconf-2.69/html_node/Header-Portability.html
 AC_CHECK_HEADERS([net/if.h],,, [
@@ -1923,7 +1923,7 @@ AC_LINK_IFELSE([AC_LANG_PROGRAM([
   AC_MSG_RESULT(no))
 
 AC_SEARCH_LIBS([setusercontext], [util])
-AC_CHECK_FUNCS([tzset sigprocmask fcntl getpwnam endpwent getrlimit setrlimit setsid chroot kill chown sleep usleep random srandom recvmsg sendmsg writev socketpair glob initgroups strftime localtime_r setusercontext _beginthreadex endservent endprotoent fsync shmget accept4 getifaddrs if_nametoindex poll gettid])
+AC_CHECK_FUNCS([tzset sigprocmask fcntl getpwnam endpwent getrlimit setrlimit setsid chroot kill chown sleep usleep random srandom recvmsg sendmsg writev socketpair glob fnmatch initgroups strftime localtime_r setusercontext _beginthreadex endservent endprotoent fsync shmget accept4 getifaddrs if_nametoindex poll gettid])
 AC_CHECK_FUNCS([setresuid],,[AC_CHECK_FUNCS([setreuid])])
 AC_CHECK_FUNCS([setresgid],,[AC_CHECK_FUNCS([setregid])])
 
index 8b084e80682d0443a07f8ec2d731a5c3f5a84835..266c848979bc6a776eb3006aa02ef10b2c1467d2 100644 (file)
@@ -4,6 +4,9 @@
          auth_transfer_limit test to use a forwarder for each type
          of failure, so the one is not blocked by the other waiting.
        - Fix to remove debug from auth_transfer_limit test.
+       - Fix that unbound-checkconf checks if an auth-zone download
+         can overwrite another file, by filename collision.
+         Thanks to Qifan Zhang, Palo Alto Networks, for the report.
 
 16 June 2026: Wouter
        - Fix to disallow $INCLUDE for secondary zones. Start up
index a8e19241f782d16128829fc843d9efae1fc3fbdd..41cc2a050b1f9d43d1ffd0bc035ed89f9fd0ed92 100644 (file)
@@ -73,6 +73,9 @@
 #ifdef HAVE_GLOB_H
 #include <glob.h>
 #endif
+#ifdef HAVE_FNMATCH_H
+#include <fnmatch.h>
+#endif
 #ifdef WITH_PYTHONMODULE
 #include "pythonmod/pythonmod.h"
 #endif
@@ -728,6 +731,120 @@ check_modules_exist(const char* module_conf)
        }
 }
 
+/** Compare filename with string, true if it matches the name. */
+static int
+file_string_matches(char* str, char* fname, struct config_file* cfg)
+{
+       char* f;
+       if(!str || str[0] == 0)
+               return 0;
+       /* compare name after chroot and working dir are applied */
+       f = fname_after_chroot(str, cfg, 1);
+       if(!f) fatal_exit("out of memory");
+       if(strcmp(fname, f) == 0) {
+               free(f);
+               return 1;
+       }
+       free(f);
+       return 0;
+}
+
+/** Compare filename with list of files, true if list contains the name. */
+static int
+file_list_contains(struct config_strlist* list, char* fname,
+       struct config_file* cfg)
+{
+       struct config_strlist* s;
+       char* f;
+       for(s = list; s; s = s->next) {
+               if(!s->str || s->str[0] == 0)
+                       continue; /* skip if no file name */
+               /* compare names after chroot and working dir are applied */
+               f = fname_after_chroot(s->str, cfg, 1);
+               if(!f) fatal_exit("out of memory");
+               if(strcmp(fname, f) == 0) {
+                       free(f);
+                       return 1;
+               }
+               free(f);
+       }
+       return 0;
+}
+
+/** Compare filename with list of files, true if list contains the name,
+ * with glob compare. */
+static int
+file_list_contains_wild(struct config_strlist* list, char* fname,
+       struct config_file* cfg)
+{
+       struct config_strlist* s;
+       char* f;
+       for(s = list; s; s = s->next) {
+               if(!s->str || s->str[0] == 0)
+                       continue; /* skip if no file name */
+               /* compare names after chroot and working dir are applied */
+               f = fname_after_chroot(s->str, cfg, 1);
+               if(!f) fatal_exit("out of memory");
+               if(strcmp(fname, f) == 0) {
+                       free(f);
+                       return 1;
+               }
+#ifdef HAVE_FNMATCH
+               if(fnmatch(f, fname, 0) == 0) {
+                       log_err("trusted-keys-file: \"%s\" matches zonefile '%s'",
+                               s->str, fname);
+                       free(f);
+                       return 1;
+               }
+#endif
+               free(f);
+       }
+       return 0;
+}
+
+/** Check if the auth-zone/rpz zonefile: conflicts with other files,
+ * so it would overwrite that file. Refuse it aliasing any read-side bootstrap
+ * file. */
+static void
+check_file_clobber(struct config_file* cfg)
+{
+       struct config_auth* p;
+       char* zfile, *sourceopt = NULL;
+       for(p = cfg->auths; p; p = p->next) {
+               if(!p->name || p->name[0] == 0)
+                       continue; /* skip if no name */
+               if(!p->zonefile || p->zonefile[0]==0)
+                       continue; /* no zone file */
+               zfile = fname_after_chroot(p->zonefile, cfg, 1);
+               if(!zfile) fatal_exit("out of memory");
+               if(file_list_contains(cfg->auto_trust_anchor_file_list, zfile,
+                       cfg))
+                       sourceopt = "auto-trust-anchor-file";
+               else if(file_list_contains(cfg->trust_anchor_file_list, zfile,
+                       cfg))
+                       sourceopt = "trust-anchor-file";
+               else if(file_list_contains_wild(cfg->trusted_keys_file_list,
+                       zfile, cfg))
+                       sourceopt = "trusted-keys-file";
+               else if(file_list_contains(cfg->root_hints, zfile, cfg))
+                       sourceopt = "root-hints";
+               else if(file_list_contains(cfg->tls_session_ticket_keys.first,
+                       zfile, cfg))
+                       sourceopt = "tls-session-ticket-keys";
+#ifdef USE_IPSECMOD
+               if(cfg->ipsecmod_enabled &&
+                       file_string_matches(cfg->ipsecmod_hook, zfile, cfg))
+                       sourceopt = "ipsecmod-hook";
+#endif
+               if(sourceopt)
+                       fatal_exit("auth-zone '%s': zonefile \"%s\" "
+                               "is the same path as a %s option. "
+                               "The auth-zone transfer would overwrite it.",
+                               p->name, p->zonefile, sourceopt);
+               free(zfile);
+       }
+}
+
 /** check configuration for errors */
 static void
 morechecks(struct config_file* cfg)
@@ -822,6 +939,7 @@ morechecks(struct config_file* cfg)
                        cfg->chrootdir, cfg);
        }
 #endif
+       check_file_clobber(cfg);
        /* remove chroot setting so that modules are not stripping pathnames */
        free(cfg->chrootdir);
        cfg->chrootdir = NULL;