diff options
author | Jan Kara <jack@suse.cz> | 2007-06-16 13:16:14 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2007-06-16 16:16:16 -0400 |
commit | 74584ae509befc2ed711810e7df4b075473869b2 (patch) | |
tree | 9cb1b4a06b8583895c96d9b1a959d81df3950a06 /fs | |
parent | 4b356be019d0c28f67af02809df7072c1c8f7d32 (diff) |
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: Jan Kara <jack@suse.cz>
Cc: Eric Sandeen <sandeen@sandeen.net>
Cc: Cyrill Gorcunov <gorcunov@gmail.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'fs')
-rw-r--r-- | fs/udf/inode.c | 11 | ||||
-rw-r--r-- | fs/udf/truncate.c | 79 | ||||
-rw-r--r-- | fs/udf/udfdecl.h | 1 |
3 files changed, 74 insertions, 17 deletions
diff --git a/fs/udf/inode.c b/fs/udf/inode.c index 1f0129405cf4..bf7de0bdbab3 100644 --- a/fs/udf/inode.c +++ b/fs/udf/inode.c | |||
@@ -100,14 +100,23 @@ no_delete: | |||
100 | clear_inode(inode); | 100 | clear_inode(inode); |
101 | } | 101 | } |
102 | 102 | ||
103 | /* | ||
104 | * If we are going to release inode from memory, we discard preallocation and | ||
105 | * truncate last inode extent to proper length. We could use drop_inode() but | ||
106 | * it's called under inode_lock and thus we cannot mark inode dirty there. We | ||
107 | * use clear_inode() but we have to make sure to write inode as it's not written | ||
108 | * automatically. | ||
109 | */ | ||
103 | void udf_clear_inode(struct inode *inode) | 110 | void udf_clear_inode(struct inode *inode) |
104 | { | 111 | { |
105 | if (!(inode->i_sb->s_flags & MS_RDONLY)) { | 112 | if (!(inode->i_sb->s_flags & MS_RDONLY)) { |
106 | lock_kernel(); | 113 | lock_kernel(); |
114 | /* Discard preallocation for directories, symlinks, etc. */ | ||
107 | udf_discard_prealloc(inode); | 115 | udf_discard_prealloc(inode); |
116 | udf_truncate_tail_extent(inode); | ||
108 | unlock_kernel(); | 117 | unlock_kernel(); |
118 | write_inode_now(inode, 1); | ||
109 | } | 119 | } |
110 | |||
111 | kfree(UDF_I_DATA(inode)); | 120 | kfree(UDF_I_DATA(inode)); |
112 | UDF_I_DATA(inode) = NULL; | 121 | UDF_I_DATA(inode) = NULL; |
113 | } | 122 | } |
diff --git a/fs/udf/truncate.c b/fs/udf/truncate.c index 77975ae291a5..60d277644248 100644 --- a/fs/udf/truncate.c +++ b/fs/udf/truncate.c | |||
@@ -61,7 +61,11 @@ static void extent_trunc(struct inode * inode, struct extent_position *epos, | |||
61 | } | 61 | } |
62 | } | 62 | } |
63 | 63 | ||
64 | void udf_discard_prealloc(struct inode * inode) | 64 | /* |
65 | * Truncate the last extent to match i_size. This function assumes | ||
66 | * that preallocation extent is already truncated. | ||
67 | */ | ||
68 | void udf_truncate_tail_extent(struct inode *inode) | ||
65 | { | 69 | { |
66 | struct extent_position epos = { NULL, 0, {0, 0}}; | 70 | struct extent_position epos = { NULL, 0, {0, 0}}; |
67 | kernel_lb_addr eloc; | 71 | kernel_lb_addr eloc; |
@@ -71,7 +75,10 @@ void udf_discard_prealloc(struct inode * inode) | |||
71 | int adsize; | 75 | int adsize; |
72 | 76 | ||
73 | if (UDF_I_ALLOCTYPE(inode) == ICBTAG_FLAG_AD_IN_ICB || | 77 | if (UDF_I_ALLOCTYPE(inode) == ICBTAG_FLAG_AD_IN_ICB || |
74 | inode->i_size == UDF_I_LENEXTENTS(inode)) | 78 | inode->i_size == UDF_I_LENEXTENTS(inode)) |
79 | return; | ||
80 | /* Are we going to delete the file anyway? */ | ||
81 | if (inode->i_nlink == 0) | ||
75 | return; | 82 | return; |
76 | 83 | ||
77 | if (UDF_I_ALLOCTYPE(inode) == ICBTAG_FLAG_AD_SHORT) | 84 | if (UDF_I_ALLOCTYPE(inode) == ICBTAG_FLAG_AD_SHORT) |
@@ -79,36 +86,76 @@ void udf_discard_prealloc(struct inode * inode) | |||
79 | else if (UDF_I_ALLOCTYPE(inode) == ICBTAG_FLAG_AD_LONG) | 86 | else if (UDF_I_ALLOCTYPE(inode) == ICBTAG_FLAG_AD_LONG) |
80 | adsize = sizeof(long_ad); | 87 | adsize = sizeof(long_ad); |
81 | else | 88 | else |
82 | adsize = 0; | 89 | BUG(); |
83 | |||
84 | epos.block = UDF_I_LOCATION(inode); | ||
85 | 90 | ||
86 | /* Find the last extent in the file */ | 91 | /* Find the last extent in the file */ |
87 | while ((netype = udf_next_aext(inode, &epos, &eloc, &elen, 1)) != -1) | 92 | while ((netype = udf_next_aext(inode, &epos, &eloc, &elen, 1)) != -1) |
88 | { | 93 | { |
89 | etype = netype; | 94 | etype = netype; |
90 | lbcount += elen; | 95 | lbcount += elen; |
91 | if (lbcount > inode->i_size && lbcount - elen < inode->i_size) | 96 | if (lbcount > inode->i_size) { |
92 | { | 97 | if (lbcount - inode->i_size >= inode->i_sb->s_blocksize) |
93 | WARN_ON(lbcount - inode->i_size >= inode->i_sb->s_blocksize); | 98 | printk(KERN_WARNING |
99 | "udf_truncate_tail_extent(): Too long " | ||
100 | "extent after EOF in inode %u: i_size: " | ||
101 | "%Ld lbcount: %Ld extent %u+%u\n", | ||
102 | (unsigned)inode->i_ino, | ||
103 | (long long)inode->i_size, | ||
104 | (long long)lbcount, | ||
105 | (unsigned)eloc.logicalBlockNum, | ||
106 | (unsigned)elen); | ||
94 | nelen = elen - (lbcount - inode->i_size); | 107 | nelen = elen - (lbcount - inode->i_size); |
95 | epos.offset -= adsize; | 108 | epos.offset -= adsize; |
96 | extent_trunc(inode, &epos, eloc, etype, elen, nelen); | 109 | extent_trunc(inode, &epos, eloc, etype, elen, nelen); |
97 | epos.offset += adsize; | 110 | epos.offset += adsize; |
98 | lbcount = inode->i_size; | 111 | if (udf_next_aext(inode, &epos, &eloc, &elen, 1) != -1) |
112 | printk(KERN_ERR "udf_truncate_tail_extent(): " | ||
113 | "Extent after EOF in inode %u.\n", | ||
114 | (unsigned)inode->i_ino); | ||
115 | break; | ||
99 | } | 116 | } |
100 | } | 117 | } |
118 | /* This inode entry is in-memory only and thus we don't have to mark | ||
119 | * the inode dirty */ | ||
120 | UDF_I_LENEXTENTS(inode) = inode->i_size; | ||
121 | brelse(epos.bh); | ||
122 | } | ||
123 | |||
124 | void udf_discard_prealloc(struct inode *inode) | ||
125 | { | ||
126 | struct extent_position epos = { NULL, 0, {0, 0}}; | ||
127 | kernel_lb_addr eloc; | ||
128 | uint32_t elen; | ||
129 | uint64_t lbcount = 0; | ||
130 | int8_t etype = -1, netype; | ||
131 | int adsize; | ||
132 | |||
133 | if (UDF_I_ALLOCTYPE(inode) == ICBTAG_FLAG_AD_IN_ICB || | ||
134 | inode->i_size == UDF_I_LENEXTENTS(inode)) | ||
135 | return; | ||
136 | |||
137 | if (UDF_I_ALLOCTYPE(inode) == ICBTAG_FLAG_AD_SHORT) | ||
138 | adsize = sizeof(short_ad); | ||
139 | else if (UDF_I_ALLOCTYPE(inode) == ICBTAG_FLAG_AD_LONG) | ||
140 | adsize = sizeof(long_ad); | ||
141 | else | ||
142 | adsize = 0; | ||
143 | |||
144 | epos.block = UDF_I_LOCATION(inode); | ||
145 | |||
146 | /* Find the last extent in the file */ | ||
147 | while ((netype = udf_next_aext(inode, &epos, &eloc, &elen, 1)) != -1) { | ||
148 | etype = netype; | ||
149 | lbcount += elen; | ||
150 | } | ||
101 | if (etype == (EXT_NOT_RECORDED_ALLOCATED >> 30)) { | 151 | if (etype == (EXT_NOT_RECORDED_ALLOCATED >> 30)) { |
102 | epos.offset -= adsize; | 152 | epos.offset -= adsize; |
103 | lbcount -= elen; | 153 | lbcount -= elen; |
104 | extent_trunc(inode, &epos, eloc, etype, elen, 0); | 154 | extent_trunc(inode, &epos, eloc, etype, elen, 0); |
105 | if (!epos.bh) | 155 | if (!epos.bh) { |
106 | { | ||
107 | UDF_I_LENALLOC(inode) = epos.offset - udf_file_entry_alloc_offset(inode); | 156 | UDF_I_LENALLOC(inode) = epos.offset - udf_file_entry_alloc_offset(inode); |
108 | mark_inode_dirty(inode); | 157 | mark_inode_dirty(inode); |
109 | } | 158 | } else { |
110 | else | ||
111 | { | ||
112 | struct allocExtDesc *aed = (struct allocExtDesc *)(epos.bh->b_data); | 159 | struct allocExtDesc *aed = (struct allocExtDesc *)(epos.bh->b_data); |
113 | aed->lengthAllocDescs = cpu_to_le32(epos.offset - sizeof(struct allocExtDesc)); | 160 | aed->lengthAllocDescs = cpu_to_le32(epos.offset - sizeof(struct allocExtDesc)); |
114 | if (!UDF_QUERY_FLAG(inode->i_sb, UDF_FLAG_STRICT) || UDF_SB_UDFREV(inode->i_sb) >= 0x0201) | 161 | 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 | mark_buffer_dirty_inode(epos.bh, inode); | 165 | mark_buffer_dirty_inode(epos.bh, inode); |
119 | } | 166 | } |
120 | } | 167 | } |
168 | /* This inode entry is in-memory only and thus we don't have to mark | ||
169 | * the inode dirty */ | ||
121 | UDF_I_LENEXTENTS(inode) = lbcount; | 170 | UDF_I_LENEXTENTS(inode) = lbcount; |
122 | |||
123 | WARN_ON(lbcount != inode->i_size); | ||
124 | brelse(epos.bh); | 171 | brelse(epos.bh); |
125 | } | 172 | } |
126 | 173 | ||
diff --git a/fs/udf/udfdecl.h b/fs/udf/udfdecl.h index 67ded289497c..f581f2f69c0f 100644 --- a/fs/udf/udfdecl.h +++ b/fs/udf/udfdecl.h | |||
@@ -146,6 +146,7 @@ extern void udf_free_inode(struct inode *); | |||
146 | extern struct inode * udf_new_inode (struct inode *, int, int *); | 146 | extern struct inode * udf_new_inode (struct inode *, int, int *); |
147 | 147 | ||
148 | /* truncate.c */ | 148 | /* truncate.c */ |
149 | extern void udf_truncate_tail_extent(struct inode *); | ||
149 | extern void udf_discard_prealloc(struct inode *); | 150 | extern void udf_discard_prealloc(struct inode *); |
150 | extern void udf_truncate_extents(struct inode *); | 151 | extern void udf_truncate_extents(struct inode *); |
151 | 152 | ||