diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index 3b9740c1c16d..7b356baf7bd4 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -83,6 +83,36 @@ static struct buffer_head *ext4_append(handle_t *handle, bh = ext4_bread(handle, inode, *block, EXT4_GET_BLOCKS_CREATE); if (IS_ERR(bh)) return bh; + + /* + * The logical block was a hole, but a corrupt block bitmap can make + * the allocator hand out a physical block that is already in use, + * possibly by another block of this very inode. Callers such as + * do_split()/make_indexed_dir() assume the newly appended block is + * distinct from the blocks they are about to modify; if it aliases + * one of them, moving directory entries between the two buffers + * corrupts the directory. Make sure the new block is not already + * mapped by this inode. + */ + for (map.m_lblk = 0; map.m_lblk < *block; ) { + map.m_len = *block - map.m_lblk; + err = ext4_map_blocks(NULL, inode, &map, 0); + if (err < 0) + goto out; + if (err == 0) { + map.m_lblk++; + continue; + } + if (unlikely(map.m_pblk == bh->b_blocknr)) { + EXT4_ERROR_INODE(inode, + "new block %llu already mapped", + (unsigned long long)bh->b_blocknr); + err = -EFSCORRUPTED; + goto out; + } + map.m_lblk += map.m_len; + } + inode->i_size += inode->i_sb->s_blocksize; EXT4_I(inode)->i_disksize = inode->i_size; err = ext4_mark_inode_dirty(handle, inode);