View | Details | Raw Unified | Return to bug 242225 | Differences between
and this patch

Collapse All | Expand All

(-)b/usr.sbin/fstyp/exfat.c (-2 / +288 lines)
Lines 25-41 Link Here
25
 */
25
 */
26
26
27
#include <sys/cdefs.h>
27
#include <sys/cdefs.h>
28
__FBSDID("$FreeBSD$");
28
__FBSDID("$FreeBSD$");
29
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>
30
#include <stdint.h>
38
#include <stdint.h>
31
#include <stdio.h>
39
#include <stdio.h>
32
#include <stdlib.h>
40
#include <stdlib.h>
33
#include <string.h>
41
#include <string.h>
34
42
35
#include "fstyp.h"
43
#include "fstyp.h"
36
44
45
/*
46
 * https://docs.microsoft.com/en-us/windows/win32/fileio/exfat-specification
47
 */
48
37
struct exfat_vbr {
49
struct exfat_vbr {
38
	char		ev_jmp[3];
50
	char		ev_jmp[3];
39
	char		ev_fsname[8];
51
	char		ev_fsname[8];
40
	char		ev_zeros[53];
52
	char		ev_zeros[53];
41
	uint64_t	ev_part_offset;
53
	uint64_t	ev_part_offset;
Lines 53-75 struct exfat_vbr { Link Here
53
	uint8_t		ev_num_fats;
65
	uint8_t		ev_num_fats;
54
	uint8_t		ev_drive_sel;
66
	uint8_t		ev_drive_sel;
55
	uint8_t		ev_percent_used;
67
	uint8_t		ev_percent_used;
56
} __packed;
68
} __packed;
57
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
			// XXX: count of following dirents, "secondary"
94
			uint8_t		xde_secondary_count_;
95
			uint16_t	xde_set_chksum_;
96
			uint16_t	xde_prim_flags_;
97
			uint8_t		xde_prim_generic_[14];
98
		} __packed xde_primary_;
99
		struct exde_secondary {
100
			uint8_t		xde_sec_flags_;
101
			uint8_t		xde_sec_generic_[18];
102
		} __packed xde_secondary_;
103
	} u;
104
	uint32_t	xde_first_cluster;
105
	uint64_t	xde_data_len;
106
} __packed;
107
#define	xde_generic		u.xde_generic_
108
#define	xde_secondary_count	u.xde_primary_.xde_secondary_count
109
#define	xde_set_chksum		u.xde_primary_.xde_set_chksum_
110
#define	xde_prim_flags		u.xde_primary_.xde_prim_flags_
111
#define	xde_sec_flags		u.xde_secondary_.xde_sec_flags_
112
_Static_assert(sizeof(struct exfat_dirent) == 32, "spec");
113
114
struct exfat_de_label {
115
	uint8_t		xdel_type;	/* XDE_TYPE_VOL_LABEL */
116
	uint8_t		xdel_char_cnt;	/* Length of UCS-2 label */
117
	uint16_t	xdel_vol_lbl[11];
118
	uint8_t		xdel_reserved[8];
119
} __packed;
120
_Static_assert(sizeof(struct exfat_de_label) == 32, "spec");
121
122
#define	MAIN_BOOT_REGION_SECT	0
123
#define	BACKUP_BOOT_REGION_SECT	12
124
125
#define	SUBREGION_CHKSUM_SECT	11
126
127
#define	FIRST_CLUSTER		2
128
#define	BAD_BLOCK_SENTINEL	0xfffffff7u
129
#define	END_CLUSTER_SENTINEL	0xffffffffu
130
131
static inline void *
132
read_sectn(FILE *fp, off_t sect, unsigned count, unsigned bytespersec)
133
{
134
	return (read_buf(fp, sect * bytespersec, bytespersec * count));
135
}
136
137
static inline void *
138
read_sect(FILE *fp, off_t sect, unsigned bytespersec)
139
{
140
	return (read_sectn(fp, sect, 1, bytespersec));
141
}
142
143
/*
144
 * Compute the byte-by-byte multi-sector checksum of the given boot region
145
 * (MAIN or BACKUP), for a given bytespersec (typically 512 or 4096).
146
 *
147
 * Endian-safe; result is host endian.
148
 */
149
static int
150
exfat_compute_boot_chksum(FILE *fp, unsigned region, unsigned bytespersec,
151
    uint32_t *result)
152
{
153
	unsigned char *sector;
154
	unsigned n, sect;
155
	uint32_t checksum;
156
157
	checksum = 0;
158
	for (sect = 0; sect < 11; sect++) {
159
		sector = read_sect(fp, region + sect, bytespersec);
160
		if (sector == NULL)
161
			return (ENXIO);
162
		for (n = 0; n < bytespersec; n++) {
163
			if (sect == 0) {
164
				switch (n) {
165
				case 106:
166
				case 107:
167
				case 112:
168
					continue;
169
				}
170
			}
171
			checksum = ((checksum & 1) ? 0x80000000u : 0u) +
172
			    (checksum >> 1) + (uint32_t)sector[n];
173
		}
174
		free(sector);
175
	}
176
177
	*result = checksum;
178
	return (0);
179
}
180
181
static void
182
convert_label(const uint16_t *ucs2label /* LE */, unsigned ucs2len, char
183
    *label_out, size_t label_sz)
184
{
185
	const char *label;
186
	char *label_out_orig;
187
	iconv_t cd;
188
	size_t srcleft, rc;
189
190
	/* Currently hardcoded in fstype.c as 256 or so. */
191
	assert(label_sz > 1);
192
193
	if (ucs2len == 0) {
194
		/*
195
		 * Kind of seems bogus, but the spec allows an empty label
196
		 * entry with the same meaning as no label.
197
		 */
198
		return;
199
	}
200
201
	if (ucs2len > 11) {
202
		warnx("exfat: Bogus volume label length: %u", ucs2len);
203
		return;
204
	}
205
206
	/* dstname="" means convert to the current locale. */
207
	cd = iconv_open("", EXFAT_ENC);
208
	if (cd == (iconv_t)-1) {
209
		warn("exfat: Could not open iconv");
210
		return;
211
	}
212
213
	label_out_orig = label_out;
214
215
	/* Dummy up the byte pointer and byte length iconv's API wants. */
216
	label = (const void *)ucs2label;
217
	srcleft = ucs2len * sizeof(*ucs2label);
218
219
	rc = iconv(cd, __DECONST(char **, &label), &srcleft, &label_out,
220
	    &label_sz);
221
	if (rc == (size_t)-1) {
222
		warn("exfat: iconv()");
223
		*label_out_orig = '\0';
224
	} else {
225
		/* NUL-terminate result (iconv advances label_out). */
226
		if (label_sz == 0)
227
			label_out--;
228
		*label_out = '\0';
229
	}
230
231
	iconv_close(cd);
232
}
233
234
/*
235
 * Using the FAT table, look up the next cluster in this chain.
236
 */
237
static uint32_t
238
exfat_fat_next(FILE *fp, const struct exfat_vbr *ev, unsigned BPS,
239
    uint32_t cluster)
240
{
241
	uint32_t fat_offset_sect, clsect, clsectoff;
242
	uint32_t *fatsect, nextclust;
243
244
	fat_offset_sect = le32toh(ev->ev_fat_offset);
245
	clsect = fat_offset_sect + (cluster / (BPS / sizeof(cluster)));
246
	clsectoff = (cluster % (BPS / sizeof(cluster)));
247
248
	/* XXX This is pretty wasteful without a block cache for the FAT. */
249
	fatsect = read_sect(fp, clsect, BPS);
250
	nextclust = le32toh(fatsect[clsectoff]);
251
	free(fatsect);
252
253
	return (nextclust);
254
}
255
256
static void
257
exfat_find_label(FILE *fp, const struct exfat_vbr *ev, unsigned BPS,
258
    char *label_out, size_t label_sz)
259
{
260
	uint32_t rootdir_cluster, sects_per_clust, cluster_offset_sect;
261
	off_t rootdir_sect;
262
	struct exfat_dirent *declust, *it;
263
264
	cluster_offset_sect = le32toh(ev->ev_cluster_offset);
265
	rootdir_cluster = le32toh(ev->ev_rootdir_cluster);
266
	sects_per_clust = (1u << ev->ev_log_sect_per_clust);
267
268
	if (rootdir_cluster < FIRST_CLUSTER) {
269
		warnx("%s: invalid rootdir cluster %u < %d", __func__,
270
		    rootdir_cluster, FIRST_CLUSTER);
271
		return;
272
	}
273
274
275
	for (; rootdir_cluster != END_CLUSTER_SENTINEL;
276
	    rootdir_cluster = exfat_fat_next(fp, ev, BPS, rootdir_cluster)) {
277
		if (rootdir_cluster == BAD_BLOCK_SENTINEL) {
278
			warnx("%s: Bogus bad block in root directory chain",
279
			    __func__);
280
			return;
281
		}
282
283
		rootdir_sect = (rootdir_cluster - FIRST_CLUSTER) *
284
		    sects_per_clust + cluster_offset_sect;
285
		declust = read_sectn(fp, rootdir_sect, sects_per_clust, BPS);
286
		for (it = declust;
287
		    it < declust + (sects_per_clust * BPS / sizeof(*it)); it++) {
288
			bool eod = false;
289
290
			/*
291
			 * Simplistic directory traversal doesn't do any
292
			 * validation of MUST requirements in spec.
293
			 */
294
			switch (it->xde_type) {
295
			case XDE_TYPE_EOD:
296
				eod = true;
297
				break;
298
			case XDE_TYPE_VOL_LABEL: {
299
				struct exfat_de_label *lde = (void*)it;
300
				convert_label(lde->xdel_vol_lbl,
301
				    lde->xdel_char_cnt, label_out, label_sz);
302
				free(declust);
303
				return;
304
				}
305
			}
306
307
			if (eod)
308
				break;
309
		}
310
		free(declust);
311
	}
312
}
313
58
int
314
int
59
fstyp_exfat(FILE *fp, char *label, size_t size)
315
fstyp_exfat(FILE *fp, char *label, size_t size)
60
{
316
{
61
	struct exfat_vbr *ev;
317
	struct exfat_vbr *ev;
318
	uint32_t *cksect;
319
	unsigned bytespersec;
320
	uint32_t chksum;
321
	int error;
62
322
323
	cksect = NULL;
63
	ev = (struct exfat_vbr *)read_buf(fp, 0, 512);
324
	ev = (struct exfat_vbr *)read_buf(fp, 0, 512);
64
	if (ev == NULL || strncmp(ev->ev_fsname, "EXFAT   ", 8) != 0)
325
	if (ev == NULL || strncmp(ev->ev_fsname, "EXFAT   ", 8) != 0)
65
		goto fail;
326
		goto fail;
66
327
328
	if (ev->ev_log_bytes_per_sect < 9 || ev->ev_log_bytes_per_sect > 12) {
329
		warnx("exfat: Invalid BytesPerSectorShift");
330
		goto done;
331
	}
332
333
	bytespersec = (1u << ev->ev_log_bytes_per_sect);
334
335
	error = exfat_compute_boot_chksum(fp, MAIN_BOOT_REGION_SECT,
336
	    bytespersec, &chksum);
337
	if (error != 0)
338
		goto done;
339
340
	cksect = read_sect(fp, MAIN_BOOT_REGION_SECT + SUBREGION_CHKSUM_SECT,
341
	    bytespersec);
342
67
	/*
343
	/*
68
	 * Reading the volume label requires walking the root directory to look
344
	 * Technically the entire sector should be full of repeating 4-byte
69
	 * for a special label file.  Left as an exercise for the reader.
345
	 * checksum pattern, but we only verify the first.
70
	 */
346
	 */
347
	if (chksum != le32toh(cksect[0])) {
348
		warnx("exfat: Found checksum 0x%08x != computed 0x%08x",
349
		    le32toh(cksect[0]), chksum);
350
		goto done;
351
	}
352
353
	exfat_find_label(fp, ev, bytespersec, label, size);
354
355
done:
356
	free(cksect);
71
	free(ev);
357
	free(ev);
72
	return (0);
358
	return (0);
73
359
74
fail:
360
fail:
75
	free(ev);
361
	free(ev);
(-)b/usr.sbin/fstyp/fstyp.c (-9 / +32 lines)
Lines 36-45 __FBSDID("$FreeBSD$"); Link Here
36
#include <sys/ioctl.h>
36
#include <sys/ioctl.h>
37
#include <sys/stat.h>
37
#include <sys/stat.h>
38
#include <capsicum_helpers.h>
38
#include <capsicum_helpers.h>
39
#include <err.h>
39
#include <err.h>
40
#include <errno.h>
40
#include <errno.h>
41
#include <iconv.h>
42
#include <locale.h>
41
#include <stdbool.h>
43
#include <stdbool.h>
42
#include <stddef.h>
44
#include <stddef.h>
43
#include <stdio.h>
45
#include <stdio.h>
44
#include <stdlib.h>
46
#include <stdlib.h>
45
#include <string.h>
47
#include <string.h>
Lines 54-75 typedef int (*fstyp_function)(FILE *, char *, size_t); Link Here
54
56
55
static struct {
57
static struct {
56
	const char	*name;
58
	const char	*name;
57
	fstyp_function	function;
59
	fstyp_function	function;
58
	bool		unmountable;
60
	bool		unmountable;
61
	char		*precache_encoding;
59
} fstypes[] = {
62
} fstypes[] = {
60
	{ "cd9660", &fstyp_cd9660, false },
63
	{ "cd9660", &fstyp_cd9660, false, NULL },
61
	{ "exfat", &fstyp_exfat, false },
64
	{ "exfat", &fstyp_exfat, false, EXFAT_ENC },
62
	{ "ext2fs", &fstyp_ext2fs, false },
65
	{ "ext2fs", &fstyp_ext2fs, false, NULL },
63
	{ "geli", &fstyp_geli, true },
66
	{ "geli", &fstyp_geli, true, NULL },
64
	{ "msdosfs", &fstyp_msdosfs, false },
67
	{ "msdosfs", &fstyp_msdosfs, false, NULL },
65
	{ "ntfs", &fstyp_ntfs, false },
68
	{ "ntfs", &fstyp_ntfs, false, NULL },
66
	{ "ufs", &fstyp_ufs, false },
69
	{ "ufs", &fstyp_ufs, false, NULL },
67
#ifdef HAVE_ZFS
70
#ifdef HAVE_ZFS
68
	{ "zfs", &fstyp_zfs, true },
71
	{ "zfs", &fstyp_zfs, true, NULL },
69
#endif
72
#endif
70
	{ NULL, NULL, NULL }
73
	{ NULL, NULL, NULL, NULL }
71
};
74
};
72
75
73
void *
76
void *
74
read_buf(FILE *fp, off_t off, size_t len)
77
read_buf(FILE *fp, off_t off, size_t len)
75
{
78
{
Lines 186-195 main(int argc, char **argv) Link Here
186
	if (argc != 1)
189
	if (argc != 1)
187
		usage();
190
		usage();
188
191
189
	path = argv[0];
192
	path = argv[0];
190
193
194
	if (setlocale(LC_CTYPE, "") == NULL)
195
		err(1, "setlocale");
196
	caph_cache_catpages();
197
198
	/* Cache iconv conversion data before entering capability mode. */
199
	if (show_label) {
200
		for (i = 0; i < nitems(fstypes); i++) {
201
			iconv_t cd;
202
203
			if (fstypes[i].precache_encoding == NULL)
204
				continue;
205
			cd = iconv_open("", fstypes[i].precache_encoding);
206
			if (cd == (iconv_t)-1)
207
				err(1, "%s: iconv_open %s", fstypes[i].name,
208
				    fstypes[i].precache_encoding);
209
			/* Iconv keeps a small cache of unused encodings. */
210
			iconv_close(cd);
211
		}
212
	}
213
191
	fp = fopen(path, "r");
214
	fp = fopen(path, "r");
192
	if (fp == NULL)
215
	if (fp == NULL)
193
		err(1, "%s", path);
216
		err(1, "%s", path);
194
217
195
	if (caph_enter() < 0)
218
	if (caph_enter() < 0)
(-)b/usr.sbin/fstyp/fstyp.h (+2 lines)
Lines 31-40 Link Here
31
31
32
#ifndef FSTYP_H
32
#ifndef FSTYP_H
33
#define	FSTYP_H
33
#define	FSTYP_H
34
34
35
#define	MIN(a,b) (((a)<(b))?(a):(b))
35
#define	MIN(a,b) (((a)<(b))?(a):(b))
36
/* The spec doesn't seem to permit UTF-16 surrogates.  Definitely LE. */
37
#define	EXFAT_ENC	"UCS-2LE"
36
38
37
void	*read_buf(FILE *fp, off_t off, size_t len);
39
void	*read_buf(FILE *fp, off_t off, size_t len);
38
char	*checked_strdup(const char *s);
40
char	*checked_strdup(const char *s);
39
void	rtrim(char *label, size_t size);
41
void	rtrim(char *label, size_t size);
40
42
(-)b/usr.sbin/fstyp/tests/fstyp_test.sh (-1 / +10 lines)
Lines 66-75 exfat_head() { Link Here
66
exfat_body() {
66
exfat_body() {
67
	bzcat $(atf_get_srcdir)/dfr-01-xfat.img.bz2 > exfat.img
67
	bzcat $(atf_get_srcdir)/dfr-01-xfat.img.bz2 > exfat.img
68
	atf_check -s exit:0 -o inline:"exfat\n" fstyp -u exfat.img
68
	atf_check -s exit:0 -o inline:"exfat\n" fstyp -u exfat.img
69
}
69
}
70
70
71
atf_test_case exfat_label
72
exfat_label_head() {
73
	atf_set "descr" "fstyp(8) can read exFAT labels"
74
}
75
exfat_label_body() {
76
	bzcat $(atf_get_srcdir)/dfr-01-xfat.img.bz2 > exfat.img
77
	atf_check -s exit:0 -o inline:"exfat exFat\n" fstyp -u -l exfat.img
78
}
79
71
atf_test_case empty
80
atf_test_case empty
72
empty_head() {
81
empty_head() {
73
	atf_set "descr" "fstyp(8) should fail on an empty file"
82
	atf_set "descr" "fstyp(8) should fail on an empty file"
74
}
83
}
75
empty_body() {
84
empty_body() {
Lines 251-260 atf_init_test_cases() { Link Here
251
	atf_add_test_case cd9660
260
	atf_add_test_case cd9660
252
	atf_add_test_case cd9660_label
261
	atf_add_test_case cd9660_label
253
	atf_add_test_case dir
262
	atf_add_test_case dir
254
	atf_add_test_case empty
263
	atf_add_test_case empty
255
	atf_add_test_case exfat
264
	atf_add_test_case exfat
265
	atf_add_test_case exfat_label
256
	atf_add_test_case ext2
266
	atf_add_test_case ext2
257
	atf_add_test_case ext3
267
	atf_add_test_case ext3
258
	atf_add_test_case ext4
268
	atf_add_test_case ext4
259
	atf_add_test_case ext4_label
269
	atf_add_test_case ext4_label
260
	atf_add_test_case fat12
270
	atf_add_test_case fat12
261
- 

Return to bug 242225