ext4: strong binding of xattr inode references
To verify that a xattr entry is not pointing to the wrong xattr inode, we currently check that the target inode has EXT4_EA_INODE_FL flag set and also the entry size matches the target inode size. For stronger validation, also incorporate crc32c hash of the value into the e_hash field. This is done regardless of whether the entry lives in the inode body or external attribute block. Signed-off-by: Tahsin Erdogan <tahsin@google.com> Signed-off-by: Theodore Ts'o <tytso@mit.edu>
This commit is contained in:
parent
daf8328172
commit
b9fc761ea2
|
@ -77,8 +77,8 @@ static void ext4_xattr_block_cache_insert(struct mb_cache *,
|
||||||
static struct buffer_head *
|
static struct buffer_head *
|
||||||
ext4_xattr_block_cache_find(struct inode *, struct ext4_xattr_header *,
|
ext4_xattr_block_cache_find(struct inode *, struct ext4_xattr_header *,
|
||||||
struct mb_cache_entry **);
|
struct mb_cache_entry **);
|
||||||
static void ext4_xattr_hash_entry(struct ext4_xattr_entry *entry,
|
static __le32 ext4_xattr_hash_entry(char *name, size_t name_len, __le32 *value,
|
||||||
void *value_base);
|
size_t value_count);
|
||||||
static void ext4_xattr_rehash(struct ext4_xattr_header *);
|
static void ext4_xattr_rehash(struct ext4_xattr_header *);
|
||||||
|
|
||||||
static const struct xattr_handler * const ext4_xattr_handler_map[] = {
|
static const struct xattr_handler * const ext4_xattr_handler_map[] = {
|
||||||
|
@ -380,7 +380,9 @@ error:
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
ext4_xattr_inode_verify_hash(struct inode *ea_inode, void *buffer, size_t size)
|
ext4_xattr_inode_verify_hashes(struct inode *ea_inode,
|
||||||
|
struct ext4_xattr_entry *entry, void *buffer,
|
||||||
|
size_t size)
|
||||||
{
|
{
|
||||||
u32 hash;
|
u32 hash;
|
||||||
|
|
||||||
|
@ -388,23 +390,35 @@ ext4_xattr_inode_verify_hash(struct inode *ea_inode, void *buffer, size_t size)
|
||||||
hash = ext4_xattr_inode_hash(EXT4_SB(ea_inode->i_sb), buffer, size);
|
hash = ext4_xattr_inode_hash(EXT4_SB(ea_inode->i_sb), buffer, size);
|
||||||
if (hash != ext4_xattr_inode_get_hash(ea_inode))
|
if (hash != ext4_xattr_inode_get_hash(ea_inode))
|
||||||
return -EFSCORRUPTED;
|
return -EFSCORRUPTED;
|
||||||
|
|
||||||
|
if (entry) {
|
||||||
|
__le32 e_hash, tmp_data;
|
||||||
|
|
||||||
|
/* Verify entry hash. */
|
||||||
|
tmp_data = cpu_to_le32(hash);
|
||||||
|
e_hash = ext4_xattr_hash_entry(entry->e_name, entry->e_name_len,
|
||||||
|
&tmp_data, 1);
|
||||||
|
if (e_hash != entry->e_hash)
|
||||||
|
return -EFSCORRUPTED;
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define EXT4_XATTR_INODE_GET_PARENT(inode) ((__u32)(inode)->i_mtime.tv_sec)
|
#define EXT4_XATTR_INODE_GET_PARENT(inode) ((__u32)(inode)->i_mtime.tv_sec)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Read the value from the EA inode.
|
* Read xattr value from the EA inode.
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
ext4_xattr_inode_get(struct inode *inode, unsigned long ea_ino, void *buffer,
|
ext4_xattr_inode_get(struct inode *inode, struct ext4_xattr_entry *entry,
|
||||||
size_t size)
|
void *buffer, size_t size)
|
||||||
{
|
{
|
||||||
struct mb_cache *ea_inode_cache = EA_INODE_CACHE(inode);
|
struct mb_cache *ea_inode_cache = EA_INODE_CACHE(inode);
|
||||||
struct inode *ea_inode;
|
struct inode *ea_inode;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
err = ext4_xattr_inode_iget(inode, ea_ino, &ea_inode);
|
err = ext4_xattr_inode_iget(inode, le32_to_cpu(entry->e_value_inum),
|
||||||
|
&ea_inode);
|
||||||
if (err) {
|
if (err) {
|
||||||
ea_inode = NULL;
|
ea_inode = NULL;
|
||||||
goto out;
|
goto out;
|
||||||
|
@ -422,7 +436,7 @@ ext4_xattr_inode_get(struct inode *inode, unsigned long ea_ino, void *buffer,
|
||||||
if (err)
|
if (err)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
err = ext4_xattr_inode_verify_hash(ea_inode, buffer, size);
|
err = ext4_xattr_inode_verify_hashes(ea_inode, entry, buffer, size);
|
||||||
/*
|
/*
|
||||||
* Compatibility check for old Lustre ea_inode implementation. Old
|
* Compatibility check for old Lustre ea_inode implementation. Old
|
||||||
* version does not have hash validation, but it has a backpointer
|
* version does not have hash validation, but it has a backpointer
|
||||||
|
@ -489,9 +503,8 @@ ext4_xattr_block_get(struct inode *inode, int name_index, const char *name,
|
||||||
if (size > buffer_size)
|
if (size > buffer_size)
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
if (entry->e_value_inum) {
|
if (entry->e_value_inum) {
|
||||||
error = ext4_xattr_inode_get(inode,
|
error = ext4_xattr_inode_get(inode, entry, buffer,
|
||||||
le32_to_cpu(entry->e_value_inum),
|
size);
|
||||||
buffer, size);
|
|
||||||
if (error)
|
if (error)
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
} else {
|
} else {
|
||||||
|
@ -539,9 +552,8 @@ ext4_xattr_ibody_get(struct inode *inode, int name_index, const char *name,
|
||||||
if (size > buffer_size)
|
if (size > buffer_size)
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
if (entry->e_value_inum) {
|
if (entry->e_value_inum) {
|
||||||
error = ext4_xattr_inode_get(inode,
|
error = ext4_xattr_inode_get(inode, entry, buffer,
|
||||||
le32_to_cpu(entry->e_value_inum),
|
size);
|
||||||
buffer, size);
|
|
||||||
if (error)
|
if (error)
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1400,7 +1412,7 @@ ext4_xattr_inode_cache_find(struct inode *inode, const void *value,
|
||||||
(EXT4_I(ea_inode)->i_flags & EXT4_EA_INODE_FL) &&
|
(EXT4_I(ea_inode)->i_flags & EXT4_EA_INODE_FL) &&
|
||||||
i_size_read(ea_inode) == value_len &&
|
i_size_read(ea_inode) == value_len &&
|
||||||
!ext4_xattr_inode_read(ea_inode, ea_data, value_len) &&
|
!ext4_xattr_inode_read(ea_inode, ea_data, value_len) &&
|
||||||
!ext4_xattr_inode_verify_hash(ea_inode, ea_data,
|
!ext4_xattr_inode_verify_hashes(ea_inode, NULL, ea_data,
|
||||||
value_len) &&
|
value_len) &&
|
||||||
!memcmp(value, ea_data, value_len)) {
|
!memcmp(value, ea_data, value_len)) {
|
||||||
mb_cache_entry_touch(ea_inode_cache, ce);
|
mb_cache_entry_touch(ea_inode_cache, ce);
|
||||||
|
@ -1665,11 +1677,35 @@ static int ext4_xattr_set_entry(struct ext4_xattr_info *i,
|
||||||
here->e_value_size = cpu_to_le32(i->value_len);
|
here->e_value_size = cpu_to_le32(i->value_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_block) {
|
if (i->value) {
|
||||||
if (i->value)
|
__le32 hash = 0;
|
||||||
ext4_xattr_hash_entry(here, s->base);
|
|
||||||
ext4_xattr_rehash((struct ext4_xattr_header *)s->base);
|
/* Entry hash calculation. */
|
||||||
|
if (in_inode) {
|
||||||
|
__le32 crc32c_hash;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Feed crc32c hash instead of the raw value for entry
|
||||||
|
* hash calculation. This is to avoid walking
|
||||||
|
* potentially long value buffer again.
|
||||||
|
*/
|
||||||
|
crc32c_hash = cpu_to_le32(
|
||||||
|
ext4_xattr_inode_get_hash(new_ea_inode));
|
||||||
|
hash = ext4_xattr_hash_entry(here->e_name,
|
||||||
|
here->e_name_len,
|
||||||
|
&crc32c_hash, 1);
|
||||||
|
} else if (is_block) {
|
||||||
|
__le32 *value = s->base + min_offs - new_size;
|
||||||
|
|
||||||
|
hash = ext4_xattr_hash_entry(here->e_name,
|
||||||
|
here->e_name_len, value,
|
||||||
|
new_size >> 2);
|
||||||
}
|
}
|
||||||
|
here->e_hash = hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_block)
|
||||||
|
ext4_xattr_rehash((struct ext4_xattr_header *)s->base);
|
||||||
|
|
||||||
ret = 0;
|
ret = 0;
|
||||||
out:
|
out:
|
||||||
|
@ -2452,9 +2488,7 @@ static int ext4_xattr_move_to_block(handle_t *handle, struct inode *inode,
|
||||||
|
|
||||||
/* Save the entry name and the entry value */
|
/* Save the entry name and the entry value */
|
||||||
if (entry->e_value_inum) {
|
if (entry->e_value_inum) {
|
||||||
error = ext4_xattr_inode_get(inode,
|
error = ext4_xattr_inode_get(inode, entry, buffer, value_size);
|
||||||
le32_to_cpu(entry->e_value_inum),
|
|
||||||
buffer, value_size);
|
|
||||||
if (error)
|
if (error)
|
||||||
goto out;
|
goto out;
|
||||||
} else {
|
} else {
|
||||||
|
@ -2944,30 +2978,22 @@ ext4_xattr_block_cache_find(struct inode *inode,
|
||||||
*
|
*
|
||||||
* Compute the hash of an extended attribute.
|
* Compute the hash of an extended attribute.
|
||||||
*/
|
*/
|
||||||
static void ext4_xattr_hash_entry(struct ext4_xattr_entry *entry,
|
static __le32 ext4_xattr_hash_entry(char *name, size_t name_len, __le32 *value,
|
||||||
void *value_base)
|
size_t value_count)
|
||||||
{
|
{
|
||||||
__u32 hash = 0;
|
__u32 hash = 0;
|
||||||
char *name = entry->e_name;
|
|
||||||
int n;
|
|
||||||
|
|
||||||
for (n = 0; n < entry->e_name_len; n++) {
|
while (name_len--) {
|
||||||
hash = (hash << NAME_HASH_SHIFT) ^
|
hash = (hash << NAME_HASH_SHIFT) ^
|
||||||
(hash >> (8*sizeof(hash) - NAME_HASH_SHIFT)) ^
|
(hash >> (8*sizeof(hash) - NAME_HASH_SHIFT)) ^
|
||||||
*name++;
|
*name++;
|
||||||
}
|
}
|
||||||
|
while (value_count--) {
|
||||||
if (!entry->e_value_inum && entry->e_value_size) {
|
|
||||||
__le32 *value = (__le32 *)((char *)value_base +
|
|
||||||
le16_to_cpu(entry->e_value_offs));
|
|
||||||
for (n = (le32_to_cpu(entry->e_value_size) +
|
|
||||||
EXT4_XATTR_ROUND) >> EXT4_XATTR_PAD_BITS; n; n--) {
|
|
||||||
hash = (hash << VALUE_HASH_SHIFT) ^
|
hash = (hash << VALUE_HASH_SHIFT) ^
|
||||||
(hash >> (8*sizeof(hash) - VALUE_HASH_SHIFT)) ^
|
(hash >> (8*sizeof(hash) - VALUE_HASH_SHIFT)) ^
|
||||||
le32_to_cpu(*value++);
|
le32_to_cpu(*value++);
|
||||||
}
|
}
|
||||||
}
|
return cpu_to_le32(hash);
|
||||||
entry->e_hash = cpu_to_le32(hash);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef NAME_HASH_SHIFT
|
#undef NAME_HASH_SHIFT
|
||||||
|
|
Loading…
Reference in New Issue