1 Blocks and block groups
ext4 divides the volume into blocks (1, 2 or 4 KiB) and groups them into block groups. Each group has its own block bitmap, inode bitmap and inode table, so that a file's metadata sits close to its data. This small sample uses 1 KiB blocks and has a single group:
What dumpe2fs -h and debugfs say about this volume
2 The superblock
The superblock is always at byte 1024 of the volume, whatever the block size, and is 1024 bytes long. Its magic
number 53 EF sits at offset 0x38, i.e. byte 1080 (0x438) of the volume.
It has the sizes you need for every calculation, plus identity information: UUID, label, the directory it
was last mounted on, and timestamps.
extent, 64bit and flex_bg mean
ext4; has_journal without them means ext3; neither means ext2. With metadata_csum every
metadata structure carries a CRC32C, so a careless hex edit is detectable.3 Group descriptors
The group descriptor table starts in the block after the superblock (here block 2, byte 0x800).
One descriptor per group (64 bytes with the 64bit feature) gives the block numbers of the group's
bitmaps and inode table.
4 Inodes
An inode holds everything about a file except its name: type and permissions, owner, size, four timestamps, and where the data is. Inodes are numbered from 1. Inode 2 is always the root directory.
Extents
ext4 describes the data with extents stored in the 60-byte i_block area: a 12-byte header
(magic 0A F3) and up to four 12-byte entries, each saying "file block n, length l,
starts at disk block b". Larger, fragmented files get an extent tree with index blocks. Here is
photos/cat.raw (inode 14), 5,000 bytes in one extent of 5 blocks:
The small text file notes.txt (inode 12) needs a single block. Follow its extent to the data:
touch), atime the last read (often lazily updated), crtime the creation (ext4 only).
All are UTC seconds since 1970, with nanoseconds in the extra fields.5 Directories
A directory's data blocks contain variable-length entries: inode number, record length (distance to the next entry), name length, file type and the name. The name is the only link between a path and its inode.
Deleted entries survive in slack
When a name is removed, ext4 doesn't erase it. It adds the entry's record length to the previous entry,
so directory walks skip it, but the bytes are still there. In the photos directory below, the
entry for cat.raw now has a much larger record length than its name needs, and the old entry for
draft.txt is hidden inside it:
The name points to inode 15. That inode is marked deleted: links 0, a deletion time, size 0, and an extent header with 0 entries. ext4 clears the extents on deletion, so the inode no longer says where the data was.
6 Try it yourself
Answer from the hex views above. Numbers can be typed in decimal or hex (0x…).
At which byte offset of the volume does inode 12 (notes.txt) start?
Use the inode formula from section 4. The inode table block is in the group descriptor (+0x08), the inode size in the superblock (+0x58).
Inode table block: 42 00 00 00 = 66. Inode size: 00 01 = 256. Inodes per group: 00 04 00 00 = 1,024, so inode 12 is in group 0 with index 12 − 1 = 11.
66 × 1024 + 11 × 256 = 0x10800 + 0xB00 = 0x11300. The notes.txt inode view above starts exactly there.
At which block number does the data of photos/cat.raw (inode 14) start?
The first extent entry starts at +0x34 of the inode. Its start block is 48 bits: a 2-byte high part at +0x3A, then a 4-byte low part at +0x3C.
The 6 bytes are 00 00 34 05 00 00. High part 00 00 = 0, low part 34 05 00 00 = 0x534.
Start block = 0 × 232 + 0x534 = 1,332, i.e. byte 1,332 × 1024 = 0x14D000.
At which offset inside the photos directory block does the hidden draft.txt entry start?
An entry needs 8 bytes of header plus its name, rounded up to a multiple of 4. Find where cat.raw's entry starts and how much of it is really needed.
The cat.raw entry starts at +0x18 and has a 7-byte name: 8 + 7 = 15, rounded up to 16 bytes. Its record length DC 03 = 988 is far more than that.
0x18 + 16 = 0x28. There you find inode 0F 00 00 00 (15), record length CC 03 and the name draft.txt.
Which field of inode 15 records when the file was deleted?
Only one of the four timestamps is 0 in every file that still exists.
The deletion time at +0x14 is D8 F6 A7 6A = 0x6AA7F6D8 = 2026-09-14 13:30:00 UTC. In a live inode this field is 00 00 00 00.
ctime has the same value, because deletion also changes the inode. But ctime changes for many other reasons (chmod, rename), so only dtime says "deleted".
These are the extent header and the first extent of cat.raw (inode 14). Edit them and watch the decoded inode below:
- Change the extent length at
+0x10(05 00) to03 00. The extent now covers 3,072 bytes, less than the 5,000-byte file size. - Change the low start block
34 05to32 05. Which file's block does the extent point to now? - Change the magic
0A F3to00 F3. A tool can no longer trust anything ini_block.