From: Karel Zak Date: Tue, 1 Jul 2025 10:26:41 +0000 (+0200) Subject: lib/canonicalize: introduce generic drop-permission caller X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=63d14a2e38dbd5c27d3badae5fd418f201a0c802;p=thirdparty%2Futil-linux.git lib/canonicalize: introduce generic drop-permission caller * add ul_restricted_path_oper() to fileutils.c * use it as backed for canonicalize_path_restricted() Signed-off-by: Karel Zak --- diff --git a/include/fileutils.h b/include/fileutils.h index 6fc93d0db..672f1c985 100644 --- a/include/fileutils.h +++ b/include/fileutils.h @@ -112,8 +112,11 @@ extern void ul_close_all_fds(unsigned int first, unsigned int last); #define UL_COPY_WRITE_ERROR (-2) int ul_copy_file(int from, int to); - extern int ul_reopen(int fd, int flags); extern char *ul_basename(char *path); +extern char *ul_restricted_path_oper(const char *path, + int (*oper)(const char *path, char **result, void *data), + void *data); + #endif /* UTIL_LINUX_FILEUTILS */ diff --git a/lib/Makemodule.am b/lib/Makemodule.am index d73ae01b7..f5cc846b5 100644 --- a/lib/Makemodule.am +++ b/lib/Makemodule.am @@ -203,7 +203,7 @@ test_fileutils_SOURCES = lib/fileutils.c test_fileutils_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_FILEUTILS test_fileutils_LDADD = $(LDADD) libcommon.la -test_canonicalize_SOURCES = lib/canonicalize.c +test_canonicalize_SOURCES = lib/canonicalize.c lib/fileutils.c test_canonicalize_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_CANONICALIZE test_canonicalize_LDADD = $(LDADD) libcommon.la diff --git a/lib/canonicalize.c b/lib/canonicalize.c index 9109b3630..ab86a7483 100644 --- a/lib/canonicalize.c +++ b/lib/canonicalize.c @@ -14,12 +14,12 @@ #include #include #include -#include #include "canonicalize.h" #include "pathnames.h" #include "all-io.h" #include "strutils.h" +#include "fileutils.h" /* * Converts private "dm-N" names to "/dev/mapper/" @@ -122,7 +122,8 @@ char *ul_absolute_path(const char *path) * Returns: <0 on error, 1 is cannot be canonicalized (errno is set); 0 on success */ static int __attribute__((nonnull(2))) -do_canonicalize(const char *path, char **result) +do_canonicalize(const char *path, char **result, + void *data __attribute__((__unused__))) { char *canonical, *dmname; @@ -160,99 +161,21 @@ char *canonicalize_path(const char *path) { char *canonical = NULL; - if (do_canonicalize(path, &canonical) == 1) + if (do_canonicalize(path, &canonical, NULL) == 1) return strdup(path); return canonical; } + /* - * Almost like canonicalize_path() but drops permissions (suid, etc.) to - * canonicalize the path. Returns NULL if the path is unreachable + * Drop permissions (e.g., suid) and canonicalize the path. If the path is + * unreadable (for example, due to missing permissions), it returns NULL. */ char *canonicalize_path_restricted(const char *path) { - char *canonical = NULL; - int errsv = 0; - int pipes[2]; - ssize_t len; - pid_t pid; - - if (!path || !*path) - return NULL; - - if (pipe(pipes) != 0) - return NULL; - - /* - * To accurately assume identity of getuid() we must use setuid() - * but if we do that, we lose ability to reassume euid of 0, so - * we fork to do the check to keep euid intact. - */ - pid = fork(); - switch (pid) { - case -1: - close(pipes[0]); - close(pipes[1]); - return NULL; /* fork error */ - case 0: - close(pipes[0]); /* close unused end */ - pipes[0] = -1; - errno = 0; - - if (drop_permissions() != 0) - canonical = NULL; /* failed */ - else - do_canonicalize(path, &canonical); - - len = canonical ?(ssize_t) strlen(canonical) : - errno ? -errno : -EINVAL; - - /* send length or errno */ - write_all(pipes[1], (char *) &len, sizeof(len)); - if (canonical) - write_all(pipes[1], canonical, len); - _exit(0); - default: - break; - } - - close(pipes[1]); /* close unused end */ - pipes[1] = -1; - - /* read size or -errno */ - if (read_all(pipes[0], (char *) &len, sizeof(len)) != sizeof(len)) - goto done; - if (len < 0) { - errsv = -len; - goto done; - } - - canonical = malloc(len + 1); - if (!canonical) { - errsv = ENOMEM; - goto done; - } - /* read path */ - if (read_all(pipes[0], canonical, len) != len) { - errsv = errno; - goto done; - } - canonical[len] = '\0'; -done: - if (errsv) { - free(canonical); - canonical = NULL; - } - close(pipes[0]); - - /* We make a best effort to reap child */ - ignore_result( waitpid(pid, NULL, 0) ); - - errno = errsv; - return canonical; + return ul_restricted_path_oper(path, do_canonicalize, NULL); } - #ifdef TEST_PROGRAM_CANONICALIZE int main(int argc, char **argv) { diff --git a/lib/fileutils.c b/lib/fileutils.c index b7acae430..c4e180617 100644 --- a/lib/fileutils.c +++ b/lib/fileutils.c @@ -12,6 +12,7 @@ #include #include #include +#include #include "c.h" #include "all-io.h" @@ -170,6 +171,94 @@ void ul_close_all_fds(unsigned int first, unsigned int last) } } +/* + * Fork, drop permissions, and call oper() and return result. + */ +char *ul_restricted_path_oper(const char *path, + int (*oper)(const char *path, char **result, void *data), + void *data) +{ + char *result = NULL; + int errsv = 0; + int pipes[2]; + ssize_t len; + pid_t pid; + + if (!path || !*path) + return NULL; + + if (pipe(pipes) != 0) + return NULL; + /* + * To accurately assume identity of getuid() we must use setuid() + * but if we do that, we lose ability to reassume euid of 0, so + * we fork to do the check to keep euid intact. + */ + pid = fork(); + switch (pid) { + case -1: + close(pipes[0]); + close(pipes[1]); + return NULL; /* fork error */ + case 0: + close(pipes[0]); /* close unused end */ + pipes[0] = -1; + errno = 0; + + if (drop_permissions() != 0) + result = NULL; /* failed */ + else + oper(path, &result, data); + + len = result ? (ssize_t) strlen(result) : + errno ? -errno : -EINVAL; + + /* send length or errno */ + write_all(pipes[1], (char *) &len, sizeof(len)); + if (result) + write_all(pipes[1], result, len); + _exit(0); + default: + break; + } + + close(pipes[1]); /* close unused end */ + pipes[1] = -1; + + /* read size or -errno */ + if (read_all(pipes[0], (char *) &len, sizeof(len)) != sizeof(len)) + goto done; + if (len < 0) { + errsv = -len; + goto done; + } + + result = malloc(len + 1); + if (!result) { + errsv = ENOMEM; + goto done; + } + /* read path */ + if (read_all(pipes[0], result, len) != len) { + errsv = errno; + goto done; + } + result[len] = '\0'; +done: + if (errsv) { + free(result); + result = NULL; + } + close(pipes[0]); + + /* We make a best effort to reap child */ + ignore_result( waitpid(pid, NULL, 0) ); + + errno = errsv; + return result; + +} + #ifdef TEST_PROGRAM_FILEUTILS int main(int argc, char *argv[]) { diff --git a/meson.build b/meson.build index 38adc8d07..061e287b4 100644 --- a/meson.build +++ b/meson.build @@ -3615,6 +3615,7 @@ endif exe = executable( 'test_canonicalize', 'lib/canonicalize.c', + 'lib/fileutils.c', c_args : ['-DTEST_PROGRAM_CANONICALIZE'], include_directories : dir_include, link_with : lib_common,