From a8f777d9050d79b002a86987003ec66964240844 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 20 May 2026 14:24:10 +0200 Subject: [PATCH] plugins/auth/pam: expose sudo prompt to PAM via SUDO_PROMPT env Some PAM implementations do not use PAM conversations and instead present their own native client UI. In those cases, modules cannot infer sudo's custom prompt text from the conversation callback path. Set SUDO_PROMPT in the PAM environment before pam_authenticate(), so modules that render their own UI can combine: - PAM service identity (from the PAM transaction) - sudo's resolved prompt string (SUDO_PROMPT) to display a consistent, context-aware prompt. To avoid stale state, clear SUDO_PROMPT before authentication starts and clear it again after pam_authenticate() returns. Allowing PAM modules in the stack to read (and potentially change) SUDO_PROMPT does not introduce a new trust boundary concern: PAM modules already control the conversation flow and can provide their own prompt text/messages through the standard PAM conversation mechanism. --- plugins/sudoers/auth/pam.c | 41 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/plugins/sudoers/auth/pam.c b/plugins/sudoers/auth/pam.c index 04ace90be8..3bb98bf1a5 100644 --- a/plugins/sudoers/auth/pam.c +++ b/plugins/sudoers/auth/pam.c @@ -56,6 +56,7 @@ #endif #include +#include #include "sudo_auth.h" /* Only OpenPAM and Linux PAM use const qualifiers. */ @@ -313,12 +314,52 @@ sudo_pam_verify(const struct sudoers_context *ctx, struct passwd *pw, } } + /* + * Clear SUDO_PROMPT before authentication to avoid stale values + * from previous PAM transactions. + */ + rc = pam_putenv(pamh, "SUDO_PROMPT"); + if (rc != PAM_SUCCESS && rc != PAM_BAD_ITEM) { + const char *errstr = sudo_pam_strerror(pamh, rc); + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "pam_putenv(pamh, SUDO_PROMPT): %s", errstr); + debug_return_int(AUTH_ERROR); + } + + if (def_prompt && ctx->user.prompt) { + char *sudo_prompt = NULL; + + if (asprintf(&sudo_prompt, "SUDO_PROMPT=%s", def_prompt) != -1) { + rc = pam_putenv(pamh, sudo_prompt); + free(sudo_prompt); + + if (rc != PAM_SUCCESS) { + const char *errstr = sudo_pam_strerror(pamh, rc); + sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO, + "pam_putenv(pamh, SUDO_PROMPT, %s): %s", + def_prompt, errstr); + } + } else { + const char *errstr = strerror(errno); + sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO, + "unable to set SUDO_PROMPT: %s", errstr); + } + } + /* PAM_SILENT prevents the authentication service from generating output. */ *pam_status = pam_authenticate(pamh, def_pam_silent ? PAM_SILENT : 0); /* Restore def_prompt, the passed-in prompt may be freed later. */ def_prompt = PASSPROMPT; + /* Clear the SUDO_PROMPT PAM environment variable. */ + rc = pam_putenv(pamh, "SUDO_PROMPT"); + if (rc != PAM_SUCCESS) { + const char *errstr = sudo_pam_strerror(pamh, rc); + sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO, + "pam_putenv(pamh, SUDO_PROMPT): %s", errstr); + } + /* Restore KRB5CCNAME to its original value. */ if (envccname == NULL && sudo_unsetenv("KRB5CCNAME") != 0) { sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,