From 36001ff931c979a117f8550d14affd05aad7392d Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Mon, 10 Jul 2017 20:39:35 +0300 Subject: [PATCH] Do not allow to access compiler internals in .kts scripts Set parent class loader to null explicitly instead of the system class loader --- .../jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt | 2 +- .../compiler/KotlinToJVMBytecodeCompiler.kt | 30 ++++------------ .../script/use_compiler_internals.kts | 14 ++++++++ .../kotlin/scripts/ScriptTemplateTest.kt | 24 +++++++------ .../jetbrains/kotlin/scripts/ScriptTest.kt | 34 +++++++------------ .../kotlin/script/util/ScriptUtilIT.kt | 13 +++---- 6 files changed, 55 insertions(+), 62 deletions(-) create mode 100644 compiler/testData/script/use_compiler_internals.kts diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt index aa5b6a79b20..119d34482a2 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt @@ -190,7 +190,7 @@ class K2JVMCompiler : CLICompiler() { val scriptArgs = arguments.freeArgs.subList(1, arguments.freeArgs.size) - return KotlinToJVMBytecodeCompiler.compileAndExecuteScript(environment, paths, scriptArgs) + return KotlinToJVMBytecodeCompiler.compileAndExecuteScript(environment, scriptArgs) } else { if (destination != null) { diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinToJVMBytecodeCompiler.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinToJVMBytecodeCompiler.kt index 6a7d5eea5ed..34b6af3d9d7 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinToJVMBytecodeCompiler.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinToJVMBytecodeCompiler.kt @@ -59,7 +59,6 @@ import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStat import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.script.tryConstructClassFromStringArgs import org.jetbrains.kotlin.util.PerformanceCounter -import org.jetbrains.kotlin.utils.KotlinPaths import org.jetbrains.kotlin.utils.newLinkedHashMapWithExpectedSize import java.io.File import java.lang.reflect.InvocationTargetException @@ -259,12 +258,8 @@ object KotlinToJVMBytecodeCompiler { } } - fun compileAndExecuteScript( - environment: KotlinCoreEnvironment, - paths: KotlinPaths, - scriptArgs: List): ExitCode - { - val scriptClass = compileScript(environment, paths) ?: return ExitCode.COMPILATION_ERROR + internal fun compileAndExecuteScript(environment: KotlinCoreEnvironment, scriptArgs: List): ExitCode { + val scriptClass = compileScript(environment) ?: return ExitCode.COMPILATION_ERROR try { try { @@ -327,29 +322,18 @@ object KotlinToJVMBytecodeCompiler { } stream.println(cause) val fullTrace = cause.stackTrace - val relevantEntries = fullTrace.size - exception.stackTrace.size - for (i in 0..relevantEntries - 1) { + for (i in 0 until fullTrace.size - exception.stackTrace.size) { stream.println("\tat " + fullTrace[i]) } } - fun compileScript(environment: KotlinCoreEnvironment, paths: KotlinPaths): Class<*>? = - compileScript(environment, - { - val classPaths = arrayListOf(paths.runtimePath.toURI().toURL()) - environment.configuration.jvmClasspathRoots.mapTo(classPaths) { it.toURI().toURL() } - URLClassLoader(classPaths.toTypedArray()) - }) - - fun compileScript(environment: KotlinCoreEnvironment, parentClassLoader: ClassLoader): Class<*>? = compileScript(environment, { parentClassLoader }) - - private inline fun compileScript( - environment: KotlinCoreEnvironment, - makeParentClassLoader: () -> ClassLoader): Class<*>? { + fun compileScript(environment: KotlinCoreEnvironment, parentClassLoader: ClassLoader? = null): Class<*>? { val state = analyzeAndGenerate(environment) ?: return null try { - val classLoader = GeneratedClassLoader(state.factory, makeParentClassLoader()) + val classLoader = GeneratedClassLoader(state.factory, parentClassLoader ?: URLClassLoader( + environment.configuration.jvmClasspathRoots.map { it.toURI().toURL() }.toTypedArray(), null + )) val script = environment.getSourceFiles()[0].script ?: error("Script must be parsed") return classLoader.loadClass(script.fqName.asString()) diff --git a/compiler/testData/script/use_compiler_internals.kts b/compiler/testData/script/use_compiler_internals.kts new file mode 100644 index 00000000000..4b10170e52d --- /dev/null +++ b/compiler/testData/script/use_compiler_internals.kts @@ -0,0 +1,14 @@ +fun checkInaccessible(name: String) { + try { + Class.forName(name) + throw AssertionError("Class should not be accessible from script via the class loader: $name") + } + catch (e: ClassNotFoundException) { + // OK + } +} + +checkInaccessible("org.jetbrains.kotlin.config.KotlinCompilerVersion") +checkInaccessible("org.jetbrains.kotlin.cli.jvm.K2JVMCompiler") +checkInaccessible("org.jetbrains.kotlin.preloading.Preloader") +print("OK") diff --git a/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTemplateTest.kt b/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTemplateTest.kt index 040e067be84..1ea9527db5f 100644 --- a/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTemplateTest.kt +++ b/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTemplateTest.kt @@ -19,7 +19,6 @@ package org.jetbrains.kotlin.scripts import com.intellij.openapi.util.Disposer import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys import org.jetbrains.kotlin.cli.common.messages.* -import org.jetbrains.kotlin.script.tryConstructClassFromStringArgs import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler @@ -27,7 +26,10 @@ import org.jetbrains.kotlin.codegen.CompilationException import org.jetbrains.kotlin.config.JVMConfigurationKeys import org.jetbrains.kotlin.config.addKotlinSourceRoot import org.jetbrains.kotlin.daemon.toFile -import org.jetbrains.kotlin.script.* +import org.jetbrains.kotlin.script.InvalidScriptResolverAnnotation +import org.jetbrains.kotlin.script.KotlinScriptDefinition +import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromAnnotatedTemplate +import org.jetbrains.kotlin.script.tryConstructClassFromStringArgs import org.jetbrains.kotlin.test.ConfigurationKind import org.jetbrains.kotlin.test.KotlinTestUtils import org.jetbrains.kotlin.test.TestJdkKind @@ -272,17 +274,19 @@ class ScriptTemplateTest { environment: Map? = null, runIsolated: Boolean = true, suppressOutput: Boolean = false, - includeKotlinRuntime: Boolean = true): Class<*>? = - compileScriptImpl("compiler/testData/script/" + scriptPath, KotlinScriptDefinitionFromAnnotatedTemplate(scriptTemplate, null, null, environment), runIsolated, suppressOutput, includeKotlinRuntime) + includeKotlinRuntime: Boolean = true + ): Class<*>? = + compileScriptImpl("compiler/testData/script/" + scriptPath, KotlinScriptDefinitionFromAnnotatedTemplate( + scriptTemplate, null, null, environment + ), runIsolated, suppressOutput, includeKotlinRuntime) private fun compileScriptImpl( scriptPath: String, scriptDefinition: KotlinScriptDefinition, runIsolated: Boolean, suppressOutput: Boolean, - includeKotlinRuntime: Boolean): Class<*>? - { - val paths = PathUtil.getKotlinPathsForDistDirectory() + includeKotlinRuntime: Boolean + ): Class<*>? { val messageCollector = if (suppressOutput) MessageCollector.NONE else PrintingMessageCollector(System.err, MessageRenderer.PLAIN_FULL_PATHS, false) @@ -298,8 +302,7 @@ class ScriptTemplateTest { val environment = KotlinCoreEnvironment.createForProduction(rootDisposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES) try { - return if (runIsolated) KotlinToJVMBytecodeCompiler.compileScript(environment, paths) - else KotlinToJVMBytecodeCompiler.compileScript(environment, this::class.java.classLoader) + return KotlinToJVMBytecodeCompiler.compileScript(environment, this::class.java.classLoader.takeUnless { runIsolated }) } catch (e: CompilationException) { messageCollector.report(CompilerMessageSeverity.EXCEPTION, OutputMessageUtil.renderException(e), @@ -310,7 +313,6 @@ class ScriptTemplateTest { MessageCollectorUtil.reportException(messageCollector, t) throw t } - } finally { Disposer.dispose(rootDisposable) @@ -372,6 +374,8 @@ class TestKotlinScriptDependenciesResolver : TestKotlinScriptDummyDependenciesRe } } +class TestParamClass(@Suppress("unused") val memberNum: Int) + @ScriptTemplateDefinition( scriptFilePattern =".*\\.kts", resolver = TestKotlinScriptDummyDependenciesResolver::class) diff --git a/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest.kt b/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest.kt index 2ad485d56d0..4d8bb1ca6d3 100644 --- a/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest.kt +++ b/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest.kt @@ -19,7 +19,6 @@ package org.jetbrains.kotlin.scripts import com.intellij.openapi.util.Disposer import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys import org.jetbrains.kotlin.cli.common.messages.* -import org.jetbrains.kotlin.script.tryConstructClassFromStringArgs import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler @@ -28,18 +27,16 @@ import org.jetbrains.kotlin.config.JVMConfigurationKeys import org.jetbrains.kotlin.config.addKotlinSourceRoot import org.jetbrains.kotlin.script.KotlinScriptDefinition import org.jetbrains.kotlin.script.StandardScriptDefinition +import org.jetbrains.kotlin.script.tryConstructClassFromStringArgs import org.jetbrains.kotlin.test.ConfigurationKind import org.jetbrains.kotlin.test.KotlinTestUtils import org.jetbrains.kotlin.test.TestJdkKind import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase -import org.jetbrains.kotlin.utils.PathUtil import org.junit.Assert -import org.junit.Test import java.io.File import java.net.URLClassLoader class ScriptTest : KtUsefulTestCase() { - @Test fun testStandardScriptWithParams() { val aClass = compileScript("fib_std.kts", StandardScriptDefinition) Assert.assertNotNull(aClass) @@ -50,7 +47,6 @@ class ScriptTest : KtUsefulTestCase() { assertEqualsTrimmed(NUM_4_LINE + " (comment)" + FIB_SCRIPT_OUTPUT_TAIL, out) } - @Test fun testStandardScriptWithoutParams() { val aClass = compileScript("fib_std.kts", StandardScriptDefinition) Assert.assertNotNull(aClass) @@ -61,7 +57,6 @@ class ScriptTest : KtUsefulTestCase() { assertEqualsTrimmed(NUM_4_LINE + " (none)" + FIB_SCRIPT_OUTPUT_TAIL, out) } - @Test fun testStandardScriptWithSaving() { val tmpdir = File(KotlinTestUtils.tmpDirForTest(this), "withSaving") tmpdir.mkdirs() @@ -82,22 +77,20 @@ class ScriptTest : KtUsefulTestCase() { assertEqualsTrimmed(NUM_4_LINE + " (none)" + FIB_SCRIPT_OUTPUT_TAIL, out2) } + fun testUseCompilerInternals() { + val scriptClass = compileScript("use_compiler_internals.kts", StandardScriptDefinition)!! + assertEquals("OK", captureOut { + tryConstructClassFromStringArgs(scriptClass, emptyList())!! + }) + } + private fun compileScript( scriptPath: String, scriptDefinition: KotlinScriptDefinition, runIsolated: Boolean = true, suppressOutput: Boolean = false, - saveClassesDir: File? = null): Class<*>? = - compileScriptImpl("compiler/testData/script/" + scriptPath, scriptDefinition, runIsolated, suppressOutput, saveClassesDir) - - private fun compileScriptImpl( - scriptPath: String, - scriptDefinition: KotlinScriptDefinition, - runIsolated: Boolean, - suppressOutput: Boolean, - saveClassesDir: File? = null): Class<*>? - { - val paths = PathUtil.getKotlinPathsForDistDirectory() + saveClassesDir: File? = null + ): Class<*>? { val messageCollector = if (suppressOutput) MessageCollector.NONE else PrintingMessageCollector(System.err, MessageRenderer.PLAIN_FULL_PATHS, false) @@ -106,7 +99,7 @@ class ScriptTest : KtUsefulTestCase() { try { val configuration = KotlinTestUtils.newConfiguration(ConfigurationKind.ALL, TestJdkKind.FULL_JDK) configuration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector) - configuration.addKotlinSourceRoot(scriptPath) + configuration.addKotlinSourceRoot("compiler/testData/script/$scriptPath") configuration.add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, scriptDefinition) configuration.put(JVMConfigurationKeys.RETAIN_OUTPUT_IN_MEMORY, true) if (saveClassesDir != null) { @@ -116,8 +109,7 @@ class ScriptTest : KtUsefulTestCase() { val environment = KotlinCoreEnvironment.createForProduction(rootDisposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES) try { - return if (runIsolated) KotlinToJVMBytecodeCompiler.compileScript(environment, paths) - else KotlinToJVMBytecodeCompiler.compileScript(environment, this::class.java.classLoader) + return KotlinToJVMBytecodeCompiler.compileScript(environment, this::class.java.classLoader.takeUnless { runIsolated }) } catch (e: CompilationException) { messageCollector.report(CompilerMessageSeverity.EXCEPTION, OutputMessageUtil.renderException(e), @@ -134,5 +126,3 @@ class ScriptTest : KtUsefulTestCase() { } } } - -class TestParamClass(@Suppress("unused") val memberNum: Int) \ No newline at end of file diff --git a/libraries/tools/kotlin-script-util/src/test/kotlin/org/jetbrains/kotlin/script/util/ScriptUtilIT.kt b/libraries/tools/kotlin-script-util/src/test/kotlin/org/jetbrains/kotlin/script/util/ScriptUtilIT.kt index eb5c0f1921e..e00a0b03557 100644 --- a/libraries/tools/kotlin-script-util/src/test/kotlin/org/jetbrains/kotlin/script/util/ScriptUtilIT.kt +++ b/libraries/tools/kotlin-script-util/src/test/kotlin/org/jetbrains/kotlin/script/util/ScriptUtilIT.kt @@ -115,15 +115,16 @@ done scriptFileName: String, scriptTemplate: KClass, environment: Map? = null, - suppressOutput: Boolean = false): Class<*>? = - compileScriptImpl("src/test/resources/scripts/" + scriptFileName, KotlinScriptDefinitionFromAnnotatedTemplate(scriptTemplate, null, null, environment), suppressOutput) + suppressOutput: Boolean = false + ): Class<*>? = + compileScriptImpl("src/test/resources/scripts/" + scriptFileName, + KotlinScriptDefinitionFromAnnotatedTemplate(scriptTemplate, null, null, environment), suppressOutput) private fun compileScriptImpl( scriptPath: String, scriptDefinition: KotlinScriptDefinition, - suppressOutput: Boolean): Class<*>? - { - val paths = PathUtil.getKotlinPathsForDistDirectory() + suppressOutput: Boolean + ): Class<*>? { val messageCollector = if (suppressOutput) MessageCollector.NONE else PrintingMessageCollector(System.err, MessageRenderer.PLAIN_FULL_PATHS, false) @@ -158,7 +159,7 @@ done val environment = KotlinCoreEnvironment.createForProduction(rootDisposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES) try { - return KotlinToJVMBytecodeCompiler.compileScript(environment, paths) + return KotlinToJVMBytecodeCompiler.compileScript(environment) } catch (e: CompilationException) { messageCollector.report(CompilerMessageSeverity.EXCEPTION, OutputMessageUtil.renderException(e),