Skip to content

Commit a898cb6

Browse files
committed
quota: Detect loops in quota tree
Syzbot has found that when it creates corrupted quota files where the quota tree contains a loop, we will deadlock when tryling to insert a dquot. Add loop detection into functions traversing the quota tree. Signed-off-by: Jan Kara <jack@suse.cz>
1 parent ccb4901 commit a898cb6

File tree

2 files changed

+105
-38
lines changed

2 files changed

+105
-38
lines changed

fs/quota/quota_tree.c

Lines changed: 96 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ MODULE_AUTHOR("Jan Kara");
2121
MODULE_DESCRIPTION("Quota trie support");
2222
MODULE_LICENSE("GPL");
2323

24+
/*
25+
* Maximum quota tree depth we support. Only to limit recursion when working
26+
* with the tree.
27+
*/
28+
#define MAX_QTREE_DEPTH 6
29+
2430
#define __QUOTA_QT_PARANOIA
2531

2632
static int __get_index(struct qtree_mem_dqinfo *info, qid_t id, int depth)
@@ -327,27 +333,36 @@ static uint find_free_dqentry(struct qtree_mem_dqinfo *info,
327333

328334
/* Insert reference to structure into the trie */
329335
static int do_insert_tree(struct qtree_mem_dqinfo *info, struct dquot *dquot,
330-
uint *treeblk, int depth)
336+
uint *blks, int depth)
331337
{
332338
char *buf = kmalloc(info->dqi_usable_bs, GFP_KERNEL);
333339
int ret = 0, newson = 0, newact = 0;
334340
__le32 *ref;
335341
uint newblk;
342+
int i;
336343

337344
if (!buf)
338345
return -ENOMEM;
339-
if (!*treeblk) {
346+
if (!blks[depth]) {
340347
ret = get_free_dqblk(info);
341348
if (ret < 0)
342349
goto out_buf;
343-
*treeblk = ret;
350+
for (i = 0; i < depth; i++)
351+
if (ret == blks[i]) {
352+
quota_error(dquot->dq_sb,
353+
"Free block already used in tree: block %u",
354+
ret);
355+
ret = -EIO;
356+
goto out_buf;
357+
}
358+
blks[depth] = ret;
344359
memset(buf, 0, info->dqi_usable_bs);
345360
newact = 1;
346361
} else {
347-
ret = read_blk(info, *treeblk, buf);
362+
ret = read_blk(info, blks[depth], buf);
348363
if (ret < 0) {
349364
quota_error(dquot->dq_sb, "Can't read tree quota "
350-
"block %u", *treeblk);
365+
"block %u", blks[depth]);
351366
goto out_buf;
352367
}
353368
}
@@ -357,8 +372,20 @@ static int do_insert_tree(struct qtree_mem_dqinfo *info, struct dquot *dquot,
357372
info->dqi_blocks - 1);
358373
if (ret)
359374
goto out_buf;
360-
if (!newblk)
375+
if (!newblk) {
361376
newson = 1;
377+
} else {
378+
for (i = 0; i <= depth; i++)
379+
if (newblk == blks[i]) {
380+
quota_error(dquot->dq_sb,
381+
"Cycle in quota tree detected: block %u index %u",
382+
blks[depth],
383+
get_index(info, dquot->dq_id, depth));
384+
ret = -EIO;
385+
goto out_buf;
386+
}
387+
}
388+
blks[depth + 1] = newblk;
362389
if (depth == info->dqi_qtree_depth - 1) {
363390
#ifdef __QUOTA_QT_PARANOIA
364391
if (newblk) {
@@ -370,16 +397,16 @@ static int do_insert_tree(struct qtree_mem_dqinfo *info, struct dquot *dquot,
370397
goto out_buf;
371398
}
372399
#endif
373-
newblk = find_free_dqentry(info, dquot, &ret);
400+
blks[depth + 1] = find_free_dqentry(info, dquot, &ret);
374401
} else {
375-
ret = do_insert_tree(info, dquot, &newblk, depth+1);
402+
ret = do_insert_tree(info, dquot, blks, depth + 1);
376403
}
377404
if (newson && ret >= 0) {
378405
ref[get_index(info, dquot->dq_id, depth)] =
379-
cpu_to_le32(newblk);
380-
ret = write_blk(info, *treeblk, buf);
406+
cpu_to_le32(blks[depth + 1]);
407+
ret = write_blk(info, blks[depth], buf);
381408
} else if (newact && ret < 0) {
382-
put_free_dqblk(info, buf, *treeblk);
409+
put_free_dqblk(info, buf, blks[depth]);
383410
}
384411
out_buf:
385412
kfree(buf);
@@ -390,15 +417,19 @@ static int do_insert_tree(struct qtree_mem_dqinfo *info, struct dquot *dquot,
390417
static inline int dq_insert_tree(struct qtree_mem_dqinfo *info,
391418
struct dquot *dquot)
392419
{
393-
int tmp = QT_TREEOFF;
420+
uint blks[MAX_QTREE_DEPTH] = { QT_TREEOFF };
394421

395422
#ifdef __QUOTA_QT_PARANOIA
396423
if (info->dqi_blocks <= QT_TREEOFF) {
397424
quota_error(dquot->dq_sb, "Quota tree root isn't allocated!");
398425
return -EIO;
399426
}
400427
#endif
401-
return do_insert_tree(info, dquot, &tmp, 0);
428+
if (info->dqi_qtree_depth >= MAX_QTREE_DEPTH) {
429+
quota_error(dquot->dq_sb, "Quota tree depth too big!");
430+
return -EIO;
431+
}
432+
return do_insert_tree(info, dquot, blks, 0);
402433
}
403434

404435
/*
@@ -511,19 +542,20 @@ static int free_dqentry(struct qtree_mem_dqinfo *info, struct dquot *dquot,
511542

512543
/* Remove reference to dquot from tree */
513544
static int remove_tree(struct qtree_mem_dqinfo *info, struct dquot *dquot,
514-
uint *blk, int depth)
545+
uint *blks, int depth)
515546
{
516547
char *buf = kmalloc(info->dqi_usable_bs, GFP_KERNEL);
517548
int ret = 0;
518549
uint newblk;
519550
__le32 *ref = (__le32 *)buf;
551+
int i;
520552

521553
if (!buf)
522554
return -ENOMEM;
523-
ret = read_blk(info, *blk, buf);
555+
ret = read_blk(info, blks[depth], buf);
524556
if (ret < 0) {
525557
quota_error(dquot->dq_sb, "Can't read quota data block %u",
526-
*blk);
558+
blks[depth]);
527559
goto out_buf;
528560
}
529561
newblk = le32_to_cpu(ref[get_index(info, dquot->dq_id, depth)]);
@@ -532,29 +564,38 @@ static int remove_tree(struct qtree_mem_dqinfo *info, struct dquot *dquot,
532564
if (ret)
533565
goto out_buf;
534566

567+
for (i = 0; i <= depth; i++)
568+
if (newblk == blks[i]) {
569+
quota_error(dquot->dq_sb,
570+
"Cycle in quota tree detected: block %u index %u",
571+
blks[depth],
572+
get_index(info, dquot->dq_id, depth));
573+
ret = -EIO;
574+
goto out_buf;
575+
}
535576
if (depth == info->dqi_qtree_depth - 1) {
536577
ret = free_dqentry(info, dquot, newblk);
537-
newblk = 0;
578+
blks[depth + 1] = 0;
538579
} else {
539-
ret = remove_tree(info, dquot, &newblk, depth+1);
580+
blks[depth + 1] = newblk;
581+
ret = remove_tree(info, dquot, blks, depth + 1);
540582
}
541-
if (ret >= 0 && !newblk) {
542-
int i;
583+
if (ret >= 0 && !blks[depth + 1]) {
543584
ref[get_index(info, dquot->dq_id, depth)] = cpu_to_le32(0);
544585
/* Block got empty? */
545586
for (i = 0; i < (info->dqi_usable_bs >> 2) && !ref[i]; i++)
546587
;
547588
/* Don't put the root block into the free block list */
548589
if (i == (info->dqi_usable_bs >> 2)
549-
&& *blk != QT_TREEOFF) {
550-
put_free_dqblk(info, buf, *blk);
551-
*blk = 0;
590+
&& blks[depth] != QT_TREEOFF) {
591+
put_free_dqblk(info, buf, blks[depth]);
592+
blks[depth] = 0;
552593
} else {
553-
ret = write_blk(info, *blk, buf);
594+
ret = write_blk(info, blks[depth], buf);
554595
if (ret < 0)
555596
quota_error(dquot->dq_sb,
556597
"Can't write quota tree block %u",
557-
*blk);
598+
blks[depth]);
558599
}
559600
}
560601
out_buf:
@@ -565,11 +606,15 @@ static int remove_tree(struct qtree_mem_dqinfo *info, struct dquot *dquot,
565606
/* Delete dquot from tree */
566607
int qtree_delete_dquot(struct qtree_mem_dqinfo *info, struct dquot *dquot)
567608
{
568-
uint tmp = QT_TREEOFF;
609+
uint blks[MAX_QTREE_DEPTH] = { QT_TREEOFF };
569610

570611
if (!dquot->dq_off) /* Even not allocated? */
571612
return 0;
572-
return remove_tree(info, dquot, &tmp, 0);
613+
if (info->dqi_qtree_depth >= MAX_QTREE_DEPTH) {
614+
quota_error(dquot->dq_sb, "Quota tree depth too big!");
615+
return -EIO;
616+
}
617+
return remove_tree(info, dquot, blks, 0);
573618
}
574619
EXPORT_SYMBOL(qtree_delete_dquot);
575620

@@ -613,18 +658,20 @@ static loff_t find_block_dqentry(struct qtree_mem_dqinfo *info,
613658

614659
/* Find entry for given id in the tree */
615660
static loff_t find_tree_dqentry(struct qtree_mem_dqinfo *info,
616-
struct dquot *dquot, uint blk, int depth)
661+
struct dquot *dquot, uint *blks, int depth)
617662
{
618663
char *buf = kmalloc(info->dqi_usable_bs, GFP_KERNEL);
619664
loff_t ret = 0;
620665
__le32 *ref = (__le32 *)buf;
666+
uint blk;
667+
int i;
621668

622669
if (!buf)
623670
return -ENOMEM;
624-
ret = read_blk(info, blk, buf);
671+
ret = read_blk(info, blks[depth], buf);
625672
if (ret < 0) {
626673
quota_error(dquot->dq_sb, "Can't read quota tree block %u",
627-
blk);
674+
blks[depth]);
628675
goto out_buf;
629676
}
630677
ret = 0;
@@ -636,8 +683,19 @@ static loff_t find_tree_dqentry(struct qtree_mem_dqinfo *info,
636683
if (ret)
637684
goto out_buf;
638685

686+
/* Check for cycles in the tree */
687+
for (i = 0; i <= depth; i++)
688+
if (blk == blks[i]) {
689+
quota_error(dquot->dq_sb,
690+
"Cycle in quota tree detected: block %u index %u",
691+
blks[depth],
692+
get_index(info, dquot->dq_id, depth));
693+
ret = -EIO;
694+
goto out_buf;
695+
}
696+
blks[depth + 1] = blk;
639697
if (depth < info->dqi_qtree_depth - 1)
640-
ret = find_tree_dqentry(info, dquot, blk, depth+1);
698+
ret = find_tree_dqentry(info, dquot, blks, depth + 1);
641699
else
642700
ret = find_block_dqentry(info, dquot, blk);
643701
out_buf:
@@ -649,7 +707,13 @@ static loff_t find_tree_dqentry(struct qtree_mem_dqinfo *info,
649707
static inline loff_t find_dqentry(struct qtree_mem_dqinfo *info,
650708
struct dquot *dquot)
651709
{
652-
return find_tree_dqentry(info, dquot, QT_TREEOFF, 0);
710+
uint blks[MAX_QTREE_DEPTH] = { QT_TREEOFF };
711+
712+
if (info->dqi_qtree_depth >= MAX_QTREE_DEPTH) {
713+
quota_error(dquot->dq_sb, "Quota tree depth too big!");
714+
return -EIO;
715+
}
716+
return find_tree_dqentry(info, dquot, blks, 0);
653717
}
654718

655719
int qtree_read_dquot(struct qtree_mem_dqinfo *info, struct dquot *dquot)

fs/quota/quota_v2.c

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -168,14 +168,17 @@ static int v2_read_file_info(struct super_block *sb, int type)
168168
i_size_read(sb_dqopt(sb)->files[type]));
169169
goto out_free;
170170
}
171-
if (qinfo->dqi_free_blk >= qinfo->dqi_blocks) {
172-
quota_error(sb, "Free block number too big (%u >= %u).",
173-
qinfo->dqi_free_blk, qinfo->dqi_blocks);
171+
if (qinfo->dqi_free_blk && (qinfo->dqi_free_blk <= QT_TREEOFF ||
172+
qinfo->dqi_free_blk >= qinfo->dqi_blocks)) {
173+
quota_error(sb, "Free block number %u out of range (%u, %u).",
174+
qinfo->dqi_free_blk, QT_TREEOFF, qinfo->dqi_blocks);
174175
goto out_free;
175176
}
176-
if (qinfo->dqi_free_entry >= qinfo->dqi_blocks) {
177-
quota_error(sb, "Block with free entry too big (%u >= %u).",
178-
qinfo->dqi_free_entry, qinfo->dqi_blocks);
177+
if (qinfo->dqi_free_entry && (qinfo->dqi_free_entry <= QT_TREEOFF ||
178+
qinfo->dqi_free_entry >= qinfo->dqi_blocks)) {
179+
quota_error(sb, "Block with free entry %u out of range (%u, %u).",
180+
qinfo->dqi_free_entry, QT_TREEOFF,
181+
qinfo->dqi_blocks);
179182
goto out_free;
180183
}
181184
ret = 0;

0 commit comments

Comments
 (0)