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