/src/util-linux/libblkid/src/superblocks/exfat.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright (C) 2010 Andrew Nayenko <resver@gmail.com> |
3 | | * |
4 | | * This file may be redistributed under the terms of the |
5 | | * GNU Lesser General Public License. |
6 | | */ |
7 | | #include "superblocks.h" |
8 | | |
9 | | struct exfat_super_block { |
10 | | uint8_t JumpBoot[3]; |
11 | | uint8_t FileSystemName[8]; |
12 | | uint8_t MustBeZero[53]; |
13 | | uint64_t PartitionOffset; |
14 | | uint64_t VolumeLength; |
15 | | uint32_t FatOffset; |
16 | | uint32_t FatLength; |
17 | | uint32_t ClusterHeapOffset; |
18 | | uint32_t ClusterCount; |
19 | | uint32_t FirstClusterOfRootDirectory; |
20 | | uint8_t VolumeSerialNumber[4]; |
21 | | struct { |
22 | | uint8_t vermin; |
23 | | uint8_t vermaj; |
24 | | } FileSystemRevision; |
25 | | uint16_t VolumeFlags; |
26 | | uint8_t BytesPerSectorShift; |
27 | | uint8_t SectorsPerClusterShift; |
28 | | uint8_t NumberOfFats; |
29 | | uint8_t DriveSelect; |
30 | | uint8_t PercentInUse; |
31 | | uint8_t Reserved[7]; |
32 | | uint8_t BootCode[390]; |
33 | | uint16_t BootSignature; |
34 | | } __attribute__((__packed__)); |
35 | | |
36 | | struct exfat_entry_label { |
37 | | uint8_t type; |
38 | | uint8_t length; |
39 | | uint8_t name[22]; |
40 | | uint8_t reserved[8]; |
41 | | } __attribute__((__packed__)); |
42 | | |
43 | 182 | #define BLOCK_SIZE(sb) ((sb)->BytesPerSectorShift < 32 ? (1u << (sb)->BytesPerSectorShift) : 0) |
44 | 205 | #define CLUSTER_SIZE(sb) ((sb)->SectorsPerClusterShift < 32 ? (BLOCK_SIZE(sb) << (sb)->SectorsPerClusterShift) : 0) |
45 | 0 | #define EXFAT_FIRST_DATA_CLUSTER 2 |
46 | 0 | #define EXFAT_LAST_DATA_CLUSTER 0xffffff6 |
47 | 0 | #define EXFAT_ENTRY_SIZE 32 |
48 | | |
49 | 0 | #define EXFAT_ENTRY_EOD 0x00 |
50 | 0 | #define EXFAT_ENTRY_LABEL 0x83 |
51 | | |
52 | 0 | #define EXFAT_MAX_DIR_SIZE (256 * 1024 * 1024) |
53 | | |
54 | | static uint64_t block_to_offset(const struct exfat_super_block *sb, |
55 | | uint64_t block) |
56 | 0 | { |
57 | 0 | return block << sb->BytesPerSectorShift; |
58 | 0 | } |
59 | | |
60 | | static uint64_t cluster_to_block(const struct exfat_super_block *sb, |
61 | | uint32_t cluster) |
62 | 0 | { |
63 | 0 | return le32_to_cpu(sb->ClusterHeapOffset) + |
64 | 0 | ((uint64_t) (cluster - EXFAT_FIRST_DATA_CLUSTER) |
65 | 0 | << sb->SectorsPerClusterShift); |
66 | 0 | } |
67 | | |
68 | | static uint64_t cluster_to_offset(const struct exfat_super_block *sb, |
69 | | uint32_t cluster) |
70 | 0 | { |
71 | 0 | return block_to_offset(sb, cluster_to_block(sb, cluster)); |
72 | 0 | } |
73 | | |
74 | | static uint32_t next_cluster(blkid_probe pr, |
75 | | const struct exfat_super_block *sb, uint32_t cluster) |
76 | 0 | { |
77 | 0 | uint32_t *nextp, next; |
78 | 0 | uint64_t fat_offset; |
79 | |
|
80 | 0 | fat_offset = block_to_offset(sb, le32_to_cpu(sb->FatOffset)) |
81 | 0 | + (uint64_t) cluster * sizeof(cluster); |
82 | 0 | nextp = (uint32_t *) blkid_probe_get_buffer(pr, fat_offset, |
83 | 0 | sizeof(uint32_t)); |
84 | 0 | if (!nextp) |
85 | 0 | return 0; |
86 | 0 | memcpy(&next, nextp, sizeof(next)); |
87 | 0 | return le32_to_cpu(next); |
88 | 0 | } |
89 | | |
90 | | static struct exfat_entry_label *find_label(blkid_probe pr, |
91 | | const struct exfat_super_block *sb) |
92 | 0 | { |
93 | 0 | uint32_t cluster = le32_to_cpu(sb->FirstClusterOfRootDirectory); |
94 | 0 | uint64_t offset = cluster_to_offset(sb, cluster); |
95 | 0 | uint8_t *entry; |
96 | 0 | const size_t max_iter = EXFAT_MAX_DIR_SIZE / EXFAT_ENTRY_SIZE; |
97 | 0 | size_t i = 0; |
98 | |
|
99 | 0 | for (; i < max_iter; i++) { |
100 | 0 | entry = (uint8_t *) blkid_probe_get_buffer(pr, offset, |
101 | 0 | EXFAT_ENTRY_SIZE); |
102 | 0 | if (!entry) |
103 | 0 | return NULL; |
104 | 0 | if (entry[0] == EXFAT_ENTRY_EOD) |
105 | 0 | return NULL; |
106 | 0 | if (entry[0] == EXFAT_ENTRY_LABEL) |
107 | 0 | return (struct exfat_entry_label *) entry; |
108 | | |
109 | 0 | offset += EXFAT_ENTRY_SIZE; |
110 | 0 | if (CLUSTER_SIZE(sb) && (offset % CLUSTER_SIZE(sb)) == 0) { |
111 | 0 | cluster = next_cluster(pr, sb, cluster); |
112 | 0 | if (cluster < EXFAT_FIRST_DATA_CLUSTER) |
113 | 0 | return NULL; |
114 | 0 | if (cluster > EXFAT_LAST_DATA_CLUSTER) |
115 | 0 | return NULL; |
116 | 0 | offset = cluster_to_offset(sb, cluster); |
117 | 0 | } |
118 | 0 | } |
119 | | |
120 | 0 | return NULL; |
121 | 0 | } |
122 | | |
123 | | /* From https://docs.microsoft.com/en-us/windows/win32/fileio/exfat-specification#34-main-and-backup-boot-checksum-sub-regions */ |
124 | | static uint32_t exfat_boot_checksum(const unsigned char *sectors, |
125 | | size_t sector_size) |
126 | 11 | { |
127 | 11 | uint32_t n_bytes = sector_size * 11; |
128 | 11 | uint32_t checksum = 0; |
129 | | |
130 | 61.9k | for (size_t i = 0; i < n_bytes; i++) { |
131 | 61.9k | if ((i == 106) || (i == 107) || (i == 112)) |
132 | 33 | continue; |
133 | | |
134 | 61.9k | checksum = ((checksum & 1) ? 0x80000000 : 0) + (checksum >> 1) |
135 | 61.9k | + (uint32_t) sectors[i]; |
136 | 61.9k | } |
137 | | |
138 | 11 | return checksum; |
139 | 11 | } |
140 | | |
141 | | static int exfat_validate_checksum(blkid_probe pr, |
142 | | const struct exfat_super_block *sb) |
143 | 11 | { |
144 | 11 | size_t sector_size = BLOCK_SIZE(sb); |
145 | | /* 11 sectors will be checksummed, the 12th contains the expected */ |
146 | 11 | const unsigned char *data = blkid_probe_get_buffer(pr, 0, sector_size * 12); |
147 | 11 | if (!data) |
148 | 0 | return 0; |
149 | | |
150 | 11 | uint32_t checksum = exfat_boot_checksum(data, sector_size); |
151 | | |
152 | | /* The expected checksum is repeated, check all of them */ |
153 | 11 | for (size_t i = 0; i < sector_size / sizeof(uint32_t); i++) { |
154 | 11 | size_t offset = sector_size * 11 + i * 4; |
155 | 11 | uint32_t *expected_addr = (uint32_t *) &data[offset]; |
156 | 11 | uint32_t expected = le32_to_cpu(*expected_addr); |
157 | 11 | if (!blkid_probe_verify_csum(pr, checksum, expected)) |
158 | 11 | return 0; |
159 | 11 | }; |
160 | |
|
161 | 0 | return 1; |
162 | 11 | } |
163 | | |
164 | 274 | #define in_range_inclusive(val, start, stop) (val >= start && val <= stop) |
165 | | |
166 | | static int exfat_valid_superblock(blkid_probe pr, const struct exfat_super_block *sb) |
167 | 288 | { |
168 | 288 | if (le16_to_cpu(sb->BootSignature) != 0xAA55) |
169 | 83 | return 0; |
170 | | |
171 | 205 | if (!CLUSTER_SIZE(sb)) |
172 | 49 | return 0; |
173 | | |
174 | 156 | if (memcmp(sb->JumpBoot, "\xEB\x76\x90", 3) != 0) |
175 | 76 | return 0; |
176 | | |
177 | 80 | if (memcmp(sb->FileSystemName, "EXFAT ", 8) != 0) |
178 | 0 | return 0; |
179 | | |
180 | 3.41k | for (size_t i = 0; i < sizeof(sb->MustBeZero); i++) |
181 | 3.35k | if (sb->MustBeZero[i] != 0x00) |
182 | 19 | return 0; |
183 | | |
184 | 61 | if (!in_range_inclusive(sb->NumberOfFats, 1, 2)) |
185 | 6 | return 0; |
186 | | |
187 | 55 | if (!in_range_inclusive(sb->BytesPerSectorShift, 9, 12)) |
188 | 5 | return 0; |
189 | | |
190 | 50 | if (!in_range_inclusive(sb->SectorsPerClusterShift, |
191 | 50 | 0, |
192 | 50 | 25 - sb->BytesPerSectorShift)) |
193 | 0 | return 0; |
194 | | |
195 | 50 | if (!in_range_inclusive(le32_to_cpu(sb->FatOffset), |
196 | 50 | 24, |
197 | 50 | le32_to_cpu(sb->ClusterHeapOffset) - |
198 | 50 | (le32_to_cpu(sb->FatLength) * sb->NumberOfFats))) |
199 | 16 | return 0; |
200 | | |
201 | 34 | if (!in_range_inclusive(le32_to_cpu(sb->ClusterHeapOffset), |
202 | 34 | le32_to_cpu(sb->FatOffset) + |
203 | 34 | le32_to_cpu(sb->FatLength) * sb->NumberOfFats, |
204 | 34 | 1U << (32 - 1))) |
205 | 10 | return 0; |
206 | | |
207 | 24 | if (!in_range_inclusive(le32_to_cpu(sb->FirstClusterOfRootDirectory), |
208 | 24 | 2, |
209 | 24 | le32_to_cpu(sb->ClusterCount) + 1)) |
210 | 13 | return 0; |
211 | | |
212 | 11 | if (!exfat_validate_checksum(pr, sb)) |
213 | 11 | return 0; |
214 | | |
215 | 0 | return 1; |
216 | 11 | } |
217 | | |
218 | | /* function prototype to avoid warnings (duplicate in partitions/dos.c) */ |
219 | | extern int blkid_probe_is_exfat(blkid_probe pr); |
220 | | |
221 | | /* |
222 | | * This function is used by MBR partition table parser to avoid |
223 | | * misinterpretation of exFAT filesystem. |
224 | | */ |
225 | | int blkid_probe_is_exfat(blkid_probe pr) |
226 | 71 | { |
227 | 71 | const struct exfat_super_block *sb; |
228 | 71 | const struct blkid_idmag *mag = NULL; |
229 | 71 | int rc; |
230 | | |
231 | 71 | rc = blkid_probe_get_idmag(pr, &vfat_idinfo, NULL, &mag); |
232 | 71 | if (rc < 0) |
233 | 0 | return rc; /* error */ |
234 | 71 | if (rc != BLKID_PROBE_OK || !mag) |
235 | 0 | return 0; |
236 | | |
237 | 71 | sb = blkid_probe_get_sb(pr, mag, struct exfat_super_block); |
238 | 71 | if (!sb) |
239 | 0 | return 0; |
240 | | |
241 | 71 | if (memcmp(sb->FileSystemName, "EXFAT ", 8) != 0) |
242 | 68 | return 0; |
243 | | |
244 | 3 | return exfat_valid_superblock(pr, sb); |
245 | 71 | } |
246 | | |
247 | | static int probe_exfat(blkid_probe pr, const struct blkid_idmag *mag) |
248 | 285 | { |
249 | 285 | const struct exfat_super_block *sb; |
250 | 285 | struct exfat_entry_label *label; |
251 | | |
252 | 285 | sb = blkid_probe_get_sb(pr, mag, struct exfat_super_block); |
253 | 285 | if (!sb) |
254 | 0 | return errno ? -errno : BLKID_PROBE_NONE; |
255 | | |
256 | 285 | if (!exfat_valid_superblock(pr, sb)) |
257 | 285 | return BLKID_PROBE_NONE; |
258 | | |
259 | 0 | label = find_label(pr, sb); |
260 | 0 | if (label) |
261 | 0 | blkid_probe_set_utf8label(pr, label->name, |
262 | 0 | min((size_t) label->length * 2, sizeof(label->name)), |
263 | 0 | UL_ENCODE_UTF16LE); |
264 | 0 | else if (errno) |
265 | 0 | return -errno; |
266 | | |
267 | 0 | blkid_probe_sprintf_uuid(pr, sb->VolumeSerialNumber, 4, |
268 | 0 | "%02hhX%02hhX-%02hhX%02hhX", |
269 | 0 | sb->VolumeSerialNumber[3], sb->VolumeSerialNumber[2], |
270 | 0 | sb->VolumeSerialNumber[1], sb->VolumeSerialNumber[0]); |
271 | |
|
272 | 0 | blkid_probe_sprintf_version(pr, "%u.%u", |
273 | 0 | sb->FileSystemRevision.vermaj, sb->FileSystemRevision.vermin); |
274 | |
|
275 | 0 | blkid_probe_set_fsblocksize(pr, BLOCK_SIZE(sb)); |
276 | 0 | blkid_probe_set_block_size(pr, BLOCK_SIZE(sb)); |
277 | 0 | blkid_probe_set_fssize(pr, BLOCK_SIZE(sb) * le64_to_cpu(sb->VolumeLength)); |
278 | |
|
279 | 0 | return BLKID_PROBE_OK; |
280 | 0 | } |
281 | | |
282 | | const struct blkid_idinfo exfat_idinfo = |
283 | | { |
284 | | .name = "exfat", |
285 | | .usage = BLKID_USAGE_FILESYSTEM, |
286 | | .probefunc = probe_exfat, |
287 | | .magics = |
288 | | { |
289 | | { .magic = "EXFAT ", .len = 8, .sboff = 3 }, |
290 | | { NULL } |
291 | | } |
292 | | }; |