Technical Analysis of Kernel BUG at fs/super.c:2174
When reading SquashFS inodes in squashfs_read_inode(), directory inodes of type SQUASHFS_DIR_TYPE and SQUASHFS_LDIR_TYPE are decoded as follows:
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;
...
case SQUASHFS_LDIR_TYPE: {
struct squashfs_ldir_inode *sqsh_ino = &squashfs_ino.ldir;
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 = le32_to_cpu(sqsh_ino->file_size);
if (inode->i_size < 3)
goto failed_read;
In squashfs_read_metadata() (fs/squashfs/cache.c:438-477), on success, the function returns the number of bytes read (res = length). Thus, err is assigned:
sizeof(struct squashfs_dir_inode) = 32 for SQUASHFS_DIR_TYPE
sizeof(struct squashfs_ldir_inode) = 40 for SQUASHFS_LDIR_TYPE
Immediately following squashfs_read_metadata(), SquashFS performs a directory sanity check: a directory must have a size of at least 3 bytes (accounting for . and .. entries). When inode->i_size < 3, execution jumps to failed_read: (fs/squashfs/inode.c:462):
failed_read:
ERROR("Unable to read inode 0x%llx\n", ino);
return err;
Crucially, neither case SQUASHFS_DIR_TYPE nor case SQUASHFS_LDIR_TYPE sets err = -EINVAL or any other negative error code before jumping to failed_read:. Consequently, squashfs_read_inode() logs:
SQUASHFS error: Unable to read inode 0x11f
and returns the positive value 40 (or 32).
2. Mount Failure and VFS Invariant Violation
During filesystem mounting:
squashfs_fill_super() (fs/squashfs/super.c:493) calls err = squashfs_read_inode(root, root_inode); to initialize the root inode.
- Because
err is positive 40 (non-zero), if (err) evaluates to true. squashfs_fill_super() destroys the root inode, tears down allocated structures, leaves sb->s_root = NULL, and returns err = 40.
squashfs_get_tree() delegates to get_tree_bdev() / get_tree_bdev_flags() (fs/super.c:2104-2108). It sees error = fill_super(s, fc); is non-zero, deactivates the superblock, does not populate fc->root, and returns 40.
- Control returns to
vfs_get_tree() (fs/super.c:2153-2175):
error = fc->ops->get_tree(fc);
if (error < 0)
return error;
if (!fc->root) {
pr_err("Filesystem %s get_tree() didn't set fc->root, returned %i\n",
fc->fs_type->name, error);
/* We don't know what the locking state of the superblock is -
* if there is a superblock.
*/
BUG();
}
- Because
error is 40 (positive), the if (error < 0) error check does not trigger. Since fc->root is NULL, vfs_get_tree() concludes that the filesystem's ->get_tree() operation improperly succeeded without setting fc->root, logs "Filesystem squashfs get_tree() didn't set fc->root, returned 40", and calls BUG().