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
51struct 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
72struct 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} __packed;
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
122struct 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} __packed;
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
139static inline void *
140read_sectn(FILE *fp, off_t sect, unsigned count, unsigned bytespersec)
141{
142	return (read_buf(fp, sect * bytespersec, bytespersec * count));
143}
144
145static inline void *
146read_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 */
157static int
158exfat_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
190static void
191convert_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 */
246static uint32_t
247exfat_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
265static void
266exfat_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
324int
325fstyp_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#endif
369
370out:
371	free(cksect);
372	free(ev);
373	return (error != 0);
374}
375