5 Commits
Author SHA1 Message Date
Ernesto A. Fernández a10279ce66 Plug buffer head leak on submit for the 7.2 kernel
Starting with the 7.2 kernel, buffer heads no longer lose a reference
during the submit-wait procedure. I didn't notice that change, so now I
would expect all buffer heads to be leaking. The fact that I still can't
tell the difference makes me believe, like I mentioned before, that
somehow the kernel is still disposing of the buffer heads. Either way,
fix this.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2026-08-14 21:35:59 -03:00
Ernesto A. Fernández 7711c4704e Plug buffer head leak for checkpoint mapping block
This is the last of the three leaks discovered by Valgrind in the
userland port. During the loop that writes ephemeral objects to disk,
checkpoint mapping blocks are created and kept around for multiple
iterations until they are full. At that point, the cpm buffer head gets
released and we get a new one. All that looks correct, but no release
happens after the final loop. Fix it.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2026-08-14 19:41:33 -03:00
Ernesto A. Fernández 9619c53686 Plug leak of ip bitmap buffer head
This is the second bug discovered by valgrind in the userland tool. The
buffer head for the ip bitmap gets nulled after joining the transaction,
but we still hold a reference to it (the transactions takes a new one).
Fix it.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2026-08-14 19:21:41 -03:00
Ernesto A. Fernández f5b71842e6 Plug checkpoint buffer head leak during mount
Work on the userland port with valgrind has reavealed a number of
longstanding leaks in the kernel driver. For some reason I always
assumed that kmemleak would eventually notice issues with buffer heads,
but it clearly doesn't. This bug in particular must have been around
since the very beginning: all buffer heads for checkpoint descriptor
blocks get leaked during mount.

Since nobody noticed the problem so far, I'm guessing that the kernel
somehow disposes of abandoned buffer heads. Perhaps it has something to
do with the old issue from patch 517dd221df ("Forcefully free buffer
heads on transaction commit")? Either way, fix the leak and get the code
to match the userland port.

One thing to note: before pushing this patch I tried asking Github
Copilot to look for leaks in the super.c file and it spotted the problem
immediately. It seems to have become far more useful since the last time
I tried it, so once I'm done fixing the current crop of leaks I should
try and see what it can find.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2026-08-14 18:48:48 -03:00
Ernesto A. Fernández 83205caaad Fix build for RHEL 9.8
Kexianliang reports that the build is broken once more for RHEL:

  https://github.com/linux-apfs/linux-apfs-rw/issues/123

Fix it.

Signed-off-by: Ernesto A. Fernández <ernesto@corellium.com>
2026-08-12 17:16:00 -03:00
4 changed files with 21 additions and 10 deletions

No files matched your search

+2 -1
View File
@@ -47,6 +47,7 @@ static inline bool sb_rdonly(const struct super_block *sb) { return sb->s_flags
#if LINUX_VERSION_CODE >= KERNEL_VERSION(7, 2, 0)
#define apfs_submit_bh(op, op_flags, bh) \
do { \
put_bh(bh); \
if (op == REQ_OP_READ) \
bh_submit(bh, op | op_flags, bh_end_read); \
else \
@@ -1346,7 +1347,7 @@ __apfs_getblk(struct super_block *sb, sector_t block)
block -= nxi->nx_tier2_bno;
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0)
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 7, 0) && !RHEL_VERSION_GE(9, 8)
return __getblk_gfp(info->blki_bdev, block, sb->s_blocksize, __GFP_MOVABLE);
#else
return bdev_getblk(info->blki_bdev, block, sb->s_blocksize, __GFP_MOVABLE);
+1
View File
@@ -308,6 +308,7 @@ static int apfs_write_single_ip_bitmap(struct super_block *sb, char *bitmap, u32
err = apfs_transaction_join(sb, bh);
if (err)
goto fail;
put_bh(bh);
bh = NULL;
spaceman->sm_ip_bmaps[idx].dirty = false;
+15 -9
View File
@@ -343,8 +343,8 @@ static int apfs_read_main_super(struct super_block *sb)
xid = le64_to_cpu(msb_raw->nx_o.o_xid);
for (i = 0; i < desc_blocks; ++i) {
struct apfs_nx_superblock *desc_raw;
bool ignore;
brelse(desc_bh);
desc_bh = apfs_sb_bread(sb, desc_base + i);
if (!desc_bh) {
apfs_err(sb, "unable to read checkpoint descriptor");
@@ -352,18 +352,24 @@ static int apfs_read_main_super(struct super_block *sb)
}
desc_raw = (struct apfs_nx_superblock *)desc_bh->b_data;
ignore = false;
if (le32_to_cpu(desc_raw->nx_magic) != APFS_NX_MAGIC)
continue; /* Not a superblock */
ignore = true; /* Not a superblock */
if (le64_to_cpu(desc_raw->nx_o.o_xid) <= xid)
continue; /* Old */
ignore = true; /* Old */
if (!apfs_obj_verify_csum(sb, desc_bh))
continue; /* Corrupted */
ignore = true; /* Corrupted */
xid = le64_to_cpu(desc_raw->nx_o.o_xid);
msb_raw = desc_raw;
bno = desc_base + i;
brelse(bh);
bh = desc_bh;
if (ignore) {
brelse(desc_bh);
} else {
xid = le64_to_cpu(desc_raw->nx_o.o_xid);
msb_raw = desc_raw;
bno = desc_base + i;
brelse(bh);
bh = desc_bh;
}
desc_raw = NULL;
desc_bh = NULL;
}
+3
View File
@@ -640,6 +640,9 @@ static int apfs_write_ephemeral_objects(struct super_block *sb)
}
data_len += obj_blkcnt;
}
brelse(cpm_bh);
cpm = NULL;
cpm_bh = NULL;
/*
* The checkpoint superblock can't be set until the very end of the