Subject: suid helper to read own passwd entry
To: None <tech-userlevel@netbsd.org>
From: Matthias Drochner <M.Drochner@fz-juelich.de>
List: tech-userlevel
Date: 12/01/2006 15:47:36
This is a multipart MIME message.

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


It is increasingly annoying that non-root applications cannot
use PAM to authenticate against a local master.passwd file.
gtk2 now completely refuses to run as SUID program, this
means that eg the gnome screensaver can't use PAM at all.
(And the suid bit on large applications like xscreensaver
is a potential risk.)

I've looked how to fix this as simple and non-invasive
as possible, and found that an nss module which uses
a setuid helper to fetch the passwd entry might be the
right thing to do.
It could get into pkgsrc so that also NetBSD-3 users
can use it, and I'd make the screensavers optionally
depend on it.

The appended code works for me; I've tested with pam-
enabled gnome-screensaver and non-suid xscreensaver.

What do you think? It there any potential for abuse?

best regards
Matthias



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

#include <sys/param.h>
#include <pwd.h>
#include <nsswitch.h>
#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>

#include <stdio.h>

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

static int getpwnam_withhelper(void *, void *, va_list);
static int getpwnam_r_withhelper(void *, void *, va_list);

static struct passwd rpw;
static char pwbuf[1024];

static ns_mtab methods[] = {
	{ NSDB_PASSWD, "getpwnam", getpwnam_withhelper, 0 },
	{ NSDB_PASSWD, "getpwnam_r", getpwnam_r_withhelper, 0 },
};

static int
askhelper(const char *name, struct passwd *pw, char *buf, size_t buflen)
{
	int fd[2];
	pid_t pid, rpid;
	int res, s;
	char *bufptr;

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

	pid = fork();
	if (pid == -1)
		return (errno);
	if (pid == 0) {
		close(1);
		dup2(fd[1], 1);
		execl(PATH_HELPER, PATH_HELPER, name, NULL);
		_exit(errno);
	}
	close(fd[1]);

	bufptr = buf;
	do {
		res = read(fd[0], bufptr, buflen);
		if (res < 0)
			return (errno);
		bufptr += res;
		buflen -= res;
	} while (res > 0 && buflen > 0);

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

	if (buflen == 0)
		return (ENOMEM);

	memcpy(pw, buf, sizeof(struct passwd));
	bufptr = buf + sizeof(struct passwd);

#define rdstr(f) \
	if (pw->f) { \
		pw->f = bufptr; \
		bufptr += strlen(bufptr) + 1; \
	}
	rdstr(pw_name);
	rdstr(pw_passwd);
	rdstr(pw_class);
	rdstr(pw_gecos);
	rdstr(pw_dir);
	rdstr(pw_shell);

	return (0);
}

static int
getpwnam_r_withhelper(void *rv, void *cb_data, va_list ap)
{
	int *retval = va_arg(ap, int *);
	const char *name = va_arg(ap, const char *);
	struct passwd *pw = va_arg(ap, struct passwd *);
	char *buf = va_arg(ap, char *);
	size_t buflen = va_arg(ap, size_t);
	struct passwd **result = va_arg(ap, struct passwd **);

	*result = 0;

	/* avoid recursion */
	if (geteuid() == 0) {
		*retval = 1;
		return (NS_UNAVAIL);
	}

	if (askhelper(name, pw, buf, buflen) != 0) {
		*retval = 1;
		return (NS_NOTFOUND);
	}

	*result = pw;
	*retval = 0;
	return (NS_SUCCESS);
}

static int
getpwnam_withhelper(void *rv, void *cb_data, va_list ap)
{
	struct passwd **retval = va_arg(ap, struct passwd **);
	const char *name = va_arg(ap, const char *);

	/* avoid recursion */
	if (geteuid() == 0) {
		*retval = 0;
		return (NS_UNAVAIL);
	}

	if (askhelper(name, &rpw, pwbuf, sizeof(pwbuf)) != 0) {
		*retval = 0;
		return (NS_NOTFOUND);
	}

	*retval = &rpw;
	return (NS_SUCCESS);
}

ns_mtab *
nss_module_register(const char *source, unsigned int *mtabsize,
		    nss_module_unregister_fn *unreg)
{
	*mtabsize = sizeof(methods)/sizeof(methods[0]);
	*unreg = NULL;

	return (methods);
}

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

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

int
main(int argc, char **argv)
{
	struct passwd *pwent;
	int res;

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

	pwent = getpwnam(argv[1]);
	if (!pwent)
		return (ENOENT);

	if (getuid() != pwent->pw_uid)
		return (EINVAL);

	res = write(1, pwent, sizeof(struct passwd));
	if (res < 0 || res != sizeof(struct passwd))
		return (EIO);

#define wrstr(f) \
	if (pwent->f) { \
		res = write(1, pwent->f, strlen(pwent->f) + 1); \
		if (res < 0) \
			return (EIO); \
	}
	wrstr(pw_name);
	wrstr(pw_passwd);
	wrstr(pw_class);
	wrstr(pw_gecos);
	wrstr(pw_dir);
	wrstr(pw_shell);

	return (0);
}

--==_Exmh_132966441140--