Can you limit the size of data that can be deserialized in Ktor?

468 Views Asked by At

In Ktor, is there a way to limit size of data that can be attempted to be deserialized from JSON? Context is defending against denial-of-service attacks where a malicious client might try and send a huge payload to cause out-of-memory issues

I've used a similar capability in Play before (https://www.playframework.com/documentation/2.8.x/ScalaBodyParsers#Max-content-length). You can set the maximum globally, and also specifically override on individual routes.

1

There are 1 best solutions below

0
Aleksei Tirman On

Unfortunately, there is no built-in functionality in Ktor to limit the size of a body for deserialization. Still, you can write an interceptor for the ApplicationReceivePipeline pipeline to replace the ByteReadChannel (body bytes are read from this object) with another channel. The latter's implementation will count a total number of bytes read and throw an exception if that count exceeds some limit. Here is an example:

import io.ktor.application.*
import io.ktor.features.*
import io.ktor.request.*
import io.ktor.routing.*
import io.ktor.serialization.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.util.pipeline.*
import io.ktor.utils.io.*
import kotlinx.coroutines.GlobalScope

fun main() {
    embeddedServer(Netty, port = 2222) {
        val beforeSerialization = PipelinePhase("")
        receivePipeline.insertPhaseBefore(ApplicationReceivePipeline.Transform, beforeSerialization)

        receivePipeline.intercept(beforeSerialization) { receive ->
            if (subject.value is ByteReadChannel) {
                val readChannel = subject.value as ByteReadChannel
                val limit = 1024
                var totalBytes = 0
                val channel = GlobalScope.writer {
                    val byteArray = ByteArray(4088)
                    val bytesRead = readChannel.readAvailable(byteArray)
                    totalBytes += bytesRead

                    if (totalBytes > limit) {
                        throw IllegalStateException("Limit ($limit) for receiving exceeded. Read $totalBytes.")
                    }

                    channel.writeFully(byteArray, 0, bytesRead)
                }.channel

                proceedWith(ApplicationReceiveRequest(receive.typeInfo, channel, reusableValue = true))
            }
        }

        install(ContentNegotiation) {
            json()
        }
        routing {
            post("/") {
                val r = call.receive<MyData>()
                println(r)
            }
        }
    }.start(wait = true)
}

@kotlinx.serialization.Serializable
data class MyData(val x: String)