Delay perlin noise initialization until actually used
This commit is contained in:
parent
fd1a63a22c
commit
ff746f43ae
@ -6,7 +6,7 @@
|
|||||||
### Worldgen
|
### Worldgen
|
||||||
* Where applicable, Perlin noise now can have custom seed specified
|
* Where applicable, Perlin noise now can have custom seed specified
|
||||||
* Change above allows to explicitly specify universe seed (as `celestial.config:systemTypePerlin:seed`)
|
* 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
|
#### Terrain
|
||||||
* `mix` terrain selector got `mixSeedBias`, `aSeedBias` and `bSeedBias` fields, whose deviate respective selectors seeds (default to `0`)
|
* `mix` terrain selector got `mixSeedBias`, `aSeedBias` and `bSeedBias` fields, whose deviate respective selectors seeds (default to `0`)
|
||||||
|
68
SECURITY.md
68
SECURITY.md
@ -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.
|
|
@ -19,7 +19,8 @@ data class PerlinNoiseParameters(
|
|||||||
val bias: Double = 0.0,
|
val bias: Double = 0.0,
|
||||||
) {
|
) {
|
||||||
init {
|
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 {
|
enum class Type(override val jsonName: String) : IStringSerializable {
|
||||||
|
@ -188,7 +188,7 @@ class ServerUniverse private constructor(marker: Nothing?) : Universe(), Closeab
|
|||||||
baseInformation = Starbound.gson.fromJson(stream0)!!
|
baseInformation = Starbound.gson.fromJson(stream0)!!
|
||||||
generationInformation = Starbound.gson.fromJson(stream1)!!
|
generationInformation = Starbound.gson.fromJson(stream1)!!
|
||||||
|
|
||||||
if (!generationInformation.systemTypePerlin.isInitialized)
|
if (!generationInformation.systemTypePerlin.hasSeedSpecified)
|
||||||
generationInformation.systemTypePerlin.init(staticRandom64("SystemTypePerlin"))
|
generationInformation.systemTypePerlin.init(staticRandom64("SystemTypePerlin"))
|
||||||
|
|
||||||
celestialNames = Starbound.gson.fromJson(Starbound.jsonReader("/celestial/names.config"))!!
|
celestialNames = Starbound.gson.fromJson(Starbound.jsonReader("/celestial/names.config"))!!
|
||||||
|
@ -229,7 +229,7 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p
|
|||||||
data,
|
data,
|
||||||
entity.entityID
|
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)
|
val (data, version) = entity.networkGroup.write(remoteVersion = entityVersions.get(id), isLegacy = client.isLegacy)
|
||||||
entityVersions.put(id, version)
|
entityVersions.put(id, version)
|
||||||
send(EntityUpdateSetPacket(entity.connectionID, Int2ObjectMaps.singleton(entity.entityID, data)))
|
send(EntityUpdateSetPacket(entity.connectionID, Int2ObjectMaps.singleton(entity.entityID, data)))
|
||||||
|
@ -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): Double
|
||||||
abstract operator fun get(x: Double, y: Double, z: Double): Double
|
abstract operator fun get(x: Double, y: Double, z: Double): Double
|
||||||
|
|
||||||
var isInitialized = false
|
var hasSeedSpecified = false
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
private var isInitialized = false
|
||||||
|
private val initLock = Any()
|
||||||
|
|
||||||
var seed: Long = 0L
|
var seed: Long = 0L
|
||||||
private set
|
private set
|
||||||
|
|
||||||
@ -48,13 +51,7 @@ abstract class AbstractPerlinNoise(val parameters: PerlinNoiseParameters) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun init(seed: Long) {
|
private fun doInit(seed: Long) {
|
||||||
if (parameters.type == PerlinNoiseParameters.Type.UNITIALIZED)
|
|
||||||
return
|
|
||||||
|
|
||||||
isInitialized = true
|
|
||||||
this.seed = seed
|
|
||||||
|
|
||||||
p.fill(0)
|
p.fill(0)
|
||||||
g1.fill(0.0)
|
g1.fill(0.0)
|
||||||
g2.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 {
|
protected fun curve(value: Double): Double {
|
||||||
return value * value * (3.0 - 2.0 * value)
|
return value * value * (3.0 - 2.0 * value)
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ class BillowNoise(parameters: PerlinNoiseParameters) : AbstractPerlinNoise(param
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun get(x: Double): Double {
|
override fun get(x: Double): Double {
|
||||||
|
checkInit()
|
||||||
var sum = 0.0
|
var sum = 0.0
|
||||||
var p = x * parameters.frequency
|
var p = x * parameters.frequency
|
||||||
var scale = 1.0
|
var scale = 1.0
|
||||||
@ -23,6 +24,7 @@ class BillowNoise(parameters: PerlinNoiseParameters) : AbstractPerlinNoise(param
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun get(x: Double, y: Double): Double {
|
override fun get(x: Double, y: Double): Double {
|
||||||
|
checkInit()
|
||||||
var sum = 0.0
|
var sum = 0.0
|
||||||
var px = x * parameters.frequency
|
var px = x * parameters.frequency
|
||||||
var py = y * 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 {
|
override fun get(x: Double, y: Double, z: Double): Double {
|
||||||
|
checkInit()
|
||||||
var sum = 0.0
|
var sum = 0.0
|
||||||
var px = x * parameters.frequency
|
var px = x * parameters.frequency
|
||||||
var py = y * parameters.frequency
|
var py = y * parameters.frequency
|
||||||
|
@ -8,6 +8,7 @@ class PerlinNoise(parameters: PerlinNoiseParameters) : AbstractPerlinNoise(param
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun get(x: Double): Double {
|
override fun get(x: Double): Double {
|
||||||
|
checkInit()
|
||||||
var sum = 0.0
|
var sum = 0.0
|
||||||
var p = x * parameters.frequency
|
var p = x * parameters.frequency
|
||||||
var scale = 1.0
|
var scale = 1.0
|
||||||
@ -22,6 +23,7 @@ class PerlinNoise(parameters: PerlinNoiseParameters) : AbstractPerlinNoise(param
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun get(x: Double, y: Double): Double {
|
override fun get(x: Double, y: Double): Double {
|
||||||
|
checkInit()
|
||||||
var sum = 0.0
|
var sum = 0.0
|
||||||
var px = x * parameters.frequency
|
var px = x * parameters.frequency
|
||||||
var py = y * 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 {
|
override fun get(x: Double, y: Double, z: Double): Double {
|
||||||
|
checkInit()
|
||||||
var sum = 0.0
|
var sum = 0.0
|
||||||
var px = x * parameters.frequency
|
var px = x * parameters.frequency
|
||||||
var py = y * parameters.frequency
|
var py = y * parameters.frequency
|
||||||
|
@ -9,6 +9,7 @@ class RidgedNoise(parameters: PerlinNoiseParameters) : AbstractPerlinNoise(param
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun get(x: Double): Double {
|
override fun get(x: Double): Double {
|
||||||
|
checkInit()
|
||||||
var sum = 0.0
|
var sum = 0.0
|
||||||
var p = x * parameters.frequency
|
var p = x * parameters.frequency
|
||||||
var scale = 1.0
|
var scale = 1.0
|
||||||
@ -31,6 +32,7 @@ class RidgedNoise(parameters: PerlinNoiseParameters) : AbstractPerlinNoise(param
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun get(x: Double, y: Double): Double {
|
override fun get(x: Double, y: Double): Double {
|
||||||
|
checkInit()
|
||||||
var sum = 0.0
|
var sum = 0.0
|
||||||
var px = x * parameters.frequency
|
var px = x * parameters.frequency
|
||||||
var py = y * 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 {
|
override fun get(x: Double, y: Double, z: Double): Double {
|
||||||
|
checkInit()
|
||||||
var sum = 0.0
|
var sum = 0.0
|
||||||
var px = x * parameters.frequency
|
var px = x * parameters.frequency
|
||||||
var py = y * parameters.frequency
|
var py = y * parameters.frequency
|
||||||
|
@ -21,7 +21,7 @@ import kotlin.concurrent.withLock
|
|||||||
// while also setting up ground for better spatial index strategies, if they
|
// while also setting up ground for better spatial index strategies, if they
|
||||||
// have to be done in the future.
|
// have to be done in the future.
|
||||||
class SpatialIndex<T>(val geometry: WorldGeometry) {
|
class SpatialIndex<T>(val geometry: WorldGeometry) {
|
||||||
private val lock = ReentrantLock()
|
private val lock = Any()
|
||||||
private val map = Long2ObjectOpenHashMap<Sector>()
|
private val map = Long2ObjectOpenHashMap<Sector>()
|
||||||
private val factory = Long2ObjectFunction { Sector(it) }
|
private val factory = Long2ObjectFunction { Sector(it) }
|
||||||
private val counter = AtomicInteger()
|
private val counter = AtomicInteger()
|
||||||
@ -97,7 +97,7 @@ class SpatialIndex<T>(val geometry: WorldGeometry) {
|
|||||||
|
|
||||||
inner class Fixture {
|
inner class Fixture {
|
||||||
init {
|
init {
|
||||||
lock.withLock {
|
synchronized(lock) {
|
||||||
fixtures.add(this)
|
fixtures.add(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -137,7 +137,7 @@ class SpatialIndex<T>(val geometry: WorldGeometry) {
|
|||||||
val newSectors0 = newSectors.toLongArray()
|
val newSectors0 = newSectors.toLongArray()
|
||||||
newSectors0.sort()
|
newSectors0.sort()
|
||||||
|
|
||||||
lock.withLock {
|
synchronized(lock) {
|
||||||
if (isRemoved) return
|
if (isRemoved) return
|
||||||
|
|
||||||
val addSectors = ArrayList<Sector>(0)
|
val addSectors = ArrayList<Sector>(0)
|
||||||
@ -217,25 +217,30 @@ class SpatialIndex<T>(val geometry: WorldGeometry) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun clear0() {
|
||||||
|
sectors.forEach { deref(it) }
|
||||||
|
sectors.clear()
|
||||||
|
}
|
||||||
|
|
||||||
fun clear() {
|
fun clear() {
|
||||||
lock.withLock {
|
synchronized(lock) {
|
||||||
sectors.forEach { deref(it) }
|
clear0()
|
||||||
sectors.clear()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun remove() {
|
fun remove() {
|
||||||
lock.withLock {
|
synchronized(lock) {
|
||||||
isRemoved = true
|
isRemoved = true
|
||||||
clear()
|
sectors.forEach { deref(it) }
|
||||||
|
sectors.clear()
|
||||||
fixtures.remove(this)
|
fixtures.remove(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun remove() {
|
fun remove() {
|
||||||
lock.withLock {
|
synchronized(lock) {
|
||||||
fixtures.forEach { it.clear() }
|
fixtures.forEach { it.clear0() }
|
||||||
sectors.keys.forEach { it.remove(this) }
|
sectors.keys.forEach { it.remove(this) }
|
||||||
sectors.clear()
|
sectors.clear()
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
|||||||
import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise
|
import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise
|
||||||
import kotlin.math.PI
|
import kotlin.math.PI
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
import kotlin.math.cos
|
|
||||||
import kotlin.math.sin
|
import kotlin.math.sin
|
||||||
|
|
||||||
class IslandSurfaceTerrainSelector(data: Data, parameters: TerrainSelectorParameters) : AbstractTerrainSelector<IslandSurfaceTerrainSelector.Data>(data, parameters) {
|
class IslandSurfaceTerrainSelector(data: Data, parameters: TerrainSelectorParameters) : AbstractTerrainSelector<IslandSurfaceTerrainSelector.Data>(data, parameters) {
|
||||||
@ -40,7 +39,7 @@ class IslandSurfaceTerrainSelector(data: Data, parameters: TerrainSelectorParame
|
|||||||
val islandHeight by lazy {
|
val islandHeight by lazy {
|
||||||
val perlin = AbstractPerlinNoise.of(this.data.islandHeight)
|
val perlin = AbstractPerlinNoise.of(this.data.islandHeight)
|
||||||
|
|
||||||
if (!perlin.isInitialized) {
|
if (!perlin.hasSeedSpecified) {
|
||||||
perlin.init(islandHeightSeed)
|
perlin.init(islandHeightSeed)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,7 +49,7 @@ class IslandSurfaceTerrainSelector(data: Data, parameters: TerrainSelectorParame
|
|||||||
val islandDepth by lazy {
|
val islandDepth by lazy {
|
||||||
val perlin = AbstractPerlinNoise.of(this.data.islandDepth)
|
val perlin = AbstractPerlinNoise.of(this.data.islandDepth)
|
||||||
|
|
||||||
if (!perlin.isInitialized) {
|
if (!perlin.hasSeedSpecified) {
|
||||||
perlin.init(islandDepthSeed)
|
perlin.init(islandDepthSeed)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,7 +59,7 @@ class IslandSurfaceTerrainSelector(data: Data, parameters: TerrainSelectorParame
|
|||||||
val islandDecision by lazy {
|
val islandDecision by lazy {
|
||||||
val perlin = AbstractPerlinNoise.of(this.data.islandDecision)
|
val perlin = AbstractPerlinNoise.of(this.data.islandDecision)
|
||||||
|
|
||||||
if (!perlin.isInitialized) {
|
if (!perlin.hasSeedSpecified) {
|
||||||
perlin.init(islandDecisionSeed)
|
perlin.init(islandDecisionSeed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user