Skip to content

Commit 08d35b2

Browse files
committed
[CVE-2017-0230] Fix incorrect byte offset values for the class constructor parse node
If there is a multi-byte character in the source before a class decl, the constructor function created for that class will have incorrect byte offset values. This leads us to truncate the source string buffer when we try to do toString on that class constructor function and accidentally print garbage. Fix is to calculate the byte offsets correctly.
1 parent f74773f commit 08d35b2

4 files changed

Lines changed: 74 additions & 3 deletions

File tree

lib/Parser/Parse.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7095,12 +7095,15 @@ ParseNodePtr Parser::ParseClassDecl(BOOL isDeclaration, LPCOLESTR pNameHint, uin
70957095

70967096
ArenaAllocator tempAllocator(_u("ClassMemberNames"), m_nodeAllocator.GetPageAllocator(), Parser::OutOfMemory);
70977097

7098+
size_t cbMinConstructor = 0;
70987099
ParseNodePtr pnodeClass = nullptr;
70997100
if (buildAST)
71007101
{
71017102
pnodeClass = CreateNode(knopClassDecl);
71027103

71037104
CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(Class, m_scriptContext);
7105+
7106+
cbMinConstructor = m_pscan->IecpMinTok();
71047107
}
71057108

71067109
m_pscan->Scan();
@@ -7393,9 +7396,11 @@ ParseNodePtr Parser::ParseClassDecl(BOOL isDeclaration, LPCOLESTR pNameHint, uin
73937396
}
73947397
}
73957398

7399+
size_t cbLimConstructor = 0;
73967400
if (buildAST)
73977401
{
73987402
pnodeClass->ichLim = m_pscan->IchLimTok();
7403+
cbLimConstructor = m_pscan->IecpLimTok();
73997404
}
74007405

74017406
if (!hasConstructor)
@@ -7430,8 +7435,8 @@ ParseNodePtr Parser::ParseClassDecl(BOOL isDeclaration, LPCOLESTR pNameHint, uin
74307435

74317436
if (buildAST)
74327437
{
7433-
pnodeConstructor->sxFnc.cbMin = pnodeClass->ichMin;
7434-
pnodeConstructor->sxFnc.cbLim = pnodeClass->ichLim;
7438+
pnodeConstructor->sxFnc.cbMin = cbMinConstructor;
7439+
pnodeConstructor->sxFnc.cbLim = cbLimConstructor;
74357440
pnodeConstructor->ichMin = pnodeClass->ichMin;
74367441
pnodeConstructor->ichLim = pnodeClass->ichLim;
74377442

lib/Runtime/Library/ScriptFunction.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,14 @@ namespace Js
484484
LPCUTF8 pbStart = pFuncBody->GetSource(_u("ScriptFunction::EnsureSourceString"));
485485
BufferStringBuilder builder(cch, scriptContext);
486486
utf8::DecodeOptions options = pFuncBody->GetUtf8SourceInfo()->IsCesu8() ? utf8::doAllowThreeByteSurrogates : utf8::doDefault;
487-
utf8::DecodeUnitsInto(builder.DangerousGetWritableBuffer(), pbStart, pbStart + cbLength, options);
487+
size_t decodedCount = utf8::DecodeUnitsInto(builder.DangerousGetWritableBuffer(), pbStart, pbStart + cbLength, options);
488+
489+
if (decodedCount != cch)
490+
{
491+
AssertMsg(false, "Decoded incorrect number of characters for function body");
492+
Js::Throw::FatalInternalError();
493+
}
494+
488495
if (pFuncBody->IsLambda() || isActiveScript || this->GetFunctionInfo()->IsClassConstructor()
489496
#ifdef ENABLE_PROJECTION
490497
|| scriptContext->GetConfig()->IsWinRTEnabled()

test/utf8/bugGH2656.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//-------------------------------------------------------------------------------------------------------
2+
// Copyright (C) Microsoft. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
4+
//-------------------------------------------------------------------------------------------------------
5+
6+
WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
7+
8+
var tests = [
9+
{
10+
name: "Serialize functions with unicode sequences",
11+
body: function () {
12+
assert.areEqual('function foo() { /* 𢭃 */ }', '' + function foo() { /* 𢭃 */ }, 'Serialized function declaration produces correct string in presense of multi-byte unicode characters');
13+
assert.areEqual('function 𢭃() { /* 𢭃 */ }', '' + function 𢭃() { /* 𢭃 */ }, 'Serialized function with a unicode identifier');
14+
assert.areEqual('function 𢭃(ā,食) { /* 𢭃 */ }', '' + function 𢭃(ā,) { /* 𢭃 */ }, 'Serialized function with a unicode identifier and unicode argument list');
15+
16+
assert.areEqual('async function foo() { /* 𢭃 */ }', '' + async function foo() { /* 𢭃 */ }, 'Serialized async function declaration produces correct string in presense of multi-byte unicode characters');
17+
assert.areEqual('async function 𢭃() { /* 𢭃 */ }', '' + async function 𢭃() { /* 𢭃 */ }, 'Serialized async function with a unicode identifier');
18+
assert.areEqual('async function 𢭃(ā,食) { /* 𢭃 */ }', '' + async function 𢭃(ā,) { /* 𢭃 */ }, 'Serialized async function with a unicode identifier and unicode argument list');
19+
20+
assert.areEqual('function* foo() { /* 𢭃 */ }', '' + function* foo() { /* 𢭃 */ }, 'Serialized generator function declaration produces correct string in presense of multi-byte unicode characters');
21+
assert.areEqual('function* 𢭃() { /* 𢭃 */ }', '' + function* 𢭃() { /* 𢭃 */ }, 'Serialized generator function with a unicode identifier');
22+
assert.areEqual('function* 𢭃(ā,食) { /* 𢭃 */ }', '' + function* 𢭃(ā,) { /* 𢭃 */ }, 'Serialized generator function with a unicode identifier and unicode argument list');
23+
24+
assert.areEqual('() => { /* 𢭃 */ }', '' + (() => { /* 𢭃 */ }), 'Serialized arrow function declaration produces correct string in presense of multi-byte unicode characters');
25+
assert.areEqual('(ā,食) => { /* 𢭃 */ }', '' + ((ā,) => { /* 𢭃 */ }), 'Serialized arrow function declaration with a unicode argument list');
26+
27+
assert.areEqual('async () => { /* 𢭃 */ }', '' + (async () => { /* 𢭃 */ }), 'Serialized async arrow function declaration produces correct string in presense of multi-byte unicode characters');
28+
assert.areEqual('async (ā,食) => { /* 𢭃 */ }', '' + (async (ā,) => { /* 𢭃 */ }), 'Serialized async arrow function declaration with a unicode argument list');
29+
}
30+
},
31+
{
32+
name: "Serialize classes with unicode sequences",
33+
body: function () {
34+
assert.areEqual('class 𢭃 { /* 𢭃 */ }', '' + class 𢭃 { /* 𢭃 */ }, 'Serialized class declaration produces correct string in presense of multi-byte unicode characters');
35+
36+
class ā { 𢭃() { /* 𢭃 */ } static () { /* 𢭃 */ } async () { /* 𢭃 */ } static async () { /* 𢭃 */ } *() { /* 𢭃 */ } static *() { /* 𢭃 */ } get () { /* 𢭃 */ } set () { /* 𢭃 */ } }
37+
38+
assert.areEqual(
39+
'class ā { 𢭃(物) { /* 𢭃 */ } static 飲(物) { /* 𢭃 */ } async 知(物) { /* 𢭃 */ } static async 愛(物) { /* 𢭃 */ } *泳(物) { /* 𢭃 */ } static *赤(物) { /* 𢭃 */ } get 青() { /* 𢭃 */ } set 緑(物) { /* 𢭃 */ } }',
40+
'' + ā,
41+
'Serialized class with different types of members');
42+
43+
class extends ā { () { /* 𢭃 */ } static () { /* 𢭃 */ } async () { /* 𢭃 */ } static async () { /* 𢭃 */ } *() { /* 𢭃 */ } static *() { /* 𢭃 */ } get () { /* 𢭃 */ } set () { /* 𢭃 */ } }
44+
45+
assert.areEqual(
46+
`class 食 extends ā { 母(物) { /* 𢭃 */ } static 父(物) { /* 𢭃 */ } async 妹(物) { /* 𢭃 */ } static async 姉(物) { /* 𢭃 */ } *兄(物) { /* 𢭃 */ } static *耳(物) { /* 𢭃 */ } get 明() { /* 𢭃 */ } set 日(物) { /* 𢭃 */ } }`,
47+
'' + ,
48+
'Serialized class with an extends clause');
49+
}
50+
},
51+
];
52+
53+
testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });

test/utf8/rlexe.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,10 @@
2929
<compile-flags>-forceserialized -oopjit-</compile-flags>
3030
</default>
3131
</test>
32+
<test>
33+
<default>
34+
<files>bugGH2656.js</files>
35+
<compile-flags>-args summary</compile-flags>
36+
</default>
37+
</test>
3238
</regress-exe>

0 commit comments

Comments
 (0)