GHSA-4qjc-2g93-36m2: Out-of-bounds read when a multi-line text view string ends with a carriage return

GHSA ID

GHSA‑4qjc‑2g93‑36m2

CVE ID

CVE‑2026‑91057

Severity

Medium

CVSS score

4.4 (CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:L)

CWE

CWE-125

Published

2026‑10‑02

Last updated

2026‑10‑02

Affected packages

Package Vulnerable versions Patched versions

GUIX

⇐ 6.5.1.202602a

6.5.2.202603

Description

Summary

_gx_multi_line_text_view_display_info_get() reads one byte past the end of the application’s text buffer when the string it is given ends with a carriage return (\r). The over-read can also cause callers to advance their index past the end of the string, producing a second over-read in _gx_multi_line_text_view_string_total_rows_compute() and a row of text that is not in the source.

Details

In common/src/gx_multi_line_text_view_display_info_get.c, the wrapping loop decides whether a line terminator is \r or \r\n:

if (ch.gx_string_ptr[0] == GX_KEY_CARRIAGE_RETURN)
{
    if (ch.gx_string_ptr[1] == GX_KEY_LINE_FEED)   /* unchecked */
    {
        text_info -> gx_text_display_number = (USHORT)(text_info -> gx_text_display_number + 2);
    }
    ...

ch points at the current character and ch.gx_string_ptr[1] is the byte after it. Nothing checks that a byte remains. When the carriage return is the last byte of the range, that access is outside the buffer.

A GX_STRING carries an explicit length and is not required to be NUL terminated — that is the reason the _ext string API exists, and the deprecation notice added for the older char * API states as much. _gx_multi_line_text_view_text_set_ext() stores the caller’s GX_STRING verbatim unless GX_STYLE_TEXT_COPY is set, so in the default configuration the pointer and length are the application’s own, with no terminator behind them. Callers of the affected function pass end_index = gx_string_length, so the read lands one byte past what the application supplied.

A second instance of the same idiom exists in common/src/gx_multi_line_text_input_text_set.c, in _gx_multi_line_text_input_new_line_character_get(). That one is not currently reachable as an out-of-bounds read: its only caller, _gx_multi_line_text_input_text_set_ext(), writes a NUL immediately after the copied text, so the byte examined is always in bounds. It is being corrected in the same change because it relies on an invariant established in a different function and documented nowhere.

Impact

Two effects, both requiring only that a widget be given text whose last byte is a carriage return:

1\. One-byte out-of-bounds read. On a small target where the string sits at the end of a mapped region, this can fault. Otherwise it reads adjacent memory.

2\. Index advanced past the end of the string, and a second over-read. If the out-of-bounds byte happens to be 0x0A, the function reports a two-byte terminator for a one-byte remainder. Every caller advances its index by gx_text_display_number, so the index becomes gx_string_length + 1. _gx_multi_line_text_view_string_total_rows_compute() then evaluates string.gx_string_ptr[index - 1] — itself one byte past the end — and may add a row that is not in the text. The same over-advance reaches the line index cache in _gx_multi_line_text_view_line_cache_update.c and the cursor arithmetic in _gx_multi_line_text_input_cursor_pos_update.c.

The adjacent byte is not attacker-chosen in any general way, and the read is a single byte, so this is a robustness and availability issue rather than a route to disclosure of a chosen secret. It is reachable from ordinary rendering, with no special widget style or configuration required.

Affected configurations

Any build. The read is reached from ordinary rendering, with no special widget style or configuration required. It depends on the widget holding the application’s own buffer, which is the default: _gx_multi_line_text_view_text_set_ext() stores the caller’s GX_STRING verbatim unless GX_STYLE_TEXT_COPY is set, so there is no terminator behind the length.

Every GUIX release up to and including 6.5.1.202602a. The unchecked read of the byte after a carriage return is present in every release tag from v6.0_rel onwards. Per the project security policy the fix ships in 6.5.x, with no backport to 6.4.x.

Severity

CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:L, 4.4, Medium.

Confidentiality is low and not none because the byte read is adjacent memory rather than nothing, but it is a single byte that no attacker chooses, so it is not a route to a selected secret. Availability is low because the fault requires the string to sit at the end of a mapped region.

Patches

Fixed in GUIX 6.5.2.202603.

Both reads are now bounded by the length the GX_STRING carries, by reading the following byte through string — which has already been advanced past the current character and whose length is exactly the number of bytes still readable — instead of indexing off ch.

guix_ml_text_line_terminator_bounds_no_output places a line feed immediately after the string under test but outside the length the widget is given, so the over-read is observable without a sanitizer: the unfixed code counts the out-of-bounds byte and reports a 4-byte row for a 3-byte string; the fixed code reports 3.

Workarounds

Either of the following avoids the read without patching:

  • Set GX_STYLE_TEXT_COPY on the widget, so GUIX copies the text into its own NUL-terminated storage.

  • Ensure application text passed to a multi-line text view does not end with a lone carriage return, or that a readable byte follows the buffer.

Credits

Found and fixed by Frédéric Desbiens.

The analysis, fix and regression test were produced with AI assistance under human review, in accordance with the Eclipse Foundation’s Generative AI Usage Guidelines.