Tue, 16 Jun 2015 18:26:25 +0530
8098578: Global scope is not accessible with indirect load call
Reviewed-by: attila, hannesw
1.1 --- a/src/jdk/nashorn/internal/objects/Global.java Tue Jun 16 13:25:41 2015 +0200 1.2 +++ b/src/jdk/nashorn/internal/objects/Global.java Tue Jun 16 18:26:25 2015 +0530 1.3 @@ -1502,26 +1502,53 @@ 1.4 } 1.5 1.6 /** 1.7 - * Global load implementation - Nashorn extension 1.8 + * Global load implementation - Nashorn extension. 1.9 * 1.10 - * @param self scope 1.11 - * @param source source to load 1.12 + * <p> 1.13 + * load builtin loads the given script. Script source can be a URL or a File 1.14 + * or a script object with name and script properties. Evaluated code gets 1.15 + * global object "this" and uses global object as scope for evaluation. 1.16 + * </p> 1.17 + * <p> 1.18 + * If self is undefined or null or global, then global object is used 1.19 + * as scope as well as "this" for the evaluated code. If self is any other 1.20 + * object, then it is indirect load call. With indirect load call, the 1.21 + * properties of scope are available to evaluated script as variables. Also, 1.22 + * global scope properties are accessible. Any var, function definition in 1.23 + * evaluated script goes into an object that is not accessible to user scripts. 1.24 + * </p> 1.25 + * Thus the indirect load call is equivalent to the following: 1.26 + * <pre> 1.27 + * <code> 1.28 + * (function (scope, source) { 1.29 + * with(scope) { 1.30 + * eval(<script_from_source>); 1.31 + * } 1.32 + * })(self, source); 1.33 + * </code> 1.34 + * </pre> 1.35 * 1.36 - * @return result of load (undefined) 1.37 + * @param self scope to use for the script evaluation 1.38 + * @param source script source 1.39 + * 1.40 + * @return result of load (may be undefined) 1.41 * 1.42 * @throws IOException if source could not be read 1.43 */ 1.44 public static Object load(final Object self, final Object source) throws IOException { 1.45 final Global global = Global.instanceFrom(self); 1.46 - final ScriptObject scope = self instanceof ScriptObject ? (ScriptObject)self : global; 1.47 - return global.getContext().load(scope, source); 1.48 + return global.getContext().load(self, source); 1.49 } 1.50 1.51 /** 1.52 - * Global loadWithNewGlobal implementation - Nashorn extension 1.53 + * Global loadWithNewGlobal implementation - Nashorn extension. 1.54 * 1.55 - * @param self scope 1.56 - * @param args from plus (optional) arguments to be passed to the loaded script 1.57 + * loadWithNewGlobal builtin loads the given script from a URL or a File 1.58 + * or a script object with name and script properties. Evaluated code gets 1.59 + * new global object "this" and uses that new global object as scope for evaluation. 1.60 + * 1.61 + * @param self self This value is ignored by this function 1.62 + * @param args optional arguments to be passed to the loaded script 1.63 * 1.64 * @return result of load (may be undefined) 1.65 *
2.1 --- a/src/jdk/nashorn/internal/runtime/Context.java Tue Jun 16 13:25:41 2015 +0200 2.2 +++ b/src/jdk/nashorn/internal/runtime/Context.java Tue Jun 16 18:26:25 2015 +0530 2.3 @@ -777,7 +777,7 @@ 2.4 * 2.5 * @throws IOException if source cannot be found or loaded 2.6 */ 2.7 - public Object load(final ScriptObject scope, final Object from) throws IOException { 2.8 + public Object load(final Object scope, final Object from) throws IOException { 2.9 final Object src = from instanceof ConsString ? from.toString() : from; 2.10 Source source = null; 2.11 2.12 @@ -829,7 +829,42 @@ 2.13 } 2.14 2.15 if (source != null) { 2.16 - return evaluateSource(source, scope, scope); 2.17 + if (scope instanceof ScriptObject && ((ScriptObject)scope).isScope()) { 2.18 + final ScriptObject sobj = (ScriptObject)scope; 2.19 + // passed object is a script object 2.20 + // Global is the only user accessible scope ScriptObject 2.21 + assert sobj.isGlobal() : "non-Global scope object!!"; 2.22 + return evaluateSource(source, sobj, sobj); 2.23 + } else if (scope == null || scope == UNDEFINED) { 2.24 + // undefined or null scope. Use current global instance. 2.25 + final Global global = getGlobal(); 2.26 + return evaluateSource(source, global, global); 2.27 + } else { 2.28 + /* 2.29 + * Arbitrary object passed for scope. 2.30 + * Indirect load that is equivalent to: 2.31 + * 2.32 + * (function(scope, source) { 2.33 + * with (scope) { 2.34 + * eval(<script_from_source>); 2.35 + * } 2.36 + * })(scope, source); 2.37 + */ 2.38 + final Global global = getGlobal(); 2.39 + // Create a new object. This is where all declarations 2.40 + // (var, function) from the evaluated code go. 2.41 + // make global to be its __proto__ so that global 2.42 + // definitions are accessible to the evaluated code. 2.43 + final ScriptObject evalScope = newScope(global); 2.44 + 2.45 + // finally, make a WithObject around user supplied scope object 2.46 + // so that it's properties are accessible as variables. 2.47 + final ScriptObject withObj = ScriptRuntime.openWith(evalScope, scope); 2.48 + 2.49 + // evaluate given source with 'withObj' as scope 2.50 + // but use global object as "this". 2.51 + return evaluateSource(source, withObj, global); 2.52 + } 2.53 } 2.54 2.55 throw typeError("cant.load.script", ScriptRuntime.safeToString(from));
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 3.2 +++ b/test/script/basic/JDK-8098578.js Tue Jun 16 18:26:25 2015 +0530 3.3 @@ -0,0 +1,107 @@ 3.4 +/* 3.5 + * Copyright (c) 2015 Oracle and/or its affiliates. All rights reserved. 3.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 3.7 + * 3.8 + * This code is free software; you can redistribute it and/or modify it 3.9 + * under the terms of the GNU General Public License version 2 only, as 3.10 + * published by the Free Software Foundation. 3.11 + * 3.12 + * This code is distributed in the hope that it will be useful, but WITHOUT 3.13 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 3.14 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 3.15 + * version 2 for more details (a copy is included in the LICENSE file that 3.16 + * accompanied this code). 3.17 + * 3.18 + * You should have received a copy of the GNU General Public License version 3.19 + * 2 along with this work; if not, write to the Free Software Foundation, 3.20 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 3.21 + * 3.22 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 3.23 + * or visit www.oracle.com if you need additional information or have any 3.24 + * questions. 3.25 + */ 3.26 + 3.27 +/** 3.28 + * JDK-8098578: Global scope is not accessible with indirect load call 3.29 + * 3.30 + * @test 3.31 + * @run 3.32 + */ 3.33 + 3.34 +var obj = { foo: 343 }; 3.35 +var global = this; 3.36 +var x = 434; 3.37 + 3.38 +// indirect load call 3.39 +var res = load.call(obj, { 3.40 + name: "t.js", 3.41 + // global is accessible. All declarations go into 3.42 + // intermediate inaccessible scope. "this" is global 3.43 + // User's passed object's properties are accessible 3.44 + // as variables. 3.45 + script: "foo -= 300; var bar = x; Assert.assertTrue(bar == 434); function func() {}; this" 3.46 +}) 3.47 + 3.48 +// 'this' for the evaluated code is global 3.49 +Assert.assertTrue(res === global); 3.50 + 3.51 +// properties of passed object are accessible in evaluated code 3.52 +Assert.assertTrue(obj.foo == 43); 3.53 + 3.54 +// vars, functions definined in evaluated code don't go into passed object 3.55 +Assert.assertTrue(typeof obj.bar == "undefined"); 3.56 +Assert.assertTrue(typeof obj.func == "undefined"); 3.57 + 3.58 +// vars, functions definined in evaluated code don't go leak into global 3.59 +Assert.assertTrue(typeof bar == "undefined"); 3.60 +Assert.assertTrue(typeof func == "undefined"); 3.61 +Assert.assertTrue(typeof foo == "undefined"); 3.62 + 3.63 +var res = load.call(undefined, { 3.64 + name: "t1.js", 3.65 + // still global is accessible and 'this' is global 3.66 + script: "Assert.assertTrue(x == 434); this" 3.67 +}); 3.68 + 3.69 +// indirect load with 'undefined' this is same as as direct load 3.70 +// or load on global itself. 3.71 +Assert.assertTrue(res === global); 3.72 + 3.73 +// indirect load with 'undefined' this is same as as direct load 3.74 +// or load on global itself. 3.75 +var res = load.call(null, { 3.76 + name: "t2.js", 3.77 + // still global is accessible and 'this' is global 3.78 + script: "Assert.assertTrue(x == 434); this" 3.79 +}); 3.80 +Assert.assertTrue(res === global); 3.81 + 3.82 +// indirect load with mirror object 3.83 +var mirror = loadWithNewGlobal({ 3.84 + name: "t3.js", 3.85 + script: "({ foo: 'hello', x: Math.PI })" 3.86 +}); 3.87 + 3.88 +var res = load.call(mirror, { 3.89 + name: "t4.js", 3.90 + script: "Assert.assertTrue(foo == 'hello'); Assert.assertTrue(x == Math.PI); this" 3.91 +}); 3.92 +Assert.assertTrue(res === global); 3.93 + 3.94 +// indirect load on non-script object, non-mirror results in TypeError 3.95 +function tryLoad(obj) { 3.96 + try { 3.97 + load.call(obj, { 3.98 + name: "t5.js", script: "this" 3.99 + }); 3.100 + throw new Error("should thrown TypeError for: " + obj); 3.101 + } catch (e if TypeError) {} 3.102 +} 3.103 + 3.104 +tryLoad("hello"); 3.105 +tryLoad(Math.E); 3.106 +tryLoad(true); 3.107 +tryLoad(false); 3.108 + 3.109 +// indirect load of a large script 3.110 +load.call({}, __DIR__ + "JDK-8098807-payload.js");