Commit message: None
CVE: CVE-2019-15937
CWE: CWE-119
Vulnerability description: Pengutronix barebox through 2019.08.1 has a remote buffer overflow in nfs_readlink_reply in net/nfs.c because a length field is directly used for a memcpy.

Project: pengutronix

static void nfs_lookup_req(char *fname)
{
	uint32_t data[1024];
	uint32_t *p;
	int len;
	int fnamelen;

	fnamelen = strlen (fname);

	p = &(data[0]);
	p = rpc_add_credentials(p);

	memcpy (p, dirfh, NFS_FHSIZE);
	p += (NFS_FHSIZE / 4);
	*p++ = htonl(fnamelen);
	if (fnamelen & 3)
		*(p + fnamelen / 4) = 0;
	memcpy (p, fname, fnamelen);
	p += (fnamelen + 3) / 4;

	len = p - &(data[0]);

	rpc_req(PROG_NFS, NFS_LOOKUP, data, len);
}

Project: pengutronix

static int nfs_mount_reply(unsigned char *pkt, unsigned len)
{
	int ret;

	ret = rpc_check_reply(pkt, 1);
	if (ret)
		return ret;

	memcpy(dirfh, pkt + sizeof(struct rpc_reply) + 4, NFS_FHSIZE);

	return 0;
}

Project: pengutronix

static void nfs_mount_req(char *path)
{
	uint32_t data[1024];
	uint32_t *p;
	int len;
	int pathlen;

	pathlen = strlen (path);

	p = &(data[0]);
	p = rpc_add_credentials(p);

	*p++ = htonl(pathlen);
	if (pathlen & 3)
		*(p + pathlen / 4) = 0;
	memcpy (p, path, pathlen);
	p += (pathlen + 3) / 4;

	len = p - &(data[0]);

	rpc_req(PROG_MOUNT, MOUNT_ADDENTRY, data, len);
}

Project: pengutronix

static void nfs_read_req(int offset, int readlen)
{
	uint32_t data[1024];
	uint32_t *p;
	int len;

	p = &(data[0]);
	p = rpc_add_credentials(p);

	memcpy (p, filefh, NFS_FHSIZE);
	p += (NFS_FHSIZE / 4);
	*p++ = htonl(offset);
	*p++ = htonl(readlen);
	*p++ = 0;

	len = p - &(data[0]);

	rpc_req(PROG_NFS, NFS_READ, data, len);
}

Project: pengutronix

static void nfs_readlink_req(void)
{
	uint32_t data[1024];
	uint32_t *p;
	int len;

	p = &(data[0]);
	p = rpc_add_credentials(p);

	memcpy (p, filefh, NFS_FHSIZE);
	p += (NFS_FHSIZE / 4);

	len = p - &(data[0]);

	rpc_req(PROG_NFS, NFS_READLINK, data, len);
}

Project: pengutronix

static void nfs_send(void)
{
	debug("%s\n", __func__);

	switch (nfs_state) {
	case STATE_PRCLOOKUP_PROG_MOUNT_REQ:
		rpc_lookup_req(PROG_MOUNT, 1);
		break;
	case STATE_PRCLOOKUP_PROG_NFS_REQ:
		rpc_lookup_req(PROG_NFS, 2);
		break;
	case STATE_MOUNT_REQ:
		nfs_mount_req(nfs_path);
		break;
	case STATE_UMOUNT_REQ:
		nfs_umountall_req();
		break;
	case STATE_LOOKUP_REQ:
		nfs_lookup_req(nfs_filename);
		break;
	case STATE_READ_REQ:
		nfs_read_req(nfs_offset, NFS_READ_SIZE);
		break;
	case STATE_READLINK_REQ:
		nfs_readlink_req();
		break;
	}

	nfs_timer_start = get_time_ns();
}

Project: pengutronix

static int nfs_umountall_reply(unsigned char *pkt, unsigned len)
{
	int ret;

	ret = rpc_check_reply(pkt, 0);
	if (ret)
		return ret;

	memset(dirfh, 0, sizeof(dirfh));

	return 0;
}

Project: pengutronix

static void nfs_umountall_req(void)
{
	uint32_t data[1024];
	uint32_t *p;
	int len;

	if (nfs_server_mount_port < 0)
		/* Nothing mounted, nothing to umount */
		return;

	p = &(data[0]);
	p = rpc_add_credentials(p);

	len = p - &(data[0]);

	rpc_req(PROG_MOUNT, MOUNT_UMOUNTALL, data, len);
}

Project: pengutronix

static uint32_t *rpc_add_credentials(uint32_t *p)
{
	int hl;
	int hostnamelen = 0;

	/* Here's the executive summary on authentication requirements of the
	 * various NFS server implementations:	Linux accepts both AUTH_NONE
	 * and AUTH_UNIX authentication (also accepts an empty hostname field
	 * in the AUTH_UNIX scheme).  *BSD refuses AUTH_NONE, but accepts
	 * AUTH_UNIX (also accepts an empty hostname field in the AUTH_UNIX
	 * scheme).  To be safe, use AUTH_UNIX and pass the hostname if we have
	 * it (if the BOOTP/DHCP reply didn't give one, just use an empty
	 * hostname).  */

	hl = (hostnamelen + 3) & ~3;

	/* Provide an AUTH_UNIX credential.  */
	*p++ = htonl(1);		/* AUTH_UNIX */
	*p++ = htonl(hl+20);		/* auth length */
	*p++ = htonl(0);		/* stamp */
	*p++ = htonl(hostnamelen);	/* hostname string */

	if (hostnamelen & 3)
		*(p + hostnamelen / 4) = 0; /* add zero padding */

	/* memcpy(p, hostname, hostnamelen); */ /* empty hostname */

	p += hl / 4;
	*p++ = 0;			/* uid */
	*p++ = 0;			/* gid */
	*p++ = 0;			/* auxiliary gid list */

	/* Provide an AUTH_NONE verifier.  */
	*p++ = 0;			/* AUTH_NONE */
	*p++ = 0;			/* auth length */

	return p;
}

Project: pengutronix

static int rpc_lookup_reply(int prog, unsigned char *pkt, unsigned len)
{
	uint32_t port;
	int ret;

	ret = rpc_check_reply(pkt, 0);
	if (ret)
		return ret;

	port = net_read_uint32((uint32_t *)(pkt + sizeof(struct rpc_reply)));
	switch (prog) {
	case PROG_MOUNT:
		nfs_server_mount_port = ntohl(port);
		break;
	case PROG_NFS:
		nfs_server_nfs_port = ntohl(port);
		break;
	}

	return 0;
}

Project: pengutronix
File: nfs.c

static void rpc_lookup_req(int prog, int ver)
{
	uint32_t data[16];

	data[0] = 0; data[1] = 0;	/* auth credential */
	data[2] = 0; data[3] = 0;	/* auth verifier */
	data[4] = htonl(prog);
	data[5] = htonl(ver);
	data[6] = htonl(17);	/* IP_UDP */
	data[7] = 0;

	rpc_req(PROG_PORTMAP, PORTMAP_GETPORT, data, 8);
}

Project: pengutronix

static int rpc_req(int rpc_prog, int rpc_proc, uint32_t *data, int datalen)
{
	struct rpc_call pkt;
	unsigned long id;
	int sport;
	int ret;
	unsigned char *payload = net_udp_get_payload(nfs_con);

	id = ++rpc_id;
	pkt.id = htonl(id);
	pkt.type = htonl(MSG_CALL);
	pkt.rpcvers = htonl(2);	/* use RPC version 2 */
	pkt.prog = htonl(rpc_prog);
	pkt.vers = htonl(2);	/* portmapper is version 2 */
	pkt.proc = htonl(rpc_proc);

	memcpy(payload, &pkt, sizeof(pkt));
	memcpy(payload + sizeof(pkt), data, datalen * sizeof(uint32_t));

	if (rpc_prog == PROG_PORTMAP)
		sport = SUNRPC_PORT;
	else if (rpc_prog == PROG_MOUNT)
		sport = nfs_server_mount_port;
	else
		sport = nfs_server_nfs_port;

	nfs_con->udp->uh_dport = htons(sport);
	ret = net_udp_send(nfs_con, sizeof(pkt) + datalen * sizeof(uint32_t));

	return ret;
}