A UFS2 inode can own up to UFS_NXADDR blocks holding extended attributes, described by di_extsize and di_extb[]. They sit outside the block tree of the file, but ffs_alloc() counts them in di_blocks like any other allocation. Linux never reads those two fields. ufs_truncate_blocks() only walks di_db[] and di_ib[], so on unlink the extattr blocks stay marked in use while ufs_update_inode() clears the whole on-disk inode, di_extb[] included. The only record of where the blocks were is gone, and nothing can ever free them. Deleting FreeBSD files that carry an extended attribute, an ACL or a MAC label therefore leaks disk space until fsck reclaims it. Keep di_extsize and di_extb[] in the in-core inode and release them from ufs_evict_inode(), before the on-disk inode is cleared. The last block of the area can be a fragment, so size it the way FreeBSD's sblksize() does rather than assuming a full block. An external attribute size larger than the area can describe means the inode is corrupted and the block numbers cannot be trusted. Warn and leave the blocks alone in that case; leaking them is better than freeing blocks that may belong to something else. Signed-off-by: Ali Ahmet Memis --- fs/ufs/ialloc.c | 2 ++ fs/ufs/inode.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++ fs/ufs/ufs.h | 2 ++ 3 files changed, 59 insertions(+) diff --git a/fs/ufs/ialloc.c b/fs/ufs/ialloc.c index 8e51f4630d18..4c3c76c4e7ff 100644 --- a/fs/ufs/ialloc.c +++ b/fs/ufs/ialloc.c @@ -294,6 +294,8 @@ struct inode *ufs_new_inode(struct inode *dir, umode_t mode) inode->i_generation = 0; simple_inode_init_ts(inode); ufsi->i_flags = UFS_I(dir)->i_flags; + ufsi->i_extsize = 0; + memset(ufsi->i_extb, 0, sizeof(ufsi->i_extb)); ufsi->i_lastfrag = 0; ufsi->i_shadow = 0; ufsi->i_osync = 0; diff --git a/fs/ufs/inode.c b/fs/ufs/inode.c index 802bc6e01db2..7079285bac2a 100644 --- a/fs/ufs/inode.c +++ b/fs/ufs/inode.c @@ -585,6 +585,8 @@ static int ufs1_read_inode(struct inode *inode, struct ufs_inode *ufs_inode) inode->i_blocks = fs32_to_cpu(sb, ufs_inode->ui_blocks); inode->i_generation = fs32_to_cpu(sb, ufs_inode->ui_gen); ufsi->i_flags = fs32_to_cpu(sb, ufs_inode->ui_flags); + ufsi->i_extsize = 0; + memset(ufsi->i_extb, 0, sizeof(ufsi->i_extb)); ufsi->i_shadow = fs32_to_cpu(sb, ufs_inode->ui_u3.ui_sun.ui_shadow); ufsi->i_oeftflag = fs32_to_cpu(sb, ufs_inode->ui_u3.ui_sun.ui_oeftflag); @@ -605,6 +607,7 @@ static int ufs2_read_inode(struct inode *inode, struct ufs2_inode *ufs2_inode) struct ufs_inode_info *ufsi = UFS_I(inode); struct super_block *sb = inode->i_sb; umode_t mode; + unsigned int i; UFSD("Reading ufs2 inode, ino %llu\n", inode->i_ino); /* @@ -631,6 +634,9 @@ static int ufs2_read_inode(struct inode *inode, struct ufs2_inode *ufs2_inode) inode->i_blocks = fs64_to_cpu(sb, ufs2_inode->ui_blocks); inode->i_generation = fs32_to_cpu(sb, ufs2_inode->ui_gen); ufsi->i_flags = fs32_to_cpu(sb, ufs2_inode->ui_flags); + ufsi->i_extsize = fs32_to_cpu(sb, ufs2_inode->ui_extsize); + for (i = 0; i < UFS_NXADDR; i++) + ufsi->i_extb[i] = fs64_to_cpu(sb, ufs2_inode->ui_extb[i]); /* ufsi->i_shadow = fs32_to_cpu(sb, ufs_inode->ui_u3.ui_sun.ui_shadow); ufsi->i_oeftflag = fs32_to_cpu(sb, ufs_inode->ui_u3.ui_sun.ui_oeftflag); @@ -846,6 +852,54 @@ int ufs_sync_inode (struct inode *inode) return ufs_update_inode (inode, 1); } +/* + * UFS2 keeps extended attributes in up to UFS_NXADDR blocks of their own. + * They live outside the block tree of the file but are counted in di_blocks. + * Linux does not implement extended attributes, but it still has to release + * those blocks when the inode goes away: ufs_update_inode() clears the whole + * on-disk inode, taking di_extb[] with it, so nothing would ever free them. + */ +static void ufs_free_ext_blocks(struct inode *inode) +{ + struct super_block *sb = inode->i_sb; + struct ufs_sb_private_info *uspi = UFS_SB(sb)->s_uspi; + struct ufs_inode_info *ufsi = UFS_I(inode); + u64 size = ufsi->i_extsize; + unsigned int i; + + if ((UFS_SB(sb)->s_flags & UFS_TYPE_MASK) != UFS_TYPE_UFS2 || !size) + return; + + if (size > ((u64)UFS_NXADDR << uspi->s_bshift)) { + // leaking is better than freeing something we cannot locate + ufs_warning(sb, __func__, + "inode %llu: bad external attribute size %llu\n", + inode->i_ino, size); + return; + } + + for (i = 0; i < UFS_NXADDR; i++) { + u64 off = (u64)i << uspi->s_bshift; + u64 block = ufsi->i_extb[i]; + unsigned int frags; + + if (!block || size <= off) + continue; + + if (size - off >= uspi->s_bsize) + frags = uspi->s_fpb; + else + frags = (size - off + uspi->s_fsize - 1) >> uspi->s_fshift; + + ufsi->i_extb[i] = 0; + if (frags == uspi->s_fpb) + ufs_free_blocks(inode, block, frags); + else + ufs_free_fragments(inode, block, frags); + } + ufsi->i_extsize = 0; +} + void ufs_evict_inode(struct inode * inode) { int want_delete = 0; @@ -864,6 +918,7 @@ void ufs_evict_inode(struct inode * inode) (S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode) || S_ISLNK(inode->i_mode))) ufs_truncate_blocks(inode); + ufs_free_ext_blocks(inode); ufs_update_inode(inode, inode_needs_sync(inode)); } diff --git a/fs/ufs/ufs.h b/fs/ufs/ufs.h index 788e025056b2..6cac5f8222eb 100644 --- a/fs/ufs/ufs.h +++ b/fs/ufs/ufs.h @@ -40,6 +40,8 @@ struct ufs_inode_info { __fs64 u2_i_data[15]; } i_u1; __u32 i_flags; + __u32 i_extsize; /* UFS2 external attribute area size */ + __u64 i_extb[UFS_NXADDR]; /* UFS2 external attribute blocks */ __u32 i_shadow; __u32 i_unused1; __u32 i_unused2; -- 2.55.0