From: "nishanbende (Nishan Bende) via ruby-core" Date: 2026-10-28T05:26:38+00:00 Subject: [ruby-core:127256] [Ruby Bug#22479] File.realdirpath returns a wrong path when GC runs while it resolves a relative symlink Issue #22479 has been reported by nishanbende (Nishan Bende). ---------------------------------------- Bug #22479: File.realdirpath returns a wrong path when GC runs while it resolves a relative symlink https://bugs.ruby-lang.org/issues/22479 * Author: nishanbende (Nishan Bende) * Status: Open * ruby -v: ruby 4.1.0dev (2026-10-28T09:52:18Z master 820ad50ee1) +PRISM [arm64-darwin27] * Backport: 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- `File.realdirpath` can return a wrong path for a path behind a relative symlink, when GC runs during the call. The result is one of these: * the unresolved symlink path, or * a path with NUL bytes, so that `Pathname.new` raises `ArgumentError: pathname contains null byte`. `File.realpath` is not affected on platforms that have realpath(3). Also seen on ruby 3.4.7 (2025-10-08 revision 7a5688e2a2) +PRISM [arm64-darwin25]. ## Reproduction This fails on every run: ```ruby require "tmpdir" require "fileutils" Dir.mktmpdir do |tmp| dir = File.realpath(tmp) target = "store/#{"t" * 100}/node_modules/pkg" FileUtils.mkdir_p("#{dir}/#{target}/sub") Dir.mkdir("#{dir}/app") File.symlink("../#{target}", "#{dir}/app/link") GC.stress = true result = File.realdirpath("#{dir}/app/link/sub") GC.stress = false p expected: "#{dir}/#{target}/sub", got: result end ``` Output on master: ``` {expected: "/private/var/folders/.../store/ttt...ttt/node_modules/pkg/sub", got: "/private/var/folders/.../app/link/sub"} ``` ## Cause In `realpath_rec` (file.c), the names of the symlink target are read through a raw pointer (`link_names`) into the string returned by `rb_readlink`, while `realpath_rec` calls itself to resolve them. After that call there is `RB_GC_GUARD(link_orig)`. For an absolute target, `link_orig` holds the string. For a relative target, `link_orig` is `Qnil`, and nothing uses `link` after the call. So GC can free the string while its later names are still read. Adding `RB_GC_GUARD(link);` next to `RB_GC_GUARD(link_orig);` fixes the reproduction above. Without the fix it fails on every run. With the fix it passes on every run. ## Impact CocoaPods calls `Pathname#realdirpath` for every source file of a local pod, and pnpm installs packages behind relative symlinks. These CocoaPods reports show the same error from the same call (`project.rb:452`, `realdirpath`), in pnpm-based React Native projects. I think they have this cause, but I did not run those projects: * https://github.com/CocoaPods/CocoaPods/issues/12798 * https://github.com/CocoaPods/CocoaPods/issues/12866 We see it in Expo's macOS CI, where `pod install` in `apps/bare-expo/macos` fails at random: https://github.com/expo/expo/pull/52403 A pull request with the fix and a test will follow. -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/