hc
2024-03-22 a0752693d998599af469473b8dc239ef973a012f
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
/** @file
  Superblock managing routines
 
  Copyright (c) 2021 Pedro Falcato All rights reserved.
  SPDX-License-Identifier: BSD-2-Clause-Patent
**/
 
#include "Ext4Dxe.h"
 
STATIC CONST UINT32  gSupportedCompatFeat = EXT4_FEATURE_COMPAT_EXT_ATTR;
 
STATIC CONST UINT32  gSupportedRoCompatFeat =
  EXT4_FEATURE_RO_COMPAT_DIR_NLINK | EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE |
  EXT4_FEATURE_RO_COMPAT_HUGE_FILE | EXT4_FEATURE_RO_COMPAT_LARGE_FILE |
  EXT4_FEATURE_RO_COMPAT_GDT_CSUM | EXT4_FEATURE_RO_COMPAT_METADATA_CSUM | EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER;
 
STATIC CONST UINT32  gSupportedIncompatFeat =
  EXT4_FEATURE_INCOMPAT_64BIT | EXT4_FEATURE_INCOMPAT_DIRDATA |
  EXT4_FEATURE_INCOMPAT_FLEX_BG | EXT4_FEATURE_INCOMPAT_FILETYPE |
  EXT4_FEATURE_INCOMPAT_EXTENTS | EXT4_FEATURE_INCOMPAT_LARGEDIR |
  EXT4_FEATURE_INCOMPAT_MMP | EXT4_FEATURE_INCOMPAT_RECOVER;
 
// Future features that may be nice additions in the future:
// 1) Btree support: Required for write support and would speed up lookups in large directories.
// 2) meta_bg: Required to mount meta_bg-enabled partitions.
 
// Note: We ignore MMP because it's impossible that it's mapped elsewhere,
// I think (unless there's some sort of network setup where we're accessing a remote partition).
 
// Note on corruption signaling:
// We (Ext4Dxe) could signal corruption by setting s_state to |= EXT4_FS_STATE_ERRORS_DETECTED.
// I've decided against that, because right now the driver is read-only, and
// that would mean we would need to writeback the superblock. If something like
// this is desired, it's fairly trivial to look for EFI_VOLUME_CORRUPTED
// references and add some Ext4SignalCorruption function + function call.
 
/**
   Checks the superblock's magic value.
 
   @param[in] DiskIo      Pointer to the DiskIo.
   @param[in] BlockIo     Pointer to the BlockIo.
 
   @returns Whether the partition has a valid EXT4 superblock magic value.
**/
BOOLEAN
Ext4SuperblockCheckMagic (
  IN EFI_DISK_IO_PROTOCOL   *DiskIo,
  IN EFI_BLOCK_IO_PROTOCOL  *BlockIo
  )
{
  UINT16      Magic;
  EFI_STATUS  Status;
 
  Status = DiskIo->ReadDisk (
                     DiskIo,
                     BlockIo->Media->MediaId,
                     EXT4_SUPERBLOCK_OFFSET + OFFSET_OF (EXT4_SUPERBLOCK, s_magic),
                     sizeof (Magic),
                     &Magic
                     );
  if (EFI_ERROR (Status)) {
    return FALSE;
  }
 
  if (Magic != EXT4_SIGNATURE) {
    return FALSE;
  }
 
  return TRUE;
}
 
/**
   Does brief validation of the ext4 superblock.
 
   @param[in] Sb     Pointer to the read superblock.
 
   @return TRUE if a valid ext4 superblock, else FALSE.
**/
BOOLEAN
Ext4SuperblockValidate (
  CONST EXT4_SUPERBLOCK  *Sb
  )
{
  if (Sb->s_magic != EXT4_SIGNATURE) {
    return FALSE;
  }
 
  if (Sb->s_rev_level != EXT4_DYNAMIC_REV && Sb->s_rev_level != EXT4_GOOD_OLD_REV) {
    return FALSE;
  }
 
  if ((Sb->s_state & EXT4_FS_STATE_UNMOUNTED) == 0) {
    DEBUG ((DEBUG_WARN, "[ext4] Filesystem was not unmounted cleanly\n"));
  }
 
  return TRUE;
}
 
/**
   Calculates the superblock's checksum.
 
   @param[in] Partition    Pointer to the opened partition.
   @param[in] Sb           Pointer to the superblock.
 
   @return The superblock's checksum.
**/
STATIC
UINT32
Ext4CalculateSuperblockChecksum (
  EXT4_PARTITION         *Partition,
  CONST EXT4_SUPERBLOCK  *Sb
  )
{
  // Most checksums require us to go through a dummy 0 as part of the requirement
  // that the checksum is done over a structure with its checksum field = 0.
  UINT32  Checksum;
 
  Checksum = Ext4CalculateChecksum (
               Partition,
               Sb,
               OFFSET_OF (EXT4_SUPERBLOCK, s_checksum),
               ~0U
               );
 
  return Checksum;
}
 
/**
   Verifies that the superblock's checksum is valid.
 
   @param[in] Partition    Pointer to the opened partition.
   @param[in] Sb           Pointer to the superblock.
 
   @return The superblock's checksum.
**/
STATIC
BOOLEAN
Ext4VerifySuperblockChecksum (
  EXT4_PARTITION         *Partition,
  CONST EXT4_SUPERBLOCK  *Sb
  )
{
  if (!EXT4_HAS_METADATA_CSUM (Partition)) {
    return TRUE;
  }
 
  return Sb->s_checksum == Ext4CalculateSuperblockChecksum (Partition, Sb);
}
 
/**
   Opens and parses the superblock.
 
   @param[out]     Partition Partition structure to fill with filesystem details.
   @retval EFI_SUCCESS       Parsing was succesful and the partition is a
                             valid ext4 partition.
**/
EFI_STATUS
Ext4OpenSuperblock (
  OUT EXT4_PARTITION  *Partition
  )
{
  UINT32                 Index;
  EFI_STATUS             Status;
  EXT4_SUPERBLOCK        *Sb;
  UINT32                 NrBlocksRem;
  UINTN                  NrBlocks;
  UINT32                 UnsupportedRoCompat;
  EXT4_BLOCK_GROUP_DESC  *Desc;
 
  Status = Ext4ReadDiskIo (
             Partition,
             &Partition->SuperBlock,
             sizeof (EXT4_SUPERBLOCK),
             EXT4_SUPERBLOCK_OFFSET
             );
 
  if (EFI_ERROR (Status)) {
    return Status;
  }
 
  Sb = &Partition->SuperBlock;
 
  if (!Ext4SuperblockValidate (Sb)) {
    return EFI_VOLUME_CORRUPTED;
  }
 
  if (Sb->s_rev_level == EXT4_DYNAMIC_REV) {
    Partition->FeaturesCompat   = Sb->s_feature_compat;
    Partition->FeaturesIncompat = Sb->s_feature_incompat;
    Partition->FeaturesRoCompat = Sb->s_feature_ro_compat;
    Partition->InodeSize = Sb->s_inode_size;
  } else {
    // GOOD_OLD_REV
    Partition->FeaturesCompat = Partition->FeaturesIncompat = Partition->FeaturesRoCompat = 0;
    Partition->InodeSize = EXT4_GOOD_OLD_INODE_SIZE;
  }
 
  // Now, check for the feature set of the filesystem
  // It's essential to check for this to avoid filesystem corruption and to avoid
  // accidentally opening an ext2/3/4 filesystem we don't understand, which would be disasterous.
 
  if (Partition->FeaturesIncompat & ~gSupportedIncompatFeat) {
    DEBUG ((
      DEBUG_ERROR,
      "[ext4] Unsupported features %lx\n",
      Partition->FeaturesIncompat & ~gSupportedIncompatFeat
      ));
    return EFI_UNSUPPORTED;
  }
 
  // This should be removed once we add ext2/3 support in the future.
  if ((Partition->FeaturesIncompat & EXT4_FEATURE_INCOMPAT_EXTENTS) == 0) {
    return EFI_UNSUPPORTED;
  }
 
  if (EXT4_HAS_INCOMPAT (Partition, EXT4_FEATURE_INCOMPAT_RECOVER)) {
    DEBUG ((DEBUG_WARN, "[ext4] Needs journal recovery, mounting read-only\n"));
    Partition->ReadOnly = TRUE;
  }
 
  // At the time of writing, it's the only supported checksum.
  if (Partition->FeaturesCompat & EXT4_FEATURE_RO_COMPAT_METADATA_CSUM &&
      Sb->s_checksum_type != EXT4_CHECKSUM_CRC32C) {
    return EFI_UNSUPPORTED;
  }
 
  if (Partition->FeaturesIncompat & EXT4_FEATURE_INCOMPAT_CSUM_SEED) {
    Partition->InitialSeed = Sb->s_checksum_seed;
  } else {
    Partition->InitialSeed = Ext4CalculateChecksum (Partition, Sb->s_uuid, 16, ~0U);
  }
 
  UnsupportedRoCompat = Partition->FeaturesRoCompat & ~gSupportedRoCompatFeat;
 
  if (UnsupportedRoCompat != 0) {
    DEBUG ((DEBUG_WARN, "[ext4] Unsupported ro compat %x\n", UnsupportedRoCompat));
    Partition->ReadOnly = TRUE;
  }
 
  // gSupportedCompatFeat is documentation-only since we never need to access it.
  // The line below avoids unused variable warnings.
  (VOID)gSupportedCompatFeat;
 
  DEBUG ((DEBUG_FS, "Read only = %u\n", Partition->ReadOnly));
 
  Partition->BlockSize = (UINT32)LShiftU64 (1024, Sb->s_log_block_size);
 
  // The size of a block group can also be calculated as 8 * Partition->BlockSize
  if (Sb->s_blocks_per_group != 8 * Partition->BlockSize) {
    return EFI_UNSUPPORTED;
  }
 
  Partition->NumberBlocks = EXT4_BLOCK_NR_FROM_HALFS (Partition, Sb->s_blocks_count, Sb->s_blocks_count_hi);
  Partition->NumberBlockGroups = DivU64x32 (Partition->NumberBlocks, Sb->s_blocks_per_group);
 
  DEBUG ((
    DEBUG_FS,
    "[ext4] Number of blocks = %lu\n[ext4] Number of block groups: %lu\n",
    Partition->NumberBlocks,
    Partition->NumberBlockGroups
    ));
 
  if (EXT4_IS_64_BIT (Partition)) {
    Partition->DescSize = Sb->s_desc_size;
  } else {
    Partition->DescSize = EXT4_OLD_BLOCK_DESC_SIZE;
  }
 
  if (Partition->DescSize < EXT4_64BIT_BLOCK_DESC_SIZE && EXT4_IS_64_BIT (Partition)) {
    // 64 bit filesystems need DescSize to be 64 bytes
    return EFI_VOLUME_CORRUPTED;
  }
 
  if (!Ext4VerifySuperblockChecksum (Partition, Sb)) {
    DEBUG ((DEBUG_ERROR, "[ext4] Bad superblock checksum %lx\n", Ext4CalculateSuperblockChecksum (Partition, Sb)));
    return EFI_VOLUME_CORRUPTED;
  }
 
  NrBlocks = (UINTN)DivU64x32Remainder (
                      MultU64x32 (Partition->NumberBlockGroups, Partition->DescSize),
                      Partition->BlockSize,
                      &NrBlocksRem
                      );
 
  if (NrBlocksRem != 0) {
    NrBlocks++;
  }
 
  Partition->BlockGroups = Ext4AllocAndReadBlocks (Partition, NrBlocks, Partition->BlockSize == 1024 ? 2 : 1);
 
  if (Partition->BlockGroups == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }
 
  for (Index = 0; Index < Partition->NumberBlockGroups; Index++) {
    Desc = Ext4GetBlockGroupDesc (Partition, Index);
    if (!Ext4VerifyBlockGroupDescChecksum (Partition, Desc, Index)) {
      DEBUG ((DEBUG_ERROR, "[ext4] Block group descriptor %u has an invalid checksum\n", Index));
      FreePool (Partition->BlockGroups);
      return EFI_VOLUME_CORRUPTED;
    }
  }
 
  // RootDentry will serve as the basis of our directory entry tree.
  Partition->RootDentry = Ext4CreateDentry (L"\\", NULL);
 
  if (Partition->RootDentry == NULL) {
    FreePool (Partition->BlockGroups);
    return EFI_OUT_OF_RESOURCES;
  }
 
  // Note that the cast below is completely safe, because EXT4_FILE is a specialisation of EFI_FILE_PROTOCOL
  Status = Ext4OpenVolume (&Partition->Interface, (EFI_FILE_PROTOCOL **)&Partition->Root);
 
  if (EFI_ERROR (Status)) {
    Ext4UnrefDentry (Partition->RootDentry);
    FreePool (Partition->BlockGroups);
  }
 
  return Status;
}
 
/**
   Calculates the checksum of the given buffer.
   @param[in]      Partition     Pointer to the opened EXT4 partition.
   @param[in]      Buffer        Pointer to the buffer.
   @param[in]      Length        Length of the buffer, in bytes.
   @param[in]      InitialValue  Initial value of the CRC.
 
   @return The checksum.
**/
UINT32
Ext4CalculateChecksum (
  IN CONST EXT4_PARTITION  *Partition,
  IN CONST VOID            *Buffer,
  IN UINTN                 Length,
  IN UINT32                InitialValue
  )
{
  if (!EXT4_HAS_METADATA_CSUM (Partition)) {
    return 0;
  }
 
  switch (Partition->SuperBlock.s_checksum_type) {
    case EXT4_CHECKSUM_CRC32C:
      // For some reason, EXT4 really likes non-inverted CRC32C checksums, so we stick to that here.
      return ~CalculateCrc32c(Buffer, Length, ~InitialValue);
    default:
      UNREACHABLE ();
      return 0;
  }
}