Skip to content

Commit 2bb37fe

Browse files
qkaiserrxpha3l
authored andcommitted
feat(handler): add EROFS filesystem handler
EROFS is a high-performance read-only filesystem originally developed by Huawei and now used extensively in AOSP. It offers features like compression (LZ4, LZ4HC, LZMA, ZLIB, ZSTD), inline data, and reduced storage overhead, making it ideal for read-only system images. Create erofs file : > mkfs.erofs -z[compressor, level (optional)] foo.erofs.img foo/ Extract erofs file: > fsck.erofs --extract=foo_extracted/ foo.erofs.img Key notes: - First 1024 bytes are reserved for the boot loader - Header starts at 0x400 with E0F5E1E2 - Header can be more than 128 bytes if extra slots provided - File format in little endian - There is no standarized file extension. The official documentation uses ".erofs.img" - Checksum changes from version to version HEADER / SUPERBLOCK: > 4 bytes : Magic > 4 bytes : Checksum > 4 bytes : Feature > 1 byte : Block size in bit shift > 1 byte : Superblock extension > 2 bytes : Root NID > 8 bytes : Total valid i-node > 8 bytes : Built time in timestamp > 4 bytes : Nanoseconds (ns) component of timestamp > 4 bytes : Total block count > 4 bytes : Start block addr of metadata area > 4 bytes : Start block of shared xattr area > 16 bytes : UUID for volume > 16 bytes : Filesystem label > 4 bytes : Incompatible feature flag for kernel > 2 bytes : Available compression algorithm > 2 bytes : LZ4 max distance > 2 bytes : Extra devices > 2 bytes : Start address of external device table > 1 byte : Directory block size 2^blkszbits+dirblkbits > 1 byte : Total number of long xattr name prefixes > 4 bytes : Start address of long xattr prefixes > 8 bytes : NID of the special packed inode. > 1 byte : xattr filter reserved > 23 bytes : Reserved Unblob structs the header until feature_incompact. The rest is labeled as "reserved". Unblob parses the end offset by multiplying the block size in bit shift with the block count. Since the checksum is not consistent troughout versions, unblob matched on block count (min 1), printable volume name, build time, build time in ns and bit shift (min 9). [Sources] https://elixir.bootlin.com/linux/v6.14-rc6/source/fs/erofs/erofs_fs.h https://web.git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/erofs/erofs_fs.h?h=v6.6 https://erofs.docs.kernel.org/en/latest/core_ondisk.html https://www.kernel.org/doc/html/latest/filesystems/erofs.html
1 parent f28dca6 commit 2bb37fe

File tree

14 files changed

+102
-2
lines changed

14 files changed

+102
-2
lines changed

docs/formats.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ For compression formats, metadata cannot be preserved, as this information in mo
9797
| ---------------------- | ---------------------------------- | ----------------------------------------------- | ----------------------------------------------- |
9898
| Android sparse image || [filesystem/android/sparse.py][android-handler] | [`simg2img`][android-extractor] |
9999
| CRAMFS || [filesystem/cramfs.py][cramfs-handler] | [`7z`][cramfs-extractor] |
100+
| EROFS || [filesystem/android/erofs.py][erofs-handler] | [`fsck.erfos`][erofs-extractor] |
100101
| ExtFS || [filesystem/extfs.py][extfs-handler] | [`debugfs`][extfs-extractor] |
101102
| FAT || [filesystem/fat.py][fat-handler] | [`7z`][fat-extractor] |
102103
| ISO9660 || [filesystem/iso9660.py][iso9660-handler] | [`7z`][iso9660-extractor] |
@@ -107,12 +108,14 @@ For compression formats, metadata cannot be preserved, as this information in mo
107108
| SquashFS v4 Big Endian || [filesystem/squashfs.py][squashfs-handler] | [`sasquatch-v4-be`][squashfs-v4-be-extractor] |
108109
| UBI || [filesystem/ubi.py][ubi-handler] | [`ubireader_extract_images`][ubi-extractor] |
109110
| UBIFS || [filesystem/ubi.py][ubi-handler] | [`ubireader_extract_files`][ubifs-extractor] |
110-
| YAFFS (1, 2) || [filesystem/yaffs.py][yaffs-handler] | [`YAFFSExtractor` custom code][yaffs-extractor] |
111+
| YAFFS (1, 2) || [filesystem/yaffs.py][yaffs-handler] | [`YAFFSExtractor` custom code][yaffs-extractor] |
111112

112113
[android-handler]: https://github.com/onekey-sec/unblob/blob/main/unblob/handlers/filesystem/android/sparse.py
113114
[android-extractor]: https://github.com/onekey-sec/unblob/blob/3008039881a0434deb75962e7999b7e35aca8271/unblob/handlers/filesystem/android/sparse.py#L61
114115
[cramfs-handler]: https://github.com/onekey-sec/unblob/blob/main/unblob/handlers/filesystem/cramfs.py
115116
[cramfs-extractor]: https://github.com/onekey-sec/unblob/blob/3008039881a0434deb75962e7999b7e35aca8271/unblob/handlers/filesystem/cramfs.py#L45
117+
[erofs-handler]: https://github.com/onekey-sec/unblob/blob/main/unblob/handlers/filesystem/android/erfos.py
118+
[erofs-extractor]: https://github.com/onekey-sec/unblob/blob/...
116119
[extfs-handler]: https://github.com/onekey-sec/unblob/blob/main/unblob/handlers/filesystem/extfs.py
117120
[extfs-extractor]: https://github.com/onekey-sec/unblob/blob/3008039881a0434deb75962e7999b7e35aca8271/unblob/handlers/filesystem/extfs.py#L68
118121
[fat-handler]: https://github.com/onekey-sec/unblob/blob/main/unblob/handlers/filesystem/fat.py

install-deps.sh

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ apt-get update
55
apt-get install --no-install-recommends -y \
66
android-sdk-libsparse-utils \
77
curl \
8+
erofs-utils \
89
lz4 \
910
lziprecover \
1011
lzop \

package.nix

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
fetchFromGitHub,
55
makeWrapper,
66
e2fsprogs-nofortify,
7+
erofs-utils,
78
jefferson,
89
lz4,
910
lziprecover,
@@ -24,6 +25,7 @@ let
2425
# These dependencies are only added to PATH
2526
runtimeDeps = [
2627
e2fsprogs-nofortify
28+
erofs-utils
2729
jefferson
2830
lziprecover
2931
lzop

python/unblob/handlers/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
ubi,
5050
yaffs,
5151
)
52-
from .filesystem.android import sparse
52+
from .filesystem.android import erofs, sparse
5353

5454
BUILTIN_HANDLERS: Handlers = (
5555
cramfs.CramFSHandler,
@@ -118,6 +118,7 @@
118118
engenius.EngeniusHandler,
119119
ecc.AutelECCHandler,
120120
uzip.UZIPHandler,
121+
erofs.EROFSHandler,
121122
)
122123

123124
BUILTIN_DIR_HANDLERS: DirectoryHandlers = (
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import io
2+
from typing import Optional
3+
4+
from unblob.extractors import Command
5+
from unblob.file_utils import (
6+
Endian,
7+
InvalidInputFormat,
8+
)
9+
from unblob.models import (
10+
File,
11+
HexString,
12+
StructHandler,
13+
ValidChunk,
14+
)
15+
16+
C_DEFINITIONS = r"""
17+
typedef struct erofs_handler{
18+
uint32_t magic;
19+
uint32_t crc32c;
20+
uint32_t feature_compact;
21+
uint8_t block_size_bs;
22+
uint8_t sb_extslots;
23+
uint16_t root_nid;
24+
uint64_t inos;
25+
uint64_t build_time;
26+
uint32_t build_time_nsec;
27+
uint32_t block_count;
28+
uint32_t meta_blkaddr;
29+
uint32_t xattr_blkaddr;
30+
uint8_t uuid[16];
31+
char volume_name[16];
32+
uint32_t feature_incompact;
33+
char reserved[44];
34+
} erofs_handler_t;
35+
"""
36+
37+
SUPERBLOCK_OFFSET = 0x400
38+
39+
40+
class EROFSHandler(StructHandler):
41+
NAME = "erofs"
42+
PATTERNS = [HexString("e2 e1 f5 e0")] # Magic in little endian
43+
HEADER_STRUCT = "erofs_handler_t"
44+
C_DEFINITIONS = C_DEFINITIONS
45+
EXTRACTOR = Command(
46+
"fsck.erofs",
47+
"--no-preserve",
48+
"--extract={outdir}",
49+
"{inpath}",
50+
)
51+
PATTERN_MATCH_OFFSET = -SUPERBLOCK_OFFSET
52+
53+
def is_valid_header(self, header) -> bool:
54+
return (
55+
header.block_count >= 1
56+
and header.build_time > 0
57+
and header.build_time_nsec > 0
58+
and header.block_size_bs >= 9
59+
and str(header.volume_name).rstrip("\x00").isprintable()
60+
)
61+
62+
def calculate_chunk(self, file: File, start_offset: int) -> Optional[ValidChunk]:
63+
file.seek(start_offset + SUPERBLOCK_OFFSET, io.SEEK_SET)
64+
header = self.parse_header(file, Endian.LITTLE)
65+
if not self.is_valid_header(header):
66+
raise InvalidInputFormat("Invalid erofs header.")
67+
68+
end_offset = (1 << header.block_size_bs) * header.block_count
69+
return ValidChunk(
70+
start_offset=start_offset,
71+
end_offset=end_offset,
72+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:7f400d3497501c6e68324868d88207aa9398888902414045f4ea498ff26bcdcd
3+
size 4096
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:7520d3fe98e5f5d25ccc4027321a561a125fbb413ecd83e935a8bc6b425b2aaf
3+
size 6144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:0bfc3200f0152ab9e91e662afd75add3306131f670a1d71539f680c7acdb0a9f
3+
size 13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef
3+
size 1024
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:a5dc7382e2247702f0e29e2e71fedfb464d2b930a439957fa7458882f7f7116d
3+
size 3072
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:0bfc3200f0152ab9e91e662afd75add3306131f670a1d71539f680c7acdb0a9f
3+
size 13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:e5a00aa9991ac8a5ee3109844d84a55583bd20572ad3ffcd42792f3c36b183ad
3+
size 2048

0 commit comments

Comments
 (0)