Skip to content

Commit 8ea17a0

Browse files
committed
menu/ozone: clamp sidebar ticker field_width, root-cause UBSan trips
UBSan-instrumented run reported, on the same arithmetic at two sites in ozone_draw_sidebar(): menu/drivers/ozone.c:3658:41: runtime error: -20.4167 is outside the range of representable values of type 'unsigned int' menu/drivers/ozone.c:3806:41: runtime error: -20.4167 is outside the range of representable values of type 'unsigned int' Two pre-existing TODO/FIXME comments in this file already noted the same trip (with a -12.549 sample) but punted on the fix. Same situation also lurks in the sibling non-smooth-ticker branches, which UBSan didn't happen to catch only because ticker.len is size_t rather than unsigned and the smooth path is the default. Where the float comes from -------------------------- ozone->dimensions_sidebar_width is a *float* "animated field" (declared so at line 578) that tweens between 0 / collapsed / normal sidebar widths via the gfx_animation system. It's initialised to 0.0f at startup (line 9324), so on the very first frame after init -- and during every collapse/expand animation -- it holds a small or fractional value. Through line 3568, entry_width = (unsigned)dimensions_sidebar_width - sidebar_padding_horizontal * 2; so entry_width is integer but tracks the float. Then both ticker computations evaluate entry_width - sidebar_entry_icon_size - 40 * scale_factor scale_factor is float (last_scale_factor at line 580), so 40 * scale_factor is float, so the whole sum is float. When entry_width is small (mid-animation, or sidebar collapsed) the sum lands below zero -- UBSan saw -20.4167 (40 * 1.5 - the ~icon_size offset against an entry_width near zero). The implicit float-to-unsigned (or float-to-size_t) conversion of a negative is UB per C11 6.3.1.4 p1. Modern compilers wrap to ~UINT_MAX/SIZE_MAX, which the ticker then treats as "plenty of room", drawing too much text for one or two animation frames until dimensions_sidebar_width settles. Visually mostly invisible; UBSan-fatally noisy. Fix --- Compute the available width in signed int, with the float intruder explicitly cast through int avail_width = entry_width - icon_size - (int)(40.0f * scale_factor); and clamp to zero before assignment to the unsigned/size_t field: ticker_field_width = avail_width > 0 ? (unsigned)avail_width : 0; This produces a defined zero-width when there's no room (the correct semantic -- the ticker has nothing to draw and skips rather than scribbling at a wrap-around offset). Apply identically at both sites (system tab loop and horizontal-list loop) and use the same local in both the smooth and non-smooth branches, which removes the duplicated arithmetic and applies the same clamp to ticker.len's previously-buggy non-smooth branch. The two TODO/FIXME comments are removed since they're now fixed rather than known-broken. Also adds a glyph_width != 0 guard around the non-smooth divide. glyph_width is populated during font init and is normally non- zero by the time draw_sidebar runs, but a font that failed to load would leave it at 0 and the existing code would divide by zero -- belt-and-suspenders since we're touching the line anyway. Address-of-root-cause vs whack-a-mole ------------------------------------- The deeper root cause is dimensions_sidebar_width being a float that flows into integer pixel math, rather than these specific sites. Normalising the animated width to an int field would require restructuring how the gfx_animation tween writes back (its subject is a float* by API), so it's not a drive-by fix. For now, every site that mixes scale_factor into integer pixel arithmetic is a candidate UBSan trip; this commit handles the two reported plus their immediate non-smooth counterparts. Future trips will need similar local clamps.
1 parent b3618d6 commit 8ea17a0

1 file changed

Lines changed: 52 additions & 34 deletions

File tree

menu/drivers/ozone.c

Lines changed: 52 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3643,6 +3643,24 @@ static void ozone_draw_sidebar(
36433643
enum msg_hash_enums value_idx = ozone_system_tabs_value[ozone->tabs[i]];
36443644
const char *title = msg_hash_to_str(value_idx);
36453645
uint32_t text_color = 0;
3646+
/* Available pixel width for the ticker. scale_factor
3647+
* promotes the right operand to float, and entry_width
3648+
* may legitimately be small or zero (sidebar collapsed
3649+
* or animating in -- dimensions_sidebar_width is a float
3650+
* tween initialised to 0.0f), so the float sum can land
3651+
* below zero. Implicit float-to-unsigned conversion of
3652+
* a negative is UB (C11 6.3.1.4 p1) -- UBSan flagged
3653+
* the previous integer-typed expression here as e.g.
3654+
* '-20.4167 outside the range of unsigned int'. Compute
3655+
* in signed int (no float intruders) and clamp at zero;
3656+
* a non-positive width means there's no room to draw
3657+
* anything, which the ticker treats as 'skip' rather
3658+
* than scribbling at the wrap-around offset. */
3659+
int avail_width = entry_width
3660+
- ozone->dimensions.sidebar_entry_icon_size
3661+
- (int)(40.0f * scale_factor);
3662+
unsigned ticker_field_width = avail_width > 0
3663+
? (unsigned)avail_width : 0;
36463664
if (ozone->theme)
36473665
text_color = selected
36483666
? COLOR_TEXT_ALPHA(ozone->theme->text_selected_rgba, text_alpha)
@@ -3651,13 +3669,7 @@ static void ozone_draw_sidebar(
36513669
if (use_smooth_ticker)
36523670
{
36533671
ticker_smooth.selected = selected;
3654-
/* TODO/FIXME - undefined behavior reported by ASAN -
3655-
*-12.549 is outside the range of representable values
3656-
of type 'unsigned int'
3657-
* */
3658-
ticker_smooth.field_width = (entry_width
3659-
- ozone->dimensions.sidebar_entry_icon_size
3660-
- 40 * scale_factor);
3672+
ticker_smooth.field_width = ticker_field_width;
36613673
ticker_smooth.src_str = title;
36623674
ticker_smooth.dst_str = console_title;
36633675
ticker_smooth.dst_str_len = sizeof(console_title);
@@ -3666,10 +3678,9 @@ static void ozone_draw_sidebar(
36663678
}
36673679
else
36683680
{
3669-
ticker.len = (entry_width
3670-
- ozone->dimensions.sidebar_entry_icon_size
3671-
- 40 * scale_factor)
3672-
/ ozone->fonts.sidebar.glyph_width;
3681+
ticker.len = ozone->fonts.sidebar.glyph_width
3682+
? ticker_field_width / ozone->fonts.sidebar.glyph_width
3683+
: 0;
36733684
ticker.s = console_title;
36743685
ticker.selected = selected;
36753686
ticker.str = title;
@@ -3796,33 +3807,40 @@ static void ozone_draw_sidebar(
37963807
if (ozone->sidebar_collapsed)
37973808
goto console_iterate;
37983809

3799-
if (use_smooth_ticker)
3810+
/* See matching computation earlier in this function for
3811+
* the rationale; entry_width can be small or zero
3812+
* (sidebar_width is a float tween, init 0.0f) and the
3813+
* float `40 * scale_factor` term promotes the sum to
3814+
* float, so the result can land below zero and the
3815+
* implicit conversion to unsigned/size_t is UB. */
38003816
{
3801-
ticker_smooth.selected = selected;
3802-
/* TODO/FIXME - undefined behavior reported by ASAN -
3803-
*-12.549 is outside the range of representable values
3804-
of type 'unsigned int'
3805-
* */
3806-
ticker_smooth.field_width = (entry_width
3817+
int avail_width = entry_width
38073818
- ozone->dimensions.sidebar_entry_icon_size
3808-
- 40 * scale_factor);
3809-
ticker_smooth.src_str = node->console_name;
3810-
ticker_smooth.dst_str = console_title;
3811-
ticker_smooth.dst_str_len = sizeof(console_title);
3819+
- (int)(40.0f * scale_factor);
3820+
unsigned ticker_field_width = avail_width > 0
3821+
? (unsigned)avail_width : 0;
38123822

3813-
gfx_animation_ticker_smooth(&ticker_smooth);
3814-
}
3815-
else
3816-
{
3817-
ticker.len = (entry_width
3818-
- ozone->dimensions.sidebar_entry_icon_size
3819-
- 40 * scale_factor)
3820-
/ ozone->fonts.sidebar.glyph_width;
3821-
ticker.s = console_title;
3822-
ticker.selected = selected;
3823-
ticker.str = node->console_name;
3823+
if (use_smooth_ticker)
3824+
{
3825+
ticker_smooth.selected = selected;
3826+
ticker_smooth.field_width = ticker_field_width;
3827+
ticker_smooth.src_str = node->console_name;
3828+
ticker_smooth.dst_str = console_title;
3829+
ticker_smooth.dst_str_len = sizeof(console_title);
38243830

3825-
gfx_animation_ticker(&ticker);
3831+
gfx_animation_ticker_smooth(&ticker_smooth);
3832+
}
3833+
else
3834+
{
3835+
ticker.len = ozone->fonts.sidebar.glyph_width
3836+
? ticker_field_width / ozone->fonts.sidebar.glyph_width
3837+
: 0;
3838+
ticker.s = console_title;
3839+
ticker.selected = selected;
3840+
ticker.str = node->console_name;
3841+
3842+
gfx_animation_ticker(&ticker);
3843+
}
38263844
}
38273845

38283846
gfx_display_draw_text(

0 commit comments

Comments
 (0)