Skip to content

Commit 4d99814

Browse files
committed
Merge tag 'hfs-v7.1-tag1' of git://git.kernel.org/pub/scm/linux/kernel/git/vdubeyko/hfs
Pull hfsplus updates from Viacheslav Dubeyko: "This contains several fixes of syzbot reported issues and HFS+ fixes of xfstests failures. - Fix a syzbot reported issue of a KMSAN uninit-value in hfsplus_strcasecmp(). The root cause was that hfs_brec_read() doesn't validate that the on-disk record size matches the expected size for the record type being read. The fix introduced hfsplus_brec_read_cat() wrapper that validates the record size based on the type field and returns -EIO if size doesn't match (Deepanshu Kartikey) - Fix a syzbot reported issue of processing corrupted HFS+ images where the b-tree allocation bitmap indicates that the header node (Node 0) is free. Node 0 must always be allocated. Violating this invariant leads to allocator corruption, which cascades into kernel panics or undefined behavior. Prevent trusting a corrupted allocator state by adding a validation check during hfs_btree_open(). If corruption is detected, print a warning identifying the specific corrupted tree and force the filesystem to mount read-only (SB_RDONLY). This prevents kernel panics from corrupted images while enabling data recovery (Shardul Bankar) - Fix a potential deadlock in hfsplus_fill_super(). hfsplus_fill_super() calls hfs_find_init() to initialize a search structure, which acquires tree->tree_lock. If the subsequent call to hfsplus_cat_build_key() fails, the function jumps to the out_put_root error label without releasing the lock. Fix this by adding the missing hfs_find_exit(&fd) call before jumping to the out_put_root error label. This ensures that tree->tree_lock is properly released on the error path (Zilin Guan) - Update a files ctime after rename in hfsplus_rename() (Yangtao Li) The rest of the patches introduce the HFS+ fixes for the case of generic/348, generic/728, generic/533, generic/523, and generic/642 test-cases of xfstests suite" * tag 'hfs-v7.1-tag1' of git://git.kernel.org/pub/scm/linux/kernel/git/vdubeyko/hfs: hfsplus: fix generic/642 failure hfsplus: rework logic of map nodes creation in xattr b-tree hfsplus: fix logic of alloc/free b-tree node hfsplus: fix error processing issue in hfs_bmap_free() hfsplus: fix potential race conditions in b-tree functionality hfsplus: extract hidden directory search into a helper function hfsplus: fix held lock freed on hfsplus_fill_super() hfsplus: fix generic/523 test-case failure hfsplus: validate b-tree node 0 bitmap at mount time hfsplus: refactor b-tree map page access and add node-type validation hfsplus: fix to update ctime after rename hfsplus: fix generic/533 test-case failure hfsplus: set ctime after setxattr and removexattr hfsplus: fix uninit-value by validating catalog record size hfsplus: fix potential Allocation File corruption after fsync
2 parents f3756af + c1307d1 commit 4d99814

15 files changed

Lines changed: 682 additions & 182 deletions

File tree

fs/hfsplus/attributes.c

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ int hfsplus_attr_build_key(struct super_block *sb, hfsplus_btree_key *key,
5757
if (name) {
5858
int res = hfsplus_asc2uni(sb,
5959
(struct hfsplus_unistr *)&key->attr.key_name,
60-
HFSPLUS_ATTR_MAX_STRLEN, name, strlen(name));
60+
HFSPLUS_ATTR_MAX_STRLEN, name, strlen(name),
61+
HFS_XATTR_NAME);
6162
if (res)
6263
return res;
6364
len = be16_to_cpu(key->attr.key_name.length);
@@ -153,14 +154,22 @@ int hfsplus_find_attr(struct super_block *sb, u32 cnid,
153154
if (err)
154155
goto failed_find_attr;
155156
err = hfs_brec_find(fd, hfs_find_rec_by_key);
156-
if (err)
157+
if (err == -ENOENT) {
158+
/* file exists but xattr is absent */
159+
err = -ENODATA;
160+
goto failed_find_attr;
161+
} else if (err)
157162
goto failed_find_attr;
158163
} else {
159164
err = hfsplus_attr_build_key(sb, fd->search_key, cnid, NULL);
160165
if (err)
161166
goto failed_find_attr;
162167
err = hfs_brec_find(fd, hfs_find_1st_rec_by_cnid);
163-
if (err)
168+
if (err == -ENOENT) {
169+
/* file exists but xattr is absent */
170+
err = -ENODATA;
171+
goto failed_find_attr;
172+
} else if (err)
164173
goto failed_find_attr;
165174
}
166175

@@ -174,6 +183,9 @@ int hfsplus_attr_exists(struct inode *inode, const char *name)
174183
struct super_block *sb = inode->i_sb;
175184
struct hfs_find_data fd;
176185

186+
hfs_dbg("name %s, ino %llu\n",
187+
name ? name : NULL, inode->i_ino);
188+
177189
if (!HFSPLUS_SB(sb)->attr_tree)
178190
return 0;
179191

@@ -241,6 +253,7 @@ int hfsplus_create_attr_nolock(struct inode *inode, const char *name,
241253
return err;
242254
}
243255

256+
hfsplus_mark_inode_dirty(HFSPLUS_ATTR_TREE_I(sb), HFSPLUS_I_ATTR_DIRTY);
244257
hfsplus_mark_inode_dirty(inode, HFSPLUS_I_ATTR_DIRTY);
245258

246259
return 0;
@@ -292,15 +305,16 @@ int hfsplus_create_attr(struct inode *inode,
292305
static int __hfsplus_delete_attr(struct inode *inode, u32 cnid,
293306
struct hfs_find_data *fd)
294307
{
295-
int err = 0;
308+
int err;
296309
__be32 found_cnid, record_type;
297310

311+
found_cnid = U32_MAX;
298312
hfs_bnode_read(fd->bnode, &found_cnid,
299313
fd->keyoffset +
300314
offsetof(struct hfsplus_attr_key, cnid),
301315
sizeof(__be32));
302316
if (cnid != be32_to_cpu(found_cnid))
303-
return -ENOENT;
317+
return -ENODATA;
304318

305319
hfs_bnode_read(fd->bnode, &record_type,
306320
fd->entryoffset, sizeof(record_type));
@@ -326,8 +340,10 @@ static int __hfsplus_delete_attr(struct inode *inode, u32 cnid,
326340
if (err)
327341
return err;
328342

343+
hfsplus_mark_inode_dirty(HFSPLUS_ATTR_TREE_I(inode->i_sb),
344+
HFSPLUS_I_ATTR_DIRTY);
329345
hfsplus_mark_inode_dirty(inode, HFSPLUS_I_ATTR_DIRTY);
330-
return err;
346+
return 0;
331347
}
332348

333349
static
@@ -351,7 +367,10 @@ int hfsplus_delete_attr_nolock(struct inode *inode, const char *name,
351367
}
352368

353369
err = hfs_brec_find(fd, hfs_find_rec_by_key);
354-
if (err)
370+
if (err == -ENOENT) {
371+
/* file exists but xattr is absent */
372+
return -ENODATA;
373+
} else if (err)
355374
return err;
356375

357376
err = __hfsplus_delete_attr(inode, inode->i_ino, fd);
@@ -411,9 +430,14 @@ int hfsplus_delete_all_attrs(struct inode *dir, u32 cnid)
411430

412431
for (;;) {
413432
err = hfsplus_find_attr(dir->i_sb, cnid, NULL, &fd);
414-
if (err) {
415-
if (err != -ENOENT)
416-
pr_err("xattr search failed\n");
433+
if (err == -ENOENT || err == -ENODATA) {
434+
/*
435+
* xattr has not been found
436+
*/
437+
err = -ENODATA;
438+
goto end_delete_all;
439+
} else if (err) {
440+
pr_err("xattr search failed\n");
417441
goto end_delete_all;
418442
}
419443

fs/hfsplus/bfind.c

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,3 +287,54 @@ int hfs_brec_goto(struct hfs_find_data *fd, int cnt)
287287
fd->bnode = bnode;
288288
return res;
289289
}
290+
291+
/**
292+
* hfsplus_brec_read_cat - read and validate a catalog record
293+
* @fd: find data structure
294+
* @entry: pointer to catalog entry to read into
295+
*
296+
* Reads a catalog record and validates its size matches the expected
297+
* size based on the record type.
298+
*
299+
* Returns 0 on success, or negative error code on failure.
300+
*/
301+
int hfsplus_brec_read_cat(struct hfs_find_data *fd, hfsplus_cat_entry *entry)
302+
{
303+
int res;
304+
u32 expected_size;
305+
306+
res = hfs_brec_read(fd, entry, sizeof(hfsplus_cat_entry));
307+
if (res)
308+
return res;
309+
310+
/* Validate catalog record size based on type */
311+
switch (be16_to_cpu(entry->type)) {
312+
case HFSPLUS_FOLDER:
313+
expected_size = sizeof(struct hfsplus_cat_folder);
314+
break;
315+
case HFSPLUS_FILE:
316+
expected_size = sizeof(struct hfsplus_cat_file);
317+
break;
318+
case HFSPLUS_FOLDER_THREAD:
319+
case HFSPLUS_FILE_THREAD:
320+
/* Ensure we have at least the fixed fields before reading nodeName.length */
321+
if (fd->entrylength < HFSPLUS_MIN_THREAD_SZ) {
322+
pr_err("thread record too short (got %u)\n", fd->entrylength);
323+
return -EIO;
324+
}
325+
expected_size = hfsplus_cat_thread_size(&entry->thread);
326+
break;
327+
default:
328+
pr_err("unknown catalog record type %d\n",
329+
be16_to_cpu(entry->type));
330+
return -EIO;
331+
}
332+
333+
if (fd->entrylength != expected_size) {
334+
pr_err("catalog record size mismatch (type %d, got %u, expected %u)\n",
335+
be16_to_cpu(entry->type), fd->entrylength, expected_size);
336+
return -EIO;
337+
}
338+
339+
return 0;
340+
}

fs/hfsplus/bnode.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,10 @@ void hfs_bnode_unlink(struct hfs_bnode *node)
420420
tree->root = 0;
421421
tree->depth = 0;
422422
}
423+
424+
spin_lock(&tree->hash_lock);
423425
set_bit(HFS_BNODE_DELETED, &node->flags);
426+
spin_unlock(&tree->hash_lock);
424427
}
425428

426429
static inline int hfs_bnode_hash(u32 num)

fs/hfsplus/brec.c

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,9 @@ static struct hfs_bnode *hfs_bnode_split(struct hfs_find_data *fd)
239239
struct hfs_bnode_desc node_desc;
240240
int num_recs, new_rec_off, new_off, old_rec_off;
241241
int data_start, data_end, size;
242+
size_t rec_off_tbl_size;
243+
size_t node_desc_size = sizeof(struct hfs_bnode_desc);
244+
size_t rec_size = sizeof(__be16);
242245

243246
tree = fd->tree;
244247
node = fd->bnode;
@@ -265,18 +268,22 @@ static struct hfs_bnode *hfs_bnode_split(struct hfs_find_data *fd)
265268
return next_node;
266269
}
267270

268-
size = tree->node_size / 2 - node->num_recs * 2 - 14;
269-
old_rec_off = tree->node_size - 4;
271+
rec_off_tbl_size = node->num_recs * rec_size;
272+
size = tree->node_size / 2;
273+
size -= node_desc_size;
274+
size -= rec_off_tbl_size;
275+
old_rec_off = tree->node_size - (2 * rec_size);
276+
270277
num_recs = 1;
271278
for (;;) {
272279
data_start = hfs_bnode_read_u16(node, old_rec_off);
273280
if (data_start > size)
274281
break;
275-
old_rec_off -= 2;
282+
old_rec_off -= rec_size;
276283
if (++num_recs < node->num_recs)
277284
continue;
278-
/* panic? */
279285
hfs_bnode_put(node);
286+
hfs_bnode_unlink(new_node);
280287
hfs_bnode_put(new_node);
281288
if (next_node)
282289
hfs_bnode_put(next_node);
@@ -287,35 +294,36 @@ static struct hfs_bnode *hfs_bnode_split(struct hfs_find_data *fd)
287294
/* new record is in the lower half,
288295
* so leave some more space there
289296
*/
290-
old_rec_off += 2;
297+
old_rec_off += rec_size;
291298
num_recs--;
292299
data_start = hfs_bnode_read_u16(node, old_rec_off);
293300
} else {
294301
hfs_bnode_put(node);
295302
hfs_bnode_get(new_node);
296303
fd->bnode = new_node;
297304
fd->record -= num_recs;
298-
fd->keyoffset -= data_start - 14;
299-
fd->entryoffset -= data_start - 14;
305+
fd->keyoffset -= data_start - node_desc_size;
306+
fd->entryoffset -= data_start - node_desc_size;
300307
}
301308
new_node->num_recs = node->num_recs - num_recs;
302309
node->num_recs = num_recs;
303310

304-
new_rec_off = tree->node_size - 2;
305-
new_off = 14;
311+
new_rec_off = tree->node_size - rec_size;
312+
new_off = node_desc_size;
306313
size = data_start - new_off;
307314
num_recs = new_node->num_recs;
308315
data_end = data_start;
309316
while (num_recs) {
310317
hfs_bnode_write_u16(new_node, new_rec_off, new_off);
311-
old_rec_off -= 2;
312-
new_rec_off -= 2;
318+
old_rec_off -= rec_size;
319+
new_rec_off -= rec_size;
313320
data_end = hfs_bnode_read_u16(node, old_rec_off);
314321
new_off = data_end - size;
315322
num_recs--;
316323
}
317324
hfs_bnode_write_u16(new_node, new_rec_off, new_off);
318-
hfs_bnode_copy(new_node, 14, node, data_start, data_end - data_start);
325+
hfs_bnode_copy(new_node, node_desc_size,
326+
node, data_start, data_end - data_start);
319327

320328
/* update new bnode header */
321329
node_desc.next = cpu_to_be32(new_node->next);

0 commit comments

Comments
 (0)