Commit 2f300785 authored by Darrick J. Wong's avatar Darrick J. Wong Committed by Ben Hutchings

ext4: check EA value offset when loading

commit a0626e75 upstream.

When loading extended attributes, check each entry's value offset to
make sure it doesn't collide with the entries.

Without this check it is easy to crash the kernel by mounting a
malicious FS containing a file with an EA wherein e_value_offs = 0 and
e_value_size > 0 and then deleting the EA, which corrupts the name
list.

(See the f_ea_value_crash test's FS image in e2fsprogs for an example.)
Signed-off-by: default avatarDarrick J. Wong <darrick.wong@oracle.com>
Signed-off-by: default avatarTheodore Ts'o <tytso@mit.edu>
[bwh: Backported to 3.2: adjust context]
Signed-off-by: default avatarBen Hutchings <ben@decadent.org.uk>
parent 7e6ccbbb
...@@ -144,14 +144,28 @@ ext4_listxattr(struct dentry *dentry, char *buffer, size_t size) ...@@ -144,14 +144,28 @@ ext4_listxattr(struct dentry *dentry, char *buffer, size_t size)
} }
static int static int
ext4_xattr_check_names(struct ext4_xattr_entry *entry, void *end) ext4_xattr_check_names(struct ext4_xattr_entry *entry, void *end,
void *value_start)
{ {
while (!IS_LAST_ENTRY(entry)) { struct ext4_xattr_entry *e = entry;
struct ext4_xattr_entry *next = EXT4_XATTR_NEXT(entry);
while (!IS_LAST_ENTRY(e)) {
struct ext4_xattr_entry *next = EXT4_XATTR_NEXT(e);
if ((void *)next >= end) if ((void *)next >= end)
return -EIO; return -EIO;
entry = next; e = next;
} }
while (!IS_LAST_ENTRY(entry)) {
if (entry->e_value_size != 0 &&
(value_start + le16_to_cpu(entry->e_value_offs) <
(void *)e + sizeof(__u32) ||
value_start + le16_to_cpu(entry->e_value_offs) +
le32_to_cpu(entry->e_value_size) > end))
return -EIO;
entry = EXT4_XATTR_NEXT(entry);
}
return 0; return 0;
} }
...@@ -163,7 +177,8 @@ ext4_xattr_check_block(struct buffer_head *bh) ...@@ -163,7 +177,8 @@ ext4_xattr_check_block(struct buffer_head *bh)
if (BHDR(bh)->h_magic != cpu_to_le32(EXT4_XATTR_MAGIC) || if (BHDR(bh)->h_magic != cpu_to_le32(EXT4_XATTR_MAGIC) ||
BHDR(bh)->h_blocks != cpu_to_le32(1)) BHDR(bh)->h_blocks != cpu_to_le32(1))
return -EIO; return -EIO;
error = ext4_xattr_check_names(BFIRST(bh), bh->b_data + bh->b_size); error = ext4_xattr_check_names(BFIRST(bh), bh->b_data + bh->b_size,
bh->b_data);
return error; return error;
} }
...@@ -276,7 +291,7 @@ ext4_xattr_ibody_get(struct inode *inode, int name_index, const char *name, ...@@ -276,7 +291,7 @@ ext4_xattr_ibody_get(struct inode *inode, int name_index, const char *name,
header = IHDR(inode, raw_inode); header = IHDR(inode, raw_inode);
entry = IFIRST(header); entry = IFIRST(header);
end = (void *)raw_inode + EXT4_SB(inode->i_sb)->s_inode_size; end = (void *)raw_inode + EXT4_SB(inode->i_sb)->s_inode_size;
error = ext4_xattr_check_names(entry, end); error = ext4_xattr_check_names(entry, end, entry);
if (error) if (error)
goto cleanup; goto cleanup;
error = ext4_xattr_find_entry(&entry, name_index, name, error = ext4_xattr_find_entry(&entry, name_index, name,
...@@ -403,7 +418,7 @@ ext4_xattr_ibody_list(struct dentry *dentry, char *buffer, size_t buffer_size) ...@@ -403,7 +418,7 @@ ext4_xattr_ibody_list(struct dentry *dentry, char *buffer, size_t buffer_size)
raw_inode = ext4_raw_inode(&iloc); raw_inode = ext4_raw_inode(&iloc);
header = IHDR(inode, raw_inode); header = IHDR(inode, raw_inode);
end = (void *)raw_inode + EXT4_SB(inode->i_sb)->s_inode_size; end = (void *)raw_inode + EXT4_SB(inode->i_sb)->s_inode_size;
error = ext4_xattr_check_names(IFIRST(header), end); error = ext4_xattr_check_names(IFIRST(header), end, IFIRST(header));
if (error) if (error)
goto cleanup; goto cleanup;
error = ext4_xattr_list_entries(dentry, IFIRST(header), error = ext4_xattr_list_entries(dentry, IFIRST(header),
...@@ -914,7 +929,8 @@ ext4_xattr_ibody_find(struct inode *inode, struct ext4_xattr_info *i, ...@@ -914,7 +929,8 @@ ext4_xattr_ibody_find(struct inode *inode, struct ext4_xattr_info *i,
is->s.here = is->s.first; is->s.here = is->s.first;
is->s.end = (void *)raw_inode + EXT4_SB(inode->i_sb)->s_inode_size; is->s.end = (void *)raw_inode + EXT4_SB(inode->i_sb)->s_inode_size;
if (ext4_test_inode_state(inode, EXT4_STATE_XATTR)) { if (ext4_test_inode_state(inode, EXT4_STATE_XATTR)) {
error = ext4_xattr_check_names(IFIRST(header), is->s.end); error = ext4_xattr_check_names(IFIRST(header), is->s.end,
IFIRST(header));
if (error) if (error)
return error; return error;
/* Find the named attribute. */ /* Find the named attribute. */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment