mini_racer 0.22.1 → 0.22.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG +12 -0
- data/README.md +7 -1
- data/ext/mini_racer_extension/mini_racer_extension.c +105 -14
- data/ext/mini_racer_extension/mini_racer_v8.cc +326 -13
- data/ext/mini_racer_extension/mini_racer_v8.h +4 -0
- data/lib/mini_racer/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 700f0533786f28a992f2b46a0790ed557c36255d8b7357aecfd3fa1bfd4a01a9
|
|
4
|
+
data.tar.gz: f45170bb718eeff6e2b5d6f83af6039d178f36541983ab20c31e1dbfab83f7cf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ae2e7f4e68826ce1115c933cc17835cc1d32155de483f00a3269b0669fb25198d648aa87fc3ac49b7496c23015b080ea2e9c8e57b061bd4c115716b0180ad6a7
|
|
7
|
+
data.tar.gz: 4d5a790f98965015cfc97138fe0e8a9e5bbfd11c0f0f67499f8780d0cdee575105f4c7deaf310b6d5937c6fe56f05b9f7548f0d998ea0658c98970fb72b4dc14
|
data/CHANGELOG
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
- 0.22.2 - 23-10-2026
|
|
2
|
+
- Fix nested Symbols, functions, and Map keys aborting the process during `eval`/`call` serialization (#447)
|
|
3
|
+
- Fix attached callback result conversion raising inside the V8 reply path, which could hang the context; conversion errors now propagate normally
|
|
4
|
+
- Fix callback exceptions whose `to_s` raises or returns a non-string, including reentrant `to_s`, hanging the callback roundtrip
|
|
5
|
+
- Bound Ruby to JavaScript serialization depth and fix an off-by-one in the JavaScript to Ruby deserializer stack
|
|
6
|
+
- Fix termination state leaking from error formatting or serialization callbacks into subsequent evaluations
|
|
7
|
+
- Fix `Snapshot#warmup!` aborting for an empty loaded snapshot (#449)
|
|
8
|
+
- Fix `Snapshot#warmup!` aborting when warmup creates Intl objects (#450); snapshot creation now uses the same handle scoping as V8's warmup implementation
|
|
9
|
+
- Reject runtime Intl access in `Snapshot.new` scripts with `MiniRacer::SnapshotError` instead of aborting the process
|
|
10
|
+
- Validate loaded snapshot versions, headers, section offsets, and checksums, raising `MiniRacer::SnapshotError` for malformed or incompatible blobs instead of terminating the process (#448)
|
|
11
|
+
- Make `Snapshot#dump` return a copy so mutating it cannot corrupt the stored snapshot
|
|
12
|
+
|
|
1
13
|
- 0.22.1 - 27-08-2026
|
|
2
14
|
- Fix building with Clang 22+ and big-endian bigint serialization by making bigint serialization byte-oriented instead of relying on native `uint64_t`/`unsigned long` representations
|
|
3
15
|
- Fix Ruby integers at or above 512 bits being silently truncated when passed to JavaScript, and large JavaScript bigints producing an invalid internal value when returned to Ruby
|
data/README.md
CHANGED
|
@@ -210,6 +210,12 @@ Snapshots can come in handy for example if you want your contexts to be pre-load
|
|
|
210
210
|
> are fixed once the snapshot has been captured. They are no longer really random
|
|
211
211
|
> nor reflect the current time.
|
|
212
212
|
|
|
213
|
+
Snapshot scripts passed to `Snapshot.new` that read or write `Intl` are
|
|
214
|
+
rejected with `MiniRacer::SnapshotError`: V8 cannot serialize Intl's foreign
|
|
215
|
+
handles, and allowing them to reach snapshot creation aborts the process.
|
|
216
|
+
`Snapshot#warmup!` may use Intl because its temporary context is discarded
|
|
217
|
+
before the snapshot is created.
|
|
218
|
+
|
|
213
219
|
Also note that snapshots can be warmed up, using the `warmup!` method, which allows you to call functions which are otherwise lazily compiled to get them to compile right away; any side effect of your warm up code being then dismissed. [More details on warming up here](https://github.com/electron/electron/issues/169#issuecomment-76783481), and a small example:
|
|
214
220
|
|
|
215
221
|
```ruby
|
|
@@ -242,7 +248,7 @@ context.eval("foo")
|
|
|
242
248
|
|
|
243
249
|
Note that snapshots are architecture and V8-version specific. A snapshot created on one platform (e.g., ARM64 macOS) cannot be loaded on a different platform (e.g., x86_64 Linux). Snapshots are best used for same-machine caching or homogeneous deployment environments.
|
|
244
250
|
|
|
245
|
-
**Security note:** Only load snapshots from trusted sources. V8 snapshots are not designed to be safely loaded from untrusted input—malformed or malicious snapshot data may cause crashes or memory corruption.
|
|
251
|
+
**Security note:** Only load snapshots from trusted sources. V8 snapshots are not designed to be safely loaded from untrusted input—malformed or malicious snapshot data may cause crashes or memory corruption. `Snapshot.load` performs basic version, header, section-offset, and checksum validation, but this is not a sandbox.
|
|
246
252
|
|
|
247
253
|
### Garbage collection
|
|
248
254
|
|
|
@@ -64,6 +64,7 @@ static inline void rb_thread_lock_native_thread(void)
|
|
|
64
64
|
#define endof(x) ((x) + countof(x))
|
|
65
65
|
#define BIGINT_STACK_WORDS 64
|
|
66
66
|
#define BIGINT_MAX_BYTES (16 * 1024 * 1024)
|
|
67
|
+
#define SERIALIZE_MAX_DEPTH 512
|
|
67
68
|
|
|
68
69
|
// mostly RO: assigned once by platform_set_flag1 while holding |flags_mtx|,
|
|
69
70
|
// from then on read-only and accessible without holding locks
|
|
@@ -138,6 +139,7 @@ typedef struct Context
|
|
|
138
139
|
struct State *pst; // used by v8 thread
|
|
139
140
|
VALUE procs; // array of js -> ruby callbacks
|
|
140
141
|
VALUE exception; // pending exception or Qnil
|
|
142
|
+
int pending_jump_tag; // non-exception jump to replay after the roundtrip
|
|
141
143
|
Buf req, res; // ruby->v8 request/response, mediated by |mtx| and |cv|
|
|
142
144
|
Buf v8_req; // stable v8-side copy of a request returned by v8_roundtrip
|
|
143
145
|
int res_ready; // protected by |mtx|; response may be filled before ready
|
|
@@ -289,7 +291,7 @@ static void push(DesCtx *c, VALUE v)
|
|
|
289
291
|
{
|
|
290
292
|
if (*c->err)
|
|
291
293
|
return;
|
|
292
|
-
if (c->tos == endof(c->stack)) {
|
|
294
|
+
if (c->tos == endof(c->stack) - 1) {
|
|
293
295
|
snprintf(c->err, sizeof(c->err), "stack overflow");
|
|
294
296
|
return;
|
|
295
297
|
}
|
|
@@ -613,13 +615,15 @@ static int serialize_bigint(Ser *s, VALUE v)
|
|
|
613
615
|
return *s->err ? -1 : 0;
|
|
614
616
|
}
|
|
615
617
|
|
|
616
|
-
static int serialize1(Ser *s, VALUE refs, VALUE v)
|
|
618
|
+
static int serialize1(Ser *s, VALUE refs, VALUE v, int depth)
|
|
617
619
|
{
|
|
618
620
|
VALUE a, t, id;
|
|
619
621
|
size_t i, n;
|
|
620
622
|
|
|
621
623
|
if (*s->err)
|
|
622
624
|
return -1;
|
|
625
|
+
if (depth > SERIALIZE_MAX_DEPTH)
|
|
626
|
+
return bail(&s->err, "max depth exceeded");
|
|
623
627
|
switch (TYPE(v)) {
|
|
624
628
|
case T_ARRAY:
|
|
625
629
|
{
|
|
@@ -631,7 +635,7 @@ static int serialize1(Ser *s, VALUE refs, VALUE v)
|
|
|
631
635
|
rb_hash_aset(refs, obj_id, LONG2FIX(i));
|
|
632
636
|
ser_array_begin(s, n);
|
|
633
637
|
for (i = 0; i < n; i++)
|
|
634
|
-
if (serialize1(s, refs, rb_ary_entry(v, i)))
|
|
638
|
+
if (serialize1(s, refs, rb_ary_entry(v, i), depth + 1))
|
|
635
639
|
return -1;
|
|
636
640
|
ser_array_end(s, n);
|
|
637
641
|
} else {
|
|
@@ -662,9 +666,9 @@ static int serialize1(Ser *s, VALUE refs, VALUE v)
|
|
|
662
666
|
if (i == 2*n) {
|
|
663
667
|
ser_object_begin(s);
|
|
664
668
|
for (i = 0; i < 2*n; i += 2) {
|
|
665
|
-
if (serialize1(s, refs, rb_ary_entry(a, i+0)))
|
|
669
|
+
if (serialize1(s, refs, rb_ary_entry(a, i+0), depth + 1))
|
|
666
670
|
return -1;
|
|
667
|
-
if (serialize1(s, refs, rb_ary_entry(a, i+1)))
|
|
671
|
+
if (serialize1(s, refs, rb_ary_entry(a, i+1), depth + 1))
|
|
668
672
|
return -1;
|
|
669
673
|
}
|
|
670
674
|
ser_object_end(s, n);
|
|
@@ -748,7 +752,7 @@ static int serialize1(Ser *s, VALUE refs, VALUE v)
|
|
|
748
752
|
// that will throw off the object reference count
|
|
749
753
|
static int serialize(Ser *s, VALUE v)
|
|
750
754
|
{
|
|
751
|
-
return serialize1(s, rb_hash_new(), v);
|
|
755
|
+
return serialize1(s, rb_hash_new(), v, 0);
|
|
752
756
|
}
|
|
753
757
|
|
|
754
758
|
static struct timespec deadline_ms(int ms)
|
|
@@ -1025,11 +1029,54 @@ static VALUE rendezvous_callback_do(VALUE arg)
|
|
|
1025
1029
|
return rb_funcall2(func, rb_intern("call"), RARRAY_LENINT(args), RARRAY_PTR(args));
|
|
1026
1030
|
}
|
|
1027
1031
|
|
|
1032
|
+
struct callback_serialize_args
|
|
1033
|
+
{
|
|
1034
|
+
Ser *ser;
|
|
1035
|
+
VALUE value;
|
|
1036
|
+
};
|
|
1037
|
+
|
|
1038
|
+
static VALUE callback_serialize(VALUE arg)
|
|
1039
|
+
{
|
|
1040
|
+
struct callback_serialize_args *args;
|
|
1041
|
+
args = (void *)arg;
|
|
1042
|
+
return INT2FIX(serialize(args->ser, args->value));
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
static VALUE callback_exception_to_s(VALUE exception)
|
|
1046
|
+
{
|
|
1047
|
+
VALUE message;
|
|
1048
|
+
message = rb_funcall(exception, rb_intern("to_s"), 0);
|
|
1049
|
+
StringValue(message);
|
|
1050
|
+
return message;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
static VALUE callback_exception_from_state(int state, int *pending_jump_tag)
|
|
1054
|
+
{
|
|
1055
|
+
VALUE exception = rb_errinfo();
|
|
1056
|
+
rb_set_errinfo(Qnil);
|
|
1057
|
+
if (RB_TYPE_P(exception, T_OBJECT) &&
|
|
1058
|
+
rb_obj_is_kind_of(exception, rb_eException)) {
|
|
1059
|
+
return exception;
|
|
1060
|
+
}
|
|
1061
|
+
// Thread#kill and other non-exception jumps leave a sentinel or internal
|
|
1062
|
+
// throw data in errinfo; do not hand that to rb_funcall/rb_exc_raise.
|
|
1063
|
+
// Replay the jump once the V8 roundtrip has fully unwound.
|
|
1064
|
+
if (state && pending_jump_tag && !*pending_jump_tag)
|
|
1065
|
+
*pending_jump_tag = state;
|
|
1066
|
+
return Qnil;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
static VALUE callback_internal_error(VALUE message)
|
|
1070
|
+
{
|
|
1071
|
+
return rb_exc_new_cstr(internal_error, (const char *)message);
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1028
1074
|
// called with |rr_mtx| and GVL held; |mtx| is unlocked
|
|
1029
1075
|
// callback data is in |a->res|, serialized result goes in |a->req|
|
|
1030
1076
|
static void *rendezvous_callback(void *arg)
|
|
1031
1077
|
{
|
|
1032
1078
|
struct rendezvous_nogvl *a;
|
|
1079
|
+
struct callback_serialize_args args;
|
|
1033
1080
|
const char *err;
|
|
1034
1081
|
Context *c;
|
|
1035
1082
|
int exc;
|
|
@@ -1040,13 +1087,29 @@ static void *rendezvous_callback(void *arg)
|
|
|
1040
1087
|
c = a->context;
|
|
1041
1088
|
r = rb_protect(rendezvous_callback_do, (VALUE)a, &exc);
|
|
1042
1089
|
if (exc) {
|
|
1043
|
-
c->exception =
|
|
1044
|
-
rb_set_errinfo(Qnil);
|
|
1090
|
+
c->exception = callback_exception_from_state(exc, &c->pending_jump_tag);
|
|
1045
1091
|
goto fail;
|
|
1046
1092
|
}
|
|
1047
1093
|
ser_init1(&s, 'c'); // callback reply
|
|
1048
|
-
|
|
1049
|
-
|
|
1094
|
+
args.ser = &s;
|
|
1095
|
+
args.value = r;
|
|
1096
|
+
r = rb_protect(callback_serialize, (VALUE)&args, &exc);
|
|
1097
|
+
if (exc) {
|
|
1098
|
+
// A Ruby conversion hook, Check_Type(), or stack overflow escaped
|
|
1099
|
+
// serialize(). Keep the V8 thread waiting for a reply and turn it
|
|
1100
|
+
// into a normal callback exception instead.
|
|
1101
|
+
c->exception = callback_exception_from_state(exc, &c->pending_jump_tag);
|
|
1102
|
+
ser_reset(&s);
|
|
1103
|
+
goto fail;
|
|
1104
|
+
}
|
|
1105
|
+
if (NUM2INT(r)) {
|
|
1106
|
+
VALUE exception = rb_protect(callback_internal_error,
|
|
1107
|
+
(VALUE)s.err, &exc);
|
|
1108
|
+
if (exc) {
|
|
1109
|
+
c->exception = callback_exception_from_state(exc, &c->pending_jump_tag);
|
|
1110
|
+
} else {
|
|
1111
|
+
c->exception = exception;
|
|
1112
|
+
}
|
|
1050
1113
|
ser_reset(&s);
|
|
1051
1114
|
goto fail;
|
|
1052
1115
|
}
|
|
@@ -1056,10 +1119,25 @@ out:
|
|
|
1056
1119
|
fail:
|
|
1057
1120
|
ser_init0(&s); // ruby exception pending
|
|
1058
1121
|
w_byte(&s, 'e'); // send ruby error message to v8 thread
|
|
1059
|
-
r =
|
|
1060
|
-
|
|
1061
|
-
|
|
1122
|
+
r = Qnil;
|
|
1123
|
+
if (!NIL_P(c->exception)) {
|
|
1124
|
+
VALUE original_exception = c->exception;
|
|
1125
|
+
r = rb_protect(callback_exception_to_s, c->exception, &exc);
|
|
1126
|
+
if (exc) {
|
|
1127
|
+
callback_exception_from_state(exc, &c->pending_jump_tag);
|
|
1128
|
+
r = Qnil;
|
|
1129
|
+
}
|
|
1130
|
+
// to_s can re-enter the context and consume c->exception. Keep the
|
|
1131
|
+
// original callback exception for the eventual Ruby-side re-raise.
|
|
1132
|
+
c->exception = original_exception;
|
|
1133
|
+
}
|
|
1134
|
+
if (NIL_P(r)) {
|
|
1135
|
+
err = "Ruby exception";
|
|
1136
|
+
w(&s, err, strlen(err));
|
|
1137
|
+
} else {
|
|
1138
|
+
err = RSTRING_PTR(r);
|
|
1062
1139
|
w(&s, err, RSTRING_LEN(r));
|
|
1140
|
+
}
|
|
1063
1141
|
goto out;
|
|
1064
1142
|
}
|
|
1065
1143
|
|
|
@@ -1350,6 +1428,12 @@ static VALUE rendezvous1(Context *c, Buf *req, DesCtx *d)
|
|
|
1350
1428
|
int exc;
|
|
1351
1429
|
|
|
1352
1430
|
rendezvous_no_des(c, req, &res); // takes ownership of |req|
|
|
1431
|
+
int jump_tag = c->pending_jump_tag;
|
|
1432
|
+
c->pending_jump_tag = 0;
|
|
1433
|
+
if (jump_tag) {
|
|
1434
|
+
buf_reset(&res);
|
|
1435
|
+
rb_jump_tag(jump_tag);
|
|
1436
|
+
}
|
|
1353
1437
|
r = c->exception;
|
|
1354
1438
|
c->exception = Qnil;
|
|
1355
1439
|
// if js land didn't handle exception from ruby callback, re-raise it now
|
|
@@ -2146,15 +2230,22 @@ static VALUE snapshot_dump(VALUE self)
|
|
|
2146
2230
|
Snapshot *ss;
|
|
2147
2231
|
|
|
2148
2232
|
TypedData_Get_Struct(self, Snapshot, &snapshot_type, ss);
|
|
2149
|
-
|
|
2233
|
+
// Callers may mutate the returned String; never expose the blob that
|
|
2234
|
+
// Context.new/snapshot_warmup later hand to V8.
|
|
2235
|
+
return rb_str_dup(ss->blob);
|
|
2150
2236
|
}
|
|
2151
2237
|
|
|
2152
2238
|
static VALUE snapshot_load(VALUE klass, VALUE blob)
|
|
2153
2239
|
{
|
|
2154
2240
|
Snapshot *ss;
|
|
2155
2241
|
VALUE self;
|
|
2242
|
+
char err[128];
|
|
2156
2243
|
|
|
2157
2244
|
Check_Type(blob, T_STRING);
|
|
2245
|
+
if (v8_snapshot_validate((const uint8_t *)RSTRING_PTR(blob),
|
|
2246
|
+
(size_t)RSTRING_LEN(blob), err, sizeof(err))) {
|
|
2247
|
+
rb_raise(snapshot_error, "%s", err);
|
|
2248
|
+
}
|
|
2158
2249
|
self = snapshot_alloc(klass);
|
|
2159
2250
|
TypedData_Get_Struct(self, Snapshot, &snapshot_type, ss);
|
|
2160
2251
|
ss->blob = rb_str_dup(blob);
|
|
@@ -276,6 +276,8 @@ bool reply(State& st, v8::Local<v8::Value> v)
|
|
|
276
276
|
Serialized serialized(st, v);
|
|
277
277
|
if (serialized.data)
|
|
278
278
|
v8_reply(st.ruby_context, serialized.data, serialized.size);
|
|
279
|
+
else
|
|
280
|
+
try_catch.ReThrow();
|
|
279
281
|
return serialized.data != nullptr; // exception pending if false
|
|
280
282
|
}
|
|
281
283
|
|
|
@@ -750,10 +752,35 @@ fail:
|
|
|
750
752
|
if (!cause && try_catch.HasCaught()) cause = RUNTIME_ERROR;
|
|
751
753
|
if (cause) result = v8::Undefined(st.isolate);
|
|
752
754
|
auto err = to_error(st, &try_catch, cause);
|
|
755
|
+
// Error formatting can run user JS (for example Error.prepareStackTrace)
|
|
756
|
+
// which may call Context#stop. Do not let that termination leak into the
|
|
757
|
+
// reply or the next evaluation.
|
|
758
|
+
bool terminated_during_error = false;
|
|
759
|
+
if (st.terminate_requested.load() || st.isolate->IsExecutionTerminating()) {
|
|
760
|
+
terminated_during_error = true;
|
|
761
|
+
preserve_termination =
|
|
762
|
+
suspend_termination(st, nested, cause) || preserve_termination;
|
|
763
|
+
}
|
|
764
|
+
if (terminated_during_error) {
|
|
765
|
+
result = v8::Undefined(st.isolate);
|
|
766
|
+
std::vector<char> buf;
|
|
767
|
+
set_error_message(buf, cause,
|
|
768
|
+
cause == MEMORY_ERROR ? "out of memory" : "terminated");
|
|
769
|
+
err = string_from_bytes(st.isolate, buf);
|
|
770
|
+
}
|
|
753
771
|
if (!reply(st, result, err)) {
|
|
754
772
|
assert(try_catch.HasCaught());
|
|
755
773
|
goto fail; // retry; can be termination exception
|
|
756
774
|
}
|
|
775
|
+
if (!nested &&
|
|
776
|
+
(st.terminate_requested.load() || st.isolate->IsExecutionTerminating())) {
|
|
777
|
+
// A callback invoked by the serializer (for example a getter) can
|
|
778
|
+
// request termination after the last suspend_termination check. Do not
|
|
779
|
+
// leak that request into the next outermost evaluation.
|
|
780
|
+
st.terminate_requested.store(0);
|
|
781
|
+
st.isolate->CancelTerminateExecution();
|
|
782
|
+
st.err_reason = NO_ERROR;
|
|
783
|
+
}
|
|
757
784
|
restore_termination(st, preserve_termination);
|
|
758
785
|
}
|
|
759
786
|
|
|
@@ -818,10 +845,35 @@ fail:
|
|
|
818
845
|
if (!cause && try_catch.HasCaught()) cause = RUNTIME_ERROR;
|
|
819
846
|
if (cause) result = v8::Undefined(st.isolate);
|
|
820
847
|
auto err = to_error(st, &try_catch, cause);
|
|
848
|
+
// Error formatting can run user JS (for example Error.prepareStackTrace)
|
|
849
|
+
// which may call Context#stop. Do not let that termination leak into the
|
|
850
|
+
// reply or the next evaluation.
|
|
851
|
+
bool terminated_during_error = false;
|
|
852
|
+
if (st.terminate_requested.load() || st.isolate->IsExecutionTerminating()) {
|
|
853
|
+
terminated_during_error = true;
|
|
854
|
+
preserve_termination =
|
|
855
|
+
suspend_termination(st, nested, cause) || preserve_termination;
|
|
856
|
+
}
|
|
857
|
+
if (terminated_during_error) {
|
|
858
|
+
result = v8::Undefined(st.isolate);
|
|
859
|
+
std::vector<char> buf;
|
|
860
|
+
set_error_message(buf, cause,
|
|
861
|
+
cause == MEMORY_ERROR ? "out of memory" : "terminated");
|
|
862
|
+
err = string_from_bytes(st.isolate, buf);
|
|
863
|
+
}
|
|
821
864
|
if (!reply(st, result, err)) {
|
|
822
865
|
assert(try_catch.HasCaught());
|
|
823
866
|
goto fail; // retry; can be termination exception
|
|
824
867
|
}
|
|
868
|
+
if (!nested &&
|
|
869
|
+
(st.terminate_requested.load() || st.isolate->IsExecutionTerminating())) {
|
|
870
|
+
// A callback invoked by the serializer (for example a getter) can
|
|
871
|
+
// request termination after the last suspend_termination check. Do not
|
|
872
|
+
// leak that request into the next outermost evaluation.
|
|
873
|
+
st.terminate_requested.store(0);
|
|
874
|
+
st.isolate->CancelTerminateExecution();
|
|
875
|
+
st.err_reason = NO_ERROR;
|
|
876
|
+
}
|
|
825
877
|
restore_termination(st, preserve_termination);
|
|
826
878
|
}
|
|
827
879
|
|
|
@@ -936,6 +988,138 @@ fail:
|
|
|
936
988
|
reply_retry(st, result);
|
|
937
989
|
}
|
|
938
990
|
|
|
991
|
+
static uint32_t read_le32(const uint8_t *p)
|
|
992
|
+
{
|
|
993
|
+
return static_cast<uint32_t>(p[0])
|
|
994
|
+
| static_cast<uint32_t>(p[1]) << 8
|
|
995
|
+
| static_cast<uint32_t>(p[2]) << 16
|
|
996
|
+
| static_cast<uint32_t>(p[3]) << 24;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
static bool snapshot_checksum_matches(const uint8_t *data, size_t n)
|
|
1000
|
+
{
|
|
1001
|
+
if (n < 13)
|
|
1002
|
+
return false;
|
|
1003
|
+
uint32_t expected = read_le32(data + 8);
|
|
1004
|
+
|
|
1005
|
+
// V8 uses zlib's adler32 when built with V8_USE_ZLIB, otherwise a
|
|
1006
|
+
// Fletcher-32 variant. Accept either so this check remains portable
|
|
1007
|
+
// across libv8-node builds. This is a corruption check, not a sandbox:
|
|
1008
|
+
// snapshots are still trusted-input-only.
|
|
1009
|
+
uint32_t s1 = 0, s2 = 0;
|
|
1010
|
+
for (size_t i = 12; i < n; i++) {
|
|
1011
|
+
s1 = (s1 + data[i]) % 65521;
|
|
1012
|
+
s2 = (s2 + s1) % 65521;
|
|
1013
|
+
}
|
|
1014
|
+
if ((s2 << 16 | s1) == expected)
|
|
1015
|
+
return true;
|
|
1016
|
+
|
|
1017
|
+
s1 = s2 = 0;
|
|
1018
|
+
for (size_t i = 12; i < n; i++) {
|
|
1019
|
+
s1 = (s1 + data[i]) % 65535;
|
|
1020
|
+
s2 = (s2 + s1) % 65535;
|
|
1021
|
+
}
|
|
1022
|
+
return (s2 << 16 | s1) == expected;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
// The header/section offsets below mirror V8 13.6's SnapshotImpl. Keep them
|
|
1026
|
+
// in sync with libv8-node; the fallback is intentionally limited to preventing
|
|
1027
|
+
// V8's fatal deserializer checks, not to sandboxing untrusted snapshots.
|
|
1028
|
+
extern "C" int v8_snapshot_validate(const uint8_t *data, size_t n,
|
|
1029
|
+
char *err, size_t errlen)
|
|
1030
|
+
{
|
|
1031
|
+
#define SNAPSHOT_INVALID(msg) \
|
|
1032
|
+
do { \
|
|
1033
|
+
if (err && errlen) \
|
|
1034
|
+
snprintf(err, errlen, "%s", msg); \
|
|
1035
|
+
return -1; \
|
|
1036
|
+
} while (0)
|
|
1037
|
+
|
|
1038
|
+
if (n == 0)
|
|
1039
|
+
return 0; // Snapshot.load("") means "use V8's built-in snapshot".
|
|
1040
|
+
if (n > static_cast<size_t>(std::numeric_limits<int>::max()))
|
|
1041
|
+
SNAPSHOT_INVALID("snapshot is too large");
|
|
1042
|
+
if (n < 88)
|
|
1043
|
+
SNAPSHOT_INVALID("snapshot is too short");
|
|
1044
|
+
|
|
1045
|
+
v8::StartupData blob{reinterpret_cast<const char*>(data),
|
|
1046
|
+
static_cast<int>(n)};
|
|
1047
|
+
if (!blob.IsValid())
|
|
1048
|
+
SNAPSHOT_INVALID("snapshot version mismatch");
|
|
1049
|
+
|
|
1050
|
+
uint32_t num_contexts = read_le32(data);
|
|
1051
|
+
uint32_t rehashability = read_le32(data + 4);
|
|
1052
|
+
if (rehashability > 1)
|
|
1053
|
+
SNAPSHOT_INVALID("snapshot header is invalid");
|
|
1054
|
+
if (num_contexts == 0)
|
|
1055
|
+
SNAPSHOT_INVALID("snapshot has no contexts");
|
|
1056
|
+
if (num_contexts > (n - 88) / 4)
|
|
1057
|
+
SNAPSHOT_INVALID("snapshot context table is invalid");
|
|
1058
|
+
|
|
1059
|
+
const size_t pointer_size = sizeof(void*);
|
|
1060
|
+
size_t header_size = 88 + static_cast<size_t>(num_contexts) * 4;
|
|
1061
|
+
size_t startup_offset =
|
|
1062
|
+
(header_size + pointer_size - 1) & ~(pointer_size - 1);
|
|
1063
|
+
if (startup_offset > n)
|
|
1064
|
+
SNAPSHOT_INVALID("snapshot context table is invalid");
|
|
1065
|
+
|
|
1066
|
+
uint32_t read_only_offset = read_le32(data + 80);
|
|
1067
|
+
uint32_t shared_heap_offset = read_le32(data + 84);
|
|
1068
|
+
if (read_only_offset <= startup_offset || read_only_offset > n)
|
|
1069
|
+
SNAPSHOT_INVALID("snapshot read-only section is invalid");
|
|
1070
|
+
if (shared_heap_offset <= read_only_offset || shared_heap_offset > n)
|
|
1071
|
+
SNAPSHOT_INVALID("snapshot shared-heap section is invalid");
|
|
1072
|
+
|
|
1073
|
+
size_t previous_offset = shared_heap_offset;
|
|
1074
|
+
for (uint32_t i = 0; i < num_contexts; i++) {
|
|
1075
|
+
uint32_t offset = read_le32(data + 88 + static_cast<size_t>(i) * 4);
|
|
1076
|
+
if (offset <= previous_offset || offset >= n)
|
|
1077
|
+
SNAPSHOT_INVALID("snapshot context section is invalid");
|
|
1078
|
+
previous_offset = offset;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
if (!snapshot_checksum_matches(data, n))
|
|
1082
|
+
SNAPSHOT_INVALID("snapshot checksum mismatch");
|
|
1083
|
+
|
|
1084
|
+
return 0;
|
|
1085
|
+
#undef SNAPSHOT_INVALID
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
// Keep all guard state in a JavaScript closure. The setup script returns a
|
|
1089
|
+
// query function to C++; do not install a native callback or External global,
|
|
1090
|
+
// because a retained property descriptor would otherwise make CreateBlob fail
|
|
1091
|
+
// with "Unknown external reference".
|
|
1092
|
+
static const char snapshot_intl_setup_source[] = R"js(
|
|
1093
|
+
;(function() {
|
|
1094
|
+
const original = globalThis.Intl
|
|
1095
|
+
let used = false
|
|
1096
|
+
const getter = function() {
|
|
1097
|
+
used = true
|
|
1098
|
+
return original
|
|
1099
|
+
}
|
|
1100
|
+
const setter = function(value) {
|
|
1101
|
+
used = true
|
|
1102
|
+
}
|
|
1103
|
+
if (!original) {
|
|
1104
|
+
const query = function() { return used }
|
|
1105
|
+
query.getter = getter
|
|
1106
|
+
return query
|
|
1107
|
+
}
|
|
1108
|
+
Object.defineProperty(globalThis, "Intl", {
|
|
1109
|
+
configurable: true,
|
|
1110
|
+
enumerable: false,
|
|
1111
|
+
get: getter,
|
|
1112
|
+
set: setter
|
|
1113
|
+
})
|
|
1114
|
+
const query = function() { return used }
|
|
1115
|
+
query.getter = getter
|
|
1116
|
+
return query
|
|
1117
|
+
})()
|
|
1118
|
+
)js";
|
|
1119
|
+
|
|
1120
|
+
static const char snapshot_intl_error[] =
|
|
1121
|
+
"Intl is not supported in Snapshot.new scripts";
|
|
1122
|
+
|
|
939
1123
|
int snapshot(bool is_warmup, bool verbose_exceptions,
|
|
940
1124
|
const v8::String::Utf8Value& code,
|
|
941
1125
|
v8::StartupData blob, v8::StartupData *result,
|
|
@@ -943,40 +1127,107 @@ int snapshot(bool is_warmup, bool verbose_exceptions,
|
|
|
943
1127
|
{
|
|
944
1128
|
// SnapshotCreator takes ownership of isolate
|
|
945
1129
|
v8::Isolate *isolate = v8::Isolate::Allocate();
|
|
946
|
-
|
|
1130
|
+
// An empty loaded snapshot means "start from V8's built-in snapshot", just
|
|
1131
|
+
// like Context.new. Passing a non-null StartupData with raw_size == 0 to
|
|
1132
|
+
// SnapshotCreator trips CHECK(!artifacts) during warmup.
|
|
1133
|
+
v8::StartupData *existing_blob =
|
|
1134
|
+
(is_warmup && blob.data && blob.raw_size > 0) ? &blob : nullptr;
|
|
947
1135
|
v8::SnapshotCreator snapshot_creator(isolate, nullptr, existing_blob);
|
|
948
1136
|
v8::Isolate::Scope isolate_scope(isolate);
|
|
949
|
-
v8::HandleScope handle_scope(isolate);
|
|
950
1137
|
v8::TryCatch try_catch(isolate);
|
|
951
1138
|
try_catch.SetVerbose(verbose_exceptions);
|
|
952
|
-
auto filename = is_warmup
|
|
953
|
-
? v8::String::NewFromUtf8Literal(isolate, "<warmup>")
|
|
954
|
-
: v8::String::NewFromUtf8Literal(isolate, "<snapshot>");
|
|
955
1139
|
auto mode = is_warmup
|
|
956
1140
|
? v8::SnapshotCreator::FunctionCodeHandling::kKeep
|
|
957
1141
|
: v8::SnapshotCreator::FunctionCodeHandling::kClear;
|
|
958
1142
|
int cause = INTERNAL_ERROR;
|
|
959
1143
|
{
|
|
1144
|
+
// Keep script-created values out of the scope that is alive when
|
|
1145
|
+
// CreateBlob() runs. V8's own warmup implementation uses separate
|
|
1146
|
+
// scopes; sharing one scope leaves Intl foreign handles visible to the
|
|
1147
|
+
// snapshot handle checker and aborts with
|
|
1148
|
+
// CheckGlobalAndEternalHandles failed.
|
|
1149
|
+
v8::HandleScope handle_scope(isolate);
|
|
1150
|
+
auto filename = is_warmup
|
|
1151
|
+
? v8::String::NewFromUtf8Literal(isolate, "<warmup>")
|
|
1152
|
+
: v8::String::NewFromUtf8Literal(isolate, "<snapshot>");
|
|
960
1153
|
auto context = v8::Context::New(isolate);
|
|
961
1154
|
v8::Context::Scope context_scope(context);
|
|
1155
|
+
auto intl_key = v8::String::NewFromUtf8Literal(isolate, "Intl");
|
|
1156
|
+
v8::Local<v8::Value> intl_original;
|
|
1157
|
+
v8::Local<v8::Function> intl_state_fn;
|
|
1158
|
+
v8::Local<v8::Function> intl_guard_getter;
|
|
1159
|
+
bool intl_guarded = false;
|
|
1160
|
+
if (!context->Global()->Get(context, intl_key).ToLocal(&intl_original)) {
|
|
1161
|
+
set_error_message(*errbuf, INTERNAL_ERROR,
|
|
1162
|
+
"failed to inspect snapshot Intl");
|
|
1163
|
+
return INTERNAL_ERROR;
|
|
1164
|
+
}
|
|
1165
|
+
if (!is_warmup && intl_original->IsObject()) {
|
|
1166
|
+
auto intl_setup_source = v8::String::NewFromUtf8Literal(
|
|
1167
|
+
isolate, snapshot_intl_setup_source);
|
|
1168
|
+
v8::Local<v8::Script> intl_setup_script;
|
|
1169
|
+
v8::Local<v8::Value> intl_state;
|
|
1170
|
+
if (!v8::Script::Compile(context, intl_setup_source)
|
|
1171
|
+
.ToLocal(&intl_setup_script) ||
|
|
1172
|
+
!intl_setup_script->Run(context).ToLocal(&intl_state) ||
|
|
1173
|
+
!intl_state->IsFunction()) {
|
|
1174
|
+
set_error_message(*errbuf, INTERNAL_ERROR,
|
|
1175
|
+
"failed to initialize snapshot Intl guard");
|
|
1176
|
+
return INTERNAL_ERROR;
|
|
1177
|
+
}
|
|
1178
|
+
intl_state_fn = intl_state.As<v8::Function>();
|
|
1179
|
+
v8::Local<v8::Value> getter;
|
|
1180
|
+
auto getter_key =
|
|
1181
|
+
v8::String::NewFromUtf8Literal(isolate, "getter");
|
|
1182
|
+
if (!intl_state_fn->Get(context, getter_key).ToLocal(&getter) ||
|
|
1183
|
+
!getter->IsFunction()) {
|
|
1184
|
+
set_error_message(*errbuf, INTERNAL_ERROR,
|
|
1185
|
+
"failed to initialize snapshot Intl guard");
|
|
1186
|
+
return INTERNAL_ERROR;
|
|
1187
|
+
}
|
|
1188
|
+
intl_guard_getter = getter.As<v8::Function>();
|
|
1189
|
+
intl_guarded = true;
|
|
1190
|
+
}
|
|
962
1191
|
v8::Local<v8::String> source;
|
|
963
1192
|
auto type = v8::NewStringType::kNormal;
|
|
964
1193
|
if (!v8::String::NewFromUtf8(isolate, *code, type, code.length()).ToLocal(&source)) {
|
|
965
1194
|
v8::String::Utf8Value s(isolate, try_catch.Exception());
|
|
966
1195
|
if (!set_error_message(*errbuf, cause, s))
|
|
967
1196
|
set_error_message(*errbuf, cause, "unexpected failure");
|
|
968
|
-
|
|
1197
|
+
return cause;
|
|
969
1198
|
}
|
|
970
1199
|
v8::ScriptOrigin origin(filename);
|
|
971
1200
|
v8::Local<v8::Script> script;
|
|
972
1201
|
cause = PARSE_ERROR;
|
|
973
1202
|
if (!v8::Script::Compile(context, source, &origin).ToLocal(&script)) {
|
|
974
|
-
|
|
1203
|
+
auto m = try_catch.Message();
|
|
1204
|
+
if (m.IsEmpty()) {
|
|
1205
|
+
set_error_message(*errbuf, cause, "unexpected failure");
|
|
1206
|
+
return cause;
|
|
1207
|
+
}
|
|
1208
|
+
v8::String::Utf8Value s(isolate, m->Get());
|
|
1209
|
+
v8::String::Utf8Value name(isolate, m->GetScriptResourceName());
|
|
1210
|
+
auto line = m->GetLineNumber(context).FromMaybe(0);
|
|
1211
|
+
auto column = m->GetStartColumn(context).FromMaybe(0);
|
|
1212
|
+
if (*s && *name) {
|
|
1213
|
+
errbuf->clear();
|
|
1214
|
+
errbuf->push_back(static_cast<char>(cause));
|
|
1215
|
+
append_utf8(*errbuf, s);
|
|
1216
|
+
append_literal(*errbuf, "\n");
|
|
1217
|
+
append_utf8(*errbuf, name);
|
|
1218
|
+
append_format(*errbuf, ":%d:%d", line, column);
|
|
1219
|
+
} else {
|
|
1220
|
+
set_error_message(*errbuf, cause, "unexpected failure");
|
|
1221
|
+
}
|
|
1222
|
+
return cause;
|
|
975
1223
|
}
|
|
976
1224
|
cause = RUNTIME_ERROR;
|
|
977
1225
|
if (script->Run(context).IsEmpty()) {
|
|
978
|
-
err:
|
|
979
1226
|
auto m = try_catch.Message();
|
|
1227
|
+
if (m.IsEmpty()) {
|
|
1228
|
+
set_error_message(*errbuf, cause, "unexpected failure");
|
|
1229
|
+
return cause;
|
|
1230
|
+
}
|
|
980
1231
|
v8::String::Utf8Value s(isolate, m->Get());
|
|
981
1232
|
v8::String::Utf8Value name(isolate, m->GetScriptResourceName());
|
|
982
1233
|
auto line = m->GetLineNumber(context).FromMaybe(0);
|
|
@@ -991,20 +1242,82 @@ int snapshot(bool is_warmup, bool verbose_exceptions,
|
|
|
991
1242
|
} else {
|
|
992
1243
|
set_error_message(*errbuf, cause, "unexpected failure");
|
|
993
1244
|
}
|
|
994
|
-
|
|
1245
|
+
return cause;
|
|
1246
|
+
}
|
|
1247
|
+
if (intl_guarded) {
|
|
1248
|
+
v8::Local<v8::Value> used;
|
|
1249
|
+
auto recv = v8::Undefined(isolate);
|
|
1250
|
+
if (!intl_state_fn->Call(context, recv, 0, nullptr)
|
|
1251
|
+
.ToLocal(&used)) {
|
|
1252
|
+
set_error_message(*errbuf, INTERNAL_ERROR,
|
|
1253
|
+
"failed to inspect snapshot Intl guard");
|
|
1254
|
+
return INTERNAL_ERROR;
|
|
1255
|
+
}
|
|
1256
|
+
if (used->IsTrue()) {
|
|
1257
|
+
set_error_message(*errbuf, RUNTIME_ERROR, snapshot_intl_error);
|
|
1258
|
+
return RUNTIME_ERROR;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
if (intl_guarded) {
|
|
1262
|
+
// Only replace our accessor. If the script deliberately replaced
|
|
1263
|
+
// or deleted Intl, leave that change in the snapshot.
|
|
1264
|
+
v8::Local<v8::Value> descriptor;
|
|
1265
|
+
if (!context->Global()
|
|
1266
|
+
->GetOwnPropertyDescriptor(context, intl_key)
|
|
1267
|
+
.ToLocal(&descriptor)) {
|
|
1268
|
+
set_error_message(*errbuf, INTERNAL_ERROR,
|
|
1269
|
+
"failed to inspect Intl after snapshot script");
|
|
1270
|
+
return INTERNAL_ERROR;
|
|
1271
|
+
}
|
|
1272
|
+
bool restore_intl = false;
|
|
1273
|
+
if (descriptor->IsObject()) {
|
|
1274
|
+
auto get_key =
|
|
1275
|
+
v8::String::NewFromUtf8Literal(isolate, "get");
|
|
1276
|
+
v8::Local<v8::Value> getter_descriptor;
|
|
1277
|
+
if (!descriptor.As<v8::Object>()
|
|
1278
|
+
->GetOwnPropertyDescriptor(context, get_key)
|
|
1279
|
+
.ToLocal(&getter_descriptor)) {
|
|
1280
|
+
set_error_message(*errbuf, INTERNAL_ERROR,
|
|
1281
|
+
"failed to inspect Intl after snapshot script");
|
|
1282
|
+
return INTERNAL_ERROR;
|
|
1283
|
+
}
|
|
1284
|
+
if (getter_descriptor->IsObject()) {
|
|
1285
|
+
v8::Local<v8::Value> getter;
|
|
1286
|
+
auto value_key =
|
|
1287
|
+
v8::String::NewFromUtf8Literal(isolate, "value");
|
|
1288
|
+
if (!getter_descriptor.As<v8::Object>()
|
|
1289
|
+
->Get(context, value_key)
|
|
1290
|
+
.ToLocal(&getter)) {
|
|
1291
|
+
set_error_message(*errbuf, INTERNAL_ERROR,
|
|
1292
|
+
"failed to inspect Intl after snapshot script");
|
|
1293
|
+
return INTERNAL_ERROR;
|
|
1294
|
+
}
|
|
1295
|
+
restore_intl =
|
|
1296
|
+
getter->IsFunction() &&
|
|
1297
|
+
getter->SameValue(intl_guard_getter);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
if (restore_intl &&
|
|
1301
|
+
!context->Global()
|
|
1302
|
+
->DefineOwnProperty(
|
|
1303
|
+
context, intl_key, intl_original,
|
|
1304
|
+
v8::PropertyAttribute::DontEnum)
|
|
1305
|
+
.FromMaybe(false)) {
|
|
1306
|
+
set_error_message(*errbuf, INTERNAL_ERROR,
|
|
1307
|
+
"failed to restore Intl after snapshot script");
|
|
1308
|
+
return INTERNAL_ERROR;
|
|
1309
|
+
}
|
|
995
1310
|
}
|
|
996
|
-
cause = INTERNAL_ERROR;
|
|
997
1311
|
if (!is_warmup) snapshot_creator.SetDefaultContext(context);
|
|
998
1312
|
}
|
|
999
1313
|
if (is_warmup) {
|
|
1314
|
+
v8::HandleScope handle_scope(isolate);
|
|
1000
1315
|
isolate->ContextDisposedNotification(false);
|
|
1001
1316
|
auto context = v8::Context::New(isolate);
|
|
1002
1317
|
snapshot_creator.SetDefaultContext(context);
|
|
1003
1318
|
}
|
|
1004
1319
|
*result = snapshot_creator.CreateBlob(mode);
|
|
1005
|
-
|
|
1006
|
-
fail:
|
|
1007
|
-
return cause;
|
|
1320
|
+
return NO_ERROR;
|
|
1008
1321
|
}
|
|
1009
1322
|
|
|
1010
1323
|
// response is errback [result, err] array
|
|
@@ -48,6 +48,10 @@ void v8_perform_microtask_checkpoint(struct State *pst);
|
|
|
48
48
|
void v8_pump_message_loop(struct State *pst);
|
|
49
49
|
void v8_snapshot(struct State *pst, const uint8_t *p, size_t n);
|
|
50
50
|
void v8_warmup(struct State *pst, const uint8_t *p, size_t n);
|
|
51
|
+
// Returns 0 for a valid blob (including an empty blob), otherwise nonzero and
|
|
52
|
+
// writes a short reason to |err|. This performs header/section/checksum checks
|
|
53
|
+
// before V8's fatal snapshot deserializer sees the blob.
|
|
54
|
+
int v8_snapshot_validate(const uint8_t *data, size_t n, char *err, size_t errlen);
|
|
51
55
|
void v8_low_memory_notification(struct State *pst);
|
|
52
56
|
void v8_terminate_execution(struct State *pst); // called from ruby thread
|
|
53
57
|
void v8_terminate_watchdog(struct State *pst); // called from watchdog thread
|
data/lib/mini_racer/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mini_racer
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.22.
|
|
4
|
+
version: 0.22.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sam Saffron
|
|
@@ -164,9 +164,9 @@ licenses:
|
|
|
164
164
|
- MIT
|
|
165
165
|
metadata:
|
|
166
166
|
bug_tracker_uri: https://github.com/discourse/mini_racer/issues
|
|
167
|
-
changelog_uri: https://github.com/discourse/mini_racer/blob/v0.22.
|
|
168
|
-
documentation_uri: https://www.rubydoc.info/gems/mini_racer/0.22.
|
|
169
|
-
source_code_uri: https://github.com/discourse/mini_racer/tree/v0.22.
|
|
167
|
+
changelog_uri: https://github.com/discourse/mini_racer/blob/v0.22.2/CHANGELOG
|
|
168
|
+
documentation_uri: https://www.rubydoc.info/gems/mini_racer/0.22.2
|
|
169
|
+
source_code_uri: https://github.com/discourse/mini_racer/tree/v0.22.2
|
|
170
170
|
rdoc_options: []
|
|
171
171
|
require_paths:
|
|
172
172
|
- lib
|