CVE-2024-29672 (published 2024-04-05T06:15Z)

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.


Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/zly2006/reden-is-what-we-made
commit_details: repo_url: https://github.com/zly2006/reden-is-what-we-made
author: Liyan Zhao
author_date: 2024-01-09 16:05:17+08:00
msg: 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
repository: repo_url: https://github.com/zly2006/reden-is-what-we-made
repo_name: zly2006/reden-is-what-we-made
description: Redstone EDEN: Undo & Redo | Ctrl+Z | Mineacrft Debugger | Redstone Version Control | Any amazing features you can image!
repo_language: Kotlin
owner: zly2006
file_changes: filename: ClientData.kt
change_type: ModificationType.MODIFY
diff: @@ -4,6 +4,7 @@ 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
 
@@ -19,7 +20,7 @@ class ClientData(
         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 }
     }
 

programming_language: Scala
filename: StageIo.kt
change_type: ModificationType.MODIFY
diff: @@ -36,8 +36,9 @@ object StageIo {
 
         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) }

programming_language: Scala
filename: KeyCallbacks.kt
change_type: ModificationType.MODIFY
diff: @@ -181,12 +181,17 @@ fun configureKeyCallbacks(mc: MinecraftClient) {
     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

programming_language: Scala
filename: RvcTrackpointsC2SRequest.kt
change_type: ModificationType.MODIFY
diff: @@ -1,12 +1,12 @@
 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
@@ -26,9 +26,12 @@ class RvcTrackpointsC2SRequest(
         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(
@@ -76,6 +79,7 @@ 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)

programming_language: Scala
filename: SelectionCreateScreen.kt
change_type: ModificationType.MODIFY
diff: @@ -10,6 +10,7 @@ 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")
@@ -17,7 +18,7 @@ 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)

programming_language: Scala
filename: RvcRepository.kt
change_type: ModificationType.MODIFY
diff: @@ -9,6 +9,7 @@ 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
@@ -20,7 +21,8 @@ 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
@@ -73,7 +75,7 @@ class RvcRepository(
         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 {
@@ -90,7 +92,7 @@ class RvcRepository(
         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)
@@ -127,18 +129,18 @@ class RvcRepository(
         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()) {
@@ -146,10 +148,11 @@ class RvcRepository(
                 i++
             }
             return RvcRepository(
-                Git.cloneRepository()
+                git = Git.cloneRepository()
                     .setURI(url)
                     .setDirectory(path / name)
-                    .call()
+                    .call(),
+                side = side
             )
         }
     }

programming_language: Scala
filename: TrackedStructure.kt
change_type: ModificationType.MODIFY
diff: @@ -11,6 +11,7 @@ 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
@@ -24,7 +25,8 @@ 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
@@ -64,6 +66,7 @@ class TrackedStructure(
     }
 
     fun debugRender() {
+        if (side == NetworkSide.SERVERBOUND) return
         BlockOutline.blocks.clear()
         BlockBorder.tags.clear()
         cachedPositions.forEach {

programming_language: Scala
filename: WorldInfo.kt
change_type: ModificationType.MODIFY
diff: @@ -7,6 +7,7 @@ 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
@@ -52,7 +53,7 @@ data class WorldInfo(
             )
         }
 
-        private fun ofLocal(world: ServerWorld): WorldInfo {
+        fun ofLocal(world: ServerWorld): WorldInfo {
             return WorldInfo(
                 isRemoteServer = false,
                 localSaveName = world.server.session.directory.path.name,
@@ -89,17 +90,22 @@ data class WorldInfo(
         }
     }
 
+    @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
     }
 }
 

programming_language: Scala