From nobody Mon Jun 8 05:25:29 2026 Received: from mail-qk1-f173.google.com (mail-qk1-f173.google.com [209.85.222.173]) (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 748FF3AC0CB for ; Fri, 5 Jun 2026 18:20:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.222.173 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780683660; cv=none; b=GUvXj4LGaF8Nub0dSo0Gbc7UB11Bc+X6AunUgs2TaSpld/5g2Mhzgo/xnVAo3ABGH9WY6HSApV389F8Oh00VxfTjiJI2DSF+x6apXWJOSkJI01gMeUgM1ws1Lyv9SX92MBATXkW7LmhVeUfF44dCyqFKeR+PNld7akspRYL/5Ks= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780683660; c=relaxed/simple; bh=Q35skCwSkwAtj11gSaw/W/fa7o+HLQlueLrqQ9D3cPM=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=KIpK/vdULtiSRIey5zDJJfPXpT806C3Ru+EJ/p8fCB+9syJPdymA0EWfZbVZtcmU0G3V1DgqecqzrdECizUgwrE3VDpHUrXNl0X3Jf2uN1d35Mtw+bemJhDBM5sjJ0Tbay2k8esq/JvnHaz2Lhl/IvoA9d5Vz10+I18zx0BiUvs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=trailofbits.com; spf=pass smtp.mailfrom=trailofbits.com; dkim=pass (2048-bit key) header.d=trailofbits.com header.i=@trailofbits.com header.b=QThWbBFt; arc=none smtp.client-ip=209.85.222.173 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=trailofbits.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=trailofbits.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=trailofbits.com header.i=@trailofbits.com header.b="QThWbBFt" Received: by mail-qk1-f173.google.com with SMTP id af79cd13be357-9156ceb55ffso195576185a.0 for ; Fri, 05 Jun 2026 11:20:59 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=trailofbits.com; s=google; t=1780683658; x=1781288458; 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; bh=1wicSgGOV1drkePXao9M1JE1iLvKbWtmSDrE0GrfVAA=; b=QThWbBFtEiYbY7ehBvYGnZigpYBk5vulKOqJRgXG6IaPN7MKfXUzOH7bE+VsPlRndC RfqzOAbb2yNRptQGXnOvebIei5B7YMeOld7gBmFPJjqWEEe9gfXL1nh/UZpR3MmEl5nL Ebl3XHUgI4TKl4Cu3irIQSsTeSDGEShWKWegapO0/qYgC7RdzcpK6AWRMKT5OQdRJjmx d+GEy88S2wHjPS8J0xan+gWuKswyDrSWn7qIUM6uWseZWhZEcCyKyp+H5qbU9NgeuRx0 rVJB5kpSfmpLbjd/aP14S+fNVN23/ySFQKkA2vyx+U+op8jl2lWcxPEvNNJhnYex+RD7 fpcg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780683658; x=1781288458; 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; bh=1wicSgGOV1drkePXao9M1JE1iLvKbWtmSDrE0GrfVAA=; b=PuV47PcUmRJbdsrYqboDYlAXtzg7hnbSxTNX0uO+rp97ilLw0eFb04BR/+ZqYIbqlN iROGGvyyDxms3yE2/YQ6sZXs10fNo1VSFNUKhxlUfJbysPlAom+r/l5GNBEhx/DqHVA9 fGxmJ6dKCBOqr+W74hBST8UHEZt8oiI+PVNZlXlJ6359UOZVAL+BWppMPOUuSODa5tSB D/YThZIdt/WEzQQwEAC7Tb5uHm0nnrBLg9jZeDx0H/S4rY4e33QoxHRFLW2h+ZhEwlWF aI7/uL53KUQy/dNATKzaspn88tHGOJXFC4qwOXj20YPgnn9EMUXzzN9gYEgRa01/Pc6P nJ4A== X-Forwarded-Encrypted: i=1; AFNElJ+47JcQU3EYZ1yIsVeiFJo0HvJRVqJoUEUnRSHKOWyBP5wbyRJm5H65xBpJo0FVwU+G6ZWMqiN99m9tjt4=@vger.kernel.org X-Gm-Message-State: AOJu0Yy/mJheea0rjJow+zsQQ2ltc+UCXkBGBd56dmK5R3BWX99rZkEm GjE595cEvg6BR/4O5lg5cw62KwvDIu7TLAbPWUp3vLkmgsheS7EdE7q5+jpNGGkmoa73df0nPzW /b1KH97Y= X-Gm-Gg: Acq92OHXD16HuFtN/dfUYZ6WQqb8GMfMKAlzF3DtBVI5dmtwpFTanpJKkrJ31wZRn5s WFVqgrNxUn4lxmnLCDoabd8lQqL6A5lU2V/g2Q7WQdepBIlMtsA5LS7JG9f/WvFO2RsTfCM/4hb c+NfdtNz+/oH4NW0JlSstSHPd6hXM+1ZCd11vQrUYB3n9o4T7FJ6YJi7las2ux36f7vQnOOvPSf g4i+etsCmFB8RDFImXjwcdfLjDul/vEWUmeQLN4sPh/9wihrISJxwFms5zYKL2h0C3PhrhBpP3N lK9EW55AF/scqGUzJM2wB0u6NOJO3RRqHsicCTtY50G73kVp99LiqjtSWPngtIcEZBsrHhL/BU3 iEN3Iwqed66J+QTU/OxYtYrGdYgdkGJK2uhrDatOZBoptdI3n6L1twgGCUCqn/lYSX+kaoXgOl3 SikTJD2yHSS3jLpInUT8oSw3czW6Im1CgTZ4nu7w== X-Received: by 2002:a05:620a:a182:10b0:915:b506:56cc with SMTP id af79cd13be357-915b50670d6mr218300485a.7.1780683658321; Fri, 05 Jun 2026 11:20:58 -0700 (PDT) Received: from localhost ([161.35.96.86]) by smtp.gmail.com with UTF8SMTPSA id af79cd13be357-9158a3bf5dasm963747285a.36.2026.06.05.11.20.57 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Fri, 05 Jun 2026 11:20:57 -0700 (PDT) From: Samuel Moelius To: Nicolas Pitre Cc: Samuel Moelius , linux-kernel@vger.kernel.org (open list) Subject: [PATCH] cramfs: reject directory entries that extend past i_size Date: Fri, 5 Jun 2026 18:20:49 +0000 Message-ID: <20260605182049.2488247-1-sam.moelius@trailofbits.com> X-Mailer: git-send-email 2.43.0 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" Cramfs directory iteration and lookup read each directory entry as sizeof(struct cramfs_inode) plus CRAMFS_MAXPATHLEN bytes, then use the on-disk namelen field to copy or compare the entry name. They do not first verify that the padded name bytes are within the containing directory inode's i_size. A crafted block-device image can declare a directory size that contains only an entry header while placing the padded name bytes immediately after that declared directory. The current kernel still exposes that name through readdir and lookup. Read the entry header first, validate that the padded name fits inside the remaining directory bytes, and only then read and use the full entry. Use the inode-size type for the remaining-byte calculation so the bound is checked before reducing it to the directory entry name length. Assisted-by: Codex:gpt-5.5-cyber-preview Signed-off-by: Samuel Moelius --- fs/cramfs/inode.c | 60 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 12 deletions(-) diff --git a/fs/cramfs/inode.c b/fs/cramfs/inode.c index 4edbfccd0bbe..527daaec024e 100644 --- a/fs/cramfs/inode.c +++ b/fs/cramfs/inode.c @@ -697,13 +697,37 @@ static int cramfs_statfs(struct dentry *dentry, struc= t kstatfs *buf) return 0; } =20 +static bool cramfs_read_dirent(struct inode *inode, unsigned int offset, + struct cramfs_inode *de, char **name, + int *namelen) +{ + loff_t remaining; + + if (offset > inode->i_size) + return false; + remaining =3D inode->i_size - offset; + if (remaining < sizeof(*de)) + return false; + + memcpy(de, + cramfs_read(inode->i_sb, OFFSET(inode) + offset, sizeof(*de)), + sizeof(*de)); + *namelen =3D de->namelen << 2; + if (*namelen > remaining - sizeof(*de)) + return false; + + *name =3D *namelen ? cramfs_read(inode->i_sb, + OFFSET(inode) + offset + sizeof(*de), + *namelen) : NULL; + return true; +} + /* * Read a cramfs directory entry. */ static int cramfs_readdir(struct file *file, struct dir_context *ctx) { struct inode *inode =3D file_inode(file); - struct super_block *sb =3D inode->i_sb; char *buf; unsigned int offset; =20 @@ -721,6 +745,7 @@ static int cramfs_readdir(struct file *file, struct dir= _context *ctx) =20 while (offset < inode->i_size) { struct cramfs_inode *de; + struct cramfs_inode entry; unsigned long nextoffset; char *name; ino_t ino; @@ -728,18 +753,22 @@ static int cramfs_readdir(struct file *file, struct d= ir_context *ctx) int namelen; =20 mutex_lock(&read_mutex); - de =3D cramfs_read(sb, OFFSET(inode) + offset, sizeof(*de)+CRAMFS_MAXPAT= HLEN); - name =3D (char *)(de+1); - /* * Namelengths on disk are shifted by two * and the name padded out to 4-byte boundaries * with zeroes. */ - namelen =3D de->namelen << 2; - memcpy(buf, name, namelen); - ino =3D cramino(de, OFFSET(inode) + offset); - mode =3D de->mode; + de =3D &entry; + if (!cramfs_read_dirent(inode, offset, de, &name, + &namelen)) { + mutex_unlock(&read_mutex); + kfree(buf); + return -EIO; + } + if (namelen) + memcpy(buf, name, namelen); + ino =3D cramino(&entry, OFFSET(inode) + offset); + mode =3D entry.mode; mutex_unlock(&read_mutex); nextoffset =3D offset + sizeof(*de) + namelen; for (;;) { @@ -773,18 +802,25 @@ static struct dentry *cramfs_lookup(struct inode *dir= , struct dentry *dentry, un sorted =3D CRAMFS_SB(dir->i_sb)->flags & CRAMFS_FLAG_SORTED_DIRS; while (offset < dir->i_size) { struct cramfs_inode *de; + struct cramfs_inode entry; char *name; int namelen, retval; int dir_off =3D OFFSET(dir) + offset; =20 - de =3D cramfs_read(dir->i_sb, dir_off, sizeof(*de)+CRAMFS_MAXPATHLEN); - name =3D (char *)(de+1); + de =3D &entry; + if (!cramfs_read_dirent(dir, offset, de, &name, &namelen)) { + inode =3D ERR_PTR(-EIO); + goto out; + } + if (!namelen) { + inode =3D ERR_PTR(-EIO); + goto out; + } =20 /* Try to take advantage of sorted directories */ if (sorted && (dentry->d_name.name[0] < name[0])) break; =20 - namelen =3D de->namelen << 2; offset +=3D sizeof(*de) + namelen; =20 /* Quick check that the name is roughly the right length */ @@ -806,7 +842,7 @@ static struct dentry *cramfs_lookup(struct inode *dir, = struct dentry *dentry, un if (retval > 0) continue; if (!retval) { - inode =3D get_cramfs_inode(dir->i_sb, de, dir_off); + inode =3D get_cramfs_inode(dir->i_sb, &entry, dir_off); break; } /* else (retval < 0) */ --=20 2.43.0