Bug #10776
closedRuby Chooses Incorrect Load Path For rubygems.rb
Description
Problem¶
I believe this problem affects version 1.9.3 and up based on a git blame, but I haven't actually checked them.
The following conditions need to all be met:
- Ruby must be compiled without
--enable-shared
- argv[0] to ruby must simply be
ruby
And either one of the following need to be met:
- Your PATH path must include a directory that has a directory named ruby before where ruby is located
- The ruby binary is located in a directory named ruby (or any set of subdirectories). Eg:
/test/ruby/bin/ruby
When you then try and execute ruby, it detects the wrong ruby install directory and fails to correctly load rubygems.rb
- an example strace:
open("/home/vagrant/compiled/lib/ruby/site_ruby/2.0.0/rubygems.rb", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/home/vagrant/compiled/lib/ruby/site_ruby/2.0.0/x86_64-linux/rubygems.rb", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/home/vagrant/compiled/lib/ruby/site_ruby/rubygems.rb", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/home/vagrant/compiled/lib/ruby/vendor_ruby/2.0.0/rubygems.rb", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/home/vagrant/compiled/lib/ruby/vendor_ruby/2.0.0/x86_64-linux/rubygems.rb", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/home/vagrant/compiled/lib/ruby/vendor_ruby/rubygems.rb", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/home/vagrant/compiled/lib/ruby/2.0.0/rubygems.rb", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/home/vagrant/compiled/lib/ruby/2.0.0/x86_64-linux/rubygems.rb", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
write(2, "<internal:gem_prelude>:1:in `req"..., 37<internal:gem_prelude>:1:in `require') = 37
write(2, ": ", 2: ) = 2
write(2, "cannot load such file -- rubygem"..., 36cannot load such file -- rubygems.rb) = 36
(Notice it is looking in /home/vagrant/compiled/lib
instead of /home/vagrant/compiled/ruby/lib
)
Reproduction¶
$ LDFLAGS='-Wl,-rpath=\$$ORIGIN/../lib' ./configure --with-out-ext=tk --with-out-ext=tcl --disable-pthread --enable-load-relative --disable-install-doc --prefix=/home/vagrant/compiled/ruby
$ make
$ make test
$ make install
$ cd /home/vagrant/compiled
$ PATH=":/home/vagrant/appinstall/ruby/bin" ruby
<internal:gem_prelude>:1:in `require': cannot load such file -- rubygems.rb (LoadError)
from <internal:gem_prelude>:1:in `<compiled>'
FYI this bug cannot be reproduced through GDB by looking directly at /home/vagrant/appinstall/ruby/bin/ruby
because GDB forces argv[0]
to be the full path. You'll need to either patch GDB or use the C program in the Ongoing Problem
section to use GDB to debug.
Patch¶
The following patch fixes the 99% case:
diff --git a/dln_find.c b/dln_find.c
index 56a1981..74beddd 100644
--- a/dln_find.c
+++ b/dln_find.c
@@ -278,11 +278,10 @@ dln_find_1(const char *fname, const char *path, char *fbuf, size_t size,
}
#endif /* _WIN32 or __EMX__ */
- if (stat(fbuf, &st) == 0) {
+ if (stat(fbuf, &st) == 0 && !S_ISDIR(st.st_mode)) {
if (exe_flag == 0) return fbuf;
/* looking for executable */
- if (!S_ISDIR(st.st_mode) && eaccess(fbuf, X_OK) == 0)
- return fbuf;
+ if (eaccess(fbuf, X_OK) == 0) return fbuf;
}
next:
/* if not, and no other alternatives, life is bleak */
How? dln_find_file_r
is called by ruby_init_loadpath_safe
to locate where the ruby binary itself is located. However dln_find_file_r
calls dln_find_1
which in turn can return a directory. This patch changes dln_find_1
to only ever return a file. I couldn't find a case of dln_find_file_r
being expected to return a directory.
Ongoing Problem¶
As I mentioned, the patch only fixes the 99% case - the code inherently is broken by relying on path. Take for example this C program:
#include <unistd.h>
int main() {
char *const argv[] = {"ruby", NULL};
execve("/home/vagrant/compiled/ruby/bin/ruby", argv, NULL);
return 0; // not reached
}
Compile and run it inside of /home/vagrant/compiled
to reproduce.
It would probably be better to look at /proc/self/exe
when possible, but that would be more of a serious change
Updated by nobu (Nobuyoshi Nakada) over 10 years ago
- Status changed from Open to Feedback
Alex Coomans wrote:
The following conditions need to all be met:
- Ruby must be compiled without
--enable-shared
- argv[0] to ruby must simply be
ruby
And either one of the following need to be met:
- Your PATH path must include a directory that has a directory named ruby before where ruby is located
- The ruby binary is located in a directory named ruby (or any set of subdirectories). Eg:
/test/ruby/bin/ruby
When you then try and execute ruby, it detects the wrong ruby install directory and fails to correctly load
rubygems.rb
- an example strace:
I can't reproduce it with the latest 2.2
$ ls ~/ruby
2.2.0
$ (PATH=$HOME exec -a ruby ./ruby/2.2.0/bin/ruby -ve 'p RbConfig::CONFIG["configure_args"]')
ruby 2.2.0p36 (2015-01-22 revision 49375) [x86_64-linux]
" '-C' '--enable-shared' '--enable-load-relative' '--prefix=/' '--with-destdir=$(HOME)/ruby/$(RUBY_PROGRAM_VERSION)' '--disable-install-doc' '--without-ext=*win32*'"
Updated by nobu (Nobuyoshi Nakada) over 10 years ago
- Status changed from Feedback to Assigned
- Assignee set to usa (Usaku NAKAMURA)
- Backport changed from 2.0.0: UNKNOWN, 2.1: UNKNOWN, 2.2: UNKNOWN to 2.0.0: REQUIRED, 2.1: DONTNEED, 2.2: DONTNEED
It could reproduce only with 2.0, but not 2.1 or later.
$ (PATH=$HOME exec -a ruby ./ruby/2.0.0/bin/ruby -ve 'p RbConfig::CONFIG["configure_args"]')
ruby 2.0.0p617 (2015-01-22 revision 49382) [x86_64-linux]
<internal:gem_prelude>:1:in `require': cannot load such file -- rubygems.rb (LoadError)
from <internal:gem_prelude>:1:in `<compiled>'
Updated by nobu (Nobuyoshi Nakada) over 10 years ago
- Status changed from Assigned to Feedback
- Assignee deleted (
usa (Usaku NAKAMURA)) - Priority changed from Normal to 3
- Backport changed from 2.0.0: REQUIRED, 2.1: DONTNEED, 2.2: DONTNEED to 2.0.0: UNKNOWN, 2.1: UNKNOWN, 2.2: UNKNOWN
Sorry, it was my configuration miss, --prefix=/
doesn't work for 2.0 and should be --prefix=/.
.
So I can't reproduce it totally.
Updated by nobu (Nobuyoshi Nakada) over 10 years ago
It seems a very platform (OS, libc) dependent issue.
What's your OS, kernel version, and libc version?
Updated by nobu (Nobuyoshi Nakada) over 10 years ago
Alex Coomans wrote:
- Ruby must be compiled without
--enable-shared
Sorry, I misread this "without" as "with".
Updated by nobu (Nobuyoshi Nakada) over 10 years ago
- Status changed from Feedback to Closed
- % Done changed from 0 to 100
Applied in changeset r49391.
dln_find.c: regular files only
- dln_find.c (dln_find_1): search regular files only. based on
the patch by Alex Coomans in [ruby-core:67766]. [Bug #10776]
Updated by alexcoomans (Alex Coomans) over 10 years ago
Awesome, thank you for looking at this so quickly!