pam_systemd: acquire inhibit lock when inhibit= is specified

This adds a new function, acquire_inhibit_lock, that takes a logind
inhibitor lock for the duration of the PAM session. The lock fd is
stored in PAM data with pam_cleanup_close. In pam_sm_close_session, the
lock is released explicitly by passing NULL to pam_set_data, which
triggers the registered cleanup function immediately rather than waiting
for pam_end.

The inhibit type and reason string can also be configured via the
XDG_SESSION_INHIBIT and XDG_SESSION_INHIBIT_WHY environment variables,
which take precedence over the module arguments.

Fixes: #20654
This commit is contained in:
Clayton Craft
2026-06-20 09:57:42 +01:00
committed by Luca Boccassi
parent 58fc96bb71
commit 179b7c2ab5
+60
View File
@@ -1747,6 +1747,57 @@ static int close_osc_context(pam_handle_t *pamh, bool debug) {
return PAM_SUCCESS;
}
static int acquire_inhibit_lock(
pam_handle_t *pamh,
const char *inhibit_what,
const char *inhibit_why,
bool debug) {
_cleanup_(pam_bus_data_disconnectp) PamBusData *d = NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
const char *service = NULL;
int r;
assert(pamh);
assert(inhibit_what);
r = pam_acquire_bus_connection(pamh, "pam-systemd", debug, &bus, &d);
if (r != PAM_SUCCESS)
return r;
(void) sym_pam_get_item(pamh, PAM_SERVICE, (const void **) &service);
r = bus_call_method(bus, bus_login_mgr, "Inhibit", &error, &reply, "ssss",
inhibit_what,
service ?: "pam_systemd",
inhibit_why ?: "Active PAM session",
"block");
if (r < 0) {
sym_pam_syslog(pamh, LOG_WARNING, "Failed to acquire inhibit lock: %s", bus_error_message(&error, r));
return PAM_SESSION_ERR;
}
int raw_fd;
r = sd_bus_message_read_basic(reply, SD_BUS_TYPE_UNIX_FD, &raw_fd);
if (r < 0)
return pam_bus_log_parse_error(pamh, r);
_cleanup_close_ int fd = fcntl(raw_fd, F_DUPFD_CLOEXEC, 3);
if (fd < 0)
return pam_syslog_errno(pamh, LOG_ERR, errno, "Failed to dup inhibit lock fd: %m");
r = sym_pam_set_data(pamh, "systemd.inhibit-fd", FD_TO_PTR(fd), pam_cleanup_close);
if (r != PAM_SUCCESS)
return pam_syslog_pam_error(pamh, LOG_ERR, r,
"Failed to store inhibit lock fd: @PAMERR@");
TAKE_FD(fd);
pam_debug_syslog(pamh, debug, "Acquired inhibit lock for '%s'.", inhibit_what);
return PAM_SUCCESS;
}
_public_ PAM_EXTERN int pam_sm_open_session(
pam_handle_t *pamh,
int flags,
@@ -1803,6 +1854,8 @@ _public_ PAM_EXTERN int pam_sm_open_session(
c.desktop = getenv_harder(pamh, "XDG_SESSION_DESKTOP", desktop_pam);
c.area = getenv_harder(pamh, "XDG_AREA", area_pam);
c.incomplete = getenv_harder_bool(pamh, "XDG_SESSION_INCOMPLETE", false);
inhibit_what = getenv_harder(pamh, "XDG_SESSION_INHIBIT", inhibit_what);
inhibit_why = getenv_harder(pamh, "XDG_SESSION_INHIBIT_WHY", inhibit_why);
const char *extra_device_access = getenv_harder(pamh, "XDG_SESSION_EXTRA_DEVICE_ACCESS", NULL);
if (extra_device_access) {
@@ -1828,6 +1881,11 @@ _public_ PAM_EXTERN int pam_sm_open_session(
if (r != PAM_SUCCESS)
return r;
if (inhibit_what)
/* Failure to acquire the inhibit lock is not fatal, session setup should proceed
* regardless. */
(void) acquire_inhibit_lock(pamh, inhibit_what, inhibit_why, debug);
r = import_shell_credentials(pamh, debug);
if (r != PAM_SUCCESS)
return r;
@@ -1880,6 +1938,8 @@ _public_ PAM_EXTERN int pam_sm_close_session(
(void) close_osc_context(pamh, debug);
(void) sym_pam_set_data(pamh, "systemd.inhibit-fd", NULL, NULL);
id = sym_pam_getenv(pamh, "XDG_SESSION_ID");
if (id) {
_cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;