Subject: Re: suid helper to verify own passwd
To: None <tech-security@netbsd.org>
From: Matthias Drochner <M.Drochner@fz-juelich.de>
List: tech-security
Date: 12/22/2006 18:56:26
This is a multipart MIME message.

--==_Exmh_30038648821100
Content-Type: text/plain; charset=us-ascii


Thanks to all who commented. I've applied all the undisputed
changes and added some comments. Here is the current
state of affairs.

have nice xmas etc
Matthias

(will be AFK until 2nd or 3rd of Jan)



--==_Exmh_30038648821100
Content-Type: text/plain ; name="pam_passwdhelper.c"; charset=us-ascii
Content-Description: pam_passwdhelper.c
Content-Disposition: attachment; filename="pam_passwdhelper.c"

/* $NetBSD$ */

#include <sys/types.h>
#include <security/pam_appl.h>
#include <security/pam_modules.h>

#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <errno.h>

#define PATH_HELPER "/home/drochner/pam_passwd_suid/helper/helper"

static int
askhelper(const char *user, const char *pass)
{
	int fd[2];
	pid_t pid, rpid;
	ssize_t res;
	size_t pwlen;
	int s;

	if (pipe(fd) < 0)
		return errno;

	pid = vfork();
	switch (pid) {
		case -1:
			return errno;
		case 0: /* child, feed it through its stdin */
			(void)dup2(fd[0], STDIN_FILENO);
			(void)close(fd[0]);
			(void)close(fd[1]);
			execl(PATH_HELPER, PATH_HELPER, user, NULL);
			_exit(errno);
		default: /* parent */
			(void)close(fd[0]);
			break;
	}

	pwlen = strlen(pass);
	res = write(fd[1], pass, pwlen);
	if (res != pwlen)
		return (res == -1 ? errno : EIO);

	(void)close(fd[1]); /* now child gets an EOF */

	rpid = waitpid(pid, &s, 0);
	if (rpid != pid)
		return errno;
	if (!WIFEXITED(s) || WEXITSTATUS(s))
		return EAUTH;

	return 0;
}

PAM_EXTERN int
pam_sm_authenticate(pam_handle_t *pamh, int flags,
		    int argc, const char **argv)
{
	const char *user, *pass;
	int res;

	res = pam_get_user(pamh, &user, NULL);
	if (res != PAM_SUCCESS)
		return res;
	res = pam_get_authtok(pamh, PAM_AUTHTOK, &pass, NULL);
	if (res != PAM_SUCCESS)
		return res;

	if (askhelper(user, pass) != 0)
		return PAM_AUTH_ERR;

	return PAM_SUCCESS;
}

PAM_MODULE_ENTRY("pam_passwdhelper");

--==_Exmh_30038648821100
Content-Type: text/plain ; name="helper.c"; charset=us-ascii
Content-Description: helper.c
Content-Disposition: attachment; filename="helper.c"

/* $NetBSD$ */

#include <pwd.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

static char pwbuf[1024];

int
main(int argc, char **argv)
{
	struct passwd *pwent;
	ssize_t res;
	char *bufptr, *pwhash;
	size_t buflen;

	if (argc != 2)
		return (EINVAL);

	bufptr = pwbuf;
	buflen = sizeof(pwbuf);
	do {
		res = read(STDIN_FILENO, bufptr, buflen);
		if (res < 0)
			return (errno);
		bufptr += res;
		buflen -= res;
	} while (res > 0 && buflen > 0);
	if (buflen == 0)
		return (ENOMEM);
	/* pwbuf is \0-terminated here b/c pwbuf is in bss */

	/*
	 * Use username as key rather than uid so that it will not
	 * fail completely if multiple pw entries share a uid.
	 * Return same result in "not me" and "doesn't exist" cases
	 * to avoid leak of account information.
	 */
	pwent = getpwnam(argv[1]);
	if (!pwent || (pwent->pw_uid != getuid()))
		return (EPERM);

	/*
	 * Forcibly eat up some wall time to prevent use of this program
	 * to brute-force? For now assume that process startup time etc.
	 * make it already ineffective.
	 */
	pwhash = crypt(pwbuf, pwent->pw_passwd);
	if (pwhash && strcmp(pwhash, pwent->pw_passwd) == 0)
		return (0);

	return (EAUTH);
}

--==_Exmh_30038648821100--