diff --git a/CVE-2017-15288-pre.patch b/CVE-2017-15288-pre.patch new file mode 100644 index 0000000000000000000000000000000000000000..d33fd2352a4f8785ee0d8353fc0f7126cb4708a7 --- /dev/null +++ b/CVE-2017-15288-pre.patch @@ -0,0 +1,277 @@ +From caa9ebc482969a884da5f9c9c246470811b8599d Mon Sep 17 00:00:00 2001 +From: Teemu Lehtinen +Date: Wed, 20 Aug 2014 13:20:41 +0300 +Subject: [PATCH] Add option -port to fsc + +Option "port" limits compile server lookup and start to given port. +Normally fsc will start a compile server in a random port if no server +is yet running. This can be problematic with firewalls and/or remote +compile servers. Option "port" should not be confused with option +"server" which looks for a compile server in given host and port and +fails if such server is not found. + +Automatic tests for command line user interface do not exist at all. +Thus, adding a test for one new option would require designing a whole +new testing method. + +Cherry picked from 7daecd8 +--- + .../scala/tools/nsc/CompileClient.scala | 4 +- + .../scala/tools/nsc/CompileServer.scala | 56 ++++++++++++------- + .../scala/tools/nsc/CompileSocket.scala | 37 +++++++----- + .../tools/nsc/settings/FscSettings.scala | 4 +- + .../scala/tools/util/SocketServer.scala | 4 +- + 5 files changed, 65 insertions(+), 40 deletions(-) + +diff --git a/src/compiler/scala/tools/nsc/CompileClient.scala b/src/compiler/scala/tools/nsc/CompileClient.scala +index 731f6926f00..842d6ac535b 100644 +--- a/src/compiler/scala/tools/nsc/CompileClient.scala ++++ b/src/compiler/scala/tools/nsc/CompileClient.scala +@@ -43,8 +43,8 @@ class StandardCompileClient extends HasCompileSocket with CompileOutputCommon { + info(vmArgs.mkString("[VM arguments: ", " ", "]")) + + val socket = +- if (settings.server.value == "") compileSocket.getOrCreateSocket(vmArgs mkString " ", !shutdown) +- else Some(compileSocket.getSocket(settings.server.value)) ++ if (settings.server.value == "") compileSocket.getOrCreateSocket(vmArgs mkString " ", !shutdown, settings.port.value) ++ else compileSocket.getSocket(settings.server.value) + + socket match { + case Some(sock) => compileOnServer(sock, fscArgs) +diff --git a/src/compiler/scala/tools/nsc/CompileServer.scala b/src/compiler/scala/tools/nsc/CompileServer.scala +index 7a0a072bb8d..6352d75686a 100644 +--- a/src/compiler/scala/tools/nsc/CompileServer.scala ++++ b/src/compiler/scala/tools/nsc/CompileServer.scala +@@ -5,11 +5,13 @@ + + package scala.tools.nsc + +-import java.io.{ BufferedOutputStream, FileOutputStream, PrintStream } +-import scala.tools.nsc.reporters.{Reporter, ConsoleReporter} +-import scala.reflect.internal.util.FakePos //Position ++import java.io.PrintStream ++ ++import scala.reflect.internal.util.FakePos ++import scala.tools.nsc.io.Directory ++import scala.tools.nsc.reporters.{ConsoleReporter, Reporter} ++import scala.tools.nsc.settings.FscSettings + import scala.tools.util.SocketServer +-import settings.FscSettings + + /** + * The server part of the fsc offline compiler. It awaits compilation +@@ -19,7 +21,7 @@ import settings.FscSettings + * @author Martin Odersky + * @version 1.0 + */ +-class StandardCompileServer extends SocketServer { ++class StandardCompileServer(fixPort: Int = 0) extends SocketServer(fixPort) { + lazy val compileSocket: CompileSocket = CompileSocket + + private var compiler: Global = null +@@ -34,7 +36,7 @@ class StandardCompileServer extends SocketServer { + val MaxCharge = 0.8 + + private val runtime = Runtime.getRuntime() +- import runtime.{ totalMemory, freeMemory, maxMemory } ++ import runtime.{freeMemory, maxMemory, totalMemory} + + /** Create a new compiler instance */ + def newGlobal(settings: Settings, reporter: Reporter) = +@@ -170,16 +172,16 @@ class StandardCompileServer extends SocketServer { + } + + +-object CompileServer extends StandardCompileServer { ++object CompileServer { + /** A directory holding redirected output */ +- private lazy val redirectDir = (compileSocket.tmpDir / "output-redirects").createDirectory() ++ //private lazy val redirectDir = (compileSocket.tmpDir / "output-redirects").createDirectory() + +- private def createRedirect(filename: String) = +- new PrintStream((redirectDir / filename).createFile().bufferedOutput()) ++ private def createRedirect(dir: Directory, filename: String) = ++ new PrintStream((dir / filename).createFile().bufferedOutput()) + +- def main(args: Array[String]) = ++ def main(args: Array[String]) = + execute(() => (), args) +- ++ + /** + * Used for internal testing. The callback is called upon + * server start, notifying the caller that the server is +@@ -191,21 +193,33 @@ object CompileServer extends StandardCompileServer { + */ + def execute(startupCallback : () => Unit, args: Array[String]) { + val debug = args contains "-v" ++ var port = 0 ++ ++ val i = args.indexOf("-p") ++ if (i >= 0 && args.length > i + 1) { ++ scala.util.control.Exception.ignoring(classOf[NumberFormatException]) { ++ 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() + + if (debug) { +- echo("Starting CompileServer on port " + port) +- echo("Redirect dir is " + redirectDir) ++ server.echo("Starting CompileServer on port " + server.port) ++ server.echo("Redirect dir is " + redirectDir) + } + +- Console.withErr(createRedirect("scala-compile-server-err.log")) { +- Console.withOut(createRedirect("scala-compile-server-out.log")) { +- Console.err.println("...starting server on socket "+port+"...") ++ Console.withErr(createRedirect(redirectDir, "scala-compile-server-err.log")) { ++ Console.withOut(createRedirect(redirectDir, "scala-compile-server-out.log")) { ++ Console.err.println("...starting server on socket "+server.port+"...") + Console.err.flush() +- compileSocket setPort port ++ server.compileSocket setPort server.port + startupCallback() +- run() +- +- compileSocket deletePort port ++ server.run() ++ ++ server.compileSocket deletePort server.port + } + } + } +diff --git a/src/compiler/scala/tools/nsc/CompileSocket.scala b/src/compiler/scala/tools/nsc/CompileSocket.scala +index 4051bda9144..f5039b8303f 100644 +--- a/src/compiler/scala/tools/nsc/CompileSocket.scala ++++ b/src/compiler/scala/tools/nsc/CompileSocket.scala +@@ -5,16 +5,13 @@ + + package scala.tools.nsc + +-import java.io.{ IOException, FileNotFoundException, PrintWriter, FileOutputStream } +-import java.io.{ BufferedReader, FileReader } +-import java.util.regex.Pattern +-import java.net._ ++import java.io.FileNotFoundException + import java.security.SecureRandom +-import io.{ File, Path, Directory, Socket } +-import scala.util.control.Exception.catching +-import scala.tools.util.CompileOutputCommon ++ + import scala.reflect.internal.util.StringOps.splitWhere + import scala.sys.process._ ++import scala.tools.nsc.io.{File, Path, Socket} ++import scala.tools.util.CompileOutputCommon + + trait HasCompileSocket { + def compileSocket: CompileSocket +@@ -50,6 +47,9 @@ class CompileSocket extends CompileOutputCommon { + protected lazy val compileClient: StandardCompileClient = CompileClient + def verbose = compileClient.verbose + ++ /* 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. + */ +@@ -67,7 +67,7 @@ class CompileSocket extends CompileOutputCommon { + + /** The class name of the scala compile server */ + protected val serverClass = "scala.tools.nsc.CompileServer" +- protected def serverClassArgs = if (verbose) List("-v") else Nil // debug ++ 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 = { +@@ -107,9 +107,14 @@ class CompileSocket extends CompileOutputCommon { + def portFile(port: Int) = portsDir / File(port.toString) + + /** Poll for a server port number; return -1 if none exists yet */ +- private def pollPort(): Int = portsDir.list.toList match { ++ private def pollPort(): Int = if (fixPort > 0) { ++ if (portsDir.list.toList.exists(_.name == fixPort.toString)) fixPort else -1 ++ } else portsDir.list.toList match { + case Nil => -1 +- case x :: xs => try x.name.toInt finally xs foreach (_.delete()) ++ case x :: xs => try x.name.toInt catch { ++ case e: Exception => x.delete() ++ throw e ++ } + } + + /** Get the port number to which a scala compile server is connected; +@@ -155,7 +160,8 @@ class CompileSocket extends CompileOutputCommon { + * create a new daemon if necessary. Returns None if the connection + * cannot be established. + */ +- def getOrCreateSocket(vmArgs: String, create: Boolean = true): Option[Socket] = { ++ def getOrCreateSocket(vmArgs: String, create: Boolean = true, fixedPort: Int = 0): Option[Socket] = { ++ fixPort = fixedPort + val maxMillis = 10 * 1000 // try for 10 seconds + val retryDelay = 50 + val maxAttempts = maxMillis / retryDelay +@@ -189,13 +195,16 @@ class CompileSocket extends CompileOutputCommon { + try { Some(x.toInt) } + catch { case _: NumberFormatException => None } + +- def getSocket(serverAdr: String): Socket = ( ++ def getSocket(serverAdr: String): Option[Socket] = ( + for ((name, portStr) <- splitWhere(serverAdr, _ == ':', true) ; port <- parseInt(portStr)) yield + getSocket(name, port) + ) getOrElse fatal("Malformed server address: %s; exiting" format serverAdr) + +- def getSocket(hostName: String, port: Int): Socket = +- Socket(hostName, port).opt getOrElse fatal("Unable to establish connection to server %s:%d; exiting".format(hostName, port)) ++ def getSocket(hostName: String, port: Int): Option[Socket] = { ++ val sock = Socket(hostName, port).opt ++ if (sock.isEmpty) warn("Unable to establish connection to server %s:%d".format(hostName, port)) ++ sock ++ } + + def getPassword(port: Int): String = { + val ff = portFile(port) +diff --git a/src/compiler/scala/tools/nsc/settings/FscSettings.scala b/src/compiler/scala/tools/nsc/settings/FscSettings.scala +index 5c852ae07c1..f5f971d697e 100644 +--- a/src/compiler/scala/tools/nsc/settings/FscSettings.scala ++++ b/src/compiler/scala/tools/nsc/settings/FscSettings.scala +@@ -22,13 +22,15 @@ class FscSettings(error: String => Unit) extends Settings(error) { + val reset = BooleanSetting("-reset", "Reset compile server caches") + val shutdown = BooleanSetting("-shutdown", "Shutdown compile server") + val server = StringSetting ("-server", "hostname:portnumber", "Specify compile server socket", "") ++ val port = IntSetting ("-port", "Search and start compile server in given port only", ++ 0, Some((0, Int.MaxValue)), (_: String) => None) + val preferIPv4 = BooleanSetting("-ipv4", "Use IPv4 rather than IPv6 for the server socket") + val idleMins = IntSetting ("-max-idle", "Set idle timeout in minutes for fsc (use 0 for no timeout)", + 30, Some((0, Int.MaxValue)), (_: String) => None) + + // For improved help output, separating fsc options from the others. + def fscSpecific = Set[Settings#Setting]( +- currentDir, reset, shutdown, server, preferIPv4, idleMins ++ currentDir, reset, shutdown, server, port, preferIPv4, idleMins + ) + val isFscSpecific: String => Boolean = fscSpecific map (_.name) + +diff --git a/src/compiler/scala/tools/util/SocketServer.scala b/src/compiler/scala/tools/util/SocketServer.scala +index 1b06ce2ff2e..edbc7ecc554 100644 +--- a/src/compiler/scala/tools/util/SocketServer.scala ++++ b/src/compiler/scala/tools/util/SocketServer.scala +@@ -27,12 +27,12 @@ trait CompileOutputCommon { + * @author Martin Odersky + * @version 1.0 + */ +-abstract class SocketServer extends CompileOutputCommon { ++abstract class SocketServer(fixPort: Int = 0) extends CompileOutputCommon { + def shutdown: Boolean + def session(): Unit + def timeout(): Unit = () // called after a timeout is detected for subclasses to cleanup + // a hook for subclasses +- protected def createServerSocket(): ServerSocket = new ServerSocket(0) ++ protected def createServerSocket(): ServerSocket = new ServerSocket(fixPort) + + var in: BufferedReader = _ + var out: PrintWriter = _ diff --git a/CVE-2017-15288.patch b/CVE-2017-15288.patch new file mode 100644 index 0000000000000000000000000000000000000000..460bcdb46b74170529f671fde61bc33c5ab751c5 --- /dev/null +++ b/CVE-2017-15288.patch @@ -0,0 +1,506 @@ +From 67e1437e55df6789d0883cb8846d12071de75c63 Mon Sep 17 00:00:00 2001 +From: Jason Zaugg +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)) ++ } ++} diff --git a/scala.spec b/scala.spec index 09d87664a44c10460568ffb78d8b65855bbd63c0..7ec45327561eba1cf7f7a62d2303ade22f3e0977 100644 --- a/scala.spec +++ b/scala.spec @@ -3,7 +3,7 @@ Name: scala Version: 2.10.6 -Release: 12 +Release: 13 Summary: Combination of object-oriented and functional programming License: BSD and CC0 and Public Domain URL: http://www.scala-lang.org/ @@ -24,6 +24,8 @@ Patch2: scala-2.10.3-compiler-pom.patch Patch3: scala-2.10.2-java7.patch Patch4: scala-2.10-jline.patch Patch5: scala-2.10.4-build_xml.patch +Patch6000: CVE-2017-15288-pre.patch +Patch6001: CVE-2017-15288.patch BuildArch: noarch BuildRequires: java-devel >= 1:1.7.0, ant, ant-junit, ant-contrib, jline >= 2.10, aqute-bnd, junit, javapackages-local, scala @@ -156,5 +158,11 @@ update-mime-database %{?fedora:-n} %{_datadir}/mime > /dev/null 2>&1 || : /usr/share/maven* %changelog +* Thu Dec 26 2019 zhujunhao - 2.10.6-13 +- Type:cves +- ID:CVE-2017-15288 +- SUG:restart +- DESC:fix CVE-2017-15288 + * Fri Dec 13 2019 openEuler Buildteam - 2.10.6-12 - Package init