Re: [PATCH] udf: avoid recursive s_alloc_mutex deadlock when freeing AED blocks
From: sashiko-bot
Date: Sun Jun 21 2026 - 00:09:26 EST
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] udf_table_prealloc_blocks() and udf_table_new_block() ignore the return value of udf_delete_aext(), leading to double-allocation on error.
- [High] udf_delete_aext() leaks two buffer_head references on early return.
--
commit b35154de447cbd2ab36c556abd7be97822502a4b
Author: Deepanshu Kartikey <kartikey406@xxxxxxxxx>
udf: avoid recursive s_alloc_mutex deadlock when freeing AED blocks
This commit fixes a recursive deadlock on s_alloc_mutex by modifying
udf_delete_aext() to return the freed block via an out-parameter.
Callers can then free the block after dropping the mutex.
Link: https://syzkaller.appspot.com/bug?extid=6a680377e13041c19d50
> diff --git a/fs/udf/balloc.c b/fs/udf/balloc.c
> index cc6dc6e1d84de..30cec56001491 100644
> --- a/fs/udf/balloc.c
> +++ b/fs/udf/balloc.c
[ ... ]
> @@ -541,7 +543,7 @@ static int udf_table_prealloc_blocks(struct super_block *sb,
> udf_write_aext(table, &epos, &eloc,
> (etype << 30) | elen, 1);
> } else
> - udf_delete_aext(table, epos);
> + udf_delete_aext(table, epos, &freed);
[Severity: High]
This is a pre-existing issue, but does ignoring the return value of
udf_delete_aext() here risk a double-allocation?
If udf_delete_aext() returns -1 due to an error, the block remains in
the unallocated space table. Since udf_table_prealloc_blocks() proceeds
as if the deletion succeeded, could a subsequent allocation find and
allocate the exact same block?
[ ... ]
> @@ -643,12 +649,14 @@ static udf_pblk_t udf_table_new_block(struct super_block *sb,
> if (goal_elen)
> udf_write_aext(table, &goal_epos, &goal_eloc, goal_elen, 1);
> else
> - udf_delete_aext(table, goal_epos);
> + udf_delete_aext(table, goal_epos, &freed);
[Severity: High]
This is a pre-existing issue, but the same return value behavior happens
here in udf_table_new_block() as well. Does this code need to handle
the case where udf_delete_aext() returns an error to avoid corruption?
> diff --git a/fs/udf/inode.c b/fs/udf/inode.c
> index 67bcf83758c8f..2c8647e0c9131 100644
> --- a/fs/udf/inode.c
> +++ b/fs/udf/inode.c
[ ... ]
> @@ -2328,7 +2328,8 @@ static int udf_insert_aext(struct inode *inode, struct extent_position epos,
> return ret;
> }
>
> -int8_t udf_delete_aext(struct inode *inode, struct extent_position epos)
> +int8_t udf_delete_aext(struct inode *inode, struct extent_position epos,
> + struct kernel_lb_addr *freed)
> {
> struct extent_position oepos;
> int adsize;
[Severity: High]
This is a pre-existing issue, but does the early return path in this
function leak buffer_head references?
At the start of udf_delete_aext(), two references to epos.bh are acquired:
if (epos.bh) {
get_bh(epos.bh);
get_bh(epos.bh);
}
However, if the initial extent traversal fails shortly after:
if (udf_next_aext(inode, &epos, &eloc, &elen, &etype, 1) <= 0)
return -1;
This early return path does not appear to call brelse() to release the
acquired references, which could pin the buffer in memory and prevent
unmounting.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260621035841.56194-1-kartikey406@xxxxxxxxx?part=1