⬢ DragonFlyBSD Kernel Audit
DF-5070 / df5070.c
← back to finding ↓ download raw
/*
 * DF-5070 sink demo — ath spectral SPECTRAL_CONTROL_SET_PARAMS
 * NULL-pointer dereference
 * (sys/dev/netif/ath/ath/if_ath_spectral.c:232-242)
 *
 * Root cause chain replicated verbatim:
 *
 *   if_ath_spectral.c:186   u_int32_t insize = ad->ad_in_size;
 *   if_ath_spectral.c:197   if (ad->ad_id & ATH_DIAG_IN) { indata = kmalloc(...); }
 *                              -- WITHOUT the flag, indata stays NULL (:184)
 *   if_ath_spectral.c:232   case SPECTRAL_CONTROL_SET_PARAMS:
 *   if_ath_spectral.c:233       if (insize < sizeof(HAL_SPECTRAL_PARAM)) {EINVAL}
 *                              -- validates ONLY the size FIELD, never the
 *                                 ATH_DIAG_IN flag nor indata != NULL
 *   if_ath_spectral.c:237       pe = (HAL_SPECTRAL_PARAM *) indata;   <-- NULL
 *   if_ath_spectral.c:238       ath_hal_spectral_configure(sc->sc_ah, pe);
 *                              -> ar5416ConfigureSpectralScan():
 *                                 ar5416_spectral.c:92  ss->ss_fft_period (READ)
 *                                 ar5416_spectral.c:143 -> ar5416GetSpectralParams
 *                                 ar5416_spectral.c:157-167  (WRITEs through ss)
 *
 * Contrast arm in the SAME function: SPECTRAL_CONTROL_ENABLE_AT_RESET
 * (if_ath_spectral.c:263-267) explicitly checks `indata == NULL` and rejects
 * with EINVAL — proving the missing check at :237 is an oversight, not a
 * HAL contract (the dfs_null twin, DF-2052, missed its check the same way
 * and was filed for DFS_SET_THRESH).
 *
 * Unprivileged reachability rides DF-2081: SIOCGATHSPECTRAL is dispatched
 * by ieee80211_ioctl's default arm (ieee80211_ioctl.c:3516-3523) straight
 * to ath_ioctl (if_ath_ioctl.c:304-305) with no privilege check, and
 * ath_hal_spectral_supported() is true for AR9280/AR9285/AR9287
 * (ar9280_attach.c:904, ar9285_attach.c:558, ar9287_attach.c:457).
 *
 * The guest has no Atheros NIC, so following the DF-4986/DF-5012/DF-5024
 * house precedent this module wires the exact control flow (copyin-flag
 * logic + switch arms) to a char device ioctl. The HAL call is replaced by
 * a function performing exactly the HAL's first field read.
 *
 * Trigger modes (df5070_trigger):
 *   control  - SET_PARAMS WITH ATH_DIAG_IN, 64B of 0xff  -> success (0)
 *   contrast - ENABLE_AT_RESET (8), in_size=4, no flag   -> EINVAL (defended)
 *   fire     - SET_PARAMS (7), in_size=64, no flag       -> kernel panic
 *
 * Build the fixed variant with -DDF5070_FIXED: adds the indata==NULL check
 * to SET_PARAMS only; `fire` then returns EINVAL with no panic.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/devfs.h>
#include <sys/malloc.h>
#include <sys/errno.h>
#include <sys/types.h>

#ifdef DF5070_FIXED
#define	PREFIX	"df5070f"
#else
#define	PREFIX	"df5070"
#endif

/* ---- verbatim from sys/dev/netif/ath/ath/if_athioctl.h:178-190 ---- */
struct df5070_diag {
	char	ad_name[16];		/* if name, e.g. "ath0" (IFNAMSIZ) */
	u_int16_t ad_id;
#define	ATH_DIAG_DYN	0x8000		/* allocate buffer in caller */
#define	ATH_DIAG_IN	0x4000		/* copy in parameters */
#define	ATH_DIAG_OUT	0x0000		/* copy out results (always) */
#define	ATH_DIAG_ID	0x0fff
	u_int16_t ad_in_size;		/* pack to fit, yech */
	caddr_t	ad_in_data;
	caddr_t	ad_out_data;
	u_int	ad_out_size;
};
#define	DF5070_IOCTL	_IOWR('D', 70, struct df5070_diag)

/* ---- verbatim from sys/dev/netif/ath/ath/if_athioctl.h:443-450 ---- */
#define	SPECTRAL_CONTROL_START		4
#define	SPECTRAL_CONTROL_STOP		5
#define	SPECTRAL_CONTROL_GET_PARAMS	6
#define	SPECTRAL_CONTROL_SET_PARAMS	7
#define	SPECTRAL_CONTROL_ENABLE_AT_RESET	8

/* ---- verbatim from sys/dev/netif/ath/ath_hal/ah.h:1005-1017
 *     (AH_MAX_CHAINS == 3; sizeof == 36) ---- */
typedef struct {
	u_int16_t	ss_fft_period;	/* Skip interval for FFT reports */
	u_int16_t	ss_period;	/* Spectral scan period */
	u_int16_t	ss_count;	/* # of reports to return from ss_active */
	u_int16_t	ss_short_report;/* Set to report ony 1 set of FFT results */
	u_int8_t	radar_bin_thresh_sel;
	u_int16_t	ss_spectral_pri;
	int8_t		ss_nf_cal[3 * 2];
	int8_t		ss_nf_pwr[3 * 2];
	int32_t		ss_nf_temp_data;
	int		ss_enabled;
	int		ss_active;
} HAL_SPECTRAL_PARAM;
#define	HAL_SPECTRAL_PARAM_NOVAL	0xFFFF

/* struct ath_spectral_state analog (if_ath_spectral.c:79-96) */
static HAL_SPECTRAL_PARAM	spectral_state;
static int			spectral_enable_after_reset;

/*
 * HAL analog: ar5416ConfigureSpectralScan (ar5416_spectral.c:84-144).
 * The FIRST thing the HAL does with the caller's pointer is READ
 * ss->ss_fft_period (line 92); the tail call ar5416GetSpectralParams
 * (line 143 -> 151-167) additionally WRITES through the same pointer.
 * Register RMWs need hardware the sink does not have; the field accesses
 * that fault on NULL are performed exactly.
 */
static void
sim_hal_spectral_configure(HAL_SPECTRAL_PARAM *ss)
{
	kprintf(PREFIX ": hal_configure(ah, ss=%p): reading ss_fft_period=%u "
	    "ss_period=%u (NOVAL=0x%x)\n", ss,
	    ss->ss_fft_period,			/* ar5416_spectral.c:92 -- READ */
	    ss->ss_period,			/* ar5416_spectral.c:97 -- READ */
	    HAL_SPECTRAL_PARAM_NOVAL);
	if (ss->ss_count != HAL_SPECTRAL_PARAM_NOVAL) {	/* :109/:120 */
		if (ss->ss_count == 128)	/* :125-126 Kiwi remap: WRITE */
			ss->ss_count = 0;
	}
	/* ar5416GetSpectralParams(ah, ss) at :143 would now WRITE *ss */
}

static d_open_t	df5070_open;
static d_close_t df5070_close;
static d_ioctl_t df5070_ioctl;

static struct dev_ops df5070_ops = {
#ifdef DF5070_FIXED
	{ "df5070f", 0, D_MEM },
#else
	{ "df5070", 0, D_MEM },
#endif
	.d_open = df5070_open,
	.d_close = df5070_close,
	.d_ioctl = df5070_ioctl,
};

static int
df5070_open(struct dev_open_args *ap)
{
	return (0);
}

static int
df5070_close(struct dev_close_args *ap)
{
	return (0);
}

/*
 * ---- VERBATIM REPLICA of ath_ioctl_spectral() control flow
 *      (if_ath_spectral.c:181-242, arms relevant to this finding) ----
 */
static int
df5070_ioctl(struct dev_ioctl_args *ap)
{
	unsigned int id = ((struct df5070_diag *)ap->a_data)->ad_id &
	    ATH_DIAG_ID;
	void *indata = NULL;
	u_int32_t insize = ((struct df5070_diag *)ap->a_data)->ad_in_size;
	int error = 0;
	HAL_SPECTRAL_PARAM *pe;
	struct df5070_diag *ad = (struct df5070_diag *)ap->a_data;
	int val;

	if (ad->ad_id & ATH_DIAG_IN) {			/* :197 */
		indata = kmalloc(insize, M_TEMP, M_INTWAIT);
		if (indata == NULL) {
			error = ENOMEM;
			goto bad;
		}
		error = copyin(ad->ad_in_data, indata, insize);
		if (error)
			goto bad;
	}
	/* (ATH_DIAG_DYN outdata branch elided: irrelevant to this path) */

	switch (id) {
	case SPECTRAL_CONTROL_SET_PARAMS:		/* :232 */
		if (insize < sizeof(HAL_SPECTRAL_PARAM)) {	/* :233 */
			error = EINVAL;
			break;
		}
#ifdef DF5070_FIXED
		/* THE FIX: mirror the ENABLE_AT_RESET defense (:263-267) */
		if (indata == NULL) {
			kprintf(PREFIX ": SET_PARAMS rejected: indata == NULL "
			    "(ATH_DIAG_IN not set)\n");
			error = EINVAL;
			break;
		}
#endif
		pe = (HAL_SPECTRAL_PARAM *)indata;	/* :237 -> NULL */
		sim_hal_spectral_configure(pe);		/* :238 -> deref */
		/* :239-241 save-local analog */
		memcpy(&spectral_state, pe, sizeof(*pe));
		break;
	case SPECTRAL_CONTROL_ENABLE_AT_RESET:		/* :255 */
		if (insize < sizeof(int)) {		/* :256 */
			error = EINVAL;
			break;
		}
		if (indata == NULL) {			/* :263-267 THE DEFENSE */
			kprintf(PREFIX ": ENABLE_AT_RESET: indata=NULL\n");
			error = EINVAL;
			break;
		}
		val = *((int *)indata);		/* :268 */
		spectral_enable_after_reset = (val != 0);
		break;
	default:
		error = EINVAL;				/* :279-280 */
	}
bad:
	if ((ad->ad_id & ATH_DIAG_IN) && indata != NULL)
		kfree(indata, M_TEMP);			/* :287-288 */
	return (error);
}

static int
df5070_modevent(module_t mod, int type, void *data)
{
	int error = 0;

	switch (type) {
	case MOD_LOAD:
#ifdef DF5070_FIXED
		make_dev(&df5070_ops, 0, UID_ROOT, GID_WHEEL, 0666, "df5070f");
#else
		make_dev(&df5070_ops, 0, UID_ROOT, GID_WHEEL, 0666, "df5070");
#endif
		kprintf(PREFIX ": sink replica of if_ath_spectral.c "
		    "SPECTRAL_CONTROL_SET_PARAMS loaded; sizeof(HAL_SPECTRAL_"
		    "PARAM)=%zu\n", sizeof(HAL_SPECTRAL_PARAM));
		break;
	case MOD_UNLOAD:
#ifdef DF5070_FIXED
		destroy_dev(devfs_find_device_by_name("df5070f"));
#else
		destroy_dev(devfs_find_device_by_name("df5070"));
#endif
		break;
	default:
		error = EOPNOTSUPP;
		break;
	}
	return (error);
}

#ifdef DF5070_FIXED
DEV_MODULE(df5070f, df5070_modevent, NULL);
#else
DEV_MODULE(df5070, df5070_modevent, NULL);
#endif