From nobody Fri Oct 2 09:17:30 2026 Received: from mail-pg1-f181.google.com (mail-pg1-f181.google.com [209.85.215.181]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 132C3374756 for ; Mon, 3 Aug 2026 03:27:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.181 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785727663; cv=none; b=j/hmwlviktpnpgiAIu6sW37kyLfRhvXF4pAaa9HX6CgYH2qUd1gq4tsoy62QpV9esnGekJ7vyf++gSqgG0C4v6rYe9ynQ2forKT1Ic7wZ484m7pmMDcRTreQ6aDbzCN/kUSt8bSw0ITgS+x0oID8tczT+93luibe/KQoPVv4rVc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785727663; c=relaxed/simple; bh=Jsn2r1uveXbUFYaD7Tectcn3uRJrKN6MuVwv/rRQ9e4=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=W3JLw4kWb2ENpkF1jDPsYKBqveITAOCbwOUuwY9Dm2p+KXfzi+JlasAI9sFwp6ZBJUtnMPv7pXtwIw0v+B+eHE1jyMTjwzuF24e4+UWxbCwz8HJgg+KgtdNb2B+VRpII8spTJT8t+wSzD4LQJ0dY5jTqT9qwzgUeUEv+5OPNwL8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=W4HDi/EV; arc=none smtp.client-ip=209.85.215.181 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="W4HDi/EV" Received: by mail-pg1-f181.google.com with SMTP id 41be03b00d2f7-c981c2c37cbso2163784a12.0 for ; Sun, 02 Aug 2026 20:27:41 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785727661; x=1786332461; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to:content-type; bh=oYoOGjZWCQI6jE6FLVWCCG9hsc76kxEv4IGNCI27rDA=; b=W4HDi/EViFVwgJqt+YeTU6JoZ3MyI9Zg5Real9ezzDYLdwKBoetvzgCnNxZSTPGwXd trsQKNNU0z3WpCHuZ6oPAc6raUvHEHlvPBGW+vyH51lF6wBYFsip7a8gYa93nMHREie/ 5RMRBKU19v1kEK+kLuvSRPae4eOKl5m3y7LX9E1VVq2fElnfH+AoGibH5XpjwkQ2OtmF QiqyAA8DhzJKb06VDIZPQxswpsmBISQOiTjJVt9QtSbwcXKqR/nGSwVNOwxU/kzrBrVO tginxrlKYnJziOPPcEdzNv1HF7Dh19fOaTQTR+AnmAWvNhijR4Q2XkC0yQx6NPV3VMN5 NxZQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785727661; x=1786332461; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=oYoOGjZWCQI6jE6FLVWCCG9hsc76kxEv4IGNCI27rDA=; b=Ad8nqEYu0UpplPrvjVpK0JQ8hdBhGSl+3hsMzYGgTnLQXpPfxIvZszCag5xojsshxi tS00h4ZwQEHu46fwWN/5jxdsU3ZggP03HkTyEx42pPxnRbQByw+Z8J1HE96ctmDQxwva LvjaKB0QdIyNFP6gaJ5EtTJfwW4wwWs5jRdB6t/yZHMSFoMx9VDgAxUcQh+Vj2zCCR+m YP7Rax1lri4T1RSovds7B2DptUr1U8007eYjgGoXSMEkGnFpVN2YTgQNBVZ5macl8swH rRAhn2QOQ/EhKbOrpg/D49EVP9nTZ7hqOb66LbukCXQpepzGLXEOFP08soX13ixl+XhK I5qg== X-Forwarded-Encrypted: i=1; AHgh+RocOdrd1xUPZrCoEC/OfTIfn3qhZn8rdRmfFXf+tl5j6oaIia2npfBFkajG9YCu6t9W/k5a9XbU80l1ZYg=@vger.kernel.org X-Gm-Message-State: AOJu0YxXIkb0G70OtglIoA78MkxG3jrCjKXTYgxTPvElbMviaSSedfb/ itH5n3Hd0jLkFTEF8Qxec2/JQs9T4894j7rSIoCjLVsmY3BZ9rEU9eyE X-Gm-Gg: AR+sD12YuFNN7bn4RVdnHVuDXGvJWRu4Zuekmd8jpQLL934gIB2y+jUizbquoGSw/yw ItJN7FJGyHvTXyHEh5d+RU+s7+1znZVD2wNVhiPWGNaVxjfJIl1ncD6zd0xqGCOLl7mZfzOlwET ewFAKE5VoeAOjseBSiXBp+nrHY+HsmdK9IHV+n/7ai5sim9SnJTtNGsRQge56A80O7m5plceWKX KePc6+FfVLXRrd66PY4kNEcHxIWy9/xIXzhZTs4ULZ6QUjVS9YLiBeZjGLJm9bGO+rBYRcSJUoX nzDzR7650cnRAj426IDx01N+nWKQYV77+QPed7f4L+FBAueQ1WmIuxK8INNYxuKEou7DLxPw1wA WFtqGKxY+xLbGRqOLXi6iEaGpd1pZ4WxMTYYLStjA55C6fd7TQsoPqqHaMunaaxt2i7c9/PONQ4 5TvorwnvgzRxKUY5uQCdaHHsfUpVIXmRDTnOtUBzNLnXM5Rdn2v9/SMntF1Tzf35T19b4td+DzX L+NwIFXvTSIvQ== X-Received: by 2002:a05:6a20:7348:b0:3bf:9142:ba3a with SMTP id adf61e73a8af0-3c92a58e834mr8230483637.26.1785727661332; Sun, 02 Aug 2026 20:27:41 -0700 (PDT) Received: from localhost.localdomain ([113.23.46.216]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-3153e18d9c6sm33203673eec.30.2026.08.02.20.27.38 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Sun, 02 Aug 2026 20:27:40 -0700 (PDT) From: Yuejie Shi To: Phillip Lougher Cc: squashfs-devel@lists.sourceforge.net, linux-kernel@vger.kernel.org, stable@vger.kernel.org Subject: [PATCH] squashfs: bound the fragment offset stored in an inode Date: Mon, 3 Aug 2026 11:27:35 +0800 Message-ID: <20260803032735.81785-1-syjcnss@gmail.com> X-Mailer: git-send-email 2.50.1 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Both the REG and LREG inode readers take the fragment offset verbatim off disk: frag =3D le32_to_cpu(sqsh_ino->fragment); if (frag !=3D SQUASHFS_INVALID_FRAG) { /* * the file cannot have a fragment (tailend) and have a * file size a multiple of the block size */ if ((inode->i_size & (msblk->block_size - 1)) =3D=3D 0) { ... } frag_offset =3D le32_to_cpu(sqsh_ino->offset); ... } ... squashfs_i(inode)->fragment_offset =3D frag_offset; The check added by commit 9ee94bfbe930 ("Squashfs: add additional inode sanity checking") is about i_size, not about offset, and nothing else looks at offset at all. It reaches squashfs_copy_data() unchanged, for instance from squashfs_readahead_fragment(): bytes =3D squashfs_copy_data(addr, buffer, offset + squashfs_i(inode)->fragment_offset, avail); where the parameter is a signed int: while (offset < entry->length) { void *buff =3D entry->data[offset / PAGE_SIZE] + (offset % PAGE_SIZE); An on-disk offset of 0x80000000 arrives as -2147483648, the loop guard passes, and entry->data[-524288] is loaded from about 4 MiB below the cache entry's page pointer array. The tailend of the file has to lie inside the uncompressed fragment block, and that block is at most msblk->block_size bytes, so check that when the inode is read -- the offset is on-disk metadata and belongs validated where it is parsed, not where it eventually faults. Mounting a crafted image requires CAP_SYS_ADMIN in the initial user namespace -- SquashFS is not FS_USERNS_MOUNT, so an unprivileged user in a user namespace cannot mount one either. The realistic threat model is untrusted images: automounted removable media and udisks2, .snap and AppImage style container images, mount -o loop from a setuid helper, and any service that mounts a squashfs it did not build. Note the trigger is unprivileged and a read-only mount is enough: a single read() of the crafted file by any user who can open it. # mksquashfs -noI -noD -noF -noX -b 4096 with one 1-byte file, then # rewrite that inode's fragment offset to 0x80000000 mount -t squashfs -o ro /dev/loop0 /mnt cat /mnt/file BUG: KASAN: wild-memory-access in squashfs_copy_data+0xb8/0x158 Read of size 8 at addr 007f0000c7b1a0a0 by task cat/132 __asan_load8+0x84/0xd0 squashfs_copy_data+0xb8/0x158 squashfs_readahead+0x9b8/0xe60 read_pages+0x134/0x550 page_cache_ra_unbounded+0x264/0x540 filemap_get_pages+0x1d0/0xaa0 filemap_splice_read+0x248/0x548 do_sendfile+0x540/0x618 __arm64_sys_sendfile64+0x1f8/0x220 el0t_64_sync+0x198/0x1a0 Unable to handle kernel paging request at virtual address 007f0000c7b1a0a0 ... Kernel panic - not syncing: Oops: Fatal exception The KASAN report is immediately followed by a fatal page fault at the same address, so this is not a sanitizer-only artefact: the same load oopses on a kernel built without KASAN. (The trace goes through sendfile(2) because busybox cat uses it; the plain read(2) path reaches the same squashfs_copy_data() through squashfs_readpage_fragment().) mksquashfs packs tailends inside a single fragment block, so every image it produces satisfies the new check. Fixes: 6545b246a2c8 ("Squashfs: inode operations") Cc: stable@vger.kernel.org Signed-off-by: Yuejie Shi --- fs/squashfs/inode.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/fs/squashfs/inode.c b/fs/squashfs/inode.c index 82b687414e65..4872b28f1b77 100644 --- a/fs/squashfs/inode.c +++ b/fs/squashfs/inode.c @@ -156,6 +156,16 @@ int squashfs_read_inode(struct inode *inode, long long goto failed_read; } frag_offset =3D le32_to_cpu(sqsh_ino->offset); + /* + * the tailend has to lie within the uncompressed + * fragment block, which is at most block_size bytes + */ + if (frag_offset + + (inode->i_size & (msblk->block_size - 1)) > + msblk->block_size) { + err =3D -EINVAL; + goto failed_read; + } frag_size =3D squashfs_frag_lookup(sb, frag, &frag_blk); if (frag_size < 0) { err =3D frag_size; @@ -212,6 +222,16 @@ int squashfs_read_inode(struct inode *inode, long long goto failed_read; } frag_offset =3D le32_to_cpu(sqsh_ino->offset); + /* + * the tailend has to lie within the uncompressed + * fragment block, which is at most block_size bytes + */ + if (frag_offset + + (inode->i_size & (msblk->block_size - 1)) > + msblk->block_size) { + err =3D -EINVAL; + goto failed_read; + } frag_size =3D squashfs_frag_lookup(sb, frag, &frag_blk); if (frag_size < 0) { err =3D frag_size; -- 2.51.0