Technical Root Cause Analysis
1. Failure Manifestation and Crash Site
The crash occurs at fs/namei.c:4295 in may_open():
static int may_open(struct mnt_idmap *idmap, const struct path *path,
int acc_mode, int flag)
{
struct dentry *dentry = path->dentry;
struct inode *inode = dentry->d_inode;
int error;
if (!inode)
return -ENOENT;
switch (inode->i_mode & S_IFMT) {
case S_IFLNK:
return -ELOOP;
case S_IFDIR:
...
case S_IFBLK:
case S_IFCHR:
...
case S_IFIFO:
case S_IFSOCK:
...
case S_IFREG:
if ((acc_mode & MAY_EXEC) && path_noexec(path))
return -EACCES;
break;
default:
VFS_BUG_ON_INODE(!IS_ANON_FILE(inode), inode);
}
During an openat syscall (do_sys_openat2 -> path_openat -> do_open -> may_open) executed by udevd with flag = 0x20141 (O_WRONLY | O_CREAT | O_TRUNC | O_LARGEFILE) and acc_mode = MAY_WRITE (0x2), may_open() evaluates inode->i_mode & S_IFMT.
Because inode->i_mode is 0, the switch falls into the default: branch. It then checks !IS_ANON_FILE(inode), which evaluates !((inode)->i_flags & S_ANON_INODE). Because inode->i_flags is 0, this assertion fails and triggers VFS_BUG_ON_INODE:
VFS_BUG_ON_INODE(!IS_ANON_FILE(inode)): inode:ffff0000c7782b80 fs:<unknown, sb unreadable> mode:0 opflags:0x0 flags:0x0 state:0x0 count:0
kernel BUG at fs/namei.c:4295!
2. Examination of Inode Invariants and Memory State
As printed by dump_inode() (fs/inode.c:3082), all memory fields of struct inode at address ffff0000c7782b80 probed via get_kernel_nofault() returned zero:
inode->i_sb = NULL (fs:<unknown, sb unreadable>)
inode->i_mode = 0
inode->i_opflags = 0x0
inode->i_flags = 0x0
inode->i_state = 0x0
inode->i_count = 0
An inode cannot be created or instantiated in this state through standard VFS lifecycle paths:
- Inode Allocation Invariant (
fs/inode.c:inode_init_always_gfp()): Every allocated inode initialized through inode_init_always_gfp() assigns inode->i_sb = sb (which is never NULL) and atomic_set(&inode->i_count, 1). Inodes that fail allocation are destroyed before ever becoming visible.
- Dentry Instantiation Invariant (
fs/dcache.c:d_flags_for_inode()): Any assignment of dentry->d_inode = inode occurs in __d_set_inode_and_type(), which is called by __d_instantiate(), __d_obtain_alias(), and __d_add(). These functions compute dentry flags using d_flags_for_inode(inode). In d_flags_for_inode():
if (unlikely(!(inode->i_opflags & IOP_NOFOLLOW))) {
if (unlikely(inode->i_op->get_link)) {
add_flags = DCACHE_SYMLINK_TYPE;
goto type_determined;
}
inode->i_opflags |= IOP_NOFOLLOW;
}
If an inode were attached with all-zero fields (i_opflags == 0 and i_op == NULL), d_flags_for_inode() would have dereferenced inode->i_op->get_link and crashed immediately with a NULL-pointer dereference at instantiation time, and would have modified inode->i_opflags.
Therefore, the inode was valid when originally attached to the dentry, but the pointer dentry->d_inode subsequently became stale.
3. Path Resolution and Propagation to may_open()
In open_last_lookups() -> lookup_open() (fs/namei.c):
- Lookup in the dcache (
lookup_fast_for_open() or d_lookup()) encountered a cached positive dentry whose dentry->d_inode pointed to ffff0000c7782b80.
- Because
dentry->d_inode was non-NULL, lookup_open() treated the dentry as an existing cached file and skipped creation, leaving file->f_mode without FMODE_CREATED.
- In
do_open():
- Dentry type checks such as
d_is_dir(nd->path.dentry) and d_is_reg(nd->path.dentry) inspect cached DCACHE_ENTRY_TYPE bits in dentry->d_flags rather than dereferencing dentry->d_inode.
may_create_in_sticky() returns early if the parent directory does not have S_ISVTX.
- Consequently,
may_open() was the first function in do_open() to dereference dentry->d_inode->i_mode.
4. Root Cause Mechanism
The root cause is a use-after-free (dangling pointer in dentry->d_inode):
- An inode reference counting or unlinking discrepancy prematurely freed the
struct inode object while an active or cached struct dentry still held a pointer to it in dentry->d_inode.
- Under the kernel build configuration (
CONFIG_INIT_ON_ALLOC_DEFAULT_ON=y), when the slab memory at ffff0000c7782b80 was returned to the allocator or reallocated for a new object, the memory block was zeroed.
- When
udevd attempted to open the path, the cached dentry provided the zeroed memory address as dentry->d_inode. In may_open(), reading inode->i_mode == 0 triggered the VFS_BUG_ON_INODE(!IS_ANON_FILE(inode), inode) assertion.