Skip to content

Commit

Permalink
Merge pull request #124 from deltacv/dev
Browse files Browse the repository at this point in the history
3.8.3
  • Loading branch information
serivesmejia authored Nov 5, 2024
2 parents df34e30 + 58b2685 commit cff0ea2
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 95 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ class PluginClassLoader(

private var additionalZipFiles = mutableListOf<WeakReference<ZipFile>>()

private var zipFiles = mutableMapOf<File, ZipFile>()

private var classpathCacheLock = Any()
private var classpathCache = mutableMapOf<String, File>()

private val zipFile = try {
ZipFile(pluginJar)
} catch (e: Exception) {
Expand Down Expand Up @@ -129,13 +134,13 @@ class PluginClassLoader(

clazz = Class.forName(name)
}
} catch(e: Throwable) {
} catch (e: Throwable) {
clazz = try {
loadClassStrict(name)
} catch(_: Throwable) {
} catch (_: Throwable) {
val classpathClass = classFromClasspath(name)

if(classpathClass != null) {
if (classpathClass != null) {
classpathClass
} else {
throw e
Expand Down Expand Up @@ -182,48 +187,62 @@ class PluginClassLoader(
* Get a resource from the classpath specified in the constructor
*/
fun resourceAsStreamFromClasspath(name: String): InputStream? {
for (file in classpath) {
if (file == pluginJar) continue

val zipFile = ZipFile(file)

val entry = zipFile.getEntry(name)

if (entry != null) {
try {
additionalZipFiles.add(WeakReference(zipFile))
return zipFile.getInputStream(entry)
} catch (_: Exception) {
zipFile.close()
}
} else {
zipFile.close()
}
}

return null
return resourceFromClasspath(name)?.openStream()
}

/**
* Get a resource from the classpath specified in the constructor
*/
fun resourceFromClasspath(name: String): URL? {
for (file in classpath) {
if (file == pluginJar) continue

val zipFile = ZipFile(file)
fun resFromJarFile(file: File): URL? {
if (file == pluginJar) return null

try {
val zipFile = zipFiles[file] ?: ZipFile(file)
zipFiles[file] = zipFile

val entry = zipFile.getEntry(name)
if (entry == null) return null

if (entry != null) {
try {
return URL("jar:file:${file.absolutePath}!/$name")
} catch (_: Exception) {
}
val packages = name.split("/")

val firstPackage = if (packages.size > 3)
packages[0] + "." + packages[1] + "." + packages[3]
else if (packages.size > 2)
packages[0] + "." + packages[1]
else name

synchronized(classpathCacheLock) {
classpathCache[firstPackage] = file
}
} finally {
zipFile.close()

try {
return URL("jar:file:${file.absolutePath}!/$name")
} catch (_: Exception) {
}
} catch (_: Exception) {
}

return null
}

val cache = synchronized(classpathCacheLock) {
classpathCache.filterKeys { name.contains(it.replace("/", ".")) }
}

for ((cacheName, file) in cache) {
if (name.contains(cacheName)) {
val res = resFromJarFile(file)
if (res != null) {
return res
}
}
}

for (file in classpath) {
val res = resFromJarFile(file)
if (res != null) {
return res
}
}

Expand All @@ -234,30 +253,64 @@ class PluginClassLoader(
* Load a class from the classpath specified in the constructor
*/
fun classFromClasspath(className: String): Class<*>? {
for (file in classpath) {
if (file == pluginJar) continue

val zipFile = ZipFile(file)
fun classFromJarFile(file: File): Class<*>? {
if (file == pluginJar) return null

try {
val zipFile = zipFiles[file] ?: ZipFile(file)
zipFiles[file] = zipFile

val entry = zipFile.getEntry(className.replace('.', '/') + ".class")
if (entry == null) return null

val packages = className.split(".")

val firstPackage = if (packages.size > 3)
packages[0] + "." + packages[1] + "." + packages[3]
else if (packages.size > 2)
packages[0] + "." + packages[1]
else className

if (entry != null) {
return loadClass(entry, zipFile = zipFile)
synchronized(classpathCacheLock) {
classpathCache[firstPackage] = file
}

return loadClass(entry, zipFile = zipFile)
} catch (_: Exception) {
}

return null
}

val cache = synchronized(classpathCacheLock) {
classpathCache.filterKeys { className.contains(it) }
}

for ((name, file) in cache) {
if (className.contains(name)) {
val clazz = classFromJarFile(file)
if (clazz != null) {
return clazz
}
} finally {
zipFile.close()
}
}

for (file in classpath) {
val clazz = classFromJarFile(file)
if (clazz != null) return clazz
}

return null
}

fun close() {
zipFile.close()
for(ref in additionalZipFiles) {
for (ref in additionalZipFiles) {
ref.get()?.close()
}
for ((_, zipFile) in zipFiles) {
zipFile.close()
}
}

override fun toString() = "PluginClassLoader@\"${pluginJar.name}\""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,17 @@ import com.github.serivesmejia.eocvsim.EOCVSim
import com.github.serivesmejia.eocvsim.config.ConfigLoader
import com.github.serivesmejia.eocvsim.gui.dialog.AppendDelegate
import com.github.serivesmejia.eocvsim.gui.dialog.PluginOutput
import com.github.serivesmejia.eocvsim.util.SysUtil
import com.github.serivesmejia.eocvsim.util.event.EventHandler
import com.github.serivesmejia.eocvsim.util.extension.fileHash
import com.github.serivesmejia.eocvsim.util.extension.hashString
import com.github.serivesmejia.eocvsim.util.extension.plus
import com.github.serivesmejia.eocvsim.util.loggerForThis
import com.moandjiezana.toml.Toml
import io.github.deltacv.common.util.ParsedVersion
import io.github.deltacv.eocvsim.plugin.EOCVSimPlugin
import io.github.deltacv.eocvsim.plugin.security.PluginSignature
import io.github.deltacv.eocvsim.plugin.security.PluginSignatureVerifier
import io.github.deltacv.eocvsim.sandbox.nio.SandboxFileSystem
import net.lingala.zip4j.ZipFile
import java.io.File
import java.security.MessageDigest

enum class PluginSource {
REPOSITORY,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,8 @@

package io.github.deltacv.eocvsim.plugin.security.superaccess

import com.github.serivesmejia.eocvsim.util.extension.plus
import com.github.serivesmejia.eocvsim.util.extension.fileHash
import com.github.serivesmejia.eocvsim.util.io.EOCVSimFolder
import com.github.serivesmejia.eocvsim.util.extension.plus
import com.github.serivesmejia.eocvsim.util.loggerForThis
import com.github.serivesmejia.eocvsim.util.serialization.PolymorphicAdapter
import com.google.gson.GsonBuilder
Expand All @@ -38,11 +37,16 @@ import io.github.deltacv.eocvsim.plugin.loader.PluginParser
import io.github.deltacv.eocvsim.plugin.security.Authority
import io.github.deltacv.eocvsim.plugin.security.AuthorityFetcher
import io.github.deltacv.eocvsim.plugin.security.MutablePluginSignature
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.core.LoggerContext
import org.apache.logging.log4j.core.appender.FileAppender
import org.java_websocket.client.WebSocketClient
import org.java_websocket.handshake.ServerHandshake
import java.io.File
import java.lang.Exception
import java.net.URI
import java.nio.file.Files
import java.nio.file.Paths
import java.util.concurrent.Executors
import java.util.zip.ZipFile
import javax.swing.SwingUtilities
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,33 +42,32 @@ typealias ResponseReceiver = (SuperAccessDaemon.SuperAccessResponse) -> Unit
typealias ResponseCondition = (SuperAccessDaemon.SuperAccessResponse) -> Boolean

private data class AccessCache(
val head: String,
val file: String,
val hasAccess: Boolean,
val timestamp: Long,
val footer: String,
val count: Int,
val countIntegrity: String
val timestamp: Long
) {
init {
// get calling class
val stackTrace = Thread.currentThread().stackTrace
val callingClass = stackTrace[2].className

fun hash() = (head + file + hasAccess.toString() + timestamp.toString() + footer + count.toString() + countIntegrity).hashString

if(callingClass != SuperAccessDaemonClient::class.java.name) {
throw IllegalAccessException("AccessCache should only be created from SuperAccessDaemonClient")
}
}
}

class SuperAccessDaemonClient(
val cacheTTLMillis: Long = 5000 // 5 seconds
val cacheTTLMillis: Long = 3_000
) {

val logger by loggerForThis()

private val startLock = ReentrantLock()
val startCondition = startLock.newCondition()

private var cacheCount = Random().nextInt(Int.MAX_VALUE)
private var integrity = ""

private val cacheLock = Any()
private val accessCache = mutableMapOf<String, MutableList<AccessCache>>()
private val accessCache = mutableMapOf<String, AccessCache>()

// create a new WebSocket server
private val server = WsServer(startLock, startCondition)
Expand Down Expand Up @@ -103,28 +102,9 @@ class SuperAccessDaemonClient(
fun checkAccess(file: File): Boolean {
synchronized(cacheLock) {
if (accessCache.containsKey(file.absolutePath)) {
val currentCaches = accessCache[file.absolutePath]!!
val currentCache = currentCaches.last()
val currentCache = accessCache[file.absolutePath]!!

if (System.currentTimeMillis() - currentCache.timestamp < cacheTTLMillis) {
val sortedCache = mutableListOf<AccessCache>()

for(caches in accessCache.values) {
sortedCache.addAll(caches)
}

sortedCache.sortBy { it.timestamp }

var expectedIntegrity = ""

for(cache in sortedCache) {
expectedIntegrity = (cache.hash() + expectedIntegrity).hashString
}

if (expectedIntegrity != integrity) {
throw SecurityException("Access cache has been tampered with")
}

return currentCache.hasAccess
}
}
Expand Down Expand Up @@ -159,18 +139,8 @@ class SuperAccessDaemonClient(
}

synchronized(cacheLock) {
val list = accessCache.getOrPut(file.absolutePath) { mutableListOf() }

val head = list.lastOrNull()?.hash() ?: ""

var countIntegrity = (cacheCount + (cacheCount - 1)).hashString

val newCache = AccessCache(head, file.absolutePath, hasAccess, System.currentTimeMillis(), integrity, cacheCount, countIntegrity).apply {
integrity = (hash() + integrity).hashString
cacheCount++
}

list.add(newCache)
val newCache = AccessCache(file.absolutePath, hasAccess, System.currentTimeMillis())
accessCache[file.absolutePath] = newCache
}

return hasAccess
Expand Down Expand Up @@ -251,7 +221,8 @@ class SuperAccessDaemonClient(
val exitCode = JavaProcess.exec(
SuperAccessDaemon::class.java,
JavaProcess.SLF4JIOReceiver(logger),
null, listOf(port.toString())
listOf("-Dlog4j.configurationFile=log4j2_nofile.xml"),
listOf(port.toString())
)

if(processRestarts == 6) {
Expand Down
6 changes: 6 additions & 0 deletions EOCV-Sim/src/main/resources/log4j2.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@
</File>
</Appenders>
<Loggers>
<!-- Exclude org.java_websocket TRACE from the file appender -->
<Logger name="io.github.deltacv.eocvsim.plugin.security.superaccess.SuperAccessDaemon" level="INFO" additivity="false">
<AppenderRef ref="stdout"/>
<AppenderRef ref="stderr"/>
</Logger>

<!-- Exclude org.java_websocket TRACE from the file appender -->
<Logger name="org.java_websocket" level="DEBUG" additivity="false">
<AppenderRef ref="stdout"/>
Expand Down
24 changes: 24 additions & 0 deletions EOCV-Sim/src/main/resources/log4j2_nofile.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>

<Configuration>
<Properties>
<Property name="pattern">[%d{HH:mm:ss}] [%t/%level]: [%c{1}] %msg%n</Property>
</Properties>
<Appenders>
<Console name="stdout" target="SYSTEM_OUT">
<PatternLayout pattern="${pattern}"/>
<ThresholdFilter level="ERROR" onMatch="DENY" onMismatch="ACCEPT"/>
</Console>

<Console name="stderr" target="SYSTEM_ERR">
<PatternLayout pattern="${pattern}"/>
<ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
</Console>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="stdout" level="INFO"/>
<AppenderRef ref="stderr" level="INFO"/>
</Root>
</Loggers>
</Configuration>
Loading

0 comments on commit cff0ea2

Please sign in to comment.