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.

Use sd-dlopen to 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.
---
 lib/selinux-dlopen.c           | 618 +++++++++++++++++++++++++++++++++
 m4/selinux-selinux-h.m4        |  82 +++--
 modules/selinux-h              |  13 +-
 tests/test-selinux-selinux-h.c |   3 +
 4 files changed, 686 insertions(+), 30 deletions(-)
 create mode 100644 lib/selinux-dlopen.c

diff --git a/lib/selinux-dlopen.c b/lib/selinux-dlopen.c
new file mode 100644
index 0000000000..8a3d4b8100
--- /dev/null
+++ b/lib/selinux-dlopen.c
@@ -0,0 +1,618 @@
+/* Load libselinux on demand.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation; either version 2.1 of the
+   License, or (at your option) any later version.
+
+   This file 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 Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser 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/context.h>
+#include <selinux/label.h>
+#include <selinux/selinux.h>
+#include <stddef.h>
+#include <sys/stat.h>
+
+#include "glthread/once.h"
+#include "sd-dlopen.h"
+
+SD_ELF_NOTE_DLOPEN ("selinux",
+                    "Support for SELinux",
+                    SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED,
+                    LIBSELINUX_SONAME);
+
+/* 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 **);
+
+/* Pointers to the libselinux functions resolved by load_libselinux.  */
+struct selinux_operations
+{
+  __typeof__ (&is_selinux_enabled) is_selinux_enabled;
+  __typeof__ (&getcon) getcon;
+  __typeof__ (&getcon_raw) getcon_raw;
+  __typeof__ (&freecon) freecon;
+  __typeof__ (&getfscreatecon_raw) getfscreatecon_raw;
+  __typeof__ (&setfscreatecon) setfscreatecon;
+  __typeof__ (&setfscreatecon_raw) setfscreatecon_raw;
+  __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;
+  __typeof__ (&lsetfilecon_raw) lsetfilecon_raw;
+  __typeof__ (&fsetfilecon) fsetfilecon;
+  __typeof__ (&fsetfilecon_raw) fsetfilecon_raw;
+  __typeof__ (&setexeccon) setexeccon;
+  __typeof__ (&security_check_context) security_check_context;
+  __typeof__ (&security_compute_create) security_compute_create;
+  __typeof__ (&security_compute_create_raw) security_compute_create_raw;
+  __typeof__ (&string_to_security_class) string_to_security_class;
+#if HAVE_DECL_MODE_TO_SECURITY_CLASS
+  __typeof__ (&mode_to_security_class) mode_to_security_class;
+#endif
+#if HAVE_SELINUX_LABEL_H
+  __typeof__ (&selabel_open) selabel_open;
+  __typeof__ (&selabel_lookup) selabel_lookup;
+  __typeof__ (&selabel_lookup_raw) selabel_lookup_raw;
+  __typeof__ (&selabel_close) selabel_close;
+  __typeof__ (&selinux_file_context_cmp) selinux_file_context_cmp;
+#endif
+#if HAVE_SELINUX_CONTEXT_H
+  __typeof__ (&context_new) context_new;
+  __typeof__ (&context_str) context_str;
+  __typeof__ (&context_free) context_free;
+  __typeof__ (&context_type_get) context_type_get;
+  __typeof__ (&context_type_set) context_type_set;
+  __typeof__ (&context_user_get) context_user_get;
+  __typeof__ (&context_user_set) context_user_set;
+  __typeof__ (&context_role_get) context_role_get;
+  __typeof__ (&context_role_set) context_role_set;
+  __typeof__ (&context_range_get) context_range_get;
+  __typeof__ (&context_range_set) context_range_set;
+#endif
+};
+
+static struct selinux_operations selinux_ops;
+static int load_status;
+gl_once_define (static, load_once)
+
+/* Return a function pointer in HANDLE for SYMBOL.  */
+static void *
+symbol_address (void *handle, char const *symbol)
+{
+  void *address = dlsym (handle, symbol);
+  if (!address)
+    errno = ENOSYS;
+  return address;
+}
+
+/* Load libselinux and resolve all required symbols.  */
+static void
+do_load_libselinux (void)
+{
+  void *handle;
+  int flags = RTLD_LAZY | RTLD_LOCAL;
+
+#ifdef RTLD_NODELETE
+  flags |= RTLD_NODELETE;
+#endif
+  handle = dlopen (LIBSELINUX_SONAME, flags);
+  if (!handle)
+    {
+      load_status = -1;
+      return;
+    }
+
+  selinux_ops.is_selinux_enabled =
+    symbol_address (handle, "is_selinux_enabled");
+  if (!selinux_ops.is_selinux_enabled)
+    goto fail;
+  selinux_ops.getcon = symbol_address (handle, "getcon");
+  if (!selinux_ops.getcon)
+    goto fail;
+  selinux_ops.getcon_raw = symbol_address (handle, "getcon_raw");
+  if (!selinux_ops.getcon_raw)
+    goto fail;
+  selinux_ops.freecon = symbol_address (handle, "freecon");
+  if (!selinux_ops.freecon)
+    goto fail;
+  selinux_ops.getfscreatecon_raw =
+    symbol_address (handle, "getfscreatecon_raw");
+  if (!selinux_ops.getfscreatecon_raw)
+    goto fail;
+  selinux_ops.setfscreatecon = symbol_address (handle, "setfscreatecon");
+  if (!selinux_ops.setfscreatecon)
+    goto fail;
+  selinux_ops.setfscreatecon_raw =
+    symbol_address (handle, "setfscreatecon_raw");
+  if (!selinux_ops.setfscreatecon_raw)
+    goto fail;
+  selinux_ops.getfilecon = symbol_address (handle, "getfilecon");
+  if (!selinux_ops.getfilecon)
+    goto fail;
+  selinux_ops.getfilecon_raw = symbol_address (handle, "getfilecon_raw");
+  if (!selinux_ops.getfilecon_raw)
+    goto fail;
+  selinux_ops.lgetfilecon = symbol_address (handle, "lgetfilecon");
+  if (!selinux_ops.lgetfilecon)
+    goto fail;
+  selinux_ops.lgetfilecon_raw = symbol_address (handle, "lgetfilecon_raw");
+  if (!selinux_ops.lgetfilecon_raw)
+    goto fail;
+  selinux_ops.fgetfilecon = symbol_address (handle, "fgetfilecon");
+  if (!selinux_ops.fgetfilecon)
+    goto fail;
+  selinux_ops.fgetfilecon_raw = symbol_address (handle, "fgetfilecon_raw");
+  if (!selinux_ops.fgetfilecon_raw)
+    goto fail;
+  selinux_ops.setfilecon = symbol_address (handle, "setfilecon");
+  if (!selinux_ops.setfilecon)
+    goto fail;
+  selinux_ops.lsetfilecon = symbol_address (handle, "lsetfilecon");
+  if (!selinux_ops.lsetfilecon)
+    goto fail;
+  selinux_ops.lsetfilecon_raw = symbol_address (handle, "lsetfilecon_raw");
+  if (!selinux_ops.lsetfilecon_raw)
+    goto fail;
+  selinux_ops.fsetfilecon = symbol_address (handle, "fsetfilecon");
+  if (!selinux_ops.fsetfilecon)
+    goto fail;
+  selinux_ops.fsetfilecon_raw = symbol_address (handle, "fsetfilecon_raw");
+  if (!selinux_ops.fsetfilecon_raw)
+    goto fail;
+  selinux_ops.setexeccon = symbol_address (handle, "setexeccon");
+  if (!selinux_ops.setexeccon)
+    goto fail;
+  selinux_ops.security_check_context =
+    symbol_address (handle, "security_check_context");
+  if (!selinux_ops.security_check_context)
+    goto fail;
+  selinux_ops.security_compute_create =
+    symbol_address (handle, "security_compute_create");
+  if (!selinux_ops.security_compute_create)
+    goto fail;
+  selinux_ops.security_compute_create_raw =
+    symbol_address (handle, "security_compute_create_raw");
+  if (!selinux_ops.security_compute_create_raw)
+    goto fail;
+  selinux_ops.string_to_security_class =
+    symbol_address (handle, "string_to_security_class");
+  if (!selinux_ops.string_to_security_class)
+    goto fail;
+#if HAVE_DECL_MODE_TO_SECURITY_CLASS
+  selinux_ops.mode_to_security_class =
+    dlsym (handle, "mode_to_security_class");
+#endif
+#if HAVE_SELINUX_LABEL_H
+  selinux_ops.selabel_open = symbol_address (handle, "selabel_open");
+  if (!selinux_ops.selabel_open)
+    goto fail;
+  selinux_ops.selabel_lookup = symbol_address (handle, "selabel_lookup");
+  if (!selinux_ops.selabel_lookup)
+    goto fail;
+  selinux_ops.selabel_lookup_raw =
+    symbol_address (handle, "selabel_lookup_raw");
+  if (!selinux_ops.selabel_lookup_raw)
+    goto fail;
+  selinux_ops.selabel_close = symbol_address (handle, "selabel_close");
+  if (!selinux_ops.selabel_close)
+    goto fail;
+  selinux_ops.selinux_file_context_cmp =
+    symbol_address (handle, "selinux_file_context_cmp");
+  if (!selinux_ops.selinux_file_context_cmp)
+    goto fail;
+#endif
+#if HAVE_SELINUX_CONTEXT_H
+  selinux_ops.context_new = symbol_address (handle, "context_new");
+  if (!selinux_ops.context_new)
+    goto fail;
+  selinux_ops.context_str = symbol_address (handle, "context_str");
+  if (!selinux_ops.context_str)
+    goto fail;
+  selinux_ops.context_free = symbol_address (handle, "context_free");
+  if (!selinux_ops.context_free)
+    goto fail;
+  selinux_ops.context_type_get = symbol_address (handle, "context_type_get");
+  if (!selinux_ops.context_type_get)
+    goto fail;
+  selinux_ops.context_type_set = symbol_address (handle, "context_type_set");
+  if (!selinux_ops.context_type_set)
+    goto fail;
+  selinux_ops.context_user_get = symbol_address (handle, "context_user_get");
+  if (!selinux_ops.context_user_get)
+    goto fail;
+  selinux_ops.context_user_set = symbol_address (handle, "context_user_set");
+  if (!selinux_ops.context_user_set)
+    goto fail;
+  selinux_ops.context_role_get = symbol_address (handle, "context_role_get");
+  if (!selinux_ops.context_role_get)
+    goto fail;
+  selinux_ops.context_role_set = symbol_address (handle, "context_role_set");
+  if (!selinux_ops.context_role_set)
+    goto fail;
+  selinux_ops.context_range_get =
+    symbol_address (handle, "context_range_get");
+  if (!selinux_ops.context_range_get)
+    goto fail;
+  selinux_ops.context_range_set =
+    symbol_address (handle, "context_range_set");
+  if (!selinux_ops.context_range_set)
+    goto fail;
+#endif
+
+  return;
+
+fail:
+  dlclose (handle);
+  load_status = -1;
+}
+
+/* Initialize the libselinux operations once.  Return 0 on success.  Return -1
+   with errno set to ENOSYS on failure.  */
+static int
+load_libselinux (void)
+{
+  gl_once (load_once, do_load_libselinux);
+  if (load_status < 0)
+    {
+      errno = ENOSYS;
+      return -1;
+    }
+  return 0;
+}
+
+#define LOAD_OR_RETURN(Failure) \
+  do \
+    { \
+      if (load_libselinux () < 0) \
+        return Failure; \
+    } \
+  while (false)
+
+extern int
+is_selinux_enabled (void)
+{
+  return load_libselinux () < 0 ? 0 : selinux_ops.is_selinux_enabled ();
+}
+
+extern int
+getcon (char **con)
+{
+  LOAD_OR_RETURN (-1);
+  return selinux_ops.getcon (con);
+}
+
+extern int
+getcon_raw (char **con)
+{
+  LOAD_OR_RETURN (-1);
+  return selinux_ops.getcon_raw (con);
+}
+
+extern void
+freecon (char *con)
+{
+  if (con && load_libselinux () == 0)
+    selinux_ops.freecon (con);
+}
+
+extern int
+getfscreatecon_raw (char **con)
+{
+  LOAD_OR_RETURN (-1);
+  return selinux_ops.getfscreatecon_raw (con);
+}
+
+extern int
+setfscreatecon (char const *con)
+{
+  LOAD_OR_RETURN (-1);
+  return selinux_ops.setfscreatecon (con);
+}
+
+extern int
+setfscreatecon_raw (char const *con)
+{
+  LOAD_OR_RETURN (-1);
+  return selinux_ops.setfscreatecon_raw (con);
+}
+
+extern int
+getfilecon (char const *file, char **con)
+{
+  LOAD_OR_RETURN (-1);
+  return selinux_ops.getfilecon (file, con);
+}
+
+extern int
+getfilecon_raw (char const *file, char **con)
+{
+  LOAD_OR_RETURN (-1);
+  return selinux_ops.getfilecon_raw (file, con);
+}
+
+extern int
+lgetfilecon (char const *file, char **con)
+{
+  LOAD_OR_RETURN (-1);
+  return selinux_ops.lgetfilecon (file, con);
+}
+
+extern int
+lgetfilecon_raw (char const *file, char **con)
+{
+  LOAD_OR_RETURN (-1);
+  return selinux_ops.lgetfilecon_raw (file, con);
+}
+
+extern int
+fgetfilecon (int fd, char **con)
+{
+  LOAD_OR_RETURN (-1);
+  return selinux_ops.fgetfilecon (fd, con);
+}
+
+extern int
+fgetfilecon_raw (int fd, char **con)
+{
+  LOAD_OR_RETURN (-1);
+  return selinux_ops.fgetfilecon_raw (fd, con);
+}
+
+extern int
+setfilecon (char const *file, char const *con)
+{
+  LOAD_OR_RETURN (-1);
+  return selinux_ops.setfilecon (file, con);
+}
+
+extern int
+lsetfilecon (char const *file, char const *con)
+{
+  LOAD_OR_RETURN (-1);
+  return selinux_ops.lsetfilecon (file, con);
+}
+
+extern int
+lsetfilecon_raw (char const *file, char const *con)
+{
+  LOAD_OR_RETURN (-1);
+  return selinux_ops.lsetfilecon_raw (file, con);
+}
+
+extern int
+fsetfilecon (int fd, char const *con)
+{
+  LOAD_OR_RETURN (-1);
+  return selinux_ops.fsetfilecon (fd, con);
+}
+
+extern int
+fsetfilecon_raw (int fd, char const *con)
+{
+  LOAD_OR_RETURN (-1);
+  return selinux_ops.fsetfilecon_raw (fd, con);
+}
+
+extern int
+setexeccon (char const *con)
+{
+  LOAD_OR_RETURN (-1);
+  return selinux_ops.setexeccon (con);
+}
+
+extern int
+security_check_context (char const *con)
+{
+  LOAD_OR_RETURN (-1);
+  return selinux_ops.security_check_context (con);
+}
+
+extern int
+security_compute_create (char const *scon, char const *tcon,
+                         security_class_t tclass, char **newcon)
+{
+  LOAD_OR_RETURN (-1);
+  return selinux_ops.security_compute_create (scon, tcon, tclass, newcon);
+}
+
+extern int
+security_compute_create_raw (char const *scon, char const *tcon,
+                             security_class_t tclass, char **newcon)
+{
+  LOAD_OR_RETURN (-1);
+  return selinux_ops.security_compute_create_raw (scon, tcon, tclass, newcon);
+}
+
+extern security_class_t
+string_to_security_class (char const *name)
+{
+  LOAD_OR_RETURN (0);
+  return selinux_ops.string_to_security_class (name);
+}
+
+#if HAVE_DECL_MODE_TO_SECURITY_CLASS
+static security_class_t
+mode_to_security_class_fallback (mode_t mode)
+{
+  char const *name;
+  if (S_ISREG (mode))
+    name = "file";
+  else if (S_ISDIR (mode))
+    name = "dir";
+  else if (S_ISCHR (mode))
+    name = "chr_file";
+  else if (S_ISBLK (mode))
+    name = "blk_file";
+  else if (S_ISFIFO (mode))
+    name = "fifo_file";
+  else if (S_ISLNK (mode))
+    name = "lnk_file";
+  else if (S_ISSOCK (mode))
+    name = "sock_file";
+  else
+    {
+      errno = EINVAL;
+      return 0;
+    }
+
+  return selinux_ops.string_to_security_class (name);
+}
+
+extern security_class_t
+mode_to_security_class (mode_t mode)
+{
+  LOAD_OR_RETURN (0);
+  return (selinux_ops.mode_to_security_class
+          ? selinux_ops.mode_to_security_class (mode)
+          : mode_to_security_class_fallback (mode));
+}
+#endif
+
+#if HAVE_SELINUX_LABEL_H
+extern struct selabel_handle *
+selabel_open (unsigned int backend, struct selinux_opt const *opts,
+              unsigned int nopts)
+{
+  LOAD_OR_RETURN (NULL);
+  return selinux_ops.selabel_open (backend, opts, nopts);
+}
+
+extern int
+selabel_lookup (struct selabel_handle *handle, char **con,
+                char const *key, int type)
+{
+  LOAD_OR_RETURN (-1);
+  return selinux_ops.selabel_lookup (handle, con, key, type);
+}
+
+extern int
+selabel_lookup_raw (struct selabel_handle *handle, char **con,
+                    char const *key, int type)
+{
+  LOAD_OR_RETURN (-1);
+  return selinux_ops.selabel_lookup_raw (handle, con, key, type);
+}
+
+extern void
+selabel_close (struct selabel_handle *handle)
+{
+  if (handle && load_libselinux () == 0)
+    selinux_ops.selabel_close (handle);
+}
+
+extern int
+selinux_file_context_cmp (char const *a, char const *b)
+{
+  LOAD_OR_RETURN (-1);
+  return selinux_ops.selinux_file_context_cmp (a, b);
+}
+#endif
+
+#if HAVE_SELINUX_CONTEXT_H
+extern context_t
+context_new (char const *con)
+{
+  LOAD_OR_RETURN (NULL);
+  return selinux_ops.context_new (con);
+}
+
+extern char const *
+context_str (context_t con)
+{
+  LOAD_OR_RETURN (NULL);
+  return selinux_ops.context_str (con);
+}
+
+extern void
+context_free (context_t con)
+{
+  if (con && load_libselinux () == 0)
+    selinux_ops.context_free (con);
+}
+
+extern char const *
+context_type_get (context_t con)
+{
+  LOAD_OR_RETURN (NULL);
+  return selinux_ops.context_type_get (con);
+}
+
+extern int
+context_type_set (context_t con, char const *value)
+{
+  LOAD_OR_RETURN (-1);
+  return selinux_ops.context_type_set (con, value);
+}
+
+extern char const *
+context_user_get (context_t con)
+{
+  LOAD_OR_RETURN (NULL);
+  return selinux_ops.context_user_get (con);
+}
+
+extern int
+context_user_set (context_t con, char const *value)
+{
+  LOAD_OR_RETURN (-1);
+  return selinux_ops.context_user_set (con, value);
+}
+
+extern char const *
+context_role_get (context_t con)
+{
+  LOAD_OR_RETURN (NULL);
+  return selinux_ops.context_role_get (con);
+}
+
+extern int
+context_role_set (context_t con, char const *value)
+{
+  LOAD_OR_RETURN (-1);
+  return selinux_ops.context_role_set (con, value);
+}
+
+extern char const *
+context_range_get (context_t con)
+{
+  LOAD_OR_RETURN (NULL);
+  return selinux_ops.context_range_get (con);
+}
+
+extern int
+context_range_set (context_t con, char const *value)
+{
+  LOAD_OR_RETURN (-1);
+  return selinux_ops.context_range_set (con, value);
+}
+#endif
+
+#undef LOAD_OR_RETURN
diff --git a/m4/selinux-selinux-h.m4 b/m4/selinux-selinux-h.m4
index c964ced072..b199b5895c 100644
--- a/m4/selinux-selinux-h.m4
+++ b/m4/selinux-selinux-h.m4
@@ -1,5 +1,5 @@
 # selinux-selinux-h.m4
-# serial 12   -*- Autoconf -*-
+# serial 14   -*- Autoconf -*-
 dnl Copyright (C) 2006-2007, 2009-2026 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -45,18 +45,18 @@ AC_DEFUN([gl_CHECK_HEADER_SELINUX_SELINUX_H],
 
     if test $ac_cv_header_selinux_selinux_h = yes; then
       USE_SELINUX_SELINUX_H=1
+      AC_CHECK_DECLS([mode_to_security_class],,,
+        [[#include <selinux/selinux.h>]])
     else
       USE_SELINUX_SELINUX_H=0
     fi
 
-    case "$ac_cv_search_getfilecon_raw:$ac_cv_header_selinux_selinux_h" in
-      no:*) # already warned
-        ;;
-      *:no)
-        AC_MSG_WARN([libselinux was found but selinux/selinux.h is missing.])
+    if test $ac_cv_header_selinux_selinux_h != yes; then
+      if test "$with_selinux" = yes; then
+        AC_MSG_WARN([selinux/selinux.h is missing.])
         AC_MSG_WARN([AC_PACKAGE_NAME will be compiled without SELinux 
support.])
-        ;;
-    esac
+      fi
+    fi
   else
     # Do as if <selinux/selinux.h> does not exist, even if
     # AC_CHECK_HEADERS_ONCE has already determined that it exists.
@@ -70,7 +70,7 @@ AC_DEFUN([gl_CHECK_HEADER_SELINUX_SELINUX_H],
 AC_DEFUN([gl_LIBSELINUX],
 [
   AC_REQUIRE([AC_CANONICAL_HOST])
-  AC_REQUIRE([AC_CANONICAL_BUILD])
+  AC_REQUIRE([gl_LIBDL])
 
   AC_ARG_WITH([selinux],
     AS_HELP_STRING([[--without-selinux]], [do not use SELinux, even on systems 
with SELinux]),
@@ -78,25 +78,53 @@ AC_DEFUN([gl_LIBSELINUX],
 
   LIB_SELINUX=
   if test "$with_selinux" != no; then
-    gl_saved_LIBS=$LIBS
-    dnl On Android, in Termux, prefer libandroid-selinux.so, because it
-    dnl defines many more API functions than /system/lib/libselinux.so.
-    AC_SEARCH_LIBS([getfilecon_raw], [android-selinux selinux],
-                   [test "$ac_cv_search_getfilecon_raw" = "none required" ||
-                    LIB_SELINUX=$ac_cv_search_getfilecon_raw])
-    LIBS=$gl_saved_LIBS
-  fi
-  AC_SUBST([LIB_SELINUX])
-
-  # Warn if SELinux is found but libselinux is absent;
-  if test "$ac_cv_search_getfilecon_raw" = no; then
-    if test "$host" = "$build" \
-       && { test -d /sys/fs/selinux || test -d /selinux; }; then
-      AC_MSG_WARN([This system supports SELinux but libselinux is missing.])
-      AC_MSG_WARN([AC_PACKAGE_NAME will be compiled without SELinux support.])
-    fi
-    if test "$with_selinux" = maybe; then
+    if test "$HAVE_DLOPEN" = 0; then
+      if test "$with_selinux" = yes; then
+        AC_MSG_ERROR([SELinux support requires dlopen and dlsym])
+      fi
       with_selinux=no
+    else
+      AC_CACHE_CHECK([for libselinux shared library name],
+        [gl_cv_lib_selinux_soname],
+        [gl_cv_lib_selinux_soname=no
+         gl_saved_LIBS=$LIBS
+         dnl On Android, in Termux, prefer libandroid-selinux because it
+         dnl defines many more API functions than the system libselinux.
+         AS_CASE([$host_os],
+           [*android*], [gl_selinux_libs='android-selinux selinux'],
+           [*], [gl_selinux_libs=selinux])
+         for gl_selinux_lib in $gl_selinux_libs; do
+           LIBS="$gl_saved_LIBS -l$gl_selinux_lib"
+           AC_LINK_IFELSE(
+             [AC_LANG_PROGRAM(
+                [[extern int getfilecon_raw (char const *, char **);]],
+                [[char *con;
+                  return getfilecon_raw (".", &con);]])],
+             [AS_CASE([$gl_selinux_lib],
+                [android-selinux],
+                  [gl_DLOPEN_SONAME([gl_selinux_soname],
+                     [libandroid-selinux\.so[[.0-9]]*])],
+                [*],
+                  [gl_DLOPEN_SONAME([gl_selinux_soname],
+                     [libselinux\.so[[.0-9]]*])])
+              AS_CASE([$gl_selinux_soname],
+                [libandroid-selinux.so* | libselinux.so*],
+                  [gl_cv_lib_selinux_soname=$gl_selinux_soname])])
+           AS_CASE([$gl_cv_lib_selinux_soname],
+             [libandroid-selinux.so* | libselinux.so*], [break])
+         done
+         LIBS=$gl_saved_LIBS])
+      AS_CASE([$gl_cv_lib_selinux_soname],
+        [libandroid-selinux.so* | libselinux.so*],
+          [AC_DEFINE_UNQUOTED([LIBSELINUX_SONAME],
+             ["$gl_cv_lib_selinux_soname"],
+             [libselinux shared object name])
+           LIB_SELINUX=$LIBDL],
+        [*],
+          [AS_IF([test "$with_selinux" = yes],
+             [AC_MSG_ERROR([libselinux not found or unusable])])
+           with_selinux=no])
     fi
   fi
+  AC_SUBST([LIB_SELINUX])
 ])
diff --git a/modules/selinux-h b/modules/selinux-h
index 6db14db546..529ebda97d 100644
--- a/modules/selinux-h
+++ b/modules/selinux-h
@@ -1,8 +1,9 @@
 Description:
-SELinux-related headers for systems that lack them.
+SELinux-related headers and a dynamically loaded implementation.
 
 Files:
 lib/getfilecon.c
+lib/selinux-dlopen.c
 lib/se-context.in.h
 lib/se-label.in.h
 lib/se-selinux.in.h
@@ -14,11 +15,16 @@ m4/selinux-label-h.m4
 m4/selinux-selinux-h.m4
 
 Depends-on:
+bool
 errno-h
-sys_types-h
 extern-inline
 gen-header
+once
+sd-dlopen
+stddef-h
 streq
+sys_stat-h
+sys_types-h
 
 configure.ac:
 gl_HEADERS_SELINUX_SELINUX_H
@@ -72,7 +78,7 @@ MOSTLYCLEANFILES += selinux/label.h selinux/label.h-t
 MOSTLYCLEANDIRS += selinux
 
 if GL_COND_OBJ_GETFILECON
-lib_SOURCES += getfilecon.c
+lib_SOURCES += getfilecon.c selinux-dlopen.c
 endif
 lib_SOURCES += se-context.in.h se-label.in.h se-selinux.in.h \
   se-context.c se-label.c se-selinux.c
@@ -84,6 +90,7 @@ Include:
 
 Link:
 $(LIB_SELINUX)
+$(LIBTHREAD)
 
 License:
 LGPLv2+
diff --git a/tests/test-selinux-selinux-h.c b/tests/test-selinux-selinux-h.c
index 57156726df..52d731f169 100644
--- a/tests/test-selinux-selinux-h.c
+++ b/tests/test-selinux-selinux-h.c
@@ -28,6 +28,9 @@ main ()
   char *con;
   if (getcon (&con) == 0)
     freecon (con);
+#if HAVE_DECL_MODE_TO_SECURITY_CLASS
+  (void) mode_to_security_class (0);
+#endif
 
   return 0;
 }
-- 
2.47.3


Reply via email to