Commit message: Fix buffer size checking

Yes, this means we've re-introduced CVE-2005-3534. Sigh.
CVE: CVE-2011-0530
CWE: CWE-119
Vulnerability description: Buffer overflow in the mainloop function in nbd-server.c in the server in Network Block Device (nbd) before 2.9.20 might allow remote attackers to execute arbitrary code via a long request.  NOTE: this issue exists because of a CVE-2005-3534 regression.

Project: nbd
File: nbd-server.c

void dousers(void) {
	struct passwd *pw;
	struct group *gr;
	gchar* str;
	if(rungroup) {
		gr=getgrnam(rungroup);
		if(!gr) {
			str = g_strdup_printf("Invalid group name: %s", rungroup);
			err(str);
		}
		if(setgid(gr->gr_gid)<0) {
			err("Could not set GID: %m"); 
		}
	}
	if(runuser) {
		pw=getpwnam(runuser);
		if(!pw) {
			str = g_strdup_printf("Invalid user name: %s", runuser);
			err(str);
		}
		if(setuid(pw->pw_uid)<0) {
			err("Could not set UID: %m");
		}
	}
}

Project: nbd
File: nbd-server.c

SERVER* dup_serve(SERVER *s) {
	SERVER *serve = NULL;

	serve=g_new0(SERVER, 1);
	if(serve == NULL)
		return NULL;

	if(s->exportname)
		serve->exportname = g_strdup(s->exportname);

	serve->expected_size = s->expected_size;

	if(s->listenaddr)
		serve->listenaddr = g_strdup(s->listenaddr);

	serve->port = s->port;

	if(s->authname)
		serve->authname = strdup(s->authname);

	serve->flags = s->flags;
	serve->socket = serve->socket;
	serve->socket_family = serve->socket_family;
	serve->cidrlen = s->cidrlen;

	if(s->prerun)
		serve->prerun = g_strdup(s->prerun);

	if(s->postrun)
		serve->postrun = g_strdup(s->postrun);
	
	if(s->servename)
		serve->servename = g_strdup(s->servename);

	serve->max_connections = s->max_connections;

	return serve;
}

Project: nbd
File: nbd-server.c

GArray* parse_cfile(gchar* f, GError** e) {
	const char* DEFAULT_ERROR = "Could not parse %s in group %s: %s";
	const char* MISSING_REQUIRED_ERROR = "Could not find required value %s in group %s: %s";
	SERVER s;
	gchar *virtstyle=NULL;
	PARAM lp[] = {
		{ "exportname", TRUE,	PARAM_STRING, 	NULL, 0 },
		{ "port", 	TRUE,	PARAM_INT, 	NULL, 0 },
		{ "authfile",	FALSE,	PARAM_STRING,	NULL, 0 },
		{ "filesize",	FALSE,	PARAM_INT,	NULL, 0 },
		{ "virtstyle",	FALSE,	PARAM_STRING,	NULL, 0 },
		{ "prerun",	FALSE,	PARAM_STRING,	NULL, 0 },
		{ "postrun",	FALSE,	PARAM_STRING,	NULL, 0 },
		{ "readonly",	FALSE,	PARAM_BOOL,	NULL, F_READONLY },
		{ "multifile",	FALSE,	PARAM_BOOL,	NULL, F_MULTIFILE },
		{ "copyonwrite", FALSE,	PARAM_BOOL,	NULL, F_COPYONWRITE },
		{ "sparse_cow",	FALSE,	PARAM_BOOL,	NULL, F_SPARSE },
		{ "sdp",	FALSE,	PARAM_BOOL,	NULL, F_SDP },
		{ "sync",	FALSE,  PARAM_BOOL,	NULL, F_SYNC },
		{ "listenaddr", FALSE,  PARAM_STRING,   NULL, 0 },
		{ "maxconnections", FALSE, PARAM_INT,	NULL, 0 },
	};
	const int lp_size=sizeof(lp)/sizeof(PARAM);
	PARAM gp[] = {
		{ "user",	FALSE, PARAM_STRING,	&runuser,	0 },
		{ "group",	FALSE, PARAM_STRING,	&rungroup,	0 },
		{ "oldstyle",	FALSE, PARAM_BOOL,	&do_oldstyle,	1 },
		{ "listenaddr", FALSE, PARAM_STRING,	&modern_listen, 0 },
	};
	PARAM* p=gp;
	int p_size=sizeof(gp)/sizeof(PARAM);
	GKeyFile *cfile;
	GError *err = NULL;
	const char *err_msg=NULL;
	GQuark errdomain;
	GArray *retval=NULL;
	gchar **groups;
	gboolean value;
	gchar* startgroup;
	gint i;
	gint j;

	errdomain = g_quark_from_string("parse_cfile");
	cfile = g_key_file_new();
	retval = g_array_new(FALSE, TRUE, sizeof(SERVER));
	if(!g_key_file_load_from_file(cfile, f, G_KEY_FILE_KEEP_COMMENTS |
			G_KEY_FILE_KEEP_TRANSLATIONS, &err)) {
		g_set_error(e, errdomain, CFILE_NOTFOUND, "Could not open config file %s.", f);
		g_key_file_free(cfile);
		return retval;
	}
	startgroup = g_key_file_get_start_group(cfile);
	if(!startgroup || strcmp(startgroup, "generic")) {
		g_set_error(e, errdomain, CFILE_MISSING_GENERIC, "Config file does not contain the [generic] group!");
		g_key_file_free(cfile);
		return NULL;
	}
	groups = g_key_file_get_groups(cfile, NULL);
	for(i=0;groups[i];i++) {
		memset(&s, '\0', sizeof(SERVER));
		lp[0].target=&(s.exportname);
		lp[1].target=&(s.port);
		lp[2].target=&(s.authname);
		lp[3].target=&(s.expected_size);
		lp[4].target=&(virtstyle);
		lp[5].target=&(s.prerun);
		lp[6].target=&(s.postrun);
		lp[7].target=lp[8].target=lp[9].target=
				lp[10].target=lp[11].target=
				lp[12].target=&(s.flags);
		lp[13].target=&(s.listenaddr);
		lp[14].target=&(s.max_connections);

		/* After the [generic] group, start parsing exports */
		if(i==1) {
			p=lp;
			p_size=lp_size;
		} 
		for(j=0;j<p_size;j++) {
			g_assert(p[j].target != NULL);
			g_assert(p[j].ptype==PARAM_INT||p[j].ptype==PARAM_STRING||p[j].ptype==PARAM_BOOL);
			switch(p[j].ptype) {
				case PARAM_INT:
					*((gint*)p[j].target) =
						g_key_file_get_integer(cfile,
								groups[i],
								p[j].paramname,
								&err);
					break;
				case PARAM_STRING:
					*((gchar**)p[j].target) =
						g_key_file_get_string(cfile,
								groups[i],
								p[j].paramname,
								&err);
					break;
				case PARAM_BOOL:
					value = g_key_file_get_boolean(cfile,
							groups[i],
							p[j].paramname, &err);
					if(!err) {
						if(value) {
							*((gint*)p[j].target) |= p[j].flagval;
						} else {
							*((gint*)p[j].target) &= ~(p[j].flagval);
						}
					}
					break;
			}
			if(!strcmp(p[j].paramname, "port") && !strcmp(p[j].target, NBD_DEFAULT_PORT)) {
				g_set_error(e, errdomain, CFILE_INCORRECT_PORT, "Config file specifies default port for oldstyle export");
				g_key_file_free(cfile);
				return NULL;
			}
			if(err) {
				if(err->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND) {
					if(!p[j].required) {
						/* Ignore not-found error for optional values */
						g_clear_error(&err);
						continue;
					} else {
						err_msg = MISSING_REQUIRED_ERROR;
					}
				} else {
					err_msg = DEFAULT_ERROR;
				}
				g_set_error(e, errdomain, CFILE_VALUE_INVALID, err_msg, p[j].paramname, groups[i], err->message);
				g_array_free(retval, TRUE);
				g_error_free(err);
				g_key_file_free(cfile);
				return NULL;
			}
		}
		if(virtstyle) {
			if(!strncmp(virtstyle, "none", 4)) {
				s.virtstyle=VIRT_NONE;
			} else if(!strncmp(virtstyle, "ipliteral", 9)) {
				s.virtstyle=VIRT_IPLIT;
			} else if(!strncmp(virtstyle, "iphash", 6)) {
				s.virtstyle=VIRT_IPHASH;
			} else if(!strncmp(virtstyle, "cidrhash", 8)) {
				s.virtstyle=VIRT_CIDR;
				if(strlen(virtstyle)<10) {
					g_set_error(e, errdomain, CFILE_VALUE_INVALID, "Invalid value %s for parameter virtstyle in group %s: missing length", virtstyle, groups[i]);
					g_array_free(retval, TRUE);
					g_key_file_free(cfile);
					return NULL;
				}
				s.cidrlen=strtol(virtstyle+8, NULL, 0);
			} else {
				g_set_error(e, errdomain, CFILE_VALUE_INVALID, "Invalid value %s for parameter virtstyle in group %s", virtstyle, groups[i]);
				g_array_free(retval, TRUE);
				g_key_file_free(cfile);
				return NULL;
			}
			if(s.port && !do_oldstyle) {
				g_warning("A port was specified, but oldstyle exports were not requested. This may not do what you expect.");
				g_warning("Please read 'man 5 nbd-server' and search for oldstyle for more info");
			}
		} else {
			s.virtstyle=VIRT_IPLIT;
		}
		/* Don't need to free this, it's not our string */
		virtstyle=NULL;
		/* Don't append values for the [generic] group */
		if(i>0) {
			s.socket_family = AF_UNSPEC;
			s.servename = groups[i];

			append_serve(&s, retval);
		} else {
			if(!do_oldstyle) {
				lp[1].required = 0;
			}
		}
#ifndef WITH_SDP
		if(s.flags & F_SDP) {
			g_set_error(e, errdomain, CFILE_VALUE_UNSUPPORTED, "This nbd-server was built without support for SDP, yet group %s uses it", groups[i]);
			g_array_free(retval, TRUE);
			g_key_file_free(cfile);
			return NULL;
		}
#endif
	}
	if(i==1) {
		g_set_error(e, errdomain, CFILE_NO_EXPORTS, "The config file does not specify any exports");
	}
	g_key_file_free(cfile);
	return retval;
}

Project: nbd
File: nbd-server.c

ssize_t rawexpread(off_t a, char *buf, size_t len, CLIENT *client) {
	int fhandle;
	off_t foffset;
	size_t maxbytes;

	if(get_filepos(client->export, a, &fhandle, &foffset, &maxbytes))
		return -1;
	if(maxbytes && len > maxbytes)
		len = maxbytes;

	DEBUG4("(READ from fd %d offset %llu len %u), ", fhandle, foffset, len);

	myseek(fhandle, foffset);
	return read(fhandle, buf, len);
}

Project: nbd
File: nbd-server.c

int serveloop(GArray* servers) {
	struct sockaddr_storage addrin;
	socklen_t addrinlen=sizeof(addrin);
	int i;
	int max;
	int sock;
	fd_set mset;
	fd_set rset;

	/* 
	 * Set up the master fd_set. The set of descriptors we need
	 * to select() for never changes anyway and it buys us a *lot*
	 * of time to only build this once. However, if we ever choose
	 * to not fork() for clients anymore, we may have to revisit
	 * this.
	 */
	max=0;
	FD_ZERO(&mset);
	for(i=0;i<servers->len;i++) {
		if((sock=(g_array_index(servers, SERVER, i)).socket)) {
			FD_SET(sock, &mset);
			max=sock>max?sock:max;
		}
	}
	if(modernsock) {
		FD_SET(modernsock, &mset);
		max=modernsock>max?modernsock:max;
	}
	for(;;) {
		CLIENT *client = NULL;
		pid_t *pid;

		memcpy(&rset, &mset, sizeof(fd_set));
		if(select(max+1, &rset, NULL, NULL, NULL)>0) {
			int net = 0;
			SERVER* serve;

			DEBUG("accept, ");
			if(FD_ISSET(modernsock, &rset)) {
				if((net=accept(modernsock, (struct sockaddr *) &addrin, &addrinlen)) < 0)
					err("accept: %m");
				client = negotiate(net, NULL, servers);
				if(!client) {
					err_nonfatal("negotiation failed");
					close(net);
					net=0;
				}
			}
			for(i=0;i<servers->len && !net;i++) {
				serve=&(g_array_index(servers, SERVER, i));
				if(FD_ISSET(serve->socket, &rset)) {
					if ((net=accept(serve->socket, (struct sockaddr *) &addrin, &addrinlen)) < 0)
						err("accept: %m");
				}
			}
			if(net) {
				int sock_flags;

				if(serve->max_connections > 0 &&
				   g_hash_table_size(children) >= serve->max_connections) {
					msg2(LOG_INFO, "Max connections reached");
					close(net);
					continue;
				}
				if((sock_flags = fcntl(net, F_GETFL, 0))==-1) {
					err("fcntl F_GETFL");
				}
				if(fcntl(net, F_SETFL, sock_flags &~O_NONBLOCK)==-1) {
					err("fcntl F_SETFL ~O_NONBLOCK");
				}
				if(!client) {
					client = g_new0(CLIENT, 1);
					client->server=serve;
					client->exportsize=OFFT_MAX;
					client->net=net;
				}
				set_peername(net, client);
				if (!authorized_client(client)) {
					msg2(LOG_INFO,"Unauthorized client") ;
					close(net);
					continue;
				}
				msg2(LOG_INFO,"Authorized client") ;
				pid=g_malloc(sizeof(pid_t));
#ifndef NOFORK
				if ((*pid=fork())<0) {
					msg3(LOG_INFO,"Could not fork (%s)",strerror(errno)) ;
					close(net);
					continue;
				}
				if (*pid>0) { /* parent */
					close(net);
					g_hash_table_insert(children, pid, pid);
					continue;
				}
				/* child */
				g_hash_table_destroy(children);
				for(i=0;i<servers->len;i++) {
					serve=&g_array_index(servers, SERVER, i);
					close(serve->socket);
				}
				/* FALSE does not free the
				actual data. This is required,
				because the client has a
				direct reference into that
				data, and otherwise we get a
				segfault... */
				g_array_free(servers, FALSE);
#endif // NOFORK
				msg2(LOG_INFO,"Starting to serve");
				serveconnection(client);
				exit(EXIT_SUCCESS);
			}
		}
	}
}