/*
 * DF-3520 — negative-length bcopy amplifier in ng_pptpgre_recv (userspace
 * arithmetic demonstration of the exact kernel expression).
 *
 * ng_pptpgre.c:641   const int index = ack - priv->recvAck - 1;
 * ng_pptpgre.c:646   if (PPTP_SEQ_DIFF(ack, priv->xmitSeq) > 0) -> badAck
 * ng_pptpgre.c:650   if (PPTP_SEQ_DIFF(ack, priv->recvAck) <= 0) -> badAck
 * ng_pptpgre.c:668   bcopy(a->timeSent + index + 1, a->timeSent,
 *                     sizeof(*a->timeSent) * (PPTP_XMIT_WIN - (index + 1)));
 *
 * The only bound on `index` is the window invariant
 *     xmitSeq - recvAck <= xmitWin <= PPTP_XMIT_WIN(16)
 * enforced by the racy read-check-increment at ng_pptpgre.c:490-495
 * (the DF-0596 TOCTOU family: two concurrent xmits both pass with
 * diff=15 and both increment -> diff=17).  A subsequent in-window ack
 * then yields index = 16 and:
 *     (PPTP_XMIT_WIN - (index + 1)) == -1
 *     sizeof(pptptime_t) * (int)-1  -> (size_t)0xFFFFFFFFFFFFFFF8
 * -> bcopy() of ~2^64 bytes: unbounded OOB read+write -> certain kernel
 * panic (non-recoverable page fault in bcopy), far beyond DF-0596's
 * "8 bytes past the array" impact ceiling.
 *
 * This program reproduces the exact arithmetic (no kernel needed) and,
 * if invoked with --bcopy, actually attempts the copy to show the
 * immediate SIGSEGV the kernel would take.
 *
 * Build: cc -O2 -o bcopy_demo bcopy_demo.c
 * Run:   ./bcopy_demo          (print arithmetic)
 *        ./bcopy_demo --bcopy  (attempt the copy -> SIGSEGV)
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stddef.h>

#define PPTP_XMIT_WIN 16
typedef uint64_t pptptime_t;
#define PPTP_SEQ_DIFF(x, y) ((int32_t)(x) - (int32_t)(y))

int
main(int argc, char **argv)
{
	uint32_t recvAck = 100, xmitSeq = 117;   /* diff = 17 via DF-0596 TOCTOU */
	uint32_t ack = 117;                      /* highest legal ack: == xmitSeq */

	/* the kernel's sanity checks pass: */
	if (PPTP_SEQ_DIFF(ack, xmitSeq) > 0) { puts("badAck(high)"); return 1; }
	if (PPTP_SEQ_DIFF(ack, recvAck) <= 0) { puts("badAck(low)"); return 1; }

	const int index = ack - recvAck - 1;                 /* = 16 */
	int len_i = PPTP_XMIT_WIN - (index + 1);             /* = -1 */
	size_t len = sizeof(pptptime_t) * (PPTP_XMIT_WIN - (index + 1));

	printf("recvAck=%u xmitSeq=%u ack=%u -> index=%d\n",
	    recvAck, xmitSeq, ack, index);
	printf("int length  = PPTP_XMIT_WIN - (index+1) = %d\n", len_i);
	printf("size_t len  = sizeof(pptptime_t) * (%d) = 0x%llx (%zu)\n",
	    len_i, (unsigned long long)len, len);
	printf("kernel timeSent[] = pptptime_t[%d] (%zu bytes), "
	    "index %d is %zd bytes past end\n",
	    PPTP_XMIT_WIN, sizeof(pptptime_t) * PPTP_XMIT_WIN, index,
	    (ssize_t)((index - PPTP_XMIT_WIN + 1) * (ssize_t)sizeof(pptptime_t)));

	if (argc > 1 && strcmp(argv[1], "--bcopy") == 0) {
		pptptime_t timeSent[PPTP_XMIT_WIN];
		printf("attempting bcopy(dst=timeSent, src=timeSent+%d, len=0x%llx)...\n",
		    index + 1, (unsigned long long)len);
		fflush(stdout);
		bcopy(timeSent + index + 1, timeSent, len);   /* SIGSEGV */
		printf("unreachable\n");
	}
	return 0;
}
