Coverage Report

Created: 2026-01-07 06:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/util-linux/libblkid/src/superblocks/vfat.c
Line
Count
Source
1
/*
2
 * Copyright (C) 1999 by Andries Brouwer
3
 * Copyright (C) 1999, 2000, 2003 by Theodore Ts'o
4
 * Copyright (C) 2001 by Andreas Dilger
5
 * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
6
 * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
7
 *
8
 * This file may be redistributed under the terms of the
9
 * GNU Lesser General Public License.
10
 */
11
#include <stdio.h>
12
#include <stdlib.h>
13
#include <unistd.h>
14
#include <string.h>
15
#include <errno.h>
16
#include <ctype.h>
17
#include <stdint.h>
18
#include <inttypes.h>
19
20
#include "pt-mbr.h"
21
#include "superblocks.h"
22
23
/* Yucky misaligned values */
24
struct vfat_super_block {
25
/* 00*/ unsigned char vs_ignored[3];
26
/* 03*/ unsigned char vs_sysid[8];
27
/* 0b*/ unsigned char vs_sector_size[2];
28
/* 0d*/ uint8_t   vs_cluster_size;
29
/* 0e*/ uint16_t  vs_reserved;
30
/* 10*/ uint8_t   vs_fats;
31
/* 11*/ unsigned char vs_dir_entries[2];
32
/* 13*/ unsigned char vs_sectors[2];
33
/* 15*/ unsigned char vs_media;
34
/* 16*/ uint16_t  vs_fat_length;
35
/* 18*/ uint16_t  vs_secs_track;
36
/* 1a*/ uint16_t  vs_heads;
37
/* 1c*/ uint32_t  vs_hidden;
38
/* 20*/ uint32_t  vs_total_sect;
39
/* 24*/ uint32_t  vs_fat32_length;
40
/* 28*/ uint16_t  vs_flags;
41
/* 2a*/ uint8_t   vs_version[2];
42
/* 2c*/ uint32_t  vs_root_cluster;
43
/* 30*/ uint16_t  vs_fsinfo_sector;
44
/* 32*/ uint16_t  vs_backup_boot;
45
/* 34*/ uint16_t  vs_reserved2[6];
46
/* 40*/ unsigned char vs_drive_number;
47
/* 41*/ unsigned char vs_boot_flags;
48
/* 42*/ unsigned char vs_ext_boot_sign; /* 0x28 - without vs_label/vs_magic; 0x29 - with */
49
/* 43*/ unsigned char vs_serno[4];
50
/* 47*/ unsigned char vs_label[11];
51
/* 52*/ unsigned char   vs_magic[8];
52
/* 5a*/ unsigned char vs_dummy2[0x1fe - 0x5a];
53
/*1fe*/ unsigned char vs_pmagic[2];
54
} __attribute__((packed));
55
56
/* Yucky misaligned values */
57
struct msdos_super_block {
58
/* DOS 2.0 BPB */
59
/* 00*/ unsigned char ms_ignored[3];
60
/* 03*/ unsigned char ms_sysid[8];
61
/* 0b*/ unsigned char ms_sector_size[2];
62
/* 0d*/ uint8_t   ms_cluster_size;
63
/* 0e*/ uint16_t  ms_reserved;
64
/* 10*/ uint8_t   ms_fats;
65
/* 11*/ unsigned char ms_dir_entries[2];
66
/* 13*/ unsigned char ms_sectors[2]; /* =0 iff V3 or later */
67
/* 15*/ unsigned char ms_media;
68
/* 16*/ uint16_t  ms_fat_length; /* Sectors per FAT */
69
/* DOS 3.0 BPB */
70
/* 18*/ uint16_t  ms_secs_track;
71
/* 1a*/ uint16_t  ms_heads;
72
/* 1c*/ uint32_t  ms_hidden;
73
/* DOS 3.31 BPB */
74
/* 20*/ uint32_t  ms_total_sect; /* iff ms_sectors == 0 */
75
/* DOS 3.4 EBPB */
76
/* 24*/ unsigned char ms_drive_number;
77
/* 25*/ unsigned char ms_boot_flags;
78
/* 26*/ unsigned char ms_ext_boot_sign; /* 0x28 - DOS 3.4 EBPB; 0x29 - DOS 4.0 EBPB */
79
/* 27*/ unsigned char ms_serno[4];
80
/* DOS 4.0 EBPB */
81
/* 2b*/ unsigned char ms_label[11];
82
/* 36*/ unsigned char   ms_magic[8];
83
/* padding */
84
/* 3e*/ unsigned char ms_dummy2[0x1fe - 0x3e];
85
/*1fe*/ unsigned char ms_pmagic[2];
86
} __attribute__((packed));
87
88
struct vfat_dir_entry {
89
  uint8_t   name[11];
90
  uint8_t   attr;
91
  uint16_t  time_creat;
92
  uint16_t  date_creat;
93
  uint16_t  time_acc;
94
  uint16_t  date_acc;
95
  uint16_t  cluster_high;
96
  uint16_t  time_write;
97
  uint16_t  date_write;
98
  uint16_t  cluster_low;
99
  uint32_t  size;
100
} __attribute__((packed));
101
102
struct fat32_fsinfo {
103
  uint8_t signature1[4];
104
  uint32_t reserved1[120];
105
  uint8_t signature2[4];
106
  uint32_t free_clusters;
107
  uint32_t next_cluster;
108
  uint32_t reserved2[4];
109
} __attribute__((packed));
110
111
/* maximum number of clusters */
112
197
#define FAT12_MAX 0xFF4
113
146
#define FAT16_MAX 0xFFF4
114
565
#define FAT32_MAX 0x0FFFFFF6
115
116
5.73k
#define FAT_ATTR_VOLUME_ID    0x08
117
2.86k
#define FAT_ATTR_DIR      0x10
118
3.20k
#define FAT_ATTR_LONG_NAME    0x0f
119
3.20k
#define FAT_ATTR_MASK     0x3f
120
98.2k
#define FAT_ENTRY_FREE      0xe5
121
122
static const char *no_name = "NO NAME    ";
123
124
#define unaligned_le16(x) \
125
2.88k
    (((unsigned char *) x)[0] + (((unsigned char *) x)[1] << 8))
126
127
/*
128
 * Look for LABEL (name) in the FAT root directory.
129
 */
130
static int search_fat_label(blkid_probe pr, uint64_t offset, uint32_t entries, unsigned char out[11])
131
6.17k
{
132
6.17k
  const struct vfat_dir_entry *ent, *dir = NULL;
133
6.17k
  uint32_t i;
134
135
6.17k
  DBG(LOWPROBE, ul_debug("\tlook for label in root-dir "
136
6.17k
      "(entries: %"PRIu32", offset: %"PRIu64")", entries, offset));
137
138
6.17k
  if (!blkid_probe_is_tiny(pr)) {
139
    /* large disk, read whole root directory */
140
6.17k
    dir = (struct vfat_dir_entry *)
141
6.17k
      blkid_probe_get_buffer(pr,
142
6.17k
          offset,
143
6.17k
          (uint64_t) entries *
144
6.17k
            sizeof(struct vfat_dir_entry));
145
6.17k
    if (!dir)
146
2.56k
      return 0;
147
6.17k
  }
148
149
101k
  for (i = 0; i < entries; i++) {
150
    /*
151
     * The root directory could be relatively large (4-16kB).
152
     * Fortunately, the LABEL is usually the first entry in the
153
     * directory. On tiny disks we call read() per entry.
154
     */
155
101k
    if (!dir)
156
0
      ent = (struct vfat_dir_entry *)
157
0
        blkid_probe_get_buffer(pr,
158
0
          (uint64_t) offset + (i *
159
0
            sizeof(struct vfat_dir_entry)),
160
0
          sizeof(struct vfat_dir_entry));
161
101k
    else
162
101k
      ent = &dir[i];
163
164
101k
    if (!ent || ent->name[0] == 0x00)
165
3.24k
      break;
166
167
98.2k
    if ((ent->name[0] == FAT_ENTRY_FREE) ||
168
97.5k
        (ent->cluster_high != 0 || ent->cluster_low != 0) ||
169
3.20k
        ((ent->attr & FAT_ATTR_MASK) == FAT_ATTR_LONG_NAME))
170
95.4k
      continue;
171
172
2.86k
    if ((ent->attr & (FAT_ATTR_VOLUME_ID | FAT_ATTR_DIR)) ==
173
2.86k
        FAT_ATTR_VOLUME_ID) {
174
22
      DBG(LOWPROBE, ul_debug("\tfound fs LABEL at entry %d", i));
175
22
      memcpy(out, ent->name, 11);
176
22
      if (out[0] == 0x05)
177
3
        out[0] = 0xE5;
178
22
      return 1;
179
22
    }
180
2.86k
  }
181
3.58k
  return 0;
182
3.60k
}
183
184
static int fat_valid_superblock(blkid_probe pr,
185
      const struct blkid_idmag *mag,
186
      const struct msdos_super_block *ms,
187
      const struct vfat_super_block *vs,
188
      uint32_t *cluster_count, uint32_t *fat_size,
189
      uint32_t *sect_count)
190
3.06k
{
191
3.06k
  uint16_t sector_size, dir_entries, reserved;
192
3.06k
  uint32_t __sect_count, __fat_size, dir_size, __cluster_count, fat_length;
193
3.06k
  uint32_t max_count;
194
195
  /* extra check for FATs without magic strings */
196
3.06k
  if (mag->len <= 2) {
197
    /* Old floppies have a valid MBR signature */
198
1.53k
    if (ms->ms_pmagic[0] != 0x55 || ms->ms_pmagic[1] != 0xAA)
199
165
      return 0;
200
201
    /*
202
     * OS/2 and apparently DFSee will place a FAT12/16-like
203
     * pseudo-superblock in the first 512 bytes of non-FAT
204
     * filesystems --- at least JFS and HPFS, and possibly others.
205
     * So we explicitly check for those filesystems at the
206
     * FAT12/16 filesystem magic field identifier, and if they are
207
     * present, we rule this out as a FAT filesystem, despite the
208
     * FAT-like pseudo-header.
209
     */
210
1.37k
    if ((memcmp(ms->ms_magic, "JFS     ", 8) == 0) ||
211
1.33k
        (memcmp(ms->ms_magic, "HPFS    ", 8) == 0)) {
212
103
      DBG(LOWPROBE, ul_debug("\tJFS/HPFS detected"));
213
103
      return 0;
214
103
    }
215
1.37k
  }
216
217
  /* fat counts(Linux kernel expects at least 1 FAT table) */
218
2.79k
  if (!ms->ms_fats)
219
700
    return 0;
220
2.09k
  if (!ms->ms_reserved)
221
97
    return 0;
222
1.99k
  if (!(0xf8 <= ms->ms_media || ms->ms_media == 0xf0))
223
790
    return 0;
224
1.20k
  if (!is_power_of_2(ms->ms_cluster_size))
225
305
    return 0;
226
227
900
  sector_size = unaligned_le16(&ms->ms_sector_size);
228
900
  if (!is_power_of_2(sector_size) ||
229
743
      sector_size < 512 || sector_size > 4096)
230
194
    return 0;
231
232
706
  dir_entries = unaligned_le16(&ms->ms_dir_entries);
233
706
  reserved =  le16_to_cpu(ms->ms_reserved);
234
706
  __sect_count = unaligned_le16(&ms->ms_sectors);
235
236
706
  if (__sect_count == 0)
237
374
    __sect_count = le32_to_cpu(ms->ms_total_sect);
238
239
706
  fat_length = le16_to_cpu(ms->ms_fat_length);
240
706
  if (fat_length == 0)
241
589
    fat_length = le32_to_cpu(vs->vs_fat32_length);
242
243
706
  __fat_size = fat_length * ms->ms_fats;
244
706
  dir_size = ((dir_entries * sizeof(struct vfat_dir_entry)) +
245
706
          (sector_size-1)) / sector_size;
246
247
706
  __cluster_count = (__sect_count - (reserved + __fat_size + dir_size)) /
248
706
              ms->ms_cluster_size;
249
706
  if (!ms->ms_fat_length && vs->vs_fat32_length)
250
565
    max_count = FAT32_MAX;
251
141
  else
252
141
    max_count = __cluster_count > FAT12_MAX ? FAT16_MAX : FAT12_MAX;
253
254
706
  if (__cluster_count > max_count)
255
99
    return 0;
256
257
607
  if (fat_size)
258
540
    *fat_size = __fat_size;
259
607
  if (cluster_count)
260
540
    *cluster_count = __cluster_count;
261
607
  if (sect_count)
262
540
    *sect_count = __sect_count;
263
264
607
  if (blkid_probe_is_bitlocker(pr))
265
13
    return 0;
266
267
594
  return 1; /* valid */
268
607
}
269
270
/* function prototype to avoid warnings (duplicate in partitions/dos.c) */
271
extern int blkid_probe_is_vfat(blkid_probe pr);
272
273
/*
274
 * This function is used by MBR partition table parser to avoid
275
 * misinterpretation of FAT filesystem.
276
 */
277
int blkid_probe_is_vfat(blkid_probe pr)
278
132
{
279
132
  const struct vfat_super_block *vs;
280
132
  const struct msdos_super_block *ms;
281
132
  const struct blkid_idmag *mag = NULL;
282
132
  int rc;
283
284
132
  rc = blkid_probe_get_idmag(pr, &vfat_idinfo, NULL, &mag);
285
132
  if (rc < 0)
286
0
    return rc; /* error */
287
132
  if (rc != BLKID_PROBE_OK || !mag)
288
0
    return 0;
289
290
132
  ms = blkid_probe_get_sb(pr, mag, struct msdos_super_block);
291
132
  if (!ms)
292
0
    return errno ? -errno : 0;
293
132
  vs = blkid_probe_get_sb(pr, mag, struct vfat_super_block);
294
132
  if (!vs)
295
0
    return errno ? -errno : 0;
296
297
132
  return fat_valid_superblock(pr, mag, ms, vs, NULL, NULL, NULL);
298
132
}
299
300
/* FAT label extraction from the root directory taken from Kay
301
 * Sievers's volume_id library */
302
static int probe_vfat(blkid_probe pr, const struct blkid_idmag *mag)
303
2.92k
{
304
2.92k
  const struct vfat_super_block *vs;
305
2.92k
  const struct msdos_super_block *ms;
306
2.92k
  const unsigned char *vol_label = NULL;
307
2.92k
  const unsigned char *boot_label = NULL;
308
2.92k
  const unsigned char *vol_serno = NULL;
309
2.92k
  unsigned char vol_label_buf[11];
310
2.92k
  uint16_t sector_size = 0, reserved;
311
2.92k
  uint32_t cluster_count, fat_size, sect_count;
312
2.92k
  const char *version = NULL;
313
314
2.92k
  ms = blkid_probe_get_sb(pr, mag, struct msdos_super_block);
315
2.92k
  if (!ms)
316
0
    return errno ? -errno : 1;
317
318
2.92k
  vs = blkid_probe_get_sb(pr, mag, struct vfat_super_block);
319
2.92k
  if (!vs)
320
0
    return errno ? -errno : 1;
321
322
2.92k
  if (!fat_valid_superblock(pr, mag, ms, vs, &cluster_count, &fat_size,
323
2.92k
        &sect_count))
324
2.39k
    return 1;
325
326
535
  sector_size = unaligned_le16(&ms->ms_sector_size);
327
535
  reserved =  le16_to_cpu(ms->ms_reserved);
328
329
535
  if (ms->ms_fat_length) {
330
    /* the label may be an attribute in the root directory */
331
38
    uint32_t root_start = (reserved + fat_size) * sector_size;
332
38
    uint32_t root_dir_entries = unaligned_le16(&vs->vs_dir_entries);
333
334
38
    if (search_fat_label(pr, root_start, root_dir_entries, vol_label_buf))
335
2
      vol_label = vol_label_buf;
336
337
38
    if (ms->ms_ext_boot_sign == 0x29)
338
2
      boot_label = ms->ms_label;
339
340
38
    if (ms->ms_ext_boot_sign == 0x28 || ms->ms_ext_boot_sign == 0x29)
341
3
      vol_serno = ms->ms_serno;
342
343
38
    blkid_probe_set_value(pr, "SEC_TYPE", (unsigned char *) "msdos",
344
38
                              sizeof("msdos"));
345
346
38
    if (cluster_count < FAT12_MAX)
347
15
      version = "FAT12";
348
23
    else if (cluster_count < FAT16_MAX)
349
23
      version = "FAT16";
350
351
497
  } else if (vs->vs_fat32_length) {
352
491
    const unsigned char *buf;
353
491
    uint16_t fsinfo_sect;
354
491
    int maxloop = 100;
355
356
    /* Search the FAT32 root dir for the label attribute */
357
491
    uint32_t buf_size = vs->vs_cluster_size * sector_size;
358
491
    uint32_t start_data_sect = reserved + fat_size;
359
491
    uint32_t entries = ((uint64_t) le32_to_cpu(vs->vs_fat32_length)
360
491
          * sector_size) / sizeof(uint32_t);
361
491
    uint32_t next = le32_to_cpu(vs->vs_root_cluster);
362
363
6.49k
    while (next && next < entries && --maxloop) {
364
6.13k
      uint32_t next_sect_off;
365
6.13k
      uint64_t next_off, fat_entry_off;
366
6.13k
      int count;
367
368
6.13k
      next_sect_off = (next - 2) * vs->vs_cluster_size;
369
6.13k
      next_off = (uint64_t)(start_data_sect + next_sect_off) *
370
6.13k
        sector_size;
371
372
6.13k
      count = buf_size / sizeof(struct vfat_dir_entry);
373
374
6.13k
      if (search_fat_label(pr, next_off, count, vol_label_buf)) {
375
20
        vol_label = vol_label_buf;
376
20
        break;
377
20
      }
378
379
      /* get FAT entry */
380
6.11k
      fat_entry_off = ((uint64_t) reserved * sector_size) +
381
6.11k
        (next * sizeof(uint32_t));
382
6.11k
      buf = blkid_probe_get_buffer(pr, fat_entry_off, buf_size);
383
6.11k
      if (buf == NULL)
384
115
        break;
385
386
      /* set next cluster */
387
6.00k
      next = le32_to_cpu(*((uint32_t *) buf)) & 0x0fffffff;
388
6.00k
    }
389
390
491
    version = "FAT32";
391
392
491
    if (vs->vs_ext_boot_sign == 0x29)
393
14
      boot_label = vs->vs_label;
394
395
491
    vol_serno = vs->vs_serno;
396
397
    /*
398
     * FAT32 should have a valid signature in the fsinfo block,
399
     * but also allow all bytes set to '\0', because some volumes
400
     * do not set the signature at all.
401
     */
402
491
    fsinfo_sect = le16_to_cpu(vs->vs_fsinfo_sector);
403
491
    if (fsinfo_sect) {
404
474
      struct fat32_fsinfo *fsinfo;
405
406
474
      buf = blkid_probe_get_buffer(pr,
407
474
          (uint64_t) fsinfo_sect * sector_size,
408
474
          sizeof(struct fat32_fsinfo));
409
474
      if (buf == NULL)
410
101
        return errno ? -errno : 1;
411
412
373
      fsinfo = (struct fat32_fsinfo *) buf;
413
373
      if (memcmp(fsinfo->signature1, "\x52\x52\x61\x41", 4) != 0 &&
414
370
          memcmp(fsinfo->signature1, "\x52\x52\x64\x41", 4) != 0 &&
415
365
          memcmp(fsinfo->signature1, "\x00\x00\x00\x00", 4) != 0)
416
230
        return 1;
417
143
      if (memcmp(fsinfo->signature2, "\x72\x72\x41\x61", 4) != 0 &&
418
142
          memcmp(fsinfo->signature2, "\x00\x00\x00\x00", 4) != 0)
419
72
        return 1;
420
143
    }
421
491
  }
422
423
132
  if (boot_label && memcmp(boot_label, no_name, 11) != 0)
424
8
    blkid_probe_set_id_label(pr, "LABEL_FATBOOT", boot_label, 11);
425
426
132
  if (vol_label)
427
5
    blkid_probe_set_label(pr, vol_label, 11);
428
429
  /* We can't just print them as %04X, because they are unaligned */
430
132
  if (vol_serno)
431
91
    blkid_probe_sprintf_uuid(pr, vol_serno, 4, "%02X%02X-%02X%02X",
432
91
      vol_serno[3], vol_serno[2], vol_serno[1], vol_serno[0]);
433
132
  if (version)
434
126
    blkid_probe_set_version(pr, version);
435
436
132
  blkid_probe_set_fsblocksize(pr, vs->vs_cluster_size * sector_size);
437
132
  blkid_probe_set_block_size(pr, sector_size);
438
132
  blkid_probe_set_fssize(pr, (uint64_t) sector_size * sect_count);
439
440
132
  return 0;
441
535
}
442
443
444
const struct blkid_idinfo vfat_idinfo =
445
{
446
  .name   = "vfat",
447
  .usage    = BLKID_USAGE_FILESYSTEM,
448
  .probefunc  = probe_vfat,
449
  .magics   =
450
  {
451
    { .magic = "MSWIN",    .len = 5, .sboff = 0x52 },
452
    { .magic = "FAT32   ", .len = 8, .sboff = 0x52 },
453
    { .magic = "MSDOS",    .len = 5, .sboff = 0x36 },
454
    { .magic = "FAT16   ", .len = 8, .sboff = 0x36 },
455
    { .magic = "FAT12   ", .len = 8, .sboff = 0x36 },
456
    { .magic = "FAT     ", .len = 8, .sboff = 0x36 },
457
    { .magic = "\353",     .len = 1, },
458
    { .magic = "\351",     .len = 1, },
459
    { .magic = "\125\252", .len = 2, .sboff = 0x1fe },
460
    { NULL }
461
  }
462
};
463