diff options
| -rw-r--r-- | src/passwd/getgr_a.c | 140 | ||||
| -rw-r--r-- | src/passwd/getpw_a.c | 114 | ||||
| -rw-r--r-- | src/passwd/nscd.h | 38 | ||||
| -rw-r--r-- | src/passwd/nscd_query.c | 100 | 
4 files changed, 390 insertions, 2 deletions
| diff --git a/src/passwd/getgr_a.c b/src/passwd/getgr_a.c index 805e28c9..7738c3c3 100644 --- a/src/passwd/getgr_a.c +++ b/src/passwd/getgr_a.c @@ -1,5 +1,21 @@  #include <pthread.h> +#include <byteswap.h> +#include <string.h> +#include <unistd.h>  #include "pwf.h" +#include "nscd.h" + +static char *itoa(char *p, uint32_t x) +{ +	// number of digits in a uint32_t + NUL +	p += 11; +	*--p = 0; +	do { +		*--p = '0' + x % 10; +		x /= 10; +	} while (x); +	return p; +}  int __getgr_a(const char *name, gid_t gid, struct group *gr, char **buf, size_t *size, char ***mem, size_t *nmem, struct group **res)  { @@ -10,7 +26,6 @@ int __getgr_a(const char *name, gid_t gid, struct group *gr, char **buf, size_t  	*res = 0;  	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs); -  	f = fopen("/etc/group", "rbe");  	if (!f) {  		rv = errno; @@ -25,6 +40,129 @@ int __getgr_a(const char *name, gid_t gid, struct group *gr, char **buf, size_t  	}  	fclose(f); +	if (!*res && (rv == 0 || rv == ENOENT || rv == ENOTDIR)) { +		int32_t req = name ? GETGRBYNAME : GETGRBYGID; +		int32_t i; +		const char *key; +		int32_t groupbuf[GR_LEN] = {0}; +		size_t len = 0; +		size_t grlist_len = 0; +		char gidbuf[11] = {0}; +		int swap = 0; +		char *ptr; + +		if (name) { +			key = name; +		} else { +			if (gid < 0 || gid > UINT32_MAX) { +				rv = 0; +				goto done; +			} +			key = itoa(gidbuf, gid); +		} + +		f = __nscd_query(req, key, groupbuf, sizeof groupbuf, &swap); +		if (!f) { rv = errno; goto done; } +		if (f == (FILE*)-1) { rv = 0; goto done; } + +		if (!groupbuf[GRFOUND]) { rv = 0; goto cleanup_f; } + +		if (!groupbuf[GRNAMELEN] || !groupbuf[GRPASSWDLEN]) { +			rv = EIO; +			goto cleanup_f; +		} + +		if (groupbuf[GRNAMELEN] > SIZE_MAX - groupbuf[GRPASSWDLEN]) { +			rv = ENOMEM; +			goto cleanup_f; +		} +		len = groupbuf[GRNAMELEN] + groupbuf[GRPASSWDLEN]; + +		for (i = 0; i < groupbuf[GRMEMCNT]; i++) { +			uint32_t name_len; +			if (fread(&name_len, sizeof name_len, 1, f) < 1) { +				rv = ferror(f) ? errno : EIO; +				goto cleanup_f; +			} +			if (swap) { +				name_len = bswap_32(name_len); +			} +			if (name_len > SIZE_MAX - grlist_len +			|| name_len > SIZE_MAX - len) { +				rv = ENOMEM; +				goto cleanup_f; +			} +			len += name_len; +			grlist_len += name_len; +		} + +		if (len > *size || !*buf) { +			char *tmp = realloc(*buf, len); +			if (!tmp) { +				rv = errno; +				goto cleanup_f; +			} +			*buf = tmp; +			*size = len; +		} + +		if (!fread(*buf, len, 1, f)) { +			rv = ferror(f) ? errno : EIO; +			goto cleanup_f; +		} + +		if (groupbuf[GRMEMCNT] + 1 > *nmem) { +			if (groupbuf[GRMEMCNT] + 1 > SIZE_MAX/sizeof(char*)) { +				rv = ENOMEM; +				goto cleanup_f; +			} +			char **tmp = realloc(*mem, (groupbuf[GRMEMCNT]+1)*sizeof(char*)); +			if (!tmp) { +				rv = errno; +				goto cleanup_f; +			} +			*mem = tmp; +			*nmem = groupbuf[GRMEMCNT] + 1; +		} + +		if (groupbuf[GRMEMCNT]) { +			mem[0][0] = *buf + groupbuf[GRNAMELEN] + groupbuf[GRPASSWDLEN]; +			for (ptr = mem[0][0], i = 0; ptr != mem[0][0]+grlist_len; ptr++) +				if (!*ptr) mem[0][++i] = ptr+1; +			mem[0][i] = 0; + +			if (i != groupbuf[GRMEMCNT]) { +				rv = EIO; +				goto cleanup_f; +			} +		} else { +			mem[0][0] = 0; +		} + +		gr->gr_name = *buf; +		gr->gr_passwd = gr->gr_name + groupbuf[GRNAMELEN]; +		gr->gr_gid = groupbuf[GRGID]; +		gr->gr_mem = *mem; + +		if (gr->gr_passwd[-1] +		|| gr->gr_passwd[groupbuf[GRPASSWDLEN]-1]) { +			rv = EIO; +			goto cleanup_f; +		} + +		if (name && strcmp(name, gr->gr_name) +		|| !name && gid != gr->gr_gid) { +			rv = EIO; +			goto cleanup_f; +		} + +		*res = gr; + +cleanup_f: +		fclose(f); +		goto done; +	} +  done:  	pthread_setcancelstate(cs, 0);  	if (rv) errno = rv; diff --git a/src/passwd/getpw_a.c b/src/passwd/getpw_a.c index 21efc5ca..b04663dd 100644 --- a/src/passwd/getpw_a.c +++ b/src/passwd/getpw_a.c @@ -1,5 +1,21 @@ -#include "pwf.h"  #include <pthread.h> +#include <byteswap.h> +#include <string.h> +#include <unistd.h> +#include "pwf.h" +#include "nscd.h" + +static char *itoa(char *p, uint32_t x) +{ +	// number of digits in a uint32_t + NUL +	p += 11; +	*--p = 0; +	do { +		*--p = '0' + x % 10; +		x /= 10; +	} while (x); +	return p; +}  int __getpw_a(const char *name, uid_t uid, struct passwd *pw, char **buf, size_t *size, struct passwd **res)  { @@ -24,6 +40,102 @@ int __getpw_a(const char *name, uid_t uid, struct passwd *pw, char **buf, size_t  	}  	fclose(f); +	if (!*res && (rv == 0 || rv == ENOENT || rv == ENOTDIR)) { +		int32_t req = name ? GETPWBYNAME : GETPWBYUID; +		const char *key; +		int32_t passwdbuf[PW_LEN] = {0}; +		size_t len = 0; +		char uidbuf[11] = {0}; + +		if (name) { +			key = name; +		} else { +			/* uid outside of this range can't be queried with the +			 * nscd interface, but might happen if uid_t ever +			 * happens to be a larger type (this is not true as of +			 * now) +			 */ +			if(uid < 0 || uid > UINT32_MAX) { +				rv = 0; +				goto done; +			} +			key = itoa(uidbuf, uid); +		} + +		f = __nscd_query(req, key, passwdbuf, sizeof passwdbuf, (int[]){0}); +		if (!f) { rv = errno; goto done; } +		if (f == (FILE*)-1) { rv = 0; goto done; } + +		if(!passwdbuf[PWFOUND]) { rv = 0; goto cleanup_f; } + +		/* A zero length response from nscd is invalid. We ignore +		 * invalid responses and just report an error, rather than +		 * trying to do something with them. +		 */ +		if (!passwdbuf[PWNAMELEN] || !passwdbuf[PWPASSWDLEN] +		|| !passwdbuf[PWGECOSLEN] || !passwdbuf[PWDIRLEN] +		|| !passwdbuf[PWSHELLLEN]) { +			rv = EIO; +			goto cleanup_f; +		} + +		if ((passwdbuf[PWNAMELEN]|passwdbuf[PWPASSWDLEN] +		     |passwdbuf[PWGECOSLEN]|passwdbuf[PWDIRLEN] +		     |passwdbuf[PWSHELLLEN]) >= SIZE_MAX/8) { +			rv = ENOMEM; +			goto cleanup_f; +		} + +		len = passwdbuf[PWNAMELEN] + passwdbuf[PWPASSWDLEN] +		    + passwdbuf[PWGECOSLEN] + passwdbuf[PWDIRLEN] +		    + passwdbuf[PWSHELLLEN]; + +		if (len > *size || !*buf) { +			char *tmp = realloc(*buf, len); +			if (!tmp) { +				rv = errno; +				goto cleanup_f; +			} +			*buf = tmp; +			*size = len; +		} + +		if (!fread(*buf, len, 1, f)) { +			rv = ferror(f) ? errno : EIO; +			goto cleanup_f; +		} + +		pw->pw_name = *buf; +		pw->pw_passwd = pw->pw_name + passwdbuf[PWNAMELEN]; +		pw->pw_gecos = pw->pw_passwd + passwdbuf[PWPASSWDLEN]; +		pw->pw_dir = pw->pw_gecos + passwdbuf[PWGECOSLEN]; +		pw->pw_shell = pw->pw_dir + passwdbuf[PWDIRLEN]; +		pw->pw_uid = passwdbuf[PWUID]; +		pw->pw_gid = passwdbuf[PWGID]; + +		/* Don't assume that nscd made sure to null terminate strings. +		 * It's supposed to, but malicious nscd should be ignored +		 * rather than causing a crash. +		 */ +		if (pw->pw_passwd[-1] || pw->pw_gecos[-1] || pw->pw_dir[-1] +		|| pw->pw_shell[passwdbuf[PWSHELLLEN]-1]) { +			rv = EIO; +			goto cleanup_f; +		} + +		if (name && strcmp(name, pw->pw_name) +		|| !name && uid != pw->pw_uid) { +			rv = EIO; +			goto cleanup_f; +		} + + +		*res = pw; +cleanup_f: +		fclose(f); +		goto done; +	} +  done:  	pthread_setcancelstate(cs, 0);  	if (rv) errno = rv; diff --git a/src/passwd/nscd.h b/src/passwd/nscd.h new file mode 100644 index 00000000..102f0b4b --- /dev/null +++ b/src/passwd/nscd.h @@ -0,0 +1,38 @@ +#ifndef NSCD_H +#define NSCD_H + +#include <stdint.h> + +#define NSCDVERSION 2 +#define GETPWBYNAME 0 +#define GETPWBYUID 1 +#define GETGRBYNAME 2 +#define GETGRBYGID 3 + +#define REQVERSION 0 +#define REQTYPE 1 +#define REQKEYLEN 2 +#define REQ_LEN 3 + +#define PWVERSION 0 +#define PWFOUND 1 +#define PWNAMELEN 2 +#define PWPASSWDLEN 3 +#define PWUID 4 +#define PWGID 5 +#define PWGECOSLEN 6 +#define PWDIRLEN 7 +#define PWSHELLLEN 8 +#define PW_LEN 9 + +#define GRVERSION 0 +#define GRFOUND 1 +#define GRNAMELEN 2 +#define GRPASSWDLEN 3 +#define GRGID 4 +#define GRMEMCNT 5 +#define GR_LEN 6 + +FILE *__nscd_query(int32_t req, const char *key, int32_t *buf, size_t len, int *swap); + +#endif diff --git a/src/passwd/nscd_query.c b/src/passwd/nscd_query.c new file mode 100644 index 00000000..f8d0fc13 --- /dev/null +++ b/src/passwd/nscd_query.c @@ -0,0 +1,100 @@ +#include <sys/socket.h> +#include <byteswap.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include "nscd.h" + +static const struct { +	short sun_family; +	char sun_path[21]; +} addr = { +	AF_UNIX, +	"/var/run/nscd/socket" +}; + +FILE *__nscd_query(int32_t req, const char *key, int32_t *buf, size_t len, int *swap) +{ +	size_t i; +	int fd; +	FILE *f = 0; +	int32_t req_buf[REQ_LEN] = { +		NSCDVERSION, +		req, +		strlen(key)+1 +	}; +	struct msghdr msg = { +		.msg_iov = (struct iovec[]){ +			{&req_buf, sizeof(req_buf)}, +			{(char*)key, strlen(key)+1} +		}, +		.msg_iovlen = 2 +	}; + +	if (strlen(key) > INT32_MAX - 1) { +		return (FILE*)-1; +	} + +	*swap = 0; +retry: + +	fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); +	if (fd < 0) return NULL; + +	if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { +		/* If there isn't a running nscd we return -1 to indicate that +		 * that is precisely what happened +		 */ +		if (errno == EACCES || errno == ECONNREFUSED || errno == ENOENT) { +			close(fd); +			return (FILE *)-1; +		} +		goto error; +	} + +	if (sendmsg(fd, &msg, MSG_NOSIGNAL) < 0) +		goto error; + +	if(!(f = fdopen(fd, "r"))) goto error; + +	if (!fread(buf, len, 1, f)) { +		/* If the VERSION entry mismatches nscd will disconnect. The +		 * most likely cause is that the endianness mismatched. So, we +		 * byteswap and try once more. (if we already swapped, just +		 * fail out) +		 */ +		if (ferror(f)) goto error; +		if (!*swap) { +			fclose(f); +			for (i = 0; i < sizeof(req_buf)/sizeof(req_buf[0]); i++) { +				req_buf[i] = bswap_32(req_buf[i]); +			} +			*swap = 1; +			goto retry; +		} else { +			errno = EIO; +			goto error; +		} +	} + +	if (*swap) { +		for (i = 0; i < len/sizeof(buf[0]); i++) { +			buf[i] = bswap_32(buf[i]); +		} +	} + +	/* The first entry in every nscd response is the version number. This +	 * really shouldn't happen, and is evidence of some form of malformed +	 * response. +	 */ +	if(buf[0] != NSCDVERSION) { +		errno = EIO; +		goto error; +	} + +	return f; +error: +	if (f) fclose(f); else close(fd); +	return 0; +} | 
