Merge pull request #27346 from poettering/pam-fixes

pam-systemd: be more careful with sharing bus connections
This commit is contained in:
Luca Boccassi
2023-04-27 20:31:57 +01:00
committed by GitHub
4 changed files with 136 additions and 57 deletions
+19 -18
View File
@@ -91,7 +91,8 @@ static int parse_env(
static int acquire_user_record(
pam_handle_t *handle,
const char *username,
UserRecord **ret_record) {
UserRecord **ret_record,
PamBusData **bus_data) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
@@ -140,7 +141,7 @@ static int acquire_user_record(
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_free_ char *generic_field = NULL, *json_copy = NULL;
r = pam_acquire_bus_connection(handle, "pam-systemd-home", &bus);
r = pam_acquire_bus_connection(handle, "pam-systemd-home", &bus, bus_data);
if (r != PAM_SUCCESS)
return r;
@@ -472,7 +473,8 @@ static int acquire_home(
pam_handle_t *handle,
bool please_authenticate,
bool please_suspend,
bool debug) {
bool debug,
PamBusData **bus_data) {
_cleanup_(user_record_unrefp) UserRecord *ur = NULL, *secret = NULL;
bool do_auth = please_authenticate, home_not_active = false, home_locked = false;
@@ -513,11 +515,11 @@ static int acquire_home(
if (r == PAM_SUCCESS && PTR_TO_FD(home_fd_ptr) >= 0)
return PAM_SUCCESS;
r = pam_acquire_bus_connection(handle, "pam-systemd-home", &bus);
r = pam_acquire_bus_connection(handle, "pam-systemd-home", &bus, bus_data);
if (r != PAM_SUCCESS)
return r;
r = acquire_user_record(handle, username, &ur);
r = acquire_user_record(handle, username, &ur, bus_data);
if (r != PAM_SUCCESS)
return r;
@@ -698,7 +700,7 @@ _public_ PAM_EXTERN int pam_sm_authenticate(
if (debug)
pam_syslog(handle, LOG_DEBUG, "pam-systemd-homed authenticating");
return acquire_home(handle, /* please_authenticate= */ true, suspend_please, debug);
return acquire_home(handle, /* please_authenticate= */ true, suspend_please, debug, NULL);
}
_public_ PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) {
@@ -710,6 +712,10 @@ _public_ PAM_EXTERN int pam_sm_open_session(
int flags,
int argc, const char **argv) {
/* Let's release the D-Bus connection once this function exits, after all the session might live
* quite a long time, and we are not going to process the bus connection in that time, so let's
* better close before the daemon kicks us off because we are not processing anything. */
_cleanup_(pam_bus_data_disconnectp) PamBusData *d = NULL;
bool debug = false, suspend_please = false;
int r;
@@ -725,9 +731,9 @@ _public_ PAM_EXTERN int pam_sm_open_session(
if (debug)
pam_syslog(handle, LOG_DEBUG, "pam-systemd-homed session start");
r = acquire_home(handle, /* please_authenticate = */ false, suspend_please, debug);
r = acquire_home(handle, /* please_authenticate = */ false, suspend_please, debug, &d);
if (r == PAM_USER_UNKNOWN) /* Not managed by us? Don't complain. */
goto success; /* Need to free the bus resource, as acquire_home() takes a reference. */
return PAM_SUCCESS;
if (r != PAM_SUCCESS)
return r;
@@ -741,11 +747,6 @@ _public_ PAM_EXTERN int pam_sm_open_session(
return pam_syslog_pam_error(handle, LOG_ERR, r,
"Failed to set PAM environment variable $SYSTEMD_HOME_SUSPEND: @PAMERR@");
success:
/* Let's release the D-Bus connection, after all the session might live quite a long time, and we are
* not going to process the bus connection in that time, so let's better close before the daemon
* kicks us off because we are not processing anything. */
(void) pam_release_bus_connection(handle, "pam-systemd-home");
return PAM_SUCCESS;
}
@@ -785,7 +786,7 @@ _public_ PAM_EXTERN int pam_sm_close_session(
if (r != PAM_SUCCESS)
return r;
r = pam_acquire_bus_connection(handle, "pam-systemd-home", &bus);
r = pam_acquire_bus_connection(handle, "pam-systemd-home", &bus, NULL);
if (r != PAM_SUCCESS)
return r;
@@ -832,11 +833,11 @@ _public_ PAM_EXTERN int pam_sm_acct_mgmt(
if (debug)
pam_syslog(handle, LOG_DEBUG, "pam-systemd-homed account management");
r = acquire_home(handle, /* please_authenticate = */ false, please_suspend, debug);
r = acquire_home(handle, /* please_authenticate = */ false, please_suspend, debug, NULL);
if (r != PAM_SUCCESS)
return r;
r = acquire_user_record(handle, NULL, &ur);
r = acquire_user_record(handle, NULL, &ur, NULL);
if (r != PAM_SUCCESS)
return r;
@@ -944,11 +945,11 @@ _public_ PAM_EXTERN int pam_sm_chauthtok(
if (debug)
pam_syslog(handle, LOG_DEBUG, "pam-systemd-homed account management");
r = pam_acquire_bus_connection(handle, "pam-systemd-home", &bus);
r = pam_acquire_bus_connection(handle, "pam-systemd-home", &bus, NULL);
if (r != PAM_SUCCESS)
return r;
r = acquire_user_record(handle, NULL, &ur);
r = acquire_user_record(handle, NULL, &ur, NULL);
if (r != PAM_SUCCESS)
return r;
+7 -12
View File
@@ -788,6 +788,10 @@ _public_ PAM_EXTERN int pam_sm_open_session(
int flags,
int argc, const char **argv) {
/* Let's release the D-Bus connection once this function exits, after all the session might live
* quite a long time, and we are not going to process the bus connection in that time, so let's
* better close before the daemon kicks us off because we are not processing anything. */
_cleanup_(pam_bus_data_disconnectp) PamBusData *d = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
const char
@@ -947,8 +951,7 @@ _public_ PAM_EXTERN int pam_sm_open_session(
return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM systemd.runtime_max_sec data: @PAMERR@");
/* Talk to logind over the message bus */
r = pam_acquire_bus_connection(handle, "pam-systemd", &bus);
r = pam_acquire_bus_connection(handle, "pam-systemd", &bus, &d);
if (r != PAM_SUCCESS)
return r;
@@ -1108,15 +1111,7 @@ success:
if (default_capability_ambient_set == UINT64_MAX)
default_capability_ambient_set = pick_default_capability_ambient_set(ur, service, seat);
r = apply_user_record_settings(handle, ur, debug, default_capability_bounding_set, default_capability_ambient_set);
if (r != PAM_SUCCESS)
return r;
/* Let's release the D-Bus connection, after all the session might live quite a long time, and we are
* not going to use the bus connection in that time, so let's better close before the daemon kicks us
* off because we are not processing anything. */
(void) pam_release_bus_connection(handle, "pam-systemd");
return PAM_SUCCESS;
return apply_user_record_settings(handle, ur, debug, default_capability_bounding_set, default_capability_ambient_set);
}
_public_ PAM_EXTERN int pam_sm_close_session(
@@ -1159,7 +1154,7 @@ _public_ PAM_EXTERN int pam_sm_close_session(
/* Before we go and close the FIFO we need to tell logind that this is a clean session
* shutdown, so that it doesn't just go and slaughter us immediately after closing the fd */
r = pam_acquire_bus_connection(handle, "pam-systemd", &bus);
r = pam_acquire_bus_connection(handle, "pam-systemd", &bus, NULL);
if (r != PAM_SUCCESS)
return r;
+106 -26
View File
@@ -6,8 +6,10 @@
#include "alloc-util.h"
#include "errno-util.h"
#include "format-util.h"
#include "macro.h"
#include "pam-util.h"
#include "process-util.h"
#include "stdio-util.h"
#include "string-util.h"
@@ -49,53 +51,131 @@ int pam_syslog_pam_error(pam_handle_t *handle, int level, int error, const char
return error;
}
static void cleanup_system_bus(pam_handle_t *handle, void *data, int error_status) {
/* The PAM_DATA_SILENT flag is the way that pam_end() communicates to the module stack that this
* invocation of pam_end() is not the final one, but in the process that is going to directly exec
* the child. This means we are being called after a fork(), and we do not want to try and clean
* up the sd-bus object, as it would affect the parent too and we'll hit an assertion. */
if (error_status & PAM_DATA_SILENT)
return (void) pam_syslog_pam_error(
handle,
LOG_ERR,
SYNTHETIC_ERRNO(EUCLEAN),
"Attempted to close sd-bus after fork, this should not happen.");
/* A small structure we store inside the PAM session object, that allows us to resue bus connections but pins
* it to the process thoroughly. */
struct PamBusData {
sd_bus *bus;
pam_handle_t *pam_handle;
char *cache_id;
};
sd_bus_flush_close_unref(data);
static PamBusData *pam_bus_data_free(PamBusData *d) {
/* The actual destructor */
if (!d)
return NULL;
/* NB: PAM sessions usually involve forking off a child process, and thus the PAM context might be
* duplicated in the child. This destructor might be called twice: both in the parent and in the
* child. sd_bus_flush_close_unref() however is smart enough to be a NOP when invoked in any other
* process than the one it was invoked from, hence we don't need to add any extra protection here to
* ensure that destruction of the bus connection in the child affects the parent's connection
* somehow. */
sd_bus_flush_close_unref(d->bus);
free(d->cache_id);
/* Note: we don't destroy pam_handle here, because this object is pinned by the handle, and not vice versa! */
return mfree(d);
}
int pam_acquire_bus_connection(pam_handle_t *handle, const char *module_name, sd_bus **ret) {
_cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
DEFINE_TRIVIAL_CLEANUP_FUNC(PamBusData*, pam_bus_data_free);
static void pam_bus_data_destroy(pam_handle_t *handle, void *data, int error_status) {
/* Destructor when called from PAM. Note that error_status is supposed to tell us via PAM_DATA_SILENT
* whether we are called in a forked off child of the PAM session or in the original parent. We don't
* bother with that however, and instead rely on the PID checks that sd_bus_flush_close_unref() does
* internally anyway. That said, we still generate a warning message, since this really shouldn't
* happen. */
if (error_status & PAM_DATA_SILENT)
pam_syslog(handle, LOG_WARNING, "Attempted to close sd-bus after fork, this should not happen.");
pam_bus_data_free(data);
}
static char* pam_make_bus_cache_id(const char *module_name) {
char *id;
/* We want to cache bus connections between hooks. But we don't want to allow them to be reused in
* child processes (because sd-bus doesn't support that). We also don't want them to be reused
* between our own PAM modules, because they might be linked against different versions of our
* utility functions and share different state. Hence include both a module ID and a PID in the data
* field ID. */
if (asprintf(&id, "system-bus-%s-" PID_FMT, ASSERT_PTR(module_name), getpid_cached()) < 0)
return NULL;
return id;
}
void pam_bus_data_disconnectp(PamBusData **_d) {
PamBusData *d = *ASSERT_PTR(_d);
pam_handle_t *handle;
int r;
/* Disconnects the connection explicitly (for use via _cleanup_()) when called */
if (!d)
return;
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);
if (r != PAM_SUCCESS)
pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to release PAM user record data, ignoring: @PAMERR@");
/* Note, the pam_set_data() call will invalidate 'd', don't access here anymore */
}
int pam_acquire_bus_connection(
pam_handle_t *handle,
const char *module_name,
sd_bus **ret_bus,
PamBusData **ret_pam_bus_data) {
_cleanup_(pam_bus_data_freep) PamBusData *d = NULL;
_cleanup_free_ char *cache_id = NULL;
int r;
assert(handle);
assert(module_name);
assert(ret);
assert(ret_bus);
cache_id = strjoin("system-bus-", module_name);
cache_id = pam_make_bus_cache_id(module_name);
if (!cache_id)
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**) &bus);
if (r == PAM_SUCCESS && bus) {
*ret = sd_bus_ref(TAKE_PTR(bus)); /* Increase the reference counter, so that the PAM data stays valid */
return PAM_SUCCESS;
}
r = 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))
return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get bus connection: @PAMERR@");
r = sd_bus_open_system(&bus);
d = new(PamBusData, 1);
if (!d)
return pam_log_oom(handle);
*d = (PamBusData) {
.cache_id = TAKE_PTR(cache_id),
.pam_handle = handle,
};
r = sd_bus_open_system(&d->bus);
if (r < 0)
return pam_syslog_errno(handle, LOG_ERR, r, "Failed to connect to system bus: %m");
r = pam_set_data(handle, cache_id, bus, cleanup_system_bus);
r = 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@");
sd_bus_ref(bus);
*ret = TAKE_PTR(bus);
success:
*ret_bus = sd_bus_ref(d->bus);
if (ret_pam_bus_data)
*ret_pam_bus_data = d;
TAKE_PTR(d); /* don't auto-destroy anymore, it's installed now */
return PAM_SUCCESS;
}
@@ -106,7 +186,7 @@ int pam_release_bus_connection(pam_handle_t *handle, const char *module_name) {
assert(module_name);
cache_id = strjoin("system-bus-", module_name);
cache_id = pam_make_bus_cache_id(module_name);
if (!cache_id)
return pam_log_oom(handle);
+4 -1
View File
@@ -24,9 +24,12 @@ static inline int pam_bus_log_parse_error(pam_handle_t *handle, int r) {
return pam_syslog_errno(handle, LOG_ERR, r, "Failed to parse bus message: %m");
}
typedef struct PamBusData PamBusData;
void pam_bus_data_disconnectp(PamBusData **d);
/* Use a different module name per different PAM module. They are all loaded in the same namespace, and this
* helps avoid a clash in the internal data structures of sd-bus. It will be used as key for cache items. */
int pam_acquire_bus_connection(pam_handle_t *handle, const char *module_name, sd_bus **ret);
int pam_acquire_bus_connection(pam_handle_t *handle, const char *module_name, sd_bus **ret_bus, PamBusData **ret_bus_data);
int pam_release_bus_connection(pam_handle_t *handle, const char *module_name);
void pam_cleanup_free(pam_handle_t *handle, void *data, int error_status);