diff --git a/convert/source-ext2.c b/convert/source-ext2.c index acba5db7..d06f90a9 100644 --- a/convert/source-ext2.c +++ b/convert/source-ext2.c @@ -390,6 +390,7 @@ static int ext2_create_file_extents(struct btrfs_trans_handle *trans, ext2_filsys ext2_fs, ext2_ino_t ext2_ino, u32 convert_flags) { + struct btrfs_fs_info *fs_info = trans->fs_info; int ret; char *buffer = NULL; errcode_t err; @@ -397,8 +398,20 @@ static int ext2_create_file_extents(struct btrfs_trans_handle *trans, u32 last_block; u32 sectorsize = root->fs_info->sectorsize; u64 inode_size = btrfs_stack_inode_size(btrfs_inode); + bool meet_inline_size_limit; struct blk_iterate_data data; + if (S_ISLNK(btrfs_stack_inode_mode(btrfs_inode))) { + meet_inline_size_limit = inode_size <= btrfs_symlink_max_size(fs_info); + if (!meet_inline_size_limit) { + error("symlink too large for ext2 inode %u, has %llu max %u", + ext2_ino, inode_size, btrfs_symlink_max_size(fs_info)); + return -ENAMETOOLONG; + } + } else { + meet_inline_size_limit = inode_size <= btrfs_data_inline_max_size(fs_info); + } + init_blk_iterate_data(&data, trans, root, btrfs_inode, objectid, convert_flags & CONVERT_FLAG_DATACSUM); @@ -430,8 +443,7 @@ static int ext2_create_file_extents(struct btrfs_trans_handle *trans, if (ret) goto fail; if ((convert_flags & CONVERT_FLAG_INLINE_DATA) && data.first_block == 0 - && data.num_blocks > 0 && inode_size < sectorsize - && inode_size <= BTRFS_MAX_INLINE_DATA_SIZE(root->fs_info)) { + && data.num_blocks > 0 && meet_inline_size_limit) { u64 num_bytes = data.num_blocks * sectorsize; u64 disk_bytenr = data.disk_block * sectorsize; u64 nbytes; @@ -476,21 +488,26 @@ static int ext2_create_symlink(struct btrfs_trans_handle *trans, int ret; char *pathname; u64 inode_size = btrfs_stack_inode_size(btrfs_inode); + if (ext2fs_inode_data_blocks2(ext2_fs, ext2_inode)) { - btrfs_set_stack_inode_size(btrfs_inode, inode_size + 1); + if (inode_size > btrfs_symlink_max_size(trans->fs_info)) { + error("symlink too large for ext2 inode %u, has %llu max %u", + ext2_ino, inode_size, + btrfs_symlink_max_size(trans->fs_info)); + return -ENAMETOOLONG; + } ret = ext2_create_file_extents(trans, root, objectid, btrfs_inode, ext2_fs, ext2_ino, CONVERT_FLAG_DATACSUM | CONVERT_FLAG_INLINE_DATA); - btrfs_set_stack_inode_size(btrfs_inode, inode_size); return ret; } pathname = (char *)&(ext2_inode->i_block[0]); BUG_ON(pathname[inode_size] != 0); ret = btrfs_insert_inline_extent(trans, root, objectid, 0, - pathname, inode_size + 1); - btrfs_set_stack_inode_nbytes(btrfs_inode, inode_size + 1); + pathname, inode_size); + btrfs_set_stack_inode_nbytes(btrfs_inode, inode_size); return ret; } diff --git a/convert/source-reiserfs.c b/convert/source-reiserfs.c index 3edc72ed..3475b152 100644 --- a/convert/source-reiserfs.c +++ b/convert/source-reiserfs.c @@ -537,9 +537,15 @@ static int reiserfs_copy_symlink(struct btrfs_trans_handle *trans, symlink = tp_item_body(&path); len = get_ih_item_len(tp_item_head(&path)); + if (len > btrfs_symlink_max_size(trans->fs_info)) { + error("symlink too large, has %u max %u", + len, btrfs_symlink_max_size(trans->fs_info)); + ret = -ENAMETOOLONG; + goto fail; + } ret = btrfs_insert_inline_extent(trans, root, objectid, 0, - symlink, len + 1); - btrfs_set_stack_inode_nbytes(btrfs_inode, len + 1); + symlink, len); + btrfs_set_stack_inode_nbytes(btrfs_inode, len); fail: pathrelse(&path); return ret; diff --git a/kernel-shared/file-item.c b/kernel-shared/file-item.c index d2da56e1..eb902402 100644 --- a/kernel-shared/file-item.c +++ b/kernel-shared/file-item.c @@ -26,6 +26,7 @@ #include "kernel-shared/extent_io.h" #include "kernel-shared/uapi/btrfs.h" #include "common/internal.h" +#include "common/messages.h" #define MAX_CSUM_ITEMS(r, size) ((((BTRFS_LEAF_DATA_SIZE(r->fs_info) - \ sizeof(struct btrfs_item) * 2) / \ @@ -88,6 +89,7 @@ int btrfs_insert_inline_extent(struct btrfs_trans_handle *trans, struct btrfs_root *root, u64 objectid, u64 offset, const char *buffer, size_t size) { + struct btrfs_fs_info *fs_info = trans->fs_info; struct btrfs_key key; struct btrfs_path *path; struct extent_buffer *leaf; @@ -97,6 +99,10 @@ int btrfs_insert_inline_extent(struct btrfs_trans_handle *trans, int err = 0; int ret; + if (size > max(btrfs_symlink_max_size(fs_info), + btrfs_data_inline_max_size(fs_info))) + return -EUCLEAN; + path = btrfs_alloc_path(); if (!path) return -ENOMEM; diff --git a/kernel-shared/file-item.h b/kernel-shared/file-item.h index 0df8f4df..2c1e17c9 100644 --- a/kernel-shared/file-item.h +++ b/kernel-shared/file-item.h @@ -92,5 +92,23 @@ int btrfs_csum_file_block(struct btrfs_trans_handle *trans, u64 logical, int btrfs_insert_inline_extent(struct btrfs_trans_handle *trans, struct btrfs_root *root, u64 objectid, u64 offset, const char *buffer, size_t size); +/* + * For symlink we allow up to PATH_MAX - 1 (PATH_MAX includes the terminating NUL, + * but fs doesn't store that terminating NUL). + * + * But for inlined data extents, the up limit is sectorsize - 1 (inclusive), or a + * regular extent should be created instead. + */ +static inline u32 btrfs_symlink_max_size(struct btrfs_fs_info *fs_info) +{ + return min_t(u32, BTRFS_MAX_INLINE_DATA_SIZE(fs_info), + PATH_MAX - 1); +} + +static inline u32 btrfs_data_inline_max_size(struct btrfs_fs_info *fs_info) +{ + return min_t(u32, BTRFS_MAX_INLINE_DATA_SIZE(fs_info), + fs_info->sectorsize - 1); +} #endif