为何Bouncy Castle DTLS的ReceiveRecord返回值大于原缓冲区大小?
搭建.NET CoAP DTLS服务器时,Bouncy Castle的DTLS模块持续崩溃并抛出“internal error (80)”错误。调试源码后定位到问题出在Org.BouncyCastle.Tls.DtlsRecordLayer的Receive方法中:
internal int Receive(Span<byte> buffer, int waitMillis, DtlsRecordCallback recordCallback) { long currentTimeMillis = DateTimeUtilities.CurrentUnixMs(); Timeout timeout = Timeout.ForWaitMillis(waitMillis, currentTimeMillis); byte[] record = null; while (waitMillis >= 0) { ... // wait millis timeout checks int receiveLimit = m_transport.GetReceiveLimit(); if (null == record || record.Length < receiveLimit) { record = new byte[receiveLimit]; } int received = ReceiveRecord(record, 0, receiveLimit, waitMillis); int processed = ProcessRecord(received, record, buffer, recordCallback); if (processed >= 0) return processed; currentTimeMillis = DateTimeUtilities.CurrentUnixMs(); waitMillis = Timeout.GetWaitMillis(timeout, currentTimeMillis); } return -1; }
核心问题是:负责解密记录并写入缓冲区的ProcessRecord方法,接收到的ReceiveRecord返回字节数大于传入的buffer缓冲区大小,导致写入溢出触发错误。对应的ReceiveRecord方法代码如下:
private int ReceiveRecord(byte[] buf, int off, int len, int waitMillis) { if (m_recordQueue.Available > 0) return ReceivePendingRecord(buf, off, len); int received = ReceiveDatagram(buf, off, len, waitMillis); if (received >= RecordHeaderLength) { this.m_inConnection = true; ... // Epoch checks, to make sure what to read int recordHeaderLength = recordEpoch.RecordHeaderLengthRead; if (received >= recordHeaderLength) { int fragmentLength = TlsUtilities.ReadUint16(buf, off + recordHeaderLength - 2); int recordLength = recordHeaderLength + fragmentLength; if (received > recordLength) { m_recordQueue.AddData(buf, off + recordLength, received - recordLength); received = recordLength; } } } return received; }
补充排查信息
最初怀疑是分片错误,但进一步确认ReceivePendingRecord仅在握手阶段触发,后续应用数据包不会走该分支。推测触发异常的记录并未分片,但通过Wireshark观察到崩溃总是发生在数据包长度异常的场景下,仍怀疑与数据长度处理逻辑相关。
问题分析与修复思路
缓冲区长度不匹配
Receive方法中按m_transport.GetReceiveLimit()创建record数组,但未校验该长度是否超过传入的buffer大小。如果receiveLimit大于buffer.Length,ReceiveRecord返回的有效字节数可能超出buffer的容纳范围,导致ProcessRecord写入时溢出。不完整记录未处理
在ReceiveRecord中,仅当收到的字节数received大于解析出的recordLength时才截断并将多余数据加入队列,但未处理received < recordLength的情况(即收到不完整的DTLS记录)。这种场景下直接返回received,会导致ProcessRecord处理不完整数据,触发解密或解析错误。修复方向
- 校验缓冲区大小:在
Receive方法调用ProcessRecord前,增加received与buffer.Length的校验,若超出则抛出明确错误或调整缓冲区:// 在调用ProcessRecord前添加 if (received > buffer.Length) { throw new TlsFatalAlert(AlertDescription.internal_error); // 或根据业务场景选择丢弃当前记录、扩容缓冲区等逻辑 } - 完善不完整记录处理:在
ReceiveRecord中,当received < recordLength时,将当前数据加入m_recordQueue,返回-1等待后续数据:// 在计算recordLength后添加 if (received < recordLength) { m_recordQueue.AddData(buf, off, received); return -1; // 表示需要继续接收数据以补全记录 } - 检查Transport实现:确认
m_transport.GetReceiveLimit()的返回值是否合理,是否与应用层传入的buffer大小匹配,避免创建过大的record数组。
内容的提问来源于stack exchange,提问作者ds600

