// https://syzkaller.appspot.com/bug?id=df2556db46e5e06146f1ffe53e18f3f1ca4eb72b #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define LOOP_DEV_SIZE (16 * 1024 * 1024) static inline unsigned int encode_dev(unsigned int maj, unsigned int min) { return (min & 0xff) | (maj << 8) | ((min & ~0xff) << 12); } static void ensure_block_node(const char *path, dev_t dev) { struct stat st; int res = stat(path, &st); if (res < 0) { if (errno == ENOENT) { res = mknod(path, S_IFBLK | 0660, dev); if (res < 0) { printf("[-] Failed to mknod %s: %s\n", path, strerror(errno)); exit(1); } printf("[+] mknod %s successful.\n", path); } else { printf("[-] Failed to stat %s: %s\n", path, strerror(errno)); exit(1); } } } int main(void) { setvbuf(stdout, NULL, _IONBF, 0); printf("[*] Starting reproducer for GPF in raid5_quiesce...\n"); ensure_block_node("/dev/loop0", makedev(LOOP_MAJOR, 0)); ensure_block_node("/dev/loop1", makedev(LOOP_MAJOR, 1)); ensure_block_node("/dev/md0", makedev(MD_MAJOR, 0)); /* Create memfd backing files */ int mfd0 = memfd_create("loop0_backing", 0); if (mfd0 < 0) { printf("[-] Failed to memfd_create mfd0: %s\n", strerror(errno)); exit(1); } printf("[+] memfd_create mfd0 successful.\n"); int res = ftruncate(mfd0, LOOP_DEV_SIZE); if (res < 0) { printf("[-] Failed to ftruncate mfd0: %s\n", strerror(errno)); exit(1); } printf("[+] ftruncate mfd0 successful (size=%d).\n", LOOP_DEV_SIZE); int mfd1 = memfd_create("loop1_backing", 0); if (mfd1 < 0) { printf("[-] Failed to memfd_create mfd1: %s\n", strerror(errno)); exit(1); } printf("[+] memfd_create mfd1 successful.\n"); res = ftruncate(mfd1, LOOP_DEV_SIZE); if (res < 0) { printf("[-] Failed to ftruncate mfd1: %s\n", strerror(errno)); exit(1); } printf("[+] ftruncate mfd1 successful (size=%d).\n", LOOP_DEV_SIZE); /* Clear and attach /dev/loop0 */ int lfd0 = open("/dev/loop0", O_RDWR); if (lfd0 < 0) { printf("[-] Failed to open /dev/loop0: %s\n", strerror(errno)); exit(1); } ioctl(lfd0, LOOP_CLR_FD, 0); close(lfd0); lfd0 = open("/dev/loop0", O_RDWR); if (lfd0 < 0) { printf("[-] Failed to reopen /dev/loop0: %s\n", strerror(errno)); exit(1); } res = ioctl(lfd0, LOOP_SET_FD, mfd0); if (res < 0) { printf("[-] Failed to LOOP_SET_FD on /dev/loop0: %s\n", strerror(errno)); exit(1); } printf("[+] LOOP_SET_FD /dev/loop0 successful.\n"); struct stat st0; res = fstat(lfd0, &st0); if (res < 0) { printf("[-] Failed to fstat /dev/loop0: %s\n", strerror(errno)); exit(1); } dev_t dev0 = st0.st_rdev; res = close(lfd0); if (res < 0) { printf("[-] Failed to close /dev/loop0: %s\n", strerror(errno)); exit(1); } printf("[+] Closed /dev/loop0 (dev=%u:%u).\n", major(dev0), minor(dev0)); /* Clear and attach /dev/loop1 */ int lfd1 = open("/dev/loop1", O_RDWR); if (lfd1 < 0) { printf("[-] Failed to open /dev/loop1: %s\n", strerror(errno)); exit(1); } ioctl(lfd1, LOOP_CLR_FD, 0); close(lfd1); lfd1 = open("/dev/loop1", O_RDWR); if (lfd1 < 0) { printf("[-] Failed to reopen /dev/loop1: %s\n", strerror(errno)); exit(1); } res = ioctl(lfd1, LOOP_SET_FD, mfd1); if (res < 0) { printf("[-] Failed to LOOP_SET_FD on /dev/loop1: %s\n", strerror(errno)); exit(1); } printf("[+] LOOP_SET_FD /dev/loop1 successful.\n"); struct stat st1; res = fstat(lfd1, &st1); if (res < 0) { printf("[-] Failed to fstat /dev/loop1: %s\n", strerror(errno)); exit(1); } dev_t dev1 = st1.st_rdev; res = close(lfd1); if (res < 0) { printf("[-] Failed to close /dev/loop1: %s\n", strerror(errno)); exit(1); } printf("[+] Closed /dev/loop1 (dev=%u:%u).\n", major(dev1), minor(dev1)); /* Ensure mddev node exists in kernel */ int na_fd = open("/sys/module/md_mod/parameters/new_array", O_WRONLY); if (na_fd >= 0) { write(na_fd, "md0\n", 4); close(na_fd); } /* Open /dev/md0 */ int mdfd = open("/dev/md0", O_RDWR); if (mdfd < 0) { printf("[-] Failed to open /dev/md0: %s\n", strerror(errno)); exit(1); } printf("[+] open /dev/md0 successful.\n"); /* Stop previous array if running */ ioctl(mdfd, STOP_ARRAY, 0); /* Configure non-persistent RAID0 */ mdu_array_info_t ainfo; memset(&ainfo, 0, sizeof(ainfo)); ainfo.major_version = 0; ainfo.minor_version = 90; ainfo.patch_version = 0; ainfo.level = 0; ainfo.size = 0; ainfo.nr_disks = 2; ainfo.raid_disks = 2; ainfo.not_persistent = 1; ainfo.state = (1 << MD_SB_CLEAN); ainfo.active_disks = 2; ainfo.working_disks = 2; ainfo.chunk_size = 64 * 1024; res = ioctl(mdfd, SET_ARRAY_INFO, &ainfo); if (res < 0) { printf("[-] Failed to ioctl SET_ARRAY_INFO: %s\n", strerror(errno)); exit(1); } printf("[+] ioctl SET_ARRAY_INFO successful.\n"); /* Add disk 0 */ mdu_disk_info_t dinfo; memset(&dinfo, 0, sizeof(dinfo)); dinfo.number = 0; dinfo.major = major(dev0); dinfo.minor = minor(dev0); dinfo.raid_disk = 0; dinfo.state = (1 << MD_DISK_ACTIVE) | (1 << MD_DISK_SYNC); res = ioctl(mdfd, ADD_NEW_DISK, &dinfo); if (res < 0) { printf("[-] Failed to ioctl ADD_NEW_DISK 0: %s\n", strerror(errno)); exit(1); } printf("[+] ioctl ADD_NEW_DISK 0 successful.\n"); /* Add disk 1 */ memset(&dinfo, 0, sizeof(dinfo)); dinfo.number = 1; dinfo.major = major(dev1); dinfo.minor = minor(dev1); dinfo.raid_disk = 1; dinfo.state = (1 << MD_DISK_ACTIVE) | (1 << MD_DISK_SYNC); res = ioctl(mdfd, ADD_NEW_DISK, &dinfo); if (res < 0) { printf("[-] Failed to ioctl ADD_NEW_DISK 1: %s\n", strerror(errno)); exit(1); } printf("[+] ioctl ADD_NEW_DISK 1 successful.\n"); /* Start array */ res = ioctl(mdfd, RUN_ARRAY, NULL); if (res < 0) { printf("[-] Failed to ioctl RUN_ARRAY: %s\n", strerror(errno)); exit(1); } printf("[+] ioctl RUN_ARRAY successful.\n"); usleep(50000); /* Mark disk 0 faulty to set MD_BROKEN on RAID0 */ printf("[*] Marking disk 0 faulty to trigger MD_BROKEN...\n"); int sfd = open("/sys/block/md0/md/dev-loop0/state", O_WRONLY); if (sfd < 0) { sfd = open("/sys/block/md0/md/rd0/state", O_WRONLY); } if (sfd >= 0) { res = write(sfd, "faulty", 6); if (res < 0 && errno != EBUSY) { printf("[-] Writing 'faulty' to sysfs state: %s\n", strerror(errno)); } else { printf("[+] Wrote 'faulty' to sysfs disk state (res=%d, errno=%s).\n", res, strerror(errno)); } close(sfd); } unsigned int encoded_dev0 = encode_dev(major(dev0), minor(dev0)); res = ioctl(mdfd, SET_DISK_FAULTY, (unsigned long)encoded_dev0); if (res < 0 && errno != EBUSY) { printf("[-] ioctl SET_DISK_FAULTY: %s\n", strerror(errno)); } else { printf("[+] ioctl SET_DISK_FAULTY executed (res=%d, errno=%s).\n", res, strerror(errno)); } /* Close /dev/md0 so atomic_read(&mddev->openers) == 0 */ res = close(mdfd); if (res < 0) { printf("[-] Failed to close /dev/md0: %s\n", strerror(errno)); exit(1); } mdfd = -1; printf("[+] Closed /dev/md0 (openers count is now 0).\n"); usleep(50000); /* Switch level to raid5 via sysfs: takeover succeeds, raid5_run fails due to MD_BROKEN, * leaves mddev->pers = &raid5_personality and mddev->private = NULL */ printf("[*] Triggering takeover to RAID5 via /sys/block/md0/md/level...\n"); int lvl_fd = open("/sys/block/md0/md/level", O_WRONLY); if (lvl_fd < 0) { printf("[-] Failed to open /sys/block/md0/md/level: %s\n", strerror(errno)); exit(1); } res = write(lvl_fd, "raid5\n", 6); if (res < 0) { printf("[-] Failed to write 'raid5' to /sys/block/md0/md/level: %s\n", strerror(errno)); exit(1); } printf("[+] Write 'raid5' to /sys/block/md0/md/level successful.\n"); close(lvl_fd); usleep(50000); /* Write 'readonly' to array_state: triggers md_set_readonly -> __md_stop_writes -> raid5_quiesce * which dereferences NULL mddev->private */ printf("[*] Writing 'readonly' to /sys/block/md0/md/array_state...\n"); int as_fd = open("/sys/block/md0/md/array_state", O_WRONLY); if (as_fd < 0) { printf("[-] Failed to open /sys/block/md0/md/array_state: %s\n", strerror(errno)); exit(1); } res = write(as_fd, "readonly", 8); if (res < 0) { printf("[-] write to /sys/block/md0/md/array_state returned: %s\n", strerror(errno)); } else { printf("[+] write 'readonly' to /sys/block/md0/md/array_state returned %d\n", res); } close(as_fd); printf("[*] Done.\n"); return 0; }