diff --git a/python/python-exec-service/BUILD.bazel b/python/python-exec-service/BUILD.bazel index 0129af03bb53..30bcaa3df969 100644 --- a/python/python-exec-service/BUILD.bazel +++ b/python/python-exec-service/BUILD.bazel @@ -36,6 +36,8 @@ jvm_library( "//platform/execution-impl", "//platform/eel-impl", "//platform/eel-impl-base", + "//platform/platform-impl:ide-impl", + "//platform/projectModel-impl", ] ) @@ -79,6 +81,8 @@ jvm_library( "//platform/execution-impl:execution-impl_test_lib", "//platform/eel-impl:eel-impl_test_lib", "//platform/eel-impl-base:eel-impl-base_test_lib", + "//platform/platform-impl:ide-impl_test_lib", + "//platform/projectModel-impl:projectModel-impl_test_lib", ] ) ### auto-generated section `build intellij.python.community.execService` end diff --git a/python/python-exec-service/intellij.python.community.execService.iml b/python/python-exec-service/intellij.python.community.execService.iml index 9dcd93137c37..b1c3cb14267c 100644 --- a/python/python-exec-service/intellij.python.community.execService.iml +++ b/python/python-exec-service/intellij.python.community.execService.iml @@ -56,5 +56,7 @@ + + \ No newline at end of file diff --git a/python/python-exec-service/resources/messages/PyExecBundle.properties b/python/python-exec-service/resources/messages/PyExecBundle.properties index 423864eaa03a..d860a4351573 100644 --- a/python/python-exec-service/resources/messages/PyExecBundle.properties +++ b/python/python-exec-service/resources/messages/PyExecBundle.properties @@ -7,5 +7,6 @@ py.exec.fileNotFound=File {0} not found on {1} py.exec.error.not.zero=Exit code is not zero py.exec.error.unexpected.output=Unexpected output {0} py.exec.target.name.default="Local" +py.exec.error.not.trusted=Could not start {0}: project is not trusted py.exec.target.binaries.are.not.supported=Target binaries are not supported diff --git a/python/python-exec-service/src/com/intellij/python/community/execService/impl/processLaunchers/eel.kt b/python/python-exec-service/src/com/intellij/python/community/execService/impl/processLaunchers/eel.kt index b68621ffa0fd..425110edf749 100644 --- a/python/python-exec-service/src/com/intellij/python/community/execService/impl/processLaunchers/eel.kt +++ b/python/python-exec-service/src/com/intellij/python/community/execService/impl/processLaunchers/eel.kt @@ -1,24 +1,37 @@ // Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.python.community.execService.impl.processLaunchers +import com.intellij.ide.trustedProjects.TrustedProjects +import com.intellij.openapi.diagnostic.fileLogger +import com.intellij.openapi.diagnostic.trace +import com.intellij.openapi.project.ProjectManager import com.intellij.platform.eel.EelExecApi import com.intellij.platform.eel.EelProcess import com.intellij.platform.eel.ExecuteProcessException import com.intellij.platform.eel.impl.base.ProcessFunctions import com.intellij.platform.eel.path.EelPath import com.intellij.platform.eel.provider.asEelPath +import com.intellij.platform.eel.provider.asNioPath import com.intellij.platform.eel.provider.getEelDescriptor import com.intellij.platform.eel.provider.toEelApi import com.intellij.platform.eel.provider.utils.EelPathUtils import com.intellij.platform.eel.spawnProcess +import com.intellij.project.stateStore import com.intellij.python.community.execService.BinOnEel import com.intellij.python.community.execService.TtySize +import com.intellij.python.community.execService.impl.PyExecBundle import com.jetbrains.python.Result import com.jetbrains.python.errorProcessing.Exe import com.jetbrains.python.errorProcessing.ExecErrorReason +import com.jetbrains.python.sdk.getModuleRoots import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.nio.file.Path import kotlin.io.path.pathString +private val log = fileLogger() + internal suspend fun createProcessLauncherOnEel(binOnEel: BinOnEel, launchRequest: LaunchRequest): ProcessLauncher { val exePath: EelPath = with(binOnEel) { (if (path.isAbsolute) path else workDir?.resolve(binOnEel.path) ?: path.toAbsolutePath()).asEelPath() @@ -61,8 +74,23 @@ private class EelProcessCommands( override suspend fun start(): Result { var workDir = binOnEel.workDir - workDir = if (workDir != null && !workDir.isAbsolute) workDir.toRealPath() else workDir + workDir = withContext(Dispatchers.IO) { if (workDir != null && !workDir.isAbsolute) workDir.toRealPath() else workDir } + + // If project is untrusted we should not execute anything there + val nioPathToExec = withContext(Dispatchers.IO) { + path.asNioPath().toAbsolutePath() + } + val pathIsProhibited = getProhibitedPaths().any { prohibitedParent -> + nioPathToExec.startsWith(prohibitedParent) || + (workDir != null && workDir.startsWith(prohibitedParent)) + } + if (pathIsProhibited) { + log.trace { "Prohibited exec $nioPathToExec" } + return Result.failure(ExecErrorReason.CantStart(null, PyExecBundle.message("py.exec.error.not.trusted", nioPathToExec))) + } + try { + log.trace { "Spawning $nioPathToExec" } val eelProcess = path.descriptor.toEelApi().exec.spawnProcess(path) .scope(scopeToBind) .args(args) @@ -78,3 +106,13 @@ private class EelProcessCommands( } } } + +/** + * List of roots of all untrusted projects + */ +private suspend fun getProhibitedPaths(): List = withContext(Dispatchers.Default) { + val untrustedProjects = ProjectManager.getInstance().openProjects.filter { !TrustedProjects.isProjectTrusted(it) } + return@withContext untrustedProjects.flatMap { project -> + setOf(project.stateStore.projectBasePath) + project.getModuleRoots().map { it.toNioPath() } + }.map { it.toAbsolutePath() } +} \ No newline at end of file