ubifs.py
ofrak.core.ubifs
SuperblockNode
dataclass
Each UBIFS image has a superblock which describe a large number of parameters regarding the filesystem. The minimal set of parameters necessary to re-pack an UBIFS filesystem are stored here. (see also: https://elixir.bootlin.com/linux/v6.1.7/source/fs/ubifs/ubifs.h#L1017).
Attributes:
| Name | Type | Description |
|---|---|---|
max_leb_count |
int |
Maximum number / limit of Logical Erase Blocks |
default_compr |
str |
Default compression algorithm |
fanout |
int |
Fanout of the index tree (number of links per indexing node) |
key_hash |
str |
Type of hash function used for keying direntries (typically 'r5' of reiserfs) |
orph_lebs |
int |
Number of LEBs used for orphan area (orphans are inodes with no links; see https://elixir.bootlin.com/linux/v6.1.7/source/fs/ubifs/orphan.c#L13) |
log_lebs |
int |
LEBs reserved for the journal (see the 'Journal' section in https://www.kernel.org/doc/Documentation/filesystems/ubifs-authentication.rst) |
Ubifs (GenericBinary, FilesystemRoot)
dataclass
UBIFS is a filesystem specially made to run on top of an UBI volume layer. UBIFS specifically provides indexing, compression, encryption / authentication and some other filesystem-related features.
As part of an UBI image, re-packing an UBIFS image requires the 'min_io_size' and 'leb_size' properties that are stored as part of the UBI header (https://elixir.bootlin.com/linux/v6.1.7/source/drivers/mtd/ubi/ubi.h#L441).
Each UBIFS image has a superblock which is encoded in OFRAK as a SuperblockNode.
Some documentation about UBIFS layout can also be found here: https://www.kernel.org/doc/Documentation/filesystems/ubifs-authentication.rst http://www.linux-mtd.infradead.org/doc/ubifs.html
Attributes:
| Name | Type | Description |
|---|---|---|
min_io_size |
int |
Minimum number of bytes per I/O transaction (see http://www.linux-mtd.infradead.org/doc/ubi.html#L_min_io_unit) |
leb_size |
int |
Size of Logical Erase Blocks |
superblock |
SuperblockNode |
A SuberblockNode |
UbifsAnalyzer (Analyzer)
Extracts UBIFS (UBI File System) parameters that are required for correctly repacking the filesystem, including minimum I/O unit size, logical erase block (LEB) size, and maximum LEB count. These parameters must match the target flash device's characteristics. Use before modifying UBIFS filesystems to preserve critical parameters, ensure repacked images are compatible with target flash hardware, or validate filesystem constraints.
analyze(self, resource, config=None)
async
Analyze a resource for to extract specific ResourceAttributes.
Users should not call this method directly; rather, they should run Resource.run or Resource.analyze.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
resource |
Resource |
The resource that is being analyzed |
required |
config |
Optional config for analyzing. If an implementation provides a default, this default will always be used when config would otherwise be None. Note that a copy of the default config will be passed, so the default config values cannot be modified persistently by a component run. |
None |
Returns:
| Type | Description |
|---|---|
Ubifs |
The analysis results |
Source code in ofrak/core/ubifs.py
async def analyze(self, resource: Resource, config=None) -> Ubifs:
async with resource.temp_to_disk() as temp_path:
ubifs_obj = ubireader_ubifs(
ubi_io.ubi_file(
temp_path,
block_size=guess_leb_size(temp_path),
start_offset=0,
end_offset=None,
)
)
return Ubifs(
ubifs_obj._get_min_io_size(),
ubifs_obj._get_leb_size(),
SuperblockNode(
ubifs_obj.superblock_node.max_leb_cnt,
PRINT_UBIFS_COMPR[ubifs_obj.superblock_node.default_compr],
ubifs_obj.superblock_node.fanout,
PRINT_UBIFS_KEY_HASH[ubifs_obj.superblock_node.key_hash],
ubifs_obj.superblock_node.orph_lebs,
ubifs_obj.superblock_node.log_lebs,
),
)
UbifsPacker (Packer)
Generates a UBIFS (UBI File System) filesystem image from a file and directory structure, creating the superblock, master nodes, index nodes, and data nodes required by UBIFS. Use after modifying extracted UBIFS contents to recreate root filesystems or data partitions for embedded devices.
pack(self, resource, config=None)
async
Pack the given resource.
Users should not call this method directly; rather, they should run Resource.run or Resource.pack.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
resource |
Resource |
required | |
config |
Optional config for packing. If an implementation provides a default, this default will always be used when config would otherwise be None. Note that a copy of the default config will be passed, so the default config values cannot be modified persistently by a component run. |
None |
Source code in ofrak/core/ubifs.py
async def pack(self, resource: Resource, config=None) -> None:
ubifs_view = await resource.view_as(Ubifs)
flush_dir = await ubifs_view.flush_to_disk()
with tempfile.NamedTemporaryFile(mode="rb", delete_on_close=False) as temp:
temp.close()
cmd = [
"mkfs.ubifs",
"-m",
f"{ubifs_view.min_io_size}",
"-e",
f"{ubifs_view.leb_size}",
"-c",
f"{ubifs_view.superblock.max_leb_count}",
"-x",
f"{ubifs_view.superblock.default_compr}",
"-f",
f"{ubifs_view.superblock.fanout}",
"-k",
f"{ubifs_view.superblock.key_hash}",
"-p",
f"{ubifs_view.superblock.orph_lebs}",
"-l",
f"{ubifs_view.superblock.log_lebs}",
"-F",
"-r",
flush_dir,
temp.name,
]
proc = await asyncio.create_subprocess_exec(
*cmd,
)
returncode = await proc.wait()
if proc.returncode:
raise CalledProcessError(returncode=returncode, cmd=cmd)
with open(temp.name, "rb") as new_fh:
new_data = new_fh.read()
resource.queue_patch(Range(0, await resource.get_data_length()), new_data)
UbifsUnpacker (Unpacker)
Extracts files and directories from UBIFS (UBI File System) images, the standard filesystem for UBI volumes on NAND flash. Use when analyzing root filesystems or data partitions from embedded devices using NAND flash. Common in routers, set-top boxes, and industrial embedded systems.
unpack(self, resource, config=None)
async
Unpack the given resource.
Users should not call this method directly; rather, they should run Resource.run or Resource.unpack.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
resource |
Resource |
The resource that is being unpacked |
required |
config |
Optional config for unpacking. If an implementation provides a default, this default will always be used when config would otherwise be None. Note that a copy of the default config will be passed, so the default config values cannot be modified persistently by a component run. |
None |
Source code in ofrak/core/ubifs.py
async def unpack(self, resource: Resource, config=None):
with tempfile.TemporaryDirectory() as temp_flush_dir:
# flush to disk
with open(f"{temp_flush_dir}/input.img", "wb") as temp_file:
resource_data = await resource.get_data()
temp_file.write(resource_data)
temp_file.flush()
cmd = [
"ubireader_extract_files",
"-k",
"-o",
f"{temp_flush_dir}/output",
temp_file.name,
]
proc = await asyncio.create_subprocess_exec(
*cmd,
)
returncode = await proc.wait()
if proc.returncode:
raise CalledProcessError(returncode=returncode, cmd=cmd)
ubifs_view = await resource.view_as(Ubifs)
await ubifs_view.initialize_from_disk(f"{temp_flush_dir}/output")