Project

General

Profile

Actions

Bug #22479

closed

File.realdirpath returns a wrong path when GC runs while it resolves a relative symlink

Bug #22479: File.realdirpath returns a wrong path when GC runs while it resolves a relative symlink

Added by nishanbende (Nishan Bende) 2 days ago. Updated 1 day ago.

Status:
Closed
Assignee:
-
Target version:
-
ruby -v:
ruby 4.1.0dev (2026-10-28T09:52:18Z master 820ad50ee1) +PRISM [arm64-darwin27]
[ruby-core:127256]

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.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:

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:

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.

Actions

Also available in: PDF Atom