/* * Copyright (C) 2010 Andrew Nayenko * * This file may be redistributed under the terms of the * GNU Lesser General Public License. */ #include "superblocks.h" struct exfat_super_block { uint8_t JumpBoot[3]; uint8_t FileSystemName[8]; uint8_t MustBeZero[53]; uint64_t PartitionOffset; uint64_t VolumeLength; uint32_t FatOffset; uint32_t FatLength; uint32_t ClusterHeapOffset; uint32_t ClusterCount; uint32_t FirstClusterOfRootDirectory; uint8_t VolumeSerialNumber[4]; struct { uint8_t vermin; uint8_t vermaj; } FileSystemRevision; uint16_t VolumeFlags; uint8_t BytesPerSectorShift; uint8_t SectorsPerClusterShift; uint8_t NumberOfFats; uint8_t DriveSelect; uint8_t PercentInUse; uint8_t Reserved[7]; uint8_t BootCode[390]; uint16_t BootSignature; } __attribute__((__packed__)); struct exfat_entry_label { uint8_t type; uint8_t length; uint8_t name[22]; uint8_t reserved[8]; } __attribute__((__packed__)); #define BLOCK_SIZE(sb) ((sb)->BytesPerSectorShift < 32 ? (1u << (sb)->BytesPerSectorShift) : 0) #define CLUSTER_SIZE(sb) ((sb)->SectorsPerClusterShift < 32 ? (BLOCK_SIZE(sb) << (sb)->SectorsPerClusterShift) : 0) #define EXFAT_FIRST_DATA_CLUSTER 2 #define EXFAT_LAST_DATA_CLUSTER 0xffffff6 #define EXFAT_ENTRY_SIZE 32 #define EXFAT_ENTRY_EOD 0x00 #define EXFAT_ENTRY_LABEL 0x83 #define EXFAT_MAX_DIR_SIZE (256 * 1024 * 1024) static uint64_t block_to_offset(const struct exfat_super_block *sb, uint64_t block) { return block << sb->BytesPerSectorShift; } static uint64_t cluster_to_block(const struct exfat_super_block *sb, uint32_t cluster) { return le32_to_cpu(sb->ClusterHeapOffset) + ((uint64_t) (cluster - EXFAT_FIRST_DATA_CLUSTER) << sb->SectorsPerClusterShift); } static uint64_t cluster_to_offset(const struct exfat_super_block *sb, uint32_t cluster) { return block_to_offset(sb, cluster_to_block(sb, cluster)); } static uint32_t next_cluster(blkid_probe pr, const struct exfat_super_block *sb, uint32_t cluster) { uint32_t *nextp, next; uint64_t fat_offset; fat_offset = block_to_offset(sb, le32_to_cpu(sb->FatOffset)) + (uint64_t) cluster * sizeof(cluster); nextp = (uint32_t *) blkid_probe_get_buffer(pr, fat_offset, sizeof(uint32_t)); if (!nextp) return 0; memcpy(&next, nextp, sizeof(next)); return le32_to_cpu(next); } static struct exfat_entry_label *find_label(blkid_probe pr, const struct exfat_super_block *sb) { uint32_t cluster = le32_to_cpu(sb->FirstClusterOfRootDirectory); uint64_t offset = cluster_to_offset(sb, cluster); uint8_t *entry; const size_t max_iter = EXFAT_MAX_DIR_SIZE / EXFAT_ENTRY_SIZE; size_t i = 0; for (; i < max_iter; i++) { entry = (uint8_t *) blkid_probe_get_buffer(pr, offset, EXFAT_ENTRY_SIZE); if (!entry) return NULL; if (entry[0] == EXFAT_ENTRY_EOD) return NULL; if (entry[0] == EXFAT_ENTRY_LABEL) return (struct exfat_entry_label *) entry; offset += EXFAT_ENTRY_SIZE; if (CLUSTER_SIZE(sb) && (offset % CLUSTER_SIZE(sb)) == 0) { cluster = next_cluster(pr, sb, cluster); if (cluster < EXFAT_FIRST_DATA_CLUSTER) return NULL; if (cluster > EXFAT_LAST_DATA_CLUSTER) return NULL; offset = cluster_to_offset(sb, cluster); } } return NULL; } /* From https://docs.microsoft.com/en-us/windows/win32/fileio/exfat-specification#34-main-and-backup-boot-checksum-sub-regions */ static uint32_t exfat_boot_checksum(unsigned char *sectors, size_t sector_size) { uint32_t n_bytes = sector_size * 11; uint32_t checksum = 0; for (size_t i = 0; i < n_bytes; i++) { if ((i == 106) || (i == 107) || (i == 112)) continue; checksum = ((checksum & 1) ? 0x80000000 : 0) + (checksum >> 1) + (uint32_t) sectors[i]; } return checksum; } static int exfat_validate_checksum(blkid_probe pr, const struct exfat_super_block *sb) { size_t sector_size = BLOCK_SIZE(sb); /* 11 sectors will be checksummed, the 12th contains the expected */ unsigned char *data = blkid_probe_get_buffer(pr, 0, sector_size * 12); if (!data) return 0; uint32_t checksum = exfat_boot_checksum(data, sector_size); /* The expected checksum is repeated, check all of them */ for (size_t i = 0; i < sector_size / sizeof(uint32_t); i++) { size_t offset = sector_size * 11 + i * 4; uint32_t *expected_addr = (uint32_t *) &data[offset]; uint32_t expected = le32_to_cpu(*expected_addr); if (!blkid_probe_verify_csum(pr, checksum, expected)) return 0; }; return 1; } static int exfat_valid_superblock(blkid_probe pr, const struct exfat_super_block *sb) { if (le16_to_cpu(sb->BootSignature) != 0xAA55) return 0; if (!CLUSTER_SIZE(sb)) return 0; if (memcmp(sb->JumpBoot, "\xEB\x76\x90", 3) != 0) return 0; for (size_t i = 0; i < sizeof(sb->MustBeZero); i++) if (sb->MustBeZero[i] != 0x00) return 0; if (!exfat_validate_checksum(pr, sb)) return 0; return 1; } /* function prototype to avoid warnings (duplicate in partitions/dos.c) */ extern int blkid_probe_is_exfat(blkid_probe pr); /* * This function is used by MBR partition table parser to avoid * misinterpretation of exFAT filesystem. */ int blkid_probe_is_exfat(blkid_probe pr) { struct exfat_super_block *sb; const struct blkid_idmag *mag = NULL; int rc; rc = blkid_probe_get_idmag(pr, &vfat_idinfo, NULL, &mag); if (rc < 0) return rc; /* error */ if (rc != BLKID_PROBE_OK || !mag) return 0; sb = blkid_probe_get_sb(pr, mag, struct exfat_super_block); if (!sb) return 0; if (memcmp(sb->FileSystemName, "EXFAT ", 8) != 0) return 0; return exfat_valid_superblock(pr, sb); } static int probe_exfat(blkid_probe pr, const struct blkid_idmag *mag) { struct exfat_super_block *sb; struct exfat_entry_label *label; sb = blkid_probe_get_sb(pr, mag, struct exfat_super_block); if (!sb) return errno ? -errno : BLKID_PROBE_NONE; if (!exfat_valid_superblock(pr, sb)) return BLKID_PROBE_NONE; label = find_label(pr, sb); if (label) blkid_probe_set_utf8label(pr, label->name, min((size_t) label->length * 2, sizeof(label->name)), UL_ENCODE_UTF16LE); else if (errno) return -errno; blkid_probe_sprintf_uuid(pr, sb->VolumeSerialNumber, 4, "%02hhX%02hhX-%02hhX%02hhX", sb->VolumeSerialNumber[3], sb->VolumeSerialNumber[2], sb->VolumeSerialNumber[1], sb->VolumeSerialNumber[0]); blkid_probe_sprintf_version(pr, "%u.%u", sb->FileSystemRevision.vermaj, sb->FileSystemRevision.vermin); blkid_probe_set_fsblocksize(pr, BLOCK_SIZE(sb)); blkid_probe_set_block_size(pr, BLOCK_SIZE(sb)); blkid_probe_set_fssize(pr, BLOCK_SIZE(sb) * le64_to_cpu(sb->VolumeLength)); return BLKID_PROBE_OK; } const struct blkid_idinfo exfat_idinfo = { .name = "exfat", .usage = BLKID_USAGE_FILESYSTEM, .probefunc = probe_exfat, .magics = { { .magic = "EXFAT ", .len = 8, .sboff = 3 }, { NULL } } };