xorl %eax, %eax

CVE-2017-1081: FreeBSD ipfilter use-after-free

leave a comment »

The SA-17:04 FreeBSD security advisory describes a logic flaw that results in a use-after-free situation in ipfilter. The vulnerability was reported by Cy Schubert and affects all FreeBSD releases prior to 11.0-STABLE, 11.0-RELEASE-p10, 10.3-STABLE, and 10.3-RELEASE-p19. Let’s have a look at sys/contrib/ipfilter/netinet/ip_frag.c to better understand the vulnerability.

/* ------------------------------------------------------------------------ */
/* Function:    ipfr_frag_new                                               */
/* Returns:     ipfr_t * - pointer to fragment cache state info or NULL     */
/* Parameters:  fin(I)   - pointer to packet information                    */
/*              table(I) - pointer to frag table to add to                  */
/*              lock(I)  - pointer to lock to get a write hold of           */
/*                                                                          */
/* Add a new entry to the fragment cache, registering it as having come     */
/* through this box, with the result of the filter operation.               */
/*                                                                          */
/* If this function succeeds, it returns with a write lock held on "lock".  */
/* If it fails, no lock is held on return.                                  */
/* ------------------------------------------------------------------------ */
static ipfr_t *
ipfr_frag_new(softc, softf, fin, pass, table
#ifdef USE_MUTEXES
, lock
#endif
)
    ...
	ipfr_t *fra, frag, *fran;
    ...
	/*
	 * allocate some memory, if possible, if not, just record that we
	 * failed to do so.
	 */
	KMALLOC(fran, ipfr_t *);
	if (fran == NULL) {
		FBUMPD(ifs_nomem);
		return NULL;
	}
    ...
	/*
	 * first, make sure it isn't already there...
	 */
	for (fra = table[idx]; (fra != NULL); fra = fra->ipfr_hnext)
		if (!bcmp((char *)&frag.ipfr_ifp, (char *)&fra->ipfr_ifp,
			  IPFR_CMPSZ)) {
			RWLOCK_EXIT(lock);
			FBUMPD(ifs_exists);
			KFREE(fra);
			return NULL;
		}

	fra = fran;
	fran = NULL;
	fr = fin->fin_fr;
	fra->ipfr_rule = fr;
	if (fr != NULL) {
		MUTEX_ENTER(&fr->fr_lock);
		fr->fr_ref++;
		MUTEX_EXIT(&fr->fr_lock);
	}
    ...
}

The above code snippet is from ipfr_frag_new() which handles new fragmented packets in the fragment cache. What is important the “for” loop which is the processing of a hash table that contains the cached fragmented packets. The bcmp() will check if the packet stored in “fra” is already in the hash table (the “frag” pointer). If it’s not it will update “fra” with the memory allocated in “fran” and store the new packet there. If is already in the table, it will release the lock, free “fra” and return NULL. Well, it’s hard to notice but that’s a logic flaw as it should have freed “fran” (the not used kernel buffer) rather than the “fra” which points to the packet to be processed. As you can easily guess, the patch is pretty straightforward.

 			RWLOCK_EXIT(lock);
 			FBUMPD(ifs_exists);
-			KFREE(fra);
+			KFREE(fran);
 			return NULL;

There are few different code paths that can result in accessing the freed “fra”. One is in the ipf_frag_lookup() function which is used to look in the fragment cache for entries of the requested packet with its filter result. You can see how this can cause issues below.

static ipfr_t *
ipf_frag_lookup(softc, softf, fin, table
#ifdef USE_MUTEXES
, lock
#endif
)
	ipf_main_softc_t *softc;
	ipf_frag_softc_t *softf;
	fr_info_t *fin;
	ipfr_t *table[];
#ifdef USE_MUTEXES
	ipfrwlock_t *lock;
#endif
{
    ...
	/*
	 * check the table, careful to only compare the right amount of data
	 */
	for (f = table[idx]; f; f = f->ipfr_hnext) {
    ...
	return NULL;
}

And a second code path that can lead to use-after-free is via ipf_slowtimer() function from sys/contrib/ipfilter/netinet/fil.c. This function is used to slowly expire the state of the fragments.

void
ipf_slowtimer(softc)
	ipf_main_softc_t *softc;
{

	ipf_token_expire(softc);
	ipf_frag_expire(softc);
	ipf_state_expire(softc);
	ipf_nat_expire(softc);
	ipf_auth_expire(softc);
	ipf_lookup_expire(softc);
	ipf_rule_expire(softc);
	ipf_sync_expire(softc);
	softc->ipf_ticks++;
#   if defined(__OpenBSD__)
	timeout_add(&ipf_slowtimer_ch, hz/2);
#   endif
}

The interesting for this vulnerability call is the one to ipf_frag_expire() which is designed to expire entries from the fragment cache table. To achieve this, ipf_frag_expire() utilizes the internal ipf_frag_delete() routine which is shown below.

static void
ipf_frag_delete(softc, fra, tail)
	ipf_main_softc_t *softc;
	ipfr_t *fra, ***tail;
{
	ipf_frag_softc_t *softf = softc->ipf_frag_soft;

	if (fra->ipfr_next)
		fra->ipfr_next->ipfr_prev = fra->ipfr_prev;
	*fra->ipfr_prev = fra->ipfr_next;
	if (*tail == &fra->ipfr_next)
		*tail = fra->ipfr_prev;

	if (fra->ipfr_hnext)
		fra->ipfr_hnext->ipfr_hprev = fra->ipfr_hprev;
	*fra->ipfr_hprev = fra->ipfr_hnext;

	if (fra->ipfr_rule != NULL) {
		(void) ipf_derefrule(softc, &fra->ipfr_rule);
	}

	if (fra->ipfr_ref <= 0)
		ipf_frag_free(softf, fra);
}

Due to the logic error in ipfr_frag_new() the above could result in attempting to delete an entry that has already been freed. Interesting vulnerability and kind of hard to catch from static code analysis.

Written by xorl

November 19, 2017 at 18:30

Posted in vulnerabilities

Leave a comment