From: Samuel Moelius <sam.moelius@trailofbits.com>
To: Nicolas Pitre <nico@fluxnic.net>
Cc: Samuel Moelius <sam.moelius@trailofbits.com>,
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 [thread overview]
Message-ID: <20260605182049.2488247-1-sam.moelius@trailofbits.com> (raw)
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 <sam.moelius@trailofbits.com>
---
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, struct kstatfs *buf)
return 0;
}
+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 = 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 = de->namelen << 2;
+ if (*namelen > remaining - sizeof(*de))
+ return false;
+
+ *name = *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 = file_inode(file);
- struct super_block *sb = inode->i_sb;
char *buf;
unsigned int offset;
@@ -721,6 +745,7 @@ static int cramfs_readdir(struct file *file, struct dir_context *ctx)
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 dir_context *ctx)
int namelen;
mutex_lock(&read_mutex);
- de = cramfs_read(sb, OFFSET(inode) + offset, sizeof(*de)+CRAMFS_MAXPATHLEN);
- name = (char *)(de+1);
-
/*
* Namelengths on disk are shifted by two
* and the name padded out to 4-byte boundaries
* with zeroes.
*/
- namelen = de->namelen << 2;
- memcpy(buf, name, namelen);
- ino = cramino(de, OFFSET(inode) + offset);
- mode = de->mode;
+ de = &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 = cramino(&entry, OFFSET(inode) + offset);
+ mode = entry.mode;
mutex_unlock(&read_mutex);
nextoffset = offset + sizeof(*de) + namelen;
for (;;) {
@@ -773,18 +802,25 @@ static struct dentry *cramfs_lookup(struct inode *dir, struct dentry *dentry, un
sorted = 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 = OFFSET(dir) + offset;
- de = cramfs_read(dir->i_sb, dir_off, sizeof(*de)+CRAMFS_MAXPATHLEN);
- name = (char *)(de+1);
+ de = &entry;
+ if (!cramfs_read_dirent(dir, offset, de, &name, &namelen)) {
+ inode = ERR_PTR(-EIO);
+ goto out;
+ }
+ if (!namelen) {
+ inode = ERR_PTR(-EIO);
+ goto out;
+ }
/* Try to take advantage of sorted directories */
if (sorted && (dentry->d_name.name[0] < name[0]))
break;
- namelen = de->namelen << 2;
offset += sizeof(*de) + namelen;
/* 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 = get_cramfs_inode(dir->i_sb, de, dir_off);
+ inode = get_cramfs_inode(dir->i_sb, &entry, dir_off);
break;
}
/* else (retval < 0) */
--
2.43.0
reply other threads:[~2026-06-05 18:20 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260605182049.2488247-1-sam.moelius@trailofbits.com \
--to=sam.moelius@trailofbits.com \
--cc=linux-kernel@vger.kernel.org \
--cc=nico@fluxnic.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®