Skip to content

feat(preload): assemble LIBVA_DRIVERS_PATH for libva's driver search - #24

Merged
Samueru-sama merged 1 commit into
mainfrom
feat/libva-drivers-path
Aug 27, 2026
Merged

feat(preload): assemble LIBVA_DRIVERS_PATH for libva's driver search#24
Samueru-sama merged 1 commit into
mainfrom
feat/libva-drivers-path

Conversation

@Samueru-sama

Copy link
Copy Markdown
Member

What and why

VA-API was classified an unmeasured plugin boundary (tools/plugin_boundaries.py:88): bundled libva.so.2 dlopens <name>_drv_video.so from the host. On the way to measuring it, one half of the problem turned out to be unfixable by the existing mechanism and is repaired here.

libva never opens its driver by soname. va_openDriver() (va/va.c) colon-splits LIBVA_DRIVERS_PATH (or a VA_DRIVERS_PATH compiled into the running libva) and opens the constructed absolute path. A bundled libva carries its build host's dri layout, and no --library-path can correct that, because no soname lookup ever happens.

The change

src/cross-libc-dlopen.c gains a guarded LIBVA_DRIVERS_PATH assembler:

  • Only when libva is loaded. dl_iterate_phdr for soname prefix libva.so., from the constructor (startup-linked libva: ffmpeg, mpv, browsers) and after every successful interposed dlopen until it fires (libva entering as a dependency of a dlopened plugin: gstreamer's va plugin). Either route has fired before vaInitialize can read the variable.
  • Host dri dirs only. Probed as <dir>/dri under the process's LD_LIBRARY_PATH (host entries, via the existing is_host_library_path boundary) and the conventional libdirs (triplet, lib64, lib, local). Never $APPDIR/lib/dri: a bundle that ships VA drivers manages the variable itself, and its entries stay ahead of anything appended here.
  • Appended, never clobbered. Existing value keeps the front of libva's walk, the same place the conventions put appended path entries. Deduped against what is already set.
  • Honors CROSS_LIBC_DLOPEN=0 and CROSS_LIBC_DLOPEN_DRYRUN=1 (reports, does not set), one CROSS_LIBC_DLOPEN_DEBUG line naming the final value.
  • ⛔ Nothing here opens a library (docs/conventions/code.md): appending directories to a search list is not searching it. libva does its own searching from the result.

The guard is "settled", not "ran" — measured, not designed

The first revision latched the guard when the constructor found no libva, which permanently disarmed the post-dlopen scan; E97 failed exactly that way and the fix is in the comment. The scan re-runs per successful dlopen until libva turns up.

The case (FAILS before, PASSES after)

Section Q of experiments/30-run-tests.sh, E95 through E100: a fake libva.so.2 implementing va_openDriver's contract, stand-in drivers that answer with the directory they were built for, and the answer round-tripped through the driver's own function, so a pass is not "a string appeared".

from main with the change
E95 startup-linked libva finds the host driver MISMATCH MATCH
E96 control, CROSS_LIBC_DLOPEN=0 MATCH (FAIL as predicted) MATCH
E97 late load through a dlopened plugin MISMATCH MATCH
E98 user-set value kept first, ours appended MISMATCH MATCH
E99 bundle dri never enters the list MISMATCH MATCH
E100 no libva, variable untouched MATCH MATCH

Full evidence table on this branch: 59/59 on x86-64 (debian:bullseye-slim, glibc 2.31), every prior prediction still held.

A finding on the way (docs/traps.md)

An interposed dlopen consults the interposer's DT_RPATH/DT_RUNPATH, not the calling binary's. A soname reachable only through the caller's own runpath stops resolving under the preload. The trap entry names the two ways out (library path, or dlopen an object that NEEDs it).

Documentation

  • docs/integrating.md: a VA-API section under "Per target".
  • Still UNVERIFIED, stated as such in the doc: a real iHD_drv_video.so or i965_drv_video.so carried across a libc boundary. That needs a host with one installed; E95-E100 measure the search-list half with a stand-in driver.

Reconciliation owed at close-out

docs/report/08-test-results.md Tier 1 says 53/53 on x86-64 and 50/50 on aarch64, citing its CI run. This branch makes the x86-64 total 59; the aarch64 total needs CI's run before the report is updated, and the six new cases are architecture-independent C, so no skip path was added.

libva never opens its driver by soname. va_openDriver() constructs
<dir>/<name>_drv_video.so and opens that absolute path, walking
LIBVA_DRIVERS_PATH or a VA_DRIVERS_PATH compiled into whichever libva
runs. A bundled libva therefore carries its build host's dri layout into
a process running on a different one, and no library path can correct
it, because no soname lookup ever happens.

The preload now assembles the list: when libva.so.2 is in the process,
every host <libdir>/dri found on LD_LIBRARY_PATH and in the conventional
directories is APPENDED to LIBVA_DRIVERS_PATH, behind anything already
set. A process that never loads libva is untouched, and the bundle's own
lib/dri is never added: a bundle that ships VA drivers manages the
variable itself. The scan runs once from the constructor and again after
every successful dlopen until libva turns up, which is what covers the
gstreamer shape, where libva enters as a dependency of a dlopened
plugin. The guard means settled, not ran: latching it on 'libva absent'
disarmed the late-load case and was measured doing so before E97 passed.

Measured as E95 through E100 in experiments/30-run-tests.sh, on the
glibc 2.31 floor. Built from main the section scores 2 of 6; with the
change 6 of 6, and the whole evidence table is 59/59 with every prior
prediction still held. E99 pins the bundle exclusion by turning the
absence into a word, the way E85 does.

The first E97 draft also measured a general interposer property, now in
docs/traps.md: a dlopen through an interposer consults the interposer's
own RUNPATH, not the calling binary's, so a soname reachable only
through the caller's runpath stops resolving under the preload.

docs/integrating.md gains the VA-API section and states what stays
UNVERIFIED: a real iHD_drv_video.so or i965_drv_video.so across a libc
boundary. That needs a host with one installed.
@Samueru-sama
Samueru-sama merged commit 5e96ad6 into main Aug 27, 2026
18 checks passed
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.

1 participant