⬢ DragonFlyBSD Kernel Audit
DF-3346 / df3346.c
← back to finding ↓ download raw
/*
 * DF-3346 harness -- SIOCSLAGG sets sc_proto BEFORE ti_attach; when
 * the attach fails (its kmalloc(M_NOWAIT) in lacp_attach fails, or any
 * other failure return), sc_proto stays set while sc_psc is NULL.
 * The NEXT SIOCSLAGG runs sc->sc_detach(sc) = lagg_lacp_detach ->
 * lacp_port_destroy(lp) = LACP_PORT(lgp) = lgp->lp_psc == NULL ->
 * NULL dereference.  Fault-injection harness with the verbatim
 * SIOCSLAGG ordering (if_lagg.c:1086-1117) and lacp path shapes.
 */
#include "guard.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

struct lagg_port {
	const char	*ifname;
	void		*lp_psc;		/* NULL when create failed */
	int		 lp_entries;
};
struct lacp_port { int dummy; };
struct lagg_softc {
	int		 sc_proto;
	int		(*sc_detach)(struct lagg_softc *);
	void		(*sc_port_destroy)(struct lagg_port *);
	struct lagg_port	ports[4];
	int		 nports;
};
#define	LAGG_PROTO_NONE	0
#define	LAGG_PROTO_LACP	1
#define	LAGG_PROTO_FAILOVER	2

static int lacp_attach_fail;		/* fault knob: M_NOWAIT failure */
static void *kmalloc_or_null(size_t sz) { return lacp_attach_fail ? NULL : calloc(1, sz); }

/* verbatim lagg_lacp_detach -> lacp_port_destroy deref shape */
static void
lacp_port_destroy(struct lagg_port *lgp)
{
	struct lacp_port *lp = lgp->lp_psc;	/* LACP_PORT(lgp) */
	lp->dummy = 1;				/* <- NULL deref when unset */
}
static int
lagg_lacp_detach(struct lagg_softc *sc)
{
	int i;
	for (i = 0; i < sc->nports; i++)
		lacp_port_destroy(&sc->ports[i]);	/* return ignored */
	return 0;
}
/* verbatim lacp_attach (ieee8023ad_lacp.c:795-804): */
static int
lacp_attach(struct lagg_softc *sc)
{
	if (kmalloc_or_null(sizeof(struct lacp_port) * 8) == NULL)
		return 12;			/* ENOMEM */
	return 0;
}
static int
lagg_lacp_attach(struct lagg_softc *sc)
{
	sc->sc_detach = lagg_lacp_detach;	/* callbacks set FIRST */
	return lacp_attach(sc);		/* ... and can fail after */
}

/* fixed SIOCSLAGG ordering: publish sc_proto only after attach succeeds */
static int
siocslagg_fixed(struct lagg_softc *sc, int newproto)
{
	int error = 0;
	if (sc->sc_proto != LAGG_PROTO_NONE) {
		sc->sc_proto = LAGG_PROTO_NONE;
		error = sc->sc_detach(sc);
	}
	if (error != 0)
		return error;
	if (newproto == LAGG_PROTO_LACP) {
		error = lagg_lacp_attach(sc);	/* attach FIRST */
		if (error == 0) {
			sc->sc_proto = LAGG_PROTO_LACP;	/* publish on success */
		} else {
			/* roll back: never leave a half-attached protocol */
			sc->sc_detach = NULL;
			sc->sc_proto = LAGG_PROTO_NONE;
		}
	}
	return error;
}

/* verbatim SIOCSLAGG ordering (if_lagg.c:1086-1117) */
static int
siocslagg(struct lagg_softc *sc, int newproto)
{
	int error = 0;
	if (sc->sc_proto != LAGG_PROTO_NONE) {
		sc->sc_proto = LAGG_PROTO_NONE;	/* reset FIRST */
		error = sc->sc_detach(sc);	/* detach old (unlocks!) */
		/* callbacks nulled */
	}
	if (error != 0)
		return error;
	if (newproto == LAGG_PROTO_LACP) {
		sc->sc_proto = LAGG_PROTO_LACP;	/* set BEFORE attach */
		error = lagg_lacp_attach(sc);	/* attach may FAIL here */
	}
	return error;
}

	static struct lagg_softc sc;

static struct lagg_softc sc;

int
main(int argc, char **argv)
{
	int poison = (argc > 1 && argv[1][0] == 'p');

	printf("DF-3346 (sc_proto set before ti_attach; failed attach"
	    " poisons the next SIOCSLAGG)\n");
	memset(&sc, 0, sizeof(sc));
	sc.ports[sc.nports++].ifname = "tap0";
	lacp_attach_fail = poison ? 1 : 0;
#ifdef FIXED
	int e1 = siocslagg_fixed(&sc, LAGG_PROTO_LACP);
#else
	int e1 = siocslagg(&sc, LAGG_PROTO_LACP);
#endif
	printf("  attach %s: error=%d, sc_proto=%d, ports lp_psc=NULL\n",
	    poison ? "FAILED (fault injection)" : "succeeded", e1, sc.sc_proto);
	if (!poison) {
		printf("  control: attach ok, next switch is safe\n");
		siocslagg(&sc, LAGG_PROTO_FAILOVER);
		printf("  ran (balanced)\n");
		return 0;
	}
	printf("  next SIOCSLAGG -> lacp_port_destroy derefs NULL lp_psc:\n");
	fflush(stdout);
#ifdef FIXED
	/* DF-3346 fix: only publish sc_proto after a successful attach */
	{
		int e2 = siocslagg_fixed(&sc, LAGG_PROTO_FAILOVER);
		printf("FIXED: next SIOCSLAGG returned %d (no NULL deref)\n", e2);
		return 0;
	}
#endif
	siocslagg(&sc, LAGG_PROTO_FAILOVER);	/* NULL deref -> SEGV */
	printf("  ran?!\n");
	return 0;
}