-
Notifications
You must be signed in to change notification settings - Fork 463
Description
Tapir version: 1.13.13
Scala version: 3.7.4
Describe the bug
The code in InputStreamPublisher for netty has a method readNextChunkIfNeeded, which runs under a monad. Under the .map call, it will self-recurse. There is a comment here:
// Note: the effect F may be Id, in which case everything here will be synchronous and blocking
// (which technically is against the reactive streams spec).
Indeed, when you run in Identity, there is no stack protection from the underlying monad and you can very easily get a stack overflow exception for large streams. I observed it with an ~100MB input stream (content header declared up front). The stack trace:
at sttp.tapir.server.netty.internal.reactivestreams.InputStreamPublisher$InputStreamSubscription.readNextChunkIf
Needed$$anonfun$1$$anonfun$1$$anonfun$2(InputStreamPublisher.scala:77)
at scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
at scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
at sttp.monad.IdentityMonad$.map(MonadError.scala:215)
at sttp.monad.syntax$MonadErrorOps.map(MonadError.scala:71)
at sttp.tapir.server.netty.internal.reactivestreams.InputStreamPublisher$InputStreamSubscription.readNextChunkIfNeeded$$anonfun$1$$anonfun$1(InputStreamPublisher.scala:80)
at sttp.monad.MonadError.handleError(MonadError.scala:26)
at sttp.monad.MonadError.handleError$(MonadError.scala:18)
at sttp.monad.IdentityMonad$.handleError(MonadError.scala:213)
at sttp.monad.syntax$MonadErrorOps.handleError(MonadError.scala:73)
at sttp.tapir.server.netty.internal.reactivestreams.InputStreamPublisher$InputStreamSubscription.readNextChunkIfNeeded$$anonfun$1(InputStreamPublisher.scala:84)
at scala.Function0.apply$mcV$sp(Function0.scala:42)
at sttp.tapir.server.netty.internal.RunAsync$$anon$1.apply(RunAsync.scala:12)
at sttp.tapir.server.netty.internal.reactivestreams.InputStreamPublisher$InputStreamSubscription.readNextChunkIfNeeded(InputStreamPublisher.scala:84)
at sttp.tapir.server.netty.internal.reactivestreams.InputStreamPublisher$InputStreamSubscription.readNextChunkIfNeeded$$anonfun$1$$anonfun$1$$anonfun$2(InputStreamPublisher.scala:77)
Which naturally repeats many many many times (so many, I couldn't even get to the top to see the stack overflow message!)
How to reproduce?
Tricky to reproduce, seems like running on localhost it is fine, but not on my production server. Enabling the netty compression did make it also happen on localhost too.
Additional information
Seems like a security vulnerability to me! For what it's worth, the FileRangePublisher seems to be able to handle ~800MB files (at least on localhost), even though its code seems to have a suspiciously similar pattern in it.