Superblock.c 11 KB

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