]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
libmount: use shared dl-utils for SELinux dlopen
authorKarel Zak <kzak@redhat.com>
Mon, 13 Jul 2026 14:18:29 +0000 (16:18 +0200)
committerKarel Zak <kzak@redhat.com>
Tue, 14 Jul 2026 12:39:33 +0000 (14:39 +0200)
Add lib/dl-selinux.c and include/dl-selinux.h as a SELinux dlopen
wrapper using the shared dl-utils infrastructure. This replaces the
direct libselinux linking with runtime dlopen(), allowing libmount to
gracefully handle the case when libselinux is not installed.

The hook_selinux.c and lib/selinux-utils.c now use selinux_call()
macro to call SELinux functions through the dlopen'd function pointer
table.

Signed-off-by: Karel Zak <kzak@redhat.com>
include/Makemodule.am
include/dl-selinux.h [new file with mode: 0644]
lib/dl-selinux.c [new file with mode: 0644]
lib/meson.build
lib/selinux-utils.c
libmount/meson.build
libmount/src/Makemodule.am
libmount/src/hook_selinux.c

index a0627a56a41c5948eaf8ea707b0f9a6d01cc01d5..1c82006db969b76b5258234c27fb844a0637e177 100644 (file)
@@ -23,6 +23,7 @@ dist_noinst_HEADERS += \
        include/debug.h \
        include/default_shell.h \
        include/dl-cryptsetup.h \
+       include/dl-selinux.h \
        include/dl-utils.h \
        include/encode.h \
        include/env.h \
diff --git a/include/dl-selinux.h b/include/dl-selinux.h
new file mode 100644 (file)
index 0000000..cb4d7df
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * No copyright is claimed.  This code is in the public domain; do with
+ * it what you wish.
+ */
+#ifndef UTIL_LINUX_DL_SELINUX_H
+#define UTIL_LINUX_DL_SELINUX_H
+
+#ifdef HAVE_LIBSELINUX
+
+#include <selinux/selinux.h>
+#include <selinux/context.h>
+#include <selinux/label.h>
+#include <selinux/get_context_list.h>
+
+#include "dl-utils.h"
+
+/* Pointers to libselinux functions (initialized by dlsym()) */
+struct ul_selinux_opers {
+       int (*is_selinux_enabled)(void);
+
+       int (*getfilecon)(const char *, char **);
+       int (*getfilecon_raw)(const char *, char **);
+       int (*fgetfilecon)(int, char **);
+       int (*lgetfilecon)(const char *, char **);
+
+       int (*getcon)(char **);
+       int (*getprevcon)(char **);
+       int (*getpidcon)(pid_t, char **);
+
+       int (*getseuserbyname)(const char *, char **, char **);
+
+       int (*setfilecon)(const char *, const char *);
+       int (*fsetfilecon)(int, const char *);
+       int (*setfscreatecon)(const char *);
+       int (*setexeccon)(const char *);
+
+       void (*freecon)(char *);
+
+       int (*selinux_check_access)(const char *, const char *,
+                       const char *, const char *, void *);
+       int (*security_compute_relabel)(const char *, const char *,
+                       security_class_t, char **);
+#ifdef HAVE_SECURITY_GET_INITIAL_CONTEXT
+       int (*security_get_initial_context)(const char *, char **);
+#endif
+       int (*selinux_file_context_cmp)(const char *, const char *);
+       int (*selinux_trans_to_raw_context)(const char *, char **);
+       security_class_t (*string_to_security_class)(const char *);
+
+       int (*get_default_context_with_level)(const char *, const char *,
+                       context_t, char **);
+
+       struct selabel_handle *(*selabel_open)(unsigned int,
+                       const struct selinux_opt *, unsigned);
+       int (*selabel_lookup)(struct selabel_handle *, char **,
+                       const char *, int);
+       void (*selabel_close)(struct selabel_handle *);
+
+       context_t (*context_new)(const char *);
+       int (*context_type_set)(context_t, const char *);
+       const char *(*context_str)(context_t);
+       void (*context_free)(context_t);
+};
+
+typedef struct ul_selinux_opers ul_selinux_opers;
+
+extern struct ul_selinux_opers ul_selinux;
+
+extern int ul_dlopen_libselinux(void);
+
+#define selinux_call(_func)    (ul_selinux._func)
+
+#endif /* HAVE_LIBSELINUX */
+#endif /* UTIL_LINUX_DL_SELINUX_H */
diff --git a/lib/dl-selinux.c b/lib/dl-selinux.c
new file mode 100644 (file)
index 0000000..6a2aecf
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * No copyright is claimed.  This code is in the public domain; do with
+ * it what you wish.
+ */
+#include <dlfcn.h>
+
+#include "c.h"
+#include "dl-selinux.h"
+
+#ifdef HAVE_LIBSELINUX
+
+UL_ELF_NOTE_DLOPEN("selinux",
+                   "Support for SELinux",
+                   UL_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED,
+                   "libselinux.so.1");
+
+struct ul_selinux_opers ul_selinux;
+
+static const struct ul_dlsym ul_selinux_symbols[] =
+{
+       UL_DLSYM( ul_selinux_opers, is_selinux_enabled ),
+
+       UL_DLSYM( ul_selinux_opers, getfilecon ),
+       UL_DLSYM( ul_selinux_opers, getfilecon_raw ),
+       UL_DLSYM( ul_selinux_opers, fgetfilecon ),
+       UL_DLSYM( ul_selinux_opers, lgetfilecon ),
+
+       UL_DLSYM( ul_selinux_opers, getcon ),
+       UL_DLSYM( ul_selinux_opers, getprevcon ),
+       UL_DLSYM( ul_selinux_opers, getpidcon ),
+
+       UL_DLSYM( ul_selinux_opers, getseuserbyname ),
+
+       UL_DLSYM( ul_selinux_opers, setfilecon ),
+       UL_DLSYM( ul_selinux_opers, fsetfilecon ),
+       UL_DLSYM( ul_selinux_opers, setfscreatecon ),
+       UL_DLSYM( ul_selinux_opers, setexeccon ),
+
+       UL_DLSYM( ul_selinux_opers, freecon ),
+
+       UL_DLSYM( ul_selinux_opers, selinux_check_access ),
+       UL_DLSYM( ul_selinux_opers, security_compute_relabel ),
+#ifdef HAVE_SECURITY_GET_INITIAL_CONTEXT
+       UL_DLSYM( ul_selinux_opers, security_get_initial_context ),
+#endif
+       UL_DLSYM( ul_selinux_opers, selinux_file_context_cmp ),
+       UL_DLSYM( ul_selinux_opers, selinux_trans_to_raw_context ),
+       UL_DLSYM( ul_selinux_opers, string_to_security_class ),
+
+       UL_DLSYM( ul_selinux_opers, get_default_context_with_level ),
+
+       UL_DLSYM( ul_selinux_opers, selabel_open ),
+       UL_DLSYM( ul_selinux_opers, selabel_lookup ),
+       UL_DLSYM( ul_selinux_opers, selabel_close ),
+
+       UL_DLSYM( ul_selinux_opers, context_new ),
+       UL_DLSYM( ul_selinux_opers, context_type_set ),
+       UL_DLSYM( ul_selinux_opers, context_str ),
+       UL_DLSYM( ul_selinux_opers, context_free ),
+};
+
+int ul_dlopen_libselinux(void)
+{
+       /* 0 = not tried, 1 = loaded, -1 = failed */
+       static int status = 0;
+       static void *dl = NULL;
+       int flags = RTLD_LAZY | RTLD_LOCAL;
+
+       if (status)
+               return status > 0 ? 0 : -ENOSYS;
+
+#ifdef RTLD_NODELETE
+       flags |= RTLD_NODELETE;
+#endif
+       status = ul_dlopen_symbols("libselinux.so.1", flags,
+                                  ul_selinux_symbols,
+                                  ARRAY_SIZE(ul_selinux_symbols),
+                                  &ul_selinux, &dl) == 0 ? 1 : -1;
+
+       return status > 0 ? 0 : -ENOSYS;
+}
+
+#endif /* HAVE_LIBSELINUX */
index 88b3fd639526e63e0de76a7ed5618372b47280e4..31dc9a93335991585977a1f8ac06e06987aa25ad 100644 (file)
@@ -65,6 +65,7 @@ lib_common_shells = static_library('common_shells',
 
 dl_utils_c = files('dl-utils.c')
 dl_cryptsetup_c = files('dl-cryptsetup.c')
+dl_selinux_c = files('dl-selinux.c')
 
 selinux_utils_c = files('selinux-utils.c')
 
index c0df8912046a754d7dd17cf46247f9cf8ca6643e..b1d77c4927e1a469d6b6f734827284b92e0b94d9 100644 (file)
@@ -4,16 +4,16 @@
  *
  * Written by Karel Zak <kzak@redhat.com> [January 2021]
  */
-#include <selinux/context.h>
-#include <selinux/selinux.h>
-#include <selinux/label.h>
 #include <stdio.h>
 #include <string.h>
 #include <sys/types.h>
 #include <errno.h>
 
+#include "dl-selinux.h"
 #include "selinux-utils.h"
 
+#ifdef HAVE_LIBSELINUX
+
 /* set the SELinux security context used for _creating_ a new file system object
  *
  * returns 0 on success,
  */
 int ul_setfscreatecon_from_file(char *orig_file)
 {
-       if (is_selinux_enabled() > 0) {
+       if (ul_dlopen_libselinux() == 0
+           && selinux_call(is_selinux_enabled)() > 0) {
                char *scontext = NULL;
 
-               if (getfilecon(orig_file, &scontext) < 0)
+               if (selinux_call(getfilecon)(orig_file, &scontext) < 0)
                        return -1;
-               if (setfscreatecon(scontext) < 0) {
-                       freecon(scontext);
+               if (selinux_call(setfscreatecon)(scontext) < 0) {
+                       selinux_call(freecon)(scontext);
                        return -1;
                }
-               freecon(scontext);
+               selinux_call(freecon)(scontext);
        }
        return 0;
 }
@@ -47,14 +48,17 @@ int ul_selinux_has_access(const char *classstr, const char *perm, char **user_cx
        if (user_cxt)
                *user_cxt = NULL;
 
-       if (getprevcon(&user) != 0)
+       if (ul_dlopen_libselinux() != 0)
+               return 0;
+
+       if (selinux_call(getprevcon)(&user) != 0)
                return 0;
 
-       rc = selinux_check_access(user, user, classstr, perm, NULL);
+       rc = selinux_call(selinux_check_access)(user, user, classstr, perm, NULL);
        if (rc != 0 && user_cxt)
                *user_cxt = user;
        else
-               freecon(user);
+               selinux_call(freecon)(user);
 
        return rc == 0 ? 1 : 0;
 }
@@ -72,14 +76,18 @@ int ul_selinux_get_default_context(const char *path, int st_mode, char **cxt)
 
        *cxt = NULL;
 
-       hnd = selabel_open(SELABEL_CTX_FILE, options, SELABEL_NOPT);
+       if (ul_dlopen_libselinux() != 0)
+               return -ENOSYS;
+
+       hnd = selinux_call(selabel_open)(SELABEL_CTX_FILE, options, SELABEL_NOPT);
        if (!hnd)
                return -errno;
 
-       if (selabel_lookup(hnd, cxt, path, st_mode) != 0)
-               rc = -errno
-                       ;
-       selabel_close(hnd);
+       if (selinux_call(selabel_lookup)(hnd, cxt, path, st_mode) != 0)
+               rc = -errno;
+       selinux_call(selabel_close)(hnd);
 
        return rc;
 }
+
+#endif /* HAVE_LIBSELINUX */
index 884a28cdc0f095e14dfa01659123f596016b2e6d..f3cc2e1c2e42fe038c63d75c53beb8842e8654de 100644 (file)
@@ -71,8 +71,15 @@ if enable_btrfs
   lib_mount_sources += 'src/btrfs.c'
 endif
 
+if lib_selinux.found()
+  lib_mount_sources += [dl_utils_c, dl_selinux_c, selinux_utils_c]
+endif
+
 if lib_cryptsetup.found()
-  lib_mount_sources += [dl_utils_c, dl_cryptsetup_c]
+  if not lib_selinux.found()
+    lib_mount_sources += dl_utils_c
+  endif
+  lib_mount_sources += dl_cryptsetup_c
 endif
 
 libmount_sym = 'src/libmount.sym'
@@ -83,6 +90,9 @@ if build_libmount and not have_dirfd and not have_ddfd
 endif
 
 lib__mount_compile_deps = [blkid_dep]
+if lib_selinux.found()
+  lib__mount_compile_deps += lib_selinux.partial_dependency(compile_args : true, includes : true)
+endif
 if lib_cryptsetup.found()
   lib__mount_compile_deps += lib_cryptsetup.partial_dependency(compile_args : true, includes : true)
 endif
@@ -103,7 +113,6 @@ lib_mount_static = static_library(
 mount_static_dep = declare_dependency(link_with: lib_mount_static, include_directories: '.')
 
 lib__mount_deps = [
-  lib_selinux,
   lib_dl,
   realtime_libs
 ]
index c854a0bc059c679bef2ace545327ba940154acb2..fc6f7f04a581b628685938926505ebe17ff36631 100644 (file)
@@ -58,15 +58,28 @@ endif # LINUX
 libmount_la_LIBADD = \
        libcommon.la \
        libblkid.la \
-       $(SELINUX_LIBS) \
        $(REALTIME_LIBS)
 
-if HAVE_CRYPTSETUP
+if HAVE_SELINUX
 libmount_la_SOURCES += \
        lib/dl-utils.c \
        include/dl-utils.h \
+       lib/dl-selinux.c \
+       include/dl-selinux.h \
+       lib/selinux-utils.c \
+       include/selinux-utils.h
+libmount_la_LIBADD += -ldl
+endif
+
+if HAVE_CRYPTSETUP
+libmount_la_SOURCES += \
        lib/dl-cryptsetup.c \
        include/dl-cryptsetup.h
+if !HAVE_SELINUX
+libmount_la_SOURCES += \
+       lib/dl-utils.c \
+       include/dl-utils.h
+endif
 libmount_la_LIBADD += -ldl
 endif
 
@@ -80,7 +93,8 @@ libmount_la_CFLAGS = \
        -I$(ul_libmount_incdir) \
        -I$(top_srcdir)/libmount/src \
        $(SOLIB_CFLAGS) \
-       $(CRYPTSETUP_CFLAGS)
+       $(CRYPTSETUP_CFLAGS) \
+       $(SELINUX_CFLAGS)
 
 EXTRA_libmount_la_DEPENDENCIES = \
        libmount/src/libmount.sym
@@ -117,12 +131,14 @@ libmount_tests_ldflags = -static
 libmount_tests_ldadd   = libmount.la libblkid.la $(LDADD) $(REALTIME_LIBS)
 
 if HAVE_SELINUX
-libmount_tests_ldadd += $(SELINUX_LIBS)
+libmount_tests_ldadd += -ldl
 endif
 
 if HAVE_CRYPTSETUP
+if !HAVE_SELINUX
 libmount_tests_ldadd += -ldl
 endif
+endif
 
 test_mount_cache_SOURCES = libmount/src/cache.c
 test_mount_cache_CFLAGS = $(libmount_tests_cflags)
index 0dfd473591e38ffa9fcf1420b3af525109beb181..ba58eba548558de8f4c89c27cc054baf15bf4b46 100644 (file)
  * Please, see the comment in libmount/src/hooks.c to understand how hooks work.
  */
 #ifdef HAVE_LIBSELINUX
-#include <selinux/selinux.h>
-#include <selinux/context.h>
 
 #include "mountP.h"
+#include "dl-selinux.h"
 #include "fileutils.h"
 #include "linux_version.h"
 
@@ -80,7 +79,7 @@ static int hook_selinux_target(
                return 0;
 
 
-       rc = getfilecon_raw(tgt, &raw);
+       rc = selinux_call(getfilecon_raw)(tgt, &raw);
        if (rc <= 0 || !raw) {
                rc = errno ? -errno : -EINVAL;
                DBG_OBJ(HOOK, hs, ul_debug(" SELinux fix @target failed [rc=%d]", rc));
@@ -91,7 +90,7 @@ static int hook_selinux_target(
        if (!rc)
                rc = mnt_opt_set_quoted_value(opt, raw);
        if (raw)
-               freecon(raw);
+               selinux_call(freecon)(raw);
 
        return rc != 0 ? -MNT_ERR_MOUNTOPT : 0;
 }
@@ -110,10 +109,13 @@ static int hook_prepare_options(
        if (!ol)
                return -EINVAL;
 
-       if (!is_selinux_enabled())
-               /* Always remove SELinux garbage if SELinux disabled */
+       if (ul_dlopen_libselinux() != 0) {
+               DBG_OBJ(HOOK, hs, ul_debug("libselinux not available via dlopen"));
                se_rem = 1;
-       else if (mnt_optlist_is_remount(ol))
+       } else if (!selinux_call(is_selinux_enabled)()) {
+               DBG_OBJ(HOOK, hs, ul_debug("SELinux disabled"));
+               se_rem = 1;
+       } else if (mnt_optlist_is_remount(ol))
                /*
                 * Linux kernel < 2.6.39 does not support remount operation
                 * with any selinux specific mount options.
@@ -167,7 +169,7 @@ static int hook_prepare_options(
                                                       hook_selinux_target);
                                        continue;
                                } else {
-                                       rc = selinux_trans_to_raw_context(val, &raw);
+                                       rc = selinux_call(selinux_trans_to_raw_context)(val, &raw);
                                        if (rc == -1 || !raw)
                                                rc = -EINVAL;
                                }
@@ -177,7 +179,7 @@ static int hook_prepare_options(
                                        rc = mnt_opt_set_quoted_value(opt, raw);
                                }
                                if (raw)
-                                       freecon(raw);
+                                       selinux_call(freecon)(raw);
 
                                /* temporary for broken fsconfig() syscall */
                                cxt->has_selinux_opt = 1;