Skip to content

Commit 1a95eb4

Browse files
committed
SERVER-19614 Use stack bounds to limit JS recursion
1 parent e556e47 commit 1a95eb4

File tree

2 files changed

+55
-50
lines changed

2 files changed

+55
-50
lines changed

jstests/core/recursion.js

Lines changed: 38 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,41 @@
11
// Basic tests for a form of stack recursion that's been shown to cause C++
22
// side stack overflows in the past. See SERVER-19614.
33

4-
// TODO: Re-enable this test once SERVER-19614 has been resolved in a
5-
// way that does not conflict with SERVER-20678. See the similar
6-
// comment in src/mongo/scripting/mozjs/implscope.cpp for additional
7-
// details.
8-
//
9-
// (function () {
10-
// "use strict";
11-
//
12-
// db.recursion.drop();
13-
//
14-
// // Make sure the shell doesn't blow up
15-
// function shellRecursion() {
16-
// shellRecursion.apply();
17-
// }
18-
// assert.throws(shellRecursion);
19-
//
20-
// // Make sure db.eval doesn't blow up
21-
// function dbEvalRecursion() {
22-
// db.eval(function () {
23-
// function recursion() {
24-
// recursion.apply();
25-
// }
26-
// recursion();
27-
// });
28-
// }
29-
// assert.commandFailedWithCode(assert.throws(dbEvalRecursion), ErrorCodes.JSInterpreterFailure);
30-
//
31-
// // Make sure mapReduce doesn't blow up
32-
// function mapReduceRecursion() {
33-
// db.recursion.mapReduce(function(){
34-
// (function recursion(){
35-
// recursion.apply();
36-
// })();
37-
// }, function(){
38-
// }, {
39-
// out: 'inline'
40-
// });
41-
// }
42-
//
43-
// db.recursion.insert({});
44-
// assert.commandFailedWithCode(
45-
// assert.throws(mapReduceRecursion), ErrorCodes.JSInterpreterFailure);
46-
// }());
4+
(function () {
5+
"use strict";
6+
7+
db.recursion.drop();
8+
9+
// Make sure the shell doesn't blow up
10+
function shellRecursion() {
11+
shellRecursion.apply();
12+
}
13+
assert.throws(shellRecursion);
14+
15+
// Make sure db.eval doesn't blow up
16+
function dbEvalRecursion() {
17+
db.eval(function () {
18+
function recursion() {
19+
recursion.apply();
20+
}
21+
recursion();
22+
});
23+
}
24+
assert.commandFailedWithCode(assert.throws(dbEvalRecursion), ErrorCodes.JSInterpreterFailure);
25+
26+
// Make sure mapReduce doesn't blow up
27+
function mapReduceRecursion() {
28+
db.recursion.mapReduce(function(){
29+
(function recursion(){
30+
recursion.apply();
31+
})();
32+
}, function(){
33+
}, {
34+
out: 'inline'
35+
});
36+
}
37+
38+
db.recursion.insert({});
39+
assert.commandFailedWithCode(
40+
assert.throws(mapReduceRecursion), ErrorCodes.JSInterpreterFailure);
41+
}());

src/mongo/scripting/mozjs/implscope.cpp

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#include "mongo/base/error_codes.h"
3939
#include "mongo/db/operation_context.h"
4040
#include "mongo/platform/decimal128.h"
41+
#include "mongo/platform/stack_locator.h"
4142
#include "mongo/scripting/mozjs/objectwrapper.h"
4243
#include "mongo/scripting/mozjs/valuereader.h"
4344
#include "mongo/scripting/mozjs/valuewriter.h"
@@ -227,13 +228,22 @@ MozJSImplScope::MozRuntime::MozRuntime() {
227228

228229
_runtime = JS_NewRuntime(kMaxBytesBeforeGC);
229230

230-
// TODO: Re-enable this when it can be done in a way that does
231-
// not conflict with the performance fix in SERVER-20678. The
232-
// jscore/recursion.js tes tshould be re-enabled when this is
233-
// uncommented.
234-
//
235-
// static_assert(kMaxStackBytes > (32 * 1024), "kMaxStackBytes must be larger than 32k");
236-
// JS_SetNativeStackQuota(_runtime, kMaxStackBytes - (32 * 1024));
231+
const StackLocator locator;
232+
const auto available = locator.available();
233+
if (available) {
234+
// We fudge by 64k for a two reasons. First, it appears
235+
// that the internal recursion checks that SM performs can
236+
// have stack usage between checks of more than 32k in
237+
// some builds. Second, some platforms report the guard
238+
// page (in the linux sense) as "part of the stack", even
239+
// though accessing that page will fault the process. We
240+
// don't have a good way of getting information about the
241+
// guard page on those platforms.
242+
//
243+
// TODO: What if we are running on a platform with very
244+
// large pages, like 4MB?
245+
JS_SetNativeStackQuota(_runtime, available.get() - (64 * 1024));
246+
}
237247
}
238248

239249
uassert(ErrorCodes::JSInterpreterFailure, "Failed to initialize JSRuntime", _runtime);

0 commit comments

Comments
 (0)