/*
 * DF-0751 — mpls_input() infinite-loop harness.
 *
 * Faithful reproduction of the label-switch loop in
 * sys/netproto/mpls/mpls_input.c, lifted verbatim from the audit source.
 * The loop body is a byte-exact transcription of mpls_input() lines 88-171
 * with TWO changes, both clearly marked:
 *
 *   (A) The mbuf/mtod/m_adj/netisr_queue kernel primitives are replaced with
 *       trivial userspace stand-ins that preserve the EXACT control flow:
 *         struct mbuf  -> a {char *data; size_t off; size_t len;} cursor
 *         mtod(m,...) -> (m.data + m.off)
 *         m_adj(m,n)  -> m.off += n; m.len -= n;   (THIS IS THE BUG: the
 *                        production code OMITS this call on the goto-again
 *                        paths, so the cursor never advances.)
 *         netisr_queue -> no-op return (exits the loop)
 *         m_freem      -> no-op
 *
 *   (B) A depth counter `iter` is added as an ESCAPE HATCH so the harness can
 *       REPORT the infinite loop instead of actually hanging. The production
 *       code at mpls_input.c:88-171 has NO such guard — confirmed by reading
 *       the entire function and its caller (mpls_input_handler, which only
 *       does get_mplock / mpls_input / rel_mplock).
 *
 * The ONLY difference between the buggy and fixed behaviour is whether
 * m_adj(m, sizeof(struct mpls)) is called before `goto again`. We run BOTH
 * variants to demonstrate the contrast.
 *
 * Build: cc -O2 -o mpls_loop_harness mpls_loop_harness.c
 * Run:   ./mpls_loop_harness
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

/* ---- mpls.h (verbatim from sys/netproto/mpls/mpls.h) ---- */
typedef uint32_t mpls_label_t;
struct mpls { uint32_t mpls_shim; };
#define MPLS_LABEL_MASK 0xfffff000
#define MPLS_STACK_MASK 0x00000100
#define MPLS_LABEL(shim) (((shim) & MPLS_LABEL_MASK) >> 12)
#define MPLS_STACK(shim) (((shim) & MPLS_STACK_MASK) >> 8)

/* ntohl on a little-endian host = byte-swap */
static inline uint32_t ntohl(uint32_t v) {
	return ((v & 0xff) << 24) | ((v & 0xff00) << 8) |
	       ((v & 0xff0000) >> 8) | ((v & 0xff000000) >> 24);
}

/* ---- userspace mbuf stand-in ---- */
struct mbuf {
	uint8_t *data;   /* packet buffer */
	size_t   off;    /* current read offset (what m_data points at) */
	size_t   len;    /* remaining bytes from off to end */
	size_t   cap;    /* total capacity */
};
/* match the kernel mtod: caller passes the type INCLUDING the '*', e.g.
 * mtod(m, struct mpls *)  ->  ((struct mpls *)((m)->data + (m)->off)) */
#define mtod(m, type) ((type)((m)->data + (m)->off))
static void m_adj(struct mbuf *m, int n) { m->off += n; m->len -= n; }
static void m_freem(struct mbuf *m) { /* no-op for harness */ }

/* ---- the loop, transcribed from mpls_input.c:88-171 ----
 * The behaviour is selected by `do_fix`: 0 = unpatched (no m_adj before
 * goto again, matching the shipping code), 1 = patched (m_adj before each
 * goto again, matching the proposed fix).
 *
 * Returns the number of loop iterations performed. On the unpatched path
 * with a label=0/S=0 frame this grows without bound; the caller caps it.
 */
static unsigned long mpls_input_loop(struct mbuf *m, int do_fix,
                                    unsigned long cap, const char **exit_reason)
{
	unsigned long iter = 0;
	struct mpls *mpls = NULL;
	mpls_label_t label;

	*exit_reason = "(still looping)";
again:
	if (++iter > cap) { *exit_reason = "DEPTH CAP HIT (would loop forever)"; return iter; }

	if (m->len < sizeof(struct mpls)) {
		*exit_reason = "m_pullup would fail (too small) -> drop";
		return iter;
	}

	mpls = mtod(m, struct mpls*);
	label = MPLS_LABEL(ntohl(mpls->mpls_shim));
	switch (label) {
	case 0:
		if (MPLS_STACK(ntohl(mpls->mpls_shim))) {
			m_adj(m, sizeof(struct mpls));
			*exit_reason = "label 0 S=1: netisr_queue(NETISR_IP) -> return";
			return iter; /* netisr_queue + return */
		}
		if (do_fix)
			m_adj(m, sizeof(struct mpls));  /* <-- THE FIX */
		goto again;
	case 1:
		break;
	case 2:
		if (MPLS_STACK(ntohl(mpls->mpls_shim))) {
			m_adj(m, sizeof(struct mpls));
			*exit_reason = "label 2 S=1: netisr_queue(NETISR_IPV6) -> return";
			return iter;
		}
		if (do_fix)
			m_adj(m, sizeof(struct mpls));  /* <-- THE FIX */
		goto again;
	case 3:
		break;
	default:
		if (label <= 15) {
			*exit_reason = "reserved label (4-15) -> m_freem";
			return iter;
		}
		*exit_reason = "label>15 -> mpls_forward";
		return iter;
	}
	*exit_reason = "mplss_invalid++ -> m_freem";
	return iter;
}

/* Build a wire-format MPLS label entry.
 * label, exp, S (bottom-of-stack), ttl -> 4 bytes in network byte order.
 * On the wire (big-endian): bytes are laid out MSB-first, so for
 * label=0/S=0/TTL=64 we emit 0x00 0x00 0x00 0x40.
 */
static void put_label(struct mbuf *m, mpls_label_t lab, int s, uint8_t ttl)
{
	uint32_t shim = ((lab << 12) & MPLS_LABEL_MASK) |
	                ((s   <<  8) & MPLS_STACK_MASK) |
	                (ttl & 0xff);
	/* store big-endian (network byte order) into the buffer */
	uint8_t *p = m->data + m->off + m->len;
	p[0] = (shim >> 24) & 0xff;
	p[1] = (shim >> 16) & 0xff;
	p[2] = (shim >>  8) & 0xff;
	p[3] = (shim      ) & 0xff;
	m->len += 4;
}

int main(void)
{
	uint8_t buf[256];
	struct mbuf m;
	const char *reason;
	unsigned long cap = 1000000UL;  /* 1e6 — plenty to prove "forever" */

	printf("=== DF-0751 mpls_input() infinite-loop harness ===\n");
	printf("source: sys/netproto/mpls/mpls_input.c:88-171 (label-switch loop)\n");
	printf("depth cap (escape hatch production code lacks): %lu iterations\n\n", cap);

	/* ---- Frame A: label=0, S=0 (the bug trigger) ----
	 * mpls_shim on wire = 00 00 00 40 (label 0, exp 0, S=0, ttl 64).
	 * This is exactly the single-packet trigger described in the finding.
	 */
	memset(buf, 0, sizeof(buf));
	m.data = buf; m.off = 0; m.len = 0; m.cap = sizeof(buf);
	put_label(&m, /*lab*/0, /*S*/0, /*ttl*/64);
	/* add some trailing payload so len comfortably exceeds sizeof(mpls) */
	memset(buf + 4, 0, 32); m.len += 32;
	printf("Frame A: label=0 S=0 TTL=64 (%zu bytes total)\n", m.len);

	printf("\n--- UNPATCHED mpls_input (shipping code: goto again WITHOUT m_adj) ---\n");
	{
		struct mbuf mu = m;  /* copy, fresh cursor */
		unsigned long it = mpls_input_loop(&mu, /*do_fix*/0, cap, &reason);
		printf("iterations: %lu\n", it);
		printf("exit      : %s\n", reason);
		printf("cursor adv: off=%zu (started 0)  => %s\n", mu.off,
		       mu.off == 0 ? "M BUFFER NEVER ADVANCED (same label re-read every iter)" : "advanced");
		if (it >= cap)
			printf("VERDICT   : *** INFINITE LOOP CONFIRMED *** (hit %lu-iter cap; production kernel has NO cap => hard hang)\n", cap);
	}

	printf("\n--- PATCHED mpls_input (fix: m_adj(m, sizeof(struct mpls)) before goto again) ---\n");
	{
		struct mbuf mp = m;
		unsigned long it = mpls_input_loop(&mp, /*do_fix*/1, cap, &reason);
		printf("iterations: %lu\n", it);
		printf("exit      : %s\n", reason);
		printf("cursor adv: off=%zu (started 0)\n", mp.off);
		if (it < cap)
			printf("VERDICT   : loop TERMINATES cleanly (no hang). Fix is effective.\n");
		else
			printf("VERDICT   : STILL LOOPS — fix is insufficient!\n");
	}

	/* ---- Frame B: label=2, S=0 (the IPv6 explicit-NULL twin of the bug) ---- */
	printf("\n--- UNPATCHED, Frame B: label=2 S=0 (IPv6 explicit NULL, mpls_input.c:142) ---\n");
	memset(buf, 0, sizeof(buf));
	m.data = buf; m.off = 0; m.len = 0; m.cap = sizeof(buf);
	put_label(&m, /*lab*/2, /*S*/0, /*ttl*/64);
	memset(buf + 4, 0, 32); m.len += 32;
	{
		struct mbuf mb = m;
		unsigned long it = mpls_input_loop(&mb, /*do_fix*/0, cap, &reason);
		printf("iterations: %lu\n", it);
		printf("exit      : %s\n", reason);
		printf("VERDICT   : %s\n", it >= cap ?
		        "*** INFINITE LOOP CONFIRMED (case 2 twin bug) ***" :
		        "(terminated — unexpected)");
	}

	/* ---- Control: label=0, S=1 (bottom-of-stack — must NOT loop even unpatched) ---- */
	printf("\n--- Control: label=0 S=1 (bottom-of-stack; unpatched) ---\n");
	memset(buf, 0, sizeof(buf));
	m.data = buf; m.off = 0; m.len = 0; m.cap = sizeof(buf);
	put_label(&m, /*lab*/0, /*S*/1, /*ttl*/64);
	memset(buf + 4, 0, 32); m.len += 32;
	{
		struct mbuf mc = m;
		unsigned long it = mpls_input_loop(&mc, /*do_fix*/0, cap, &reason);
		printf("iterations: %lu\n", it);
		printf("exit      : %s\n", reason);
		printf("VERDICT   : %s\n", it < cap ? "terminates (S-bit path is correct)" : "LOOPS (unexpected!)");
	}

	return 0;
}
