general protection fault in __d_obtain_alias

Status: moderation: reported on 2026/11/14 02:16
Subsystems: squashfs
Labels: prio:normal
[Documentation on labels]
Reported-by: syzbot+3add20eda8a95b68a861@syzkaller.appspotmail.com
First crash: 1h58m, last: 1h57m
✨ AI-generated root cause summary:

In fs/squashfs/inode.c:squashfs_read_inode(), squashfs_read_metadata() returns the number of bytes read on success (e.g. sizeof(struct squashfs_dir_inode) == 32) into err. When checking directory inodes (SQUASHFS_DIR_TYPE and SQUASHFS_LDIR_TYPE) where inode->i_size < 3, the code jumps to failed_read via goto without updating err to a negative error code (such as -EINVAL). As a result, squashfs_read_inode() returns a positive integer (32 or 40), which squashfs_iget() wraps with ERR_PTR(err) into (struct inode *)0x20. Callers like d_obtain_alias() check IS_ERR(inode), which evaluates to false for positive values, and subsequently dereference inode->i_sb at offset 0x28 (address 0x48), triggering a near-NULL pointer dereference crash.

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

Technical Analysis of the Root Cause

1. Relevant Subsystem and Files

  • File: fs/squashfs/inode.c
  • Functions: squashfs_read_inode() (lines 256–260, 285–289) and squashfs_iget() (lines 93–97)
  • Callers / Manifestation Sites: fs/squashfs/export.c:squashfs_export_iget(), fs/dcache.c:__d_obtain_alias(), and fs/squashfs/namei.c:squashfs_lookup()

2. Root Cause Mechanism

When reading metadata for SquashFS directory inodes in squashfs_read_inode(), squashfs_read_metadata() is invoked to read the on-disk inode structure into memory:

case SQUASHFS_DIR_TYPE: {
    struct squashfs_dir_inode *sqsh_ino = &squashfs_ino.dir;

    err = squashfs_read_metadata(sb, sqsh_ino, &block, &offset,
            sizeof(*sqsh_ino));
    if (err < 0)
        goto failed_read;

    /* The directory size must be 3 or larger, as it includes "." and ".." entries */
    inode->i_size = le16_to_cpu(sqsh_ino->file_size);
    if (inode->i_size < 3)
        goto failed_read;
...
  1. Positive Return Value Retained: squashfs_read_metadata() returns the number of bytes read on success, which equals sizeof(struct squashfs_dir_inode) (32 bytes / 0x20) for standard directory inodes (SQUASHFS_DIR_TYPE), or sizeof(struct squashfs_ldir_inode) (40 bytes / 0x28) for long directory inodes (SQUASHFS_LDIR_TYPE). Thus, err is positive (0x20).
  2. Missing Error Code Assignment: If the directory inode has inode->i_size < 3 (e.g., from a malformed or corrupted filesystem image), the condition if (inode->i_size < 3) triggers and jumps to failed_read. However, unlike other error branches in squashfs_read_inode() which set err = -EINVAL; before jumping, this branch does not set err. Consequently, squashfs_read_inode() logs the error message:
    SQUASHFS error: Unable to read inode 0x87
    
    and returns err = 32 (0x20) instead of a negative errno.
  3. Improper Pointer Formation in squashfs_iget(): In squashfs_iget():
    err = squashfs_read_inode(inode, ino);
    if (err) {
        iget_failed(inode);
        return ERR_PTR(err);
    }
    
    Because err == 32, squashfs_iget() calls iget_failed(inode) (which frees and destroys the inode) and returns ERR_PTR(32). ERR_PTR(32) evaluates to (struct inode *)0x0000000000000020.
  4. Bypassing IS_ERR() Check: In callers like squashfs_export_iget():
    dentry = d_obtain_alias(squashfs_iget(sb, ino, ino_num));
    
    d_obtain_alias() calls __d_obtain_alias(inode, true). In __d_obtain_alias():
    if (!inode)
        return ERR_PTR(-ESTALE);
    if (IS_ERR(inode))
        return ERR_CAST(inode);
    
    sb = inode->i_sb;
    
    In the Linux kernel, IS_ERR_VALUE(x) checks whether x >= (unsigned long)-MAX_ERRNO (i.e., in the range [0xfffffffffffff001, 0xffffffffffffffff]). The value 0x20 is neither NULL nor within the negative error range, so IS_ERR(inode) evaluates to false.
  5. Near-NULL Dereference: The function proceeds to dereference inode->i_sb at offset 0x28 (0x20 + 0x28 = 0x48). Accessing memory at address 0x48 in the zero page triggers a kernel crash (on KASAN-enabled builds, checking KASAN shadow at 0xdffffc0000000000 + (0x48 >> 3) = 0xdffffc0000000009 triggers a GPF / null-ptr-deref splat).
▶ ▼ ✨ AI Jobs (1)
ID Workflow Result Correct Bug Created Started Finished Revision
f69a9320 assessment-security DenialOfService: ✅ Exploitable: ❌ FilesystemTrigger: ✅ FuzzingArtifact: ❌ MemoryCorruption: ❌ NetworkNeedsSetup: ❌ NetworkTrigger: ❌ PeripheralTrigger: ❌ RemoteTrigger: ❌ Unprivileged: ❌ UserNamespace: ❌ VMGuestTrigger: ❌ VMHostTrigger: ❌ ❓ general protection fault in __d_obtain_alias 2026/11/14 01:30 2026/11/14 01:30 2026/11/14 02:06 819ebe34

Sample crash report:
SQUASHFS: Entered squashfs_read_metadata [4e8:87]
SQUASHFS: Got metadata 0, start block 1256, refcount 1, error 0
SQUASHFS: Entered squashfs_read_metadata [6ec:0]
SQUASHFS: Got metadata 1, start block 1772, refcount 1, error 0
SQUASHFS: Entered squashfs_read_metadata [6ec:0]
SQUASHFS: Got metadata 1, start block 1772, refcount 1, error 0
SQUASHFS: Entered squashfs_read_metadata [4e8:87]
SQUASHFS: Got metadata 0, start block 1256, refcount 1, error 0
SQUASHFS error: Unable to read inode 0x87
Oops: general protection fault, probably for non-canonical address 0xdffffc0000000009: 0000 [#1] SMP KASAN PTI
KASAN: null-ptr-deref in range [0x0000000000000048-0x000000000000004f]
CPU: 0 UID: 0 PID: 9518 Comm: syz.0.1014 Tainted: G             L      syzkaller #0 PREEMPT(full) 
Tainted: [L]=SOFTLOCKUP
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 11/06/2026
RIP: 0010:__d_obtain_alias+0x50/0x880 fs/dcache.c:2359
Code: e8 25 e6 64 ff eb 53 e8 1e e6 64 ff 49 c7 c5 8c ff ff ff eb 45 49 bf 00 00 00 00 00 fc ff df 49 8d 7d 28 48 89 f8 48 c1 e8 03 <42> 80 3c 38 00 74 05 e8 14 da e4 ff 4d 8b 75 28 4c 89 ef e8 a8 a8
RSP: 0018:ffffc90004d179d0 EFLAGS: 00010206
RAX: 0000000000000009 RBX: 0000000000000002 RCX: 0000000000080000
RDX: ffffc90005281000 RSI: 00000000000021d2 RDI: 0000000000000048
RBP: 0000000000000001 R08: ffffffff91bec8bf R09: 1ffffffff237d917
R10: dffffc0000000000 R11: fffffbfff237d918 R12: ffff88804d1bc000
R13: 0000000000000020 R14: 1ffff920009a2f4c R15: dffffc0000000000
FS:  00007f41d9fd26c0(0000) GS:ffff8881233cd000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000001b34d1aff8 CR3: 0000000033c92000 CR4: 00000000003526f0
Call Trace:
 <TASK>
 squashfs_export_iget+0x1d8/0x370 fs/squashfs/export.c:117
 exportfs_decode_fh_raw+0x1c8/0x770 fs/exportfs/expfs.c:460
 do_handle_to_path+0xa4/0x1a0 fs/fhandle.c:274
 handle_to_path fs/fhandle.c:423 [inline]
 do_handle_open+0x6dd/0xb10 fs/fhandle.c:447
 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:0x00007f41d91a94d9
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 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 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007f41d9fd2028 EFLAGS: 00000246 ORIG_RAX: 0000000000000130
RAX: ffffffffffffffda RBX: 00007f41d9425fa0 RCX: 00007f41d91a94d9
RDX: 0000000000000000 RSI: 0000200000000340 RDI: 0000000000000004
RBP: 00007f41d923804e R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007f41d9426038 R14: 00007f41d9425fa0 R15: 00007ffde0c3d718
 </TASK>
Modules linked in:
---[ end trace 0000000000000000 ]---
RIP: 0010:__d_obtain_alias+0x50/0x880 fs/dcache.c:2359
Code: e8 25 e6 64 ff eb 53 e8 1e e6 64 ff 49 c7 c5 8c ff ff ff eb 45 49 bf 00 00 00 00 00 fc ff df 49 8d 7d 28 48 89 f8 48 c1 e8 03 <42> 80 3c 38 00 74 05 e8 14 da e4 ff 4d 8b 75 28 4c 89 ef e8 a8 a8
RSP: 0018:ffffc90004d179d0 EFLAGS: 00010206
RAX: 0000000000000009 RBX: 0000000000000002 RCX: 0000000000080000
RDX: ffffc90005281000 RSI: 00000000000021d2 RDI: 0000000000000048
RBP: 0000000000000001 R08: ffffffff91bec8bf R09: 1ffffffff237d917
R10: dffffc0000000000 R11: fffffbfff237d918 R12: ffff88804d1bc000
R13: 0000000000000020 R14: 1ffff920009a2f4c R15: dffffc0000000000
FS:  00007f41d9fd26c0(0000) GS:ffff8881233cd000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000001b34d1aff8 CR3: 0000000033c92000 CR4: 00000000003526f0
----------------
Code disassembly (best guess):
   0:	e8 25 e6 64 ff       	call   0xff64e62a
   5:	eb 53                	jmp    0x5a
   7:	e8 1e e6 64 ff       	call   0xff64e62a
   c:	49 c7 c5 8c ff ff ff 	mov    $0xffffffffffffff8c,%r13
  13:	eb 45                	jmp    0x5a
  15:	49 bf 00 00 00 00 00 	movabs $0xdffffc0000000000,%r15
  1c:	fc ff df
  1f:	49 8d 7d 28          	lea    0x28(%r13),%rdi
  23:	48 89 f8             	mov    %rdi,%rax
  26:	48 c1 e8 03          	shr    $0x3,%rax
* 2a:	42 80 3c 38 00       	cmpb   $0x0,(%rax,%r15,1) <-- trapping instruction
  2f:	74 05                	je     0x36
  31:	e8 14 da e4 ff       	call   0xffe4da4a
  36:	4d 8b 75 28          	mov    0x28(%r13),%r14
  3a:	4c 89 ef             	mov    %r13,%rdi
  3d:	e8                   	.byte 0xe8
  3e:	a8 a8                	test   $0xa8,%al

Crashes (2):
Time Kernel Commit Syzkaller Config Log Report Syz repro C repro VM info Assets (help?) Manager Title
2026/11/14 01:12 linux-next 84abb955b101 be5d1971 .config console log report info [disk image] [vmlinux] [kernel image] ci-upstream-linux-next-kasan-gce-root general protection fault in __d_obtain_alias
2026/11/14 01:12 linux-next 84abb955b101 be5d1971 .config console log report info [disk image] [vmlinux] [kernel image] ci-upstream-linux-next-kasan-gce-root general protection fault in __d_obtain_alias
* Struck through repros no longer work on HEAD.