1 /* $NetBSD: exfat.c,v 1.4 2021/01/24 14:37:32 tkusumi Exp $ */ 2 3 /* 4 * Copyright (c) 2017 Conrad Meyer <cem@FreeBSD.org> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 #include <sys/cdefs.h> 29 __RCSID("$NetBSD: exfat.c,v 1.4 2021/01/24 14:37:32 tkusumi Exp $"); 30 31 #include <sys/param.h> 32 #include <sys/endian.h> 33 34 #include <assert.h> 35 #include <err.h> 36 #include <errno.h> 37 #include <iconv.h> 38 #include <stdbool.h> 39 #include <stdint.h> 40 #include <stdio.h> 41 #include <stdlib.h> 42 #include <string.h> 43 44 #include "fstyp.h" 45 46 /* 47 * https://docs.microsoft.com/en-us/windows/win32/fileio/exfat-specification 48 */ 49 50 struct exfat_vbr { 51 char ev_jmp[3]; 52 char ev_fsname[8]; 53 char ev_zeros[53]; 54 uint64_t ev_part_offset; 55 uint64_t ev_vol_length; 56 uint32_t ev_fat_offset; 57 uint32_t ev_fat_length; 58 uint32_t ev_cluster_offset; 59 uint32_t ev_cluster_count; 60 uint32_t ev_rootdir_cluster; 61 uint32_t ev_vol_serial; 62 uint16_t ev_fs_revision; 63 uint16_t ev_vol_flags; 64 uint8_t ev_log_bytes_per_sect; 65 uint8_t ev_log_sect_per_clust; 66 uint8_t ev_num_fats; 67 uint8_t ev_drive_sel; 68 uint8_t ev_percent_used; 69 } __packed; 70 71 struct exfat_dirent { 72 uint8_t xde_type; 73 #define XDE_TYPE_INUSE_MASK 0x80 /* 1=in use */ 74 #define XDE_TYPE_INUSE_SHIFT 7 75 #define XDE_TYPE_CATEGORY_MASK 0x40 /* 0=primary */ 76 #define XDE_TYPE_CATEGORY_SHIFT 6 77 #define XDE_TYPE_IMPORTNC_MASK 0x20 /* 0=critical */ 78 #define XDE_TYPE_IMPORTNC_SHIFT 5 79 #define XDE_TYPE_CODE_MASK 0x1f 80 /* InUse=0, ..., TypeCode=0: EOD. */ 81 #define XDE_TYPE_EOD 0x00 82 #define XDE_TYPE_ALLOC_BITMAP (XDE_TYPE_INUSE_MASK | 0x01) 83 #define XDE_TYPE_UPCASE_TABLE (XDE_TYPE_INUSE_MASK | 0x02) 84 #define XDE_TYPE_VOL_LABEL (XDE_TYPE_INUSE_MASK | 0x03) 85 #define XDE_TYPE_FILE (XDE_TYPE_INUSE_MASK | 0x05) 86 #define XDE_TYPE_VOL_GUID (XDE_TYPE_INUSE_MASK | XDE_TYPE_IMPORTNC_MASK) 87 #define XDE_TYPE_STREAM_EXT (XDE_TYPE_INUSE_MASK | XDE_TYPE_CATEGORY_MASK) 88 #define XDE_TYPE_FILE_NAME (XDE_TYPE_INUSE_MASK | XDE_TYPE_CATEGORY_MASK | 0x01) 89 #define XDE_TYPE_VENDOR (XDE_TYPE_INUSE_MASK | XDE_TYPE_CATEGORY_MASK | XDE_TYPE_IMPORTNC_MASK) 90 #define XDE_TYPE_VENDOR_ALLOC (XDE_TYPE_INUSE_MASK | XDE_TYPE_CATEGORY_MASK | XDE_TYPE_IMPORTNC_MASK | 0x01) 91 union { 92 uint8_t xde_generic_[19]; 93 struct exde_primary { 94 /* 95 * Count of "secondary" dirents following this one. 96 * 97 * A single logical entity may be composed of a 98 * sequence of several dirents, starting with a primary 99 * one; the rest are secondary dirents. 100 */ 101 uint8_t xde_secondary_count_; 102 uint16_t xde_set_chksum_; 103 uint16_t xde_prim_flags_; 104 uint8_t xde_prim_generic_[14]; 105 } __packed xde_primary_; 106 struct exde_secondary { 107 uint8_t xde_sec_flags_; 108 uint8_t xde_sec_generic_[18]; 109 } __packed xde_secondary_; 110 } u; 111 uint32_t xde_first_cluster; 112 uint64_t xde_data_len; 113 } __packed; 114 #define xde_generic u.xde_generic_ 115 #define xde_secondary_count u.xde_primary_.xde_secondary_count 116 #define xde_set_chksum u.xde_primary_.xde_set_chksum_ 117 #define xde_prim_flags u.xde_primary_.xde_prim_flags_ 118 #define xde_sec_flags u.xde_secondary_.xde_sec_flags_ 119 _Static_assert(sizeof(struct exfat_dirent) == 32, "spec"); 120 121 struct exfat_de_label { 122 uint8_t xdel_type; /* XDE_TYPE_VOL_LABEL */ 123 uint8_t xdel_char_cnt; /* Length of UCS-2 label */ 124 uint16_t xdel_vol_lbl[11]; 125 uint8_t xdel_reserved[8]; 126 } __packed; 127 _Static_assert(sizeof(struct exfat_de_label) == 32, "spec"); 128 129 #define MAIN_BOOT_REGION_SECT 0 130 #define BACKUP_BOOT_REGION_SECT 12 131 132 #define SUBREGION_CHKSUM_SECT 11 133 134 #define FIRST_CLUSTER 2 135 #define BAD_BLOCK_SENTINEL 0xfffffff7u 136 #define END_CLUSTER_SENTINEL 0xffffffffu 137 138 static inline void * 139 read_sectn(FILE *fp, off_t sect, unsigned count, unsigned bytespersec) 140 { 141 return (read_buf(fp, sect * bytespersec, bytespersec * count)); 142 } 143 144 static inline void * 145 read_sect(FILE *fp, off_t sect, unsigned bytespersec) 146 { 147 return (read_sectn(fp, sect, 1, bytespersec)); 148 } 149 150 /* 151 * Compute the byte-by-byte multi-sector checksum of the given boot region 152 * (MAIN or BACKUP), for a given bytespersec (typically 512 or 4096). 153 * 154 * Endian-safe; result is host endian. 155 */ 156 static int 157 exfat_compute_boot_chksum(FILE *fp, unsigned region, unsigned bytespersec, 158 uint32_t *result) 159 { 160 unsigned char *sector; 161 unsigned n, sect; 162 uint32_t checksum; 163 164 checksum = 0; 165 for (sect = 0; sect < 11; sect++) { 166 sector = read_sect(fp, region + sect, bytespersec); 167 if (sector == NULL) 168 return (ENXIO); 169 for (n = 0; n < bytespersec; n++) { 170 if (sect == 0) { 171 switch (n) { 172 case 106: 173 case 107: 174 case 112: 175 continue; 176 } 177 } 178 checksum = ((checksum & 1) ? 0x80000000u : 0u) + 179 (checksum >> 1) + (uint32_t)sector[n]; 180 } 181 free(sector); 182 } 183 184 *result = checksum; 185 return (0); 186 } 187 188 static void 189 convert_label(const uint16_t *ucs2label /* LE */, unsigned ucs2len, char 190 *label_out, size_t label_sz) 191 { 192 const char *label; 193 char *label_out_orig; 194 iconv_t cd; 195 size_t srcleft, rc; 196 197 /* Currently hardcoded in fstyp.c as 256 or so. */ 198 assert(label_sz > 1); 199 200 if (ucs2len == 0) { 201 /* 202 * Kind of seems bogus, but the spec allows an empty label 203 * entry with the same meaning as no label. 204 */ 205 return; 206 } 207 208 if (ucs2len > 11) { 209 warnx("exfat: Bogus volume label length: %u", ucs2len); 210 return; 211 } 212 213 /* dstname="" means convert to the current locale. */ 214 cd = iconv_open("", EXFAT_ENC); 215 if (cd == (iconv_t)-1) { 216 warn("exfat: Could not open iconv"); 217 return; 218 } 219 220 label_out_orig = label_out; 221 222 /* Dummy up the byte pointer and byte length iconv's API wants. */ 223 label = (const void *)ucs2label; 224 srcleft = ucs2len * sizeof(*ucs2label); 225 226 rc = iconv(cd, __UNCONST(&label), &srcleft, &label_out, 227 &label_sz); 228 if (rc == (size_t)-1) { 229 warn("exfat: iconv()"); 230 *label_out_orig = '\0'; 231 } else { 232 /* NUL-terminate result (iconv advances label_out). */ 233 if (label_sz == 0) 234 label_out--; 235 *label_out = '\0'; 236 } 237 238 iconv_close(cd); 239 } 240 241 /* 242 * Using the FAT table, look up the next cluster in this chain. 243 */ 244 static uint32_t 245 exfat_fat_next(FILE *fp, const struct exfat_vbr *ev, unsigned BPS, 246 uint32_t cluster) 247 { 248 uint32_t fat_offset_sect, clsect, clsectoff; 249 uint32_t *fatsect, nextclust; 250 251 fat_offset_sect = le32toh(ev->ev_fat_offset); 252 clsect = fat_offset_sect + (cluster / (BPS / (uint32_t)sizeof(cluster))); 253 clsectoff = (cluster % (BPS / (uint32_t)sizeof(cluster))); 254 255 /* XXX This is pretty wasteful without a block cache for the FAT. */ 256 fatsect = read_sect(fp, clsect, BPS); 257 nextclust = le32toh(fatsect[clsectoff]); 258 free(fatsect); 259 260 return (nextclust); 261 } 262 263 static void 264 exfat_find_label(FILE *fp, const struct exfat_vbr *ev, unsigned BPS, 265 char *label_out, size_t label_sz) 266 { 267 uint32_t rootdir_cluster, sects_per_clust, cluster_offset_sect; 268 off_t rootdir_sect; 269 struct exfat_dirent *declust, *it; 270 271 cluster_offset_sect = le32toh(ev->ev_cluster_offset); 272 rootdir_cluster = le32toh(ev->ev_rootdir_cluster); 273 sects_per_clust = (1u << ev->ev_log_sect_per_clust); 274 275 if (rootdir_cluster < FIRST_CLUSTER) { 276 warnx("%s: invalid rootdir cluster %u < %d", __func__, 277 rootdir_cluster, FIRST_CLUSTER); 278 return; 279 } 280 281 282 for (; rootdir_cluster != END_CLUSTER_SENTINEL; 283 rootdir_cluster = exfat_fat_next(fp, ev, BPS, rootdir_cluster)) { 284 if (rootdir_cluster == BAD_BLOCK_SENTINEL) { 285 warnx("%s: Bogus bad block in root directory chain", 286 __func__); 287 return; 288 } 289 290 rootdir_sect = (rootdir_cluster - FIRST_CLUSTER) * 291 sects_per_clust + cluster_offset_sect; 292 declust = read_sectn(fp, rootdir_sect, sects_per_clust, BPS); 293 for (it = declust; 294 it < declust + (sects_per_clust * BPS / sizeof(*it)); it++) { 295 bool eod = false; 296 297 /* 298 * Simplistic directory traversal; doesn't do any 299 * validation of "MUST" requirements in spec. 300 */ 301 switch (it->xde_type) { 302 case XDE_TYPE_EOD: 303 eod = true; 304 break; 305 case XDE_TYPE_VOL_LABEL: { 306 struct exfat_de_label *lde = (void*)it; 307 convert_label(lde->xdel_vol_lbl, 308 lde->xdel_char_cnt, label_out, label_sz); 309 free(declust); 310 return; 311 } 312 } 313 314 if (eod) 315 break; 316 } 317 free(declust); 318 } 319 } 320 321 int 322 fstyp_exfat(FILE *fp, char *label, size_t size) 323 { 324 struct exfat_vbr *ev; 325 uint32_t *cksect; 326 unsigned bytespersec; 327 uint32_t chksum; 328 int error; 329 330 error = 1; 331 cksect = NULL; 332 ev = (struct exfat_vbr *)read_buf(fp, 0, 512); 333 if (ev == NULL || strncmp(ev->ev_fsname, "EXFAT ", 8) != 0) 334 goto out; 335 336 if (ev->ev_log_bytes_per_sect < 9 || ev->ev_log_bytes_per_sect > 12) { 337 warnx("exfat: Invalid BytesPerSectorShift"); 338 goto out; 339 } 340 341 bytespersec = (1u << ev->ev_log_bytes_per_sect); 342 343 error = exfat_compute_boot_chksum(fp, MAIN_BOOT_REGION_SECT, 344 bytespersec, &chksum); 345 if (error != 0) 346 goto out; 347 348 cksect = read_sect(fp, MAIN_BOOT_REGION_SECT + SUBREGION_CHKSUM_SECT, 349 bytespersec); 350 351 /* 352 * Technically the entire sector should be full of repeating 4-byte 353 * checksum pattern, but we only verify the first. 354 */ 355 if (chksum != le32toh(cksect[0])) { 356 warnx("exfat: Found checksum 0x%08x != computed 0x%08x", 357 le32toh(cksect[0]), chksum); 358 error = 1; 359 goto out; 360 } 361 362 if (show_label) 363 exfat_find_label(fp, ev, bytespersec, label, size); 364 365 out: 366 free(cksect); 367 free(ev); 368 return (error != 0); 369 } 370