ocfs2: fix UBSAN array-index-out-of-bounds in ocfs2_sum_rightmost_rec
PATCH v3 1/1 ocfs2: fix UBSAN array-index-out-of-bounds in ocfs2_sum_rightmost_rec
[BUG]
On-disk corruption setting l_next_free_rec to 0 in an inode's inline extent list triggers an UBSAN panic on the next write to that file.
[CAUSE]ocfs2_sum_rightmost_rec() computes
i = le16_to_cpu(el->l_next_free_rec) - 1
and accesses el->l_recs[i] without validating i. When l_next_free_rec is 0, i becomes -1; when l_next_free_rec exceeds l_count, i falls past the end of the array. Either case violates the __counted_by_le(l_count) annotation on l_recs[] and triggers UBSAN.
[FIX]
Validate the inode's inline extent list when the inode is read, in ocfs2_validate_inode_block(): l_count must be non-zero and no larger than the inode block can hold, and l_next_free_rec must not exceed l_count. A corrupt list is rejected at read time, before the b-tree code can index l_recs[] out of bounds.
---
Changes in v3:
- Update commit message to use "inline" instead of "embedded"
- CC [email protected]
v2: https://lore.kernel.org/all/aiLNd9gAuCC5u2jf@dev/
fs/ocfs2/inode.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+)
fs/ocfs2/inode.c +32 -0
@@ -1559,6 +1559,38 @@ int ocfs2_validate_inode_block(struct super_block *sb, goto bail; } +if (ocfs2_dinode_has_extents (di)) { +struct ocfs2_extent_list *el = &di->id2.i_list; +u16 count =le16_to_cpu (el->l_count); +u16 next_free =le16_to_cpu (el->l_next_free_rec); + +if (count ==0 ) { + rc =ocfs2_error (sb, +"Invalid dinode %llu: extent list l_count is zero\n" , + (unsigned long long )bh->b_blocknr); + goto bail; + } +/* +* The exact capacity depends on i_xattr_inline_size, another +* unvalidated on-disk field. Inline xattrs only shrink the +* list, so the no-xattr maximum is a safe upper bound that a +* valid l_count never exceeds. +*/ +if (count >ocfs2_extent_recs_per_inode (sb)) { + rc =ocfs2_error (sb, +"Invalid dinode %llu: extent list l_count %u exceeds max %u\n" , + (unsigned long long )bh->b_blocknr, count, +ocfs2_extent_recs_per_inode (sb)); + goto bail; + } +if (next_free > count) { + rc =ocfs2_error (sb, +"Invalid dinode %llu: extent list l_next_free_rec %u exceeds l_count %u\n" , + (unsigned long long )bh->b_blocknr, next_free, count); + goto bail; + } + } + rc =0 ; bail:
Signature · 1 line
2.47.3
Quoted 4 lines · Ian Bridges
[BUG]
On-disk corruption settingl_next_free_recto 0 in an inode's inline extent list triggers an UBSAN panic on the next write to that file.
I misread the recommendation on the previous patch review regarding "inline" vs "embedded". I will release a v4 with the correct terminology.
Ian
Quoted 75 lines
[CAUSE]
ocfs2_sum_rightmost_rec()computes
i =le16_to_cpu(el->l_next_free_rec)- 1
and accesses el->l_recs[i] without validating i. Whenl_next_free_recis 0, i becomes -1; whenl_next_free_recexceedsl_count, i falls past the end of the array. Either case violates the__counted_by_le(l_count)annotation on l_recs[] and triggers UBSAN.[FIX]
Validate the inode's inline extent list when the inode is read, inocfs2_validate_inode_block():l_countmust be non-zero and no larger than the inode block can hold, andl_next_free_recmust not exceedl_count. A corrupt list is rejected at read time, before the b-tree code can index l_recs[] out of bounds.Reported-by [email protected]Signed-off-by Ian Bridges <[email protected]>---
Changes in v3:
- Update commit message to use "inline" instead of "embedded"
- CC [email protected]v2: https://lore.kernel.org/all/aiLNd9gAuCC5u2jf@dev/
fs/ocfs2/inode.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+)fs/ocfs2/inode.c +32
-0@@ -1559,6 +1559,38 @@ int ocfs2_validate_inode_block(struct super_block *sb, goto bail; } +if (ocfs2_dinode_has_extents (di)) { +struct ocfs2_extent_list *el = &di->id2.i_list; +u16 count =le16_to_cpu (el->l_count); +u16 next_free =le16_to_cpu (el->l_next_free_rec); + +if (count ==0 ) { + rc =ocfs2_error (sb, +"Invalid dinode %llu: extent list l_count is zero\n" , + (unsigned long long )bh->b_blocknr); + goto bail; + } +/* +* The exact capacity depends on i_xattr_inline_size, another +* unvalidated on-disk field. Inline xattrs only shrink the +* list, so the no-xattr maximum is a safe upper bound that a +* valid l_count never exceeds. +*/ +if (count >ocfs2_extent_recs_per_inode (sb)) { + rc =ocfs2_error (sb, +"Invalid dinode %llu: extent list l_count %u exceeds max %u\n" , + (unsigned long long )bh->b_blocknr, count, +ocfs2_extent_recs_per_inode (sb)); + goto bail; + } +if (next_free > count) { + rc =ocfs2_error (sb, +"Invalid dinode %llu: extent list l_next_free_rec %u exceeds l_count %u\n" , + (unsigned long long )bh->b_blocknr, next_free, count); + goto bail; + } + } + rc =0 ; bail:Signature · 1 line
2.47.3