JDK-8232817 : DatagramPacket.setLength restricts usable portion of the packet
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.net
  • Affected Version: 14
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • Submitted: 2019-10-22
  • Updated: 2019-12-19
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbdUnresolved
Related Reports
Relates :  
Relates :  
Description
The `receive` method of the datagram channel socket adapter inadvertently
restricts the usable portion of the DatagramPacket's byte array. The most
obvious case is where a DatagramPacket is reused by several receive invocations,
where subsequently received packets contain larger data payloads. The maximum
usable portion of the packet's byte array is restricted to the smallest received
payload.

The following jshell snippet demonstrates the issue. First the snippet operates
with a java.net.DatagramSocket, the second with a socket adapter.

jshell> var ds1 = new DatagramSocket()
ds1 ==> java.net.DatagramSocket@5ce65a89

jshell> var ds2 = new DatagramSocket()
ds2 ==> java.net.DatagramSocket@1a86f2f1

jshell> ds2.getLocalPort()
$6 ==> 65145

jshell> ds1.send(new DatagramPacket("hello".getBytes(), 0, 5, new InetSocketAddress("localhost", 65145)))

jshell> var packet = new DatagramPacket(new byte[100], 0, 100)
packet ==> java.net.DatagramPacket@1de0aca6      <<<< This packet will be reused when receiving

jshell> ds2.receive(packet)

jshell> new String(packet.getData(), packet.getOffset(), packet.getLength())
$10 ==> "hello"

jshell> ds1.send(new DatagramPacket("Bob Cratchit".getBytes(), 0, 12, new InetSocketAddress("localhost", 65145)))

jshell> ds2.receive(packet)

jshell> new String(packet.getData(), packet.getOffset(), packet.getLength())
$13 ==> "Bob Cratchit"

---

jshell> var ds1 = new DatagramSocket()
ds1 ==> java.net.DatagramSocket@5ce65a89

jshell> var ds2 = DatagramChannel.open().socket()
ds2 ==> sun.nio.ch.DatagramSocketAdaptor@443b7951

jshell> ds2.getLocalPort()
$6 ==> 0

jshell> var ds2 = DatagramChannel.open().bind(null).socket()
ds2 ==> sun.nio.ch.DatagramSocketAdaptor@69663380

jshell> ds2.getLocalPort()
$8 ==> 58305

jshell> ds1.send(new DatagramPacket("hello".getBytes(), 0, 5, new InetSocketAddress("localhost", 58305)))

jshell> var packet = new DatagramPacket(new byte[100], 0, 100)
packet ==> java.net.DatagramPacket@736e9adb

jshell> ds2.receive(packet)

jshell> new String(packet.getData(), packet.getOffset(), packet.getLength())
$12 ==> "hello"

jshell> ds1.send(new DatagramPacket("Bob Cratchit".getBytes(), 0, 12, new InetSocketAddress("localhost", 58305)))

jshell> ds2.receive(packet)

jshell> new String(packet.getData(), packet.getOffset(), packet.getLength())
$15 ==> "Bob C"

Comments
Alternatives could be to add methods that operate directly with ByteBuffers: DatagramPacket.put(ByteBuffer buffer) could set the packet length without touching bufLength, and could have the logic to copy the bytes or not depending on whether the buffer share the same underlying heap array. The catch is that you'd still need bufLength to properly position the ByteBuffer limit before calling receive. I haven't been able to find any clean API to do that. It seems difficult to add a DatagramPacket.get(ByteBuffer buffer) that would do the right thing in an intuitive way.
24-10-2019

The bug is that setLength is doing the wrong thing. It should check the input against bufLength and then set the "length" field. It should not set the "bufLength" field that is the available space, not the used space. If we can't fix the setLength method then maybe it should be deprecated and replaced with an alternative method that just sets the length field. We also need a method to return the available space (bufLength) as it is impossible to populate a DatagramPacket without knowing the space available.
24-10-2019

The scope of this particular issue is limited to code that wants to receive data into a DatagramPacket's buffer. I would suggest that the set of code that would want to do this is very small. To the best of my knowledge this issue has not been reported before, either in DatagramChannel's adapter or more generally. Given this, a fix that leaves the Java SE API unchanged would 1) resolve the problem at-hand ( with DatagramChannel's adapter ), 2) affect compatibility minimally, and 3) reduce the overall energy and cost of this issue. Such a fix would not preclude a future amendment to the Java SE API, if it became clear that that was needed.
24-10-2019

I suspect this will require a re-visiting DatagramPacket::setLength as it should not be setting bufLength (bufLength should only be set by the constructors or the setData methods that provide the underlying byte array). Looks like this issue has existed since early JDK releases so needs analysis to see what the impact of fixing it might be (it may have no impact).
23-10-2019

Shared secrets seems the safer options unless we want to expose bufLength - but that would be awkward.
23-10-2019

One possible solution is to use a shared secret to access and set the DatagramPacket's internal bufLength field. Similar to https://hg.openjdk.java.net/jdk/sandbox/rev/2b1e684c3ce6
22-10-2019