1 From d0141191a20289f8955c1e03dad08e42e6f71ca9 Mon Sep 17 00:00:00 2001
2 From: Jan Kara <jack@suse.cz>
3 Date: Thu, 11 Aug 2016 11:50:30 -0400
4 Subject: [PATCH] ext4: fix xattr shifting when expanding inodes
6 The code in ext4_expand_extra_isize_ea() treated new_extra_isize
7 argument sometimes as the desired target i_extra_isize and sometimes as
8 the amount by which we need to grow current i_extra_isize. These happen
9 to coincide when i_extra_isize is 0 which used to be the common case and
10 so nobody noticed this until recently when we added i_projid to the
11 inode and so i_extra_isize now needs to grow from 28 to 32 bytes.
13 The result of these bugs was that we sometimes unnecessarily decided to
14 move xattrs out of inode even if there was enough space and we often
15 ended up corrupting in-inode xattrs because arguments to
16 ext4_xattr_shift_entries() were just wrong. This could demonstrate
17 itself as BUG_ON in ext4_xattr_shift_entries() triggering.
19 Fix the problem by introducing new isize_diff variable and use it where
22 CC: stable@vger.kernel.org # 4.4.x
23 Reported-by: Dave Chinner <david@fromorbit.com>
24 Signed-off-by: Jan Kara <jack@suse.cz>
25 Signed-off-by: Theodore Ts'o <tytso@mit.edu>
27 fs/ext4/xattr.c | 27 ++++++++++++++-------------
28 1 file changed, 14 insertions(+), 13 deletions(-)
30 diff --git a/fs/ext4/xattr.c b/fs/ext4/xattr.c
31 index 39e9cfb..cb1d7b4 100644
34 @@ -1353,15 +1353,17 @@ int ext4_expand_extra_isize_ea(struct inode *inode, int new_extra_isize,
35 size_t min_offs, free;
37 void *base, *start, *end;
38 - int extra_isize = 0, error = 0, tried_min_extra_isize = 0;
39 + int error = 0, tried_min_extra_isize = 0;
40 int s_min_extra_isize = le16_to_cpu(EXT4_SB(inode->i_sb)->s_es->s_min_extra_isize);
41 + int isize_diff; /* How much do we need to grow i_extra_isize */
43 down_write(&EXT4_I(inode)->xattr_sem);
45 * Set EXT4_STATE_NO_EXPAND to avoid recursion when marking inode dirty
47 ext4_set_inode_state(inode, EXT4_STATE_NO_EXPAND);
49 + isize_diff = new_extra_isize - EXT4_I(inode)->i_extra_isize;
50 if (EXT4_I(inode)->i_extra_isize >= new_extra_isize)
53 @@ -1382,7 +1384,7 @@ int ext4_expand_extra_isize_ea(struct inode *inode, int new_extra_isize,
56 free = ext4_xattr_free_space(last, &min_offs, base, &total_ino);
57 - if (free >= new_extra_isize) {
58 + if (free >= isize_diff) {
59 entry = IFIRST(header);
60 ext4_xattr_shift_entries(entry, EXT4_I(inode)->i_extra_isize
61 - new_extra_isize, (void *)raw_inode +
62 @@ -1414,7 +1416,7 @@ int ext4_expand_extra_isize_ea(struct inode *inode, int new_extra_isize,
63 end = bh->b_data + bh->b_size;
64 min_offs = end - base;
65 free = ext4_xattr_free_space(first, &min_offs, base, NULL);
66 - if (free < new_extra_isize) {
67 + if (free < isize_diff) {
68 if (!tried_min_extra_isize && s_min_extra_isize) {
69 tried_min_extra_isize++;
70 new_extra_isize = s_min_extra_isize;
71 @@ -1428,7 +1430,7 @@ int ext4_expand_extra_isize_ea(struct inode *inode, int new_extra_isize,
72 free = inode->i_sb->s_blocksize;
75 - while (new_extra_isize > 0) {
76 + while (isize_diff > 0) {
77 size_t offs, size, entry_size;
78 struct ext4_xattr_entry *small_entry = NULL;
79 struct ext4_xattr_info i = {
80 @@ -1459,7 +1461,7 @@ int ext4_expand_extra_isize_ea(struct inode *inode, int new_extra_isize,
81 EXT4_XATTR_SIZE(le32_to_cpu(last->e_value_size)) +
82 EXT4_XATTR_LEN(last->e_name_len);
83 if (total_size <= free && total_size < min_total_size) {
84 - if (total_size < new_extra_isize) {
85 + if (total_size < isize_diff) {
89 @@ -1516,20 +1518,19 @@ int ext4_expand_extra_isize_ea(struct inode *inode, int new_extra_isize,
92 entry = IFIRST(header);
93 - if (entry_size + EXT4_XATTR_SIZE(size) >= new_extra_isize)
94 - shift_bytes = new_extra_isize;
95 + if (entry_size + EXT4_XATTR_SIZE(size) >= isize_diff)
96 + shift_bytes = isize_diff;
98 shift_bytes = entry_size + size;
99 /* Adjust the offsets and shift the remaining entries ahead */
100 - ext4_xattr_shift_entries(entry, EXT4_I(inode)->i_extra_isize -
101 - shift_bytes, (void *)raw_inode +
102 - EXT4_GOOD_OLD_INODE_SIZE + extra_isize + shift_bytes,
103 + ext4_xattr_shift_entries(entry, -shift_bytes,
104 + (void *)raw_inode + EXT4_GOOD_OLD_INODE_SIZE +
105 + EXT4_I(inode)->i_extra_isize + shift_bytes,
106 (void *)header, total_ino - entry_size,
107 inode->i_sb->s_blocksize);
109 - extra_isize += shift_bytes;
110 - new_extra_isize -= shift_bytes;
111 - EXT4_I(inode)->i_extra_isize = extra_isize;
112 + isize_diff -= shift_bytes;
113 + EXT4_I(inode)->i_extra_isize += shift_bytes;
115 i.name = b_entry_name;
120 From 418c12d08dc64a45107c467ec1ba29b5e69b0715 Mon Sep 17 00:00:00 2001
121 From: Jan Kara <jack@suse.cz>
122 Date: Thu, 11 Aug 2016 11:58:32 -0400
123 Subject: [PATCH] ext4: fix xattr shifting when expanding inodes part 2
125 When multiple xattrs need to be moved out of inode, we did not properly
126 recompute total size of xattr headers in the inode and the new header
127 position. Thus when moving the second and further xattr we asked
128 ext4_xattr_shift_entries() to move too much and from the wrong place,
129 resulting in possible xattr value corruption or general memory
132 CC: stable@vger.kernel.org # 4.4.x
133 Signed-off-by: Jan Kara <jack@suse.cz>
134 Signed-off-by: Theodore Ts'o <tytso@mit.edu>
136 fs/ext4/xattr.c | 5 +++--
137 1 file changed, 3 insertions(+), 2 deletions(-)
139 diff --git a/fs/ext4/xattr.c b/fs/ext4/xattr.c
140 index cb1d7b4..b18b1ff 100644
141 --- a/fs/ext4/xattr.c
142 +++ b/fs/ext4/xattr.c
143 @@ -1516,6 +1516,7 @@ int ext4_expand_extra_isize_ea(struct inode *inode, int new_extra_isize,
144 error = ext4_xattr_ibody_set(handle, inode, &i, is);
147 + total_ino -= entry_size;
149 entry = IFIRST(header);
150 if (entry_size + EXT4_XATTR_SIZE(size) >= isize_diff)
151 @@ -1526,11 +1527,11 @@ int ext4_expand_extra_isize_ea(struct inode *inode, int new_extra_isize,
152 ext4_xattr_shift_entries(entry, -shift_bytes,
153 (void *)raw_inode + EXT4_GOOD_OLD_INODE_SIZE +
154 EXT4_I(inode)->i_extra_isize + shift_bytes,
155 - (void *)header, total_ino - entry_size,
156 - inode->i_sb->s_blocksize);
157 + (void *)header, total_ino, inode->i_sb->s_blocksize);
159 isize_diff -= shift_bytes;
160 EXT4_I(inode)->i_extra_isize += shift_bytes;
161 + header = IHDR(inode, raw_inode);
163 i.name = b_entry_name;
168 From 443a8c41cd49de66a3fda45b32b9860ea0292b84 Mon Sep 17 00:00:00 2001
169 From: Jan Kara <jack@suse.cz>
170 Date: Thu, 11 Aug 2016 12:00:01 -0400
171 Subject: [PATCH] ext4: properly align shifted xattrs when expanding inodes
173 We did not count with the padding of xattr value when computing desired
174 shift of xattrs in the inode when expanding i_extra_isize. As a result
175 we could create unaligned start of inline xattrs. Account for alignment
178 CC: stable@vger.kernel.org # 4.4.x-
179 Signed-off-by: Jan Kara <jack@suse.cz>
181 fs/ext4/xattr.c | 2 +-
182 1 file changed, 1 insertion(+), 1 deletion(-)
184 diff --git a/fs/ext4/xattr.c b/fs/ext4/xattr.c
185 index b18b1ff..c893f00 100644
186 --- a/fs/ext4/xattr.c
187 +++ b/fs/ext4/xattr.c
188 @@ -1522,7 +1522,7 @@ int ext4_expand_extra_isize_ea(struct inode *inode, int new_extra_isize,
189 if (entry_size + EXT4_XATTR_SIZE(size) >= isize_diff)
190 shift_bytes = isize_diff;
192 - shift_bytes = entry_size + size;
193 + shift_bytes = entry_size + EXT4_XATTR_SIZE(size);
194 /* Adjust the offsets and shift the remaining entries ahead */
195 ext4_xattr_shift_entries(entry, -shift_bytes,
196 (void *)raw_inode + EXT4_GOOD_OLD_INODE_SIZE +
200 From e3014d14a81edde488d9a6758eea8afc41752d2d Mon Sep 17 00:00:00 2001
201 From: Jan Kara <jack@suse.cz>
202 Date: Mon, 29 Aug 2016 15:38:11 -0400
203 Subject: [PATCH] ext4: fixup free space calculations when expanding inodes
205 Conditions checking whether there is enough free space in an xattr block
206 and when xattr is large enough to make enough space in the inode forgot
207 to account for the fact that inode need not be completely filled up with
208 xattrs. Thus we could move unnecessarily many xattrs out of inode or
209 even falsely claim there is not enough space to expand the inode. We
210 also forgot to update the amount of free space in xattr block when moving
211 more xattrs and thus could decide to move too big xattr resulting in
214 Fix these problems by properly updating free space in the inode and
215 xattr block as we move xattrs. To simplify the math, avoid shifting
216 xattrs after removing each one xattr and instead just shift xattrs only
217 once there is enough free space in the inode.
219 Signed-off-by: Jan Kara <jack@suse.cz>
220 Signed-off-by: Theodore Ts'o <tytso@mit.edu>
222 fs/ext4/xattr.c | 58 ++++++++++++++++++++++++---------------------------------
223 1 file changed, 24 insertions(+), 34 deletions(-)
225 diff --git a/fs/ext4/xattr.c b/fs/ext4/xattr.c
226 index 2eb935c..22d2ebc 100644
227 --- a/fs/ext4/xattr.c
228 +++ b/fs/ext4/xattr.c
229 @@ -1350,7 +1350,8 @@ int ext4_expand_extra_isize_ea(struct inode *inode, int new_extra_isize,
230 struct ext4_xattr_ibody_find *is = NULL;
231 struct ext4_xattr_block_find *bs = NULL;
232 char *buffer = NULL, *b_entry_name = NULL;
233 - size_t min_offs, free;
235 + size_t ifree, bfree;
237 void *base, *start, *end;
238 int error = 0, tried_min_extra_isize = 0;
239 @@ -1385,17 +1386,9 @@ int ext4_expand_extra_isize_ea(struct inode *inode, int new_extra_isize,
243 - free = ext4_xattr_free_space(last, &min_offs, base, &total_ino);
244 - if (free >= isize_diff) {
245 - entry = IFIRST(header);
246 - ext4_xattr_shift_entries(entry, EXT4_I(inode)->i_extra_isize
247 - - new_extra_isize, (void *)raw_inode +
248 - EXT4_GOOD_OLD_INODE_SIZE + new_extra_isize,
249 - (void *)header, total_ino,
250 - inode->i_sb->s_blocksize);
251 - EXT4_I(inode)->i_extra_isize = new_extra_isize;
254 + ifree = ext4_xattr_free_space(last, &min_offs, base, &total_ino);
255 + if (ifree >= isize_diff)
259 * Enough free space isn't available in the inode, check if
260 @@ -1416,8 +1409,8 @@ int ext4_expand_extra_isize_ea(struct inode *inode, int new_extra_isize,
262 end = bh->b_data + bh->b_size;
263 min_offs = end - base;
264 - free = ext4_xattr_free_space(first, &min_offs, base, NULL);
265 - if (free < isize_diff) {
266 + bfree = ext4_xattr_free_space(first, &min_offs, base, NULL);
267 + if (bfree + ifree < isize_diff) {
268 if (!tried_min_extra_isize && s_min_extra_isize) {
269 tried_min_extra_isize++;
270 new_extra_isize = s_min_extra_isize;
271 @@ -1428,10 +1421,10 @@ int ext4_expand_extra_isize_ea(struct inode *inode, int new_extra_isize,
275 - free = inode->i_sb->s_blocksize;
276 + bfree = inode->i_sb->s_blocksize;
279 - while (isize_diff > 0) {
280 + while (isize_diff > ifree) {
281 size_t offs, size, entry_size;
282 struct ext4_xattr_entry *small_entry = NULL;
283 struct ext4_xattr_info i = {
284 @@ -1439,7 +1432,6 @@ int ext4_expand_extra_isize_ea(struct inode *inode, int new_extra_isize,
287 unsigned int total_size; /* EA entry size + value size */
288 - unsigned int shift_bytes; /* No. of bytes to shift EAs by? */
289 unsigned int min_total_size = ~0U;
291 is = kzalloc(sizeof(struct ext4_xattr_ibody_find), GFP_NOFS);
292 @@ -1461,8 +1453,9 @@ int ext4_expand_extra_isize_ea(struct inode *inode, int new_extra_isize,
294 EXT4_XATTR_SIZE(le32_to_cpu(last->e_value_size)) +
295 EXT4_XATTR_LEN(last->e_name_len);
296 - if (total_size <= free && total_size < min_total_size) {
297 - if (total_size < isize_diff) {
298 + if (total_size <= bfree &&
299 + total_size < min_total_size) {
300 + if (total_size + ifree < isize_diff) {
304 @@ -1491,6 +1484,7 @@ int ext4_expand_extra_isize_ea(struct inode *inode, int new_extra_isize,
305 offs = le16_to_cpu(entry->e_value_offs);
306 size = le32_to_cpu(entry->e_value_size);
307 entry_size = EXT4_XATTR_LEN(entry->e_name_len);
308 + total_size = entry_size + EXT4_XATTR_SIZE(size);
309 i.name_index = entry->e_name_index,
310 buffer = kmalloc(EXT4_XATTR_SIZE(size), GFP_NOFS);
311 b_entry_name = kmalloc(entry->e_name_len + 1, GFP_NOFS);
312 @@ -1518,21 +1512,8 @@ int ext4_expand_extra_isize_ea(struct inode *inode, int new_extra_isize,
315 total_ino -= entry_size;
317 - entry = IFIRST(header);
318 - if (entry_size + EXT4_XATTR_SIZE(size) >= isize_diff)
319 - shift_bytes = isize_diff;
321 - shift_bytes = entry_size + EXT4_XATTR_SIZE(size);
322 - /* Adjust the offsets and shift the remaining entries ahead */
323 - ext4_xattr_shift_entries(entry, -shift_bytes,
324 - (void *)raw_inode + EXT4_GOOD_OLD_INODE_SIZE +
325 - EXT4_I(inode)->i_extra_isize + shift_bytes,
326 - (void *)header, total_ino, inode->i_sb->s_blocksize);
328 - isize_diff -= shift_bytes;
329 - EXT4_I(inode)->i_extra_isize += shift_bytes;
330 - header = IHDR(inode, raw_inode);
331 + ifree += total_size;
332 + bfree -= total_size;
334 i.name = b_entry_name;
336 @@ -1553,6 +1534,15 @@ int ext4_expand_extra_isize_ea(struct inode *inode, int new_extra_isize,
342 + /* Adjust the offsets and shift the remaining entries ahead */
343 + entry = IFIRST(header);
344 + ext4_xattr_shift_entries(entry, EXT4_I(inode)->i_extra_isize
345 + - new_extra_isize, (void *)raw_inode +
346 + EXT4_GOOD_OLD_INODE_SIZE + new_extra_isize,
347 + (void *)header, total_ino, inode->i_sb->s_blocksize);
348 + EXT4_I(inode)->i_extra_isize = new_extra_isize;
351 ext4_clear_inode_state(inode, EXT4_STATE_NO_EXPAND);
355 From 94405713889d4a9d341b4ad92956e4e2ec8ec2c2 Mon Sep 17 00:00:00 2001
356 From: Jan Kara <jack@suse.cz>
357 Date: Mon, 29 Aug 2016 15:41:11 -0400
358 Subject: [PATCH] ext4: replace bogus assertion in ext4_xattr_shift_entries()
360 We were checking whether computed offsets do not exceed end of block in
361 ext4_xattr_shift_entries(). However this does not make sense since we
362 always only decrease offsets. So replace that assertion with a check
363 whether we really decrease xattrs value offsets.
365 Signed-off-by: Jan Kara <jack@suse.cz>
366 Signed-off-by: Theodore Ts'o <tytso@mit.edu>
368 fs/ext4/xattr.c | 9 +++++----
369 1 file changed, 5 insertions(+), 4 deletions(-)
371 diff --git a/fs/ext4/xattr.c b/fs/ext4/xattr.c
372 index 1447860..82b025c 100644
373 --- a/fs/ext4/xattr.c
374 +++ b/fs/ext4/xattr.c
375 @@ -1319,18 +1319,19 @@ ext4_xattr_set(struct inode *inode, int name_index, const char *name,
377 static void ext4_xattr_shift_entries(struct ext4_xattr_entry *entry,
378 int value_offs_shift, void *to,
379 - void *from, size_t n, int blocksize)
380 + void *from, size_t n)
382 struct ext4_xattr_entry *last = entry;
385 + /* We always shift xattr headers further thus offsets get lower */
386 + BUG_ON(value_offs_shift > 0);
388 /* Adjust the value offsets of the entries */
389 for (; !IS_LAST_ENTRY(last); last = EXT4_XATTR_NEXT(last)) {
390 if (!last->e_value_inum && last->e_value_size) {
391 new_offs = le16_to_cpu(last->e_value_offs) +
393 - BUG_ON(new_offs + le32_to_cpu(last->e_value_size)
395 last->e_value_offs = cpu_to_le16(new_offs);
398 @@ -1542,7 +1543,7 @@ int ext4_expand_extra_isize_ea(struct inode *inode, int new_extra_isize,
399 ext4_xattr_shift_entries(entry, EXT4_I(inode)->i_extra_isize
400 - new_extra_isize, (void *)raw_inode +
401 EXT4_GOOD_OLD_INODE_SIZE + new_extra_isize,
402 - (void *)header, total_ino, inode->i_sb->s_blocksize);
403 + (void *)header, total_ino);
404 EXT4_I(inode)->i_extra_isize = new_extra_isize;
410 From 887a9730614727c4fff7cb756711b190593fc1df Mon Sep 17 00:00:00 2001
411 From: Konstantin Khlebnikov <khlebnikov@yandex-team.ru>
412 Date: Sun, 21 May 2017 22:36:23 -0400
413 Subject: [PATCH] ext4: keep existing extra fields when inode expands
415 ext4_expand_extra_isize() should clear only space between old and new
418 Fixes: 6dd4ee7cab7e # v2.6.23
419 Cc: stable@vger.kernel.org
420 Signed-off-by: Konstantin Khlebnikov <khlebnikov@yandex-team.ru>
421 Signed-off-by: Theodore Ts'o <tytso@mit.edu>
423 fs/ext4/inode.c | 5 +++--
424 1 file changed, 3 insertions(+), 2 deletions(-)
426 diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
427 index 1bd0bfa..7cd99de 100644
428 --- a/fs/ext4/inode.c
429 +++ b/fs/ext4/inode.c
430 @@ -5637,8 +5637,9 @@ static int ext4_expand_extra_isize(struct inode *inode,
431 /* No extended attributes present */
432 if (!ext4_test_inode_state(inode, EXT4_STATE_XATTR) ||
433 header->h_magic != cpu_to_le32(EXT4_XATTR_MAGIC)) {
434 - memset((void *)raw_inode + EXT4_GOOD_OLD_INODE_SIZE, 0,
436 + memset((void *)raw_inode + EXT4_GOOD_OLD_INODE_SIZE +
437 + EXT4_I(inode)->i_extra_isize, 0,
438 + new_extra_isize - EXT4_I(inode)->i_extra_isize);
439 EXT4_I(inode)->i_extra_isize = new_extra_isize;