Skip to content

Commit 74dca86

Browse files
author
Brian Burkhalter
committed
8371718: (sc) Channels.new{Input,Output}Stream can allocate unbounded memory for a socket channel
Reviewed-by: alanb
1 parent 52aa7fe commit 74dca86

File tree

6 files changed

+83
-16
lines changed

6 files changed

+83
-16
lines changed

src/java.base/share/classes/sun/nio/ch/ChannelInputStream.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -109,6 +109,7 @@ public synchronized int read(byte[] bs, int off, int len)
109109
if (len == 0)
110110
return 0;
111111

112+
len = Math.min(len, Streams.MAX_BUFFER_SIZE);
112113
ByteBuffer bb = ((this.bs == bs)
113114
? this.bb
114115
: ByteBuffer.wrap(bs));

src/java.base/share/classes/sun/nio/ch/ChannelOutputStream.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -64,10 +64,15 @@ WritableByteChannel channel() {
6464
* If the channel is selectable then it must be configured blocking.
6565
*/
6666
private void writeFully(ByteBuffer bb) throws IOException {
67-
while (bb.remaining() > 0) {
67+
int pos = bb.position();
68+
int rem = bb.limit() - pos;
69+
while (rem > 0) {
70+
bb.limit(pos + Math.min(Streams.MAX_BUFFER_SIZE, rem));
6871
int n = ch.write(bb);
6972
if (n <= 0)
70-
throw new RuntimeException("no bytes written");
73+
throw new IOException("Write failed");
74+
pos += n;
75+
rem -= n;
7176
}
7277
}
7378

src/java.base/share/classes/sun/nio/ch/NioSocketImpl.java

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,6 @@
7878
public final class NioSocketImpl extends SocketImpl implements PlatformSocketImpl {
7979
private static final NativeDispatcher nd = new SocketDispatcher();
8080

81-
// The maximum number of bytes to read/write per syscall to avoid needing
82-
// a huge buffer from the temporary buffer cache
83-
private static final int MAX_BUFFER_SIZE = 128 * 1024;
84-
8581
// true if this is a SocketImpl for a ServerSocket
8682
private final boolean server;
8783

@@ -355,8 +351,8 @@ private int read(byte[] b, int off, int len) throws IOException {
355351
// emulate legacy behavior to return -1, even if socket is closed
356352
if (readEOF)
357353
return -1;
358-
// read up to MAX_BUFFER_SIZE bytes
359-
int size = Math.min(len, MAX_BUFFER_SIZE);
354+
// read up to Streams.MAX_BUFFER_SIZE bytes
355+
int size = Math.min(len, Streams.MAX_BUFFER_SIZE);
360356
int n = implRead(b, off, size, remainingNanos);
361357
if (n == -1)
362358
readEOF = true;
@@ -453,8 +449,8 @@ private void write(byte[] b, int off, int len) throws IOException {
453449
int pos = off;
454450
int end = off + len;
455451
while (pos < end) {
456-
// write up to MAX_BUFFER_SIZE bytes
457-
int size = Math.min((end - pos), MAX_BUFFER_SIZE);
452+
// write up to Streams.MAX_BUFFER_SIZE bytes
453+
int size = Math.min((end - pos), Streams.MAX_BUFFER_SIZE);
458454
int n = implWrite(b, pos, size);
459455
pos += n;
460456
}

src/java.base/share/classes/sun/nio/ch/SocketChannelImpl.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1373,6 +1373,7 @@ int blockingRead(byte[] b, int off, int len, long nanos) throws IOException {
13731373
// nothing to do
13741374
return 0;
13751375
}
1376+
len = Math.min(len, Streams.MAX_BUFFER_SIZE);
13761377

13771378
readLock.lock();
13781379
try {
@@ -1469,7 +1470,7 @@ void blockingWriteFully(byte[] b, int off, int len) throws IOException {
14691470
beginWrite(true);
14701471
configureSocketNonBlockingIfVirtualThread();
14711472
while (pos < end && isOpen()) {
1472-
int size = end - pos;
1473+
int size = Math.min(end - pos, Streams.MAX_BUFFER_SIZE);
14731474
int n = tryWrite(b, pos, size);
14741475
while (IOStatus.okayToRetry(n) && isOpen()) {
14751476
park(Net.POLLOUT);

src/java.base/share/classes/sun/nio/ch/Streams.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -33,6 +33,10 @@
3333
* Factory methods for input/output streams based on channels.
3434
*/
3535
public class Streams {
36+
// The maximum number of bytes to read/write per syscall to avoid needing
37+
// a huge buffer from the temporary buffer cache
38+
static final int MAX_BUFFER_SIZE = 128 * 1024;
39+
3640
private Streams() { }
3741

3842
/**

test/jdk/java/nio/channels/Channels/SocketChannelStreams.java

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@
2222
*/
2323

2424
/* @test
25-
* @bug 8279339
26-
* @run testng SocketChannelStreams
25+
* @bug 8279339 8371718
2726
* @summary Exercise InputStream/OutputStream returned by Channels.newXXXStream
2827
* when channel is a SocketChannel
28+
* @run testng SocketChannelStreams
2929
*/
3030

3131
import java.io.Closeable;
@@ -35,6 +35,8 @@
3535
import java.net.InetAddress;
3636
import java.net.InetSocketAddress;
3737
import java.net.Socket;
38+
import java.nio.ByteBuffer;
39+
import java.nio.channels.ByteChannel;
3840
import java.nio.channels.Channels;
3941
import java.nio.channels.IllegalBlockingModeException;
4042
import java.nio.channels.ServerSocketChannel;
@@ -51,6 +53,9 @@
5153

5254
@Test
5355
public class SocketChannelStreams {
56+
// Maximum size of internal temporary buffer
57+
private static final int MAX_BUFFER_SIZE = 128*1024;
58+
5459
private ScheduledExecutorService executor;
5560

5661
@BeforeClass()
@@ -379,6 +384,25 @@ public void testIndexOutOfBoundsException() throws Exception {
379384
});
380385
}
381386

387+
/**
388+
* Test that internal buffers have at most MAX_BUFFER_SIZE bytes remaining.
389+
*/
390+
public void testReadLimit() throws IOException {
391+
InputStream in = Channels.newInputStream(new TestChannel());
392+
byte[] b = new byte[3*MAX_BUFFER_SIZE];
393+
int n = in.read(b, 0, b.length);
394+
assertEquals(n, MAX_BUFFER_SIZE);
395+
}
396+
397+
/**
398+
* Test that internal buffers have at most MAX_BUFFER_SIZE bytes remaining.
399+
*/
400+
public void testWriteLimit() throws IOException {
401+
OutputStream out = Channels.newOutputStream(new TestChannel());
402+
byte[] b = new byte[3*MAX_BUFFER_SIZE];
403+
out.write(b, 0, b.length);
404+
}
405+
382406
// -- test infrastructure --
383407

384408
private interface ThrowingTask {
@@ -477,4 +501,40 @@ private Future<?> scheduleInterrupt(Thread t, long delay) {
477501
private Future<?> schedule(Runnable task, long delay) {
478502
return executor.schedule(task, delay, TimeUnit.MILLISECONDS);
479503
}
504+
505+
/**
506+
* ByteChannel that throws if more than 128k bytes remain
507+
* in the buffer supplied for reading or writing.
508+
*/
509+
private static class TestChannel implements ByteChannel {
510+
@Override
511+
public int read(ByteBuffer bb) throws IOException {
512+
int rem = bb.remaining();
513+
if (rem > MAX_BUFFER_SIZE) {
514+
throw new IOException("too big");
515+
}
516+
bb.position(bb.limit());
517+
return rem;
518+
}
519+
520+
@Override
521+
public int write(ByteBuffer bb) throws IOException {
522+
int rem = bb.remaining();
523+
if (rem > MAX_BUFFER_SIZE) {
524+
throw new IOException("too big");
525+
}
526+
bb.position(bb.limit());
527+
return rem;
528+
}
529+
530+
@Override
531+
public boolean isOpen() {
532+
return true;
533+
}
534+
535+
@Override
536+
public void close() {
537+
throw new UnsupportedOperationException();
538+
}
539+
}
480540
}

0 commit comments

Comments
 (0)