CVE-2024-29672: Insufficient Information (NVD-CWE-noinfo)
value: Directory Traversal vulnerability in zly2006 Reden before v.0.2.514 allows a remote attacker to execute arbitrary code via the DEBUG_RTC_REQUEST_SYNC_DATA in KeyCallbacks.kt.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-04-05T06:15Z
Repository: https://github.com/zly2006/reden-is-what-we-made
Language: Kotlin
Files changed: src/main/java/com/github/zly2006/reden/access/ClientData.kt, src/main/java/com/github/zly2006/reden/debugger/tree/StageIo.kt, src/main/java/com/github/zly2006/reden/malilib/KeyCallbacks.kt, src/main/java/com/github/zly2006/reden/network/RvcTrackpointsC2SRequest.kt, src/main/java/com/github/zly2006/reden/rvc/gui/SelectionCreateScreen.kt, src/main/java/com/github/zly2006/reden/rvc/tracking/RvcRepository.kt, src/main/java/com/github/zly2006/reden/rvc/tracking/TrackedStructure.kt, src/main/java/com/github/zly2006/reden/rvc/tracking/WorldInfo.kt
Commit date: 2024-01-09 16:05:17 +0800
Commit message: refactor: rvc (#81)

* added client/server for rvc structure
> sometimes for a very huge building, we have to request the server for all block data.
* fixed a security issue in the debug protocol

Diff: diff --git a/src/main/java/com/github/zly2006/reden/access/ClientData.kt b/src/main/java/com/github/zly2006/reden/access/ClientData.kt
index 686f43d..b847a70 100644
--- a/src/main/java/com/github/zly2006/reden/access/ClientData.kt
+++ b/src/main/java/com/github/zly2006/reden/access/ClientData.kt
@@ -2,10 +2,11 @@ package com.github.zly2006.reden.access
 
 import com.github.zly2006.reden.debugger.breakpoint.BreakPoint
 import com.github.zly2006.reden.debugger.breakpoint.BreakpointsManager
 import com.github.zly2006.reden.rvc.tracking.RvcRepository
 import net.minecraft.client.MinecraftClient
+import net.minecraft.network.NetworkSide
 import org.eclipse.jgit.api.Git
 import java.io.File
 
 class ClientData(
     @get:JvmName("mc") val mc: MinecraftClient
@@ -17,11 +18,11 @@ class ClientData(
 
     init {
         File("rvc").mkdirs()
         File("rvc").listFiles()!!.asSequence()
             .filter { it.isDirectory && it.resolve(".git").exists() }
-            .map { RvcRepository(Git.open(it)) }
+            .map { RvcRepository(Git.open(it), side = NetworkSide.CLIENTBOUND) }
             .forEach { rvcStructures[it.name] = it }
     }
 
     interface ClientDataAccess {
         @Suppress("INAPPLICABLE_JVM_NAME")
diff --git a/src/main/java/com/github/zly2006/reden/debugger/tree/StageIo.kt b/src/main/java/com/github/zly2006/reden/debugger/tree/StageIo.kt
index a85d601..5fd10da 100644
--- a/src/main/java/com/github/zly2006/reden/debugger/tree/StageIo.kt
+++ b/src/main/java/com/github/zly2006/reden/debugger/tree/StageIo.kt
@@ -34,12 +34,13 @@ object StageIo {
         constructors["end"] = Constructor { EmptyTickStage("end", it) }
         constructors["global_network"] = Constructor { EmptyTickStage("global_network", it) }
 
         constructors["world_root"] = Constructor { WorldRootStage(null, it!!, null) }
         constructors["network"] = Constructor { EmptyTickStage("network", it) }
-        constructors["update_block"] = Constructor { EmptyTickStage("update_block", it!!) }
-        constructors["commands_stage"] = Constructor { EmptyTickStage("commands_stage", it!!) }
+        constructors["update_block"] = Constructor { EmptyTickStage("update_block", it) }
+        constructors["commands_stage"] = Constructor { EmptyTickStage("commands_stage", it) }
+        constructors["set_block"] = Constructor { EmptyTickStage("set_block", it) }
 
         constructors["block_update"] = Constructor { BlockUpdateStage(it!!) }
         constructors["nc_update"] = Constructor { StageBlockNCUpdate(it!!, null) }
         constructors["cu_update"] = Constructor { StageBlockComparatorUpdate(it!!, null) }
         constructors["nc_update_1"] = Constructor { StageBlockNCUpdateSixWay.StageBlockNCUpdateOneWay(it!!, direction = null) }
diff --git a/src/main/java/com/github/zly2006/reden/malilib/KeyCallbacks.kt b/src/main/java/com/github/zly2006/reden/malilib/KeyCallbacks.kt
index 92845c6..6931832 100644
--- a/src/main/java/com/github/zly2006/reden/malilib/KeyCallbacks.kt
+++ b/src/main/java/com/github/zly2006/reden/malilib/KeyCallbacks.kt
@@ -179,16 +179,21 @@ fun configureKeyCallbacks(mc: MinecraftClient) {
         true
     }
     DEBUG_RVC_REQUEST_SYNC_DATA.callback {
         ClientPlayNetworking.send(RvcTrackpointsC2SRequest(1, selectedStructure!!))
         RvcDataS2CPacket.consumer = {
+            val rootFile = mc.runDirectory.resolve("DEBUG_RVC_REQUEST_SYNC_DATA").normalize()
             ZipInputStream(it.inputStream()).use { zip ->
                 var entry = zip.nextEntry
                 while (entry != null) {
                     val name = entry.name
                     print(name)
-                    val file = mc.runDirectory.resolve("DEBUG_RVC_REQUEST_SYNC_DATA").resolve(name)
+                    val file = rootFile.resolve(name).normalize()
+                    if (!file.startsWith(rootFile)) {
+                        Reden.LOGGER.error("Zip entry $name is outside of root directory")
+                        continue
+                    }
                     file.parentFile.mkdirs()
                     file.writeBytes(zip.readAllBytes())
                     entry = zip.nextEntry
                     print(file.absolutePath)
                 }
diff --git a/src/main/java/com/github/zly2006/reden/network/RvcTrackpointsC2SRequest.kt b/src/main/java/com/github/zly2006/reden/network/RvcTrackpointsC2SRequest.kt
index a7171cd..e39d6b9 100644
--- a/src/main/java/com/github/zly2006/reden/network/RvcTrackpointsC2SRequest.kt
+++ b/src/main/java/com/github/zly2006/reden/network/RvcTrackpointsC2SRequest.kt
@@ -1,14 +1,14 @@
 package com.github.zly2006.reden.network
 
 import com.github.zly2006.reden.Reden
-import com.github.zly2006.reden.rvc.tracking.RvcFileIO
-import com.github.zly2006.reden.rvc.tracking.TrackPredicate
-import com.github.zly2006.reden.rvc.tracking.TrackedStructure
+import com.github.zly2006.reden.rvc.tracking.*
+import com.github.zly2006.reden.utils.server
 import net.fabricmc.fabric.api.networking.v1.FabricPacket
 import net.fabricmc.fabric.api.networking.v1.PacketType
 import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking
+import net.minecraft.network.NetworkSide
 import net.minecraft.network.PacketByteBuf
 import java.io.ByteArrayOutputStream
 import java.util.zip.ZipEntry
 import java.util.zip.ZipOutputStream
 import kotlin.io.path.Path
@@ -24,13 +24,16 @@ class RvcTrackpointsC2SRequest(
         const val SIGNED_COMMIT = 2
 
         val id = Reden.identifier("rvc_trackpoints_c2s")
         val pType = PacketType.create(id) {
             val op = it.readVarInt()
-            val structure = TrackedStructure(it.readString())
+            val structure = TrackedStructure(it.readString(), NetworkSide.SERVERBOUND)
+            val origin = it.readBlockPos()
             val size = it.readVarInt()
             val trackpoints = ArrayList<TrackedStructure.TrackPoint>(size)
+            structure.world = server.overworld
+            structure.placementInfo = PlacementInfo(WorldInfo.ofLocal(server.overworld), origin)
             for (i in 0 until size) {
                 trackpoints.add(
                     TrackedStructure.TrackPoint(
                         structure.getRelativeCoordinate(it.readBlockPos()),
                         TrackPredicate.valueOf(it.readString()),
@@ -74,10 +77,11 @@ class RvcTrackpointsC2SRequest(
     }
 
     override fun write(buf: PacketByteBuf) {
         buf.writeVarInt(operation)
         buf.writeString(structure.name)
+        buf.writeBlockPos(structure.origin)
         buf.writeVarInt(structure.trackPoints.size)
         for (trackpoint in structure.trackPoints) {
             buf.writeBlockPos(trackpoint.pos)
             buf.writeString(trackpoint.predicate.name)
             buf.writeString(trackpoint.mode.name)
diff --git a/src/main/java/com/github/zly2006/reden/rvc/gui/SelectionCreateScreen.kt b/src/main/java/com/github/zly2006/reden/rvc/gui/SelectionCreateScreen.kt
index 15d33f7..fa91d89 100644
--- a/src/main/java/com/github/zly2006/reden/rvc/gui/SelectionCreateScreen.kt
+++ b/src/main/java/com/github/zly2006/reden/rvc/gui/SelectionCreateScreen.kt
@@ -8,18 +8,19 @@ import io.wispforest.owo.ui.base.BaseOwoScreen
 import io.wispforest.owo.ui.component.ButtonComponent
 import io.wispforest.owo.ui.component.Components
 import io.wispforest.owo.ui.container.Containers
 import io.wispforest.owo.ui.container.FlowLayout
 import io.wispforest.owo.ui.core.*
+import net.minecraft.network.NetworkSide
 import net.minecraft.text.Text
 
 private val DEFAULT_STATUS_TIP = Text.literal("Create a new RVC repository")
 
 class SelectionCreateScreen: BaseOwoScreen<FlowLayout>() {
     override fun createAdapter() = OwoUIAdapter.create(this, Containers::verticalFlow)!!
     private val confirmNameButton: ButtonComponent = Components.button(Text.literal("OK")) {
-        val repository = RvcRepository.create(nameField.text, client!!.getWorldInfo())
+        val repository = RvcRepository.create(nameField.text, client!!.getWorldInfo(), NetworkSide.CLIENTBOUND)
         client!!.data.rvcStructures[nameField.text] = repository
         selectedRepository = repository
         client!!.data.mc.setScreen(null)
     }.active(false)
     private val nameField = Components.textBox(Sizing.fixed(100)).apply {
diff --git a/src/main/java/com/github/zly2006/reden/rvc/tracking/RvcRepository.kt b/src/main/java/com/github/zly2006/reden/rvc/tracking/RvcRepository.kt
index 00a8876..f9247aa 100644
--- a/src/main/java/com/github/zly2006/reden/rvc/tracking/RvcRepository.kt
+++ b/src/main/java/com/github/zly2006/reden/rvc/tracking/RvcRepository.kt
@@ -7,10 +7,11 @@ import kotlinx.serialization.ExperimentalSerializationApi
 import kotlinx.serialization.encodeToString
 import kotlinx.serialization.json.Json
 import kotlinx.serialization.json.decodeFromStream
 import net.minecraft.client.MinecraftClient
 import net.minecraft.entity.player.PlayerEntity
+import net.minecraft.network.NetworkSide
 import net.minecraft.util.math.BlockPos
 import org.eclipse.jgit.api.CloneCommand
 import org.eclipse.jgit.api.Git
 import org.eclipse.jgit.api.InitCommand
 import org.jetbrains.annotations.Contract
@@ -18,11 +19,12 @@ import java.nio.file.Path
 import kotlin.io.path.*
 
 @OptIn(ExperimentalSerializationApi::class)
 class RvcRepository(
     private val git: Git,
-    val name: String = git.repository.workTree.name
+    val name: String = git.repository.workTree.name,
+    val side: NetworkSide
 ) {
     var headCache: TrackedStructure? = null
         private set
     var placementInfo: PlacementInfo? = null
         private set
@@ -71,11 +73,11 @@ class RvcRepository(
 
     fun head(): TrackedStructure {
         if (headCache == null) {
             val refs = git.branchList().call()
             headCache = if (refs.isEmpty()) {
-                TrackedStructure(name)
+                TrackedStructure(name, side)
             } else if (refs.any { it.name == RVC_BRANCH_REF }) {
                 checkoutBranch(RVC_BRANCH)
             } else {
                 checkout(refs.first().name)
             }
@@ -88,11 +90,11 @@ class RvcRepository(
             headCache!!.placementInfo = it
         }
         return headCache!!
     }
 
-    fun checkout(tag: String) = TrackedStructure(name).apply {
+    fun checkout(tag: String) = TrackedStructure(name, side).apply {
         git.checkout().setName(tag).setForced(true).call()
         this@RvcRepository.placementInfo?.let { this.placementInfo = it }
         RvcFileIO.load(git.repository.workTree.toPath(), this)
     }
 
@@ -125,33 +127,34 @@ class RvcRepository(
 
     companion object {
         val path = Path("rvc")
         const val RVC_BRANCH = "rvc"
         const val RVC_BRANCH_REF = "refs/heads/$RVC_BRANCH"
-        fun create(name: String, worldInfo: WorldInfo): RvcRepository {
+        fun create(name: String, worldInfo: WorldInfo, side: NetworkSide): RvcRepository {
             val git = Git.init()
                 .setDirectory(path / name)
                 .setInitialBranch(RVC_BRANCH)
                 .call()
-            return RvcRepository(git).apply {
+            return RvcRepository(git, side = side).apply {
                 placementInfo = PlacementInfo(worldInfo, BlockPos.ORIGIN)
                 createReadmeIfNotExists()
             }
         }
 
-        fun clone(url: String): RvcRepository {
+        fun clone(url: String, side: NetworkSide): RvcRepository {
             var name = url.split("/").last().removeSuffix(".git")
             var i = 1
             while ((path / name).exists()) {
                 name = "$name$i"
                 i++
             }
             return RvcRepository(
-                Git.cloneRepository()
+                git = Git.cloneRepository()
                     .setURI(url)
                     .setDirectory(path / name)
-                    .call()
+                    .call(),
+                side = side
             )
         }
     }
 }
 
diff --git a/src/main/java/com/github/zly2006/reden/rvc/tracking/TrackedStructure.kt b/src/main/java/com/github/zly2006/reden/rvc/tracking/TrackedStructure.kt
index fec1546..8565fa2 100644
--- a/src/main/java/com/github/zly2006/reden/rvc/tracking/TrackedStructure.kt
+++ b/src/main/java/com/github/zly2006/reden/rvc/tracking/TrackedStructure.kt
@@ -9,10 +9,11 @@ import net.minecraft.block.Block
 import net.minecraft.block.Blocks
 import net.minecraft.entity.Entity
 import net.minecraft.entity.player.PlayerEntity
 import net.minecraft.fluid.Fluid
 import net.minecraft.nbt.NbtCompound
+import net.minecraft.network.NetworkSide
 import net.minecraft.registry.Registries
 import net.minecraft.registry.Registry
 import net.minecraft.server.world.ServerWorld
 import net.minecraft.util.math.*
 import net.minecraft.world.World
@@ -22,11 +23,12 @@ import java.util.*
 
 /**
  * todo: see #81
  */
 class TrackedStructure(
-    name: String
+    name: String,
+    var side: NetworkSide
 ) : ReadWriteStructure(name), IPlacement, PositionIterable {
     override var xSize: Int = 0
     override var ySize: Int = 0
     override var zSize: Int = 0
     override var enabled: Boolean = true
@@ -62,10 +64,11 @@ class TrackedStructure(
             return "${pos.x},${pos.y},${pos.z},$type,$data,${Registries.BLOCK.getId(block)}"
         }
     }
 
     fun debugRender() {
+        if (side == NetworkSide.SERVERBOUND) return
         BlockOutline.blocks.clear()
         BlockBorder.tags.clear()
         cachedPositions.forEach {
             if (!world.isAir(it.key))
                 BlockOutline.blocks[it.key] = world.getBlockState(it.key)
diff --git a/src/main/java/com/github/zly2006/reden/rvc/tracking/WorldInfo.kt b/src/main/java/com/github/zly2006/reden/rvc/tracking/WorldInfo.kt
index 2eefa5b..59bf42a 100644
--- a/src/main/java/com/github/zly2006/reden/rvc/tracking/WorldInfo.kt
+++ b/src/main/java/com/github/zly2006/reden/rvc/tracking/WorldInfo.kt
@@ -5,10 +5,11 @@ import com.github.zly2006.reden.access.WorldData.Companion.data
 import com.github.zly2006.reden.debugger.breakpoint.BlockPosSerializer
 import com.github.zly2006.reden.debugger.breakpoint.IdentifierSerializer
 import com.github.zly2006.reden.utils.isClient
 import com.github.zly2006.reden.utils.server
 import kotlinx.serialization.Serializable
+import kotlinx.serialization.Transient
 import net.minecraft.client.MinecraftClient
 import net.minecraft.registry.RegistryKey
 import net.minecraft.registry.RegistryKeys
 import net.minecraft.server.world.ServerWorld
 import net.minecraft.util.Identifier
@@ -50,11 +51,11 @@ data class WorldInfo(
                 worldDimension = mc.world!!.dimensionKey.value,
                 worldKey = mc.world!!.registryKey.value,
             )
         }
 
-        private fun ofLocal(world: ServerWorld): WorldInfo {
+        fun ofLocal(world: ServerWorld): WorldInfo {
             return WorldInfo(
                 isRemoteServer = false,
                 localSaveName = world.server.session.directory.path.name,
                 worldId = world.data.worldId,
                 worldDimension = world.dimensionKey.value,
@@ -87,21 +88,26 @@ data class WorldInfo(
         } else {
             (localSaveName.hashCode() * 31 + worldKey.hashCode())
         }
     }
 
+    @Transient
+    var world: World? = null
+
     fun getWorld(): World? {
+        if (world != null) return world
         val registryKey = RegistryKey.of(RegistryKeys.WORLD, worldKey)
-        if (isClient) {
+        world = if (isClient) {
             val server = MinecraftClient.getInstance().server
             if (server != null) {
-                return server.getWorld(registryKey)
-            }
-            return MinecraftClient.getInstance().world
+                server.getWorld(registryKey)
+            }else
+                MinecraftClient.getInstance().world
         } else {
-            return server.getWorld(registryKey)
+            server.getWorld(registryKey)
         }
+        return world
     }
 }
 
 private infix fun <T> T?.eqNotNull(other: T?): Boolean {
     return this != null && other != null && this == other

Vulnerable code:             .map { RvcRepository(Git.open(it)) }
        constructors["update_block"] = Constructor { EmptyTickStage("update_block", it!!) }
        constructors["commands_stage"] = Constructor { EmptyTickStage("commands_stage", it!!) }
                    val file = mc.runDirectory.resolve("DEBUG_RVC_REQUEST_SYNC_DATA").resolve(name)
import com.github.zly2006.reden.rvc.tracking.RvcFileIO
import com.github.zly2006.reden.rvc.tracking.TrackPredicate
import com.github.zly2006.reden.rvc.tracking.TrackedStructure
            val structure = TrackedStructure(it.readString())
        val repository = RvcRepository.create(nameField.text, client!!.getWorldInfo())
    val name: String = git.repository.workTree.name
                TrackedStructure(name)
    fun checkout(tag: String) = TrackedStructure(name).apply {
        fun create(name: String, worldInfo: WorldInfo): RvcRepository {
            return RvcRepository(git).apply {
        fun clone(url: String): RvcRepository {
                Git.cloneRepository()
                    .call()
    name: String
        private fun ofLocal(world: ServerWorld): WorldInfo {
        if (isClient) {
                return server.getWorld(registryKey)
            }
            return MinecraftClient.getInstance().world
            return server.getWorld(registryKey)
Fixed code: import net.minecraft.network.NetworkSide
            .map { RvcRepository(Git.open(it), side = NetworkSide.CLIENTBOUND) }
        constructors["update_block"] = Constructor { EmptyTickStage("update_block", it) }
        constructors["commands_stage"] = Constructor { EmptyTickStage("commands_stage", it) }
        constructors["set_block"] = Constructor { EmptyTickStage("set_block", it) }
            val rootFile = mc.runDirectory.resolve("DEBUG_RVC_REQUEST_SYNC_DATA").normalize()
                    val file = rootFile.resolve(name).normalize()
                    if (!file.startsWith(rootFile)) {
                        Reden.LOGGER.error("Zip entry $name is outside of root directory")
                        continue
                    }
import com.github.zly2006.reden.rvc.tracking.*
import com.github.zly2006.reden.utils.server
import net.minecraft.network.NetworkSide
            val structure = TrackedStructure(it.readString(), NetworkSide.SERVERBOUND)
            val origin = it.readBlockPos()
            structure.world = server.overworld
            structure.placementInfo = PlacementInfo(WorldInfo.ofLocal(server.overworld), origin)
        buf.writeBlockPos(structure.origin)
import net.minecraft.network.NetworkSide
        val repository = RvcRepository.create(nameField.text, client!!.getWorldInfo(), NetworkSide.CLIENTBOUND)
import net.minecraft.network.NetworkSide
    val name: String = git.repository.workTree.name,
    val side: NetworkSide
                TrackedStructure(name, side)
    fun checkout(tag: String) = TrackedStructure(name, side).apply {
        fun create(name: String, worldInfo: WorldInfo, side: NetworkSide): RvcRepository {
            return RvcRepository(git, side = side).apply {
        fun clone(url: String, side: NetworkSide): RvcRepository {
                git = Git.cloneRepository()
                    .call(),
                side = side
import net.minecraft.network.NetworkSide
    name: String,
    var side: NetworkSide
        if (side == NetworkSide.SERVERBOUND) return
import kotlinx.serialization.Transient
        fun ofLocal(world: ServerWorld): WorldInfo {
    @Transient
    var world: World? = null

        if (world != null) return world
        world = if (isClient) {
                server.getWorld(registryKey)
            }else
                MinecraftClient.getInstance().world
            server.getWorld(registryKey)
        return world