Root Cause Technical Analysis
1. Overview and Key Entities
The reported defect is a lockdep circular locking dependency warning involving three distinct locking layers:
sb_writers (specifically SB_FREEZE_WRITE, acquired via sb_start_write() / mnt_want_write() / kiocb_start_write()) on the local filesystem hosting the cachefiles cache (fs/cachefiles/io.c, fs/namei.c, include/linux/fs.h).
iint->mutex (struct ima_iint_cache::mutex, annotated with lockdep key &ima_iint_mutex_key[depth]) in the Integrity Measurement Architecture (IMA) subsystem (security/integrity/ima/ima_iint.c, security/integrity/ima/ima_main.c).
inode->i_rwsem (&sb->s_type->i_mutex_key#30) on a network filesystem inode (v9fs / fs/9p/vfs_file.c, fs/netfs/locking.c, fs/open.c).
2. Detailed Locking Chain Breakdown
Chain #1: sb_writers -> &ima_iint_mutex_key[depth]
In fs/namei.c:do_open():
} else if (d_is_reg(nd->path.dentry) && open_flag & O_TRUNC) {
error = mnt_want_write(nd->path.mnt);
if (error)
return error;
mnt = mntget(nd->path.mnt);
do_truncate = true;
}
error = may_open(idmap, &nd->path, acc_mode, open_flag);
if (!error && !(file->f_mode & FMODE_OPENED))
error = vfs_open_consume(&nd->path, file);
if (!error)
error = security_file_post_open(file, op->acc_mode);
When opening an existing file with O_TRUNC on the local filesystem (where cachefiles caches data, superblock #5), do_open() calls mnt_want_write(), which acquires sb_start_write(sb) (sb_writers#5). While holding this write freeze protection lock, do_open() invokes security_file_post_open(), which calls ima_file_check() -> process_measurement(), acquiring iint->mutex for that local inode. Lockdep records the dependency:
$$\text{sb_writers#5} \longrightarrow \text{&ima_iint_mutex_key[depth]}$$
Chain #2: &ima_iint_mutex_key[depth] -> &sb->s_type->i_mutex_key#30
When a file is opened on v9fs (9p network filesystem, superblock #15), do_open() calls security_file_post_open() -> ima_file_check() -> process_measurement(), which locks iint->mutex for the 9p inode. To measure the file content:
process_measurement() $\to$ ima_collect_measurement() $\to$ ima_calc_file_hash() $\to$ __kernel_read() $\to$ v9fs_file_read_iter() $\to$ netfs_file_read_iter() $\to$ netfs_buffered_read_iter() $\to$ netfs_start_io_read().
netfs_start_io_read() acquires inode_lock_shared(inode) (&sb->s_type->i_mutex_key#30) on the 9p inode. Lockdep records the dependency:
$$\text{&ima_iint_mutex_key[depth]} \longrightarrow \text{&sb->s_type->i_mutex_key#30}$$
Chain #0: &sb->s_type->i_mutex_key#30 -> sb_writers#5
When a 9p file is opened with O_TRUNC, do_open() calls handle_truncate() $\to$ do_truncate(). do_truncate() locks the 9p inode exclusively via inode_lock_killable() (&sb->s_type->i_mutex_key#30) and calls notify_change() $\to$ v9fs_vfs_setattr().
In v9fs_vfs_setattr() (fs/9p/vfs_inode.c:1087):
if (d_is_reg(dentry)) {
retval = filemap_fdatawrite(inode->i_mapping);
...
Flushing dirty page cache before truncation triggers:
filemap_fdatawrite() $\to$ filemap_writeback() $\to$ do_writepages() $\to$ netfs_writepages() $\to$ netfs_write_folio() $\to$ netfs_advance_write() $\to$ netfs_do_issue_write() $\to$ cachefiles_issue_write() $\to$ cachefiles_write() $\to$ __cachefiles_write().
In fs/cachefiles/io.c:355:
ret = vfs_iocb_iter_write(file, &ki->iocb, iter);
vfs_iocb_iter_write() (fs/read_write.c:929) calls kiocb_start_write(iocb), which calls sb_start_write(file_inode(file)->i_sb) on the local backing filesystem (sb_writers#5) to ensure freeze protection while writing to the cache file. Lockdep observes:
$$\text{&sb->s_type->i_mutex_key#30} \longrightarrow \text{sb_writers#5}$$
This completes the apparent circular dependency:
$$\text{sb_writers#5} \longrightarrow \text{&ima_iint_mutex_key[depth]} \longrightarrow \text{&sb->s_type->i_mutex_key#30} \longrightarrow \text{sb_writers#5}$$
3. Root Cause: Lockdep Class Conflation and Stack Depth Annotations
In security/integrity/ima/ima_iint.c:
static inline void ima_iint_lockdep_annotate(struct ima_iint_cache *iint,
struct inode *inode)
{
#ifdef CONFIG_LOCKDEP
static struct lock_class_key ima_iint_mutex_key[IMA_MAX_NESTING];
int depth = inode->i_sb->s_stack_depth;
if (WARN_ON_ONCE(depth < 0 || depth >= IMA_MAX_NESTING))
depth = 0;
lockdep_set_class(&iint->mutex, &ima_iint_mutex_key[depth]);
#endif
}
- Inode stacking depth (
sb->s_stack_depth) was implemented to distinguish nesting levels across stacked filesystems (such as overlayfs and ecryptfs), preventing false-positive lockdep reports when IMA measures files on both the upper and lower filesystems.
- Neither
v9fs (nor other network filesystems using netfs / fscache) nor standard local backing filesystems (e.g., ext4, xfs, tmpfs) increment s_stack_depth. Consequently, both superblocks have s_stack_depth == 0.
- Lockdep therefore assigns
&ima_iint_mutex_key[0] to both the local cache file's iint->mutex and the 9p file's iint->mutex.
- While
do_open() holds mnt_want_write() across security_file_post_open(), the local file's iint->mutex and the 9p file's iint->mutex are completely distinct mutex instances residing in separate struct ima_iint_cache structures. The local filesystem never invokes operations on 9p, so no actual runtime cycle exists between lock instances.