[PATCH] Keep the symlink target alive while realpath_rec reads it
realpath_rec reads the names of a symlink target through a raw pointer
into the string returned by rb_readlink, while it calls itself to resolve
those names. RB_GC_GUARD(link_orig) keeps that string alive only for an
absolute target. For a relative target, link_orig is Qnil and nothing
uses `link` after the recursive call, so GC can free the string while the
later names are still being read.
File.realdirpath then returns a wrong path: the unresolved symlink path,
or a path with NUL bytes, which makes Pathname.new raise "pathname
contains null byte". CocoaPods calls Pathname#realdirpath for every
source file, and pnpm installs packages behind relative symlinks, so
`pod install` fails at random in such projects.
File.realpath is not affected on platforms with realpath(3), because only
RB_REALPATH_DIR uses realpath_rec there.
Fixes [Bug #22479]
Since the fix for Bug #21441, ec->cfp is set to NULL before clearing
the VM stack so that thread_profile_frames() called from a signal
handler returns early instead of walking a stack that is no longer set
on the ec. But these are plain stores to distinct fields of the same
struct, so the compiler is free to reorder them.
With Apple clang 21 and GCC 16 on macOS arm64, and with clang 18 and
GCC 13 on Linux aarch64, both stores of rb_ec_set_vm_stack() were
combined into a single stp emitted before the store to ec->cfp
(vm_stack is at offset 0, vm_stack_size at 0x8 and cfp at 0x10):
stp xzr, xzr, [x0] ; ec->vm_stack = NULL, vm_stack_size = 0
str xzr, [x0, #0x10] ; ec->cfp = NULL
ret
So a signal in between would see a non-NULL ec->cfp with a NULL
ec->vm_stack. Stopping there in lldb when a thread terminates and
calling rb_profile_frames() segfaults in thread_profile_frames(), as
end_cfp is computed from the NULL ec->vm_stack and the loop walks past
the control frames. With this change, ec->cfp is already NULL at that
point and rb_profile_frames() returns 0.
Like in vm_push_frame(), use atomic_signal_fence() to keep the store to
ec->cfp first. With it, all these compilers emit the store to ec->cfp no
later than the stores clearing the stack. The rest of vm.o is unchanged
with Apple clang 21, GCC 16 and GCC 13 on aarch64. Clang 21 and 18:
stp xzr, xzr, [x0, #0x8] ; ec->vm_stack_size = 0, cfp = NULL
str xzr, [x0] ; ec->vm_stack = NULL
ret
GCC 16 and 13:
str xzr, [x0, #0x10] ; ec->cfp = NULL
stp xzr, xzr, [x0] ; ec->vm_stack = NULL, vm_stack_size = 0
ret
With GCC 13 on x86_64, the store to ec->cfp is already emitted first
without the fence, and vm.o is identical with and without it:
endbr64
movq $0x0, 0x10(%rdi) ; ec->cfp = NULL
movq $0x0, (%rdi) ; ec->vm_stack = NULL
movq $0x0, 0x8(%rdi) ; ec->vm_stack_size = 0
ret
[Bug #22477]
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
[PATCH] Discard unreachable exception table entries
Dead labels can leave invalid catch entries that make ensure clauses
run for code that cannot be reached.
With a subexp call, the result of unset_addr_list_fix overwrote the error, so a pattern over the limit compiled without raising RegexpError.
Follow-up to cf35145fc4.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
[PATCH] Reject overflowed repeat/null-check IDs in huge regexps (#16801)
Add explicit errors for excessive range-repeat and null-check IDs;
map new errors in regerror; add regression tests for oversized generated
patterns.
A thread that is waiting for the fork lock when another thread forks
exists only in the parent, but on macOS the child's copy of the lock
still counts it. After the child unlocks its copy,
pthread_rwlock_destroy() and pthread_rwlock_init() fail with EBUSY, so
the child aborts with
"[BUG] pthread_rwlock_destroy: Resource busy (EBUSY)". Lookup threads
wait for the lock whenever a fork holds it, so a process that resolves
names in background threads while it forks loses a child now and then.
Reset the child's copy by assigning PTHREAD_RWLOCK_INITIALIZER, as
rb_internal_thread_event_hooks_rw_lock_atfork() does for its lock, and
do not unlock it first. With a waiter counted, macOS's unlock enters the
kernel to wake it, and the state that leaves behind for the lock's
address survives the assignment, so a later waiter in the child can
sleep forever although no thread holds the lock. FreeBSD already skipped
the unlock in the child (dc8c382266), and on glibc the child's unlock
does not release the write lock anyway.
[Bug #22445]
A getaddrinfo thread calls getaddrinfo(3) inside rb_thread_prevent_fork,
but on glibc its thread exit takes the resolver configuration lock again
after the thread has released the fork lock. A fork that lands then
leaves the child with that lock held, and every later name lookup in the
child blocks forever. getnameinfo(3) attaches the same resolver state,
even for a name in /etc/hosts, and its thread ran outside
rb_thread_prevent_fork altogether.
Make the lookup threads joinable and have fork join any that may still
be exiting. As a lookup thread finishes, still inside
rb_thread_prevent_fork, it swaps itself into a single slot with an
atomic exchange and joins the thread it displaced, which has usually
exited by then, and rb_thread_acquire_fork_lock joins whatever is left
in the slot. Once lookups settle, at most one finished thread is left
unjoined. The slot holds pthread_t as a pointer, with NULL for empty,
which assumes pthread_t is scalar and never 0.
getnameinfo threads now run within rb_thread_prevent_fork and register
as they exit, as getaddrinfo threads do, and where getnameinfo runs
without a helper thread it is called within rb_thread_prevent_fork too.
So fork also waits for a reverse lookup in progress, as it already did
for a forward one. raddrinfo_pthread_create now always creates joinable
threads, which drops its pthread_detach fallback for platforms without
pthread_attr_setdetachstate.
The slot is kept across fork, so every fork path that keeps running
Ruby code has to take the fork lock. On the BSDs, Process.daemon forks
with daemon(3) and did not take it, so the daemon would inherit the
registered thread, and its next fork would abort in pthread_join with
ESRCH. Take and release the fork lock around daemon(3) as rb_fork_ruby
does. daemon(3) can fail in the child as well as in the parent, so
compare pids to tell which process returned.
[Bug #22406]
(cherry picked from commit 44f7c6b00d)
Interrupts are checked at arbitrary points, including while a jump
(break/next/redo/return/raise/throw) is being propagated, and the payload
of that jump lives in ec->errinfo.
Handling an interrupt can run Ruby code: rb_signal_exec() runs a trap
handler and threadptr_interrupt_exec_exec() runs interrupt_exec tasks.
Neither preserves ec->errinfo, and any rescue or ensure clause that code
enters sets it to nil.
Thread.handle_interrupt checks for interrupts after its block returns but
before re-raising the jump that left the block. If a signal handler runs
in that window the throw data is replaced with nil and the VM dereferences
nil as a vm_throw_data:
THROW_DATA_CATCH_FRAME (obj=0x4) at vm_insnhelper.h:217
vm_exec_handle_exception (ec=..., errinfo=4) at vm.c:2880
vm_exec_loop / rb_vm_exec at vm.c:2829 / vm.c:2812
invoke_iseq_block_from_c / rb_yield / rb_ary_each
rb_postponed_job_flush() and rb_exec_event_hooks() already save and restore
ec->errinfo around the Ruby code they run; do the same for the rest of
rb_threadptr_execute_interrupts(). The paths that set errinfo on purpose
(rb_exc_raise, rb_threadptr_to_kill) jump out of the function and so do not
reach the restore.
A range of single character Strings told that it includes its end
even when it is empty, since the end was not compared with the begin.
("z".."a").to_a #=> []
("z".."a").include?("a") #=> true
(cherry picked from commit 1bf9130943)
Win: Strip CRs from `cpp` and `nm` output
The combination of mingw tools and cygin/msys2 ruby leaves CRs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI: mingw: Use the official actions for msys2
Because ruby/setup-ruby is affected to test result.
Also includes the prerequisite changes from e4b386e090, 90f8199412,
a443cd012a and 4adb6f6969 (Remove Strawberry Perl pkg-config, misc
system & package info, `cmd` as the default shell, set up msys2
environment variables).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
setup-ruby v1.236.0 raises "Cannot find Git's cert file" on Windows
runners because Git for Windows moved from mingw64 to ucrt64.
This was fixed in ruby/setup-ruby#947 (v1.328.0).
C23 has qualifier-preserving standard library functions, so calling
strchr with a `const char *` will return a `const char *`. We can change
the type of the local variables because we don't mutate the strings.
Prevent double free for too big repetition quantifiers
The previous implementation calls `free(node)` twice (on parsing and compiling a
regexp) when it has an error, so it leads to a double-free issue. This
commit enforces `free(node)` once by introducing a temporal pointer to hold
parsing nodes.
(cherry picked from commit 35000ac2ed)
Concatenated large repeats reach a program past INT_MAX from a few hundred
kilobytes of source, and the offsets emitted into it wrap. Cap the size
while the program is emitted, which does not depend on the length pass that
overflows along with it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nested bounded repeats let the int product tlen * qn->lower overflow, so
the check that keeps the compiler from unrolling a large repeat could pass
for an enormous one, hanging the compile or emitting wrapped jump offsets.
Compare against the limit by division instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
[PATCH] Fix buffer overflow in Array#values_at
Array#values_at can execute arbitrary code during execution which may
modify the source array. This can cause a buffer overflow if the new length
of the array has decreased. The following script crashes:
a = (0..100_000).to_a
obj = Object.new
obj.define_singleton_method(:begin) do
a.clear
0
end
obj.define_singleton_method(:end) { 10_000 }
obj.define_singleton_method(:exclude_end?) { false }
puts a.values_at(obj).length
The buffer is copied over based on the new string's termlen
so we can just set the new encoding raw
instead of recalculating based on the old encoding's termlen.
The result jobs in macOS workflows only check if other jobs failed
(just runs `exit 1`). They don't need macOS runners. Using
ubuntu-latest instead frees up macOS runner slots and starts faster
due to shorter queue times.
[PATCH] [Bug #22284] Fix curry with a non-Fixnum arity on LLP64
FIX2INT does not call to_int where int and long have the same size, so `method(:puts).curry(1r)` waited for a garbage number of arguments on Windows. Convert the arity once and keep the result as a Fixnum, which also limits the to_int call to one.
[PATCH] [Bug #22284] Let NUM2INT handle Fixnum arity of curry
NUM2INT already takes the Fixnum path by itself.
[PATCH] [Bug #22284] Test curry with an arity that responds to to_int
It also counts the to_int calls, so it fails on LP64 platforms too, where the old code converted the arity again on every call.
[PATCH] Fix IO#gets ignoring the limit argument
IO#gets first searches for separator candidates by looking for the last
byte of the record separator, and then checks whether it is an actual
match.
If a candidate is found at limit-th byte of the stream but is rejected
because it does not start at a character boundary, the limit check is
incorrectly skipped.
[Bug #22342]
[PATCH] [Bug] win32: create directory symlinks for relative directory targets
File.symlink decided whether to create a directory symlink by calling
GetFileAttributesW on the target as given, which resolves a relative
target against the current directory instead of the link's directory.
When the two differ, a relative target pointing at a directory became a
file symlink, so Dir operations on it failed. Resolve relative targets
against the link's directory before testing for a directory.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
[PATCH] win32: use MEMCPY for overflow-safe copy in w32_symlink
MEMCPY checks the size calculation for overflow, unlike a raw memcpy.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
[PATCH] [Bug] win32: do not resolve a root-relative symlink target against the link dir
A target starting with a single backslash is relative to the root of the
current drive, not to the link's directory. Treat any backslash-leading
target (root-relative or UNC) as non-relative when deciding the directory
flag; otherwise w32_symlink builds a malformed path and fails to detect
the directory attribute.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
[PATCH] win32: rename the symlink target flag to independent
The flag also covers drive-relative and root-relative targets, which are
not absolute paths. "independent" describes that the target is not
interpreted relative to the link's directory.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
[PATCH] [Bug #22335] Use identity hashes for keyword argument tracking
With more than 31 keyword arguments, tracking unevaluated defaults
uses an integer-keyed Hash whose collisions can invoke methods when
`Integer#==` is refined or redefined, corrupting argument locals above
`cfp->sp`. Use identity hashes to prevent these calls during argument
setup and `checkkeyword` execution.
Making `rb_any_cmp` compare special constants directly would also
bypass redefined `eql?` methods in ordinary hashes. Limit identity
comparison to the internal keyword tracking hashes to preserve that
behavior for now.
[PATCH] [Bug #22340] Fix stat on symbolic-link loops on Windows
Preserve `ELOOP` instead of hiding symbolic-link resolution errors
with the file-enumeration fallback, so existence checks return false.
[PATCH] Fix memory leak of transcoding when fallback raises
When the fallback function in transcode_loop raises, it will leak the memory
in rb_econv_t. The following script reproduces the leak:
10.times do
100_000.times do
"\ufffd".encode(Encoding::US_ASCII, fallback: proc { raise })
rescue
end
puts `ps -o rss= -p #{$$}`
end
Before:
451196
889980
1328508
1767676
2206460
2645372
3083900
3522428
3960956
4399484
After:
12508
12636
12892
12892
13148
13404
13532
13788
13916
13916
[PATCH] Fix use-after-free in String#encode when string modified
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
[Bug #22330]
The following script demonstrates a use-afer-free where we see corruption:
s = "あ" * 10_000
s.encode("US-ASCII", fallback: proc { |c| s.clear; "?" })
Raises may different errors such as:
"\xCB" followed by "\xF5" on UTF-8 (Encoding::InvalidByteSequenceError)
"\xF4" followed by "r" on UTF-8 (Encoding::InvalidByteSequenceError)
"\x8B" on UTF-8 (Encoding::InvalidByteSequenceError)
[PATCH] Fix out-of-bounds in Array#permutation when array modified
Calling to_int on the count may run Ruby code that modifies the array.
This can cause an out-of-bounds in Array#permutation. For example, the
following code crashes:
ary = (1..1_000).to_a
obj = Object.new
obj.define_singleton_method(:to_int) do
ary.replace(Array.new(5) { :x })
2
end
ary.permutation(obj) { }
[PATCH] Fix buffer overflow in String#rpartition
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The conversion of the pattern to string can cause the string to be modified.
This can cause a buffer overflow since it uses the original length of the
string. For example, the following script reports a buffer overflow with
ASAN:
s = "héllo" * 1000
obj = Object.new
obj.define_singleton_method(:to_str) do
s.replace("hé")
"l"
end
p s.rpartition(obj)
[PATCH] Fix use-after-free in String#% when array modified
If the array object passed into String#% is modified during conversion
(such as via to_s, to_i, to_f, etc.) then it can be a use-after-free if
the buffer of the array is freed.
This commit changes it to detect if the buffer has been changed and raises
an error when that happens.
The following script reproduces the crash:
count = 1_000
obj = Object.new
ary = [obj, *Array.new(count - 1) { "x" }]
obj.define_singleton_method(:to_s) do
ary.replace([])
"X"
end
str = "%s" * count
puts (str % ary).length
[PATCH] Fix out-of-bounds access in String#byteindex/byterindex
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
If the source string is modified when converting offset to an integer,
then there could be an out-of-bounds access because the length of the
string is captured before to_int is called. For example, the following
script triggers an ASAN error:
s = "héllo" * 1_000_000
obj = Object.new
obj.define_singleton_method(:to_int) do
s.replace("é" * 1000)
450000
end
p s.byteindex("l", obj)
[PATCH] Raise the ModGC `make check` timeout for MMTk to 90 minutes
MMTk's `make check` on macos-26 takes 27 minutes at the median and 37 at the 90th percentile, so the default 40 minute step limit sits inside the distribution of successful runs. Of the 61 MMTk macos-26 `make check` failures in the 1000 ModGC runs between 2026-09-04 and 09-17, 26 are the step timing out with no test failure at all. 90 minutes matches what macos.yml already gives the same step on the same runner.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
[PATCH] Add timeout scale to macOS ModGC CI jobs
Same fix as commit 8e8682fc23, which added
RUBY_TEST_TIMEOUT_SCALE=10 to all macOS check jobs in macos.yml but did
not cover modgc.yml. assert_separately subprocess assertions flake on
slower/contended macOS runners, especially with the slower MMTk GC.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[PATCH] Fix crash when returning splat
[Bug #22334]
If we return a with a keyword splat, then each element does not necessarily
push two values onto the stack, so we cannot compile this as a simple sequence
of key-value pairs combined with newhash. The following script reproduces
the issue and crashes:
def r
return **{foo: 1}
end
puts r()
[PATCH] Fix Method#inspect when klass is iclass
[Bug #22321]
In the following script, the klass of the Method object is an iclass, which
will raise an error "NoMethodError: undefined method 'inspect' for an instance of M":
module M
def foo = 1
end
class C
include M
end
obj = C.new
meth = obj.method(:foo).unbind.bind(obj)
puts meth.inspect
[PATCH] Fix use-after-free when clearing array during zip
[Bug #22319]
Array#zip triggers an use-after-free it does not account for when the call
to rb_check_array_type modifies the source array. The following script crashes:
a = (1..100_000).to_a
evil = Object.new; $a = a
def evil.to_ary; $a.clear; [1,2,3]; end
a.zip(evil)
[PATCH] Fix use-after-free when clearing array during flatten
[Bug #22318]
Array#flatten triggers an use-after-free it does not account for when the
call to rb_check_array_type modifies the source array. The following script
crashes:
a = (1..100_000).to_a
$a = a
bad = Object.new
def bad.to_ary; $a.clear; [1,2,3]; end
a << bad
a.flatten
[PATCH] Fix crash when source string modified in String#unpack
[Bug #22315]
The following script crashes because there is a use-after-free on the
source string during String#unpack:
s = "C" * 4000000
s.unpack("L*") { s.clear }
String#unpack will now instead raise RuntimeError with message "string modified".
[PATCH] Fix the `intern` VM instruction to be `leaf=false` (also in YJIT, ZJIT)
rb_str_intern can raise EncodingError and call its initializer.
This call can push a Ruby frame.
Fix in the VM, YJIT, and ZJIT.
[PATCH] Fix splicing an array that shares its buffer with a longer array
Arrays sharing one buffer have the same data pointer, so rb_ary_splice()
took `a` in `b = a.dup; b.pop; b.concat(a)` for `b` itself and rebased
the source onto `b`'s shorter storage, copying uninitialized memory.
Have the callers tell whether the replacement really is `ary`.
[Bug #22259]
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>