summaryrefslogtreecommitdiff
path: root/fs/ext4/inline.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/ext4/inline.c')
-rw-r--r--fs/ext4/inline.c62
1 files changed, 44 insertions, 18 deletions
diff --git a/fs/ext4/inline.c b/fs/ext4/inline.c
index 1f6bc05593df..ceee69a66482 100644
--- a/fs/ext4/inline.c
+++ b/fs/ext4/inline.c
@@ -22,8 +22,7 @@
static int ext4_da_convert_inline_data_to_extent(struct address_space *mapping,
- struct inode *inode,
- void **fsdata);
+ struct inode *inode);
static int ext4_get_inline_size(struct inode *inode)
{
@@ -119,7 +118,7 @@ int ext4_get_max_inline_size(struct inode *inode)
error = ext4_get_inode_loc(inode, &iloc);
if (error) {
ext4_error_inode_err(inode, __func__, __LINE__, 0, -error,
- "can't get inode location %lu",
+ "can't get inode location %llu",
inode->i_ino);
return 0;
}
@@ -512,7 +511,7 @@ static int ext4_read_inline_folio(struct inode *inode, struct folio *folio)
BUG_ON(folio->index);
if (!EXT4_I(inode)->i_inline_off) {
- ext4_warning(inode->i_sb, "inode %lu doesn't have inline data.",
+ ext4_warning(inode->i_sb, "inode %llu doesn't have inline data.",
inode->i_ino);
goto out;
}
@@ -522,7 +521,15 @@ static int ext4_read_inline_folio(struct inode *inode, struct folio *folio)
goto out;
len = min_t(size_t, ext4_get_inline_size(inode), i_size_read(inode));
- BUG_ON(len > PAGE_SIZE);
+
+ if (len > PAGE_SIZE) {
+ ext4_error_inode(inode, __func__, __LINE__, 0,
+ "inline size %zu exceeds PAGE_SIZE", len);
+ ret = -EFSCORRUPTED;
+ brelse(iloc.bh);
+ goto out;
+ }
+
kaddr = kmap_local_folio(folio, 0);
ret = ext4_read_inline_data(inode, kaddr, len, &iloc);
kaddr = folio_zero_tail(folio, len, kaddr + len);
@@ -689,7 +696,7 @@ int ext4_generic_write_inline_data(struct address_space *mapping,
struct inode *inode,
loff_t pos, unsigned len,
struct folio **foliop,
- void **fsdata, bool da)
+ bool da)
{
int ret;
handle_t *handle;
@@ -720,7 +727,7 @@ retry_journal:
return ext4_convert_inline_data_to_extent(mapping, inode);
}
- ret = ext4_da_convert_inline_data_to_extent(mapping, inode, fsdata);
+ ret = ext4_da_convert_inline_data_to_extent(mapping, inode);
if (ret == -ENOSPC &&
ext4_should_retry_alloc(inode->i_sb, &retries))
goto retry_journal;
@@ -780,7 +787,7 @@ int ext4_try_to_write_inline_data(struct address_space *mapping,
if (pos + len > ext4_get_max_inline_size(inode))
return ext4_convert_inline_data_to_extent(mapping, inode);
return ext4_generic_write_inline_data(mapping, inode, pos, len,
- foliop, NULL, false);
+ foliop, false);
}
int ext4_write_inline_data_end(struct inode *inode, loff_t pos, unsigned len,
@@ -804,7 +811,19 @@ int ext4_write_inline_data_end(struct inode *inode, loff_t pos, unsigned len,
goto out;
}
ext4_write_lock_xattr(inode, &no_expand);
- BUG_ON(!ext4_has_inline_data(inode));
+ /*
+ * We could have raced with ext4_page_mkwrite() converting
+ * the inode and clearing the inline data flag, so we just
+ * release resources and retry the whole write.
+ */
+ if (unlikely(!ext4_has_inline_data(inode))) {
+ ext4_write_unlock_xattr(inode, &no_expand);
+ brelse(iloc.bh);
+ folio_unlock(folio);
+ folio_put(folio);
+ ext4_journal_stop(handle);
+ return 0;
+ }
/*
* ei->i_inline_off may have changed since
@@ -875,8 +894,7 @@ out:
* need to start the journal since the file's metadata isn't changed now.
*/
static int ext4_da_convert_inline_data_to_extent(struct address_space *mapping,
- struct inode *inode,
- void **fsdata)
+ struct inode *inode)
{
int ret = 0, inline_size;
struct folio *folio;
@@ -914,7 +932,6 @@ static int ext4_da_convert_inline_data_to_extent(struct address_space *mapping,
folio_mark_dirty(folio);
folio_mark_uptodate(folio);
ext4_clear_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA);
- *fsdata = (void *)CONVERT_INLINE_DATA;
out:
up_read(&EXT4_I(inode)->xattr_sem);
@@ -934,7 +951,7 @@ void ext4_show_inline_dir(struct inode *dir, struct buffer_head *bh,
struct ext4_dir_entry_2 *de = inline_start;
void *dlimit = inline_start + inline_size;
- trace_printk("inode %lu\n", dir->i_ino);
+ trace_printk("inode %llu\n", dir->i_ino);
offset = 0;
while ((void *)de < dlimit) {
de_len = ext4_rec_len_from_disk(de->rec_len, inline_size);
@@ -1071,7 +1088,7 @@ static void ext4_restore_inline_data(handle_t *handle, struct inode *inode,
ret = ext4_create_inline_data(handle, inode, inline_size);
if (ret) {
ext4_msg(inode->i_sb, KERN_EMERG,
- "error restoring inline_data for inode -- potential data loss! (inode %lu, error %d)",
+ "error restoring inline_data for inode -- potential data loss! (inode %llu, error %d)",
inode->i_ino, ret);
return;
}
@@ -1446,6 +1463,8 @@ int ext4_read_inline_dir(struct file *file,
/* for other entry, the real offset in
* the buf has to be tuned accordingly.
*/
+ if (i + ext4_dir_rec_len(1, NULL) > extra_size)
+ break;
de = (struct ext4_dir_entry_2 *)
(dir_buf + i - extra_offset);
/* It's too expensive to do a full
@@ -1480,10 +1499,17 @@ int ext4_read_inline_dir(struct file *file,
continue;
}
+ /*
+ * de lives at dir_buf + ctx->pos - extra_offset, within the
+ * kmalloc(inline_size) buffer. Make sure its header fits before
+ * ext4_check_dir_entry() dereferences de->rec_len.
+ */
+ if (ctx->pos + ext4_dir_rec_len(1, NULL) > extra_size)
+ goto out;
de = (struct ext4_dir_entry_2 *)
(dir_buf + ctx->pos - extra_offset);
if (ext4_check_dir_entry(inode, file, de, iloc.bh, dir_buf,
- extra_size, ctx->pos))
+ inline_size, ctx->pos))
goto out;
if (le32_to_cpu(de->inode)) {
if (!dir_emit(ctx, de->name, de->name_len,
@@ -1740,7 +1766,7 @@ bool empty_inline_dir(struct inode *dir, int *has_inline_data)
err = ext4_get_inode_loc(dir, &iloc);
if (err) {
EXT4_ERROR_INODE_ERR(dir, -err,
- "error %d getting inode %lu block",
+ "error %d getting inode %llu block",
err, dir->i_ino);
return false;
}
@@ -1755,7 +1781,7 @@ bool empty_inline_dir(struct inode *dir, int *has_inline_data)
de = (struct ext4_dir_entry_2 *)ext4_raw_inode(&iloc)->i_block;
if (!le32_to_cpu(de->inode)) {
ext4_warning(dir->i_sb,
- "bad inline directory (dir #%lu) - no `..'",
+ "bad inline directory (dir #%llu) - no `..'",
dir->i_ino);
goto out;
}
@@ -1769,7 +1795,7 @@ bool empty_inline_dir(struct inode *dir, int *has_inline_data)
iloc.bh, inline_pos,
inline_size, offset)) {
ext4_warning(dir->i_sb,
- "bad inline directory (dir #%lu) - "
+ "bad inline directory (dir #%llu) - "
"inode %u, rec_len %u, name_len %d"
"inline size %d",
dir->i_ino, le32_to_cpu(de->inode),