1import binascii
2import io
3
4from structlog import get_logger
5
6from unblob.file_utils import (
7 Endian,
8 InvalidInputFormat,
9 convert_int16,
10 read_until_past,
11 round_up,
12)
13
14from ...extractors import Command
15from ...models import (
16 File,
17 HandlerDoc,
18 HandlerType,
19 HexString,
20 Reference,
21 StructHandler,
22 ValidChunk,
23)
24
25logger = get_logger()
26
27
28BLOCK_ALIGNMENT = 4
29JFFS2_MAGICS = [0x1985, 0x8519, 0x1984, 0x8419]
30
31# Compatibility flags.
32JFFS2_NODE_ACCURATE = 0x2000
33JFFS2_FEATURE_INCOMPAT = 0xC000
34JFFS2_FEATURE_RWCOMPAT_DELETE = 0x0000
35
36DIRENT = JFFS2_FEATURE_INCOMPAT | JFFS2_NODE_ACCURATE | 1
37INODE = JFFS2_FEATURE_INCOMPAT | JFFS2_NODE_ACCURATE | 2
38CLEANMARKER = JFFS2_FEATURE_RWCOMPAT_DELETE | JFFS2_NODE_ACCURATE | 3
39PADDING = JFFS2_FEATURE_RWCOMPAT_DELETE | JFFS2_NODE_ACCURATE | 4
40SUMMARY = JFFS2_FEATURE_RWCOMPAT_DELETE | JFFS2_NODE_ACCURATE | 6
41XATTR = JFFS2_FEATURE_INCOMPAT | JFFS2_NODE_ACCURATE | 8
42XREF = JFFS2_FEATURE_INCOMPAT | JFFS2_NODE_ACCURATE | 9
43
44JFFS2_NODETYPES = {DIRENT, INODE, CLEANMARKER, PADDING, SUMMARY, XATTR, XREF}
45
46
47class _JFFS2Base(StructHandler):
48 C_DEFINITIONS = r"""
49 typedef struct jffs2_unknown_node
50 {
51 uint16 magic;
52 uint16 nodetype;
53 uint32 totlen;
54 uint32 hdr_crc;
55 } jffs2_unknown_node_t;
56 """
57
58 HEADER_STRUCT = "jffs2_unknown_node_t"
59
60 BIG_ENDIAN_MAGIC = 0x19_85
61
62 EXTRACTOR = Command("jefferson", "-v", "-f", "-d", "{outdir}", "{inpath}")
63
64 def guess_endian(self, file: File) -> Endian:
65 magic = convert_int16(file.read(2), Endian.BIG)
66 endian = Endian.BIG if magic == self.BIG_ENDIAN_MAGIC else Endian.LITTLE
67 file.seek(-2, io.SEEK_CUR)
68 return endian
69
70 def valid_header(self, header, node_start_offset: int, eof: int) -> bool:
71 header_crc = (binascii.crc32(header.dumps()[:-4], -1) ^ -1) & 0xFFFFFFFF
72 check_crc = True
73
74 if header.nodetype not in JFFS2_NODETYPES:
75 if header.nodetype | JFFS2_NODE_ACCURATE not in JFFS2_NODETYPES:
76 logger.debug(
77 "Invalid JFFS2 node type", node_type=header.nodetype, _verbosity=2
78 )
79 return False
80 logger.debug(
81 "Not accurate JFFS2 node type, ignore CRC",
82 node_type=header.nodetype,
83 _verbosity=2,
84 )
85 check_crc = False
86
87 if check_crc and header_crc != header.hdr_crc:
88 logger.debug("node header CRC missmatch", _verbosity=2)
89 return False
90
91 if node_start_offset + header.totlen > eof:
92 logger.debug(
93 "node length greater than total file size",
94 node_len=header.totlen,
95 file_size=eof,
96 _verbosity=2,
97 )
98 return False
99
100 if header.totlen < len(header):
101 logger.debug(
102 "node length greater than header size",
103 node_len=header.totlen,
104 _verbosity=2,
105 )
106 return False
107 return True
108
109 def calculate_chunk(self, file: File, start_offset: int) -> ValidChunk | None:
110 file.seek(0, io.SEEK_END)
111 eof = file.tell()
112 file.seek(start_offset)
113
114 endian = self.guess_endian(file)
115 current_offset = start_offset
116
117 while current_offset < eof:
118 node_start_offset = current_offset
119 file.seek(current_offset)
120 try:
121 header = self.parse_header(file, endian=endian)
122 except EOFError:
123 break
124
125 if header.magic not in JFFS2_MAGICS:
126 # JFFS2 allows padding at the end with 0xFF or 0x00, usually
127 # to the size of an erase block.
128 if header.magic in [0x0000, 0xFFFF]:
129 file.seek(-len(header), io.SEEK_CUR)
130 current_offset = read_until_past(file, b"\x00\xff")
131 continue
132
133 logger.debug(
134 "unexpected header magic",
135 header_magic=header.magic,
136 _verbosity=2,
137 )
138 break
139
140 if not self.valid_header(header, node_start_offset, eof):
141 return None
142
143 node_len = round_up(header.totlen, BLOCK_ALIGNMENT)
144 current_offset += node_len
145
146 if current_offset > eof:
147 raise InvalidInputFormat("Corrupt file or last chunk isn't really JFFS2")
148
149 return ValidChunk(
150 start_offset=start_offset,
151 end_offset=current_offset,
152 )
153
154
155class JFFS2OldHandler(_JFFS2Base):
156 NAME = "jffs2_old"
157
158 PATTERNS = [
159 HexString("84 19 ( 01 | 02 | 03 | 04 | 06 | 08 | 09 ) ( e0 | 20 )"), # LE
160 HexString("19 84 ( e0 | 20 ) ( 01 | 02 | 03 | 04 | 06 | 08 | 09 )"), # BE
161 ]
162
163 BIG_ENDIAN_MAGIC = 0x19_84
164
165 DOC = HandlerDoc(
166 name="JFFS2 (old)",
167 description="JFFS2 (Journaling Flash File System version 2) is a log-structured file system for flash memory devices, using an older magic number to identify its nodes. It organizes data into nodes with headers containing metadata and CRC checks for integrity.",
168 handler_type=HandlerType.FILESYSTEM,
169 vendor=None,
170 references=[
171 Reference(
172 title="JFFS2 Documentation",
173 url="https://sourceware.org/jffs2/",
174 ),
175 Reference(
176 title="JFFS2 Wikipedia",
177 url="https://en.wikipedia.org/wiki/JFFS2",
178 ),
179 ],
180 limitations=[],
181 )
182
183
184class JFFS2NewHandler(_JFFS2Base):
185 NAME = "jffs2_new"
186
187 PATTERNS = [
188 HexString("85 19 ( 01 | 02 | 03 | 04 | 06 | 08 | 09 ) ( e0 | 20 )"), # LE
189 HexString("19 85 ( e0 | 20 ) ( 01 | 02 | 03 | 04 | 06 | 08 | 09 )"), # BE
190 ]
191
192 BIG_ENDIAN_MAGIC = 0x19_85
193
194 DOC = HandlerDoc(
195 name="JFFS2 (new)",
196 description="JFFS2 (Journaling Flash File System version 2) is a log-structured file system for flash memory devices, using an older magic number to identify its nodes. It organizes data into nodes with headers containing metadata and CRC checks for integrity.",
197 handler_type=HandlerType.FILESYSTEM,
198 vendor=None,
199 references=[
200 Reference(
201 title="JFFS2 Documentation",
202 url="https://sourceware.org/jffs2/",
203 ),
204 Reference(
205 title="JFFS2 Wikipedia",
206 url="https://en.wikipedia.org/wiki/JFFS2",
207 ),
208 ],
209 limitations=[],
210 )