facebookarchive / nifty

Thrift on Netty

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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.