* [BUG] general protection fault in jffs2_xattr_delete_inode
@ 2026-08-19 18:28 Jaeyoung Chung
2026-08-19 21:03 ` Richard Weinberger
2026-08-20 3:39 ` Zhan Xusheng
0 siblings, 2 replies; 3+ messages in thread
From: Jaeyoung Chung @ 2026-08-19 18:28 UTC (permalink / raw)
To: dwmw2, linux-mtd, richard; +Cc: kees, linux-kernel, eulgyukim, jjy600901
Hello,
We found a "general protection fault in jffs2_xattr_delete_inode" on Linux v7.2.
The issue was found by our own race fuzzer. We have not analyzed the root cause,
so we do not have a proposed fix to offer.
To reproduce the race reliably, we applied the delay patch below to the
kernel and ran the C reproducer as root inside an x86_64 QEMU guest. The
crash log we observed, the delay patch and the reproducer are all included
below.
The following kernel config options are required to reproduce the issue:
CONFIG_MTD=y
CONFIG_MTD_MTDRAM=y
CONFIG_MTDRAM_TOTAL_SIZE=128
CONFIG_MTDRAM_ERASE_SIZE=4
CONFIG_MTD_BLOCK=y
CONFIG_JFFS2_FS=y
CONFIG_JFFS2_FS_XATTR=y
CONFIG_DEBUG_FS=y
CONFIG_FAULT_INJECTION=y
CONFIG_FAILSLAB=y
CONFIG_FAULT_INJECTION_DEBUG_FS=y
CONFIG_KASAN=y
We hope this report is useful. Please let us know if any further
information would help.
Reported-by: Eulgyu Kim <eulgyukim@snu.ac.kr>
Reported-by: Jaeyoung Chung <jjy600901@snu.ac.kr>
Kernel delay patch:
==================================================================
diff --git a/fs/jffs2/fs.c b/fs/jffs2/fs.c
index 6ada8369a762..32c2aaae80d5 100644
--- a/fs/jffs2/fs.c
+++ b/fs/jffs2/fs.c
@@ -25,6 +25,7 @@
#include <linux/vmalloc.h>
#include <linux/vfs.h>
#include <linux/crc32.h>
+#include <linux/delay.h>
#include "nodelist.h"
static int jffs2_flash_setup(struct jffs2_sb_info *c);
@@ -433,6 +434,9 @@ struct inode *jffs2_new_inode (struct inode *dir_i, umode_t mode, struct jffs2_r
c = JFFS2_SB_INFO(sb);
+ if (strncmp(current->comm, "syzrepro1", 9) == 0) {
+ mdelay(10);
+ }
inode = new_inode(sb);
if (!inode)
diff --git a/fs/jffs2/write.c b/fs/jffs2/write.c
index cda9a361368e..9e7886c5b95a 100644
--- a/fs/jffs2/write.c
+++ b/fs/jffs2/write.c
@@ -12,6 +12,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
+#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/crc32.h>
#include <linux/pagemap.h>
==================================================================
C reproducer:
==================================================================
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <unistd.h>
#define SYSCHK(x) ({ long __r = (long)(x); if (__r == -1L) { perror(#x); exit(1); } __r; })
#define JM "/root/jm"
#define DIR0 JM "/d0"
#define DIR1 JM "/d1"
#define DBG "/sys/kernel/debug"
static volatile int stop_all;
static void knob(const char *path, const char *val)
{
int fd = SYSCHK(open(path, O_WRONLY));
write(fd, val, strlen(val));
close(fd);
}
static void *th_victim(void *arg)
{
char p[128];
unsigned i = 0;
int fd;
prctl(PR_SET_NAME, "syzrepro0", 0, 0, 0);
while (!stop_all) {
snprintf(p, sizeof(p), DIR0 "/v%u", i++ & 31u);
fd = open(p, O_RDWR | O_CREAT | O_EXCL, 0600);
if (fd >= 0)
close(fd);
else if (errno == ENOSPC || errno == EIO)
sched_yield();
unlink(p);
}
return NULL;
}
static void *th_inject(void *arg)
{
char p[128], nb[16];
unsigned nth = 1, i = 0;
int ffd, len, fd;
prctl(PR_SET_NAME, "syzrepro1", 0, 0, 0);
ffd = SYSCHK(open("/proc/thread-self/fail-nth", O_RDWR));
while (!stop_all) {
snprintf(p, sizeof(p), DIR1 "/t%u", i++ & 15u);
unlink(p);
len = snprintf(nb, sizeof(nb), "%u", nth);
if (write(ffd, nb, len) != len)
break;
fd = syscall(SYS_openat, AT_FDCWD, p, O_RDWR | O_CREAT | O_EXCL, 0777);
write(ffd, "0", 1);
if (fd >= 0) {
close(fd);
unlink(p);
} else if (errno == ENOSPC || errno == EIO) {
sched_yield();
}
if (++nth > 32)
nth = 1;
}
close(ffd);
return NULL;
}
static void *th_churn(void *arg)
{
long idx = (long)arg;
char nm[16], *p;
int k, fd;
snprintf(nm, sizeof(nm), "syzrepro%ld", idx);
prctl(PR_SET_NAME, nm, 0, 0, 0);
p = malloc(4008);
p[0] = '/';
memset(p + 1, 'a' + (int)(idx & 7), 3998);
p[3999] = 0;
while (!stop_all) {
for (k = 0; k < 256; k++) {
fd = syscall(SYS_openat, AT_FDCWD, p, O_RDONLY, 0);
if (fd >= 0)
close(fd);
}
}
free(p);
return NULL;
}
int main(void)
{
pthread_t th[4];
char p[128];
int i, fd;
mkdir(DBG, 0755);
if (mount("none", DBG, "debugfs", 0, NULL) != 0 && errno != EBUSY)
SYSCHK(-1);
knob(DBG "/failslab/ignore-gfp-wait", "N");
knob(DBG "/failslab/verbose", "1");
knob(DBG "/failslab/probability", "0");
knob(DBG "/failslab/cache-filter", "N");
mkdir(JM, 0777);
if (mount("mtd0", JM, "jffs2", 0, NULL) != 0 &&
mount("/dev/mtdblock0", JM, "jffs2", 0, NULL) != 0)
SYSCHK(-1);
mkdir(DIR0, 0777);
mkdir(DIR1, 0777);
for (i = 0; i < 4; i++) {
snprintf(p, sizeof(p), DIR1 "/w%d", i);
fd = open(p, O_RDWR | O_CREAT | O_EXCL, 0600);
if (fd >= 0)
close(fd);
unlink(p);
}
pthread_create(&th[0], NULL, th_victim, NULL);
pthread_create(&th[1], NULL, th_inject, NULL);
pthread_create(&th[2], NULL, th_churn, (void *)2L);
pthread_create(&th[3], NULL, th_churn, (void *)3L);
sleep(165);
stop_all = 1;
for (i = 0; i < 4; i++)
pthread_join(th[i], NULL);
return 0;
}
==================================================================
Crash log:
==================================================================
name failslab, interval 1, probability 0, space 0, times 0
FAULT_INJECTION: forcing a failure.
name failslab, interval 1, probability 0, space 0, times 0
FAULT_INJECTION: forcing a failure.
name failslab, interval 1, probability 0, space 0, times 0
Oops: general protection fault, probably for non-canonical address 0xec8c888c8c8c8c91: 0000 [#1] SMP KASAN PTI
KASAN: maybe wild-memory-access in range [0x6464646464646488-0x646464646464648f]
CPU: 3 UID: 0 PID: 402 Comm: syzrepro1 Not tainted 7.2.0-dirty #2 PREEMPT
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.13.0-1ubuntu1.1 04/01/2014
RIP: 0010:jffs2_xattr_delete_inode+0x38/0x300 fs/jffs2/xattr.c:602
Code: 41 55 41 54 53 48 83 ec 50 48 89 3c 24 48 85 f6 74 2a 49 89 f6 49 bd 00 00 00 00 00 fc ff df 48 8d 5e 28 48 89 d8 48 c1 e8 03 <42> 0f b6 04 28 84 c0 0f 85 90 02 00 00 83 3b 00 74 14 48 83 c4 50
RSP: 0018:ffff888104ad7918 EFLAGS: 00010203
RAX: 0c8c8c8c8c8c8c91 RBX: 646464646464648c RCX: 0000000000000001
RDX: 0000000000000001 RSI: 6464646464646464 RDI: ffff888108260000
RBP: dffffc0000000000 R08: ffff888104ad7987 R09: 1ffff1102095af30
R10: dffffc0000000000 R11: ffffed102095af31 R12: 1ffff11022349cfc
R13: dffffc0000000000 R14: 6464646464646464 R15: 1ffff11022349ce8
FS: 00007863627186c0(0000) GS:ffff88815e8ac000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000786362717f78 CR3: 000000010795c000 CR4: 00000000000006f0
Call Trace:
<TASK>
jffs2_do_clear_inode+0x4d/0x2f0 fs/jffs2/readinode.c:1418
evict+0x353/0x700 fs/inode.c:825
jffs2_new_inode+0x443/0xce0 fs/jffs2/fs.c:-1
jffs2_create+0x87/0x300 fs/jffs2/dir.c:182
lookup_open fs/namei.c:4508 [inline]
open_last_lookups fs/namei.c:4608 [inline]
path_openat+0xe3c/0x29b0 fs/namei.c:4860
do_file_open+0x19d/0x360 fs/namei.c:4892
do_sys_openat2+0x9a/0x100 fs/open.c:1368
do_sys_open fs/open.c:1374 [inline]
__do_sys_openat fs/open.c:1390 [inline]
__se_sys_openat fs/open.c:1385 [inline]
__x64_sys_openat+0xf8/0x130 fs/open.c:1385
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0xf7/0x370 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x76/0x7e
RIP: 0033:0x78636301e829
Code: 08 89 e8 5b 5d c3 66 2e 0f 1f 84 00 00 00 00 00 90 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 8b 0d a7 15 0d 00 f7 d8 64 89 01 48
RSP: 002b:0000786362717df8 EFLAGS: 00000246 ORIG_RAX: 0000000000000101
RAX: ffffffffffffffda RBX: 0000000000000007 RCX: 000078636301e829
RDX: 00000000000000c2 RSI: 0000786362717e10 RDI: 00000000ffffff9c
RBP: 0000000000000001 R08: 0000000000000075 R09: 0000000000000037
R10: 00000000000001ff R11: 0000000000000246 R12: 0000786362717e10
R13: 0000000000000027 R14: 0000000000000004 R15: 0000786362717e00
</TASK>
Modules linked in:
---[ end trace 0000000000000000 ]---
RIP: 0010:jffs2_xattr_delete_inode+0x38/0x300 fs/jffs2/xattr.c:602
Code: 41 55 41 54 53 48 83 ec 50 48 89 3c 24 48 85 f6 74 2a 49 89 f6 49 bd 00 00 00 00 00 fc ff df 48 8d 5e 28 48 89 d8 48 c1 e8 03 <42> 0f b6 04 28 84 c0 0f 85 90 02 00 00 83 3b 00 74 14 48 83 c4 50
RSP: 0018:ffff888104ad7918 EFLAGS: 00010203
RAX: 0c8c8c8c8c8c8c91 RBX: 646464646464648c RCX: 0000000000000001
RDX: 0000000000000001 RSI: 6464646464646464 RDI: ffff888108260000
RBP: dffffc0000000000 R08: ffff888104ad7987 R09: 1ffff1102095af30
R10: dffffc0000000000 R11: ffffed102095af31 R12: 1ffff11022349cfc
R13: dffffc0000000000 R14: 6464646464646464 R15: 1ffff11022349ce8
FS: 00007863627186c0(0000) GS:ffff88815e8ac000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000786362717f78 CR3: 000000010795c000 CR4: 00000000000006f0
----------------
Code disassembly (best guess):
0: 41 55 push %r13
2: 41 54 push %r12
4: 53 push %rbx
5: 48 83 ec 50 sub $0x50,%rsp
9: 48 89 3c 24 mov %rdi,(%rsp)
d: 48 85 f6 test %rsi,%rsi
10: 74 2a je 0x3c
12: 49 89 f6 mov %rsi,%r14
15: 49 bd 00 00 00 00 00 movabs $0xdffffc0000000000,%r13
1c: fc ff df
1f: 48 8d 5e 28 lea 0x28(%rsi),%rbx
23: 48 89 d8 mov %rbx,%rax
26: 48 c1 e8 03 shr $0x3,%rax
* 2a: 42 0f b6 04 28 movzbl (%rax,%r13,1),%eax <-- trapping instruction
2f: 84 c0 test %al,%al
31: 0f 85 90 02 00 00 jne 0x2c7
37: 83 3b 00 cmpl $0x0,(%rbx)
3a: 74 14 je 0x50
3c: 48 83 c4 50 add $0x50,%rsp
==================================================================
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 3+ messages in thread* Re: [BUG] general protection fault in jffs2_xattr_delete_inode
2026-08-19 18:28 [BUG] general protection fault in jffs2_xattr_delete_inode Jaeyoung Chung
@ 2026-08-19 21:03 ` Richard Weinberger
2026-08-20 3:39 ` Zhan Xusheng
1 sibling, 0 replies; 3+ messages in thread
From: Richard Weinberger @ 2026-08-19 21:03 UTC (permalink / raw)
To: Jaeyoung Chung
Cc: David Woodhouse, linux-mtd, Kees Cook, linux-kernel, eulgyukim
----- Ursprüngliche Mail -----
> Von: "Jaeyoung Chung" <jjy600901@snu.ac.kr>
[...]
> RIP: 0010:jffs2_xattr_delete_inode+0x38/0x300 fs/jffs2/xattr.c:602
> Code: 41 55 41 54 53 48 83 ec 50 48 89 3c 24 48 85 f6 74 2a 49 89 f6 49 bd 00 00
> 00 00 00 fc ff df 48 8d 5e 28 48 89 d8 48 c1 e8 03 <42> 0f b6 04 28 84 c0 0f 85
> 90 02 00 00 83 3b 00 74 14 48 83 c4 50
> RSP: 0018:ffff888104ad7918 EFLAGS: 00010203
> RAX: 0c8c8c8c8c8c8c91 RBX: 646464646464648c RCX: 0000000000000001
> RDX: 0000000000000001 RSI: 6464646464646464 RDI: ffff888108260000
> RBP: dffffc0000000000 R08: ffff888104ad7987 R09: 1ffff1102095af30
> R10: dffffc0000000000 R11: ffffed102095af31 R12: 1ffff11022349cfc
> R13: dffffc0000000000 R14: 6464646464646464 R15: 1ffff11022349ce8
> FS: 00007863627186c0(0000) GS:ffff88815e8ac000(0000) knlGS:0000000000000000
> CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
> CR2: 0000786362717f78 CR3: 000000010795c000 CR4: 00000000000006f0
> Call Trace:
> <TASK>
> jffs2_do_clear_inode+0x4d/0x2f0 fs/jffs2/readinode.c:1418
> evict+0x353/0x700 fs/inode.c:825
> jffs2_new_inode+0x443/0xce0 fs/jffs2/fs.c:-1
> jffs2_create+0x87/0x300 fs/jffs2/dir.c:182
> lookup_open fs/namei.c:4508 [inline]
> open_last_lookups fs/namei.c:4608 [inline]
> path_openat+0xe3c/0x29b0 fs/namei.c:4860
> do_file_open+0x19d/0x360 fs/namei.c:4892
> do_sys_openat2+0x9a/0x100 fs/open.c:1368
> do_sys_open fs/open.c:1374 [inline]
> __do_sys_openat fs/open.c:1390 [inline]
> __se_sys_openat fs/open.c:1385 [inline]
> __x64_sys_openat+0xf8/0x130 fs/open.c:1385
> do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
> do_syscall_64+0xf7/0x370 arch/x86/entry/syscall_64.c:94
> entry_SYSCALL_64_after_hwframe+0x76/0x7e
> RIP: 0033:0x78636301e829
So, the partially created inode is evicted in the create path.
Does this change help?
diff --git a/fs/jffs2/os-linux.h b/fs/jffs2/os-linux.h
index 86ab014a349c..a9906f9e3638 100644
--- a/fs/jffs2/os-linux.h
+++ b/fs/jffs2/os-linux.h
@@ -57,6 +57,7 @@ static inline void jffs2_init_inode_info(struct jffs2_inode_info *f)
f->target = NULL;
f->flags = 0;
f->usercompr = 0;
+ f->inocache = NULL;
}
Thanks,
//richard
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 3+ messages in thread
* Re: [BUG] general protection fault in jffs2_xattr_delete_inode
2026-08-19 18:28 [BUG] general protection fault in jffs2_xattr_delete_inode Jaeyoung Chung
2026-08-19 21:03 ` Richard Weinberger
@ 2026-08-20 3:39 ` Zhan Xusheng
1 sibling, 0 replies; 3+ messages in thread
From: Zhan Xusheng @ 2026-08-20 3:39 UTC (permalink / raw)
To: Jaeyoung Chung, dwmw2, linux-mtd, richard
Cc: Zhan Xusheng, kees, linux-kernel, eulgyukim
On Thu, 20 Aug 2026 03:28:04 +0900, Jaeyoung Chung wrote:
> We have not analyzed the root cause, so we do not have a proposed fix
> to offer.
f->inocache is never initialised, and jffs2_new_inode() has two error paths
that evict the inode before it gets assigned.
Your log says it outright. RSI is the ic argument:
RSI: 6464646464646464
RBX: 646464646464648c
1f: 48 8d 5e 28 lea 0x28(%rsi),%rbx
0x64 is 'd', and th_churn() does memset(p + 1, 'a' + (idx & 7), 3998) with
idx 3. So ic is not a stale pointer to anything, it is the pathname bytes
that thread left lying in the page the inode was later carved from. With
CONFIG_JFFS2_FS_XATTR the 0x28 is pino_nlink, so the faulting access is the
ic->pino_nlink of xattr.c:602.
Nothing initialises that field. jffs2_inode_cachep has a constructor, and
jffs2_i_init_once() only does mutex_init(), target = NULL and
inode_init_once(). jffs2_alloc_inode() allocates with plain GFP_KERNEL, so
no zeroing. jffs2_init_inode_info() sets seven fields and inocache is not
among them. inocache also sits before vfs_inode in the struct, so
inode_init_always() does not reach it.
On this path f->inocache is assigned only by jffs2_do_new_inode(),
write.c:35. Both of these return before that, and both then do
make_bad_inode() and iput():
fs.c:459 jffs2_init_acl_pre() fails
fs.c:466 jffs2_do_new_inode() fails in jffs2_alloc_inode_cache(),
write.c:28
Either is an allocation failure, so fail-nth reaches both. The inode is
unhashed, so inode_generic_drop() makes iput() evict it, which is the
evict() -> jffs2_do_clear_inode() -> jffs2_xattr_delete_inode() in your
trace. The mdelay() before new_inode() gives the churn threads a window to
leave their bytes where the inode is then carved from.
mtd/next has the same jffs2_init_inode_info(), so:
--- a/fs/jffs2/os-linux.h
+++ b/fs/jffs2/os-linux.h
@@ -55,6 +55,7 @@ static inline void jffs2_init_inode_info(struct jffs2_inode_info *f)
f->metadata = NULL;
f->dents = NULL;
f->target = NULL;
+ f->inocache = NULL;
f->flags = 0;
f->usercompr = 0;
}
Both callers assign inocache immediately afterwards, jffs2_do_read_inode()
at readinode.c:1335 as its first act and jffs2_do_new_inode() at write.c:35,
and jffs2_xattr_delete_inode() returns early on a NULL ic, so the read path
is unaffected.
I have not run your reproducer, so please confirm it on your setup before
this goes out as a patch.
Thanks,
Zhan Xusheng
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-08-20 3:40 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-19 18:28 [BUG] general protection fault in jffs2_xattr_delete_inode Jaeyoung Chung
2026-08-19 21:03 ` Richard Weinberger
2026-08-20 3:39 ` Zhan Xusheng
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox