⬢ DragonFlyBSD Kernel Audit
DF-3558 / echo_neg.c
← back to finding ↓ download raw
/*
 * DF echo-pair inertness -- live negative evidence for the pass-2 lead on
 * sys/netgraph7/echo/ng_echo.c (DF-3540/DF-3553/DF-3557 cycle class).
 *
 * Claim under test: a closed echo<->echo DATA cycle is NOT injectable
 * (echo reflects data back out the arrival hook only -- ng_echo.c:106
 * NG_FWD_ITEM_HOOK(item, hook); and its control path RESPONDS to the
 * original sender -- netgraph.h:998-1013 NG_RESPOND_MSG ->
 * ng_address_ID(NGI_RETADDR) -- it never re-enters the graph via hooks,
 * unlike ng_UI's FLOW relay of DF-3557).
 *
 * Topology (4 generic messages, DF-3557's dance with echo nodes):
 *   mkpeer  : S.a   <-> E1.p
 *   mkpeer  : E1.q  <-> E2.m
 *   rmhook  : S.a              (E1 keeps q -> survives)
 *   connect : E2.n <-> E1.s    ==> closed ring E1.q<->E2.m, E1.s<->E2.n
 *   mkpeer  : S.b   <-> E1.inj (third hook on E1 for the data-injection
 *                               attempt -- must merely bounce back)
 *
 * Attempts (all must survive, guest must stay up):
 *   A. DATA injection: sendto(ds, "PING", addr "b") -> reflected straight
 *      back to the socket (readable on ds).  Ring never carries traffic.
 *   B. FLOW-cookie control hook-addressed INTO ring hooks ("e1:q",
 *      "e2:n"): echo bounces the message back to the SENDER (response
 *      arrives on cs with NGF_RESP + our token); it does NOT relay into
 *      the ring.
 *   C. Survival sleep.
 *
 * Build: cc -O2 -o echo_neg echo_neg.c
 * Run (root; netgraph.ko + ng_socket.ko + ng_echo.ko loaded): ./echo_neg
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#define AF_NG			32
#define NG_CONTROL		2
#define NG_DATA			1
#define NG_VERSION		8
#define GEN_COOKIE		1137070366	/* NGM_GENERIC_COOKIE */
#define FLOW_COOKIE		851672669	/* NGM_FLOW_COOKIE */
#define NGM_MKPEER		2
#define NGM_CONNECT		3
#define NGM_NAME		4
#define NGM_RMHOOK		5

#define NG_TYPESIZ		32
#define NG_HOOKSIZ		32
#define NG_NODESIZ		32

#define HDRSZ			56

struct ngm_hdr_ {
	u_char	version; u_char spare; u_short spare2;
	u_int	arglen, cmd, flags, token, typecookie;
	char	cmdstr[32];
};
struct ng_mesg_ { struct ngm_hdr_ header; char data[2048]; };
struct ngm_mkpeer_ { char type[NG_TYPESIZ]; char ourhook[NG_HOOKSIZ]; char peerhook[NG_HOOKSIZ]; };
struct ngm_name_  { char name[NG_NODESIZ]; };
struct ngm_connect_ { char path[512]; char ourhook[NG_HOOKSIZ]; char peerhook[NG_HOOKSIZ]; };
struct ngm_rmhook_ { char ourhook[NG_HOOKSIZ]; };
struct sockaddr_ng_ { u_char sg_len; u_char sg_family; char sg_data[64]; };

#define NGF_RESP_ 0x00000001u		/* ng7 ng_message.h:113 */

static u_int32_t tok = 0xE000;
static int cs, ds;

static u_int32_t
ngc(const char *path, u_int cookie, u_int cmd, const void *arg, u_int arglen)
{
	struct ng_mesg_ m;
	struct sockaddr_ng_ sa;
	u_int32_t t = ++tok;
	ssize_t n;

	memset(&m, 0, sizeof(m));
	m.header.version = NG_VERSION;
	m.header.arglen = arglen;
	m.header.cmd = cmd;
	m.header.token = t;
	m.header.typecookie = cookie;
	if (arglen)
		memcpy(m.data, arg, arglen);

	memset(&sa, 0, sizeof(sa));
	sa.sg_family = AF_NG;
	snprintf(sa.sg_data, sizeof(sa.sg_data), "%s", path);
	sa.sg_len = 2 + strlen(sa.sg_data) + 1;

	n = sendto(cs, &m, HDRSZ + arglen, 0, (struct sockaddr *)&sa, sa.sg_len);
	if (n < 0) {
		printf("RESULT sendto(%s) errno=%d (%s)\n", path, errno,
		    strerror(errno));
		exit(1);
	}
	return (t);
}

#define STEP(msg) do { printf("STEP %s\n", msg); fflush(stdout); } while (0)

int
main(void)
{
	struct ngm_mkpeer_ mkp;
	struct ngm_name_ nm;
	struct ngm_connect_ con;
	struct ngm_rmhook_ rmh;
	struct sockaddr_ng_ sa;
	struct pollfd pfd;
	struct ng_mesg_ m;
	char buf[64];
	u_int32_t t;
	int n, i, resp = 0, bounced = 0;

	printf("echo_neg: uid=%d\n", getuid());
	fflush(stdout);

	cs = socket(AF_NG, SOCK_DGRAM, NG_CONTROL);
	if (cs < 0) {
		printf("RESULT control-socket errno=%d (%s)\n", errno,
		    strerror(errno));
		exit(0);
	}

	memset(&nm, 0, sizeof(nm));
	strcpy(nm.name, "neg");
	ngc(".", GEN_COOKIE, NGM_NAME, &nm, sizeof(nm));
	STEP("socket node named neg");

	/* S.a <-> E1.p */
	memset(&mkp, 0, sizeof(mkp));
	strcpy(mkp.type, "echo"); strcpy(mkp.ourhook, "a"); strcpy(mkp.peerhook, "p");
	ngc(".", GEN_COOKIE, NGM_MKPEER, &mkp, sizeof(mkp));
	STEP("mkpeer echo(E1) S.a<->E1.p");

	memset(&nm, 0, sizeof(nm));
	strcpy(nm.name, "e1");
	ngc("a", GEN_COOKIE, NGM_NAME, &nm, sizeof(nm));
	STEP("named E1 = e1");

	/* E1.q <-> E2.m */
	memset(&mkp, 0, sizeof(mkp));
	strcpy(mkp.type, "echo"); strcpy(mkp.ourhook, "q"); strcpy(mkp.peerhook, "m");
	ngc("e1:", GEN_COOKIE, NGM_MKPEER, &mkp, sizeof(mkp));
	STEP("mkpeer echo(E2) E1.q<->E2.m");

	memset(&nm, 0, sizeof(nm));
	strcpy(nm.name, "e2");
	ngc("a.q", GEN_COOKIE, NGM_NAME, &nm, sizeof(nm));
	STEP("named E2 = e2");

	/* cut the umbilical: destroy S.a (and E1.p with it) */
	memset(&rmh, 0, sizeof(rmh));
	strcpy(rmh.ourhook, "a");
	ngc("neg:", GEN_COOKIE, NGM_RMHOOK, &rmh, sizeof(rmh));
	STEP("rmhook neg:a (E1 survives with q only)");

	/* close the ring: E2.n <-> E1.s */
	memset(&con, 0, sizeof(con));
	strcpy(con.path, "m");		/* from E2: through E2.m -> E1 */
	strcpy(con.ourhook, "n");
	strcpy(con.peerhook, "s");
	ngc("e2:", GEN_COOKIE, NGM_CONNECT, &con, sizeof(con));
	STEP("connect E2.n<->E1.s -- RING CLOSED (E1.q<->E2.m, E1.s<->E2.n)");

	/* data socket on the ctrl node; third hook for injection attempt */
	ds = socket(AF_NG, SOCK_DGRAM, NG_DATA);
	if (ds < 0) {
		printf("RESULT data-socket errno=%d (%s)\n", errno,
		    strerror(errno));
		exit(1);
	}
	memset(&sa, 0, sizeof(sa));
	sa.sg_family = AF_NG;
	strcpy(sa.sg_data, "neg:");
	sa.sg_len = 2 + strlen(sa.sg_data) + 1;
	if (connect(ds, (struct sockaddr *)&sa, sa.sg_len) < 0) {
		printf("RESULT connect-ds errno=%d\n", errno);
		exit(1);
	}
	STEP("data socket connected to neg");

	memset(&mkp, 0, sizeof(mkp));
	strcpy(mkp.type, "echo"); strcpy(mkp.ourhook, "b"); strcpy(mkp.peerhook, "inj");
	ngc(".", GEN_COOKIE, NGM_MKPEER, &mkp, sizeof(mkp));
	STEP("mkpeer S.b<->E1.inj (entry-attempt hook)");

	/* A. data injection: expect bounce straight back */
	memset(&sa, 0, sizeof(sa));
	sa.sg_family = AF_NG;
	strcpy(sa.sg_data, "b");
	sa.sg_len = 2 + strlen(sa.sg_data) + 1;
	if (sendto(ds, "PING", 4, 0, (struct sockaddr *)&sa, sa.sg_len) < 0) {
		printf("RESULT data-inject errno=%d\n", errno);
		exit(1);
	}
	STEP("A: data sent to E1.inj");
	pfd.fd = ds; pfd.events = POLLIN;
	n = poll(&pfd, 1, 800);
	if (n > 0) {
		n = recv(ds, buf, sizeof(buf), MSG_DONTWAIT);
		if (n > 0) {
			bounced = 1;
			printf("STEP A: %d bytes bounced back to sender: "
			    "\"%.*s\" (ring untouched)\n", n, n, buf);
		}
	}
	if (!bounced)
		printf("STEP A: no bounce observed (n=%d errno=%d)\n", n,
		    errno);
	fflush(stdout);

	/* B. FLOW-cookie control hook-addressed into the ring hooks */
	for (i = 0; i < 4; i++) {
		const char *addr = (i & 1) ? "e2:n" : "e1:q";
		t = ngc(addr, FLOW_COOKIE, 0, NULL, 0);
		pfd.fd = cs; pfd.events = POLLIN;
		n = poll(&pfd, 1, 800);
		if (n > 0) {
			n = recv(cs, &m, sizeof(m), MSG_DONTWAIT);
			if (n >= (int)HDRSZ && m.header.token == t) {
				resp++;
				printf("STEP B: %s -> response returned to "
				    "sender: token=%u flags=%#x cookie=%u "
				    "(no relay into ring)\n", addr,
				    m.header.token, m.header.flags,
				    m.header.typecookie);
			}
		} else {
			printf("STEP B: %s -> no response (n=%d)\n", addr, n);
		}
		fflush(stdout);
	}

	/* C. survive */
	sleep(2);
	printf("RESULT SURVIVED: bounced=%d responses=%d guest-alive\n",
	    bounced, resp);
	printf("RESULT echo-ring inert: no injection channel (data reflects "
	    "to sender; control responds to sender) -- DF-3557-style FLOW "
	    "relay absent in ng_echo\n");
	fflush(stdout);
	return (0);
}