Stop the DNS shim and LSFG layer from loading libandroid.so

Merged
UT
18d ago
+43-25

Description

libandroid.so pulls in ~200 system libraries, and inside a game they look up their own dependencies through our LD_LIBRARY_PATH, so they bind to imagefs libraries with the same name. On Samsung (libsqlite needs BoringSSL's libcrypto) and Xiaomi HyperOS (libjpeg-hyper needs the system libjpeg) loading libandroid.so fails. Gamescope is covered by #2227. This PR fixes the two other libraries we ship that load it into game processes.

DNS shim (app/src/main/cpp/gamenative_dns_v4mapped.c, preloaded into every game)

  • When dlopen("libandroid.so") failed, res_query() returned ENOSYS for every query. EA App's DirtySDK networking and the Rockstar launcher go through this path, which may explain the EA/Rockstar reports from these phones.
  • Wine's Android dnsapi (Proton 9 and 11) also dlopens libandroid.so. It then calls android_res_nquery without a NULL check, so on these phones the first DnsQuery jumps to address 0.
  • Fix: call resNetworkQuery / resNetworkResult from libnetd_client.so directly. That is all libandroid's wrappers do (checked in the disassembly), and libnetd_client's dependencies share no names with imagefs. The shim also exports android_res_nquery / android_res_nresult with libandroid's exact behaviour. Bionic's dlsym(NULL, ...) is RTLD_DEFAULT, so Proton's dnsapi finds ours when its own dlopen fails, with no Proton rebuild. android_getaddrinfofornetwork(0, ...) is the same call bionic's getaddrinfo makes, so that now goes straight to getaddrinfo.

LSFG layer (liblsfg-vk-layer.so)

  • It had a NEEDED on libandroid.so but imports nothing from it (its only non-libc import is __android_log_print, from liblog). On these phones the Vulkan loader could not load the layer, so LSFG silently did nothing.
  • Fix: rebuilt from source with GameNative/lsfg-vk-android#9, which links nativewindow instead of android (release needs neither; android-gen8-fixes needs AHardwareBuffer_*, which libnativewindow exports). Same dynamic exports as the old binary, 16 KB alignment. RUNTIME_VERSION is bumped so existing containers reinstall the layer.

Testing

On a POCO F6 (HyperOS 1, Android 14), with a test binary and the Samsung failure forced by putting a stub libsqlite.so first on LD_LIBRARY_PATH:

libandroid loadslibandroid fails
old DNS shim, res_query A40 bytes-1
old DNS shim, dnsapi-style dlsym(NULL, "android_res_nquery")foundNULL
new DNS shim, res_query A40 bytes40 bytes
new DNS shim, dnsapi-style lookup, SRV / A94 / 40 bytes94 / 40 bytes
old LSFG layer dlopenloadsfails
new LSFG layer dlopenloadsloads

Not yet tested in a real game session.

Recording

N/A, no UI change.

Type of Change

  • Bug fix
  • Performance / stability improvement
  • Compatibility improvements
  • Other (requires prior approval)

Checklist

  • If I have access to #code-changes, I have discussed this change there and it has been green-lighted. If I do not have access, I have still provided clear context in this PR. If I skip both, I accept that this change may face delays in review, may not be reviewed at all, or may be closed.
  • This change aligns with the current project scope (core functionality, stability, or performance). If not, it has been explicitly approved beforehand.
  • I have attached a recording of the change.
  • I have read and agree to the contribution guidelines in CONTRIBUTING.md.