pid1: when a password is requested during PAMName= processing, query it via the ask-password logic

This commit is contained in:
Lennart Poettering
2025-01-15 00:24:29 +01:00
committed by Luca Boccassi
parent f55a6fc1e3
commit 8af1b296cb
2 changed files with 132 additions and 15 deletions

View File

@@ -770,6 +770,15 @@
changes in the original unit through notification messages. These messages will be considered belonging to the
session scope unit and not the original unit. It is hence not recommended to use <varname>PAMName=</varname> in
combination with <varname>NotifyAccess=</varname><option>all</option>.</para>
<para>If a PAM module interactively requests input (a password or suchlike) it will be attempted to
be read from a service credential (as configured via <varname>SetCredential=</varname>,
<varname>ImportCredential=</varname> and related calls) under the name
<varname>pam.authtok.<replaceable>pamservice</replaceable></varname>, where
<replaceable>pamservice</replaceable> is replaced by the PAM service name as configured with
<varname>PAMName=</varname>. (Note that the credential remains accessible for the runtime of the
service!) If no matching credential is set, the user is prompted for it interactively via the <ulink
url="https://systemd.io/PASSWORD_AGENTS">Password Agent</ulink> logic.</para>
</listitem>
</varlistentry>

View File

@@ -21,6 +21,7 @@
#include "apparmor-util.h"
#endif
#include "argv-util.h"
#include "ask-password-api.h"
#include "barrier.h"
#include "bitfield.h"
#include "bpf-dlopen.h"
@@ -1048,15 +1049,114 @@ static int enforce_user(
#if HAVE_PAM
static int null_conv(
static void pam_response_free_array(struct pam_response *responses, size_t n_responses) {
assert(responses || n_responses == 0);
FOREACH_ARRAY(resp, responses, n_responses)
erase_and_free(resp->resp);
free(responses);
}
typedef struct AskPasswordConvData {
const ExecContext *context;
const ExecParameters *params;
} AskPasswordConvData;
static int ask_password_conv(
int num_msg,
const struct pam_message **msg,
struct pam_response **resp,
void *appdata_ptr) {
const struct pam_message *msg[],
struct pam_response **ret,
void *userdata) {
/* We don't support conversations */
AskPasswordConvData *data = ASSERT_PTR(userdata);
bool set_credential_env_var = false;
int r;
return PAM_CONV_ERR;
assert(num_msg >= 0);
assert(msg);
assert(data->context);
assert(data->params);
size_t n = num_msg;
struct pam_response *responses = new0(struct pam_response, n);
if (!responses)
return PAM_BUF_ERR;
CLEANUP_ARRAY(responses, n, pam_response_free_array);
for (size_t i = 0; i < n; i++) {
const struct pam_message *mi = *msg + i;
switch (mi->msg_style) {
case PAM_PROMPT_ECHO_ON:
case PAM_PROMPT_ECHO_OFF: {
/* Locally set the $CREDENTIALS_DIRECTORY to the credentials directory we just populated */
if (!set_credential_env_var) {
_cleanup_free_ char *creds_dir = NULL;
r = exec_context_get_credential_directory(data->context, data->params, data->params->unit_id, &creds_dir);
if (r < 0)
return log_exec_error_errno(data->context, data->params, r, "Failed to determine credentials directory: %m");
if (creds_dir) {
if (setenv("CREDENTIALS_DIRECTORY", creds_dir, /* overwrite= */ true) < 0)
return log_exec_error_errno(data->context, data->params, r, "Failed to set $CREDENTIALS_DIRECTORY: %m");
} else
(void) unsetenv("CREDENTIALS_DIRECTORY");
set_credential_env_var = true;
}
_cleanup_free_ char *credential_name = strjoin("pam.authtok.", data->context->pam_name);
if (!credential_name)
return log_oom();
AskPasswordRequest req = {
.message = mi->msg,
.credential = credential_name,
.tty_fd = -EBADF,
.hup_fd = -EBADF,
.until = now(CLOCK_MONOTONIC) + 15 * USEC_PER_SEC,
};
_cleanup_(strv_free_erasep) char **acquired = NULL;
r = ask_password_auto(
&req,
ASK_PASSWORD_ACCEPT_CACHED|
ASK_PASSWORD_NO_TTY|
(mi->msg_style == PAM_PROMPT_ECHO_ON ? ASK_PASSWORD_ECHO : 0),
&acquired);
if (r < 0) {
log_exec_error_errno(data->context, data->params, r, "Failed to query for password: %m");
return PAM_CONV_ERR;
}
responses[i].resp = strdup(ASSERT_PTR(acquired[0]));
if (!responses[i].resp) {
log_oom();
return PAM_BUF_ERR;
}
break;
}
case PAM_ERROR_MSG:
log_exec_error(data->context, data->params, "PAM: %s", mi->msg);
break;
case PAM_TEXT_INFO:
log_exec_info(data->context, data->params, "PAM: %s", mi->msg);
break;
default:
return PAM_CONV_ERR;
}
}
*ret = TAKE_PTR(responses);
n = 0;
return PAM_SUCCESS;
}
static int pam_close_session_and_delete_credentials(pam_handle_t *handle, int flags) {
@@ -1074,24 +1174,27 @@ static int pam_close_session_and_delete_credentials(pam_handle_t *handle, int fl
return r != PAM_SUCCESS ? r : s;
}
#endif
static int setup_pam(
const char *name,
const ExecContext *context,
ExecParameters *params,
const char *user,
uid_t uid,
gid_t gid,
const char *tty,
char ***env, /* updated on success */
const int fds[], size_t n_fds,
int exec_fd) {
#if HAVE_PAM
AskPasswordConvData conv_data = {
.context = context,
.params = params,
};
static const struct pam_conv conv = {
.conv = null_conv,
.appdata_ptr = NULL
const struct pam_conv conv = {
.conv = ask_password_conv,
.appdata_ptr = &conv_data,
};
_cleanup_(barrier_destroy) Barrier barrier = BARRIER_NULL;
@@ -1103,8 +1206,12 @@ static int setup_pam(
pid_t parent_pid;
int flags = 0;
assert(name);
assert(context);
assert(params);
assert(user);
assert(uid_is_valid(uid));
assert(gid_is_valid(gid));
assert(fds || n_fds == 0);
assert(env);
/* We set up PAM in the parent process, then fork. The child
@@ -1121,12 +1228,13 @@ static int setup_pam(
if (log_get_max_level() < LOG_DEBUG)
flags |= PAM_SILENT;
pam_code = pam_start(name, user, &conv, &handle);
pam_code = pam_start(context->pam_name, user, &conv, &handle);
if (pam_code != PAM_SUCCESS) {
handle = NULL;
goto fail;
}
const char *tty = context->tty_path;
if (!tty) {
_cleanup_free_ char *q = NULL;
@@ -4976,7 +5084,7 @@ int exec_invoke(
* wins here. (See above.) */
/* All fds passed in the fds array will be closed in the pam child process. */
r = setup_pam(context->pam_name, username, uid, gid, context->tty_path, &accum_env, params->fds, n_fds, params->exec_fd);
r = setup_pam(context, params, username, uid, gid, &accum_env, params->fds, n_fds, params->exec_fd);
if (r < 0) {
*exit_status = EXIT_PAM;
return log_exec_error_errno(context, params, r, "Failed to set up PAM session: %m");