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-18 11:45:44 +00:00
committed by Luca Boccassi
parent f55a6fc1e3
commit 8af1b296cb
2 changed files with 132 additions and 15 deletions
+123 -15
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");