aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKarel Zak <kzak@redhat.com>2025-09-03 09:47:04 +0200
committerKarel Zak <kzak@redhat.com>2025-09-03 09:47:04 +0200
commit1c28f8f5f391c00a3eb5c78f6a50bc520121e53b (patch)
treea05a9057bf100593adb823a91b17cf496644fd10
parentc407a13fc1f5842881c967d0132035c7db442d4a (diff)
parent6585841587c4664ee0941611b682c28d3f36c7a2 (diff)
downloadutil-linux-1c28f8f5f391c00a3eb5c78f6a50bc520121e53b.tar.gz
Merge branch 'PR/libmount-canonicalize-cleanup' of https://github.com/karelzak/util-linux-work
* 'PR/libmount-canonicalize-cleanup' of https://github.com/karelzak/util-linux-work: lib/canonicalize: use ul_ prefix lib/canonicalize: introduce generic drop-permission caller meson: cleanup tests to use libcommon.la autotools: cleanup tests to use libcommon.la tests: add canonicalize test lib/canonicalize: refactor canonicalize_path() lib/canonicalize: rename to ul_absolute_path()
-rw-r--r--disk-utils/fdisk-list.c2
-rw-r--r--include/canonicalize.h12
-rw-r--r--include/fileutils.h5
-rw-r--r--lib/Makemodule.am47
-rw-r--r--lib/canonicalize.c157
-rw-r--r--lib/env.c2
-rw-r--r--lib/fileutils.c89
-rw-r--r--lib/loopdev.c4
-rw-r--r--libblkid/src/devname.c4
-rw-r--r--libblkid/src/evaluate.c4
-rw-r--r--libfdisk/src/utils.c2
-rw-r--r--libmount/src/cache.c2
-rw-r--r--libmount/src/tab.c2
-rw-r--r--libmount/src/utils.c4
-rw-r--r--login-utils/sulogin-consoles.c2
-rw-r--r--meson.build15
-rw-r--r--misc-utils/lsblk.c2
-rw-r--r--misc-utils/whereis.c2
-rw-r--r--sys-utils/losetup.c4
-rw-r--r--sys-utils/mount.c4
-rw-r--r--sys-utils/umount.c2
-rw-r--r--tests/commands.sh1
-rw-r--r--tests/expected/misc/canonicalize-non-root-user3
-rw-r--r--tests/expected/misc/canonicalize-root-user3
-rwxr-xr-xtests/ts/misc/canonicalize80
25 files changed, 291 insertions, 163 deletions
diff --git a/disk-utils/fdisk-list.c b/disk-utils/fdisk-list.c
index 24c4e04044..04ee2b431f 100644
--- a/disk-utils/fdisk-list.c
+++ b/disk-utils/fdisk-list.c
@@ -421,7 +421,7 @@ char *next_proc_partition(FILE **f)
if (!sysfs_devno_to_devpath(devno, buf, sizeof(buf)))
continue;
- cn = canonicalize_path(buf);
+ cn = ul_canonicalize_path(buf);
if (!cn)
continue;
diff --git a/include/canonicalize.h b/include/canonicalize.h
index ff6ef0dd6e..08cdb37ecc 100644
--- a/include/canonicalize.h
+++ b/include/canonicalize.h
@@ -15,14 +15,14 @@
#include "c.h" /* for PATH_MAX */
#include "strutils.h"
-extern char *canonicalize_path(const char *path);
-extern char *canonicalize_path_restricted(const char *path);
-extern char *canonicalize_dm_name(const char *ptname);
-extern char *__canonicalize_dm_name(const char *prefix, const char *ptname);
+extern char *ul_canonicalize_path(const char *path);
+extern char *ul_canonicalize_path_restricted(const char *path);
+extern char *ul_canonicalize_dm_name(const char *ptname);
+extern char *ul_canonicalize_dm_name_prefixed(const char *prefix, const char *ptname);
-extern char *absolute_path(const char *path);
+extern char *ul_absolute_path(const char *path);
-static inline int is_relative_path(const char *path)
+static inline int ul_is_relative_path(const char *path)
{
if (!path || *path == '/')
return 0;
diff --git a/include/fileutils.h b/include/fileutils.h
index 6fc93d0db8..672f1c9854 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 bf24b6bee8..f5cc846b58 100644
--- a/lib/Makemodule.am
+++ b/lib/Makemodule.am
@@ -140,6 +140,7 @@ test_mangle_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_MANGLE
test_strutils_SOURCES = lib/strutils.c
test_strutils_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_STRUTILS
+test_strutils_LDADD = $(LDADD) libcommon.la
test_c_strtod_SOURCES = lib/c_strtod.c
test_c_strtod_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM
@@ -158,7 +159,7 @@ if HAVE_CPU_SET_T
test_path_SOURCES += lib/cpuset.c
endif
test_path_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_PATH
-test_path_LDADD = $(LDADD)
+test_path_LDADD = $(LDADD) libcommon.la
endif
endif
@@ -174,26 +175,25 @@ endif
if LINUX
test_cpuset_SOURCES = lib/cpuset.c
test_cpuset_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_CPUSET
+test_cpuset_LDADD = $(LDADD) libcommon.la
-test_sysfs_SOURCES = lib/sysfs.c lib/path.c lib/fileutils.c lib/buffer.c lib/mbsalign.c
-if HAVE_CPU_SET_T
-test_sysfs_SOURCES += lib/cpuset.c
-endif
+test_sysfs_SOURCES = lib/sysfs.c
test_sysfs_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_SYSFS
-test_sysfs_LDADD = $(LDADD)
+test_sysfs_LDADD = $(LDADD) libcommon.la
-test_procfs_SOURCES = lib/procfs.c lib/path.c lib/fileutils.c lib/strutils.c
-if HAVE_CPU_SET_T
-test_procfs_SOURCES += lib/cpuset.c
-endif
+test_procfs_SOURCES = lib/procfs.c
test_procfs_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_PROCFS
-test_procfs_LDADD = $(LDADD)
+test_procfs_LDADD = $(LDADD) libcommon.la
test_pager_SOURCES = lib/pager.c
test_pager_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_PAGER
test_linux_version_SOURCES = lib/linux_version.c
test_linux_version_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_LINUXVERSION
+
+test_loopdev_SOURCES = lib/loopdev.c
+test_loopdev_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_LOOPDEV
+test_loopdev_LDADD = $(LDADD) libcommon.la
endif
test_fileeq_SOURCES = lib/fileeq.c
@@ -201,31 +201,26 @@ test_fileeq_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_FILEEQ
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
-test_timeutils_SOURCES = lib/timeutils.c lib/strutils.c
+test_timeutils_SOURCES = lib/timeutils.c
test_timeutils_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_TIMEUTILS
+test_timeutils_LDADD = $(LDADD) libcommon.la
test_pwdutils_SOURCES = lib/pwdutils.c
test_pwdutils_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM
-test_remove_env_SOURCES = lib/env.c lib/strv.c lib/strutils.c
-test_remove_env_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM
+test_remove_env_SOURCES = lib/env.c
+test_remove_env_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_ENV
+test_remove_env_LDADD = $(LDADD) libcommon.la
-test_buffer_SOURCES = lib/buffer.c lib/mbsalign.c
+test_buffer_SOURCES = lib/buffer.c
test_buffer_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_BUFFER
-
-if LINUX
-test_loopdev_SOURCES = lib/loopdev.c \
- lib/blkdev.c \
- lib/linux_version.c \
- $(test_sysfs_SOURCES) \
- $(test_canonicalize_SOURCES)
-test_loopdev_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_LOOPDEV
-endif
-
+test_buffer_LDADD = $(LDADD) libcommon.la
test_logindefs_SOURCES = lib/logindefs.c
test_logindefs_CPPFLAGS = -DTEST_PROGRAM $(AM_CPPFLAGS)
diff --git a/lib/canonicalize.c b/lib/canonicalize.c
index 6e70afe18a..854e611465 100644
--- a/lib/canonicalize.c
+++ b/lib/canonicalize.c
@@ -14,20 +14,23 @@
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
-#include <sys/wait.h>
#include "canonicalize.h"
#include "pathnames.h"
#include "all-io.h"
#include "strutils.h"
+#include "fileutils.h"
/*
* Converts private "dm-N" names to "/dev/mapper/<name>"
*
* Since 2.6.29 (patch 784aae735d9b0bba3f8b9faef4c8b30df3bf0128) kernel sysfs
* provides the real DM device names in /sys/block/<ptname>/dm/name
+ *
+ * The @prefix allows /sys to be mounted or stored outside the system root
+ * (/prefix/sys/...).
*/
-char *__canonicalize_dm_name(const char *prefix, const char *ptname)
+char *ul_canonicalize_dm_name_prefixed(const char *prefix, const char *ptname)
{
FILE *f;
size_t sz;
@@ -55,9 +58,9 @@ char *__canonicalize_dm_name(const char *prefix, const char *ptname)
return res;
}
-char *canonicalize_dm_name(const char *ptname)
+char *ul_canonicalize_dm_name(const char *ptname)
{
- return __canonicalize_dm_name(NULL, ptname);
+ return ul_canonicalize_dm_name_prefixed(NULL, ptname);
}
static int is_dm_devname(char *canonical, char **name)
@@ -83,12 +86,12 @@ static int is_dm_devname(char *canonical, char **name)
* relative path. If the path is no relative than returns NULL. The path does
* not have to exist.
*/
-char *absolute_path(const char *path)
+char *ul_absolute_path(const char *path)
{
char cwd[PATH_MAX], *res, *p;
size_t psz, csz;
- if (!is_relative_path(path)) {
+ if (!ul_is_relative_path(path)) {
errno = EINVAL;
return NULL;
}
@@ -118,132 +121,84 @@ char *absolute_path(const char *path)
return res;
}
-char *canonicalize_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,
+ void *data __attribute__((__unused__)))
{
char *canonical, *dmname;
- if (!path || !*path)
- return NULL;
+ *result = NULL;
+ if (!path || !*path) {
+ errno = EINVAL;
+ return -errno;
+ }
+
+ errno = 0;
canonical = realpath(path, NULL);
if (!canonical)
- return strdup(path);
+ return 1;
if (is_dm_devname(canonical, &dmname)) {
- char *dm = canonicalize_dm_name(dmname);
+ char *dm = ul_canonicalize_dm_name(dmname);
if (dm) {
free(canonical);
- return dm;
+ canonical = dm;
}
}
- return canonical;
+ if (canonical)
+ *result = canonical;
+ return 0;
}
-char *canonicalize_path_restricted(const char *path)
+/*
+ * Always returns a newly allocated string or NULL in case of an error. An
+ * unreachable path is not an error (!), and in this case, it just duplicates
+ * @path.
+ */
+char *ul_canonicalize_path(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 {
- char *dmname = NULL;
-
- canonical = realpath(path, NULL);
- if (canonical && is_dm_devname(canonical, &dmname)) {
- char *dm = canonicalize_dm_name(dmname);
- if (dm) {
- free(canonical);
- canonical = dm;
- }
- }
- }
- 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) );
+ if (do_canonicalize(path, &canonical, NULL) == 1)
+ return strdup(path);
- errno = errsv;
return canonical;
}
+/*
+ * Drop permissions (e.g., suid) and canonicalize the path. If the path is
+ * unreadable (for example, due to missing permissions), it returns NULL.
+ */
+char *ul_canonicalize_path_restricted(const char *path)
+{
+ return ul_restricted_path_oper(path, do_canonicalize, NULL);
+}
#ifdef TEST_PROGRAM_CANONICALIZE
int main(int argc, char **argv)
{
+ char *p;
+
if (argc < 2) {
fprintf(stderr, "usage: %s <device>\n", argv[0]);
exit(EXIT_FAILURE);
}
- fprintf(stdout, "orig: %s\n", argv[1]);
- fprintf(stdout, "real: %s\n", canonicalize_path(argv[1]));
+ fprintf(stdout, "orig: %s\n", argv[1]);
+
+ p = ul_canonicalize_path(argv[1]);
+ fprintf(stdout, "real: %s\n", p);
+ free(p);
+
+ p = ul_canonicalize_path_restricted(argv[1]);
+ fprintf(stdout, "real-restricted: %s\n", p);
+ free(p);
+
exit(EXIT_SUCCESS);
}
#endif
diff --git a/lib/env.c b/lib/env.c
index cdd8f8c587..039fad0dc5 100644
--- a/lib/env.c
+++ b/lib/env.c
@@ -280,7 +280,7 @@ return secure_getenv(arg);
#endif
}
-#ifdef TEST_PROGRAM
+#ifdef TEST_PROGRAM_ENV
int main(void)
{
char *const *bad;
diff --git a/lib/fileutils.c b/lib/fileutils.c
index b7acae4308..c4e1806177 100644
--- a/lib/fileutils.c
+++ b/lib/fileutils.c
@@ -12,6 +12,7 @@
#include <sys/time.h>
#include <sys/resource.h>
#include <string.h>
+#include <sys/wait.h>
#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/lib/loopdev.c b/lib/loopdev.c
index 8c5beaeb7e..8a51e5e5f4 100644
--- a/lib/loopdev.c
+++ b/lib/loopdev.c
@@ -692,7 +692,7 @@ int is_loopdev(const char *device)
snprintf(name, sizeof(name), _PATH_SYS_DEVBLOCK "/%d:%d",
major(st.st_rdev), minor(st.st_rdev));
- cn = canonicalize_path(name);
+ cn = ul_canonicalize_path(name);
if (cn)
p = stripoff_last_component(cn);
rc = p && ul_startswith(p, "loop");
@@ -1257,7 +1257,7 @@ int loopcxt_set_backing_file(struct loopdev_cxt *lc, const char *filename)
if (!lc)
return -EINVAL;
- lc->filename = canonicalize_path(filename);
+ lc->filename = ul_canonicalize_path(filename);
if (!lc->filename)
return -errno;
diff --git a/libblkid/src/devname.c b/libblkid/src/devname.c
index 0fb25325ef..b9717e23ab 100644
--- a/libblkid/src/devname.c
+++ b/libblkid/src/devname.c
@@ -66,7 +66,7 @@ blkid_dev blkid_get_dev(blkid_cache cache, const char *devname, int flags)
}
/* try canonicalize the name */
- if (!dev && (cn = canonicalize_path(devname))) {
+ if (!dev && (cn = ul_canonicalize_path(devname))) {
if (strcmp(cn, devname) != 0) {
DBG(DEVNAME, ul_debug("search canonical %s", cn));
list_for_each(p, &cache->bic_devs) {
@@ -200,7 +200,7 @@ static void probe_one(blkid_cache cache, const char *ptname,
* to standard /dev/mapper/<name>.
*/
if (!strncmp(ptname, "dm-", 3) && isdigit(ptname[3])) {
- devname = canonicalize_dm_name(ptname);
+ devname = ul_canonicalize_dm_name(ptname);
if (!devname)
blkid__scan_dir("/dev/mapper", devno, NULL, &devname);
if (devname)
diff --git a/libblkid/src/evaluate.c b/libblkid/src/evaluate.c
index 26e556c089..a4d04bca93 100644
--- a/libblkid/src/evaluate.c
+++ b/libblkid/src/evaluate.c
@@ -171,7 +171,7 @@ static char *evaluate_by_udev(const char *token, const char *value, int uevent)
if (!S_ISBLK(st.st_mode))
return NULL;
- path = canonicalize_path(dev);
+ path = ul_canonicalize_path(dev);
if (!path)
return NULL;
@@ -298,7 +298,7 @@ char *blkid_evaluate_spec(const char *spec, blkid_cache *cache)
if (v)
res = blkid_evaluate_tag(t, v, cache);
else
- res = canonicalize_path(spec);
+ res = ul_canonicalize_path(spec);
free(t);
free(v);
diff --git a/libfdisk/src/utils.c b/libfdisk/src/utils.c
index ef2663737d..a3397ab523 100644
--- a/libfdisk/src/utils.c
+++ b/libfdisk/src/utils.c
@@ -128,7 +128,7 @@ char *fdisk_partname(const char *dev, size_t partno)
/* It is impossible to predict /dev/dm-N partition names. */
if (strncmp(dev, "/dev/dm-", sizeof("/dev/dm-") - 1) == 0) {
- dev_mapped = canonicalize_dm_name (dev + 5);
+ dev_mapped = ul_canonicalize_dm_name (dev + 5);
if (dev_mapped)
dev = dev_mapped;
}
diff --git a/libmount/src/cache.c b/libmount/src/cache.c
index b7956346f8..2942654491 100644
--- a/libmount/src/cache.c
+++ b/libmount/src/cache.c
@@ -518,7 +518,7 @@ static char *canonicalize_path_and_cache(const char *path,
char *value;
DBG(CACHE, ul_debugobj(cache, "canonicalize path %s", path));
- p = canonicalize_path(path);
+ p = ul_canonicalize_path(path);
if (p && cache) {
value = p;
diff --git a/libmount/src/tab.c b/libmount/src/tab.c
index 4209d697a5..9a8033c256 100644
--- a/libmount/src/tab.c
+++ b/libmount/src/tab.c
@@ -1161,7 +1161,7 @@ struct libmnt_fs *mnt_table_find_target(struct libmnt_table *tb, const char *pat
}
/* try absolute path */
- if (is_relative_path(path) && (cn = absolute_path(path))) {
+ if (ul_is_relative_path(path) && (cn = ul_absolute_path(path))) {
DBG(TAB, ul_debugobj(tb, "lookup absolute TARGET: '%s'", cn));
mnt_reset_iter(&itr, direction);
while (mnt_table_next_fs(tb, &itr, &fs) == 0) {
diff --git a/libmount/src/utils.c b/libmount/src/utils.c
index 46050ff51f..7626b6f91e 100644
--- a/libmount/src/utils.c
+++ b/libmount/src/utils.c
@@ -1416,7 +1416,7 @@ static int test_mountpoint(struct libmnt_test *ts __attribute__((unused)),
if (argc != 2)
return -1;
- char *path = canonicalize_path(argv[1]),
+ char *path = ul_canonicalize_path(argv[1]),
*mnt = path ? mnt_get_mountpoint(path) : NULL;
printf("%s: %s\n", argv[1], mnt ? : "unknown");
@@ -1451,7 +1451,7 @@ static int test_chdir(struct libmnt_test *ts __attribute__((unused)),
return -1;
int rc;
- char *path = canonicalize_path(argv[1]),
+ char *path = ul_canonicalize_path(argv[1]),
*last = NULL;
if (!path)
diff --git a/login-utils/sulogin-consoles.c b/login-utils/sulogin-consoles.c
index cb9eb02bb8..93eeab111e 100644
--- a/login-utils/sulogin-consoles.c
+++ b/login-utils/sulogin-consoles.c
@@ -227,7 +227,7 @@ dev_t devattr(const char * const tty)
/*
* Search below /dev for the character device in `dev_t comparedev' variable.
* Note that realpath(3) is used here to avoid not existent devices due the
- * strdup(3) used in our canonicalize_path()!
+ * strdup(3) used in our ul_canonicalize_path()!
*/
static
#ifdef __GNUC__
diff --git a/meson.build b/meson.build
index e3038240a8..061e287b4c 100644
--- a/meson.build
+++ b/meson.build
@@ -3487,6 +3487,7 @@ exe = executable(
'lib/strutils.c',
c_args : ['-DTEST_PROGRAM_STRUTILS'],
include_directories : dir_include,
+ link_with : lib_common,
build_by_default: program_tests)
if not is_disabler(exe)
exes += exe
@@ -3530,8 +3531,6 @@ if conf.get('HAVE_OPENAT').to_string() == '1' \
exe = executable(
'test_path',
'lib/path.c',
- 'lib/fileutils.c',
- have_cpu_set_t ? 'lib/cpuset.c' : [],
c_args : ['-DTEST_PROGRAM_PATH'],
include_directories : dir_include,
link_with : lib_common,
@@ -3564,6 +3563,7 @@ if LINUX
'lib/cpuset.c',
c_args : ['-DTEST_PROGRAM_CPUSET'],
include_directories : dir_include,
+ link_with : lib_common,
build_by_default: program_tests)
if not is_disabler(exe)
exes += exe
@@ -3573,13 +3573,9 @@ endif
exe = executable(
'test_sysfs',
'lib/sysfs.c',
- 'lib/path.c',
- 'lib/buffer.c',
- 'lib/mbsalign.c',
- 'lib/fileutils.c',
- have_cpu_set_t ? 'lib/cpuset.c' : [],
c_args : ['-DTEST_PROGRAM_SYSFS'],
include_directories : dir_include,
+ link_with : lib_common,
build_by_default: program_tests)
if not is_disabler(exe)
exes += exe
@@ -3610,6 +3606,7 @@ exe = executable(
'lib/fileutils.c',
c_args : ['-DTEST_PROGRAM_FILEUTILS'],
include_directories : dir_include,
+ link_with : lib_common,
build_by_default: program_tests)
if not is_disabler(exe)
exes += exe
@@ -3618,8 +3615,10 @@ endif
exe = executable(
'test_canonicalize',
'lib/canonicalize.c',
+ 'lib/fileutils.c',
c_args : ['-DTEST_PROGRAM_CANONICALIZE'],
include_directories : dir_include,
+ link_with : lib_common,
build_by_default: program_tests)
if not is_disabler(exe)
exes += exe
@@ -3628,9 +3627,9 @@ endif
exe = executable(
'test_timeutils',
'lib/timeutils.c',
- 'lib/strutils.c',
c_args : ['-DTEST_PROGRAM_TIMEUTILS'],
include_directories : dir_include,
+ link_with : lib_common,
build_by_default: program_tests)
if not is_disabler(exe)
exes += exe
diff --git a/misc-utils/lsblk.c b/misc-utils/lsblk.c
index 4690083cde..39a8ca83f4 100644
--- a/misc-utils/lsblk.c
+++ b/misc-utils/lsblk.c
@@ -401,7 +401,7 @@ static char *get_device_path(struct lsblk_device *dev)
assert(dev->name);
if (is_dm(dev->name))
- return __canonicalize_dm_name(lsblk->sysroot, dev->name);
+ return ul_canonicalize_dm_name_prefixed(lsblk->sysroot, dev->name);
snprintf(path, sizeof(path), "/dev/%s", dev->name);
sysfs_devname_sys_to_dev(path);
diff --git a/misc-utils/whereis.c b/misc-utils/whereis.c
index b575e57aee..8e500c0098 100644
--- a/misc-utils/whereis.c
+++ b/misc-utils/whereis.c
@@ -251,7 +251,7 @@ static void dirlist_add_dir(struct wh_dirlist **ls0, int type, const char *dir)
ls->st_ino = st.st_ino;
ls->st_dev = st.st_dev;
ls->type = type;
- ls->path = canonicalize_path(dir);
+ ls->path = ul_canonicalize_path(dir);
if (!*ls0)
*ls0 = ls; /* first in the list */
diff --git a/sys-utils/losetup.c b/sys-utils/losetup.c
index 1a66173865..f35bd79fc4 100644
--- a/sys-utils/losetup.c
+++ b/sys-utils/losetup.c
@@ -209,7 +209,7 @@ static int show_all_loops(struct loopdev_cxt *lc, const char *file,
used = loopcxt_is_used(lc, st, bf, offset, 0, flags);
if (!used && !cn_file) {
- bf = cn_file = canonicalize_path(file);
+ bf = cn_file = ul_canonicalize_path(file);
used = loopcxt_is_used(lc, st, bf, offset, 0, flags);
}
if (!used)
@@ -430,7 +430,7 @@ static int show_table(struct loopdev_cxt *lc,
used = loopcxt_is_used(lc, st, bf, offset, 0, flags);
if (!used && !cn_file) {
- bf = cn_file = canonicalize_path(file);
+ bf = cn_file = ul_canonicalize_path(file);
used = loopcxt_is_used(lc, st, bf, offset, 0, flags);
}
if (!used)
diff --git a/sys-utils/mount.c b/sys-utils/mount.c
index d6dd2805af..2450228d04 100644
--- a/sys-utils/mount.c
+++ b/sys-utils/mount.c
@@ -496,7 +496,7 @@ static int sanitize_paths(struct libmnt_context *cxt)
p = mnt_fs_get_target(fs);
if (p) {
- char *np = canonicalize_path_restricted(p);
+ char *np = ul_canonicalize_path_restricted(p);
if (!np)
return -EPERM;
mnt_fs_set_target(fs, np);
@@ -505,7 +505,7 @@ static int sanitize_paths(struct libmnt_context *cxt)
p = mnt_fs_get_srcpath(fs);
if (p) {
- char *np = canonicalize_path_restricted(p);
+ char *np = ul_canonicalize_path_restricted(p);
if (!np)
return -EPERM;
mnt_fs_set_source(fs, np);
diff --git a/sys-utils/umount.c b/sys-utils/umount.c
index c115f44a0e..18824e09f2 100644
--- a/sys-utils/umount.c
+++ b/sys-utils/umount.c
@@ -446,7 +446,7 @@ static char *sanitize_path(const char *path)
if (!path)
return NULL;
- p = canonicalize_path_restricted(path);
+ p = ul_canonicalize_path_restricted(path);
if (!p)
err(MNT_EX_USAGE, "%s", path);
diff --git a/tests/commands.sh b/tests/commands.sh
index fe989a541a..00be966554 100644
--- a/tests/commands.sh
+++ b/tests/commands.sh
@@ -4,6 +4,7 @@ TS_TESTUSER=${TS_TESTUSER:-"nobody"}
# helpers
TS_HELPER_BOILERPLATE="${ts_helpersdir}test_boilerplate"
TS_HELPER_BYTESWAP="${ts_helpersdir}test_byteswap"
+TS_HELPER_CANONICALIZE="${ts_helpersdir}test_canonicalize"
TS_HELPER_COLORS="${ts_helpersdir}test_colors"
TS_HELPER_CPUSET="${ts_helpersdir}test_cpuset"
TS_HELPER_CAP="${ts_helpersdir}test_cap"
diff --git a/tests/expected/misc/canonicalize-non-root-user b/tests/expected/misc/canonicalize-non-root-user
new file mode 100644
index 0000000000..204e83b67a
--- /dev/null
+++ b/tests/expected/misc/canonicalize-non-root-user
@@ -0,0 +1,3 @@
+orig: /root-sym/foo
+real: /root/foo
+real-restricted: (null)
diff --git a/tests/expected/misc/canonicalize-root-user b/tests/expected/misc/canonicalize-root-user
new file mode 100644
index 0000000000..87213a7039
--- /dev/null
+++ b/tests/expected/misc/canonicalize-root-user
@@ -0,0 +1,3 @@
+orig: /aaa/bbb/ccc-sym/ddd
+real: /ccc/ddd
+real-restricted: /ccc/ddd
diff --git a/tests/ts/misc/canonicalize b/tests/ts/misc/canonicalize
new file mode 100755
index 0000000000..2e111659c9
--- /dev/null
+++ b/tests/ts/misc/canonicalize
@@ -0,0 +1,80 @@
+#!/bin/bash
+
+# Copyright (C) 2025 Karel Zak <kzak@redhat.com>
+#
+# This file is part of util-linux.
+#
+# This file is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+TS_TOPDIR="${0%/*}/../.."
+TS_DESC="canonicalize"
+
+. "$TS_TOPDIR"/functions.sh
+ts_init "$*"
+
+ts_skip_nonroot
+
+ts_check_prog "getent"
+ts_check_test_command "$TS_CMD_MOUNT"
+ts_check_test_command "$TS_CMD_UMOUNT"
+ts_check_test_command "$TS_CMD_SETPRIV"
+ts_check_test_command "$TS_HELPER_CANONICALIZE"
+
+grep -q 'nodev[[:space:]]*tmpfs' /proc/filesystems || \
+ ts_skip_subtest "tmpfs unsupported"
+
+mkdir -p $TS_MOUNTPOINT &> /dev/null
+$TS_CMD_MOUNT -t tmpfs tmpfs $TS_MOUNTPOINT >> $TS_OUTPUT 2>> $TS_ERRLOG
+[ $? -eq 0 ] || ts_skip "tmpfs mount failed"
+
+# reuse the same TS_MOUNTPOINT in all subtests
+BASE=$TS_MOUNTPOINT
+
+# setup suid binary
+TESTPROG=${BASE}/$(basename $TS_HELPER_CANONICALIZE)
+cp $TS_HELPER_CANONICALIZE $TESTPROG
+chown root:root $TESTPROG
+chmod +x,u+s $TESTPROG
+
+if [ ! -u "$TESTPROG" ] || [ ! -x "$TESTPROG" ]; then
+ $TS_CMD_UMOUNT $BASE
+ ts_skip "cannot setup suid test"
+fi
+
+
+ts_init_subtest root-user
+mkdir -p ${BASE}/aaa/bbb
+mkdir -p ${BASE}/ccc/ddd
+ln -s ${BASE}/ccc ${BASE}/aaa/bbb/ccc-sym
+$TESTPROG ${BASE}/aaa/bbb/ccc-sym/ddd | sed "s:${BASE}::g" >> $TS_OUTPUT 2>> $TS_ERRLOG
+ts_finalize_subtest
+
+
+ts_init_subtest non-root-user
+uid="nobody"
+if id $uid &>/dev/null; then
+ mkdir -p ${BASE}/root/foo
+ chown -R root:root ${BASE}/root
+ chmod -R o-rwx ${BASE}/root
+ ln -s ${BASE}/root ${BASE}/root-sym
+
+ gid=$(getent passwd "$uid" | cut -d: -f4)
+
+ $TS_CMD_SETPRIV --reuid="$uid" --regid="$gid" --clear-groups \
+ --inh-caps=-all --reset-env \
+ -- $TESTPROG ${BASE}/root-sym/foo \
+ | sed "s:${BASE}::g" >> $TS_OUTPUT 2>> $TS_ERRLOG
+
+ ts_finalize_subtest
+else
+ ts_skip_subtest "nobody user is missing"
+fi
+
+
+# cleanup
+$TS_CMD_UMOUNT $BASE &> /dev/null
+
+ts_finalize