general protection fault in lookup_one_qstr_excl

Status: moderation: ready to report (reported on 2026/11/15 03:44)
Subsystems: squashfs
Labels: prio:normal
[Documentation on labels]
Reported-by: syzbot+57470a9c0555e9e9ae37@syzkaller.appspotmail.com
First crash: 1h13m, last: now
✨ AI-generated root cause summary:

In squashfs_lookup() (fs/squashfs/namei.c), squashfs_read_metadata() returns the positive number of bytes read on success, which is stored in err. When a subsequent squashfs_dir_space() check fails, execution branches directly to read_failure without setting err to a negative errno like -EIO, causing squashfs_lookup() to return ERR_PTR(err) where err is a positive integer (e.g., 6). The VFS caller lookup_one_qstr_excl() treats this positive pointer as a valid struct dentry pointer because IS_ERR() evaluates to false, and immediately dereferences dentry->d_flags (offset 0), causing a near-NULL pointer dereference crash.

▶ ▼ ✨ Full AI root cause analysis
Based on kernel commit 84abb955b101:

Root Cause Analysis

1. Involved Components

  • File: fs/squashfs/namei.c
  • Function: squashfs_lookup()
  • VFS Caller: lookup_one_qstr_excl() in fs/namei.c (called via start_dirop() / filename_create())
  • Underlying Mechanism: Handling of error return values across squashfs_read_metadata() and squashfs_dir_space().

2. Technical Breakdown and Mechanism

In fs/squashfs/namei.c, squashfs_lookup() iterates over directory headers and directory entries to locate an inode matching the requested filename:

static struct dentry *squashfs_lookup(struct inode *dir, struct dentry *dentry,
				 unsigned int flags)
{
...
	int err;
...
	while (length < i_size_read(dir)) {
		if (!squashfs_dir_space(dir, length, sizeof(dirh)))
			goto read_failure;

		err = squashfs_read_metadata(dir->i_sb, &dirh, &block,
				&offset, sizeof(dirh));
		if (err < 0)
			goto read_failure;

		length += sizeof(dirh);
		dir_count = le32_to_cpu(dirh.count) + 1;
...
		while (dir_count--) {
			if (!squashfs_dir_space(dir, length, sizeof(*dire)))
				goto read_failure;

			err = squashfs_read_metadata(dir->i_sb, dire, &block,
					&offset, sizeof(*dire));
			if (err < 0)
				goto read_failure;

			length += sizeof(*dire);
			size = le16_to_cpu(dire->size) + 1;
...
			if (!squashfs_dir_space(dir, length, size))
				goto read_failure;

			err = squashfs_read_metadata(dir->i_sb, dire->name,
					&block, &offset, size);
			if (err < 0)
				goto read_failure;

			length += size;
...

When reading metadata succeeds, squashfs_read_metadata() returns the number of bytes read as a positive integer:

  • sizeof(dirh) (12 bytes)
  • sizeof(*dire) (8 bytes)
  • size (dire->size + 1, between 1 and 257 bytes; in this report, 6 bytes for a 5-byte filename).

When reading metadata succeeds, err holds this positive integer.

If a subsequent boundary check via squashfs_dir_space() fails (at line 205, line 224, or line 239) due to a malformed or truncated directory layout on disk, execution jumps directly to read_failure:

data_error:
	err = -EIO;

read_failure:
	ERROR("Unable to read directory block [%llx:%x]\n",
		squashfs_i(dir)->start, squashfs_i(dir)->offset);
failed:
	kfree(dire);
	return ERR_PTR(err);

Notice that while data_error sets err = -EIO before falling into read_failure, jumping directly to read_failure does not set err = -EIO or any negative error code. Consequently, err retains the positive integer from the previous successful squashfs_read_metadata() call (or 0 if stack initialization zeroed it prior to any read).


3. How the Failure Manifests

squashfs_lookup() returns ERR_PTR(err). Because err is a positive number (such as 6), ERR_PTR(6) evaluates to (void *)(long)6, i.e., 0x0000000000000006.

The VFS caller in fs/namei.c (lookup_one_qstr_excl()):

	old = dir->i_op->lookup(dir, dentry, flags);
	if (unlikely(old)) {
		dput(dentry);
		dentry = old;
	}
found:
	if (IS_ERR(dentry))
		return dentry;
	if (d_is_negative(dentry) && !(flags & LOOKUP_CREATE)) {

Because old is non-NULL (0x6), dentry is set to 0x6. Next, IS_ERR(dentry) tests whether (unsigned long)dentry >= (unsigned long)-MAX_ERRNO (>= 0xfffffffffffff001). Since 0x6 < -MAX_ERRNO, IS_ERR() evaluates to false.

The VFS assumes dentry is a valid pointer and calls d_is_negative(dentry):

static inline unsigned __d_entry_type(const struct dentry *dentry)
{
	return dentry->d_flags & DCACHE_ENTRY_TYPE;
}

This attempts to read dentry->d_flags at offset 0 (0x0000000000000006). Under KASAN, checking shadow memory for 0x6 >> 3 == 0 causes an access to KASAN_SHADOW_OFFSET (0xdffffc0000000000), triggering a General Protection Fault (#GP) reported as KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007]. In a kernel without sanitizers, this results in an unhandled supervisor read page fault at address 0x0000000000000006, halting the process or panicking the system.

▶ ▼ ✨ AI Jobs (1)
ID Workflow Result Correct Bug Created Started Finished Revision
4b036ad4 assessment-security DenialOfService: ✅ Exploitable: ❌ FilesystemTrigger: ✅ FuzzingArtifact: ❌ MemoryCorruption: ❌ NetworkNeedsSetup: ❌ NetworkTrigger: ❌ PeripheralTrigger: ❌ RemoteTrigger: ❌ Unprivileged: ❌ UserNamespace: ❌ VMGuestTrigger: ❌ VMHostTrigger: ❌ ❓ general protection fault in lookup_one_qstr_excl 2026/11/15 03:26 2026/11/15 03:26 2026/11/15 03:35 819ebe34
▶ ▼ Similar bugs (1)
Kernel Title Rank 🛈 Repro Cause bisect Fix bisect Count Last Reported Patched Status
upstream BUG: unable to handle kernel paging request in lookup_one_qstr_excl fs 8 syz, C done 7696 626d 638d 0/29 auto-obsoleted due to no activity on 2025/05/23 05:18

Sample crash report:
loop0: detected capacity change from 0 to 8
SQUASHFS error: Unable to read directory block [0:26]
Oops: general protection fault, probably for non-canonical address 0xdffffc0000000000: 0000 [#1] SMP KASAN PTI
KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007]
CPU: 0 UID: 0 PID: 5646 Comm: syz-executor151 Not tainted syzkaller #0 PREEMPT(full) 
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 11/06/2026
RIP: 0010:__d_entry_type include/linux/dcache.h:452 [inline]
RIP: 0010:d_is_miss include/linux/dcache.h:457 [inline]
RIP: 0010:d_is_negative include/linux/dcache.h:503 [inline]
RIP: 0010:lookup_one_qstr_excl+0x155/0x360 fs/namei.c:1847
Code: ff d3 48 85 c0 0f 85 8f 01 00 00 e8 a5 a6 66 ff 49 81 fe 01 f0 ff ff 72 0a e8 97 a6 66 ff e9 86 00 00 00 4c 89 f0 48 c1 e8 03 <0f> b6 04 28 84 c0 0f 85 b6 01 00 00 bd 00 00 38 00 41 23 2e 41 89
RSP: 0018:ffffc90003c57cb0 EFLAGS: 00010247
RAX: 0000000000000000 RBX: 0000000000060000 RCX: ffff8880269a1f40
RDX: 0000000000000000 RSI: 0000000000000004 RDI: 00000000ffffffff
RBP: dffffc0000000000 R08: ffff88807705343b R09: 1ffff1100ee0a687
R10: dffffc0000000000 R11: ffffed100ee0a688 R12: ffff888077053380
R13: ffff888079bb7240 R14: 0000000000000006 R15: 0000000000000006
FS:  00005555603cd400(0000) GS:ffff8881233cd000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000000000000000 CR3: 0000000026d50000 CR4: 00000000003526f0
Call Trace:
 <TASK>
 __start_dirop fs/namei.c:2971 [inline]
 start_dirop fs/namei.c:2993 [inline]
 filename_create+0x20e/0x370 fs/namei.c:5214
 filename_symlinkat+0xe8/0x430 fs/namei.c:5913
 __do_sys_symlink fs/namei.c:5946 [inline]
 __se_sys_symlink+0x4d/0x2b0 fs/namei.c:5942
 do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
 do_syscall_64+0x171/0x540 arch/x86/entry/syscall_64.c:89
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x00007fe4baeb7c49
Code: c0 79 93 eb d5 48 8d 7c 1d 00 eb 99 0f 1f 44 00 00 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 c7 c1 d8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007ffe25a11318 EFLAGS: 00000246 ORIG_RAX: 0000000000000058
RAX: ffffffffffffffda RBX: 00007ffe25a11340 RCX: 00007fe4baeb7c49
RDX: 00007fe4baeb7c49 RSI: 0000000000000000 RDI: 0000000000000000
RBP: 00007fe4baf170b0 R08: 00005555603ce060 R09: 00005555603ce060
R10: 0000000000000000 R11: 0000000000000246 R12: 00007ffe25a11340
R13: 0000000000000002 R14: 431bde82d7b634db R15: 00007fe4baf1704b
 </TASK>
Modules linked in:
---[ end trace 0000000000000000 ]---
RIP: 0010:__d_entry_type include/linux/dcache.h:452 [inline]
RIP: 0010:d_is_miss include/linux/dcache.h:457 [inline]
RIP: 0010:d_is_negative include/linux/dcache.h:503 [inline]
RIP: 0010:lookup_one_qstr_excl+0x155/0x360 fs/namei.c:1847
Code: ff d3 48 85 c0 0f 85 8f 01 00 00 e8 a5 a6 66 ff 49 81 fe 01 f0 ff ff 72 0a e8 97 a6 66 ff e9 86 00 00 00 4c 89 f0 48 c1 e8 03 <0f> b6 04 28 84 c0 0f 85 b6 01 00 00 bd 00 00 38 00 41 23 2e 41 89
RSP: 0018:ffffc90003c57cb0 EFLAGS: 00010247
RAX: 0000000000000000 RBX: 0000000000060000 RCX: ffff8880269a1f40
RDX: 0000000000000000 RSI: 0000000000000004 RDI: 00000000ffffffff
RBP: dffffc0000000000 R08: ffff88807705343b R09: 1ffff1100ee0a687
R10: dffffc0000000000 R11: ffffed100ee0a688 R12: ffff888077053380
R13: ffff888079bb7240 R14: 0000000000000006 R15: 0000000000000006
FS:  00005555603cd400(0000) GS:ffff8881234cd000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007ffe25a12000 CR3: 0000000026d50000 CR4: 00000000003526f0
----------------
Code disassembly (best guess):
   0:	ff d3                	call   *%rbx
   2:	48 85 c0             	test   %rax,%rax
   5:	0f 85 8f 01 00 00    	jne    0x19a
   b:	e8 a5 a6 66 ff       	call   0xff66a6b5
  10:	49 81 fe 01 f0 ff ff 	cmp    $0xfffffffffffff001,%r14
  17:	72 0a                	jb     0x23
  19:	e8 97 a6 66 ff       	call   0xff66a6b5
  1e:	e9 86 00 00 00       	jmp    0xa9
  23:	4c 89 f0             	mov    %r14,%rax
  26:	48 c1 e8 03          	shr    $0x3,%rax
* 2a:	0f b6 04 28          	movzbl (%rax,%rbp,1),%eax <-- trapping instruction
  2e:	84 c0                	test   %al,%al
  30:	0f 85 b6 01 00 00    	jne    0x1ec
  36:	bd 00 00 38 00       	mov    $0x380000,%ebp
  3b:	41 23 2e             	and    (%r14),%ebp
  3e:	41                   	rex.B
  3f:	89                   	.byte 0x89

Crashes (2):
Time Kernel Commit Syzkaller Config Log Report Syz repro C repro VM info Assets (help?) Manager Title
2026/11/15 04:39 linux-next 84abb955b101 819ebe34 .config strace log report syz / log C [disk image] [vmlinux] [kernel image] [mounted in repro] ci-upstream-linux-next-kasan-gce-root general protection fault in lookup_one_qstr_excl
2026/11/15 03:26 linux-next 84abb955b101 819ebe34 .config console log report info [disk image] [vmlinux] [kernel image] ci-upstream-linux-next-kasan-gce-root general protection fault in lookup_one_qstr_excl
* Struck through repros no longer work on HEAD.