Bug 1337564 - Don't use JSON behavior for __proto__ with eval. r=jwalden

Differential Revision: https://phabricator.services.mozilla.com/D45381

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Tom Schuster 2019-09-23 17:21:36 +00:00
parent 3778a57619
commit 10bb1ec461
5 changed files with 58 additions and 11 deletions

View File

@ -175,7 +175,8 @@ static EvalJSONResult ParseEvalStringAsJSON(
chars.begin().get() + 1U, len - 2);
Rooted<JSONParser<CharT>> parser(
cx, JSONParser<CharT>(cx, jsonChars, JSONParserBase::NoError));
cx, JSONParser<CharT>(cx, jsonChars,
JSONParserBase::ParseType::AttemptForEval));
if (!parser.parse(rval)) {
return EvalJSON_Failure;
}

View File

@ -1008,7 +1008,8 @@ bool js::ParseJSONWithReviver(JSContext* cx,
const mozilla::Range<const CharT> chars,
HandleValue reviver, MutableHandleValue vp) {
/* 15.12.2 steps 2-3. */
Rooted<JSONParser<CharT>> parser(cx, JSONParser<CharT>(cx, chars));
Rooted<JSONParser<CharT>> parser(
cx, JSONParser<CharT>(cx, chars, JSONParserBase::ParseType::JSONParse));
if (!parser.parse(vp)) {
return false;
}

View File

@ -0,0 +1,25 @@
load(libdir + "asserts.js");
// eval({__proto__}) changes [[Prototype]]
var obj = eval('({"__proto__": null})');
assertEq(Object.getPrototypeOf(obj), null);
assertEq(Object.prototype.hasOwnProperty.call(obj, "__proto__"), false);
// JSON.parse({__proto__}) creates new property __proto__
obj = JSON.parse('{"__proto__": null}');
assertEq(Object.getPrototypeOf(obj), Object.prototype);
assertEq(Object.prototype.hasOwnProperty.call(obj, "__proto__"), true);
// If __proto__ appears more than once as quoted or unquoted property name in an
// object initializer expression, that's an error.
//(No other property name has this restriction.)"
assertThrowsInstanceOf(() =>
eval('({ "__proto__" : null, "__proto__" : null })'), SyntaxError);
assertThrowsInstanceOf(() =>
eval(' ({ "__proto__" : null, "__proto__" : null })'), SyntaxError);
// JSON.parse doesn't care about duplication, the last definition counts.
obj = JSON.parse('{"__proto__": null, "__proto__": 5}');
assertEq(Object.getPrototypeOf(obj), Object.prototype);
assertEq(obj["__proto__"], 5);

View File

@ -77,7 +77,7 @@ void JSONParser<CharT>::getTextPosition(uint32_t* column, uint32_t* line) {
template <typename CharT>
void JSONParser<CharT>::error(const char* msg) {
if (errorHandling == RaiseError) {
if (parseType == ParseType::JSONParse) {
uint32_t column = 1, line = 1;
getTextPosition(&column, &line);
@ -93,7 +93,9 @@ void JSONParser<CharT>::error(const char* msg) {
}
}
bool JSONParserBase::errorReturn() { return errorHandling == NoError; }
bool JSONParserBase::errorReturn() {
return parseType == ParseType::AttemptForEval;
}
template <typename CharT>
template <JSONParserBase::StringType ST>
@ -666,6 +668,17 @@ bool JSONParser<CharT>::parse(MutableHandleValue vp) {
JSONMember:
if (token == String) {
jsid id = AtomToId(atomValue());
if (parseType == ParseType::AttemptForEval) {
// In |JSON.parse|, "__proto__" is a property like any other and may
// appear multiple times. In object literal syntax, "__proto__" is
// prototype mutation and can appear at most once. |JSONParser| only
// supports the former semantics, so if this parse attempt is for
// |eval|, return true (without reporting an error) to indicate the
// JSON parse attempt was unsuccessful.
if (id == NameToId(cx->names().proto)) {
return true;
}
}
PropertyVector& properties = stack.back().properties();
if (!properties.append(IdValuePair(id))) {
return false;

View File

@ -22,7 +22,14 @@ namespace js {
// can be shared between the two encodings.
class MOZ_STACK_CLASS JSONParserBase {
public:
enum ErrorHandling { RaiseError, NoError };
enum class ParseType {
// Parsing a string as if by JSON.parse.
JSONParse,
// Parsing what may or may not be JSON in a string of eval code.
// In this case, a failure to parse indicates either syntax that isn't JSON,
// or syntax that has different semantics in eval code than in JSON.
AttemptForEval,
};
private:
/* Data members */
@ -31,7 +38,7 @@ class MOZ_STACK_CLASS JSONParserBase {
protected:
JSContext* const cx;
const ErrorHandling errorHandling;
const ParseType parseType;
enum Token {
String,
@ -114,9 +121,9 @@ class MOZ_STACK_CLASS JSONParserBase {
Token lastToken;
#endif
JSONParserBase(JSContext* cx, ErrorHandling errorHandling)
JSONParserBase(JSContext* cx, ParseType parseType)
: cx(cx),
errorHandling(errorHandling),
parseType(parseType),
stack(cx),
freeElements(cx),
freeProperties(cx)
@ -132,7 +139,7 @@ class MOZ_STACK_CLASS JSONParserBase {
JSONParserBase(JSONParserBase&& other)
: v(other.v),
cx(other.cx),
errorHandling(other.errorHandling),
parseType(other.parseType),
stack(std::move(other.stack)),
freeElements(std::move(other.freeElements)),
freeProperties(std::move(other.freeProperties))
@ -212,8 +219,8 @@ class MOZ_STACK_CLASS JSONParser : public JSONParserBase {
/* Create a parser for the provided JSON data. */
JSONParser(JSContext* cx, mozilla::Range<const CharT> data,
ErrorHandling errorHandling = RaiseError)
: JSONParserBase(cx, errorHandling),
ParseType parseType)
: JSONParserBase(cx, parseType),
current(data.begin()),
begin(current),
end(data.end()) {