diff --git a/ADDITIONS.md b/ADDITIONS.md index b6ac2a13..b0e73315 100644 --- a/ADDITIONS.md +++ b/ADDITIONS.md @@ -6,7 +6,7 @@ ### Worldgen * Where applicable, Perlin noise now can have custom seed specified * Change above allows to explicitly specify universe seed (as `celestial.config:systemTypePerlin:seed`) - * Perlin noise now can be of arbitrary scale (defaults to `512`, specified with `scale` key, integer type, >=16) + * Perlin noise now can be of arbitrary scale (defaults to `512`, specified with `scale` key, integer type, 2048>=x>=16) #### Terrain * `mix` terrain selector got `mixSeedBias`, `aSeedBias` and `bSeedBias` fields, whose deviate respective selectors seeds (default to `0`) diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 62220234..00000000 --- a/SECURITY.md +++ /dev/null @@ -1,68 +0,0 @@ - -## Vulnerabilities in original engine - -This document points out vulnerabilities in original game engine and describes vectors of attack -to exploit them. - -This document is for educational purposes only to raise awareness (about learning how dangerous it is to run public Starbound -server on original engine), and pursues no goal of harming users of original engine. - -Experienced blackhats already could take sources and dig these invulnerabilities themselves, -since most of them are not buried anywhere deep in code. - ------------ - -### EntityDestroyPacket vulnerability - -When client sends EntityCreatePacket to WorldServer, it checks whenever received `entityId` is within -allowed range (range of IDs allocated specifically for that client). Same happens on EntityUpdateSetPacket. - -However, someone forgot to put the same check when receiving EntityDestroyPacket, hence -any client can remove ANY other entity inside world, including other PlayerEntitys'. - -On side note, original client makes sure it sends EntityDestroyPacket only for entities it owns. - -This attack require modified game client. - ------------ - -### Zip bomb in PacketSocket - -When packets are received on network socket, they are checked for not exceeding 16 MiB, -by reading packet length header. However, when receiving compressed packets, -only compressed size is checked against 16 MiB limit, and -they are uncompressed in one shot, without limiting uncompressed size. - -This vulnerability allows to make server quickly run out of memory by forging zip-bomb packet. - -This attack require modified game client. - ------------ - -### Client's ShipWorld size - -When joining server, client sends contents of `.shipworld` in form of chunk map -(Map with bytearray keys and bytearray values, which represent data stored inside BTreeDB). - -Server instances WorldServer with provided world chunks. The vulnerability lies within world's size. - -Original engine world's chunk map is always stored as tight 2D array of chunk (sector) pointers, -and pointer array is always fully preallocated when world is instanced. - -So client can forge custom shipworld, with 2^31 x 2^31 dimensions, which will instantly cause -server to consume at least 128 GiB of RAM when client connects. - -This attack does not require modified game client. - ------------ - -## Exploits in original engine - -These kind of bugs don't directly compromise security of server, but may degrade its performance. - ------------ - -### Client context window size - -Window size as reported by client is not checked for insane values, allowing to greatly slowdown the server if client -is residing in large world, and reporting to be tracking entire world. diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/PerlinNoiseParameters.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/PerlinNoiseParameters.kt index a3d007d5..b5343b63 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/PerlinNoiseParameters.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/PerlinNoiseParameters.kt @@ -19,7 +19,8 @@ data class PerlinNoiseParameters( val bias: Double = 0.0, ) { init { - require(scale >= 16) { "Too little perlin noise scale" } + require(scale >= 16) { "Too little perlin noise scale: $scale" } + require(scale <= 2048) { "Absurd noise scale: $scale" } } enum class Type(override val jsonName: String) : IStringSerializable { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerUniverse.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerUniverse.kt index b4fd2c63..de425e0d 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerUniverse.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerUniverse.kt @@ -188,7 +188,7 @@ class ServerUniverse private constructor(marker: Nothing?) : Universe(), Closeab baseInformation = Starbound.gson.fromJson(stream0)!! generationInformation = Starbound.gson.fromJson(stream1)!! - if (!generationInformation.systemTypePerlin.isInitialized) + if (!generationInformation.systemTypePerlin.hasSeedSpecified) generationInformation.systemTypePerlin.init(staticRandom64("SystemTypePerlin")) celestialNames = Starbound.gson.fromJson(Starbound.jsonReader("/celestial/names.config"))!! diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorldTracker.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorldTracker.kt index 9510fca9..4156ac39 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorldTracker.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorldTracker.kt @@ -229,7 +229,7 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p data, entity.entityID )) - } else { + } else if (entity.networkGroup.upstream.hasChangedSince(entityVersions.get(id))) { val (data, version) = entity.networkGroup.write(remoteVersion = entityVersions.get(id), isLegacy = client.isLegacy) entityVersions.put(id, version) send(EntityUpdateSetPacket(entity.connectionID, Int2ObjectMaps.singleton(entity.entityID, data))) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/random/AbstractPerlinNoise.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/random/AbstractPerlinNoise.kt index 8d6a61a6..225e71a9 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/util/random/AbstractPerlinNoise.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/util/random/AbstractPerlinNoise.kt @@ -27,9 +27,12 @@ abstract class AbstractPerlinNoise(val parameters: PerlinNoiseParameters) { abstract operator fun get(x: Double, y: Double): Double abstract operator fun get(x: Double, y: Double, z: Double): Double - var isInitialized = false + var hasSeedSpecified = false private set + private var isInitialized = false + private val initLock = Any() + var seed: Long = 0L private set @@ -48,13 +51,7 @@ abstract class AbstractPerlinNoise(val parameters: PerlinNoiseParameters) { } } - fun init(seed: Long) { - if (parameters.type == PerlinNoiseParameters.Type.UNITIALIZED) - return - - isInitialized = true - this.seed = seed - + private fun doInit(seed: Long) { p.fill(0) g1.fill(0.0) g2.fill(0.0) @@ -116,6 +113,30 @@ abstract class AbstractPerlinNoise(val parameters: PerlinNoiseParameters) { } } + protected fun checkInit() { + synchronized(initLock) { + check(hasSeedSpecified) { "Tried to use perlin noise without seed specified" } + + if (!isInitialized) { + doInit(seed) + isInitialized = true + } + } + } + + fun init(seed: Long, now: Boolean = false) { + if (parameters.type == PerlinNoiseParameters.Type.UNITIALIZED) + return + + this.hasSeedSpecified = true + this.isInitialized = false + this.seed = seed + + if (now) { + checkInit() + } + } + protected fun curve(value: Double): Double { return value * value * (3.0 - 2.0 * value) } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/random/BillowNoise.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/random/BillowNoise.kt index 28425226..a683f470 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/util/random/BillowNoise.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/util/random/BillowNoise.kt @@ -9,6 +9,7 @@ class BillowNoise(parameters: PerlinNoiseParameters) : AbstractPerlinNoise(param } override fun get(x: Double): Double { + checkInit() var sum = 0.0 var p = x * parameters.frequency var scale = 1.0 @@ -23,6 +24,7 @@ class BillowNoise(parameters: PerlinNoiseParameters) : AbstractPerlinNoise(param } override fun get(x: Double, y: Double): Double { + checkInit() var sum = 0.0 var px = x * parameters.frequency var py = y * parameters.frequency @@ -39,6 +41,7 @@ class BillowNoise(parameters: PerlinNoiseParameters) : AbstractPerlinNoise(param } override fun get(x: Double, y: Double, z: Double): Double { + checkInit() var sum = 0.0 var px = x * parameters.frequency var py = y * parameters.frequency diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/random/PerlinNoise.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/random/PerlinNoise.kt index 7a028e7f..2129b666 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/util/random/PerlinNoise.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/util/random/PerlinNoise.kt @@ -8,6 +8,7 @@ class PerlinNoise(parameters: PerlinNoiseParameters) : AbstractPerlinNoise(param } override fun get(x: Double): Double { + checkInit() var sum = 0.0 var p = x * parameters.frequency var scale = 1.0 @@ -22,6 +23,7 @@ class PerlinNoise(parameters: PerlinNoiseParameters) : AbstractPerlinNoise(param } override fun get(x: Double, y: Double): Double { + checkInit() var sum = 0.0 var px = x * parameters.frequency var py = y * parameters.frequency @@ -38,6 +40,7 @@ class PerlinNoise(parameters: PerlinNoiseParameters) : AbstractPerlinNoise(param } override fun get(x: Double, y: Double, z: Double): Double { + checkInit() var sum = 0.0 var px = x * parameters.frequency var py = y * parameters.frequency diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/random/RidgedNoise.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/random/RidgedNoise.kt index 5bb3d33d..eb8c5517 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/util/random/RidgedNoise.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/util/random/RidgedNoise.kt @@ -9,6 +9,7 @@ class RidgedNoise(parameters: PerlinNoiseParameters) : AbstractPerlinNoise(param } override fun get(x: Double): Double { + checkInit() var sum = 0.0 var p = x * parameters.frequency var scale = 1.0 @@ -31,6 +32,7 @@ class RidgedNoise(parameters: PerlinNoiseParameters) : AbstractPerlinNoise(param } override fun get(x: Double, y: Double): Double { + checkInit() var sum = 0.0 var px = x * parameters.frequency var py = y * parameters.frequency @@ -55,6 +57,7 @@ class RidgedNoise(parameters: PerlinNoiseParameters) : AbstractPerlinNoise(param } override fun get(x: Double, y: Double, z: Double): Double { + checkInit() var sum = 0.0 var px = x * parameters.frequency var py = y * parameters.frequency diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/SpatialIndex.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/SpatialIndex.kt index 41b4c468..f5f09267 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/SpatialIndex.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/SpatialIndex.kt @@ -21,7 +21,7 @@ import kotlin.concurrent.withLock // while also setting up ground for better spatial index strategies, if they // have to be done in the future. class SpatialIndex(val geometry: WorldGeometry) { - private val lock = ReentrantLock() + private val lock = Any() private val map = Long2ObjectOpenHashMap() private val factory = Long2ObjectFunction { Sector(it) } private val counter = AtomicInteger() @@ -97,7 +97,7 @@ class SpatialIndex(val geometry: WorldGeometry) { inner class Fixture { init { - lock.withLock { + synchronized(lock) { fixtures.add(this) } } @@ -137,7 +137,7 @@ class SpatialIndex(val geometry: WorldGeometry) { val newSectors0 = newSectors.toLongArray() newSectors0.sort() - lock.withLock { + synchronized(lock) { if (isRemoved) return val addSectors = ArrayList(0) @@ -217,25 +217,30 @@ class SpatialIndex(val geometry: WorldGeometry) { } } + internal fun clear0() { + sectors.forEach { deref(it) } + sectors.clear() + } + fun clear() { - lock.withLock { - sectors.forEach { deref(it) } - sectors.clear() + synchronized(lock) { + clear0() } } fun remove() { - lock.withLock { + synchronized(lock) { isRemoved = true - clear() + sectors.forEach { deref(it) } + sectors.clear() fixtures.remove(this) } } } fun remove() { - lock.withLock { - fixtures.forEach { it.clear() } + synchronized(lock) { + fixtures.forEach { it.clear0() } sectors.keys.forEach { it.remove(this) } sectors.clear() } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/terrain/IslandSurfaceTerrainSelector.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/terrain/IslandSurfaceTerrainSelector.kt index 2f0ef107..905230a1 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/terrain/IslandSurfaceTerrainSelector.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/terrain/IslandSurfaceTerrainSelector.kt @@ -9,7 +9,6 @@ import ru.dbotthepony.kstarbound.json.builder.JsonFactory import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise import kotlin.math.PI import kotlin.math.absoluteValue -import kotlin.math.cos import kotlin.math.sin class IslandSurfaceTerrainSelector(data: Data, parameters: TerrainSelectorParameters) : AbstractTerrainSelector(data, parameters) { @@ -40,7 +39,7 @@ class IslandSurfaceTerrainSelector(data: Data, parameters: TerrainSelectorParame val islandHeight by lazy { val perlin = AbstractPerlinNoise.of(this.data.islandHeight) - if (!perlin.isInitialized) { + if (!perlin.hasSeedSpecified) { perlin.init(islandHeightSeed) } @@ -50,7 +49,7 @@ class IslandSurfaceTerrainSelector(data: Data, parameters: TerrainSelectorParame val islandDepth by lazy { val perlin = AbstractPerlinNoise.of(this.data.islandDepth) - if (!perlin.isInitialized) { + if (!perlin.hasSeedSpecified) { perlin.init(islandDepthSeed) } @@ -60,7 +59,7 @@ class IslandSurfaceTerrainSelector(data: Data, parameters: TerrainSelectorParame val islandDecision by lazy { val perlin = AbstractPerlinNoise.of(this.data.islandDecision) - if (!perlin.isInitialized) { + if (!perlin.hasSeedSpecified) { perlin.init(islandDecisionSeed) }