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
353
354
355
356
357
358
359
360
361
362
363
|
/** @file
Superblock managing routines
Copyright (c) 2021 - 2023 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 | EXT4_FEATURE_INCOMPAT_CSUM_SEED;
// 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 successful 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;
// Check for proper alignment of InodeSize and that InodeSize is indeed larger than
// the minimum size, 128 bytes.
if (((Partition->InodeSize % 4) != 0) || (Partition->InodeSize < EXT4_GOOD_OLD_INODE_SIZE)) {
return EFI_VOLUME_CORRUPTED;
}
} 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 disastrous.
if (Partition->FeaturesIncompat & ~gSupportedIncompatFeat) {
DEBUG ((
DEBUG_ERROR,
"[ext4] Unsupported features %lx\n",
Partition->FeaturesIncompat & ~gSupportedIncompatFeat
));
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 (EXT4_HAS_METADATA_CSUM (Partition) && (Sb->s_checksum_type != EXT4_CHECKSUM_CRC32C)) {
return EFI_UNSUPPORTED;
}
if (EXT4_HAS_INCOMPAT (Partition, 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));
if (Sb->s_inodes_per_group == 0) {
DEBUG ((DEBUG_ERROR, "[ext4] Inodes per group can not be zero\n"));
return EFI_VOLUME_CORRUPTED;
}
if (Sb->s_log_block_size > EXT4_LOG_BLOCK_SIZE_MAX) {
DEBUG ((DEBUG_ERROR, "[ext4] SuperBlock s_log_block_size %lu is too big\n", Sb->s_log_block_size));
return EFI_UNSUPPORTED;
}
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)) {
// s_desc_size should be 4 byte aligned and
// 64 bit filesystems need DescSize to be 64 bytes
if (((Sb->s_desc_size % 4) != 0) || (Sb->s_desc_size < EXT4_64BIT_BLOCK_DESC_SIZE)) {
return EFI_VOLUME_CORRUPTED;
}
Partition->DescSize = Sb->s_desc_size;
} else {
Partition->DescSize = EXT4_OLD_BLOCK_DESC_SIZE;
}
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 specialization 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:
ASSERT (FALSE);
return 0;
}
}
|