Coverage Report

Created: 2025-11-25 07:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
};