Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 24 additions & 14 deletions Ext4Fsd/ext3/generic.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,10 @@ Ext2SaveSuper(
LONGLONG offset;
BOOLEAN rc;

ext4_superblock_csum_set(&Vcb->sb);
ext3_blocks_count_set(SUPER_BLOCK, Vcb->TotalBlocks);
ext3_r_blocks_count_set(SUPER_BLOCK, Vcb->ReservedBlocks);
ext3_free_blocks_count_set(SUPER_BLOCK, Vcb->FreeBlocks);
ext4_superblock_csum_set(&Vcb->sb);
offset = (LONGLONG) SUPER_BLOCK_OFFSET;
rc = Ext2SaveBuffer( IrpContext,
Vcb,
Expand Down Expand Up @@ -107,10 +110,10 @@ Ext2RefreshSuper (
}

/* reload root inode */
if (Vcb->McbTree) {
if (!Ext2LoadInode(Vcb, &Vcb->McbTree->Inode))
return FALSE;
if (Vcb->McbTree) {

if (!Ext2LoadInode(Vcb, &Vcb->McbTree->Inode))
return FALSE;

/* initializeroot node */
Vcb->McbTree->LastAccessTime = Ext2GetInodeTime(Vcb->McbTree->Inode.i_atime, Vcb->McbTree->Inode.i_atime_extra);
Expand All @@ -119,11 +122,15 @@ Ext2RefreshSuper (
if (Vcb->McbTree->Inode.i_crtime)
Vcb->McbTree->CreationTime = Ext2GetInodeTime(Vcb->McbTree->Inode.i_crtime, Vcb->McbTree->Inode.i_crtime_extra);
else
Vcb->McbTree->CreationTime = Ext2GetInodeTime(Vcb->McbTree->Inode.i_ctime, Vcb->McbTree->Inode.i_ctime_extra);
}

return TRUE;
}
Vcb->McbTree->CreationTime = Ext2GetInodeTime(Vcb->McbTree->Inode.i_ctime, Vcb->McbTree->Inode.i_ctime_extra);
}

Vcb->FreeBlocks = ext3_free_blocks_count(SUPER_BLOCK);
Vcb->TotalBlocks = ext3_blocks_count(SUPER_BLOCK);
Vcb->ReservedBlocks = ext3_r_blocks_count(SUPER_BLOCK);

return TRUE;
}

VOID
Ext2DropGroupBH(IN PEXT2_VCB Vcb)
Expand Down Expand Up @@ -961,10 +968,13 @@ Ext2UpdateVcbStat(
IN PEXT2_VCB Vcb
)
{
Vcb->SuperBlock->s_free_inodes_count = ext4_count_free_inodes(&Vcb->sb);
ext3_free_blocks_count_set(SUPER_BLOCK, ext4_count_free_blocks(&Vcb->sb));
Ext2SaveSuper(IrpContext, Vcb);
}
Vcb->SuperBlock->s_free_inodes_count = ext4_count_free_inodes(&Vcb->sb);
Vcb->FreeBlocks = ext4_count_free_blocks(&Vcb->sb);
ext3_free_blocks_count_set(SUPER_BLOCK, Vcb->FreeBlocks);
ext3_blocks_count_set(SUPER_BLOCK, Vcb->TotalBlocks);
ext3_r_blocks_count_set(SUPER_BLOCK, Vcb->ReservedBlocks);
Ext2SaveSuper(IrpContext, Vcb);
}

NTSTATUS
Ext2NewBlock(
Expand Down
13 changes: 8 additions & 5 deletions Ext4Fsd/include/ext2fs.h
Original file line number Diff line number Diff line change
Expand Up @@ -752,11 +752,14 @@ typedef struct _EXT2_VCB {
struct ext3_sb_info sbi;

/* Maximum file size in blocks ... */
ULONG max_blocks_per_layer[EXT2_BLOCK_TYPES];
ULONG max_data_blocks;
loff_t max_bitmap_bytes;
loff_t max_bytes;
} EXT2_VCB, *PEXT2_VCB;
ULONG max_blocks_per_layer[EXT2_BLOCK_TYPES];
ULONG max_data_blocks;
loff_t max_bitmap_bytes;
loff_t max_bytes;
ext4_fsblk_t TotalBlocks;
ext4_fsblk_t ReservedBlocks;
ext4_fsblk_t FreeBlocks;
} EXT2_VCB, *PEXT2_VCB;

//
// Flags for EXT2_VCB
Expand Down
15 changes: 9 additions & 6 deletions Ext4Fsd/memory.c
Original file line number Diff line number Diff line change
Expand Up @@ -2614,10 +2614,13 @@ Ext2InitializeVcb( IN PEXT2_IRP_CONTEXT IrpContext,

has_huge_files = EXT3_HAS_RO_COMPAT_FEATURE(&Vcb->sb, EXT4_FEATURE_RO_COMPAT_HUGE_FILE);

Vcb->sb.s_maxbytes = ext3_max_size(BLOCK_BITS, has_huge_files);
Vcb->max_bitmap_bytes = ext3_max_bitmap_size(BLOCK_BITS,
has_huge_files);
Vcb->max_bytes = ext3_max_size(BLOCK_BITS, has_huge_files);
Vcb->sb.s_maxbytes = ext3_max_size(BLOCK_BITS, has_huge_files);
Vcb->max_bitmap_bytes = ext3_max_bitmap_size(BLOCK_BITS,
has_huge_files);
Vcb->max_bytes = ext3_max_size(BLOCK_BITS, has_huge_files);
Vcb->TotalBlocks = ext3_blocks_count(sb);
Vcb->ReservedBlocks = ext3_r_blocks_count(sb);
Vcb->FreeBlocks = ext3_free_blocks_count(sb);

/* calculate maximum file bocks ... */
{
Expand All @@ -2626,8 +2629,8 @@ Ext2InitializeVcb( IN PEXT2_IRP_CONTEXT IrpContext,

ASSERT(BLOCK_BITS == Ext2Log2(BLOCK_SIZE));

Vcb->sbi.s_groups_count = (ULONG)(ext3_blocks_count(sb) - sb->s_first_data_block +
sb->s_blocks_per_group - 1) / sb->s_blocks_per_group;
Vcb->sbi.s_groups_count = (ULONG)(ext3_blocks_count(sb) - sb->s_first_data_block +
sb->s_blocks_per_group - 1) / sb->s_blocks_per_group;

Vcb->max_data_blocks = 0;
for (i = 0; i < EXT2_BLOCK_TYPES; i++) {
Expand Down
175 changes: 175 additions & 0 deletions tests/64bit_superblock_stress.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
#!/usr/bin/env python3
"""Stress test for Ext4Fsd 64-bit superblock counters."""
from __future__ import annotations

import struct
import subprocess
import tempfile
from pathlib import Path

SUPER_OFFSET = 1024
SUPER_SIZE = 1024
EXT4_FEATURE_RO_COMPAT_METADATA_CSUM = 0x2000


def _build_crc32c_table() -> list[int]:
poly = 0x1EDC6F41
table = []
for i in range(256):
crc = i
for _ in range(8):
if crc & 1:
crc = (crc >> 1) ^ poly
else:
crc >>= 1
crc &= 0xFFFFFFFF
table.append(crc)
return table


CRC32C_TABLE = _build_crc32c_table()


def crc32c(data: bytes, crc: int = 0xFFFFFFFF) -> int:
"""Compute the CRC32C checksum using the kernel's convention."""
for b in data:
crc = CRC32C_TABLE[(crc ^ b) & 0xFF] ^ (crc >> 8)
crc &= 0xFFFFFFFF
Comment on lines +15 to +37

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Generate CRC32C table with wrong polynomial orientation

The stress test builds its CRC table by shifting the CRC right and XORing with 0x1EDC6F41. That polynomial value is the non‑reflected CRC32C polynomial, so using it with a right‑shift algorithm produces an incorrect checksum. As written the script always writes an invalid superblock checksum even after repairing the block counters, causing e2fsck to continue failing. Use the reflected constant 0x82F63B78 (or a standard CRC32C implementation) when generating the table.

Useful? React with 👍 / 👎.

return crc


def run(cmd: list[str], check: bool = True) -> subprocess.CompletedProcess:
return subprocess.run(cmd, check=check, capture_output=True, text=True)


def read_super(path: Path) -> bytearray:
with path.open('rb') as f:
f.seek(SUPER_OFFSET)
return bytearray(f.read(SUPER_SIZE))


def write_super(path: Path, sb: bytearray) -> None:
with path.open('r+b') as f:
f.seek(SUPER_OFFSET)
f.write(sb)


def superblock_feature_ro(sb: bytearray) -> int:
return struct.unpack_from('<I', sb, 0x5C)[0]


def update_super_checksum(sb: bytearray) -> None:
if not (superblock_feature_ro(sb) & EXT4_FEATURE_RO_COMPAT_METADATA_CSUM):
return
checksum = crc32c(sb[:0x3FC])
struct.pack_into('<I', sb, 0x3FC, checksum)


def truncate_super_counts(image: Path) -> None:
sb = read_super(image)
for offset in (0x150, 0x154, 0x158):
struct.pack_into('<I', sb, offset, 0)
update_super_checksum(sb)
write_super(image, sb)


def compute_groups_count(sb: bytearray) -> int:
inodes_count = struct.unpack_from('<I', sb, 0x00)[0]
inodes_per_group = struct.unpack_from('<I', sb, 0x28)[0]
return (inodes_count + inodes_per_group - 1) // inodes_per_group


def compute_free_blocks(image: Path, sb: bytearray) -> int:
block_size = 1024 << struct.unpack_from('<I', sb, 0x18)[0]
first_data_block = struct.unpack_from('<I', sb, 0x14)[0]
desc_size = struct.unpack_from('<H', sb, 0xFE)[0] or 32
groups = compute_groups_count(sb)
table_offset = (first_data_block + 1) * block_size
total_free = 0
with image.open('rb') as f:
f.seek(table_offset)
descriptor_bytes = f.read(desc_size * groups)
for group in range(groups):
base = group * desc_size
free_lo = struct.unpack_from('<H', descriptor_bytes, base + 12)[0]
free_hi = 0
if desc_size >= 64:
free_hi = struct.unpack_from('<H', descriptor_bytes, base + 32 + 12)[0]
total_free += free_lo | (free_hi << 16)
return total_free


def repair_super_counts(image: Path) -> None:
sb = read_super(image)
blocks_lo = struct.unpack_from('<I', sb, 0x04)[0]
blocks_hi = struct.unpack_from('<I', sb, 0x150)[0]
reserved_lo = struct.unpack_from('<I', sb, 0x08)[0]
reserved_hi = struct.unpack_from('<I', sb, 0x154)[0]
total_blocks = blocks_lo | (blocks_hi << 32)
reserved = reserved_lo | (reserved_hi << 32)
free_blocks = compute_free_blocks(image, sb)

struct.pack_into('<I', sb, 0x04, total_blocks & 0xFFFFFFFF)
struct.pack_into('<I', sb, 0x150, total_blocks >> 32)
struct.pack_into('<I', sb, 0x08, reserved & 0xFFFFFFFF)
struct.pack_into('<I', sb, 0x154, reserved >> 32)
struct.pack_into('<I', sb, 0x0C, free_blocks & 0xFFFFFFFF)
struct.pack_into('<I', sb, 0x158, free_blocks >> 32)
update_super_checksum(sb)
write_super(image, sb)


def mount(volume: Path, target: Path) -> None:
run(['mount', '-t', 'ext4', '-o', 'loop', str(volume), str(target)])


def umount(target: Path) -> None:
run(['umount', str(target)])


def make_random_writes(target: Path, iteration: int) -> None:
file_path = target / f'stress-{iteration}.bin'
run(['dd', 'if=/dev/zero', f'of={file_path}', 'bs=1M', 'count=1'], check=True)


def main() -> None:
iterations = 3
if not any(Path('/dev').glob('loop*')):
print('Loop devices unavailable; skipping 64-bit superblock stress test.')
return
with tempfile.TemporaryDirectory() as tmp_dir:
tmp_path = Path(tmp_dir)
image = tmp_path / 'ext4-64bit.img'
mount_point = tmp_path / 'mnt'
mount_point.mkdir()

run(['truncate', '-s', '5T', str(image)])
run([
'mkfs.ext4',
'-O', '64bit,^meta_bg',
'-b', '1024',
'-E', 'lazy_itable_init=1,lazy_journal_init=1',
str(image),
])

for i in range(iterations):
mount(image, mount_point)
make_random_writes(mount_point, i)
run(['sync'])
umount(mount_point)

truncate_super_counts(image)
bad = run(['e2fsck', '-n', str(image)], check=False)
if bad.returncode == 0:
raise RuntimeError('Expected fsck failure after truncating counters')

repair_super_counts(image)
good = run(['e2fsck', '-n', str(image)], check=False)
if good.returncode != 0:
raise RuntimeError('fsck failed after repairing counters')

print('64-bit superblock stress test completed successfully.')


if __name__ == '__main__':
main()
1 change: 1 addition & 0 deletions tests/include/poppack.h
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#pragma pack(pop)
1 change: 1 addition & 0 deletions tests/include/pshpack1.h
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#pragma pack(push, 1)