[PATCH v2] gfs2: Fix use-after-free and reference leaks in iomap inline data paths

Deepanshu Kartikey <[email protected]>
Newsgroups dev.linux.lists.gfs2,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
The inline data buffer head (dibh) is being released prematurely in
gfs2_iomap_begin() via release_metapath(), while iomap->inline_data
still points to dibh->b_data. This causes a use-after-free when
iomap_write_end_inline() later attempts to write to the inline data
area.

Additionally, gfs2_iomap_get() and gfs2_iomap_alloc() leak buffer head
references when they call __gfs2_iomap_get() for inline data files,
as they don't release the dibh reference stored in iomap->private.

The bug sequence for the use-after-free:
1. __gfs2_iomap_get() calls gfs2_meta_inode_buffer() to read inode
   metadata into dibh
2. Sets iomap->inline_data = dibh->b_data + sizeof(struct gfs2_dinode)
3. Calls release_metapath() which calls brelse(dibh), dropping refcount
   to 0
4. kswapd reclaims the page (~39ms later in the syzbot report)
5. iomap_write_end_inline() tries to memcpy() to iomap->inline_data
6. KASAN detects use-after-free write to freed memory

Fix by storing dibh in iomap->private and incrementing its refcount
with get_bh() in __gfs2_iomap_get(). The buffer is then properly
released in:
- gfs2_iomap_end() for the iomap iterator path
- gfs2_iomap_get() before returning
- gfs2_iomap_alloc() before returning

This ensures the page stays alive for the entire iomap operation and
prevents reference leaks.

Changes in v2:
- Simplified iomap->private cleanup in gfs2_iomap_end() per feedback
  from Andreas Gruenbacher (removed redundant type check and explicit
  NULL assignment)
- Added missing brelse(iomap->private) in gfs2_iomap_get() to fix
  buffer head reference leak
- Added missing brelse(iomap->private) in gfs2_iomap_alloc() to fix
  buffer head reference leak

Note: A C reproducer is not available for this issue. The fix is based
on analysis of the KASAN report and code review.

Reported-by: [email protected]
Closes: https://syzkaller.appspot.com/bug?extid=ea1cd4aa4d1e98458a55
Signed-off-by: Deepanshu Kartikey <[email protected]>
---
 fs/gfs2/bmap.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/fs/gfs2/bmap.c b/fs/gfs2/bmap.c
index 131091520de6..81a958694d1f 100644
--- a/fs/gfs2/bmap.c
+++ b/fs/gfs2/bmap.c
@@ -887,6 +887,8 @@ static int __gfs2_iomap_get(struct inode *inode, loff_t pos, loff_t length,
 			      sizeof(struct gfs2_dinode);
 		iomap->type = IOMAP_INLINE;
 		iomap->inline_data = dibh->b_data + sizeof(struct gfs2_dinode);
+		iomap->private = dibh;
+		get_bh(dibh);
 		goto out;
 	}
 
@@ -1144,6 +1146,9 @@ static int gfs2_iomap_end(struct inode *inode, loff_t pos, loff_t length,
 	struct gfs2_inode *ip = GFS2_I(inode);
 	struct gfs2_sbd *sdp = GFS2_SB(inode);
 
+	if (iomap->private)
+		brelse(iomap->private);
+
 	switch (flags & (IOMAP_WRITE | IOMAP_ZERO)) {
 	case IOMAP_WRITE:
 		if (flags & IOMAP_DIRECT)
@@ -1419,6 +1424,8 @@ int gfs2_iomap_get(struct inode *inode, loff_t pos, loff_t length,
 
 	ret = __gfs2_iomap_get(inode, pos, length, 0, iomap, &mp);
 	release_metapath(&mp);
+	if (iomap->private)
+		brelse(iomap->private);
 	return ret;
 }
 
@@ -1432,6 +1439,8 @@ int gfs2_iomap_alloc(struct inode *inode, loff_t pos, loff_t length,
 	if (!ret && iomap->type == IOMAP_HOLE)
 		ret = __gfs2_iomap_alloc(inode, iomap, &mp);
 	release_metapath(&mp);
+	if (iomap->private)
+		brelse(iomap->private);
 	return ret;
 }
 
-- 
2.43.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.