Skip to content

vertx-mssql-client RowResultDecoder issue with decoding NBC rows with nullable columns containing null #1582

@seulbdoowle

Description

@seulbdoowle

Version

4.5 and 5.x

Context

java.lang.IndexOutOfBoundsException: index: 16, length: 1280 (expected: range(0, 85))
	at io.netty.buffer.AbstractByteBuf.checkRangeBounds(AbstractByteBuf.java:1390)
	at io.netty.buffer.AbstractByteBuf.checkIndex0(AbstractByteBuf.java:1397)
	at io.netty.buffer.AbstractUnpooledSlicedByteBuf.getCharSequence(AbstractUnpooledSlicedByteBuf.java:263)
	at io.netty.buffer.AbstractByteBuf.readCharSequence(AbstractByteBuf.java:518)
	at io.vertx.mssqlclient.impl.codec.DataType$31.decodeValue(DataType.java:838)
	at io.vertx.mssqlclient.impl.codec.DataType$28.decodeValue(DataType.java:773)
	at io.vertx.mssqlclient.impl.codec.RowResultDecoder.decodeMssqlNbcRow(RowResultDecoder.java:80)
	at io.vertx.mssqlclient.impl.codec.RowResultDecoder.decodeRow(RowResultDecoder.java:37)
	at io.vertx.sqlclient.impl.RowDecoder.handleRow(RowDecoder.java:58)
	at io.vertx.mssqlclient.impl.codec.QueryCommandBaseCodec.handleNbcRow(QueryCommandBaseCodec.java:48)
	at io.vertx.mssqlclient.impl.codec.ExtendedCursorQueryCommandCodec.handleNbcRow(ExtendedCursorQueryCommandCodec.java:173)
	at io.vertx.mssqlclient.impl.codec.MSSQLCommandCodec.decode(MSSQLCommandCodec.java:57)
	at io.vertx.mssqlclient.impl.codec.TdsMessageDecoder.decodeMessage(TdsMessageDecoder.java:75)
	at io.vertx.mssqlclient.impl.codec.TdsMessageDecoder.channelRead(TdsMessageDecoder.java:47)
	at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
	at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:346)
	at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:318)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
	at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1475)
	at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1338)
	at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1387)
	at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:530)
	at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:469)
	at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:290)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
	at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1407)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
	at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:918)
	at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
	at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562)
	at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:994)
	at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.base/java.lang.Thread.run(Thread.java:833)

From openspecs (2.2.7.15: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/c0da3cc9-4441-476b-aa99-5a518f22dc0a) it says:
The null bitmap contains one bit for each column defined in COLMETADATA.

But there may be an additional column next to the defined columns called ROWSTAT. If this column is present, it is one more column.

Possible fix
Methode decodeMssqlNbcRow Line 71:
Actual implementation:
int nullBitmapByteCount = ((len - 1) >> 3) + 1;
Should be:
int nullBitmapByteCount = ((len - (desc.hasRowStat() ? 0 : 1)) >> 3) + 1;

Steps to reproduce

  1. The number of columns in the tables must be a multiple of 8 (8, 16, 24, ...)
  2. The table must contain nullable columns
  3. There must be columns with the content NULL

Do you have a reproducer?

No response

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions