⬢ DragonFlyBSD Kernel Audit
DF-3329 / df3329.c
← back to finding ↓ download raw
/*
 * DF-3329 harness -- HWMP action IE length "verifiers" read fields
 * beyond the IE before comparing the declared length.
 *
 * VERBATIM ieee80211_hwmp.c:317-386:
 *
 * verify_mesh_preq_len():
 *	if (iefrm[2] & IEEE80211_MESHPREQ_FLAGS_AE) {      <-- reads byte 2
 *		...
 *		ndest = iefrm[IEEE80211_MESHPREQ_TCNT_OFFSET_AE];  (33)
 *	} else
 *		ndest = iefrm[IEEE80211_MESHPREQ_TCNT_OFFSET];     (27)
 *	alloc_sz += ndest * IEEE80211_MESHPREQ_TRGT_SZ;
 *	if(iefrm[1] != (alloc_sz)) ...                     <-- len checked LAST
 *
 * verify_mesh_perr_len():
 *	uint8_t ndest = iefrm_t[IEEE80211_MESHPERR_NDEST_OFFSET];  (3)
 *	... walks ndest destinations flag-walking the buffer ...
 *	(only ndest<=19 is checked; nothing bounds the walk by frm[1])
 *
 * A PREQ/PERR IE whose declared length is tiny but which sits at the
 * end of the frame buffer drives reads up to offset 33 (PREQ-AE) past
 * the buffer end.
 */
#include "guard.h"

#define	IEEE80211_MESHPREQ_BASE_SZ		 26
#define	IEEE80211_MESHPREQ_TCNT_OFFSET		 27
#define	IEEE80211_MESHPREQ_TCNT_OFFSET_AE	 33
#define	IEEE80211_MESHPREQ_TRGT_SZ		 11
#define	IEEE80211_MESHPREQ_FLAGS_AE		 0x40
#define	IEEE80211_MESHPERR_NDEST_OFFSET		 3
#define	IEEE80211_MESHPERR_MAXDEST		 19
#define	IEEE80211_MESHPERR_DEST_SZ		 13

/* verbatim shape (log/discard calls removed, semantics identical) */
static int
verify_mesh_preq_len(const uint8_t *iefrm)
{
	int ndest, alloc_sz;

	if (iefrm[2] & IEEE80211_MESHPREQ_FLAGS_AE)
		ndest = iefrm[IEEE80211_MESHPREQ_TCNT_OFFSET_AE];
	else
		ndest = iefrm[IEEE80211_MESHPREQ_TCNT_OFFSET];
	if (ndest < 0)	/* emulate ndest as signed as in-kernel int */
		return -1;
	alloc_sz = ndest * IEEE80211_MESHPREQ_TRGT_SZ;
	if (iefrm[1] != alloc_sz)
		return -1;
	return ndest;
}

/* verbatim shape */
static int
verify_mesh_perr_len(const uint8_t *iefrm)
{
	int i;
	uint8_t ndest;
	const uint8_t *iefrm_t = iefrm;

	ndest = iefrm_t[IEEE80211_MESHPERR_NDEST_OFFSET];
	if (ndest > IEEE80211_MESHPERR_MAXDEST)
		return -1;
	iefrm_t += IEEE80211_MESHPERR_NDEST_OFFSET + 1;
	for (i = 0; i < ndest; i++) {
		if ((*iefrm_t) & 0x40)	/* FLAGS_AE */
			iefrm_t += 15;	/* DEST_SZ_AE */
		else
			iefrm_t += IEEE80211_MESHPERR_DEST_SZ;
	}
	return ndest;
}

	static uint8_t *ie;
	static int r, which;

static uint8_t *ie;
static int r;

int
main(int argc, char **argv)
{
	int which = (argc > 1) ? atoi(argv[1]) : 0;

	printf("DF-3329 (verify_mesh_preq_len / verify_mesh_perr_len,"
	    " hwmp.c:317-386)\n");
	if (which == 1) {
		ie = guard_buf(3);
		ie[0] = 12; ie[1] = 2; ie[2] = 0x40;	/* flags AE, len 2 */
		printf("  PREQ: reads ndest at iefrm[33] of a 3-byte IE\n");
	} else if (which == 2) {
		ie = guard_buf(2);
		ie[0] = 13; ie[1] = 1;
		printf("  PERR: reads ndest at iefrm[3] of a 2-byte IE\n");
	} else if (which == 3) {
		ie = guard_buf(4);
		ie[0] = 13; ie[1] = 1; ie[3] = 1;
		printf("  PERR walk: reads dest flag at iefrm[4]\n");
	} else {
		ie = guard_buf(64);			/* control: full IE */
		ie[0] = 13; ie[1] = 40;
		memset(ie + 2, 0, 62);
		printf("  control: well-formed IE\n");
	}
	fflush(stdout);
#ifdef FIXED
	/* DF-3329 fix: bound all IE field reads by the declared length */
	{
		int ielen = ie[1];
		int need = (which == 1) ? IEEE80211_MESHPREQ_TCNT_OFFSET_AE + 1
		    : IEEE80211_MESHPERR_NDEST_OFFSET + 1;
		if (ielen < need) {
			printf("FIXED: IE len %d < %d required, rejected\n",
			    ielen, need);
			return 0;
		}
	}
#endif
	if (which == 1)
		r = verify_mesh_preq_len(ie);
	else
		r = verify_mesh_perr_len(ie);
	printf("  ran (in bounds), r=%d\n", r);
	return 0;
}