c1860bfe0f359ef83374829ca4d39eef1e64504c Fix float-cast-overflow UBSAN issues in media_query_evaluator.cc
Triage note: Float-cast-overflow UB in CSS media-query evaluation, reachable from web content. Sanitizer-class UB with no confirmed corruption, consistent with the d7d097 UB precedent. watch.
The bug at a glance
This is a pure undefined-behavior hardening fix with no demonstrated security impact on shipping builds. The pre-fix code cast a floating-point viewport width/height into a plain int; when the source floating value exceeds INT_MAX (or is below INT_MIN, inf, or NaN), the float->int conversion is undefined behavior in C++ and is flagged by UBSAN’s float-cast-overflow check. On a normal optimizing compiler the result is an implementation-defined garbage integer, not a memory-safety fault: the ints are only compared (width > height) inside a media-query orientation evaluation, so the worst realistic consequence is a wrong orientation answer for an absurd viewport, or a UBSAN abort in a sanitizer build. There is no out-of-bounds access, no allocation sized by these values, and no attacker-controlled write. It is honestly watch-tier: real UB removed from a web-reachable path, but with essentially no exploitation potential. cvss is left null because the vendor-supplied 5.0 overstates a sanitizer-only correctness fix; there is no credible confidentiality/integrity/availability impact on release channels to score.
Media-query evaluation runs on every page for every matchMedia() / @media orientation query and consumes viewport metrics that can be pushed to extreme magnitudes via zoom, device-pixel-ratio, iframe sizing, or emulated/print media values. The angle is a well-known C++ footgun: implicitly narrowing a float/double viewport dimension to int at the point of comparison. It is the kind of latent UB that fuzzers and UBSAN builds surface constantly and that occasionally underpins a more serious bug when the truncated value later indexes or sizes something – here it does not, so it is a clean defensive fix rather than a vuln.
Root cause
OrientationMediaFeatureEval() decides portrait vs landscape by comparing viewport width and height. media_values.Width() and Height() return floating-point viewport dimensions (optional). The old code dereferenced them and forced the result into ‘int width’ / ‘int height’. For ordinary viewports this is harmless, but the values are ultimately derived from attacker-influenceable inputs (CSS/zoom/DPR/emulation) and can be made to exceed the representable range of int. A float/double value >= 2^31 (or inf/NaN) converted to int is undefined behavior per the C++ standard; UBSAN’s float-cast-overflow instrumentation traps it. The fix simply changes the locals to ‘double width’ / ‘double height’, keeping the values in their native floating-point domain so no lossy, potentially-overflowing narrowing occurs. The subsequent comparison (width > height for portrait/landscape) works identically for all sane viewports and is now well-defined for extreme ones.
Key code
Keep viewport dimensions as double instead of narrowing to int, eliminating the float-cast-overflow UB.
static bool OrientationMediaFeatureEval(const MediaQueryExpValue& value,
MediaQueryOperator,
const MediaValues& media_values) {
- int width = *media_values.Width();
- int height = *media_values.Height();
+ double width = *media_values.Width();
+ double height = *media_values.Height();
if (value.IsId()) {
if (width > height) { // Square viewport is portrait.
Patch walkthrough
third_party/blink/renderer/core/css/media_query_evaluator.cc (OrientationMediaFeatureEval)— The only change: the two local variables holding viewport width and height are changed from int to double. media_values.Width()/Height() already return floating-point values, so this removes the implicit float->int narrowing entirely. The downstream logic only compares width vs height to classify portrait/landscape, so double comparison is semantically identical for all realistic inputs and is now defined for out-of-int-range magnitudes, inf and NaN. No test was added; the fix is a correctness/UBSAN cleanup validated by existing sanitizer coverage (Fixed: 440464151).
Background
float-cast-overflow (UBSAN) — A UndefinedBehaviorSanitizer check that fires when a floating-point value is converted to an integer type but does not fit in that type’s range (including inf and NaN). The C++ standard makes such a conversion undefined behavior; UBSAN builds abort, while release builds produce an implementation-defined value. It is a common finding in web-reachable numeric code.
MediaValues::Width()/Height() — Accessors returning the current viewport dimensions used by media-query evaluation. They yield floating-point values (optional) reflecting CSS pixels after zoom/DPR, which can be driven to large magnitudes and were being truncated to int at the comparison site.
orientation media feature — The CSS @media (orientation: portrait|landscape) feature; Blink evaluates it by comparing viewport width and height (square counts as portrait). It runs for every matching media query on a page, making it a broadly reachable, low-severity surface.
Vulnerability window
- discovery — Filed as crbug 440464151 (Fixed: tag). The shape – a float-cast-overflow in orientation evaluation on extreme viewport values – is a classic UBSAN/fuzzer finding rather than a reported attack.
- fix — Change-Id Ia6799cc…, authored by Philip Rogers, reviewed by Anders Hartvoll Ruud, landed 2026-01-13 at r1568335. One-line rationale: ‘This code can just use doubles and avoid a float cast overflow.’
Proof of concept
No test or exploit shipped with the commit. A UBSAN abort can be reproduced conceptually in a sanitizer build by forcing the viewport width or height above INT_MAX (or to inf/NaN) and evaluating an @media (orientation: portrait) / matchMedia(’(orientation: landscape)’) query – e.g. via extreme zoom, device metrics emulation, or print/emulated MediaValues – so that *media_values.Width() no longer fits in int. On a release build this produces no observable memory-safety effect (at most a garbage orientation result), so no meaningful PoC exists and no exploit is demonstrated or plausible from this diff.
Exploitation
- reachability — Any page can invoke orientation media-query evaluation via @media rules or matchMedia(); viewport dimensions can be pushed toward extreme values through zoom/DPR/emulation.
- impact — In a UBSAN build, an out-of-int-range value aborts the process. In release builds, undefined behavior yields an implementation-defined int used only for a >/comparison, so at most an incorrect orientation classification. No memory corruption, no controllable primitive; not exploitable for code execution.
Detection & hunting
For defenders and detection engineers:
- UBSAN float-cast-overflow reports — Sanitizer/fuzzer aborts referencing media_query_evaluator.cc OrientationMediaFeatureEval or float->int conversion in CSS media evaluation.
- Anomalous viewport metrics — Telemetry/tests exercising extreme zoom, DPR, or emulated device metrics that drive viewport dimensions beyond int range while orientation queries are active.
Audit directions
- Other media-feature evaluators — Audit sibling MediaFeatureEval functions in media_query_evaluator.cc (resolution, aspect-ratio, width/height ranges, device- features) for the same float/double -> int narrowing pattern on viewport-derived values.
- Float-to-int narrowing on viewport metrics — Grep Blink layout/CSS code for ‘int x = *media_values.’ and similar implicit narrowings of floating viewport/zoom values; prioritize any case where the narrowed value later sizes an allocation or indexes a container, which would elevate severity beyond this benign comparison-only case.