Bug #22479
closedFile.realdirpath returns a wrong path when GC runs while it resolves a relative symlink
Description
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.newraisesArgumentError: 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:
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.