]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
allow UTF-8 characters while reading directories
authorAlan T. DeKok <aland@freeradius.org>
Sun, 28 Sep 2025 16:00:48 +0000 (12:00 -0400)
committerAlan T. DeKok <aland@freeradius.org>
Thu, 2 Oct 2025 21:07:59 +0000 (17:07 -0400)
src/lib/util/file.c

index 5f60d9340529094d5bfc081b02ea349f127a20ce..84d4f14add0c4ee422532a0e2f5ed468f30a1407 100644 (file)
@@ -429,6 +429,91 @@ int fr_dirfd(int *dirfd, char const **filename, char const *pathname)
        }
 }
 
+static bool fr_globdir_file_ok(char const *try, fr_globdir_iter_t *iter)
+{
+       size_t len, room;
+       char const *p;
+       struct stat stat_buf;
+
+       /*
+        *      Filter the filenames.
+        */
+       if (try[0] == '.') return false;
+
+       /*
+        *      Check for valid characters
+        */
+       p = try;
+       while (*p) {
+               /*
+                *      Control characters are invalid UTF-8, too.
+                */
+               len = fr_utf8_char((uint8_t const *) p, -1);
+               if (!len) return false;
+
+               /*
+                *      The string is valid UTF-8, but is NOT an ASCII
+                *      character.  We allow it.
+                */
+               if (len > 1) {
+                       p += len;
+                       continue;
+               }
+
+               /*
+                *      Limit the ASCII characters we allow.
+                */
+               if (isalpha((uint8_t)*p) ||
+                   isdigit((uint8_t)*p) ||
+                   (*p == '-') ||
+                   (*p == '_') ||
+                   (*p == '.')) {
+                       p++;
+                       continue;
+               }
+
+               /*
+                *      Invalid character in filename, it's not a match.
+                */
+               return false;
+       }
+
+       len = p - try;
+       if (!len) return false;
+
+       /*
+        *      Ignore files generated by deb / rpm packaging updates.
+        */
+       if ((len > 10) && (strncmp(&try[len - 10], ".dpkg-dist", 10) == 0)) return false;
+       if ((len > 9) && (strncmp(&try[len - 9], ".dpkg-old", 9) == 0)) return false;
+       if ((len > 7) && (strncmp(&try[len - 7], ".rpmnew", 9) == 0)) return false;
+       if ((len > 8) && (strncmp(&try[len - 8], ".rpmsave", 10) == 0)) return false;
+
+       /*
+        *      strlcpy() returns the length of the input, which can be larger than the available space.
+        */
+       room = (iter->path + PATH_MAX) - iter->filename;
+
+       if (strlcpy(iter->filename, try, room) >= room) return false;
+
+       /*
+        *      We only read normal files which are NOT executable, and symlinks.
+        */
+       if (stat(iter->path, &stat_buf) != 0) {
+               return false;
+       }
+
+       if (S_ISREG(stat_buf.st_mode)) {
+               if ((stat_buf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0) {
+                       return false;
+               }
+       } else if (!S_ISLNK(stat_buf.st_mode)) { /* soft links are executable */
+               return false;
+       }
+
+       return true;
+}
+
 #ifdef HAVE_DIRENT_H
 /*
  *     Filter filenames when reading a directory.
@@ -436,8 +521,6 @@ int fr_dirfd(int *dirfd, char const **filename, char const *pathname)
 static int fr_globdir_dir_next(char const **filename, fr_globdir_iter_t *iter)
 {
        struct dirent *dp;
-       char const *p;
-       size_t len;
 
        fr_assert(iter->dir != NULL);
 
@@ -455,36 +538,7 @@ static int fr_globdir_dir_next(char const **filename, fr_globdir_iter_t *iter)
                        return 0;
                }
 
-               /*
-                *      Filter the filenames.
-                */
-               if (dp->d_name[0] == '.') continue;
-
-               /*
-                *      Check for valid characters
-                */
-               for (p = dp->d_name; *p != '\0'; p++) {
-                       if (isalpha((uint8_t)*p) ||
-                           isdigit((uint8_t)*p) ||
-                           (*p == '-') ||
-                           (*p == '_') ||
-                           (*p == '.')) continue;
-                       break;
-               }
-
-               /*
-                *      Invalid character in filename.  Skip it.
-                */
-               if (*p != '\0') continue;
-
-               /*
-                *      Ignore files generated by deb / rpm packaging updates.
-                */
-               len = strlen(dp->d_name);
-               if ((len > 10) && (strncmp(&dp->d_name[len - 10], ".dpkg-dist", 10) == 0)) continue;
-               if ((len > 9) && (strncmp(&dp->d_name[len - 9], ".dpkg-old", 9) == 0)) continue;
-               if ((len > 7) && (strncmp(&dp->d_name[len - 7], ".rpmnew", 9) == 0)) continue;
-               if ((len > 8) && (strncmp(&dp->d_name[len - 8], ".rpmsave", 10) == 0)) continue;
+               if (!fr_globdir_file_ok(dp->d_name, iter)) continue;
 
                /*
                 *      It's mostly ASCII, and not a leftover file name, stop.
@@ -492,7 +546,7 @@ static int fr_globdir_dir_next(char const **filename, fr_globdir_iter_t *iter)
                break;
        }
 
-       *filename = dp->d_name;
+       *filename = iter->filename;
        return 0;
 }
 #endif
@@ -505,12 +559,11 @@ static int fr_globdir_get_path(char const *dir, char const *pattern, fr_globdir_
        char const *p;
        char *q;
        bool slash;
-       size_t size = strlen(dir) + 1 + strlen(pattern) + 1;
 
        /*
         *      Bootstrap the full path.
         */
-       iter->path = malloc(size);
+       iter->path = malloc(PATH_MAX);
        if (!iter->path) {
                errno = ENOMEM;
                return -1;
@@ -536,7 +589,7 @@ static int fr_globdir_get_path(char const *dir, char const *pattern, fr_globdir_
                        continue;
                }
 
-               if ((size_t) (q - iter->path) >= size) {
+               if ((size_t) (q - iter->path) >= PATH_MAX) {
                        free(iter->path);
                        errno = ENOMEM;
                        return -1;
@@ -557,7 +610,7 @@ static int fr_globdir_get_path(char const *dir, char const *pattern, fr_globdir_
         */
        p = pattern;
        while (*p) {
-               if ((size_t) (q - iter->path) >= size) {
+               if ((size_t) (q - iter->path) >= PATH_MAX) {
                        free(iter->path);
                        errno = ENOMEM;
                        return -1;
@@ -674,6 +727,9 @@ int fr_globdir_iter_init(char const **filename, char const *dir, char const *pat
                 */
                if ((*p == '*') || (*p == '?') || (*p == '[')) {
 #ifdef HAVE_GLOB_H
+                       /*
+                        *      @todo - call realpath() to get the canonical filename?
+                        */
                        if ((pattern[0] != '/') && (dir[0] != '/')) {
                                errno = ENOENT;
                                return -1;
@@ -760,6 +816,10 @@ int fr_globdir_iter_init(char const **filename, char const *dir, char const *pat
                        *filename = NULL;
                } else {
                        iter->gl_current = 0;
+
+                       /*
+                        *      @todo - check the filenames using fr_globdir_file_ok()
+                        */
                        *filename = iter->glob.gl_pathv[iter->gl_current];
                }
                break;