Projects
Eulaceura:Mainline
scala
_service:obs_scm:CVE-2017-15288.patch
Sign Up
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File _service:obs_scm:CVE-2017-15288.patch of Package scala
From 67e1437e55df6789d0883cb8846d12071de75c63 Mon Sep 17 00:00:00 2001 From: Jason Zaugg <jzaugg@gmail.com> Date: Mon, 2 Oct 2017 10:06:55 +1000 Subject: [PATCH] Move compilation daemon portfile under `~/.scalac/` Store the compilation daemon's administrativia (port file, redirection) under `~/.scalac/`, instead of the less standard `/tmp/scala-devel/${USER:shared}/scalac-compile-server-port`. On creation, remove group- and other-permissions from these private files, ditto for the repl's history file. On Java 6 on Windows, opt in to compilation daemon using `-nc:false`. Cherry picked from b64ad85, aa133c9, 2ceb09c --- .../scala/tools/nsc/CompileServer.scala | 22 ++-- .../scala/tools/nsc/CompileSocket.scala | 68 ++++++----- .../tools/nsc/GenericRunnerSettings.scala | 5 +- src/compiler/scala/tools/nsc/Properties.scala | 5 + .../scala/tools/nsc/ScriptRunner.scala | 20 +++- .../session/FileBackedHistory.scala | 32 +++++- .../tools/nsc/util/ScalaClassLoader.scala | 27 ++--- .../internal/util/OwnerOnlyChmod.scala | 107 ++++++++++++++++++ 8 files changed, 221 insertions(+), 65 deletions(-) create mode 100644 src/reflect/scala/reflect/internal/util/OwnerOnlyChmod.scala diff --git a/src/compiler/scala/tools/nsc/CompileServer.scala b/src/compiler/scala/tools/nsc/CompileServer.scala index 6352d75686a..c454ba8b62b 100644 --- a/src/compiler/scala/tools/nsc/CompileServer.scala +++ b/src/compiler/scala/tools/nsc/CompileServer.scala @@ -183,14 +183,15 @@ object CompileServer { execute(() => (), args) /** - * Used for internal testing. The callback is called upon - * server start, notifying the caller that the server is - * ready to run. WARNING: the callback runs in the - * server's thread, blocking the server from doing any work - * until the callback is finished. Callbacks should be kept - * simple and clients should not try to interact with the - * server while the callback is processing. - */ + * The server's main loop. + * + * `startupCallback` is used for internal testing; it's called upon server start, + * notifying the caller that the server is ready to run. + * + * WARNING: the callback runs in the server's thread, blocking the server from doing any work + * until the callback is finished. Callbacks should be kept simple and clients should not try to + * interact with the server while the callback is processing. + */ def execute(startupCallback : () => Unit, args: Array[String]) { val debug = args contains "-v" var port = 0 @@ -198,14 +199,13 @@ object CompileServer { val i = args.indexOf("-p") if (i >= 0 && args.length > i + 1) { scala.util.control.Exception.ignoring(classOf[NumberFormatException]) { - port = args(i + 1).toInt + port = args(i + 1).toInt } } // Create instance rather than extend to pass a port parameter. val server = new StandardCompileServer(port) - val redirectDir = (server.compileSocket.tmpDir / "output-redirects").createDirectory() - + val redirectDir = server.compileSocket.mkDaemonDir("fsc_redirects") if (debug) { server.echo("Starting CompileServer on port " + server.port) server.echo("Redirect dir is " + redirectDir) diff --git a/src/compiler/scala/tools/nsc/CompileSocket.scala b/src/compiler/scala/tools/nsc/CompileSocket.scala index f5039b8303f..b73d251e9cc 100644 --- a/src/compiler/scala/tools/nsc/CompileSocket.scala +++ b/src/compiler/scala/tools/nsc/CompileSocket.scala @@ -5,13 +5,17 @@ package scala.tools.nsc -import java.io.FileNotFoundException +import java.math.BigInteger import java.security.SecureRandom +import scala.io.Codec +import scala.reflect.internal.util.OwnerOnlyChmod import scala.reflect.internal.util.StringOps.splitWhere import scala.sys.process._ -import scala.tools.nsc.io.{File, Path, Socket} +import scala.tools.nsc.Properties.scalacDir +import scala.tools.nsc.io.{File, Socket} import scala.tools.util.CompileOutputCommon +import scala.util.control.NonFatal trait HasCompileSocket { def compileSocket: CompileSocket @@ -46,14 +50,10 @@ trait HasCompileSocket { class CompileSocket extends CompileOutputCommon { protected lazy val compileClient: StandardCompileClient = CompileClient def verbose = compileClient.verbose - + def verbose_=(v: Boolean) = compileClient.verbose = v /* Fixes the port where to start the server, 0 yields some free port */ var fixPort = 0 - /** The prefix of the port identification file, which is followed - * by the port number. - */ - protected lazy val dirName = "scalac-compile-server-port" protected def cmdName = Properties.scalaCmd /** The vm part of the command to start a new scala compile server */ @@ -69,20 +69,8 @@ class CompileSocket extends CompileOutputCommon { protected val serverClass = "scala.tools.nsc.CompileServer" protected def serverClassArgs = (if (verbose) List("-v") else Nil) ::: (if (fixPort > 0) List("-p", fixPort.toString) else Nil) - /** A temporary directory to use */ - val tmpDir = { - val udir = Option(Properties.userName) getOrElse "shared" - val f = (Path(Properties.tmpDir) / ("scala-devel" + udir)).createDirectory() - - if (f.isDirectory && f.canWrite) { - info("[Temp directory: " + f + "]") - f - } - else fatal("Could not find a directory for temporary files") - } - /* A directory holding port identification files */ - val portsDir = (tmpDir / dirName).createDirectory() + private lazy val portsDir = mkDaemonDir("fsc_port") /** The command which starts the compile server, given vm arguments. * @@ -104,7 +92,7 @@ class CompileSocket extends CompileOutputCommon { } /** The port identification file */ - def portFile(port: Int) = portsDir / File(port.toString) + def portFile(port: Int): File = portsDir / File(port.toString) /** Poll for a server port number; return -1 if none exists yet */ private def pollPort(): Int = if (fixPort > 0) { @@ -138,19 +126,19 @@ class CompileSocket extends CompileOutputCommon { } info("[Port number: " + port + "]") if (port < 0) - fatal("Could not connect to compilation daemon after " + attempts + " attempts.") + fatal(s"Could not connect to compilation daemon after $attempts attempts. To run without it, use `-nocompdaemon` or `-nc`.") port } /** Set the port number to which a scala compile server is connected */ - def setPort(port: Int) { - val file = portFile(port) - val secret = new SecureRandom().nextInt.toString - - try file writeAll secret catch { - case e @ (_: FileNotFoundException | _: SecurityException) => - fatal("Cannot create file: %s".format(file.path)) - } + def setPort(port: Int): Unit = { + val file = portFile(port) + // 128 bits of delicious randomness, suitable for printing with println over a socket, + // and storage in a file -- see getPassword + val secretDigits = new BigInteger(128, new SecureRandom()).toString.getBytes("UTF-8") + + try OwnerOnlyChmod().chmodAndWrite(file.jfile, secretDigits) + catch chmodFailHandler(s"Cannot create file: ${file}") } /** Delete the port number to which a scala compile server was connected */ @@ -208,7 +196,7 @@ class CompileSocket extends CompileOutputCommon { def getPassword(port: Int): String = { val ff = portFile(port) - val f = ff.bufferedReader() + val f = ff.bufferedReader(Codec.UTF8) // allow some time for the server to start up def check = { @@ -223,6 +211,24 @@ class CompileSocket extends CompileOutputCommon { f.close() result } + + private def chmodFailHandler(msg: String): PartialFunction[Throwable, Unit] = { + case NonFatal(e) => + if (verbose) e.printStackTrace() + fatal(msg) + } + + def mkDaemonDir(name: String) = { + val dir = (scalacDir / name).createDirectory() + + if (dir.isDirectory && dir.canWrite) info(s"[Temp directory: $dir]") + else fatal(s"Could not create compilation daemon directory $dir") + + try OwnerOnlyChmod().chmod(dir.jfile) + catch chmodFailHandler(s"Failed to change permissions on $dir. The compilation daemon requires a secure directory; use -nc to disable the daemon.") + dir + } + } diff --git a/src/compiler/scala/tools/nsc/GenericRunnerSettings.scala b/src/compiler/scala/tools/nsc/GenericRunnerSettings.scala index 9c2db11a56e..edfc095c7f7 100644 --- a/src/compiler/scala/tools/nsc/GenericRunnerSettings.scala +++ b/src/compiler/scala/tools/nsc/GenericRunnerSettings.scala @@ -38,8 +38,11 @@ class GenericRunnerSettings(error: String => Unit) extends Settings(error) { val nc = BooleanSetting( "-nc", - "do not use the fsc compilation daemon") withAbbreviation "-nocompdaemon" + "do not use the fsc compilation daemon") withAbbreviation "-nocompdaemon" withPostSetHook((x: BooleanSetting) => {_useCompDaemon = !x.value }) @deprecated("Use `nc` instead", "2.9.0") def nocompdaemon = nc @deprecated("Use `save` instead", "2.9.0") def savecompiled = save + + private[this] var _useCompDaemon = true + def useCompDaemon: Boolean = _useCompDaemon } diff --git a/src/compiler/scala/tools/nsc/Properties.scala b/src/compiler/scala/tools/nsc/Properties.scala index 55fd1967164..8b314ba0b82 100644 --- a/src/compiler/scala/tools/nsc/Properties.scala +++ b/src/compiler/scala/tools/nsc/Properties.scala @@ -5,6 +5,8 @@ package scala.tools.nsc +import scala.tools.nsc.io.Path + /** Loads `compiler.properties` from the jar archive file. */ object Properties extends scala.util.PropertiesTrait { @@ -22,4 +24,7 @@ object Properties extends scala.util.PropertiesTrait { // derived values def isEmacsShell = propOrEmpty("env.emacs") != "" def fileEndings = fileEndingString.split("""\|""").toList + + // Where we keep fsc's state (ports/redirection) + lazy val scalacDir = (Path(Properties.userHome) / ".scalac").createDirectory(force = false) } diff --git a/src/compiler/scala/tools/nsc/ScriptRunner.scala b/src/compiler/scala/tools/nsc/ScriptRunner.scala index 107c4b3df3d..9af0079ffd6 100644 --- a/src/compiler/scala/tools/nsc/ScriptRunner.scala +++ b/src/compiler/scala/tools/nsc/ScriptRunner.scala @@ -77,7 +77,10 @@ class ScriptRunner extends HasCompileSocket { val coreCompArgs = compSettings flatMap (_.unparse) val compArgs = coreCompArgs ++ List("-Xscript", scriptMain(settings), scriptFile) - CompileSocket getOrCreateSocket "" match { + // TODO: untangle this mess of top-level objects with their own little view of the mutable world of settings + compileSocket.verbose = settings.verbose.value + + compileSocket getOrCreateSocket "" match { case Some(sock) => compileOnServer(sock, compArgs) case _ => false } @@ -109,14 +112,23 @@ class ScriptRunner extends HasCompileSocket { settings.outdir.value = compiledPath.path - if (settings.nc.value) { - /** Setting settings.script.value informs the compiler this is not a - * self contained compilation unit. + // can't reliably lock down permissions on the portfile in this environment => disable by default. + // not the cleanest to do this here, but I don't see where else to decide this and emit the warning below + val cantLockdown = !settings.nc.isSetByUser && scala.util.Properties.isWin && !scala.util.Properties.isJavaAtLeast("7") + + if (cantLockdown) settings.nc.value = true + + if (!settings.useCompDaemon) { + /* Setting settings.script.value informs the compiler this is not a + * self contained compilation unit. */ settings.script.value = mainClass val reporter = new ConsoleReporter(settings) val compiler = newGlobal(settings, reporter) + if (cantLockdown) + reporter.echo("[info] The compilation daemon is disabled by default on this platform. To force its usage, use `-nocompdaemon:false`.") + new compiler.Run compile List(scriptFile) if (reporter.hasErrors) None else Some(compiledPath) } diff --git a/src/compiler/scala/tools/nsc/interpreter/session/FileBackedHistory.scala b/src/compiler/scala/tools/nsc/interpreter/session/FileBackedHistory.scala index dddfb1b8f64..5467c0a61ef 100644 --- a/src/compiler/scala/tools/nsc/interpreter/session/FileBackedHistory.scala +++ b/src/compiler/scala/tools/nsc/interpreter/session/FileBackedHistory.scala @@ -7,14 +7,37 @@ package scala.tools.nsc package interpreter package session -import scala.tools.nsc.io._ -import FileBackedHistory._ +import scala.reflect.internal.util.OwnerOnlyChmod +import scala.reflect.io.{File, Path} +import scala.tools.nsc.Properties.{propOrNone, userHome} +import scala.util.control.NonFatal /** TODO: file locking. */ trait FileBackedHistory extends JLineHistory with JPersistentHistory { def maxSize: Int = 2500 - protected lazy val historyFile: File = defaultFile + + // For a history file in the standard location, always try to restrict permission, + // creating an empty file if none exists. + // For a user-specified location, only lock down permissions if we're the ones + // creating it, otherwise responsibility for permissions is up to the caller. + protected lazy val historyFile: File = File { + propOrNone("scala.shell.histfile").map(Path.apply) match { + case Some(p) => if (!p.exists) secure(p) else p + case None => secure(Path(userHome) / FileBackedHistory.defaultFileName) + } + } + + private def secure(p: Path): Path = { + try OwnerOnlyChmod().chmodOrCreateEmpty(p.jfile) + catch { case NonFatal(e) => + if (interpreter.isReplDebug) e.printStackTrace() + interpreter.replinfo(s"Warning: history file ${p}'s permissions could not be restricted to owner-only.") + } + + p + } + private var isPersistent = true locally { @@ -79,6 +102,5 @@ object FileBackedHistory { // val ContinuationNL: String = Array('\003', '\n').mkString import Properties.userHome - def defaultFileName = ".scala_history" - def defaultFile: File = File(Path(userHome) / defaultFileName) + final val defaultFileName = ".scala_history" } diff --git a/src/compiler/scala/tools/nsc/util/ScalaClassLoader.scala b/src/compiler/scala/tools/nsc/util/ScalaClassLoader.scala index 1f6fa68f572..0673fa1f758 100644 --- a/src/compiler/scala/tools/nsc/util/ScalaClassLoader.scala +++ b/src/compiler/scala/tools/nsc/util/ScalaClassLoader.scala @@ -3,19 +3,18 @@ * @author Paul Phillips */ -package scala.tools.nsc -package util - -import java.lang.{ ClassLoader => JClassLoader } -import java.lang.reflect.{ Constructor, Modifier, Method } -import java.io.{ File => JFile } -import java.net.{ URLClassLoader => JURLClassLoader } -import java.net.URL -import scala.reflect.runtime.ReflectionUtils.unwrapHandler -import ScalaClassLoader._ -import scala.util.control.Exception.{ catching } +package scala.tools.nsc.util + +import java.io.{File => JFile} +import java.lang.reflect.{Constructor, Modifier} +import java.lang.{ClassLoader => JClassLoader} +import java.net.{URL, URLClassLoader => JURLClassLoader} + import scala.language.implicitConversions -import scala.reflect.{ ClassTag, classTag } +import scala.reflect.runtime.ReflectionUtils.unwrapHandler +import scala.reflect.{ClassTag, classTag} +import scala.tools.nsc.io.Streamable +import scala.util.control.Exception.catching trait HasClassPath { def classPathURLs: Seq[URL] @@ -25,6 +24,8 @@ trait HasClassPath { * of java reflection. */ trait ScalaClassLoader extends JClassLoader { + import ScalaClassLoader._ + /** Executing an action with this classloader as context classloader */ def asContext[T](action: => T): T = { val saved = contextLoader @@ -52,7 +53,7 @@ trait ScalaClassLoader extends JClassLoader { /** The actual bytes for a class file, or an empty array if it can't be found. */ def classBytes(className: String): Array[Byte] = classAsStream(className) match { case null => Array() - case stream => io.Streamable.bytes(stream) + case stream => Streamable.bytes(stream) } /** An InputStream representing the given class name, or null if not found. */ diff --git a/src/reflect/scala/reflect/internal/util/OwnerOnlyChmod.scala b/src/reflect/scala/reflect/internal/util/OwnerOnlyChmod.scala new file mode 100644 index 00000000000..c0da65db387 --- /dev/null +++ b/src/reflect/scala/reflect/internal/util/OwnerOnlyChmod.scala @@ -0,0 +1,107 @@ +/* NSC -- new Scala compiler + * Copyright 2017 LAMP/EPFL + * @author Martin Odersky + */ +package scala.reflect.internal.util + +import java.io.{File, FileOutputStream, IOException} + + +trait OwnerOnlyChmod { + /** Remove group/other permissions for `file`, it if exists */ + def chmod(file: java.io.File): Unit + + /** Delete `file` if it exists, recreate it with no group/other permissions, and write `contents` */ + final def chmodAndWrite(file: File, contents: Array[Byte]): Unit = { + file.delete() + val fos = new FileOutputStream(file) + fos.close() + chmod(file) + val fos2 = new FileOutputStream(file) + try { + fos2.write(contents) + } finally { + fos2.close() + } + } + + // TODO: use appropriate NIO call instead of two-step exists?/create! + final def chmodOrCreateEmpty(file: File): Unit = + if (!file.exists()) chmodAndWrite(file, Array[Byte]()) else chmod(file) + +} + +object OwnerOnlyChmod { + def apply(): OwnerOnlyChmod = { + if (!util.Properties.isWin) Java6UnixChmod + else if (util.Properties.isJavaAtLeast("7")) new NioAclChmodReflective + else NoOpOwnerOnlyChmod + } +} + +object NoOpOwnerOnlyChmod extends OwnerOnlyChmod { + override def chmod(file: File): Unit = () +} + + +/** Adjust permissions with `File.{setReadable, setWritable}` */ +object Java6UnixChmod extends OwnerOnlyChmod { + + def chmod(file: File): Unit = if (file.exists()) { + def clearAndSetOwnerOnly(f: (Boolean, Boolean) => Boolean): Unit = { + def fail() = throw new IOException("Unable to modify permissions of " + file) + // attribute = false, ownerOnly = false + if (!f(false, false)) fail() + // attribute = true, ownerOnly = true + if (!f(true, true)) fail() + } + if (file.isDirectory) { + clearAndSetOwnerOnly(file.setExecutable) + } + clearAndSetOwnerOnly(file.setReadable) + clearAndSetOwnerOnly(file.setWritable) + } +} + + +object NioAclChmodReflective { + val file_toPath = classOf[java.io.File].getMethod("toPath") + val files = Class.forName("java.nio.file.Files") + val path_class = Class.forName("java.nio.file.Path") + val getFileAttributeView = files.getMethod("getFileAttributeView", path_class, classOf[Class[_]], Class.forName("[Ljava.nio.file.LinkOption;")) + val linkOptionEmptyArray = java.lang.reflect.Array.newInstance(Class.forName("java.nio.file.LinkOption"), 0) + val aclFileAttributeView_class = Class.forName("java.nio.file.attribute.AclFileAttributeView") + val aclEntry_class = Class.forName("java.nio.file.attribute.AclEntry") + val aclEntryBuilder_class = Class.forName("java.nio.file.attribute.AclEntry$Builder") + val newBuilder = aclEntry_class.getMethod("newBuilder") + val aclEntryBuilder_build = aclEntryBuilder_class.getMethod("build") + val userPrinciple_class = Class.forName("java.nio.file.attribute.UserPrincipal") + val setPrincipal = aclEntryBuilder_class.getMethod("setPrincipal", userPrinciple_class) + val setPermissions = aclEntryBuilder_class.getMethod("setPermissions", Class.forName("[Ljava.nio.file.attribute.AclEntryPermission;")) + val aclEntryType_class = Class.forName("java.nio.file.attribute.AclEntryType") + val setType = aclEntryBuilder_class.getMethod("setType", aclEntryType_class) + val aclEntryPermission_class = Class.forName("java.nio.file.attribute.AclEntryPermission") + val aclEntryPermissionValues = aclEntryPermission_class.getDeclaredMethod("values") + val aclEntryType_ALLOW = aclEntryType_class.getDeclaredField("ALLOW") +} + +/** Reflective version of `NioAclChmod` */ +final class NioAclChmodReflective extends OwnerOnlyChmod { + import NioAclChmodReflective._ + def chmod(file: java.io.File): Unit = { + val path = file_toPath.invoke(file) + val view = getFileAttributeView.invoke(null, path, aclFileAttributeView_class, linkOptionEmptyArray) + val setAcl = aclFileAttributeView_class.getMethod("setAcl", classOf[java.util.List[_]]) + val getOwner = aclFileAttributeView_class.getMethod("getOwner") + val owner = getOwner.invoke(view) + setAcl.invoke(view, acls(owner)) + } + + private def acls(owner: Object) = { + val builder = newBuilder.invoke(null) + setPrincipal.invoke(builder, owner) + setPermissions.invoke(builder, aclEntryPermissionValues.invoke(null)) + setType.invoke(builder, aclEntryType_ALLOW.get(null)) + java.util.Collections.singletonList(aclEntryBuilder_build.invoke(builder)) + } +}
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.
浙ICP备2022010568号-2