linux/fs/udf/truncate.c
Jan Kara 00a2b0f6dd Fix possible UDF deadlock and memory corruption (CVE-2006-4145)
UDF code is not really ready to handle extents larger that 1GB. This is
the easy way to forbid creating those.

Also truncation code did not count with the case when there are no
extents in the file and we are extending the file.

Signed-off-by: Jan Kara <jack@suse.cz>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
2006-08-22 12:52:23 -07:00

293 lines
8 KiB
C

/*
* truncate.c
*
* PURPOSE
* Truncate handling routines for the OSTA-UDF(tm) filesystem.
*
* COPYRIGHT
* This file is distributed under the terms of the GNU General Public
* License (GPL). Copies of the GPL can be obtained from:
* ftp://prep.ai.mit.edu/pub/gnu/GPL
* Each contributing author retains all rights to their own work.
*
* (C) 1999-2004 Ben Fennema
* (C) 1999 Stelias Computing Inc
*
* HISTORY
*
* 02/24/99 blf Created.
*
*/
#include "udfdecl.h"
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/udf_fs.h>
#include <linux/buffer_head.h>
#include "udf_i.h"
#include "udf_sb.h"
static void extent_trunc(struct inode * inode, kernel_lb_addr bloc, int extoffset,
kernel_lb_addr eloc, int8_t etype, uint32_t elen, struct buffer_head *bh, uint32_t nelen)
{
kernel_lb_addr neloc = { 0, 0 };
int last_block = (elen + inode->i_sb->s_blocksize - 1) >> inode->i_sb->s_blocksize_bits;
int first_block = (nelen + inode->i_sb->s_blocksize - 1) >> inode->i_sb->s_blocksize_bits;
if (nelen)
{
if (etype == (EXT_NOT_RECORDED_ALLOCATED >> 30))
{
udf_free_blocks(inode->i_sb, inode, eloc, 0, last_block);
etype = (EXT_NOT_RECORDED_NOT_ALLOCATED >> 30);
}
else
neloc = eloc;
nelen = (etype << 30) | nelen;
}
if (elen != nelen)
{
udf_write_aext(inode, bloc, &extoffset, neloc, nelen, bh, 0);
if (last_block - first_block > 0)
{
if (etype == (EXT_RECORDED_ALLOCATED >> 30))
mark_inode_dirty(inode);
if (etype != (EXT_NOT_RECORDED_NOT_ALLOCATED >> 30))
udf_free_blocks(inode->i_sb, inode, eloc, first_block, last_block - first_block);
}
}
}
void udf_discard_prealloc(struct inode * inode)
{
kernel_lb_addr bloc, eloc;
uint32_t extoffset = 0, elen, nelen;
uint64_t lbcount = 0;
int8_t etype = -1, netype;
struct buffer_head *bh = NULL;
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;
bloc = UDF_I_LOCATION(inode);
while ((netype = udf_next_aext(inode, &bloc, &extoffset, &eloc, &elen, &bh, 1)) != -1)
{
etype = netype;
lbcount += elen;
if (lbcount > inode->i_size && lbcount - inode->i_size < inode->i_sb->s_blocksize)
{
nelen = elen - (lbcount - inode->i_size);
extent_trunc(inode, bloc, extoffset-adsize, eloc, etype, elen, bh, nelen);
lbcount = inode->i_size;
}
}
if (etype == (EXT_NOT_RECORDED_ALLOCATED >> 30))
{
extoffset -= adsize;
lbcount -= elen;
extent_trunc(inode, bloc, extoffset, eloc, etype, elen, bh, 0);
if (!bh)
{
UDF_I_LENALLOC(inode) = extoffset - udf_file_entry_alloc_offset(inode);
mark_inode_dirty(inode);
}
else
{
struct allocExtDesc *aed = (struct allocExtDesc *)(bh->b_data);
aed->lengthAllocDescs = cpu_to_le32(extoffset - sizeof(struct allocExtDesc));
if (!UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_STRICT) || UDF_SB_UDFREV(inode->i_sb) >= 0x0201)
udf_update_tag(bh->b_data, extoffset);
else
udf_update_tag(bh->b_data, sizeof(struct allocExtDesc));
mark_buffer_dirty_inode(bh, inode);
}
}
UDF_I_LENEXTENTS(inode) = lbcount;
udf_release_data(bh);
}
void udf_truncate_extents(struct inode * inode)
{
kernel_lb_addr bloc, eloc, neloc = { 0, 0 };
uint32_t extoffset, elen, offset, nelen = 0, lelen = 0, lenalloc;
int8_t etype;
int first_block = inode->i_size >> inode->i_sb->s_blocksize_bits;
struct buffer_head *bh = NULL;
int adsize;
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;
etype = inode_bmap(inode, first_block, &bloc, &extoffset, &eloc, &elen, &offset, &bh);
offset += (inode->i_size & (inode->i_sb->s_blocksize - 1));
if (etype != -1)
{
extoffset -= adsize;
extent_trunc(inode, bloc, extoffset, eloc, etype, elen, bh, offset);
extoffset += adsize;
if (offset)
lenalloc = extoffset;
else
lenalloc = extoffset - adsize;
if (!bh)
lenalloc -= udf_file_entry_alloc_offset(inode);
else
lenalloc -= sizeof(struct allocExtDesc);
while ((etype = udf_current_aext(inode, &bloc, &extoffset, &eloc, &elen, &bh, 0)) != -1)
{
if (etype == (EXT_NEXT_EXTENT_ALLOCDECS >> 30))
{
udf_write_aext(inode, bloc, &extoffset, neloc, nelen, bh, 0);
extoffset = 0;
if (lelen)
{
if (!bh)
BUG();
else
memset(bh->b_data, 0x00, sizeof(struct allocExtDesc));
udf_free_blocks(inode->i_sb, inode, bloc, 0, lelen);
}
else
{
if (!bh)
{
UDF_I_LENALLOC(inode) = lenalloc;
mark_inode_dirty(inode);
}
else
{
struct allocExtDesc *aed = (struct allocExtDesc *)(bh->b_data);
aed->lengthAllocDescs = cpu_to_le32(lenalloc);
if (!UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_STRICT) || UDF_SB_UDFREV(inode->i_sb) >= 0x0201)
udf_update_tag(bh->b_data, lenalloc +
sizeof(struct allocExtDesc));
else
udf_update_tag(bh->b_data, sizeof(struct allocExtDesc));
mark_buffer_dirty_inode(bh, inode);
}
}
udf_release_data(bh);
extoffset = sizeof(struct allocExtDesc);
bloc = eloc;
bh = udf_tread(inode->i_sb, udf_get_lb_pblock(inode->i_sb, bloc, 0));
if (elen)
lelen = (elen + inode->i_sb->s_blocksize - 1) >>
inode->i_sb->s_blocksize_bits;
else
lelen = 1;
}
else
{
extent_trunc(inode, bloc, extoffset, eloc, etype, elen, bh, 0);
extoffset += adsize;
}
}
if (lelen)
{
if (!bh)
BUG();
else
memset(bh->b_data, 0x00, sizeof(struct allocExtDesc));
udf_free_blocks(inode->i_sb, inode, bloc, 0, lelen);
}
else
{
if (!bh)
{
UDF_I_LENALLOC(inode) = lenalloc;
mark_inode_dirty(inode);
}
else
{
struct allocExtDesc *aed = (struct allocExtDesc *)(bh->b_data);
aed->lengthAllocDescs = cpu_to_le32(lenalloc);
if (!UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_STRICT) || UDF_SB_UDFREV(inode->i_sb) >= 0x0201)
udf_update_tag(bh->b_data, lenalloc +
sizeof(struct allocExtDesc));
else
udf_update_tag(bh->b_data, sizeof(struct allocExtDesc));
mark_buffer_dirty_inode(bh, inode);
}
}
}
else if (inode->i_size)
{
if (offset)
{
/*
* OK, there is not extent covering inode->i_size and
* no extent above inode->i_size => truncate is
* extending the file by 'offset'.
*/
if ((!bh && extoffset == udf_file_entry_alloc_offset(inode)) ||
(bh && extoffset == sizeof(struct allocExtDesc))) {
/* File has no extents at all! */
memset(&eloc, 0x00, sizeof(kernel_lb_addr));
elen = EXT_NOT_RECORDED_NOT_ALLOCATED | offset;
udf_add_aext(inode, &bloc, &extoffset, eloc, elen, &bh, 1);
}
else {
extoffset -= adsize;
etype = udf_next_aext(inode, &bloc, &extoffset, &eloc, &elen, &bh, 1);
if (etype == (EXT_NOT_RECORDED_NOT_ALLOCATED >> 30))
{
extoffset -= adsize;
elen = EXT_NOT_RECORDED_NOT_ALLOCATED | (elen + offset);
udf_write_aext(inode, bloc, &extoffset, eloc, elen, bh, 0);
}
else if (etype == (EXT_NOT_RECORDED_ALLOCATED >> 30))
{
kernel_lb_addr neloc = { 0, 0 };
extoffset -= adsize;
nelen = EXT_NOT_RECORDED_NOT_ALLOCATED |
((elen + offset + inode->i_sb->s_blocksize - 1) &
~(inode->i_sb->s_blocksize - 1));
udf_write_aext(inode, bloc, &extoffset, neloc, nelen, bh, 1);
udf_add_aext(inode, &bloc, &extoffset, eloc, (etype << 30) | elen, &bh, 1);
}
else
{
if (elen & (inode->i_sb->s_blocksize - 1))
{
extoffset -= adsize;
elen = EXT_RECORDED_ALLOCATED |
((elen + inode->i_sb->s_blocksize - 1) &
~(inode->i_sb->s_blocksize - 1));
udf_write_aext(inode, bloc, &extoffset, eloc, elen, bh, 1);
}
memset(&eloc, 0x00, sizeof(kernel_lb_addr));
elen = EXT_NOT_RECORDED_NOT_ALLOCATED | offset;
udf_add_aext(inode, &bloc, &extoffset, eloc, elen, &bh, 1);
}
}
}
}
UDF_I_LENEXTENTS(inode) = inode->i_size;
udf_release_data(bh);
}