]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
pam: make libpam a dlopen() based dependency
authorLennart Poettering <lennart@poettering.net>
Mon, 8 Sep 2025 14:04:23 +0000 (16:04 +0200)
committerLennart Poettering <lennart@poettering.net>
Sat, 20 Sep 2025 06:41:20 +0000 (08:41 +0200)
meson.build
src/core/exec-invoke.c
src/core/meson.build
src/home/pam_systemd_home.c
src/login/pam_systemd.c
src/login/pam_systemd_loadkey.c
src/shared/meson.build
src/shared/pam-util.c
src/shared/pam-util.h
src/test/test-dlopen-so.c

index 68b2df7b4959f34d077db8a6849e5b231cff13e3..c5a2953200f92bc51ec85be4fa98b5b8db06f237 100644 (file)
@@ -1220,6 +1220,7 @@ if not libpam_misc.found()
         libpam_misc = cc.find_library('pam_misc', required : feature)
 endif
 conf.set10('HAVE_PAM', libpam.found() and libpam_misc.found())
+libpam_cflags = libpam.partial_dependency(includes: true, compile_args: true)
 
 libmicrohttpd = dependency('libmicrohttpd',
                            version : '>= 0.9.33',
@@ -2294,6 +2295,19 @@ pam_template = {
                 libshared_static,
         ],
         'dependencies' : [
+                # Note: our PAM modules also call dlopen_libpam() and use
+                # symbols aquired through that, hence the explicit dep here is
+                # strictly speaking unnecessary. We put it in place anyway,
+                # since for the PAM modules we cannot avoid libpam anyway,
+                # after all they are loaded *by* libpam, and hence there's no
+                # loss in having explicit deps here, but there's a win: it
+                # makes the deps more visible.
+                #
+                # (In case you wonder why we do dlopen_libpam() from the PAM
+                # modules in the first place: that's mostly so that all our PAM
+                # code (regardless if our PAM modules or our PAM consuming
+                # programs) can use the same helpers, which hence go via
+                # dlopen_libpam().
                 libpam_misc,
                 libpam,
                 threads,
index e093be03582dfe6a963713ba19df255f3b803319..2c489dfce40a763538fa9699315037ad530b1572 100644 (file)
 #include <sys/statvfs.h>
 #include <unistd.h>
 
-#if HAVE_PAM
-#include <security/pam_appl.h>
-#endif
-
 #include "sd-messages.h"
 
 #include "apparmor-util.h"
@@ -61,6 +57,7 @@
 #include "nsflags.h"
 #include "open-file.h"
 #include "osc-context.h"
+#include "pam-util.h"
 #include "path-util.h"
 #include "pidref.h"
 #include "proc-cmdline.h"
@@ -1155,13 +1152,13 @@ static int pam_close_session_and_delete_credentials(pam_handle_t *handle, int fl
 
         assert(handle);
 
-        r = pam_close_session(handle, flags);
+        r = sym_pam_close_session(handle, flags);
         if (r != PAM_SUCCESS)
-                log_debug("pam_close_session() failed: %s", pam_strerror(handle, r));
+                pam_syslog_pam_error(handle, LOG_DEBUG, r, "pam_close_session() failed: @PAMERR@");
 
-        s = pam_setcred(handle, PAM_DELETE_CRED | flags);
+        s = sym_pam_setcred(handle, PAM_DELETE_CRED | flags);
         if (s != PAM_SUCCESS)
-                log_debug("pam_setcred(PAM_DELETE_CRED) failed: %s", pam_strerror(handle, s));
+                pam_syslog_pam_error(handle, LOG_DEBUG, r, "pam_setcred(PAM_DELETE_CRED) failed: @PAMERR@");
 
         return r != PAM_SUCCESS ? r : s;
 }
@@ -1305,12 +1302,14 @@ static int setup_pam(
         assert(fds || n_fds == 0);
         assert(env);
 
-        /* We set up PAM in the parent process, then fork. The child
-         * will then stay around until killed via PR_GET_PDEATHSIG or
-         * systemd via the cgroup logic. It will then remove the PAM
-         * session again. The parent process will exec() the actual
-         * daemon. We do things this way to ensure that the main PID
-         * of the daemon is the one we initially fork()ed. */
+        /* We set up PAM in the parent process, then fork. The child will then stay around until killed via
+         * PR_GET_PDEATHSIG or systemd via the cgroup logic. It will then remove the PAM session again. The
+         * parent process will exec() the actual daemon. We do things this way to ensure that the main PID of
+         * the daemon is the one we initially fork()ed. */
+
+        r = dlopen_libpam();
+        if (r < 0)
+                return log_error_errno(r, "PAM support not available: %m");
 
         r = barrier_create(&barrier);
         if (r < 0)
@@ -1319,7 +1318,7 @@ static int setup_pam(
         if (log_get_max_level() < LOG_DEBUG)
                 flags |= PAM_SILENT;
 
-        pam_code = pam_start(context->pam_name, user, &conv, &handle);
+        pam_code = sym_pam_start(context->pam_name, user, &conv, &handle);
         if (pam_code != PAM_SUCCESS) {
                 handle = NULL;
                 goto fail;
@@ -1329,32 +1328,32 @@ static int setup_pam(
         if (r < 0)
                 goto fail;
         if (r > 0) {
-                pam_code = pam_set_item(handle, PAM_TTY, tty);
+                pam_code = sym_pam_set_item(handle, PAM_TTY, tty);
                 if (pam_code != PAM_SUCCESS)
                         goto fail;
         }
 
         STRV_FOREACH(nv, *env) {
-                pam_code = pam_putenv(handle, *nv);
+                pam_code = sym_pam_putenv(handle, *nv);
                 if (pam_code != PAM_SUCCESS)
                         goto fail;
         }
 
-        pam_code = pam_acct_mgmt(handle, flags);
+        pam_code = sym_pam_acct_mgmt(handle, flags);
         if (pam_code != PAM_SUCCESS)
                 goto fail;
 
-        pam_code = pam_setcred(handle, PAM_ESTABLISH_CRED | flags);
+        pam_code = sym_pam_setcred(handle, PAM_ESTABLISH_CRED | flags);
         if (pam_code != PAM_SUCCESS)
-                log_debug("pam_setcred(PAM_ESTABLISH_CRED) failed, ignoring: %s", pam_strerror(handle, pam_code));
+                pam_syslog_pam_error(handle, LOG_DEBUG, pam_code, "pam_setcred(PAM_ESTABLISH_CRED) failed, ignoring: @PAMERR@");
 
-        pam_code = pam_open_session(handle, flags);
+        pam_code = sym_pam_open_session(handle, flags);
         if (pam_code != PAM_SUCCESS)
                 goto fail;
 
         close_session = true;
 
-        e = pam_getenvlist(handle);
+        e = sym_pam_getenvlist(handle);
         if (!e) {
                 pam_code = PAM_BUF_ERR;
                 goto fail;
@@ -1439,7 +1438,7 @@ static int setup_pam(
         child_finish:
                 /* NB: pam_end() when called in child processes should set PAM_DATA_SILENT to let the module
                  * know about this. See pam_end(3) */
-                (void) pam_end(handle, pam_code | flags | PAM_DATA_SILENT);
+                (void) sym_pam_end(handle, pam_code | flags | PAM_DATA_SILENT);
                 _exit(ret);
         }
 
@@ -1465,7 +1464,7 @@ static int setup_pam(
 
 fail:
         if (pam_code != PAM_SUCCESS) {
-                log_error("PAM failed: %s", pam_strerror(handle, pam_code));
+                pam_syslog_pam_error(handle, LOG_ERR, pam_code, "PAM failed: @PAMERR@");
                 r = -EPERM;  /* PAM errors do not map to errno */
         } else
                 log_error_errno(r, "PAM failed: %m");
@@ -1474,7 +1473,7 @@ fail:
                 if (close_session)
                         pam_code = pam_close_session_and_delete_credentials(handle, flags);
 
-                (void) pam_end(handle, pam_code | flags);
+                (void) sym_pam_end(handle, pam_code | flags);
         }
 
         closelog();
index 16c5df0c45e2834b53a87deffd1e0950a9c9bce9..1e701a90f233b4db650e535d10fadd2304983199 100644 (file)
@@ -213,7 +213,7 @@ executables += [
                 'link_with' : executor_libs,
                 'dependencies' : [
                         libapparmor_cflags,
-                        libpam,
+                        libpam_cflags,
                         libseccomp,
                         libselinux,
                 ],
index 78cd06b8e80ee6c80be95811aab7ec12847960e0..529fee2937bc9e97ac557f0c2c11fa86b9e59c2e 100644 (file)
@@ -1,9 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include <libintl.h>
-#include <security/pam_ext.h>
 #include <security/pam_misc.h>
-#include <security/pam_modules.h>
 
 #include "sd-bus.h"
 
@@ -791,6 +789,11 @@ _public_ PAM_EXTERN int pam_sm_authenticate(
 
         AcquireHomeFlags flags = 0;
         bool debug = false;
+        int r;
+
+        r = dlopen_libpam();
+        if (r < 0)
+                return PAM_SERVICE_ERR;
 
         pam_log_setup();
 
@@ -855,6 +858,10 @@ _public_ PAM_EXTERN int pam_sm_open_session(
         bool debug = false;
         int r;
 
+        r = dlopen_libpam();
+        if (r < 0)
+                return PAM_SERVICE_ERR;
+
         pam_log_setup();
 
         if (parse_env(handle, &flags) < 0)
@@ -969,6 +976,10 @@ _public_ PAM_EXTERN int pam_sm_acct_mgmt(
         usec_t t;
         int r;
 
+        r = dlopen_libpam();
+        if (r < 0)
+                return PAM_SERVICE_ERR;
+
         pam_log_setup();
 
         if (parse_env(handle, &flags) < 0)
@@ -1084,6 +1095,10 @@ _public_ PAM_EXTERN int pam_sm_chauthtok(
         bool debug = false;
         int r;
 
+        r = dlopen_libpam();
+        if (r < 0)
+                return PAM_SERVICE_ERR;
+
         pam_log_setup();
 
         if (parse_argv(handle,
index dd96a27479dd18be6bba994e5d49230e827a0554..30325cd7ee61284c6031c9eb36d16c338451c039 100644 (file)
@@ -3,13 +3,8 @@
 #include <endian.h>
 #include <fcntl.h>
 #include <pwd.h>
-#include <security/_pam_macros.h>
-#include <security/pam_ext.h>
 #include <security/pam_misc.h>
-#include <security/pam_modules.h>
-#include <security/pam_modutil.h>
 #include <sys/file.h>
-#include "time-util.h"
 #include <sys/stat.h>
 #include <sys/sysmacros.h>
 #include <unistd.h>
@@ -52,6 +47,7 @@
 #include "string-util.h"
 #include "strv.h"
 #include "terminal-util.h"
+#include "time-util.h"
 #include "tmpfile-util.h"
 #include "user-util.h"
 #include "userdb.h"
@@ -1728,6 +1724,10 @@ _public_ PAM_EXTERN int pam_sm_open_session(
 
         assert(handle);
 
+        r = dlopen_libpam();
+        if (r < 0)
+                return PAM_SERVICE_ERR;
+
         pam_log_setup();
 
         uint64_t default_capability_bounding_set = UINT64_MAX, default_capability_ambient_set = UINT64_MAX;
index 36b4b22552589f7c2a34ef2a78b5299b4164cfaa..2c17eae46fc9be1f19a3fb61c22cd53453cf9f64 100644 (file)
@@ -1,11 +1,5 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
-#include <security/_pam_macros.h>
-#include <security/pam_ext.h>
-#include <security/pam_misc.h>
-#include <security/pam_modules.h>
-#include <security/pam_modutil.h>
-
 #include "keyring-util.h"
 #include "nulstr-util.h"
 #include "pam-util.h"
@@ -21,8 +15,14 @@ _public_ PAM_EXTERN int pam_sm_authenticate(
                 int flags,
                 int argc, const char **argv) {
 
+        int r;
+
         assert(handle);
 
+        r = dlopen_libpam();
+        if (r < 0)
+                return PAM_SERVICE_ERR;
+
         pam_log_setup();
 
         /* Parse argv. */
@@ -63,7 +63,6 @@ _public_ PAM_EXTERN int pam_sm_authenticate(
 
         _cleanup_(erase_and_freep) void *p = NULL;
         size_t n;
-        int r;
 
         r = keyring_read(serial, &p, &n);
         if (r < 0)
index f341c79df808edc9e5d69e5859ad67221ba95d3d..790b1f2825c525cae5e2fd40884683db1dddddd9 100644 (file)
@@ -323,7 +323,7 @@ libshared_deps = [threads,
                   libmount,
                   libopenssl,
                   libp11kit_cflags,
-                  libpam,
+                  libpam_cflags,
                   librt,
                   libseccomp,
                   libselinux,
index a2f2126cbc63c186382cb9f60714ea7845632f94..a967a41bee7241e96ef1690bc8e5231750ecb001 100644 (file)
 #include "stdio-util.h"
 #include "string-util.h"
 
+#if HAVE_PAM
+static void *libpam_dl = NULL;
+
+DLSYM_PROTOTYPE(pam_acct_mgmt) = NULL;
+DLSYM_PROTOTYPE(pam_close_session) = NULL;
+DLSYM_PROTOTYPE(pam_end) = NULL;
+DLSYM_PROTOTYPE(pam_get_data) = NULL;
+DLSYM_PROTOTYPE(pam_get_item) = NULL;
+DLSYM_PROTOTYPE(pam_getenvlist) = NULL;
+DLSYM_PROTOTYPE(pam_open_session) = NULL;
+DLSYM_PROTOTYPE(pam_putenv) = NULL;
+DLSYM_PROTOTYPE(pam_set_data) = NULL;
+DLSYM_PROTOTYPE(pam_set_item) = NULL;
+DLSYM_PROTOTYPE(pam_setcred) = NULL;
+DLSYM_PROTOTYPE(pam_start) = NULL;
+DLSYM_PROTOTYPE(pam_strerror) = NULL;
+DLSYM_PROTOTYPE(pam_syslog) = NULL;
+DLSYM_PROTOTYPE(pam_vsyslog) = NULL;
+
+int dlopen_libpam(void) {
+        ELF_NOTE_DLOPEN("libpam",
+                        "Support for LinuxPAM",
+                        ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED,
+                        "libpam.so.0");
+
+        return dlopen_many_sym_or_warn(
+                        &libpam_dl,
+                        "libpam.so.0",
+                        LOG_DEBUG,
+                        DLSYM_ARG(pam_acct_mgmt),
+                        DLSYM_ARG(pam_close_session),
+                        DLSYM_ARG(pam_end),
+                        DLSYM_ARG(pam_get_data),
+                        DLSYM_ARG(pam_get_item),
+                        DLSYM_ARG(pam_getenvlist),
+                        DLSYM_ARG(pam_open_session),
+                        DLSYM_ARG(pam_putenv),
+                        DLSYM_ARG(pam_set_data),
+                        DLSYM_ARG(pam_set_item),
+                        DLSYM_ARG(pam_setcred),
+                        DLSYM_ARG(pam_start),
+                        DLSYM_ARG(pam_strerror),
+                        DLSYM_ARG(pam_syslog),
+                        DLSYM_ARG(pam_vsyslog));
+}
+#endif
+
 void pam_log_setup(void) {
         /* Make sure we don't leak the syslog fd we open by opening/closing the fd each time. */
         log_set_open_when_needed(true);
@@ -30,7 +77,7 @@ int pam_syslog_errno(pam_handle_t *handle, int level, int error, const char *for
         LOCAL_ERRNO(error);
 
         va_start(ap, format);
-        pam_vsyslog(handle, LOG_ERR, format, ap);
+        sym_pam_vsyslog(handle, LOG_ERR, format, ap);
         va_end(ap);
 
         return error == -ENOMEM ? PAM_BUF_ERR : PAM_SERVICE_ERR;
@@ -45,7 +92,7 @@ int pam_syslog_pam_error(pam_handle_t *handle, int level, int error, const char
 
         const char *p = endswith(format, "@PAMERR@");
         if (p) {
-                const char *pamerr = pam_strerror(handle, error);
+                const char *pamerr = sym_pam_strerror(handle, error);
                 if (strchr(pamerr, '%'))
                         pamerr = "n/a";  /* We cannot have any formatting chars */
 
@@ -53,10 +100,10 @@ int pam_syslog_pam_error(pam_handle_t *handle, int level, int error, const char
                 xsprintf(buf, "%.*s%s", (int)(p - format), format, pamerr);
 
                 DISABLE_WARNING_FORMAT_NONLITERAL;
-                pam_vsyslog(handle, level, buf, ap);
+                sym_pam_vsyslog(handle, level, buf, ap);
                 REENABLE_WARNING;
         } else
-                pam_vsyslog(handle, level, format, ap);
+                sym_pam_vsyslog(handle, level, format, ap);
 
         va_end(ap);
 
@@ -106,9 +153,9 @@ static void pam_bus_data_destroy(pam_handle_t *handle, void *data, int error_sta
         if (FLAGS_SET(error_status, PAM_DATA_SILENT) &&
             d->bus && bus_origin_changed(d->bus))
                 /* Please adjust test/units/end.sh when updating the log message. */
-                pam_syslog(handle, LOG_DEBUG,
-                           "Warning: cannot close sd-bus connection (%s) after fork when it was opened before the fork.",
-                           strna(d->cache_id));
+                sym_pam_syslog(handle, LOG_DEBUG,
+                               "Warning: cannot close sd-bus connection (%s) after fork when it was opened before the fork.",
+                               strna(d->cache_id));
 
         pam_bus_data_free(data);
 }
@@ -140,7 +187,7 @@ void pam_bus_data_disconnectp(PamBusData **_d) {
 
         handle = ASSERT_PTR(d->pam_handle); /* Keep a reference to the session even after 'd' might be invalidated */
 
-        r = pam_set_data(handle, ASSERT_PTR(d->cache_id), NULL, NULL);
+        r = sym_pam_set_data(handle, ASSERT_PTR(d->cache_id), NULL, NULL);
         if (r != PAM_SUCCESS)
                 pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to release PAM user record data, ignoring: @PAMERR@");
 
@@ -167,7 +214,7 @@ int pam_acquire_bus_connection(
                 return pam_log_oom(handle);
 
         /* We cache the bus connection so that we can share it between the session and the authentication hooks */
-        r = pam_get_data(handle, cache_id, (const void**) &d);
+        r = sym_pam_get_data(handle, cache_id, (const void**) &d);
         if (r == PAM_SUCCESS && d)
                 goto success;
         if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA))
@@ -186,7 +233,7 @@ int pam_acquire_bus_connection(
         if (r < 0)
                 return pam_syslog_errno(handle, LOG_ERR, r, "Failed to connect to system bus: %m");
 
-        r = pam_set_data(handle, d->cache_id, d, pam_bus_data_destroy);
+        r = sym_pam_set_data(handle, d->cache_id, d, pam_bus_data_destroy);
         if (r != PAM_SUCCESS)
                 return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to set PAM bus data: @PAMERR@");
 
@@ -221,7 +268,7 @@ int pam_get_bus_data(
                 return pam_log_oom(handle);
 
         /* We cache the bus connection so that we can share it between the session and the authentication hooks */
-        r = pam_get_data(handle, cache_id, (const void**) &d);
+        r = sym_pam_get_data(handle, cache_id, (const void**) &d);
         if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA))
                 return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get bus connection: @PAMERR@");
 
@@ -263,7 +310,7 @@ int pam_get_item_many_internal(pam_handle_t *handle, ...) {
                 }
 
                 const void **value = ASSERT_PTR(va_arg(ap, const void **));
-                r = pam_get_item(handle, item_type, value);
+                r = sym_pam_get_item(handle, item_type, value);
                 if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS))
                         break;
         }
@@ -287,7 +334,7 @@ int pam_get_data_many_internal(pam_handle_t *handle, ...) {
                 }
 
                 const void **value = ASSERT_PTR(va_arg(ap, const void **));
-                r = pam_get_data(handle, data_name, value);
+                r = sym_pam_get_data(handle, data_name, value);
                 if (!IN_SET(r, PAM_NO_MODULE_DATA, PAM_SUCCESS))
                         break;
         }
@@ -313,11 +360,11 @@ int pam_prompt_graceful(pam_handle_t *handle, int style, char **ret_response, co
                 return PAM_BUF_ERR;
 
         const struct pam_conv *conv = NULL;
-        r = pam_get_item(handle, PAM_CONV, (const void**) &conv);
+        r = sym_pam_get_item(handle, PAM_CONV, (const void**) &conv);
         if (!IN_SET(r, PAM_SUCCESS, PAM_BAD_ITEM))
                 return pam_syslog_pam_error(handle, LOG_DEBUG, r, "Failed to get conversation function structure: @PAMERR@");
         if (!conv || !conv->conv) {
-                pam_syslog(handle, LOG_DEBUG, "No conversation function.");
+                sym_pam_syslog(handle, LOG_DEBUG, "No conversation function.");
                 return PAM_SYSTEM_ERR;
         }
 
index f48a170c483758e1a45a3d31a9216287effb6308..d33ab537eb00d08721bed4cfb2ef1d592e63e03e 100644 (file)
@@ -1,11 +1,36 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
-#include <security/pam_modules.h> /* IWYU pragma: export */
 #include <syslog.h>
 
 #include "forward.h"
 
+#if HAVE_PAM
+#include <security/pam_appl.h>
+#include <security/pam_ext.h>
+#include <security/pam_modules.h> /* IWYU pragma: export */
+
+#include "dlfcn-util.h"
+
+extern DLSYM_PROTOTYPE(pam_acct_mgmt);
+extern DLSYM_PROTOTYPE(pam_close_session);
+extern DLSYM_PROTOTYPE(pam_end);
+extern DLSYM_PROTOTYPE(pam_get_data);
+extern DLSYM_PROTOTYPE(pam_get_item);
+extern DLSYM_PROTOTYPE(pam_getenvlist);
+extern DLSYM_PROTOTYPE(pam_open_session);
+extern DLSYM_PROTOTYPE(pam_putenv);
+extern DLSYM_PROTOTYPE(pam_set_data);
+extern DLSYM_PROTOTYPE(pam_set_item);
+extern DLSYM_PROTOTYPE(pam_setcred);
+extern DLSYM_PROTOTYPE(pam_start);
+extern DLSYM_PROTOTYPE(pam_strerror);
+extern DLSYM_PROTOTYPE(pam_syslog);
+extern DLSYM_PROTOTYPE(pam_vsyslog);
+
+int dlopen_libpam(void);
+#endif
+
 void pam_log_setup(void);
 
 int pam_syslog_errno(pam_handle_t *handle, int level, int error, const char *format, ...) _printf_(4,5);
@@ -13,10 +38,11 @@ int pam_syslog_errno(pam_handle_t *handle, int level, int error, const char *for
 int pam_syslog_pam_error(pam_handle_t *handle, int level, int error, const char *format, ...) _printf_(4,5);
 
 /* Call pam_vsyslog if debug is enabled */
-#define pam_debug_syslog(handle, debug, fmt, ...) ({                    \
-                        if (debug)                                      \
-                                pam_syslog(handle, LOG_DEBUG, fmt, ## __VA_ARGS__); \
-                })
+#define pam_debug_syslog(handle, debug, fmt, ...)                       \
+        ({                                                              \
+                if (debug)                                              \
+                        sym_pam_syslog(handle, LOG_DEBUG, fmt, ## __VA_ARGS__); \
+        })
 
 static inline int pam_log_oom(pam_handle_t *handle) {
         /* This is like log_oom(), but uses PAM logging */
index ede99123629c4dd39d6cb86f438c38923a9d83ce..ada19fdc66d6f4527bf1fc80d925578572da6e17 100644 (file)
@@ -12,6 +12,7 @@
 #include "libfido2-util.h"
 #include "main-func.h"
 #include "module-util.h"
+#include "pam-util.h"
 #include "password-quality-util-passwdqc.h"
 #include "password-quality-util-pwquality.h"
 #include "pcre2-util.h"
@@ -50,6 +51,7 @@ static int run(int argc, char **argv) {
         ASSERT_DLOPEN(dlopen_libkmod, HAVE_KMOD);
         ASSERT_DLOPEN(dlopen_libapparmor, HAVE_APPARMOR);
         ASSERT_DLOPEN(dlopen_libaudit, HAVE_AUDIT);
+        ASSERT_DLOPEN(dlopen_libpam, HAVE_PAM);
 
         return 0;
 }