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