Skip to content

Commit ad02e0d

Browse files
bnoordhuisapapirovski
authored andcommitted
timers: make setImmediate() immune to tampering
Make setImmediate() immune to `process` global tampering by removing the dependency on the `process._immediateCallback` property. PR-URL: #17736 Fixes: #17681 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Tobias Nießen <tniessen@tnie.de> Reviewed-By: Anatoli Papirovski <apapirovski@mac.com> Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 15d880b commit ad02e0d

File tree

7 files changed

+41
-30
lines changed

7 files changed

+41
-30
lines changed

lib/timers.js

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@
2222
'use strict';
2323

2424
const async_wrap = process.binding('async_wrap');
25-
const TimerWrap = process.binding('timer_wrap').Timer;
25+
const {
26+
Timer: TimerWrap,
27+
setImmediateCallback,
28+
} = process.binding('timer_wrap');
2629
const L = require('internal/linkedlist');
2730
const timerInternals = require('internal/timers');
2831
const internalUtil = require('internal/util');
@@ -48,12 +51,8 @@ const { kInit, kDestroy, kAsyncIdCounter } = async_wrap.constants;
4851
const async_id_symbol = timerInternals.async_id_symbol;
4952
const trigger_async_id_symbol = timerInternals.trigger_async_id_symbol;
5053

51-
/* This is an Uint32Array for easier sharing with C++ land. */
52-
const scheduledImmediateCount = process._scheduledImmediateCount;
53-
delete process._scheduledImmediateCount;
54-
/* Kick off setImmediate processing */
55-
const activateImmediateCheck = process._activateImmediateCheck;
56-
delete process._activateImmediateCheck;
54+
const [activateImmediateCheck, scheduledImmediateCountArray] =
55+
setImmediateCallback(processImmediate);
5756

5857
// The Timeout class
5958
const Timeout = timerInternals.Timeout;
@@ -676,8 +675,6 @@ function processImmediate() {
676675
}
677676
}
678677

679-
process._immediateCallback = processImmediate;
680-
681678
// An optimization so that the try/finally only de-optimizes (since at least v8
682679
// 4.7) what is in this smaller function.
683680
function tryOnImmediate(immediate, oldTail) {
@@ -694,7 +691,7 @@ function tryOnImmediate(immediate, oldTail) {
694691

695692
if (!immediate._destroyed) {
696693
immediate._destroyed = true;
697-
scheduledImmediateCount[0]--;
694+
scheduledImmediateCountArray[0]--;
698695

699696
if (async_hook_fields[kDestroy] > 0) {
700697
emitDestroy(immediate[async_id_symbol]);
@@ -748,9 +745,9 @@ function Immediate(callback, args) {
748745
this);
749746
}
750747

751-
if (scheduledImmediateCount[0] === 0)
748+
if (scheduledImmediateCountArray[0] === 0)
752749
activateImmediateCheck();
753-
scheduledImmediateCount[0]++;
750+
scheduledImmediateCountArray[0]++;
754751

755752
immediateQueue.append(this);
756753
}
@@ -796,7 +793,7 @@ exports.clearImmediate = function(immediate) {
796793
if (!immediate) return;
797794

798795
if (!immediate._destroyed) {
799-
scheduledImmediateCount[0]--;
796+
scheduledImmediateCountArray[0]--;
800797
immediate._destroyed = true;
801798

802799
if (async_hook_fields[kDestroy] > 0) {

src/env.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ void Environment::CheckImmediate(uv_check_t* handle) {
311311

312312
MakeCallback(env->isolate(),
313313
env->process_object(),
314-
env->immediate_callback_string(),
314+
env->immediate_callback_function(),
315315
0,
316316
nullptr,
317317
{0, 0}).ToLocalChecked();

src/env.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,6 @@ class ModuleWrap;
158158
V(homedir_string, "homedir") \
159159
V(hostmaster_string, "hostmaster") \
160160
V(ignore_string, "ignore") \
161-
V(immediate_callback_string, "_immediateCallback") \
162161
V(infoaccess_string, "infoAccess") \
163162
V(inherit_string, "inherit") \
164163
V(input_string, "input") \
@@ -289,6 +288,7 @@ class ModuleWrap;
289288
V(http2ping_constructor_template, v8::ObjectTemplate) \
290289
V(http2stream_constructor_template, v8::ObjectTemplate) \
291290
V(http2settings_constructor_template, v8::ObjectTemplate) \
291+
V(immediate_callback_function, v8::Function) \
292292
V(inspector_console_api_object, v8::Object) \
293293
V(module_load_list_array, v8::Array) \
294294
V(pbkdf2_constructor_template, v8::ObjectTemplate) \

src/node.cc

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2939,12 +2939,6 @@ static void DebugEnd(const FunctionCallbackInfo<Value>& args);
29392939

29402940
namespace {
29412941

2942-
void ActivateImmediateCheck(const FunctionCallbackInfo<Value>& args) {
2943-
Environment* env = Environment::GetCurrent(args);
2944-
env->ActivateImmediateCheck();
2945-
}
2946-
2947-
29482942
void StartProfilerIdleNotifier(const FunctionCallbackInfo<Value>& args) {
29492943
Environment* env = Environment::GetCurrent(args);
29502944
env->StartProfilerIdleNotifier();
@@ -3168,12 +3162,6 @@ void SetupProcessObject(Environment* env,
31683162
FIXED_ONE_BYTE_STRING(env->isolate(), "ppid"),
31693163
GetParentProcessId).FromJust());
31703164

3171-
auto scheduled_immediate_count =
3172-
FIXED_ONE_BYTE_STRING(env->isolate(), "_scheduledImmediateCount");
3173-
CHECK(process->Set(env->context(),
3174-
scheduled_immediate_count,
3175-
env->scheduled_immediate_count().GetJSArray()).FromJust());
3176-
31773165
auto should_abort_on_uncaught_toggle =
31783166
FIXED_ONE_BYTE_STRING(env->isolate(), "_shouldAbortOnUncaughtToggle");
31793167
CHECK(process->Set(env->context(),
@@ -3305,9 +3293,6 @@ void SetupProcessObject(Environment* env,
33053293
env->as_external()).FromJust());
33063294

33073295
// define various internal methods
3308-
env->SetMethod(process,
3309-
"_activateImmediateCheck",
3310-
ActivateImmediateCheck);
33113296
env->SetMethod(process,
33123297
"_startProfilerIdleNotifier",
33133298
StartProfilerIdleNotifier);

src/timer_wrap.cc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@
2929
namespace node {
3030
namespace {
3131

32+
using v8::Array;
3233
using v8::Context;
34+
using v8::Function;
3335
using v8::FunctionCallbackInfo;
3436
using v8::FunctionTemplate;
3537
using v8::HandleScope;
@@ -67,11 +69,32 @@ class TimerWrap : public HandleWrap {
6769
env->SetProtoMethod(constructor, "stop", Stop);
6870

6971
target->Set(timerString, constructor->GetFunction());
72+
73+
target->Set(env->context(),
74+
FIXED_ONE_BYTE_STRING(env->isolate(), "setImmediateCallback"),
75+
env->NewFunctionTemplate(SetImmediateCallback)
76+
->GetFunction(env->context()).ToLocalChecked()).FromJust();
7077
}
7178

7279
size_t self_size() const override { return sizeof(*this); }
7380

7481
private:
82+
static void SetImmediateCallback(const FunctionCallbackInfo<Value>& args) {
83+
CHECK(args[0]->IsFunction());
84+
auto env = Environment::GetCurrent(args);
85+
env->set_immediate_callback_function(args[0].As<Function>());
86+
auto activate_cb = [] (const FunctionCallbackInfo<Value>& args) {
87+
Environment::GetCurrent(args)->ActivateImmediateCheck();
88+
};
89+
auto activate_function =
90+
env->NewFunctionTemplate(activate_cb)->GetFunction(env->context())
91+
.ToLocalChecked();
92+
auto result = Array::New(env->isolate(), 2);
93+
result->Set(0, activate_function);
94+
result->Set(1, env->scheduled_immediate_count().GetJSArray());
95+
args.GetReturnValue().Set(result);
96+
}
97+
7598
static void New(const FunctionCallbackInfo<Value>& args) {
7699
// This constructor should not be exposed to public javascript.
77100
// Therefore we assert that we are not trying to call this as a

test/common/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
/* eslint-disable required-modules, crypto-check */
2323
'use strict';
24+
const process = global.process; // Some tests tamper with the process global.
2425
const path = require('path');
2526
const fs = require('fs');
2627
const assert = require('assert');
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
'use strict';
2+
const common = require('../common');
3+
common.globalCheck = false;
4+
global.process = {}; // Boom!
5+
setImmediate(common.mustCall());

0 commit comments

Comments
 (0)