From: Luca Boccassi <[email protected]>

libselinux is built in most distros, but it is seldomly needed at
runtime (e.g.: it is not used by default on Debian/Ubuntu). In order to
allow building small images from the same packages, switch it to
runtime optional by using dlopen(), and gracefully fallback if the
library is not found.

If libsystemd headers are available at build time stamp the ELF
binaries using the ELF dlopen metadata format as defined by:
https://uapi-group.org/specifications/specs/elf_dlopen_metadata/
so that packaging tools for rpm/deb can automatically derive
dependencies for it.
---
 build-aux/gnulib_extra_libs.am |   2 +-
 configure.ac                   |  24 ++++
 find/Makefile.am               |   3 +
 find/selinux-dlopen.c          | 204 +++++++++++++++++++++++++++++++++
 4 files changed, 232 insertions(+), 1 deletion(-)
 create mode 100644 find/selinux-dlopen.c

diff --git a/build-aux/gnulib_extra_libs.am b/build-aux/gnulib_extra_libs.am
index 285601f8..a5117c67 100644
--- a/build-aux/gnulib_extra_libs.am
+++ b/build-aux/gnulib_extra_libs.am
@@ -14,4 +14,4 @@
 ## along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 # Recommended by the gnulib importer
-findutils_gnulib_extra_libs = $(CLOCK_TIME_LIB) $(EUIDACCESS_LIBGEN) 
$(GETHOSTNAME_LIB) $(GETLOCALENAME_L_LIB) $(HARD_LOCALE_LIB) $(LIBPTHREAD) 
$(LIBSOCKET) $(LIBTHREAD) $(LIB_SELINUX) $(LIBC32CONV) $(LIBINTL) 
$(LTLIBUNISTRING) $(MBRTOWC_LIB) $(MODF_LIBM) $(PTHREAD_ONCE_LIB) 
$(SETLOCALE_NULL_LIB) $(TRUNC_LIBM)
+findutils_gnulib_extra_libs = $(CLOCK_TIME_LIB) $(EUIDACCESS_LIBGEN) 
$(GETHOSTNAME_LIB) $(GETLOCALENAME_L_LIB) $(HARD_LOCALE_LIB) $(LIBPTHREAD) 
$(LIBSOCKET) $(LIBTHREAD) $(LIB_SELINUX_DLOPEN) $(LIBC32CONV) $(LIBINTL) 
$(LTLIBUNISTRING) $(MBRTOWC_LIB) $(MODF_LIBM) $(PTHREAD_ONCE_LIB) 
$(SETLOCALE_NULL_LIB) $(TRUNC_LIBM)
diff --git a/configure.ac b/configure.ac
index a14c2384..c70d38da 100644
--- a/configure.ac
+++ b/configure.ac
@@ -103,6 +103,30 @@ AC_SYS_LARGEFILE
 
 gl_INIT
 
+dnl Find the library for dynamic loading of libselinux.
+findutils_saved_LIBS=$LIBS
+AC_SEARCH_LIBS([dlopen], [dl])
+AS_CASE([$ac_cv_search_dlopen],
+  [no | 'none required'], [LIB_DL=],
+  [*], [LIB_DL=$ac_cv_search_dlopen])
+LIBS=$findutils_saved_LIBS
+AC_SUBST([LIB_DL])
+
+AS_IF([test "$USE_SELINUX_SELINUX_H" = 1],
+  [AS_IF([test "$ac_cv_search_dlopen" = no],
+     [AC_MSG_ERROR([SELinux support requires dlopen])])
+   AS_CASE([$ac_cv_search_getfilecon_raw],
+     [-landroid-selinux], [LIBSELINUX_SONAME=libandroid-selinux.so],
+     [*], [LIBSELINUX_SONAME=libselinux.so.1])
+   AC_DEFINE_UNQUOTED([LIBSELINUX_SONAME], ["$LIBSELINUX_SONAME"],
+     [libselinux shared object name])
+   LIB_SELINUX_DLOPEN=$LIB_DL],
+  [LIB_SELINUX_DLOPEN=])
+AC_SUBST([LIB_SELINUX_DLOPEN])
+
+dnl Used to advertise optional dlopen dependencies in ELF metadata.
+AC_CHECK_HEADERS([systemd/sd-dlopen.h])
+
 AC_ARG_ENABLE(compiler-warnings,
   AS_HELP_STRING(--enable-compiler-warnings,Enable many compiler warnings),
   [
diff --git a/find/Makefile.am b/find/Makefile.am
index ef95ba85..92c272c4 100644
--- a/find/Makefile.am
+++ b/find/Makefile.am
@@ -22,6 +22,9 @@ localedir = $(datadir)/locale
 
 noinst_LIBRARIES = libfindtools.a
 libfindtools_a_SOURCES = finddata.c fstype.c parser.c pred.c exec.c tree.c 
util.c sharefile.c print.c
+if GL_COND_OBJ_GETFILECON
+libfindtools_a_SOURCES += selinux-dlopen.c
+endif
 
 # We only build the version of find with fts (called "find"),
 # i.e., no longer the one called "oldfind".
diff --git a/find/selinux-dlopen.c b/find/selinux-dlopen.c
new file mode 100644
index 00000000..2e24150d
--- /dev/null
+++ b/find/selinux-dlopen.c
@@ -0,0 +1,204 @@
+/* Load libselinux on demand.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+
+   This program 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 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#include <dlfcn.h>
+#include <errno.h>
+#include <selinux/selinux.h>
+
+#if HAVE_SYSTEMD_SD_DLOPEN_H
+# include <systemd/sd-dlopen.h>
+
+SD_ELF_NOTE_DLOPEN ("selinux",
+                    "Support for SELinux",
+                    SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED,
+                    LIBSELINUX_SONAME);
+#endif
+
+/* Gnulib redirects these names to wrappers that validate the returned
+   contexts.  This file provides the underlying libselinux entry points.  */
+#undef getfilecon
+#undef getfilecon_raw
+#undef lgetfilecon
+#undef lgetfilecon_raw
+#undef fgetfilecon
+#undef fgetfilecon_raw
+
+extern int getfilecon (char const *, char **);
+extern int getfilecon_raw (char const *, char **);
+extern int lgetfilecon (char const *, char **);
+extern int lgetfilecon_raw (char const *, char **);
+extern int fgetfilecon (int, char **);
+extern int fgetfilecon_raw (int, char **);
+
+struct selinux_operations
+{
+  __typeof__ (&is_selinux_enabled) is_selinux_enabled;
+  __typeof__ (&freecon) freecon;
+  __typeof__ (&getfilecon) getfilecon;
+  __typeof__ (&getfilecon_raw) getfilecon_raw;
+  __typeof__ (&lgetfilecon) lgetfilecon;
+  __typeof__ (&lgetfilecon_raw) lgetfilecon_raw;
+  __typeof__ (&fgetfilecon) fgetfilecon;
+  __typeof__ (&fgetfilecon_raw) fgetfilecon_raw;
+  __typeof__ (&setfilecon) setfilecon;
+  __typeof__ (&lsetfilecon) lsetfilecon;
+};
+
+static struct selinux_operations selinux_ops;
+
+static void *
+symbol_address (void *handle, char const *symbol)
+{
+  void *address = dlsym (handle, symbol);
+  if (!address)
+    errno = ENOSYS;
+  return address;
+}
+
+static int
+load_libselinux (void)
+{
+  static int status;
+  static void *handle;
+  int flags = RTLD_LAZY | RTLD_LOCAL;
+
+  if (status)
+    {
+      if (status < 0)
+        errno = ENOSYS;
+      return status < 0 ? -1 : 0;
+    }
+
+#ifdef RTLD_NODELETE
+  flags |= RTLD_NODELETE;
+#endif
+  handle = dlopen (LIBSELINUX_SONAME, flags);
+  if (!handle)
+    goto fail;
+
+#define LOAD_SYMBOL(Name) \
+  do \
+    { \
+      selinux_ops.Name = symbol_address (handle, #Name); \
+      if (!selinux_ops.Name) \
+        goto fail; \
+    } \
+  while (false)
+
+  LOAD_SYMBOL (is_selinux_enabled);
+  LOAD_SYMBOL (freecon);
+  LOAD_SYMBOL (getfilecon);
+  LOAD_SYMBOL (getfilecon_raw);
+  LOAD_SYMBOL (lgetfilecon);
+  LOAD_SYMBOL (lgetfilecon_raw);
+  LOAD_SYMBOL (fgetfilecon);
+  LOAD_SYMBOL (fgetfilecon_raw);
+  LOAD_SYMBOL (setfilecon);
+  LOAD_SYMBOL (lsetfilecon);
+#undef LOAD_SYMBOL
+
+  status = 1;
+  return 0;
+
+fail:
+  if (handle)
+    dlclose (handle);
+  handle = NULL;
+  status = -1;
+  errno = ENOSYS;
+  return -1;
+}
+
+#define LOAD_OR_RETURN \
+  do \
+    { \
+      if (load_libselinux () < 0) \
+        return -1; \
+    } \
+  while (false)
+
+extern int
+is_selinux_enabled (void)
+{
+  return load_libselinux () < 0 ? 0 : selinux_ops.is_selinux_enabled ();
+}
+
+extern void
+freecon (char *con)
+{
+  if (con && load_libselinux () == 0)
+    selinux_ops.freecon (con);
+}
+
+extern int
+getfilecon (char const *file, char **con)
+{
+  LOAD_OR_RETURN;
+  return selinux_ops.getfilecon (file, con);
+}
+
+extern int
+getfilecon_raw (char const *file, char **con)
+{
+  LOAD_OR_RETURN;
+  return selinux_ops.getfilecon_raw (file, con);
+}
+
+extern int
+lgetfilecon (char const *file, char **con)
+{
+  LOAD_OR_RETURN;
+  return selinux_ops.lgetfilecon (file, con);
+}
+
+extern int
+lgetfilecon_raw (char const *file, char **con)
+{
+  LOAD_OR_RETURN;
+  return selinux_ops.lgetfilecon_raw (file, con);
+}
+
+extern int
+fgetfilecon (int fd, char **con)
+{
+  LOAD_OR_RETURN;
+  return selinux_ops.fgetfilecon (fd, con);
+}
+
+extern int
+fgetfilecon_raw (int fd, char **con)
+{
+  LOAD_OR_RETURN;
+  return selinux_ops.fgetfilecon_raw (fd, con);
+}
+
+extern int
+setfilecon (char const *file, char const *con)
+{
+  LOAD_OR_RETURN;
+  return selinux_ops.setfilecon (file, con);
+}
+
+extern int
+lsetfilecon (char const *file, char const *con)
+{
+  LOAD_OR_RETURN;
+  return selinux_ops.lsetfilecon (file, con);
+}
+
+#undef LOAD_OR_RETURN
-- 
2.47.3


Reply via email to