summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWaley Chen <waleycz@gmail.com>2016-02-09 12:06:43 -0500
committerWaley Chen <waleycz@gmail.com>2016-02-11 13:18:52 -0500
commitac424fe0c912f1e5553d40f79a168d09ccffef31 (patch)
treeff63a3e9e5bf79940e8c26d951738141afd09fac
parent7cb6feb7ec15b628d5180bcf64f1a9b2687c4802 (diff)
downloadmongo-ac424fe0c912f1e5553d40f79a168d09ccffef31.tar.gz
SERVER-22461 Crash in mozjs code with invalid NumberLong objectt
-rw-r--r--jstests/core/numberlong.js30
-rw-r--r--jstests/core/numberlong2.js8
-rw-r--r--src/mongo/scripting/mozjs/numberlong.cpp167
-rw-r--r--src/mongo/scripting/mozjs/numberlong.h13
4 files changed, 158 insertions, 60 deletions
diff --git a/jstests/core/numberlong.js b/jstests/core/numberlong.js
index 4f1d27d6cb0..acbde248889 100644
--- a/jstests/core/numberlong.js
+++ b/jstests/core/numberlong.js
@@ -25,6 +25,21 @@ a.a = n;
p = tojson( a );
assert.eq.automsg( "'{ \"a\" : NumberLong(-4) }'", "p" );
+// double
+n = new NumberLong(4294967296); // 2^32
+assert.eq.automsg( "4294967296", "n" );
+assert.eq.automsg( "4294967296", "n.toNumber()" );
+assert.eq.automsg( "4294967295", "n - 1" );
+assert.eq.automsg( "'NumberLong(\"4294967296\")'", "n.toString()" );
+assert.eq.automsg( "'NumberLong(\"4294967296\")'", "tojson( n )" );
+assert.eq.automsg( "4294967296", "n.floatApprox" );
+assert.eq.automsg( "", "n.top" );
+assert.eq.automsg( "", "n.bottom" );
+a = {}
+a.a = n;
+p = tojson( a );
+assert.eq.automsg( "'{ \"a\" : NumberLong(\"4294967296\") }'", "p" );
+
// too big to fit in double
n = new NumberLong( "11111111111111111" );
assert.eq.automsg( "11111111111111112", "n.toNumber()" );
@@ -44,11 +59,24 @@ assert.eq.automsg( "-11111111111111112", "n.toNumber()" );
assert.eq.automsg( "-11111111111111108", "n + 4" );
assert.eq.automsg( "'NumberLong(\"-11111111111111111\")'", "n.toString()" );
assert.eq.automsg( "'NumberLong(\"-11111111111111111\")'", "tojson( n )" );
-a = {};
+assert.eq.automsg( "-11111111111111112", "n.floatApprox" );
+assert.eq.automsg( "4292380288", "n.top" );
+assert.eq.automsg( "3643379257", "n.bottom" );
+a = {}
a.a = n;
p = tojson( a );
assert.eq.automsg( "'{ \"a\" : NumberLong(\"-11111111111111111\") }'", "p" );
+n = new NumberLong( "9223372036854775807" );
+assert.eq.automsg( "9223372036854775807", "n.floatApprox" );
+assert.eq.automsg( "2147483647", "n.top" );
+assert.eq.automsg( "4294967295", "n.bottom" );
+
+n = new NumberLong( 9223372036854775807, 2147483647, 4294967295 );
+assert.eq.automsg( "9223372036854775807", "n.floatApprox" );
+assert.eq.automsg( "2147483647", "n.top" );
+assert.eq.automsg( "4294967295", "n.bottom" );
+
// parsing
assert.throws.automsg( function() { new NumberLong( "" ); } );
assert.throws.automsg( function() { new NumberLong( "y" ); } );
diff --git a/jstests/core/numberlong2.js b/jstests/core/numberlong2.js
index 5d7529a9e21..c730345f307 100644
--- a/jstests/core/numberlong2.js
+++ b/jstests/core/numberlong2.js
@@ -12,11 +12,11 @@ function chk(longNum) {
assert.eq(longNum, t.find({}, { _id: 0, x: 1 }).hint({ x: 1 }).next().x);
}
-chk( NumberLong("1123539983311657217") );
+chk(NumberLong("1123539983311657217"));
chk(NumberLong("-1123539983311657217"));
- chk(NumberLong("4503599627370495"));
- chk(NumberLong("4503599627370496"));
- chk(NumberLong("4503599627370497"));
+chk(NumberLong("4503599627370495"));
+chk(NumberLong("4503599627370496"));
+chk(NumberLong("4503599627370497"));
t.remove({});
diff --git a/src/mongo/scripting/mozjs/numberlong.cpp b/src/mongo/scripting/mozjs/numberlong.cpp
index c1bab0979e0..7abef7ed34c 100644
--- a/src/mongo/scripting/mozjs/numberlong.cpp
+++ b/src/mongo/scripting/mozjs/numberlong.cpp
@@ -48,36 +48,29 @@ const JSFunctionSpec NumberLongInfo::methods[5] = {
MONGO_ATTACH_JS_CONSTRAINED_METHOD(toString, NumberLongInfo),
MONGO_ATTACH_JS_CONSTRAINED_METHOD(valueOf, NumberLongInfo),
MONGO_ATTACH_JS_CONSTRAINED_METHOD(compare, NumberLongInfo),
- JS_FS_END,
-};
+ JS_FS_END};
const char* const NumberLongInfo::className = "NumberLong";
-long long NumberLongInfo::ToNumberLong(JSContext* cx, JS::HandleValue thisv) {
- JS::RootedObject obj(cx, thisv.toObjectOrNull());
- return ToNumberLong(cx, obj);
-}
-
-long long NumberLongInfo::ToNumberLong(JSContext* cx, JS::HandleObject thisv) {
- ObjectWrapper o(cx, thisv);
+void NumberLongInfo::finalize(JSFreeOp* fop, JSObject* obj) {
+ auto numLong = static_cast<int*>(JS_GetPrivate(obj));
- if (!o.hasOwnField(InternedString::top)) {
- if (!o.hasOwnField(InternedString::floatApprox))
- uasserted(ErrorCodes::InternalError, "No top and no floatApprox fields");
-
- return o.getNumberLongLong(InternedString::floatApprox);
- }
+ if (numLong)
+ delete numLong;
+}
- if (!o.hasOwnField(InternedString::bottom))
- uasserted(ErrorCodes::InternalError, "top but no bottom field");
+int64_t NumberLongInfo::ToNumberLong(JSContext* cx, JS::HandleValue thisv) {
+ auto numLong = static_cast<int64_t*>(JS_GetPrivate(thisv.toObjectOrNull()));
+ return numLong ? *numLong : 0;
+}
- return ((unsigned long long)((long long)o.getNumberLongLong(InternedString::top) << 32) +
- (unsigned)(o.getNumberLongLong(InternedString::bottom)));
+int64_t NumberLongInfo::ToNumberLong(JSContext* cx, JS::HandleObject thisv) {
+ auto numLong = static_cast<int64_t*>(JS_GetPrivate(thisv));
+ return numLong ? *numLong : 0;
}
void NumberLongInfo::Functions::valueOf::call(JSContext* cx, JS::CallArgs args) {
- long long out = NumberLongInfo::ToNumberLong(cx, args.thisv());
-
+ int64_t out = NumberLongInfo::ToNumberLong(cx, args.thisv());
args.rval().setDouble(out);
}
@@ -88,15 +81,16 @@ void NumberLongInfo::Functions::toNumber::call(JSContext* cx, JS::CallArgs args)
void NumberLongInfo::Functions::toString::call(JSContext* cx, JS::CallArgs args) {
str::stream ss;
- long long val = NumberLongInfo::ToNumberLong(cx, args.thisv());
+ int64_t val = NumberLongInfo::ToNumberLong(cx, args.thisv());
- const long long limit = 2LL << 30;
+ const int64_t limit = 2LL << 30;
if (val <= -limit || limit <= val)
ss << "NumberLong(\"" << val << "\")";
else
ss << "NumberLong(" << val << ")";
+
ValueReader(cx, args.rval()).fromStringData(ss.operator std::string());
}
@@ -106,8 +100,8 @@ void NumberLongInfo::Functions::compare::call(JSContext* cx, JS::CallArgs args)
"NumberLong.compare() argument must be an object",
args.get(0).isObject());
- long long thisVal = NumberLongInfo::ToNumberLong(cx, args.thisv());
- long long otherVal = NumberLongInfo::ToNumberLong(cx, args.get(0));
+ int64_t thisVal = NumberLongInfo::ToNumberLong(cx, args.thisv());
+ int64_t otherVal = NumberLongInfo::ToNumberLong(cx, args.get(0));
int comparison = 0;
if (thisVal < otherVal) {
@@ -119,6 +113,35 @@ void NumberLongInfo::Functions::compare::call(JSContext* cx, JS::CallArgs args)
args.rval().setDouble(comparison);
}
+void NumberLongInfo::Functions::floatApprox::call(JSContext* cx, JS::CallArgs args) {
+ int64_t numLong = NumberLongInfo::ToNumberLong(cx, args.thisv());
+ args.rval().setDouble(numLong);
+}
+
+void NumberLongInfo::Functions::top::call(JSContext* cx, JS::CallArgs args) {
+ int64_t numLong = NumberLongInfo::ToNumberLong(cx, args.thisv());
+
+ // values above 2^53 are not accurately represented in JS
+ if (numLong == INT64_MIN || std::abs(numLong) >= 9007199254740992LL) {
+ auto val64 = static_cast<unsigned long long>(numLong);
+ args.rval().setDouble(val64 >> 32);
+ } else {
+ args.rval().setNull();
+ }
+}
+
+void NumberLongInfo::Functions::bottom::call(JSContext* cx, JS::CallArgs args) {
+ int64_t numLong = NumberLongInfo::ToNumberLong(cx, args.thisv());
+
+ // values above 2^53 are not accurately represented in JS
+ if (numLong == INT64_MIN || std::abs(numLong) >= 9007199254740992LL) {
+ auto val64 = static_cast<unsigned long long>(numLong);
+ args.rval().setDouble(val64 & 0x00000000FFFFFFFF);
+ } else {
+ args.rval().setNull();
+ }
+}
+
void NumberLongInfo::construct(JSContext* cx, JS::CallArgs args) {
uassert(ErrorCodes::BadValue,
"NumberLong needs 0, 1 or 3 arguments",
@@ -129,60 +152,98 @@ void NumberLongInfo::construct(JSContext* cx, JS::CallArgs args) {
JS::RootedObject thisv(cx);
scope->getProto<NumberLongInfo>().newObject(&thisv);
- ObjectWrapper o(cx, thisv);
- JS::RootedValue floatApprox(cx);
- JS::RootedValue top(cx);
- JS::RootedValue bottom(cx);
+ int64_t numLong;
+
+ ObjectWrapper o(cx, thisv);
if (args.length() == 0) {
- o.setNumber(InternedString::floatApprox, 0);
+ numLong = 0;
} else if (args.length() == 1) {
auto arg = args.get(0);
- if (arg.isNumber()) {
- o.setValue(InternedString::floatApprox, arg);
+
+ if (arg.isInt32()) {
+ numLong = arg.toInt32();
+ } else if (arg.isDouble()) {
+ numLong = arg.toDouble();
} else {
std::string str = ValueWriter(cx, arg).toString();
- long long val;
+ int64_t val;
// For string values we call strtoll because we expect non-number string
- // values to fail rather than return 0 (which is the behavior of ToInt64.
+ // values to fail rather than return 0 (which is the behavior of ToInt64).
if (arg.isString())
val = parseLL(str.c_str());
- // Otherwise we call the toNumber on the js value to get the long long value.
+ // Otherwise we call the toNumber on the js value to get the int64_t value.
else
val = ValueWriter(cx, arg).toInt64();
- // values above 2^53 are not accurately represented in JS
- if (val == INT64_MIN || std::abs(val) >= 9007199254740992LL) {
- auto val64 = static_cast<unsigned long long>(val);
- o.setNumber(InternedString::floatApprox, val);
- o.setNumber(InternedString::top, val64 >> 32);
- o.setNumber(InternedString::bottom, val64 & 0x00000000ffffffff);
- } else {
- o.setNumber(InternedString::floatApprox, val);
- }
+ numLong = val;
}
} else {
if (!args.get(0).isNumber())
uasserted(ErrorCodes::BadValue, "floatApprox must be a number");
- if (!args.get(1).isNumber() ||
- args.get(1).toNumber() !=
- static_cast<double>(static_cast<uint32_t>(args.get(1).toNumber())))
+ double top;
+ if (args.get(1).isNumber())
+ top = static_cast<double>(static_cast<uint32_t>(args.get(1).toNumber()));
+
+ if (!args.get(1).isNumber() || args.get(1).toNumber() != top)
uasserted(ErrorCodes::BadValue, "top must be a 32 bit unsigned number");
- if (!args.get(2).isNumber() ||
- args.get(2).toNumber() !=
- static_cast<double>(static_cast<uint32_t>(args.get(2).toNumber())))
+ double bot;
+ if (args.get(2).isNumber())
+ bot = static_cast<double>(static_cast<uint32_t>(args.get(2).toNumber()));
+
+ if (!args.get(2).isNumber() || args.get(2).toNumber() != bot)
uasserted(ErrorCodes::BadValue, "bottom must be a 32 bit unsigned number");
- o.setValue(InternedString::floatApprox, args.get(0));
- o.setValue(InternedString::top, args.get(1));
- o.setValue(InternedString::bottom, args.get(2));
+ numLong = (static_cast<uint64_t>(top) << 32) + static_cast<uint64_t>(bot);
}
+ JS_SetPrivate(thisv, new int64_t(numLong));
+
args.rval().setObjectOrNull(thisv);
}
+void NumberLongInfo::postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObject proto) {
+ JS::RootedValue undef(cx);
+ undef.setUndefined();
+
+ // floatapprox
+ if (!JS_DefinePropertyById(
+ cx,
+ proto,
+ getScope(cx)->getInternedStringId(InternedString::floatApprox),
+ undef,
+ JSPROP_READONLY | JSPROP_ENUMERATE,
+ smUtils::wrapConstrainedMethod<Functions::floatApprox, true, NumberLongInfo>,
+ nullptr)) {
+ uasserted(ErrorCodes::JSInterpreterFailure, "Failed to JS_DefinePropertyById");
+ }
+
+ // top
+ if (!JS_DefinePropertyById(cx,
+ proto,
+ getScope(cx)->getInternedStringId(InternedString::top),
+ undef,
+ JSPROP_READONLY | JSPROP_ENUMERATE,
+ smUtils::wrapConstrainedMethod<Functions::top, true, NumberLongInfo>,
+ nullptr)) {
+ uasserted(ErrorCodes::JSInterpreterFailure, "Failed to JS_DefinePropertyById");
+ }
+
+ // bottom
+ if (!JS_DefinePropertyById(
+ cx,
+ proto,
+ getScope(cx)->getInternedStringId(InternedString::bottom),
+ undef,
+ JSPROP_READONLY | JSPROP_ENUMERATE,
+ smUtils::wrapConstrainedMethod<Functions::bottom, true, NumberLongInfo>,
+ nullptr)) {
+ uasserted(ErrorCodes::JSInterpreterFailure, "Failed to JS_DefinePropertyById");
+ }
+}
+
} // namespace mozjs
} // namespace mongo
diff --git a/src/mongo/scripting/mozjs/numberlong.h b/src/mongo/scripting/mozjs/numberlong.h
index 145afd387b5..22bf2b94790 100644
--- a/src/mongo/scripting/mozjs/numberlong.h
+++ b/src/mongo/scripting/mozjs/numberlong.h
@@ -28,6 +28,8 @@
#pragma once
+#include <jsapi.h>
+
#include "mongo/scripting/mozjs/wraptype.h"
namespace mongo {
@@ -49,20 +51,27 @@ namespace mozjs {
*/
struct NumberLongInfo : public BaseInfo {
static void construct(JSContext* cx, JS::CallArgs args);
+ static void finalize(JSFreeOp* fop, JSObject* obj);
struct Functions {
MONGO_DECLARE_JS_FUNCTION(toNumber);
MONGO_DECLARE_JS_FUNCTION(toString);
MONGO_DECLARE_JS_FUNCTION(valueOf);
MONGO_DECLARE_JS_FUNCTION(compare);
+ MONGO_DECLARE_JS_FUNCTION(floatApprox);
+ MONGO_DECLARE_JS_FUNCTION(top);
+ MONGO_DECLARE_JS_FUNCTION(bottom);
};
static const JSFunctionSpec methods[5];
static const char* const className;
+ static const unsigned classFlags = JSCLASS_HAS_PRIVATE;
+
+ static void postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObject proto);
- static long long ToNumberLong(JSContext* cx, JS::HandleObject object);
- static long long ToNumberLong(JSContext* cx, JS::HandleValue value);
+ static int64_t ToNumberLong(JSContext* cx, JS::HandleObject object);
+ static int64_t ToNumberLong(JSContext* cx, JS::HandleValue value);
};
} // namespace mozjs