From 12b3b03a0f2b94b1973fdb086adffd496e0cfaad Mon Sep 17 00:00:00 2001 From: "Hitalo M." Date: Fri, 16 Oct 2026 00:56:24 -0300 Subject: [PATCH] Fix Riot Client crash when cancelling CFHost resolution --- README.md | 2 +- docs/TESTING.md | 16 ++++++++++++ docs/TROUBLESHOOTING.md | 14 +++++++++++ patches/cfnetwork.patch | 17 +++++++++++++ tools/build-riot-cfhost-cancel-check.py | 22 ++++++++++++++++ tools/install-riot-presentation-runtime.sh | 5 +++- tools/riot-cfhost-cancel-check.c | 29 ++++++++++++++++++++++ tools/run-riot-native-check.py | 1 + upstream/manifest.json | 8 ++++++ 9 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 patches/cfnetwork.patch create mode 100644 tools/build-riot-cfhost-cancel-check.py create mode 100644 tools/riot-cfhost-cancel-check.c diff --git a/README.md b/README.md index 37a08c6..8d06d41 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ This is a development snapshot, not a finished launcher. Riot and League clients ## Included -- Pinned upstream revisions and patches for 29 Darling components, including Metal/Indium, AppKit, QuartzCore, CoreAudio, the kernel shim and darlingserver. +- Pinned upstream revisions and patches for 30 Darling components, including Metal/Indium, AppKit, QuartzCore, CoreAudio, the kernel shim and darlingserver. - Event-poll presentation and GPU synchronization fixes, floating-point-safe GL resolvers, multi-window presentation and nullable LaunchServices error handling. - Reusable server receive buffers and Darwin allocator hints translated to Linux lazy reclamation. - Build/install/launch wrappers, atomic incremental installers and standalone regression probes. diff --git a/docs/TESTING.md b/docs/TESTING.md index 13482df..64488ed 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -394,3 +394,19 @@ Observe host CPU/thread scheduling, GPU counters, temperatures and memory pressu Compare similar scene, ability and camera activity with the same power settings (and correct charger on a laptop). Do not report a paused, dead, unfocused or empty scene as a fight benchmark. If an allowed host measurement supplies frame times, report p99 in milliseconds; its reciprocal is percentile-equivalent FPS, not the mean of the slowest 1% of frames. Manual FPS readings alone do not establish p99 frame times. The CPU symbolizer offers `--leaf-only` because the host DWARF unwinder can drop guest Mach-O stacks. Successfully unwound samples alone are not a representative profile. Local API lockfiles, account data and raw logs must stay outside version control. + +## CFHost cancellation regression + +After configuring and building CFNetwork, build and run the isolated probe: + +```sh +python3 build/runtime/tools/build-riot-cfhost-cancel-check.py +python3 build/runtime/tools/run-riot-native-check.py riot-cfhost-cancel-check +``` + +Run against the installed CFNetwork override. Expect +`CFHOST_CANCEL_CHECK cancel callback lifetime repeated pass=1`. The unpatched +library traps in `CFRetain(NULL)`; the runner checks the success marker because +Darling can return status zero even when the guest traps. The probe uses localhost +and exercises 100 cancellations with both retained and early-released caller +references, without launching or inspecting Riot. diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index 8e16f5e..d1bd865 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -56,6 +56,20 @@ The installer fetcher no longer treats a missing archive `Info.plist` or the inf There is also a separate known compatibility gap: native LaunchAgent watch registration can return HTTP 409 after the League install path is accepted, leaving the download idle. A historical inspector hook handles this and interrupted `pending_verify` states in 28 mock cases, but that hook is disabled in normal launches and excluded from the manual workflow. There is no verified runtime-only replacement for that handoff in this snapshot. Do not assume the native permission and launcher fixes resolve every idle Install case, or enable debugger/API automation to work around it. +## Riot closes after a network lookup is cancelled + +Two local crashes were traced to `CFNetwork::_HostCancel` calling `CFRetain(NULL)`. +`CFHostCancelInfoResolution` scheduled its cancellation callback with an empty +context, so processing that source triggered `SIGTRAP` in CoreFoundation and +terminated RiotClientServices. The UI helpers then exited as a consequence. + +The CFNetwork patch supplies the host and retains it until the cancellation source +is invalidated. The standalone `riot-cfhost-cancel-check` reproduces the old trap +and passes 100 cancellations with the fix, including releasing the caller's host +reference before callback delivery. This establishes the cancellation fix; a long +Riot session still needs confirmation. With both clients and the game closed, +run `./scripts/update.sh` to install it, then `sh scripts/run.sh`. + ## UI crashes after a button opens an external link LaunchServices dereferenced a nullable error-output pointer in six paths. The patch checks the pointer itself and populates supplied outputs; the standalone regression covers missing handlers and null outputs. Electron opens LaunchServices through an absolute CoreServices subframework path, so the paired installer also replaces that binary in the stopped guest prefix. Updating only `RuntimeFrameworks/LaunchServices.framework` can leave the crash unfixed. diff --git a/patches/cfnetwork.patch b/patches/cfnetwork.patch new file mode 100644 index 0000000..4654032 --- /dev/null +++ b/patches/cfnetwork.patch @@ -0,0 +1,17 @@ +diff --git a/src/Host/CFHost.c b/src/Host/CFHost.c +index 91c0d0f..d6e3daf 100644 +--- a/src/Host/CFHost.c ++++ b/src/Host/CFHost.c +@@ -2221,9 +2221,9 @@ CFHostCancelInfoResolution(CFHostRef theHost, CFHostInfoType info) { + + CFRunLoopSourceContext ctxt = { + 0, // version +- NULL, // info +- NULL, // retain +- NULL, // release ++ host, // info ++ CFRetain, // retain ++ CFRelease, // release + NULL, // copyDescription + NULL, // equal + NULL, // hash diff --git a/tools/build-riot-cfhost-cancel-check.py b/tools/build-riot-cfhost-cancel-check.py new file mode 100644 index 0000000..9d9e47d --- /dev/null +++ b/tools/build-riot-cfhost-cancel-check.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +"""Build the standalone CFHost cancellation regression probe.""" +from pathlib import Path +import shlex +import subprocess + +project = Path(__file__).resolve().parent.parent +framework = project / 'build/darling-src/build/src/external/cfnetwork/src' +flags = {} +for line in (framework / 'CMakeFiles/CFNetwork.dir/flags.make').read_text().splitlines(): + if line.startswith('C_') and ' = ' in line: + name, value = line.split(' = ', 1) + flags[name] = shlex.split(value) +name = 'riot-cfhost-cancel-check' +obj = project / ('build/' + name + '.o') +subprocess.run(['clang', *flags['C_DEFINES'], *flags['C_INCLUDES'], *flags['C_FLAGS'], + '-O2', '-Wno-macro-redefined', '-c', str(project / ('tools/' + name + '.c')), + '-o', str(obj)], check=True) +subprocess.run(['clang', '-target', 'x86_64-apple-macosx11.0', '-fuse-ld=lld', '-nostdlib', + '-isysroot', '@DARLING_SDK@', str(obj), '-lSystem', '-framework', 'CFNetwork', + '-framework', 'CoreFoundation', '-framework', 'Foundation', + '-o', str(project / ('build/' + name))], check=True) diff --git a/tools/install-riot-presentation-runtime.sh b/tools/install-riot-presentation-runtime.sh index 0f4705c..525af97 100644 --- a/tools/install-riot-presentation-runtime.sh +++ b/tools/install-riot-presentation-runtime.sh @@ -8,6 +8,7 @@ guest_home="${RIOT_GUEST_HOME:-$HOME/.darling@GUEST_HOME@}" make -C "$build_dir/src/external/xnu/darling/src/libsystem_kernel" system_kernel -j8 make -C "$build_dir/src/external/libplatform" platform -j8 make -C "$build_dir/src/external/corefoundation" CoreFoundation_x86_64 -j8 +make -C "$build_dir/src/external/cfnetwork/src" CFNetwork -j8 make -C "$build_dir/src/external/configd/SystemConfiguration.fproj" SystemConfiguration -j8 make -C "$build_dir/src/external/cocotron/QuartzCore" QuartzCore -j8 make -C "$build_dir/src/external/cocotron/AppKit" AppKit X11_backend -j8 @@ -26,7 +27,7 @@ darling shutdown || true mv "$project_dir/build/libdarling-xshm.next.so" "$project_dir/build/libdarling-xshm.so" mv "$guest_home/libDarlingXShm.next.dylib" "$guest_home/libDarlingXShm.dylib" mkdir -p "$guest_home/RuntimeFrameworks" -for framework in AppKit QuartzCore CoreFoundation SystemConfiguration; do +for framework in AppKit QuartzCore CoreFoundation SystemConfiguration CFNetwork; do if [ ! -d "$guest_home/RuntimeFrameworks/$framework.framework" ]; then cp -a "@DARLING_SDK@/System/Library/Frameworks/$framework.framework" "$guest_home/RuntimeFrameworks/" fi @@ -43,6 +44,8 @@ atomic_install "$build_dir/src/external/corefoundation/CoreFoundation" "$guest_h atomic_install "$build_dir/src/external/configd/SystemConfiguration.fproj/SystemConfiguration" "$guest_home/RuntimeFrameworks/SystemConfiguration.framework/Versions/A/SystemConfiguration" +atomic_install "$build_dir/src/external/cfnetwork/src/CFNetwork" "$guest_home/RuntimeFrameworks/CFNetwork.framework/Versions/A/CFNetwork" + mkdir -p "$guest_home/RuntimeLibraries" atomic_install "$build_dir/src/external/libplatform/libsystem_platform.dylib" "$guest_home/RuntimeLibraries/libsystem_platform.dylib" atomic_install "$build_dir/src/external/xnu/darling/src/libsystem_kernel/libsystem_kernel.dylib" "$guest_home/RuntimeLibraries/libsystem_kernel.dylib" diff --git a/tools/riot-cfhost-cancel-check.c b/tools/riot-cfhost-cancel-check.c new file mode 100644 index 0000000..f74d8fc --- /dev/null +++ b/tools/riot-cfhost-cancel-check.c @@ -0,0 +1,29 @@ +#include +#include +#include + +static unsigned callbacks; +static void completed(CFHostRef host, CFHostInfoType type, + const CFStreamError *error, void *info) { + if (host && type == kCFHostAddresses && info == &callbacks) + ++callbacks; +} + +int main(void) { + CFHostClientContext context = {0, &callbacks, NULL, NULL, NULL}; + for (unsigned i = 0; i < 100; ++i) { + CFHostRef host = CFHostCreateWithName(NULL, CFSTR("localhost")); + CFStreamError error = {0}; + if (!host || !CFHostSetClient(host, completed, &context)) return 1; + CFHostScheduleWithRunLoop(host, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + if (!CFHostStartInfoResolution(host, kCFHostAddresses, &error)) return 2; + CFHostCancelInfoResolution(host, kCFHostAddresses); + /* The cancel source must keep its host alive until delivery. */ + if (i % 2) CFRelease(host); + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.05, false); + if (callbacks != i + 1) return 3; + if (!(i % 2)) CFRelease(host); + } + puts("CFHOST_CANCEL_CHECK cancel callback lifetime repeated pass=1"); + return 0; +} diff --git a/tools/run-riot-native-check.py b/tools/run-riot-native-check.py index 3c06200..df6fddb 100644 --- a/tools/run-riot-native-check.py +++ b/tools/run-riot-native-check.py @@ -9,6 +9,7 @@ import tempfile root = Path(__file__).resolve().parent.parent checks = { + "riot-cfhost-cancel-check": "CFHOST_CANCEL_CHECK", "riot-launchservices-null-error-check": "LAUNCHSERVICES_NULL_ERROR", "m327-cgl-resolver-check": "GL_RESOLVER", "riot-mach-exception-state-check": "MACH_EXCEPTION_STATE", diff --git a/upstream/manifest.json b/upstream/manifest.json index 63d753c..bc13a46 100644 --- a/upstream/manifest.json +++ b/upstream/manifest.json @@ -305,6 +305,14 @@ "private-include/indium/submit-thread.private.hpp", "private-include/indium/waits.private.hpp" ] + }, + { + "path": "src/external/cfnetwork", + "revision": "e7e3db881008d883f82914765a72ce842bcba735", + "repository": "https://github.com/darlinghq/darling-cfnetwork.git", + "patch": "patches/cfnetwork.patch", + "modified_files": 1, + "new_files": [] } ], "omitted": [