| From 2e6b11b8cd3be32ce43e3c56d5eb4f9ac17096b3 Mon Sep 17 00:00:00 2001 |
| From: Sasha Levin <sashal@kernel.org> |
| Date: Fri, 30 Jan 2026 14:51:34 +0530 |
| Subject: gfs2: Fix use-after-free in iomap inline data write path |
| |
| From: Deepanshu Kartikey <kartikey406@gmail.com> |
| |
| [ Upstream commit faddeb848305e79db89ee0479bb0e33380656321 ] |
| |
| 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. |
| |
| The bug sequence: |
| 1. gfs2_iomap_begin() 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_begin(). The buffer is then properly |
| released in gfs2_iomap_end() after the inline write completes, |
| ensuring the page stays alive for the entire iomap operation. |
| |
| Note: A C reproducer is not available for this issue. The fix is based |
| on analysis of the KASAN report and code review showing the buffer head |
| is freed before use. |
| |
| [agruenba: Take buffer head reference in gfs2_iomap_begin() to avoid |
| leaks in gfs2_iomap_get() and gfs2_iomap_alloc().] |
| |
| Reported-by: syzbot+ea1cd4aa4d1e98458a55@syzkaller.appspotmail.com |
| Closes: https://syzkaller.appspot.com/bug?extid=ea1cd4aa4d1e98458a55 |
| Fixes: d0a22a4b03b8 ("gfs2: Fix iomap write page reclaim deadlock") |
| Signed-off-by: Deepanshu Kartikey <kartikey406@gmail.com> |
| Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com> |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| fs/gfs2/bmap.c | 13 ++++++++++++- |
| 1 file changed, 12 insertions(+), 1 deletion(-) |
| |
| diff --git a/fs/gfs2/bmap.c b/fs/gfs2/bmap.c |
| index 131091520de6b..fdcac8e3f2ba2 100644 |
| --- a/fs/gfs2/bmap.c |
| +++ b/fs/gfs2/bmap.c |
| @@ -1127,10 +1127,18 @@ static int gfs2_iomap_begin(struct inode *inode, loff_t pos, loff_t length, |
| goto out_unlock; |
| break; |
| default: |
| - goto out_unlock; |
| + goto out; |
| } |
| |
| ret = gfs2_iomap_begin_write(inode, pos, length, flags, iomap, &mp); |
| + if (ret) |
| + goto out_unlock; |
| + |
| +out: |
| + if (iomap->type == IOMAP_INLINE) { |
| + iomap->private = metapath_dibh(&mp); |
| + get_bh(iomap->private); |
| + } |
| |
| out_unlock: |
| release_metapath(&mp); |
| @@ -1144,6 +1152,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) |
| -- |
| 2.51.0 |
| |