Bug #22110
closedRuby::Box C-extension loading can be denied by predictable temporary filename
Description
Ruby::Box is experimental, but I found a reproducible local hardening issue in its native extension loading path.
When a C extension is required inside a Ruby::Box, Ruby copies the .so to a temporary filename derived from predictable values:
Because the filename has no random component, another same-host local user can pre-create the path before the victim process loads the extension. The victim’s open(..., O_CREAT|O_EXCL, ...) call then fails, and require raises LoadError.
This does not allow code injection. O_CREAT|O_EXCL correctly rejects pre-existing files and symlinks. The impact is only denial of Ruby::Box C-extension loading.
Environment:
Ruby 4.1.0-dev @ 533abc7ded
Ruby::Box enabled with RUBY_BOX=1
Linux x86_64
Reproduction:
From a Ruby build tree with ./ruby available:
Observed:
The attacker process discovers the victim PID, pre-creates likely paths under /tmp, and the victim fails with:
LoadError: can't prepare the extension file for Ruby Box (.../_ruby_box_p<pid>_4_fcntl.so from .../fcntl.so): can't open the file to write
Control:
Without pre-creating the path, the same Ruby::Box extension require succeeds.
Expected:
A third-party same-host user should not be able to deny Ruby::Box C-extension loading by pre-creating a predictable temp filename.
Suggested fix:
Use an unpredictable temporary filename, for example mkstemp or a random suffix, preferably inside a private per-process directory with 0700 permissions. The copied extension file can also use mode 0600 instead of mirroring the source .so permissions.
Notes:
This is not a sandbox escape, not code execution, not privilege escalation, and not information disclosure. It is a local denial/hardening issue for an experimental opt-in feature.
Files
Updated by mame (Yusuke Endoh) 3 months ago
- Status changed from Open to Assigned
- Assignee set to tagomoris (Satoshi Tagomori)
Updated by hsbt (Hiroshi SHIBATA) 9 days ago
- Related to Misc #22275: Ruby::Box support plan for RubyGems and Bundler added
Updated by hsbt (Hiroshi SHIBATA) 6 days ago
- Status changed from Assigned to Closed
This is fixed by https://github.com/ruby/ruby/pull/18536, merged as commit 6eba15864c4. Extension copies now go into a process-private 0700 directory with a random suffix in its name, so another local user can no longer pre-create the destination path. See also #22271, which was fixed by the same change.
Updated by hsbt (Hiroshi SHIBATA) 6 days ago
- Related to Bug #22271: Ruby::Box fails to load C extensions from deep paths (temporary file name exceeds NAME_MAX) added