Skip to content

fix(glx): terminate the extension string with a space, as Xorg does - #120

Merged
joske merged 4 commits into
joske:masterfrom
ariel3259:glx-extension-string-terminator
Aug 8, 2026
Merged

fix(glx): terminate the extension string with a space, as Xorg does#120
joske merged 4 commits into
joske:masterfrom
ariel3259:glx-extension-string-terminator

Conversation

@ariel3259

Copy link
Copy Markdown
Contributor

fix(glx): terminate the extension string with a space, as Xorg does

Branch: glx-extension-string-terminator, off master @ f4c0b60c.

The whole fix is one byte. Finding it took eight wrong hypotheses, so the
design doc is longer than the diff — deliberately, so nobody has to
re-derive it.

Problem

glx_extension_string joined tokens with separating spaces and ended the
string at the last token. Xorg writes ' ' after every enabled
extension
(glx/extension_string.c:144-145):

buffer[length + len + 0] = ' ';
buffer[length + len + 1] = '\0';

so every Xorg extension string ends with a space before the NUL. Ours did
not.

libGLX_nvidia responds to an unterminated list by losing its last
token
, and separately by dropping GLX_ARB_get_proc_address from the
client extension string it computes. The first silently cost us the
GLX_SGIX_fbconfig we do advertise — the anomaly that made this so hard
to find, since the driver was withholding an extension we were sending.
The second makes libepoxy abort:

No provider of glXGetProcAddressARB found.  Requires one of:
    GLX_ARB_get_proc_address

which killed kwin_x11 with SIGABRT on every session.

Mesa's libGLX tokenises correctly and never noticed, which is why this
looked like an NVIDIA problem for four days when it was ours.

Evidence

An X proxy (glxmangle.py) sits between a client and a live XWayland and
rewrites only the QueryServerString(GLX_EXTENSIONS) reply, so a
working server can be degraded toward yserver's shape one variable at a
time. Oracle is glXQueryExtensionsString(dpy, 0) — the same call
epoxy_has_glx_extension makes.

injected server string get_proc_address SGIX_fbconfig
XWayland's own list, with trailing space yes yes
XWayland's own list, without trailing space no yes
yserver's list, with trailing space yes yes
yserver's list, without trailing space no no
yserver's list + a dummy token appended no yes

Rows 1/2 and 3/4 are one-variable pairs — identical tokens, differing by
the single trailing byte. Rows 2 and 5 separate the two effects: the lost
token is whichever one is last (GLX_SGIX_fbconfig survives when it
isn't), while GLX_ARB_get_proc_address is lost regardless of position.

Every row's injected string is preserved with repr() and byte length in
~/yserver-glx-logs/2026-08-07-terminator-evidence/. Corroborated
independently by an earlier capture taken on real KMS with no proxy in
the path (2026-08-06-defect-d-ab/v3-xwayland-exts-probe.txt).

Hardware gate, RTX 5060 Ti / NVIDIA 610.57.04 / open kernel modules,
against a preserved pre-fix baseline:

vendor before after
nvidia epoxy aborta en glXGetProcAddressARB epoxy resuelve glXGetProcAddressARB
mesa (control) resolves resolves — unchanged

The server log shows the string going out terminated:
... GLX_EXT_libglvnd GLX_SGIX_fbconfig ".

The fix

The terminator is applied when a chunk is appended rather than added once
at the end, so the builder's output ends in a terminator by construction
and a future extension cannot reintroduce the defect:

let mut push = |chunk: &str| {
    s.push_str(chunk);
    s.push(' ');
};

SERVER_EXTENSIONS stays a space-separated constant and is not
modified — keeping the terminator in one place avoids a second convention
where callers must remember whether a given constant is self-terminating.
This mirrors Xorg, where known_glx_extensions[].name are bare tokens and
extension_string.c:144 supplies the space.

Both reply paths already route through this one builder — opcode 19
(QueryServerString/GLX_EXTENSIONS) and opcode 18
(QueryExtensionsString) — so both are fixed by construction.

n grows by one; the padded reply is unchanged in size in both real
configurations (232→233 bytes of string, n 233→234, padded stays 236;
and 260→261, n 261→262, padded stays 264).

A second, unrelated defect this turned up

glx_extension_string_contains_sgix_fbconfig did not call
glx_extension_string. Its body said so verbatim — "glx_extension_string()
is a private fn; mirror its logic here"
— and asserted against its own
copy of the builder, so it would have passed if the function were deleted,
and it passed throughout this defect. It calls the real function now, and
matches whole tokens rather than substrings.

Known limitation — KWin still does not composite on NVIDIA

Worth reading before merging, because this change moves the failure rather
than removing it.

KWin now starts compositing instead of aborting, but windows get no
texture: no wallpaper, panel renders grey, menus wrong. Traced to
GlxBackend::infoForVisual (kwin-x11 6.6.6), which skips every fbconfig
lacking GLX_BIND_TO_TEXTURE_RGB(A)_EXT; we emit those attributes only
when glx_tfp_supported, which is forced false on NVIDIA. With no
surviving config, GlxPixmapTexture::create returns false before
reaching glXBindTexImageEXT — hence no epoxy abort and zero
GLX::CreatePixmap in the trace.

Un-suppressing TFP was measured and is worse: KWin then finds a
config, calls glXBindTexImageEXT, and libepoxy aborts, because
libGLX_nvidia does not list GLX_EXT_texture_from_pixmap in its
client extension string
— verified against XWayland, which does
advertise it server-side and still gets a client string without it. So
this is a driver-side limitation, not a yserver defect, and not something
this PR can address. Applications are unaffected: Steam and games run on
the GPU.

Two further divergences from Xorg were found while investigating, measured
not to cause any of the above, and deliberately left out to keep this a
one-variable change (both recorded in the design's "Deferred" section):
GetVisualConfigs declares 18 properties where Xorg declares 40, and
GetFBConfigs declares 33 attributes where Xorg declares 44. The second is
not a drop-in — implemented as an experiment it broke Mesa outright
(No matching fbConfigs or visuals found), because driConfigEqual
scalar-compares those attributes.

Notes

  • Workspace green: 2256 tests, cargo clippy --all-targets -- -D warnings
    clean, cargo +nightly fmt --check clean. All four commits signed.
  • Design (rev 3, two adversarial review rounds — the first returned REJECT
    for presenting measurements whose artifacts had not been preserved) and
    the implementation plan are included under docs/superpowers/.
  • docs/status.md updated.

🤖 Generated with Claude Code

The GLX extension string yserver sends is not space-terminated; Xorg's
is (glx/extension_string.c:144-145). libGLX_nvidia loses the final
token of an unterminated list -- costing us the GLX_SGIX_fbconfig we
advertise -- and additionally withholds GLX_ARB_get_proc_address from
the client extension string, which aborts libepoxy and kills kwin_x11.

Design at rev 3 after two Opus review rounds (REJECT, then APPROVE WITH
EDITS); plan after one round (APPROVE WITH EDITS). Evidence is six
controlled rows with preserved artifacts, produced with a new X proxy
that mutates replies so a working XWayland can be degraded toward
yserver's shape without a KMS session.
libGLX_nvidia loses the final token of an unterminated GLX extension
list and additionally withholds GLX_ARB_get_proc_address from the
client extension string. The first cost us the GLX_SGIX_fbconfig we
do advertise; the second aborts libepoxy and kills kwin_x11 with
SIGABRT.

Xorg writes a space after every enabled extension
(glx/extension_string.c:144-145). Apply the terminator when a chunk is
appended so the invariant holds for extensions added later.
glx_extension_string_contains_sgix_fbconfig re-implemented the
builder's body in the test and asserted against its own copy, so it
would have passed if the function were deleted -- and it did pass
throughout the missing-terminator defect. Call the real function, and
match whole tokens rather than substrings.
@joske
joske merged commit 3191535 into joske:master Aug 8, 2026
1 check passed
@joske

joske commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Thx. Not sure why you keep trying to make KDE use TFP, this is known impossible with the nvidia proprietary driver.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants