syzbot
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
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;
...
- 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).
- 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.
- 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.
- 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.
- 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).
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
* Struck through repros no longer work on HEAD.