From: Karel Zak Date: Mon, 13 Jul 2026 14:18:29 +0000 (+0200) Subject: libmount: use shared dl-utils for SELinux dlopen X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b93cc39a8ed6d9ee537a28b3f011df27ed7d6feb;p=thirdparty%2Futil-linux.git libmount: use shared dl-utils for SELinux dlopen 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 --- diff --git a/include/Makemodule.am b/include/Makemodule.am index a0627a56a..1c82006db 100644 --- a/include/Makemodule.am +++ b/include/Makemodule.am @@ -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 index 000000000..cb4d7df75 --- /dev/null +++ b/include/dl-selinux.h @@ -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 +#include +#include +#include + +#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 index 000000000..6a2aecf20 --- /dev/null +++ b/lib/dl-selinux.c @@ -0,0 +1,83 @@ +/* + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + */ +#include + +#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 */ diff --git a/lib/meson.build b/lib/meson.build index 88b3fd639..31dc9a933 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -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') diff --git a/lib/selinux-utils.c b/lib/selinux-utils.c index c0df89120..b1d77c492 100644 --- a/lib/selinux-utils.c +++ b/lib/selinux-utils.c @@ -4,16 +4,16 @@ * * Written by Karel Zak [January 2021] */ -#include -#include -#include #include #include #include #include +#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, @@ -21,16 +21,17 @@ */ 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 */ diff --git a/libmount/meson.build b/libmount/meson.build index 884a28cdc..f3cc2e1c2 100644 --- a/libmount/meson.build +++ b/libmount/meson.build @@ -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 ] diff --git a/libmount/src/Makemodule.am b/libmount/src/Makemodule.am index c854a0bc0..fc6f7f04a 100644 --- a/libmount/src/Makemodule.am +++ b/libmount/src/Makemodule.am @@ -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) diff --git a/libmount/src/hook_selinux.c b/libmount/src/hook_selinux.c index 0dfd47359..ba58eba54 100644 --- a/libmount/src/hook_selinux.c +++ b/libmount/src/hook_selinux.c @@ -13,10 +13,9 @@ * Please, see the comment in libmount/src/hooks.c to understand how hooks work. */ #ifdef HAVE_LIBSELINUX -#include -#include #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;