Commit 74584ae5 authored by Jan Kara's avatar Jan Kara Committed by Linus Torvalds

udf: fix possible leakage of blocks

We have to take care that when we call udf_discard_prealloc() from
udf_clear_inode() we have to write inode ourselves afterwards (otherwise,
some changes might be lost leading to leakage of blocks, use of free blocks
or improperly aligned extents).

Also udf_discard_prealloc() does two different things - it removes
preallocated blocks and truncates the last extent to exactly match i_size.
We move the latter functionality to udf_truncate_tail_extent(), call
udf_discard_prealloc() when last reference to a file is dropped and call
udf_truncate_tail_extent() when inode is being removed from inode cache
(udf_clear_inode() call).

We cannot call udf_truncate_tail_extent() earlier as subsequent open+write
would find the last block of the file mapped and happily write to the end
of it, although the last extent says it's shorter.

[akpm@linux-foundation.org: Make checkpatch.pl happier]
Signed-off-by: default avatarJan Kara <jack@suse.cz>
Cc: Eric Sandeen <sandeen@sandeen.net>
Cc: Cyrill Gorcunov <gorcunov@gmail.com>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 4b356be0
...@@ -100,14 +100,23 @@ void udf_delete_inode(struct inode * inode) ...@@ -100,14 +100,23 @@ void udf_delete_inode(struct inode * inode)
clear_inode(inode); clear_inode(inode);
} }
/*
* If we are going to release inode from memory, we discard preallocation and
* truncate last inode extent to proper length. We could use drop_inode() but
* it's called under inode_lock and thus we cannot mark inode dirty there. We
* use clear_inode() but we have to make sure to write inode as it's not written
* automatically.
*/
void udf_clear_inode(struct inode *inode) void udf_clear_inode(struct inode *inode)
{ {
if (!(inode->i_sb->s_flags & MS_RDONLY)) { if (!(inode->i_sb->s_flags & MS_RDONLY)) {
lock_kernel(); lock_kernel();
/* Discard preallocation for directories, symlinks, etc. */
udf_discard_prealloc(inode); udf_discard_prealloc(inode);
udf_truncate_tail_extent(inode);
unlock_kernel(); unlock_kernel();
write_inode_now(inode, 1);
} }
kfree(UDF_I_DATA(inode)); kfree(UDF_I_DATA(inode));
UDF_I_DATA(inode) = NULL; UDF_I_DATA(inode) = NULL;
} }
......
...@@ -61,7 +61,11 @@ static void extent_trunc(struct inode * inode, struct extent_position *epos, ...@@ -61,7 +61,11 @@ static void extent_trunc(struct inode * inode, struct extent_position *epos,
} }
} }
void udf_discard_prealloc(struct inode * inode) /*
* Truncate the last extent to match i_size. This function assumes
* that preallocation extent is already truncated.
*/
void udf_truncate_tail_extent(struct inode *inode)
{ {
struct extent_position epos = { NULL, 0, {0, 0}}; struct extent_position epos = { NULL, 0, {0, 0}};
kernel_lb_addr eloc; kernel_lb_addr eloc;
...@@ -71,7 +75,10 @@ void udf_discard_prealloc(struct inode * inode) ...@@ -71,7 +75,10 @@ void udf_discard_prealloc(struct inode * inode)
int adsize; int adsize;
if (UDF_I_ALLOCTYPE(inode) == ICBTAG_FLAG_AD_IN_ICB || if (UDF_I_ALLOCTYPE(inode) == ICBTAG_FLAG_AD_IN_ICB ||
inode->i_size == UDF_I_LENEXTENTS(inode)) inode->i_size == UDF_I_LENEXTENTS(inode))
return;
/* Are we going to delete the file anyway? */
if (inode->i_nlink == 0)
return; return;
if (UDF_I_ALLOCTYPE(inode) == ICBTAG_FLAG_AD_SHORT) if (UDF_I_ALLOCTYPE(inode) == ICBTAG_FLAG_AD_SHORT)
...@@ -79,36 +86,76 @@ void udf_discard_prealloc(struct inode * inode) ...@@ -79,36 +86,76 @@ void udf_discard_prealloc(struct inode * inode)
else if (UDF_I_ALLOCTYPE(inode) == ICBTAG_FLAG_AD_LONG) else if (UDF_I_ALLOCTYPE(inode) == ICBTAG_FLAG_AD_LONG)
adsize = sizeof(long_ad); adsize = sizeof(long_ad);
else else
adsize = 0; BUG();
epos.block = UDF_I_LOCATION(inode);
/* Find the last extent in the file */ /* Find the last extent in the file */
while ((netype = udf_next_aext(inode, &epos, &eloc, &elen, 1)) != -1) while ((netype = udf_next_aext(inode, &epos, &eloc, &elen, 1)) != -1)
{ {
etype = netype; etype = netype;
lbcount += elen; lbcount += elen;
if (lbcount > inode->i_size && lbcount - elen < inode->i_size) if (lbcount > inode->i_size) {
{ if (lbcount - inode->i_size >= inode->i_sb->s_blocksize)
WARN_ON(lbcount - inode->i_size >= inode->i_sb->s_blocksize); printk(KERN_WARNING
"udf_truncate_tail_extent(): Too long "
"extent after EOF in inode %u: i_size: "
"%Ld lbcount: %Ld extent %u+%u\n",
(unsigned)inode->i_ino,
(long long)inode->i_size,
(long long)lbcount,
(unsigned)eloc.logicalBlockNum,
(unsigned)elen);
nelen = elen - (lbcount - inode->i_size); nelen = elen - (lbcount - inode->i_size);
epos.offset -= adsize; epos.offset -= adsize;
extent_trunc(inode, &epos, eloc, etype, elen, nelen); extent_trunc(inode, &epos, eloc, etype, elen, nelen);
epos.offset += adsize; epos.offset += adsize;
lbcount = inode->i_size; if (udf_next_aext(inode, &epos, &eloc, &elen, 1) != -1)
printk(KERN_ERR "udf_truncate_tail_extent(): "
"Extent after EOF in inode %u.\n",
(unsigned)inode->i_ino);
break;
} }
} }
/* This inode entry is in-memory only and thus we don't have to mark
* the inode dirty */
UDF_I_LENEXTENTS(inode) = inode->i_size;
brelse(epos.bh);
}
void udf_discard_prealloc(struct inode *inode)
{
struct extent_position epos = { NULL, 0, {0, 0}};
kernel_lb_addr eloc;
uint32_t elen;
uint64_t lbcount = 0;
int8_t etype = -1, netype;
int adsize;
if (UDF_I_ALLOCTYPE(inode) == ICBTAG_FLAG_AD_IN_ICB ||
inode->i_size == UDF_I_LENEXTENTS(inode))
return;
if (UDF_I_ALLOCTYPE(inode) == ICBTAG_FLAG_AD_SHORT)
adsize = sizeof(short_ad);
else if (UDF_I_ALLOCTYPE(inode) == ICBTAG_FLAG_AD_LONG)
adsize = sizeof(long_ad);
else
adsize = 0;
epos.block = UDF_I_LOCATION(inode);
/* Find the last extent in the file */
while ((netype = udf_next_aext(inode, &epos, &eloc, &elen, 1)) != -1) {
etype = netype;
lbcount += elen;
}
if (etype == (EXT_NOT_RECORDED_ALLOCATED >> 30)) { if (etype == (EXT_NOT_RECORDED_ALLOCATED >> 30)) {
epos.offset -= adsize; epos.offset -= adsize;
lbcount -= elen; lbcount -= elen;
extent_trunc(inode, &epos, eloc, etype, elen, 0); extent_trunc(inode, &epos, eloc, etype, elen, 0);
if (!epos.bh) if (!epos.bh) {
{
UDF_I_LENALLOC(inode) = epos.offset - udf_file_entry_alloc_offset(inode); UDF_I_LENALLOC(inode) = epos.offset - udf_file_entry_alloc_offset(inode);
mark_inode_dirty(inode); mark_inode_dirty(inode);
} } else {
else
{
struct allocExtDesc *aed = (struct allocExtDesc *)(epos.bh->b_data); struct allocExtDesc *aed = (struct allocExtDesc *)(epos.bh->b_data);
aed->lengthAllocDescs = cpu_to_le32(epos.offset - sizeof(struct allocExtDesc)); aed->lengthAllocDescs = cpu_to_le32(epos.offset - sizeof(struct allocExtDesc));
if (!UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_STRICT) || UDF_SB_UDFREV(inode->i_sb) >= 0x0201) if (!UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_STRICT) || UDF_SB_UDFREV(inode->i_sb) >= 0x0201)
...@@ -118,9 +165,9 @@ void udf_discard_prealloc(struct inode * inode) ...@@ -118,9 +165,9 @@ void udf_discard_prealloc(struct inode * inode)
mark_buffer_dirty_inode(epos.bh, inode); mark_buffer_dirty_inode(epos.bh, inode);
} }
} }
/* This inode entry is in-memory only and thus we don't have to mark
* the inode dirty */
UDF_I_LENEXTENTS(inode) = lbcount; UDF_I_LENEXTENTS(inode) = lbcount;
WARN_ON(lbcount != inode->i_size);
brelse(epos.bh); brelse(epos.bh);
} }
......
...@@ -146,6 +146,7 @@ extern void udf_free_inode(struct inode *); ...@@ -146,6 +146,7 @@ extern void udf_free_inode(struct inode *);
extern struct inode * udf_new_inode (struct inode *, int, int *); extern struct inode * udf_new_inode (struct inode *, int, int *);
/* truncate.c */ /* truncate.c */
extern void udf_truncate_tail_extent(struct inode *);
extern void udf_discard_prealloc(struct inode *); extern void udf_discard_prealloc(struct inode *);
extern void udf_truncate_extents(struct inode *); extern void udf_truncate_extents(struct inode *);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment