Files
Pierre Warnier 9bd1cec3d3 shadow-core: give the tool its own environment, not the caller's (#323)
harden_process() left the process environment as the caller exported
it. The PAM stack, the NSS modules and the crypt library run inside the
tool and read the environment themselves, so they read the caller's;
only children were given a clean one. Every tool now replaces its
environment first thing, while still single-threaded.

What stays is what has a known reader here: TERM and NO_COLOR for how
prompts are drawn; LANG, LANGUAGE and LC_* for the language of messages,
PAM's included; VISUAL and EDITOR for vipw; and TZ, so chage -l prints
dates in the caller's zone, but only when it names a zone -- glibc also
takes a path there and reads that file with the process's privilege, so
such a value is dropped. PATH is fixed. The rest is gone before the
first line of the tool runs. The allow-list is a pure function with
unit tests, and a further test re-runs the test binary as a child,
calls harden_process there and reads the environment back.

The deployment suite proves the claim where it matters. A small PAM
module, tests/e2e/pam_envprobe.c, built into the e2e image, runs inside
a real password change and writes out the environment of the process
it runs in: a variable the caller exported is not there, and PATH is
the fixed one. pam_exec was tried first and cannot do this job: it
gives its child the PAM environment and the PAM items, never the
calling process's, so a probe run through it saw nothing whether or not
the tool had cleaned up. Checked against main's binary with the module:
there the caller's variable and PATH both reach the PAM stack.

Closes #314.
2026-09-21 16:32:53 +02:00

41 lines
1.3 KiB
C

/* A PAM module for the deployment suite, and nothing else.
*
* It writes the environment of the process the PAM stack runs in to
* /tmp/pam-envprobe.out and reports PAM_IGNORE, so it changes no outcome.
* pam_exec cannot do this job: it hands its child the PAM environment and the
* PAM items, never the calling process's environment, so a probe run through
* it sees nothing whether or not the tool cleaned up after the caller. This
* module runs inside the tool, and `environ` is the tool's.
*/
#include <security/pam_modules.h>
#include <stdio.h>
extern char **environ;
static int dump(void) {
FILE *f = fopen("/tmp/pam-envprobe.out", "w");
if (f == NULL) {
return PAM_IGNORE;
}
for (char **e = environ; *e != NULL; e++) {
fprintf(f, "%s\n", *e);
}
fclose(f);
return PAM_IGNORE;
}
int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv) {
(void)pamh; (void)flags; (void)argc; (void)argv;
return dump();
}
int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) {
(void)pamh; (void)flags; (void)argc; (void)argv;
return dump();
}
int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) {
(void)pamh; (void)flags; (void)argc; (void)argv;
return PAM_IGNORE;
}