Bug 219472

Summary: Out of bounds access in vlan
Product: Base System Reporter: CTurt <ecturt>
Component: kernAssignee: freebsd-net
Status: New ---    
Severity: Affects Only Me CC: emaste, op
Priority: ---    
Version: CURRENT   
Hardware: Any   
OS: Any   

Description CTurt 2017-05-23 12:36:18 UTC
This whole struct (`ifv_mib`) is user controllable through the `IFDATA_LINKSPECIFIC` sysctl command:


		error = SYSCTL_OUT(req, ifp->if_linkmib, ifp->if_linkmiblen);
		if (error || !req->newptr)
			goto out;

		error = SYSCTL_IN(req, ifp->if_linkmib, ifp->if_linkmiblen);
		if (error)
			goto out;

In the case of `struct ifvlan`, the contained `if_linkmib` is a `struct ifv_linkmib` containing a `uint16_t` called `ifvm_vid`:


struct	ifvlan {
	struct	ifvlantrunk *ifv_trunk;
	struct	ifnet *ifv_ifp;
#define	TRUNK(ifv)	((ifv)->ifv_trunk)
#define	PARENT(ifv)	((ifv)->ifv_trunk->parent)
	void	*ifv_cookie;
	int	ifv_pflags;	/* special flags we have set on parent */
	struct	ifv_linkmib {
		int	ifvm_encaplen;	/* encapsulation length */
		int	ifvm_mtufudge;	/* MTU fudged by this much */
		int	ifvm_mintu;	/* min transmission unit */
		uint16_t ifvm_proto;	/* encapsulation ethertype */
		uint16_t ifvm_tag;	/* tag to apply on packets leaving if */
              	uint16_t ifvm_vid;	/* VLAN ID */
		uint8_t	ifvm_pcp;	/* Priority Code Point (PCP). */
	}	ifv_mib;
	SLIST_HEAD(, vlan_mc_entry) vlan_mc_listhead;
#ifndef VLAN_ARRAY
	LIST_ENTRY(ifvlan) ifv_list;
#define	ifv_proto	ifv_mib.ifvm_proto
#define	ifv_tag		ifv_mib.ifvm_tag
#define	ifv_vid 	ifv_mib.ifvm_vid
#define	ifv_pcp		ifv_mib.ifvm_pcp
#define	ifv_encaplen	ifv_mib.ifvm_encaplen
#define	ifv_mtufudge	ifv_mib.ifvm_mtufudge
#define	ifv_mintu	ifv_mib.ifvm_mintu

Thus, it follows that `ifv->ifv_vid` is a completely user controlled `uint16_t`, through the `IFDATA_LINKSPECIFIC` `sysctl` name.

This value is used as an index to perform reads and writes on the `vlans` array of size `0x1000` in multiple places.


static __inline int
vlan_inshash(struct ifvlantrunk *trunk, struct ifvlan *ifv)

	if (trunk->vlans[ifv->ifv_vid] != NULL)
		return EEXIST;
	trunk->vlans[ifv->ifv_vid] = ifv;

	return (0);

static __inline int
vlan_remhash(struct ifvlantrunk *trunk, struct ifvlan *ifv)

	trunk->vlans[ifv->ifv_vid] = NULL;

	return (0);


However, this is a static array of size `VLAN_ARRAY_SIZE` (`0x1000`) elements:


#define EVL_VLID_MASK 0x0FFF


struct ifvlantrunk {
	struct	ifnet   *parent;	/* parent interface of this trunk */
	struct	rmlock	lock;
	struct	ifvlan	*vlans[VLAN_ARRAY_SIZE]; /* static table */
	struct	ifvlanhead *hash;	/* dynamic hash-list table */
	uint16_t	hmask;
	uint16_t	hwidth;
	int		refcnt;

So, out of bounds access is possible if `ifv_vid` is set to a value greater than `0xfff`.