Skip to content

Commit 75de2be

Browse files
committed
Load library with enforced local v8 symbols
1 parent 1a96f96 commit 75de2be

File tree

5 files changed

+141
-2
lines changed

5 files changed

+141
-2
lines changed

Rakefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ end
1111
task :default => [:compile, :test]
1212

1313
gem = Gem::Specification.load( File.dirname(__FILE__) + '/mini_racer.gemspec' )
14+
Rake::ExtensionTask.new( 'mini_racer_loader', gem )
1415
Rake::ExtensionTask.new( 'mini_racer_extension', gem )
1516

1617

ext/mini_racer_loader/extconf.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
require 'mkmf'
2+
3+
extension_name = 'mini_racer_loader'
4+
dir_config extension_name
5+
6+
$CPPFLAGS += " -fvisibility=hidden "
7+
8+
create_makefile extension_name
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
#include <ruby.h>
2+
#include <dlfcn.h>
3+
#include <string.h>
4+
#include <stdint.h>
5+
#include <stdlib.h>
6+
7+
// Load a Ruby extension like Ruby does, only with flags that:
8+
// a) hide symbols from other extensions (RTLD_LOCAL)
9+
// b) bind symbols tightly (RTLD_DEEPBIND, when available)
10+
11+
void Init_mini_racer_loader(void);
12+
13+
static void *_dln_load(const char *file);
14+
15+
static VALUE _load_shared_lib(VALUE self, volatile VALUE fname)
16+
{
17+
(void) self;
18+
19+
// check that path is not tainted
20+
SafeStringValue(fname);
21+
22+
FilePathValue(fname);
23+
VALUE path = rb_str_encode_ospath(fname);
24+
25+
char *loc = StringValueCStr(path);
26+
void *handle = _dln_load(loc);
27+
28+
return handle ? Qtrue : Qfalse;
29+
}
30+
31+
// adapted from Ruby's dln.c
32+
#define INIT_FUNC_PREFIX ((char[]) {'I', 'n', 'i', 't', '_'})
33+
#define INIT_FUNCNAME(buf, file) do { \
34+
const char *base = (file); \
35+
const size_t flen = _init_funcname(&base); \
36+
const size_t plen = sizeof(INIT_FUNC_PREFIX); \
37+
char *const tmp = ALLOCA_N(char, plen + flen + 1); \
38+
memcpy(tmp, INIT_FUNC_PREFIX, plen); \
39+
memcpy(tmp+plen, base, flen); \
40+
tmp[plen+flen] = '\0'; \
41+
*(buf) = tmp; \
42+
} while(0)
43+
44+
// adapted from Ruby's dln.c
45+
static size_t _init_funcname(const char **file)
46+
{
47+
const char *p = *file,
48+
*base,
49+
*dot = NULL;
50+
51+
for (base = p; *p; p++) { /* Find position of last '/' */
52+
if (*p == '.' && !dot) {
53+
dot = p;
54+
}
55+
if (*p == '/') {
56+
base = p + 1;
57+
dot = NULL;
58+
}
59+
}
60+
*file = base;
61+
return (uintptr_t) ((dot ? dot : p) - base);
62+
}
63+
64+
// adapted from Ruby's dln.c
65+
static void *_dln_load(const char *file)
66+
{
67+
char *buf;
68+
const char *error;
69+
#define DLN_ERROR() (error = dlerror(), strcpy(ALLOCA_N(char, strlen(error) + 1), error))
70+
71+
void *handle;
72+
void (*init_fct)(void);
73+
74+
INIT_FUNCNAME(&buf, file);
75+
76+
#ifndef RTLD_DEEPBIND
77+
# define RTLD_DEEPBIND 0
78+
#endif
79+
/* Load file */
80+
if ((handle = dlopen(file, RTLD_NOW|RTLD_LOCAL|RTLD_DEEPBIND)) == NULL) {
81+
DLN_ERROR();
82+
goto failed;
83+
}
84+
#if defined(RUBY_EXPORT)
85+
{
86+
static const char incompatible[] = "incompatible library version";
87+
void *ex = dlsym(handle, "ruby_xmalloc");
88+
if (ex && ex != (void *) &ruby_xmalloc) {
89+
90+
# if defined __APPLE__
91+
/* dlclose() segfaults */
92+
rb_fatal("%s - %s", incompatible, file);
93+
# else
94+
dlclose(handle);
95+
error = incompatible;
96+
goto failed;
97+
#endif
98+
}
99+
}
100+
# endif
101+
102+
init_fct = (void (*)(void)) dlsym(handle, buf);
103+
if (init_fct == NULL) {
104+
error = DLN_ERROR();
105+
dlclose(handle);
106+
goto failed;
107+
}
108+
109+
/* Call the init code */
110+
(*init_fct)();
111+
112+
return handle;
113+
114+
failed:
115+
rb_raise(rb_eLoadError, "%s", error);
116+
}
117+
118+
__attribute__((visibility("default"))) void Init_mini_racer_loader()
119+
{
120+
VALUE mSqreen = rb_define_module("MiniRacer");
121+
VALUE mPrvExtLoader = rb_define_module_under(mSqreen, "Loader");
122+
rb_define_singleton_method(mPrvExtLoader, "load", _load_shared_lib, 1);
123+
}

lib/mini_racer.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
require "mini_racer/version"
2-
require "mini_racer_extension"
2+
require "mini_racer_loader"
3+
4+
ext_filename = "mini_racer_extension.#{RbConfig::CONFIG['DLEXT']}"
5+
ext_path = $LOAD_PATH.map { |p| (Pathname.new(p) + ext_filename) }.find { |p| p.file? }
6+
7+
raise LoadError, "Could not find #{name}" unless ext_path
8+
MiniRacer::Loader.load(ext_path.to_s)
9+
310
require "thread"
411
require "json"
512

mini_racer.gemspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Gem::Specification.new do |spec|
3535
spec.add_dependency 'libv8', MiniRacer::LIBV8_VERSION
3636
spec.require_paths = ["lib", "ext"]
3737

38-
spec.extensions = ["ext/mini_racer_extension/extconf.rb"]
38+
spec.extensions = ["ext/mini_racer_loader/extconf.rb", "ext/mini_racer_extension/extconf.rb"]
3939

4040
spec.required_ruby_version = '>= 2.3'
4141
end

0 commit comments

Comments
 (0)