buffer overflow? `Maximum frame size of -2147483648 exceeded `
emaxerrno opened this issue · comments
I'm sending a thrift struct of < 300MB.
Versions:
libraryDependencies ++= Seq(
"com.facebook.nifty" % "nifty-core" % "0.18.0",
"com.facebook.nifty" % "nifty-client" % "0.18.0",
"com.facebook.swift" % "swift-service" % "0.18.0",
"com.facebook.swift" % "swift-codec" % "0.18.0",
"com.facebook.swift" % "swift-annotations" % "0.18.0"
)
Using java8 VM, oracle JDK.
Thrift Server Setup:
import io.airlift.units.DataSize.Unit.GIGABYTE;
import io.airlift.units.DataSize
val serverConfig = new ThriftServerConfig()
.setBindAddress(config.schedulerEndpoint.ip)
.setPort(config.schedulerEndpoint.port)
.setMaxFrameSize(new DataSize(2, GIGABYTE)) // the default is 64MB
val server = new ThriftServer(processor, serverConfig)
Runtime.getRuntime.addShutdownHook(new Thread() {
override def run() = {
Try(server.close) match {
case Success(p) => logger.info(s"Successfully stopped server")
case Failure(exn) => logger.error(s"Error stoping service $exn")
}
}
});
server.start
The overflow?
org.jboss.netty.handler.codec.frame.TooLongFrameException: Maximum frame size of -2147483648 exceeded
at com.facebook.nifty.codec.DefaultThriftFrameDecoder.tryDecodeFramedMessage(DefaultThriftFrameDecoder.java:102)
at com.facebook.nifty.codec.DefaultThriftFrameDecoder.decode(DefaultThriftFrameDecoder.java:68)
at com.facebook.nifty.codec.DefaultThriftFrameDecoder.decode(DefaultThriftFrameDecoder.java:33)
at org.jboss.netty.handler.codec.frame.FrameDecoder.callDecode(FrameDecoder.java:425)
at org.jboss.netty.handler.codec.frame.FrameDecoder.messageReceived(FrameDecoder.java:303)
at org.jboss.netty.channel.SimpleChannelUpstreamHandler.handleUpstream(SimpleChannelUpstreamHandler.java:70)
at com.facebook.nifty.codec.DefaultThriftFrameCodec.handleUpstream(DefaultThriftFrameCodec.java:42)
at org.jboss.netty.channel.DefaultChannelPipeline.sendUpstream(DefaultChannelPipeline.java:564)
at org.jboss.netty.channel.DefaultChannelPipeline$DefaultChannelHandlerContext.sendUpstream(DefaultChannelPipeline.java:791)
at com.facebook.nifty.core.ChannelStatistics.handleUpstream(ChannelStatistics.java:79)
at org.jboss.netty.channel.DefaultChannelPipeline.sendUpstream(DefaultChannelPipeline.java:564)
at org.jboss.netty.channel.DefaultChannelPipeline$DefaultChannelHandlerContext.sendUpstream(DefaultChannelPipeline.java:791)
at org.jboss.netty.channel.SimpleChannelUpstreamHandler.messageReceived(SimpleChannelUpstreamHandler.java:124)
at org.jboss.netty.channel.SimpleChannelUpstreamHandler.handleUpstream(SimpleChannelUpstreamHandler.java:70)
at org.jboss.netty.channel.DefaultChannelPipeline.sendUpstream(DefaultChannelPipeline.java:564)
at org.jboss.netty.channel.DefaultChannelPipeline$DefaultChannelHandlerContext.sendUpstream(DefaultChannelPipeline.java:791)
at org.jboss.netty.channel.SimpleChannelUpstreamHandler.messageReceived(SimpleChannelUpstreamHandler.java:124)
at org.jboss.netty.channel.SimpleChannelUpstreamHandler.handleUpstream(SimpleChannelUpstreamHandler.java:70)
at org.jboss.netty.channel.DefaultChannelPipeline.sendUpstream(DefaultChannelPipeline.java:564)
at org.jboss.netty.channel.DefaultChannelPipeline.sendUpstream(DefaultChannelPipeline.java:559)
Things I've tried:
- Upgraded everything from 0.15.1 to 0.18.0 nifty & swfit.
- Regenerated the python structs (client) & swift (with the facebook swift generator)
- Problem only visible for big structs > 90MB
- I also tried overriding my own thrift frme codec factory like this:
import com.facebook.nifty.codec.{
ThriftFrameCodecFactory,
DefaultThriftFrameCodec}
import org.apache.thrift.protocol.TProtocolFactory
import org.jboss.netty.channel.ChannelHandler
val frameFactory = new ThriftFrameCodecFactory(){
override def create(maxFrameSize: Int,
defaultProtocolFactory: TProtocolFactory): ChannelHandler = {
import com.google.common.base.Verify.verify
verify(maxFrameSize > 0, s"Frame size ($maxFrameSize) is negative!")
new DefaultThriftFrameCodec(size, defaultProtocolFactory)
}
}
import com.facebook.nifty.core.NiftyTimer
import com.google.common.collect.ImmutableMap
import org.apache.thrift.protocol.TBinaryProtocol
import com.facebook.nifty.codec.ThriftFrameCodecFactory
import com.facebook.nifty.duplex.TDuplexProtocolFactory
val server = new ThriftServer(processor,
serverConfig,
new NiftyTimer("thrift"),
ImmutableMap.of("framed",
frameFactory.asInstanceOf[ThriftFrameCodecFactory]),
ImmutableMap.of("binary",
TDuplexProtocolFactory.fromSingleFactory(new TBinaryProtocol.Factory())),
ThriftServer.DEFAULT_WORKER_EXECUTORS,
ThriftServer.DEFAULT_SECURITY_FACTORY)
However, this fails trying to create teh ChannelHandler
verify(maxFrameSize > 0, s"Frame size ($maxFrameSize) is negative!")
I'm using the thrift 0.9.2 compiler for python, the swift 0.18.0 compiler for java.
Any tips, suggestions would be greatly appreciated. Note that this doesn't happen between a C++ service and the python client.
Thanks in advance!
- Alex
Forgot to mention that I am also using the Binary protocol & TFramed transport.
I also tried the client (python) with thrift 0.9.2 and 0.9.3
I've also, preloaded the netty version
"io.netty" % "netty-all" % "4.0.34.Final",
to my dependencies to make sure this happens with both libs.
my python client code looks like this
def tproto(ip, port):
socket = TSocket.TSocket(ip, port)
transport = TTransport.TFramedTransport(socket)
protocol = TBinaryProtocol.TBinaryProtocol(transport)
return (protocol, transport)
def get_sched_service_client(ip, port):
(protocol, transport) = tproto(ip, port)
client = MyClass.Client(protocol)
transport.open()
return client
FYI: I fixed the problem of negative size frame by doing this:
val frameFactory = new ThriftFrameCodecFactory(){
override def create(maxFrameSize: Int,
defaultProtocolFactory: TProtocolFactory): ChannelHandler = {
// TODO(agallego): This is A HACK!
// https://github.com/facebook/nifty/issues/144
// import com.google.common.base.Verify.verify
// verify(maxFrameSize > 0, s"Frame size ($maxFrameSize) is negative!")
val twogb:Int = 1 << 30
new DefaultThriftFrameCodec(twogb, defaultProtocolFactory)
}
}
perhaps we can use this?
ThriftServerDef serverDef = thriftServerDefBuilder.limitFrameSizeTo(Integer.MAX_VALUE).build();
Bug here is that we should limit max frame size to under 2GB. Maybe should even be limited to just under 1GB I think. At exactly 2GB, you have bit 31 set, which Java treats as negative i32, and anyway thrift doesn't want you to set that bit because a server that understands different incoming protocols from different clients uses it to detect unframed messages.
As for 1GB, its for a similar reason, some Thrift servers also want to leave that space (with either of top two bits set) to detect HTTP incoming messages.
So yes it is a bug, but the bug is we should not allow a large size like that. And your workaround is perfect.