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