1from typing import Optional
2
3from structlog import get_logger
4
5from unblob.file_utils import InvalidInputFormat
6
7from ...extractors import Command
8from ...models import (
9 File,
10 HandlerDoc,
11 HandlerType,
12 HexString,
13 Reference,
14 StructHandler,
15 ValidChunk,
16)
17
18logger = get_logger()
19
20
21EXT_BLOCK_SIZE = 0x400
22MAGIC_OFFSET = 0x438
23
24OS_LIST = [
25 (0x0, "Linux"),
26 (0x1, "GNU HURD"),
27 (0x2, "MASIX"),
28 (0x3, "FreeBSD"),
29 (
30 0x4,
31 "Other",
32 ), # Other "Lites" (BSD4.4-Lite derivatives such as NetBSD, OpenBSD, XNU/Darwin, etc.)
33]
34
35
36class EXTHandler(StructHandler):
37 NAME = "extfs"
38
39 PATTERNS = [HexString("53 ef ( 00 | 01 | 02 ) 00 ( 00 | 01 | 02 | 03 | 04 ) 00")]
40
41 C_DEFINITIONS = r"""
42 typedef struct ext4_superblock {
43 char blank[0x400]; // Not a part of the spec. But we expect the magic to be at 0x438.
44 uint32 s_inodes_count; // Total number of inodes in file system
45 uint32 s_blocks_count_lo; // Total number of blocks in file system
46 uint32 s_r_blocks_count_lo; // Number of blocks reserved for superuser (see offset 80)
47 uint32 s_free_blocks_count_lo; // Total number of unallocated blocks
48 uint32 s_free_inodes_count; // Total number of unallocated inodes
49 uint32 s_first_data_block; // Block number of the block containing the superblock
50 uint32 s_log_block_size; // log2 (block size) - 10 (In other words, the number to shift 1,024 to the left by to obtain the block size)
51 uint32 s_log_cluster_size; // log2 (fragment size) - 10. (In other words, the number to shift 1,024 to the left by to obtain the fragment size)
52 uint32 s_blocks_per_group; // Number of blocks in each block group
53 uint32 s_clusters_per_group; // Number of fragments in each block group
54 uint32 s_inodes_per_group; // Number of inodes in each block group
55 uint32 s_mtime; // Last mount time
56 uint32 s_wtime; // Last written time
57 uint16 s_mnt_count; // Number of times the volume has been mounted since its last consistency check
58 uint16 s_max_mnt_count; // Number of mounts allowed before a consistency check must be done
59 uint16 s_magic; // Ext signature (0xef53), used to help confirm the presence of Ext2 on a volume
60 uint16 s_state; // File system state (0x1 - clean or 0x2 - has errors)
61 uint16 s_errors; // What to do when an error is detected (ignore/remount/kernel panic)
62 uint16 s_minor_rev_level; // Minor portion of version (combine with Major portion below to construct full version field)
63 uint32 s_lastcheck; // time of last consistency check
64 uint32 s_checkinterval; // Interval between forced consistency checks
65 uint32 s_creator_os; // Operating system ID from which the filesystem on this volume was created
66 uint32 s_rev_level; // Major portion of version (combine with Minor portion above to construct full version field)
67 uint16 s_def_resuid; // User ID that can use reserved blocks
68 uint16 s_def_resgid; // Group ID that can use reserved blocks
69 } ext4_superblock_t;
70 """
71 HEADER_STRUCT = "ext4_superblock_t"
72
73 PATTERN_MATCH_OFFSET = -MAGIC_OFFSET
74
75 EXTRACTOR = Command("debugfs", "-R", 'rdump / "{outdir}"', "{inpath}")
76
77 DOC = HandlerDoc(
78 name="ExtFS",
79 description="ExtFS (Ext2/Ext3/Ext4) is a family of journaling file systems commonly used in Linux-based operating systems. It supports features like large file sizes, extended attributes, and journaling for improved reliability.",
80 handler_type=HandlerType.FILESYSTEM,
81 vendor=None,
82 references=[
83 Reference(
84 title="Ext4 Documentation",
85 url="https://www.kernel.org/doc/html/latest/filesystems/ext4/index.html",
86 ),
87 Reference(
88 title="ExtFS Wikipedia",
89 url="https://en.wikipedia.org/wiki/Ext4",
90 ),
91 ],
92 limitations=[],
93 )
94
95 def valid_header(self, header) -> bool:
96 if header.s_state not in [0x0, 0x1, 0x2]:
97 logger.debug("ExtFS header state not valid", state=header.s_state)
98 return False
99 if header.s_errors not in [0x0, 0x1, 0x2, 0x3]:
100 logger.debug(
101 "ExtFS header error handling method value not valid",
102 errors=header.s_errors,
103 )
104 return False
105 if header.s_creator_os not in [x[0] for x in OS_LIST]:
106 logger.debug("Creator OS value not valid.", creator_os=header.s_creator_os)
107 return False
108 if header.s_rev_level > 2:
109 logger.debug(
110 "ExtFS header major version too high", rev_level=header.s_rev_level
111 )
112 return False
113 if header.s_log_block_size > 6:
114 logger.debug(
115 "ExtFS header s_log_block_size is too large",
116 s_log_block_size=header.s_log_block_size,
117 )
118 return False
119 return True
120
121 def calculate_chunk(self, file: File, start_offset: int) -> Optional[ValidChunk]:
122 header = self.parse_header(file)
123 end_offset = start_offset + (
124 header.s_blocks_count_lo * (EXT_BLOCK_SIZE << header.s_log_block_size)
125 )
126
127 if not self.valid_header(header):
128 raise InvalidInputFormat("Invalid ExtFS header.")
129
130 return ValidChunk(
131 start_offset=start_offset,
132 end_offset=end_offset,
133 )