syzbot
general protection fault in security_d_instantiate
Status:
upstream: reported C repro on 2026/11/14 14:11
Subsystems:
lsm
Labels:
prio:normal
[Documentation on labels]
Reported-by: syzbot+d8a69701a6e796ad5d3e@syzkaller.appspotmail.com
First crash: 15h13m, last: 43m
✨ AI-generated root cause summary:
In squashfs_read_inode(), validation checks for directory types SQUASHFS_DIR_TYPE and SQUASHFS_LDIR_TYPE jump to failed_read when inode->i_size < 3 without initializing err to a negative errno (-EINVAL). Consequently, err retains the positive byte count returned by the preceding squashfs_read_metadata() call (sizeof(struct squashfs_dir_inode) == 32 == 0x20, or 40 == 0x28 for LDIR). When squashfs_iget() receives this positive return value, it invokes ERR_PTR(err), yielding an invalid non-negative pointer (e.g. (void *)0x20) that bypasses IS_ERR() and NULL checks in d_splice_alias_ops(). Passing this bogus inode pointer to security_d_instantiate() causes a NULL-pointer dereference when reading inode->i_flags at offset 0x24.
▶
▼
✨ Full AI root cause analysis
Root Cause Analysis
Involved Components
- Source Files:
fs/squashfs/inode.c, fs/squashfs/namei.c, fs/dcache.c, security/security.c
- Functions:
squashfs_read_inode(), squashfs_iget(), squashfs_lookup(), d_splice_alias(), d_splice_alias_ops(), security_d_instantiate()
- Structures and Fields:
struct squashfs_dir_inode, struct squashfs_ldir_inode, struct inode::i_size, struct inode::i_flags
Mechanism and Failure Chain
-
Missing Error Code Assignment in squashfs_read_inode():
In fs/squashfs/inode.c, squashfs_read_inode() reads inode metadata from disk using squashfs_read_metadata():
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;
squashfs_read_metadata() returns the number of bytes read on success. For SQUASHFS_DIR_TYPE, sizeof(struct squashfs_dir_inode) is 32 (0x20) (and 40 / 0x28 for SQUASHFS_LDIR_TYPE).
When inode->i_size < 3, execution jumps to failed_read without assigning err = -EINVAL; (unlike the other failure paths in squashfs_read_inode(), such as corrupted regular files or symlinks).
Consequently, failed_read: returns the positive value err = 32 (0x20).
-
Violation of ERR_PTR Invariant in squashfs_iget():
In squashfs_iget() (fs/squashfs/inode.c:93-97):
err = squashfs_read_inode(inode, ino);
if (err) {
iget_failed(inode);
return ERR_PTR(err);
}
The kernel convention and contract for ERR_PTR() requires a negative error code (e.g., -EINVAL / -22). Passing a positive integer 32 produces (struct inode *)(long)32, i.e., (struct inode *)0x0000000000000020.
-
Bypassing Error and NULL Validation in d_splice_alias_ops():
In squashfs_lookup() (fs/squashfs/namei.c:265):
inode = squashfs_iget(dir->i_sb, ino, ino_num);
goto exit_lookup;
...
exit_lookup:
kfree(dire);
return d_splice_alias(inode, dentry);
squashfs_lookup() passes inode = (void *)0x20 to d_splice_alias(), which calls d_splice_alias_ops() (fs/dcache.c:3447):
struct dentry *d_splice_alias_ops(struct inode *inode, struct dentry *dentry,
const struct dentry_operations *ops)
{
if (IS_ERR(inode))
return ERR_CAST(inode);
BUG_ON(d_really_is_positive(dentry));
if (!inode)
goto out;
security_d_instantiate(dentry, inode);
IS_ERR() tests whether the pointer value falls within the range [(unsigned long)-MAX_ERRNO, (unsigned long)-1]. Since 0x20 is neither within that range nor NULL, both checks evaluate to false. d_splice_alias_ops() treats 0x20 as a legitimate inode pointer.
-
Kernel Oops in security_d_instantiate():
security_d_instantiate() (security/security.c:3781) executes:
void security_d_instantiate(struct dentry *dentry, struct inode *inode)
{
if (unlikely(inode && IS_PRIVATE(inode)))
return;
IS_PRIVATE(inode) evaluates (inode)->i_flags & S_PRIVATE. In struct inode, i_flags is located at byte offset 4 (bits 32..63).
Accessing inode->i_flags attempts to read address 0x20 + 0x4 = 0x24. Under KASAN, shadow address calculation accesses (0x24 >> 3) + 0xdffffc0000000000 = 0xdffffc0000000004, generating a General Protection Fault and a null-ptr-deref in range [0x0000000000000020-0x0000000000000027]. In production kernels without KASAN, dereferencing 0x24 within unmapped page 0 generates a NULL-pointer dereference page fault oops/panic.
Sample crash report:
loop0: detected capacity change from 0 to 8
SQUASHFS error: Unable to read inode 0x87
Oops: general protection fault, probably for non-canonical address 0xdffffc0000000004: 0000 [#1] SMP KASAN PTI
KASAN: null-ptr-deref in range [0x0000000000000020-0x0000000000000027]
CPU: 1 UID: 0 PID: 5647 Comm: syz-executor337 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 11/06/2026
RIP: 0010:security_d_instantiate+0x2f/0x1d0 security/security.c:3781
Code: 41 57 41 56 53 48 89 f3 49 89 fe e8 bb 2c 20 fd 48 85 db 74 42 4c 8d 7b 04 4c 89 f8 48 c1 e8 03 48 b9 00 00 00 00 00 fc ff df <0f> b6 04 08 84 c0 0f 85 9d 00 00 00 41 8b 2f 89 ee 81 e6 00 02 00
RSP: 0018:ffffc90003b1f758 EFLAGS: 00010203
RAX: 0000000000000004 RBX: 0000000000000020 RCX: dffffc0000000000
RDX: 0000000000000000 RSI: 0000000000000020 RDI: ffff888077497d40
RBP: ffffc90003b1f9b0 R08: ffffffff91bec8bf R09: 1ffffffff237d917
R10: dffffc0000000000 R11: fffffbfff237d918 R12: 0000000000000000
R13: dffffc0000000000 R14: ffff888077497d40 R15: 0000000000000024
FS: 0000555576a8a400(0000) GS:ffff8881234cd000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00002000000000c0 CR3: 0000000034bb2000 CR4: 00000000003526f0
Call Trace:
<TASK>
d_splice_alias_ops+0x72/0x440 fs/dcache.c:3458
squashfs_lookup+0xc5e/0x14e0 fs/squashfs/namei.c:273
finish_lookup fs/namei.c:1934 [inline]
lookup_open+0x816/0x16e0 fs/namei.c:4644
open_last_lookups fs/namei.c:4880 [inline]
path_openat+0x7a5/0x20e0 fs/namei.c:5119
do_file_open+0x23e/0x4a0 fs/namei.c:5151
do_sys_openat2+0x115/0x200 fs/open.c:1492
do_sys_open fs/open.c:1498 [inline]
__do_sys_openat fs/open.c:1838 [inline]
__se_sys_openat fs/open.c:1833 [inline]
__x64_sys_openat+0x138/0x170 fs/open.c:1833
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:0x00007f101b51a909
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:00007ffd8afc50d8 EFLAGS: 00000246 ORIG_RAX: 0000000000000101
RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007f101b51a909
RDX: 0000000000040042 RSI: 00002000000000c0 RDI: 00000000ffffff9c
RBP: 0000000000000001 R08: 00000000ec776000 R09: 00000000ec776000
R10: 0000000000000001 R11: 0000000000000246 R12: 00007ffd8afc5110
R13: 0000000000000002 R14: 431bde82d7b634db R15: 00007f101b57a04b
</TASK>
Modules linked in:
---[ end trace 0000000000000000 ]---
RIP: 0010:security_d_instantiate+0x2f/0x1d0 security/security.c:3781
Code: 41 57 41 56 53 48 89 f3 49 89 fe e8 bb 2c 20 fd 48 85 db 74 42 4c 8d 7b 04 4c 89 f8 48 c1 e8 03 48 b9 00 00 00 00 00 fc ff df <0f> b6 04 08 84 c0 0f 85 9d 00 00 00 41 8b 2f 89 ee 81 e6 00 02 00
RSP: 0018:ffffc90003b1f758 EFLAGS: 00010203
RAX: 0000000000000004 RBX: 0000000000000020 RCX: dffffc0000000000
RDX: 0000000000000000 RSI: 0000000000000020 RDI: ffff888077497d40
RBP: ffffc90003b1f9b0 R08: ffffffff91bec8bf R09: 1ffffffff237d917
R10: dffffc0000000000 R11: fffffbfff237d918 R12: 0000000000000000
R13: dffffc0000000000 R14: ffff888077497d40 R15: 0000000000000024
FS: 0000555576a8a400(0000) GS:ffff8881234cd000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00002000000000c0 CR3: 0000000034bb2000 CR4: 00000000003526f0
----------------
Code disassembly (best guess):
0: 41 57 push %r15
2: 41 56 push %r14
4: 53 push %rbx
5: 48 89 f3 mov %rsi,%rbx
8: 49 89 fe mov %rdi,%r14
b: e8 bb 2c 20 fd call 0xfd202ccb
10: 48 85 db test %rbx,%rbx
13: 74 42 je 0x57
15: 4c 8d 7b 04 lea 0x4(%rbx),%r15
19: 4c 89 f8 mov %r15,%rax
1c: 48 c1 e8 03 shr $0x3,%rax
20: 48 b9 00 00 00 00 00 movabs $0xdffffc0000000000,%rcx
27: fc ff df
* 2a: 0f b6 04 08 movzbl (%rax,%rcx,1),%eax <-- trapping instruction
2e: 84 c0 test %al,%al
30: 0f 85 9d 00 00 00 jne 0xd3
36: 41 8b 2f mov (%r15),%ebp
39: 89 ee mov %ebp,%esi
3b: 81 .byte 0x81
3c: e6 00 out %al,$0x0
3e: 02 00 add (%rax),%al
* Struck through repros no longer work on HEAD.