From: Lennart Poettering Date: Mon, 8 Sep 2025 14:04:23 +0000 (+0200) Subject: pam: make libpam a dlopen() based dependency X-Git-Tag: v259-rc1~483 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=882c9ce0402ec6e37201628a9a361500ff39b1ed;p=thirdparty%2Fsystemd.git pam: make libpam a dlopen() based dependency --- diff --git a/meson.build b/meson.build index 68b2df7b495..c5a2953200f 100644 --- a/meson.build +++ b/meson.build @@ -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, diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index e093be03582..2c489dfce40 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -14,10 +14,6 @@ #include #include -#if HAVE_PAM -#include -#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(); diff --git a/src/core/meson.build b/src/core/meson.build index 16c5df0c45e..1e701a90f23 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -213,7 +213,7 @@ executables += [ 'link_with' : executor_libs, 'dependencies' : [ libapparmor_cflags, - libpam, + libpam_cflags, libseccomp, libselinux, ], diff --git a/src/home/pam_systemd_home.c b/src/home/pam_systemd_home.c index 78cd06b8e80..529fee2937b 100644 --- a/src/home/pam_systemd_home.c +++ b/src/home/pam_systemd_home.c @@ -1,9 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include -#include #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, diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c index dd96a27479d..30325cd7ee6 100644 --- a/src/login/pam_systemd.c +++ b/src/login/pam_systemd.c @@ -3,13 +3,8 @@ #include #include #include -#include -#include #include -#include -#include #include -#include "time-util.h" #include #include #include @@ -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; diff --git a/src/login/pam_systemd_loadkey.c b/src/login/pam_systemd_loadkey.c index 36b4b225525..2c17eae46fc 100644 --- a/src/login/pam_systemd_loadkey.c +++ b/src/login/pam_systemd_loadkey.c @@ -1,11 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include -#include -#include -#include -#include - #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) diff --git a/src/shared/meson.build b/src/shared/meson.build index f341c79df80..790b1f2825c 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -323,7 +323,7 @@ libshared_deps = [threads, libmount, libopenssl, libp11kit_cflags, - libpam, + libpam_cflags, librt, libseccomp, libselinux, diff --git a/src/shared/pam-util.c b/src/shared/pam-util.c index a2f2126cbc6..a967a41bee7 100644 --- a/src/shared/pam-util.c +++ b/src/shared/pam-util.c @@ -16,6 +16,53 @@ #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; } diff --git a/src/shared/pam-util.h b/src/shared/pam-util.h index f48a170c483..d33ab537eb0 100644 --- a/src/shared/pam-util.h +++ b/src/shared/pam-util.h @@ -1,11 +1,36 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include /* IWYU pragma: export */ #include #include "forward.h" +#if HAVE_PAM +#include +#include +#include /* 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 */ diff --git a/src/test/test-dlopen-so.c b/src/test/test-dlopen-so.c index ede99123629..ada19fdc66d 100644 --- a/src/test/test-dlopen-so.c +++ b/src/test/test-dlopen-so.c @@ -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; }